From ea3a39819d827ce0f10d3e24e2ba92523420f250 Mon Sep 17 00:00:00 2001 From: Saurabh Ahuja Date: Fri, 10 Oct 2025 13:26:46 +0530 Subject: [PATCH 1/2] GitHub main operator2.0 (#1) Oracle Database Operator Release 2.0 --- .gitignore | 3 +- .gitlab-ci.yml | 38 + Dockerfile | 37 +- Makefile | 33 +- PROJECT | 28 +- README.md | 197 +- THIRD_PARTY_LICENSES.txt | 40 +- .../v1alpha1/adbfamily_common_spec.go | 6 +- .../autonomouscontainerdatabase_webhook.go | 34 +- .../v1alpha1/autonomousdatabase_conversion.go | 8 +- .../v1alpha1/autonomousdatabase_types.go | 4 +- .../v1alpha1/autonomousdatabase_webhook.go | 73 +- .../autonomousdatabasebackup_webhook.go | 71 +- .../autonomousdatabasebackup_webhook_test.go | 19 +- .../autonomousdatabaserestore_types.go | 2 +- .../autonomousdatabaserestore_webhook.go | 56 +- .../autonomousdatabaserestore_webhook_test.go | 10 +- apis/database/v1alpha1/cdb_types.go | 190 - apis/database/v1alpha1/cdb_webhook.go | 224 - .../v1alpha1/dataguardbroker_webhook.go | 86 +- apis/database/v1alpha1/dbcssystem_webhook.go | 17 +- .../v1alpha1/oraclerestdataservice_webhook.go | 87 +- apis/database/v1alpha1/pdb_types.go | 236 - apis/database/v1alpha1/pdb_webhook.go | 369 - .../v1alpha1/shardingdatabase_webhook.go | 260 - .../singleinstancedatabase_webhook.go | 346 +- .../v1alpha1/zz_generated.deepcopy.go | 606 +- apis/database/v4/adbfamily_common_spec.go | 6 +- .../v4/autonomouscontainerdatabase_webhook.go | 34 +- apis/database/v4/autonomousdatabase_types.go | 4 +- .../database/v4/autonomousdatabase_webhook.go | 74 +- .../v4/autonomousdatabasebackup_types.go | 6 +- .../v4/autonomousdatabasebackup_webhook.go | 70 +- .../v4/autonomousdatabaserestore_types.go | 4 +- .../v4/autonomousdatabaserestore_webhook.go | 57 +- apis/database/v4/cdb_types.go | 191 - apis/database/v4/cdb_webhook.go | 224 - apis/database/v4/dataguardbroker_types.go | 1 + apis/database/v4/dbcssystem_types.go | 162 +- apis/database/v4/dbcssystem_webhook.go | 138 +- apis/database/v4/lrest_types.go | 37 +- apis/database/v4/lrest_webhook.go | 52 +- apis/database/v4/lrpdb_types.go | 79 +- apis/database/v4/lrpdb_webhook.go | 144 +- apis/database/v4/oraclerestart_types.go | 381 + apis/database/v4/oraclerestart_webhook.go | 1194 ++ .../v4/oraclerestdataservice_types.go | 1 + apis/database/v4/ordssrvs_types.go | 66 +- apis/database/v4/pdb_types.go | 237 - apis/database/v4/pdb_webhook.go | 369 - apis/database/v4/shardingdatabase_types.go | 70 +- apis/database/v4/shardingdatabase_webhook.go | 198 +- .../v4/singleinstancedatabase_types.go | 5 +- apis/database/v4/zz_generated.deepcopy.go | 1599 +- .../v1/databaseobserver_types.go | 178 +- .../v1/databaseobserver_webhook.go | 112 +- .../observability/v1/zz_generated.deepcopy.go | 422 +- .../v1alpha1/databaseobserver_types.go | 178 +- .../v1alpha1/databaseobserver_webhook.go | 111 +- .../v1alpha1/zz_generated.deepcopy.go | 422 +- .../v4/databaseobserver_types.go | 178 +- .../v4/databaseobserver_webhook.go | 112 +- .../observability/v4/zz_generated.deepcopy.go | 422 +- bundle.Dockerfile | 14 +- ...acle.com_autonomouscontainerdatabases.yaml | 111 +- ....oracle.com_autonomousdatabasebackups.yaml | 132 +- ...oracle.com_autonomousdatabaserestores.yaml | 132 +- ...tabase.oracle.com_autonomousdatabases.yaml | 704 + .../manifests}/database.oracle.com_cdbs.yaml | 9 +- .../database.oracle.com_dataguardbrokers.yaml | 217 + .../database.oracle.com_dbcssystems.yaml | 418 +- .../manifests/database.oracle.com_lrests.yaml | 127 +- .../manifests/database.oracle.com_lrpdbs.yaml | 223 +- ...ase.oracle.com_oraclerestdataservices.yaml | 213 +- .../database.oracle.com_ordssrvs.yaml | 495 + .../manifests}/database.oracle.com_pdbs.yaml | 9 +- ...database.oracle.com_shardingdatabases.yaml | 1115 ++ ...se.oracle.com_singleinstancedatabases.yaml | 717 + ...vability.oracle.com_databaseobservers.yaml | 6995 +++++++ ...er-manager-metrics-service_v1_service.yaml | 16 + ...c.authorization.k8s.io_v1_clusterrole.yaml | 10 + ...e-operator-webhook-service_v1_service.yaml | 14 + ...tabase-operator.clusterserviceversion.yaml | 1442 ++ bundle/metadata/annotations.yaml | 10 + commons/adb_family/utils.go | 2 +- commons/dbcssystem/dbcs_reconciler.go | 1222 +- commons/dbcssystem/dcommon.go | 34 +- commons/k8s/create.go | 3 + commons/k8s/utils.go | 8 + commons/multitenant/lrest/common.go | 176 +- commons/observability/constants.go | 90 +- commons/observability/utils.go | 540 +- commons/oci/containerdatabase.go | 2 +- commons/oci/database.go | 24 + commons/oci/wallet.go | 6 +- commons/oraclerestart/exec.go | 112 + commons/oraclerestart/oraclerestartcommon.go | 1397 ++ .../oraclerestart/oraclerestartconstants.go | 159 + commons/oraclerestart/oraclerestartprov.go | 1154 ++ commons/oraclerestart/oraclerestartstatus.go | 340 + commons/oraclerestart/utils/utils.go | 189 + commons/sharding/catalog.go | 38 + commons/sharding/gsm.go | 23 + commons/sharding/scommon.go | 54 +- commons/sharding/shard.go | 44 + config/certmanager/certificate-metrics.yaml | 20 + config/certmanager/certificate-webhook.yaml | 20 + config/certmanager/certificate.yaml | 6 +- config/certmanager/issuer.yaml | 13 + ...acle.com_autonomouscontainerdatabases.yaml | 2 +- ....oracle.com_autonomousdatabasebackups.yaml | 14 +- ...oracle.com_autonomousdatabaserestores.yaml | 18 +- ...tabase.oracle.com_autonomousdatabases.yaml | 18 +- .../database.oracle.com_dataguardbrokers.yaml | 5 +- .../database.oracle.com_dbcssystems.yaml | 167 +- .../crd/bases/database.oracle.com_lrests.yaml | 33 +- .../crd/bases/database.oracle.com_lrpdbs.yaml | 66 +- ...database.oracle.com_oraclerestartnews.yaml | 1468 ++ .../database.oracle.com_oraclerestarts.yaml | 1431 ++ ...base.oracle.com_oraclerestartservices.yaml | 1468 ++ ...ase.oracle.com_oraclerestdataservices.yaml | 4 +- .../bases/database.oracle.com_ordssrvs.yaml | 77 +- ...database.oracle.com_shardingdatabases.yaml | 74 +- ...se.oracle.com_singleinstancedatabases.yaml | 5 +- ...vability.oracle.com_databaseobservers.yaml | 11030 +++++----- config/crd/kustomization.yaml | 51 +- ...ction_in_autonomouscontainerdatabases.yaml | 2 +- ...njection_in_autonomousdatabasebackups.yaml | 2 +- ...jection_in_autonomousdatabaserestores.yaml | 2 +- .../cainjection_in_autonomousdatabases.yaml | 2 +- config/crd/patches/cainjection_in_cdbs.yaml | 2 +- .../patches/cainjection_in_dbcssystems.yaml | 2 +- config/crd/patches/cainjection_in_pdbs.yaml | 2 +- .../cainjection_in_shardingdatabases.yaml | 2 +- ...ainjection_in_singleinstancedatabases.yaml | 2 +- ...tabase.oracle.com_autonomousdatabases.yaml | 324 - .../database.oracle.com_dataguardbrokers.yaml | 134 - ...database.oracle.com_shardingdatabases.yaml | 688 - ...se.oracle.com_singleinstancedatabases.yaml | 421 - config/default/kustomization.yaml | 138 +- config/default/webhookcainjection_patch.yaml | 6 +- config/manager/kustomization.yaml | 4 +- ...tabase-operator.clusterserviceversion.yaml | 147 +- .../network-policy/allow-webhook-traffic.yaml | 27 + config/rbac/cdb_editor_role.yaml | 24 - config/rbac/cdb_viewer_role.yaml | 20 - .../database_oraclerestart_admin_role.yaml | 27 + .../database_oraclerestart_editor_role.yaml | 33 + .../database_oraclerestart_viewer_role.yaml | 29 + config/rbac/kustomization.yaml | 6 + config/rbac/pdb_editor_role.yaml | 24 - config/rbac/pdb_viewer_role.yaml | 20 - config/rbac/role.yaml | 22 +- .../acd/autonomouscontainerdatabase_bind.yaml | 2 +- ...scontainerdatabase_change_displayname.yaml | 2 +- .../autonomouscontainerdatabase_create.yaml | 2 +- ...mouscontainerdatabase_delete_resource.yaml | 2 +- ...uscontainerdatabase_restart_terminate.yaml | 2 +- .../adb/autonomousdatabase_backup.yaml | 8 +- .../samples/adb/autonomousdatabase_bind.yaml | 2 +- .../samples/adb/autonomousdatabase_clone.yaml | 5 +- .../adb/autonomousdatabase_create.yaml | 58 +- .../autonomousdatabase_delete_resource.yaml | 2 +- ...> autonomousdatabase_download_wallet.yaml} | 2 +- .../autonomousdatabase_manual_failover.yaml | 17 + .../adb/autonomousdatabase_rename.yaml | 2 +- .../adb/autonomousdatabase_restore.yaml | 10 +- .../samples/adb/autonomousdatabase_scale.yaml | 6 +- ...tonomousdatabase_stop_start_terminate.yaml | 4 +- .../adb/autonomousdatabase_switchover.yaml | 17 + ...onomousdatabase_update_admin_password.yaml | 2 +- .../adb/autonomousdatabase_update_mtls.yaml | 4 +- ...onomousdatabase_update_network_access.yaml | 22 +- config/samples/database_v4_oraclerestart.yaml | 9 + config/samples/kustomization.yaml | 56 +- config/samples/multitenant/cdb.yaml | 43 - config/samples/multitenant/cdb_secret.yaml | 17 - config/samples/multitenant/pdb_clone.yaml | 27 - config/samples/multitenant/pdb_create.yaml | 27 - config/samples/multitenant/pdb_delete.yaml | 16 - config/samples/multitenant/pdb_modify.yaml | 22 - config/samples/multitenant/pdb_plug.yaml | 19 - config/samples/multitenant/pdb_secret.yaml | 13 - config/samples/multitenant/pdb_unplug.yaml | 27 - .../observability/databaseobserver.yaml | 31 +- .../databaseobserver_custom_config.yaml | 28 - .../databaseobserver_minimal.yaml | 22 - .../observability/databaseobserver_vault.yaml | 25 - .../observability/v1/databaseobserver.yaml | 75 +- ...databaseobserver_customization_fields.yaml | 58 +- .../v1/databaseobserver_logs_promtail.yaml | 68 +- .../v1alpha1/databaseobserver.yaml | 71 +- .../databaseobserver_custom_config.yaml | 35 +- .../databaseobserver_logs_promtail.yaml | 79 +- .../v1alpha1/databaseobserver_minimal.yaml | 15 +- .../v1alpha1/databaseobserver_vault.yaml | 30 - .../v1alpha1/databaseobserver_vault_oci.yaml | 29 + .../observability/v4/databaseobserver.yaml | 69 +- .../v4/databaseobserver_custom_config.yaml | 30 +- ...databaseobserver_exporter_config_file.yaml | 42 + .../v4/databaseobserver_logs_promtail.yaml | 70 +- .../v4/databaseobserver_minimal.yaml | 15 +- .../v4/databaseobserver_vault.yaml | 39 - .../v4/databaseobserver_vault_azure.yaml | 24 + .../v4/databaseobserver_vault_oci.yaml | 29 + .../samples/sidb/singleinstancedatabase.yaml | 3 + config/scorecard/kustomization.yaml | 9 +- config/webhook/manifests.yaml | 229 +- .../database/autonomousdatabase_controller.go | 38 +- .../autonomousdatabasebackup_controller.go | 4 +- .../autonomousdatabaserestore_controller.go | 4 +- controllers/database/cdb_controller.go | 1093 - controllers/database/dbcssystem_controller.go | 1251 +- controllers/database/lrest_controller.go | 1020 +- controllers/database/lrpdb_controller.go | 3843 ++-- .../database/oraclerestart_controller.go | 3529 ++++ controllers/database/ordssrvs_controller.go | 458 +- controllers/database/ordssrvs_ordsconfig.go | 30 +- controllers/database/pdb_controller.go | 1631 -- .../database/shardingdatabase_controller.go | 224 +- .../singleinstancedatabase_controller.go | 18 +- controllers/dataguard/dataguard_utils.go | 2 + .../dataguard/dataguardbroker_controller.go | 2 +- .../databaseobserver_controller.go | 79 +- .../databaseobserver_resource.go | 8 +- docs/adb/ACD.md | 6 +- docs/adb/ADB_LONG_TERM_BACKUP.md | 10 +- docs/adb/ADB_RESTORE.md | 16 +- docs/adb/NETWORK_ACCESS_OPTIONS.md | 19 +- docs/adb/README.md | 126 +- .../orestart/custom-kubeletconfig.yaml | 13 + docs/config/samples/orestart/custom-scc.yaml | 50 + docs/dbcs/README.md | 31 +- .../backup_database_sample_output.log | 100 + docs/dbcs/provisioning/backup_of_database.md | 35 + .../dbcs/provisioning/backup_of_database.yaml | 11 + .../bind_to_existing_dbcs_system.md | 12 +- .../bind_to_existing_dbcs_system.yaml | 19 +- ..._dbcs_system_from_backup_sample_output.log | 66 +- ...bcs_system_from_database_sample_output.log | 66 +- .../clone_dbcs_system_sample_output.log | 63 + .../provisioning/clone_from_backup_dbcs.md | 12 +- docs/dbcs/provisioning/clone_from_database.md | 12 +- .../provisioning/clone_from_existing_dbcs.md | 16 +- .../dbcs/provisioning/create_dbcs_with_kms.md | 25 +- .../provisioning/dataguard_in_database.yaml | 20 + .../dataguard_in_database_sample_output.log | 226 + .../provisioning/dataguard_to_database.md | 33 + docs/dbcs/provisioning/delete_pdb.md | 20 +- .../disable_dataguard_in_database.yaml | 12 + ...le_dataguard_in_database_sample_output.log | 141 + .../disable_dataguard_to_database.md | 40 + .../disabled_dataguard_to_database.md | 0 docs/dbcs/provisioning/patch_dbcs_system.yaml | 29 + .../patch_dbcs_system_sample_output.log | 0 docs/dbcs/provisioning/patching_database.md | 45 + .../restore_database_sample_output.log | 22 + docs/dbcs/provisioning/restore_of_database.md | 34 + .../provisioning/restore_of_database.yaml | 11 + .../scale_down_dbcs_system_shape.md | 14 +- .../scale_up_dbcs_system_shape.md | 14 +- .../scale_up_dbcs_system_shape.yaml | 12 +- docs/dbcs/provisioning/scale_up_storage.md | 14 +- .../provisioning/terminate_dbcs_system.md | 20 +- docs/dbcs/provisioning/update_license.md | 18 +- .../provisioning/upgrade_dbcs_system.yaml | 32 + .../upgrade_dbcs_system_sample_output.log | 2 + docs/dbcs/provisioning/upgrading_database.md | 45 + docs/multitenant/NamespaceSeg.md | 14 - docs/multitenant/README.md | 803 +- .../images/Generalschema2.jpg | Bin docs/multitenant/images/KubectlGetSchema.jpg | Bin 0 -> 206768 bytes docs/multitenant/images/KubectlGetSchema2.jpg | Bin 0 -> 187365 bytes .../old_testcase_wf.jpg} | Bin docs/multitenant/images/plsqlmap.jpg | Bin 0 -> 94000 bytes docs/multitenant/images/usecaseschema.jpg | Bin 0 -> 124714 bytes docs/multitenant/lrest-based/README.md | 501 - .../multitenant/lrest-based/usecase/README.md | 139 - .../lrest-based/usecase/config-map-pdb.yaml | 11 - docs/multitenant/lrest-based/usecase/makefile | 911 - docs/multitenant/ords-based/NamespaceSeg.md | 14 - docs/multitenant/ords-based/README.md | 411 - .../ords-based/images/K8S_NAMESPACE_SEG.png | Bin 269014 -> 0 bytes .../ords-based/images/K8S_SECURE1.png | Bin 129940 -> 0 bytes .../ords-based/images/K8S_SECURE2.png | Bin 130742 -> 0 bytes .../ords-based/images/K8S_SECURE3.png | Bin 130498 -> 0 bytes .../ords-based/images/K8S_SECURE4.png | Bin 132342 -> 0 bytes .../ords-based/images/makerunall.png | Bin 211874 -> 0 bytes .../ords-based/images/makesecrets_1_1.png | Bin 117953 -> 0 bytes .../multitenant/ords-based/openssl_schema.jpg | Bin 54221 -> 0 bytes .../example_setup_using_oci_oke_cluster.md | 38 - .../multinamespace/cdb_create.yaml | 48 - .../multinamespace/pdb_clone.yaml | 54 - .../multinamespace/pdb_close.yaml | 48 - .../multinamespace/pdb_create.yaml | 50 - .../multinamespace/pdb_delete.yaml | 39 - .../provisioning/multinamespace/pdb_open.yaml | 47 - .../provisioning/multinamespace/pdb_plug.yaml | 51 - .../multinamespace/pdb_unplug.yaml | 43 - .../ords-based/provisioning/ords_image.md | 81 - .../provisioning/quickOKEcreation.md | 136 - .../singlenamespace/cdb_create.yaml | 49 - .../singlenamespace/cdb_secret.yaml | 17 - .../singlenamespace/pdb_clone.yaml | 60 - .../singlenamespace/pdb_close.yaml | 48 - .../singlenamespace/pdb_create.yaml | 51 - .../singlenamespace/pdb_delete.yaml | 39 - .../singlenamespace/pdb_open.yaml | 47 - .../singlenamespace/pdb_plug.yaml | 55 - .../singlenamespace/pdb_secret.yaml | 16 - .../singlenamespace/pdb_unplug.yaml | 49 - docs/multitenant/ords-based/usecase/README.md | 112 - .../usecase/cdbnamespace_binding.yaml | 13 - .../usecase/clone_pdb1_resource.yaml | 50 - .../usecase/clone_pdb2_resource.yaml | 50 - .../usecase/close_pdb1_resource.yaml | 47 - .../usecase/close_pdb2_resource.yaml | 47 - .../usecase/close_pdb3_resource.yaml | 47 - .../ords-based/usecase/create_ords_pod.yaml | 48 - .../usecase/create_pdb1_resource.yaml | 51 - .../usecase/create_pdb2_resource.yaml | 51 - .../usecase/delete_pdb1_resource.yaml | 45 - .../usecase/delete_pdb2_resource.yaml | 45 - docs/multitenant/ords-based/usecase/makefile | 915 - .../ords-based/usecase/map_pdb1_resource.yaml | 49 - .../ords-based/usecase/map_pdb2_resource.yaml | 49 - .../ords-based/usecase/map_pdb3_resource.yaml | 49 - .../usecase/open_pdb1_resource.yaml | 47 - .../usecase/open_pdb2_resource.yaml | 47 - .../usecase/open_pdb3_resource.yaml | 47 - .../ords-based/usecase/parameters.txt | 61 - .../usecase/pdbnamespace_binding.yaml | 13 - .../usecase/plug_pdb1_resource.yaml | 53 - .../usecase/unplug_pdb1_resource.yaml | 46 - .../ords-based/usecase01/README.md | 516 - docs/multitenant/ords-based/usecase01/ca.crt | 25 - docs/multitenant/ords-based/usecase01/ca.key | 27 - docs/multitenant/ords-based/usecase01/ca.srl | 1 - .../ords-based/usecase01/cdb_create.yaml | 44 - .../ords-based/usecase01/cdb_secret.yaml | 17 - .../usecase01/clone_pdb1_resource.yaml | 50 - .../usecase01/clone_pdb2_resource.yaml | 50 - .../usecase01/close_pdb1_resource.yaml | 47 - .../usecase01/close_pdb2_resource.yaml | 47 - .../usecase01/close_pdb3_resource.yaml | 47 - .../ords-based/usecase01/create_ords_pod.yaml | 48 - .../usecase01/create_pdb1_resource.yaml | 51 - .../usecase01/create_pdb2_resource.yaml | 51 - .../usecase01/delete_pdb1_resource.yaml | 45 - .../usecase01/delete_pdb2_resource.yaml | 45 - .../ords-based/usecase01/extfile.txt | 1 - .../usecase01/logfiles/BuildImage.log | 896 - .../usecase01/logfiles/ImagePush.log | 11 - .../ords-based/usecase01/logfiles/cdb.log | 372 - .../usecase01/logfiles/cdb_creation.log | 357 - .../usecase01/logfiles/openssl_execution.log | 19 - .../usecase01/logfiles/ordsconfig.log | 39 - .../usecase01/logfiles/tagandpush.log | 14 - .../ords-based/usecase01/logfiles/testapi.log | 62 - .../multitenant/ords-based/usecase01/makefile | 906 - .../usecase01/map_pdb1_resource.yaml | 49 - .../usecase01/map_pdb2_resource.yaml | 49 - .../usecase01/map_pdb3_resource.yaml | 49 - .../usecase01/open_pdb1_resource.yaml | 47 - .../usecase01/open_pdb2_resource.yaml | 47 - .../usecase01/open_pdb3_resource.yaml | 47 - ...acle-database-operator-system_binding.yaml | 13 - .../usecase01/oracle-database-operator.yaml | 1 - .../ords-based/usecase01/parameters.txt | 61 - .../ords-based/usecase01/pdb_close.yaml | 44 - .../ords-based/usecase01/pdb_create.yaml | 47 - .../ords-based/usecase01/pdb_delete.yaml | 34 - .../ords-based/usecase01/pdb_map.yaml | 45 - .../ords-based/usecase01/pdb_open.yaml | 43 - .../ords-based/usecase01/pdb_secret.yaml | 16 - .../usecase01/plug_pdb1_resource.yaml | 53 - .../ords-based/usecase01/server.csr | 18 - .../ords-based/usecase01/tde_secret.yaml | 17 - docs/multitenant/ords-based/usecase01/tls.crt | 24 - docs/multitenant/ords-based/usecase01/tls.key | 28 - .../usecase01/unplug_pdb1_resource.yaml | 46 - .../ords-based/usecase02/README.md | 523 - .../ords-based/usecase02/pdb_clone.yaml | 50 - .../ords-based/usecase02/pdb_plug.yaml | 53 - .../ords-based/usecase02/pdb_plugtde.yaml | 56 - .../ords-based/usecase02/pdb_unplug.yaml | 46 - .../ords-based/usecase02/pdb_unplugtde.yaml | 54 - docs/multitenant/provisioning/ords_image.md | 81 - docs/multitenant/usecase/README.md | 248 + .../usecase/altersystem_pdb1_resource.yaml | 3 - .../usecase/cdbnamespace_binding.yaml | 0 .../usecase/clone_pdb1_resource.yaml | 3 +- .../usecase/clone_pdb2_resource.yaml | 3 +- .../usecase/close_pdb1_resource.yaml | 1 - .../usecase/close_pdb2_resource.yaml | 1 - .../usecase/close_pdb3_resource.yaml | 3 +- .../usecase/config_map_pdb.yaml | 0 .../multitenant/usecase/config_map_plsql.yaml | 29 + .../usecase/create_lrest_pod.yaml | 9 + .../usecase/create_pdb1_resource.yaml | 3 +- .../usecase/create_pdb2_resource.yaml | 3 +- .../usecase/delete_pdb1_resource.yaml | 3 +- .../usecase/delete_pdb2_resource.yaml | 3 +- .../usecase/delete_pdb3_resource.yaml | 46 + docs/multitenant/usecase/makefile | 1545 ++ .../usecase/map_pdb1_resource.yaml | 3 +- .../usecase/map_pdb2_resource.yaml | 3 +- .../usecase/map_pdb3_resource.yaml | 3 +- docs/multitenant/usecase/node_rbac.yaml | 27 + .../usecase/open_pdb1_resource.yaml | 1 - .../usecase/open_pdb2_resource.yaml | 1 - .../usecase/open_pdb3_resource.yaml | 1 - .../{lrest-based => }/usecase/parameters.txt | 57 +- .../usecase/pdbnamespace_binding.yaml | 0 .../usecase/plug_pdb1_resource.yaml | 7 +- .../multitenant/usecase/security_context.yaml | 93 + .../usecase/unplug_pdb1_resource.yaml | 4 +- .../usecase01/logfiles/BuildImage.log | 896 - .../usecase01/logfiles/openssl_execution.log | 19 - .../usecase01/logfiles/ordsconfig.log | 39 - docs/multitenant/usecase01/makefile | 284 - docs/multitenant/usecase03/Dockerfile | 80 - .../usecase03/NamespaceSegregation.png | Bin 270813 -> 0 bytes docs/multitenant/usecase03/README.md | 268 - docs/multitenant/usecase03/cdb_create.yaml | 44 - .../usecase03/cdb_creation_log.txt | 336 - docs/multitenant/usecase03/cdb_secret.yaml | 17 - docs/multitenant/usecase03/gentlscert.sh | 23 - docs/multitenant/usecase03/makefile | 285 - .../usecase03/ns_namespace_cdb.yaml | 7 - .../usecase03/ns_namespace_pdb.yaml | 7 - .../usecase03/operator_creation_log.txt | 27 - docs/multitenant/usecase03/pdb_create.yaml | 46 - .../usecase03/pdb_creation_log.txt | 6 - docs/multitenant/usecase03/pdb_secret.yaml | 16 - docs/multitenant/usecase03/runOrdsSSL.sh | 190 - docs/observability/README.md | 1054 +- docs/oraclerestart/README.md | 75 + ...sm_disk_to_an_existing_restart_database.md | 64 + .../asm_addition_autoupdate_false.txt | 424 + .../asm_addition_autoupdate_true.txt | 423 + .../provisioning/asm_disk_deletion.txt | 453 + .../provisioning/asm_disk_deletion1.txt | 619 + ...change_memory_cpu_for_oracle_restart_db.md | 55 + ...e_sw_storage_size_for_oracle_restart_db.md | 59 + docs/oraclerestart/provisioning/cleanup.md | 20 + .../create_kubernetes_secret_for_db_user.md | 27 + .../create_kubernetes_secret_for_ssh_setup.md | 19 + .../provisioning/database_connection.md | 100 + docs/oraclerestart/provisioning/debugging.md | 41 + ...disks_from_an_existing_restart_database.md | 47 + .../provisioning/known_issues.md | 5 + .../provisioning/nfs_pv_stage_vol.yaml | 39 + .../provisioning/oraclerestart_prov.yaml | 100 + .../oraclerestart_prov_loadbalancer.yaml | 114 + ...oraclerestart_prov_multiple_diskgroups.txt | 315 + ...raclerestart_prov_multiple_diskgroups.yaml | 131 + ...ov_multiple_diskgroups_with_redundancy.txt | 335 + ...v_multiple_diskgroups_with_redundancy.yaml | 143 + ..._redundancy_with_separate_storage_class.md | 53 + ...redundancy_with_separate_storage_class.txt | 344 + ...edundancy_with_separate_storage_class.yaml | 154 + .../oraclerestart_prov_nodeports.yaml | 117 + ...aclerestart_prov_nodeports_mcpu_change.txt | 389 + ...clerestart_prov_nodeports_mcpu_change.yaml | 117 + .../oraclerestart_prov_rupatch.yaml | 120 + ...tart_prov_rupatch_oneoff_storageclass.yaml | 134 + .../oraclerestart_prov_rupatch_pvc.yaml | 131 + .../oraclerestart_prov_storage_class.yaml | 124 + ...ov_storage_class_after_sw_home_resize.yaml | 125 + ...v_storage_class_before_sw_home_resize.yaml | 125 + .../orestart_db_rupatch_oneoffs_object.txt | 197 + .../orestart_loadbalancer_object.txt | 169 + .../provisioning/orestart_nodeport_object.txt | 153 + .../provisioning/orestart_object.txt | 147 + .../orestart_prov_asm_disk_addition.yaml | 120 + ...ov_asm_disk_addition_autoupdate_false.yaml | 120 + .../orestart_prov_asm_disk_deletion.yaml | 116 + .../provisioning/orestart_rupatch_object.txt | 176 + .../orestart_rupatch_pvc_object.txt | 209 + .../orestart_storage_class_object.txt | 180 + ...rage_class_object_after_sw_home_resize.txt | 197 + ...age_class_object_before_sw_home_resize.txt | 196 + .../prerequisites_oracle_restart_db.md | 323 + .../provisioning_oracle_restart_db.md | 41 + ...isioning_oracle_restart_db_loadbalancer.md | 44 + ...provisioning_oracle_restart_db_nodeport.md | 43 + .../provisioning_oracle_restart_db_rupatch.md | 45 + ...oning_oracle_restart_db_rupatch_oneoffs.md | 47 + ...ning_oracle_restart_multiple_diskgroups.md | 42 + ...art_multiple_diskgroups_with_redundancy.md | 48 + ...provisioning_oracle_restart_rupatch_pvc.md | 47 + ...ovisioning_oracle_restart_storage_class.md | 42 + docs/ordsservices/README.md | 124 +- docs/ordsservices/api.md | 22 +- docs/ordsservices/autoupgrade.md | 129 +- docs/ordsservices/examples/adb.md | 14 +- docs/ordsservices/examples/adb_oraoper.md | 12 +- docs/ordsservices/examples/existing_db.md | 13 +- docs/ordsservices/examples/mongo_api.md | 13 +- docs/ordsservices/examples/multi_pool.md | 15 +- .../examples/ordssrvs-sa-scc.yaml | 50 + docs/ordsservices/examples/ordssrvs-sa.yaml | 5 + docs/ordsservices/examples/ordssrvs.yaml | 32 + docs/ordsservices/examples/sidb_container.md | 18 +- docs/ordsservices/usecase01/makefile | 19 +- docs/sharding/README.md | 14 +- .../sharding_provisioning_with_db_events.yaml | 82 +- ...harding_provisioning_with_free_images.yaml | 72 +- docs/sharding/provisioning/oraclesi.yaml | 26 +- .../provisioning/oraclesi_pvc_commented.yaml | 28 +- ...y_cloning_db_from_gold_image_across_ads.md | 58 - ...ing_by_cloning_db_gold_image_in_same_ad.md | 54 - ...ding_provisioning_with_chunks_specified.md | 44 - ...rding_scale_in_delete_an_existing_shard.md | 51 - .../snr_ssharding_scale_out_add_shards.md | 38 - .../snr_ssharding_shard_prov.yaml | 74 +- .../snr_ssharding_shard_prov_chunks.yaml | 61 - .../snr_ssharding_shard_prov_clone.yaml | 83 - ...ssharding_shard_prov_clone_across_ads.yaml | 91 - .../snr_ssharding_shard_prov_delshard.yaml | 69 - .../snr_ssharding_shard_prov_extshard.yaml | 68 - .../snr_ssharding_shard_prov_memory_cpu.yaml | 88 +- ...sharding_shard_prov_send_notification.yaml | 98 +- ...ding_provisioning_with_chunks_specified.md | 2 +- ..._provisioning_with_control_on_resources.md | 2 +- ...ith_notification_using_oci_notification.md | 24 +- ...ding_provisioning_without_db_gold_image.md | 10 +- ...rding_scale_in_delete_an_existing_shard.md | 2 +- .../ssharding_scale_out_add_shards.md | 2 +- .../system_sharding/ssharding_shard_prov.yaml | 72 +- .../ssharding_shard_prov_chunks.yaml | 78 +- .../ssharding_shard_prov_clone.yaml | 90 +- ...ssharding_shard_prov_clone_across_ads.yaml | 98 +- .../ssharding_shard_prov_delshard.yaml | 76 +- .../ssharding_shard_prov_extshard.yaml | 78 +- .../ssharding_shard_prov_memory_cpu.yaml | 88 +- ...sharding_shard_prov_send_notification.yaml | 97 +- ..._provisioning_with_control_on_resources.md | 12 +- ...ith_notification_using_oci_notification.md | 20 +- ...ding_provisioning_without_db_gold_image.md | 16 +- ...rding_scale_in_delete_an_existing_shard.md | 30 +- .../udsharding_scale_out_add_shards.md | 18 +- .../udsharding_shard_prov.yaml | 76 +- .../udsharding_shard_prov_clone.yaml | 90 +- ...dsharding_shard_prov_clone_across_ads.yaml | 96 +- .../udsharding_shard_prov_delshard.yaml | 78 +- .../udsharding_shard_prov_extshard.yaml | 81 +- .../udsharding_shard_prov_memory_cpu.yaml | 87 +- ...sharding_shard_prov_send_notification.yaml | 104 +- docs/sidb/README.md | 8 +- go.mod | 154 +- go.sum | 439 +- main.go | 91 +- oracle-database-operator.yaml | 16722 ++++++++-------- ords/Dockerfile | 86 - ords/ords_init.sh | 524 +- ords/ords_start.sh | 26 + ords/runOrdsSSL.sh | 197 - ...tonomousdatabase_controller_create_test.go | 2 +- test/e2e/util/oci_acd_request.go | 5 +- test/e2e/util/util.go | 4 +- 562 files changed, 67698 insertions(+), 42601 deletions(-) create mode 100644 .gitlab-ci.yml delete mode 100644 apis/database/v1alpha1/cdb_types.go delete mode 100644 apis/database/v1alpha1/cdb_webhook.go delete mode 100644 apis/database/v1alpha1/pdb_types.go delete mode 100644 apis/database/v1alpha1/pdb_webhook.go delete mode 100644 apis/database/v4/cdb_types.go delete mode 100644 apis/database/v4/cdb_webhook.go create mode 100644 apis/database/v4/oraclerestart_types.go create mode 100644 apis/database/v4/oraclerestart_webhook.go delete mode 100644 apis/database/v4/pdb_types.go delete mode 100644 apis/database/v4/pdb_webhook.go rename {config => bundle/manifests}/database.oracle.com_autonomouscontainerdatabases.yaml (50%) rename {config => bundle/manifests}/database.oracle.com_autonomousdatabasebackups.yaml (50%) rename {config => bundle/manifests}/database.oracle.com_autonomousdatabaserestores.yaml (50%) create mode 100644 bundle/manifests/database.oracle.com_autonomousdatabases.yaml rename {config/crd/bases => bundle/manifests}/database.oracle.com_cdbs.yaml (98%) create mode 100644 bundle/manifests/database.oracle.com_dataguardbrokers.yaml rename config/database.oracle.com_DbcsSystem.yaml => bundle/manifests/database.oracle.com_dbcssystems.yaml (50%) rename config/database.oracle.com_cdbs.yaml => bundle/manifests/database.oracle.com_lrests.yaml (63%) rename config/database.oracle.com_pdbs.yaml => bundle/manifests/database.oracle.com_lrpdbs.yaml (55%) rename {config => bundle/manifests}/database.oracle.com_oraclerestdataservices.yaml (50%) create mode 100644 bundle/manifests/database.oracle.com_ordssrvs.yaml rename {config/crd/bases => bundle/manifests}/database.oracle.com_pdbs.yaml (98%) create mode 100644 bundle/manifests/database.oracle.com_shardingdatabases.yaml create mode 100644 bundle/manifests/database.oracle.com_singleinstancedatabases.yaml create mode 100644 bundle/manifests/observability.oracle.com_databaseobservers.yaml create mode 100644 bundle/manifests/oracle-database-operator-controller-manager-metrics-service_v1_service.yaml create mode 100644 bundle/manifests/oracle-database-operator-metrics-reader_rbac.authorization.k8s.io_v1_clusterrole.yaml create mode 100644 bundle/manifests/oracle-database-operator-webhook-service_v1_service.yaml create mode 100644 bundle/manifests/oracle-database-operator.clusterserviceversion.yaml create mode 100644 bundle/metadata/annotations.yaml create mode 100644 commons/oraclerestart/exec.go create mode 100644 commons/oraclerestart/oraclerestartcommon.go create mode 100644 commons/oraclerestart/oraclerestartconstants.go create mode 100644 commons/oraclerestart/oraclerestartprov.go create mode 100644 commons/oraclerestart/oraclerestartstatus.go create mode 100644 commons/oraclerestart/utils/utils.go create mode 100644 config/certmanager/certificate-metrics.yaml create mode 100644 config/certmanager/certificate-webhook.yaml create mode 100644 config/certmanager/issuer.yaml create mode 100644 config/crd/bases/database.oracle.com_oraclerestartnews.yaml create mode 100644 config/crd/bases/database.oracle.com_oraclerestarts.yaml create mode 100644 config/crd/bases/database.oracle.com_oraclerestartservices.yaml delete mode 100644 config/database.oracle.com_autonomousdatabases.yaml delete mode 100644 config/database.oracle.com_dataguardbrokers.yaml delete mode 100644 config/database.oracle.com_shardingdatabases.yaml delete mode 100644 config/database.oracle.com_singleinstancedatabases.yaml create mode 100644 config/network-policy/allow-webhook-traffic.yaml delete mode 100644 config/rbac/cdb_editor_role.yaml delete mode 100644 config/rbac/cdb_viewer_role.yaml create mode 100644 config/rbac/database_oraclerestart_admin_role.yaml create mode 100644 config/rbac/database_oraclerestart_editor_role.yaml create mode 100644 config/rbac/database_oraclerestart_viewer_role.yaml delete mode 100644 config/rbac/pdb_editor_role.yaml delete mode 100644 config/rbac/pdb_viewer_role.yaml rename config/samples/adb/{autonomousdatabase_wallet.yaml => autonomousdatabase_download_wallet.yaml} (96%) create mode 100644 config/samples/adb/autonomousdatabase_manual_failover.yaml create mode 100644 config/samples/adb/autonomousdatabase_switchover.yaml create mode 100644 config/samples/database_v4_oraclerestart.yaml delete mode 100644 config/samples/multitenant/cdb.yaml delete mode 100644 config/samples/multitenant/cdb_secret.yaml delete mode 100644 config/samples/multitenant/pdb_clone.yaml delete mode 100644 config/samples/multitenant/pdb_create.yaml delete mode 100644 config/samples/multitenant/pdb_delete.yaml delete mode 100644 config/samples/multitenant/pdb_modify.yaml delete mode 100644 config/samples/multitenant/pdb_plug.yaml delete mode 100644 config/samples/multitenant/pdb_secret.yaml delete mode 100644 config/samples/multitenant/pdb_unplug.yaml delete mode 100644 config/samples/observability/databaseobserver_custom_config.yaml delete mode 100644 config/samples/observability/databaseobserver_minimal.yaml delete mode 100644 config/samples/observability/databaseobserver_vault.yaml delete mode 100644 config/samples/observability/v1alpha1/databaseobserver_vault.yaml create mode 100644 config/samples/observability/v1alpha1/databaseobserver_vault_oci.yaml create mode 100644 config/samples/observability/v4/databaseobserver_exporter_config_file.yaml delete mode 100644 config/samples/observability/v4/databaseobserver_vault.yaml create mode 100644 config/samples/observability/v4/databaseobserver_vault_azure.yaml create mode 100644 config/samples/observability/v4/databaseobserver_vault_oci.yaml delete mode 100644 controllers/database/cdb_controller.go create mode 100644 controllers/database/oraclerestart_controller.go delete mode 100644 controllers/database/pdb_controller.go create mode 100644 docs/config/samples/orestart/custom-kubeletconfig.yaml create mode 100644 docs/config/samples/orestart/custom-scc.yaml create mode 100644 docs/dbcs/provisioning/backup_database_sample_output.log create mode 100644 docs/dbcs/provisioning/backup_of_database.md create mode 100644 docs/dbcs/provisioning/backup_of_database.yaml create mode 100644 docs/dbcs/provisioning/dataguard_in_database.yaml create mode 100644 docs/dbcs/provisioning/dataguard_in_database_sample_output.log create mode 100644 docs/dbcs/provisioning/dataguard_to_database.md create mode 100644 docs/dbcs/provisioning/disable_dataguard_in_database.yaml create mode 100644 docs/dbcs/provisioning/disable_dataguard_in_database_sample_output.log create mode 100644 docs/dbcs/provisioning/disable_dataguard_to_database.md create mode 100644 docs/dbcs/provisioning/disabled_dataguard_to_database.md create mode 100644 docs/dbcs/provisioning/patch_dbcs_system.yaml create mode 100644 docs/dbcs/provisioning/patch_dbcs_system_sample_output.log create mode 100644 docs/dbcs/provisioning/patching_database.md create mode 100644 docs/dbcs/provisioning/restore_database_sample_output.log create mode 100644 docs/dbcs/provisioning/restore_of_database.md create mode 100644 docs/dbcs/provisioning/restore_of_database.yaml create mode 100644 docs/dbcs/provisioning/upgrade_dbcs_system.yaml create mode 100644 docs/dbcs/provisioning/upgrade_dbcs_system_sample_output.log create mode 100644 docs/dbcs/provisioning/upgrading_database.md delete mode 100644 docs/multitenant/NamespaceSeg.md rename docs/multitenant/{lrest-based => }/images/Generalschema2.jpg (100%) create mode 100644 docs/multitenant/images/KubectlGetSchema.jpg create mode 100644 docs/multitenant/images/KubectlGetSchema2.jpg rename docs/multitenant/{lrest-based/images/UsecaseSchema.jpg => images/old_testcase_wf.jpg} (100%) create mode 100644 docs/multitenant/images/plsqlmap.jpg create mode 100644 docs/multitenant/images/usecaseschema.jpg delete mode 100644 docs/multitenant/lrest-based/README.md delete mode 100644 docs/multitenant/lrest-based/usecase/README.md delete mode 100644 docs/multitenant/lrest-based/usecase/config-map-pdb.yaml delete mode 100644 docs/multitenant/lrest-based/usecase/makefile delete mode 100644 docs/multitenant/ords-based/NamespaceSeg.md delete mode 100644 docs/multitenant/ords-based/README.md delete mode 100644 docs/multitenant/ords-based/images/K8S_NAMESPACE_SEG.png delete mode 100644 docs/multitenant/ords-based/images/K8S_SECURE1.png delete mode 100644 docs/multitenant/ords-based/images/K8S_SECURE2.png delete mode 100644 docs/multitenant/ords-based/images/K8S_SECURE3.png delete mode 100644 docs/multitenant/ords-based/images/K8S_SECURE4.png delete mode 100644 docs/multitenant/ords-based/images/makerunall.png delete mode 100644 docs/multitenant/ords-based/images/makesecrets_1_1.png delete mode 100644 docs/multitenant/ords-based/openssl_schema.jpg delete mode 100644 docs/multitenant/ords-based/provisioning/example_setup_using_oci_oke_cluster.md delete mode 100644 docs/multitenant/ords-based/provisioning/multinamespace/cdb_create.yaml delete mode 100644 docs/multitenant/ords-based/provisioning/multinamespace/pdb_clone.yaml delete mode 100644 docs/multitenant/ords-based/provisioning/multinamespace/pdb_close.yaml delete mode 100644 docs/multitenant/ords-based/provisioning/multinamespace/pdb_create.yaml delete mode 100644 docs/multitenant/ords-based/provisioning/multinamespace/pdb_delete.yaml delete mode 100644 docs/multitenant/ords-based/provisioning/multinamespace/pdb_open.yaml delete mode 100644 docs/multitenant/ords-based/provisioning/multinamespace/pdb_plug.yaml delete mode 100644 docs/multitenant/ords-based/provisioning/multinamespace/pdb_unplug.yaml delete mode 100644 docs/multitenant/ords-based/provisioning/ords_image.md delete mode 100644 docs/multitenant/ords-based/provisioning/quickOKEcreation.md delete mode 100644 docs/multitenant/ords-based/provisioning/singlenamespace/cdb_create.yaml delete mode 100644 docs/multitenant/ords-based/provisioning/singlenamespace/cdb_secret.yaml delete mode 100644 docs/multitenant/ords-based/provisioning/singlenamespace/pdb_clone.yaml delete mode 100644 docs/multitenant/ords-based/provisioning/singlenamespace/pdb_close.yaml delete mode 100644 docs/multitenant/ords-based/provisioning/singlenamespace/pdb_create.yaml delete mode 100644 docs/multitenant/ords-based/provisioning/singlenamespace/pdb_delete.yaml delete mode 100644 docs/multitenant/ords-based/provisioning/singlenamespace/pdb_open.yaml delete mode 100644 docs/multitenant/ords-based/provisioning/singlenamespace/pdb_plug.yaml delete mode 100644 docs/multitenant/ords-based/provisioning/singlenamespace/pdb_secret.yaml delete mode 100644 docs/multitenant/ords-based/provisioning/singlenamespace/pdb_unplug.yaml delete mode 100644 docs/multitenant/ords-based/usecase/README.md delete mode 100644 docs/multitenant/ords-based/usecase/cdbnamespace_binding.yaml delete mode 100644 docs/multitenant/ords-based/usecase/clone_pdb1_resource.yaml delete mode 100644 docs/multitenant/ords-based/usecase/clone_pdb2_resource.yaml delete mode 100644 docs/multitenant/ords-based/usecase/close_pdb1_resource.yaml delete mode 100644 docs/multitenant/ords-based/usecase/close_pdb2_resource.yaml delete mode 100644 docs/multitenant/ords-based/usecase/close_pdb3_resource.yaml delete mode 100644 docs/multitenant/ords-based/usecase/create_ords_pod.yaml delete mode 100644 docs/multitenant/ords-based/usecase/create_pdb1_resource.yaml delete mode 100644 docs/multitenant/ords-based/usecase/create_pdb2_resource.yaml delete mode 100644 docs/multitenant/ords-based/usecase/delete_pdb1_resource.yaml delete mode 100644 docs/multitenant/ords-based/usecase/delete_pdb2_resource.yaml delete mode 100644 docs/multitenant/ords-based/usecase/makefile delete mode 100644 docs/multitenant/ords-based/usecase/map_pdb1_resource.yaml delete mode 100644 docs/multitenant/ords-based/usecase/map_pdb2_resource.yaml delete mode 100644 docs/multitenant/ords-based/usecase/map_pdb3_resource.yaml delete mode 100644 docs/multitenant/ords-based/usecase/open_pdb1_resource.yaml delete mode 100644 docs/multitenant/ords-based/usecase/open_pdb2_resource.yaml delete mode 100644 docs/multitenant/ords-based/usecase/open_pdb3_resource.yaml delete mode 100644 docs/multitenant/ords-based/usecase/parameters.txt delete mode 100644 docs/multitenant/ords-based/usecase/pdbnamespace_binding.yaml delete mode 100644 docs/multitenant/ords-based/usecase/plug_pdb1_resource.yaml delete mode 100644 docs/multitenant/ords-based/usecase/unplug_pdb1_resource.yaml delete mode 100644 docs/multitenant/ords-based/usecase01/README.md delete mode 100644 docs/multitenant/ords-based/usecase01/ca.crt delete mode 100644 docs/multitenant/ords-based/usecase01/ca.key delete mode 100644 docs/multitenant/ords-based/usecase01/ca.srl delete mode 100644 docs/multitenant/ords-based/usecase01/cdb_create.yaml delete mode 100644 docs/multitenant/ords-based/usecase01/cdb_secret.yaml delete mode 100644 docs/multitenant/ords-based/usecase01/clone_pdb1_resource.yaml delete mode 100644 docs/multitenant/ords-based/usecase01/clone_pdb2_resource.yaml delete mode 100644 docs/multitenant/ords-based/usecase01/close_pdb1_resource.yaml delete mode 100644 docs/multitenant/ords-based/usecase01/close_pdb2_resource.yaml delete mode 100644 docs/multitenant/ords-based/usecase01/close_pdb3_resource.yaml delete mode 100644 docs/multitenant/ords-based/usecase01/create_ords_pod.yaml delete mode 100644 docs/multitenant/ords-based/usecase01/create_pdb1_resource.yaml delete mode 100644 docs/multitenant/ords-based/usecase01/create_pdb2_resource.yaml delete mode 100644 docs/multitenant/ords-based/usecase01/delete_pdb1_resource.yaml delete mode 100644 docs/multitenant/ords-based/usecase01/delete_pdb2_resource.yaml delete mode 100644 docs/multitenant/ords-based/usecase01/extfile.txt delete mode 100644 docs/multitenant/ords-based/usecase01/logfiles/BuildImage.log delete mode 100644 docs/multitenant/ords-based/usecase01/logfiles/ImagePush.log delete mode 100644 docs/multitenant/ords-based/usecase01/logfiles/cdb.log delete mode 100644 docs/multitenant/ords-based/usecase01/logfiles/cdb_creation.log delete mode 100644 docs/multitenant/ords-based/usecase01/logfiles/openssl_execution.log delete mode 100644 docs/multitenant/ords-based/usecase01/logfiles/ordsconfig.log delete mode 100644 docs/multitenant/ords-based/usecase01/logfiles/tagandpush.log delete mode 100644 docs/multitenant/ords-based/usecase01/logfiles/testapi.log delete mode 100644 docs/multitenant/ords-based/usecase01/makefile delete mode 100644 docs/multitenant/ords-based/usecase01/map_pdb1_resource.yaml delete mode 100644 docs/multitenant/ords-based/usecase01/map_pdb2_resource.yaml delete mode 100644 docs/multitenant/ords-based/usecase01/map_pdb3_resource.yaml delete mode 100644 docs/multitenant/ords-based/usecase01/open_pdb1_resource.yaml delete mode 100644 docs/multitenant/ords-based/usecase01/open_pdb2_resource.yaml delete mode 100644 docs/multitenant/ords-based/usecase01/open_pdb3_resource.yaml delete mode 100644 docs/multitenant/ords-based/usecase01/oracle-database-operator-system_binding.yaml delete mode 120000 docs/multitenant/ords-based/usecase01/oracle-database-operator.yaml delete mode 100644 docs/multitenant/ords-based/usecase01/parameters.txt delete mode 100644 docs/multitenant/ords-based/usecase01/pdb_close.yaml delete mode 100644 docs/multitenant/ords-based/usecase01/pdb_create.yaml delete mode 100644 docs/multitenant/ords-based/usecase01/pdb_delete.yaml delete mode 100644 docs/multitenant/ords-based/usecase01/pdb_map.yaml delete mode 100644 docs/multitenant/ords-based/usecase01/pdb_open.yaml delete mode 100644 docs/multitenant/ords-based/usecase01/pdb_secret.yaml delete mode 100644 docs/multitenant/ords-based/usecase01/plug_pdb1_resource.yaml delete mode 100644 docs/multitenant/ords-based/usecase01/server.csr delete mode 100644 docs/multitenant/ords-based/usecase01/tde_secret.yaml delete mode 100644 docs/multitenant/ords-based/usecase01/tls.crt delete mode 100644 docs/multitenant/ords-based/usecase01/tls.key delete mode 100644 docs/multitenant/ords-based/usecase01/unplug_pdb1_resource.yaml delete mode 100644 docs/multitenant/ords-based/usecase02/README.md delete mode 100644 docs/multitenant/ords-based/usecase02/pdb_clone.yaml delete mode 100644 docs/multitenant/ords-based/usecase02/pdb_plug.yaml delete mode 100644 docs/multitenant/ords-based/usecase02/pdb_plugtde.yaml delete mode 100644 docs/multitenant/ords-based/usecase02/pdb_unplug.yaml delete mode 100644 docs/multitenant/ords-based/usecase02/pdb_unplugtde.yaml delete mode 100644 docs/multitenant/provisioning/ords_image.md create mode 100644 docs/multitenant/usecase/README.md rename docs/multitenant/{lrest-based => }/usecase/altersystem_pdb1_resource.yaml (97%) rename docs/multitenant/{lrest-based => }/usecase/cdbnamespace_binding.yaml (100%) rename docs/multitenant/{lrest-based => }/usecase/clone_pdb1_resource.yaml (95%) rename docs/multitenant/{lrest-based => }/usecase/clone_pdb2_resource.yaml (95%) rename docs/multitenant/{lrest-based => }/usecase/close_pdb1_resource.yaml (97%) rename docs/multitenant/{lrest-based => }/usecase/close_pdb2_resource.yaml (97%) rename docs/multitenant/{lrest-based => }/usecase/close_pdb3_resource.yaml (95%) rename docs/multitenant/{lrest-based => }/usecase/config_map_pdb.yaml (100%) create mode 100644 docs/multitenant/usecase/config_map_plsql.yaml rename docs/multitenant/{lrest-based => }/usecase/create_lrest_pod.yaml (86%) rename docs/multitenant/{lrest-based => }/usecase/create_pdb1_resource.yaml (95%) rename docs/multitenant/{lrest-based => }/usecase/create_pdb2_resource.yaml (95%) rename docs/multitenant/{lrest-based => }/usecase/delete_pdb1_resource.yaml (94%) rename docs/multitenant/{lrest-based => }/usecase/delete_pdb2_resource.yaml (94%) create mode 100644 docs/multitenant/usecase/delete_pdb3_resource.yaml create mode 100644 docs/multitenant/usecase/makefile rename docs/multitenant/{lrest-based => }/usecase/map_pdb1_resource.yaml (95%) rename docs/multitenant/{lrest-based => }/usecase/map_pdb2_resource.yaml (95%) rename docs/multitenant/{lrest-based => }/usecase/map_pdb3_resource.yaml (95%) create mode 100644 docs/multitenant/usecase/node_rbac.yaml rename docs/multitenant/{lrest-based => }/usecase/open_pdb1_resource.yaml (97%) rename docs/multitenant/{lrest-based => }/usecase/open_pdb2_resource.yaml (97%) rename docs/multitenant/{lrest-based => }/usecase/open_pdb3_resource.yaml (97%) rename docs/multitenant/{lrest-based => }/usecase/parameters.txt (52%) rename docs/multitenant/{lrest-based => }/usecase/pdbnamespace_binding.yaml (100%) rename docs/multitenant/{lrest-based => }/usecase/plug_pdb1_resource.yaml (91%) create mode 100644 docs/multitenant/usecase/security_context.yaml rename docs/multitenant/{lrest-based => }/usecase/unplug_pdb1_resource.yaml (93%) delete mode 100644 docs/multitenant/usecase01/logfiles/BuildImage.log delete mode 100644 docs/multitenant/usecase01/logfiles/openssl_execution.log delete mode 100644 docs/multitenant/usecase01/logfiles/ordsconfig.log delete mode 100644 docs/multitenant/usecase01/makefile delete mode 100644 docs/multitenant/usecase03/Dockerfile delete mode 100644 docs/multitenant/usecase03/NamespaceSegregation.png delete mode 100644 docs/multitenant/usecase03/README.md delete mode 100644 docs/multitenant/usecase03/cdb_create.yaml delete mode 100644 docs/multitenant/usecase03/cdb_creation_log.txt delete mode 100644 docs/multitenant/usecase03/cdb_secret.yaml delete mode 100644 docs/multitenant/usecase03/gentlscert.sh delete mode 100644 docs/multitenant/usecase03/makefile delete mode 100644 docs/multitenant/usecase03/ns_namespace_cdb.yaml delete mode 100644 docs/multitenant/usecase03/ns_namespace_pdb.yaml delete mode 100644 docs/multitenant/usecase03/operator_creation_log.txt delete mode 100644 docs/multitenant/usecase03/pdb_create.yaml delete mode 100644 docs/multitenant/usecase03/pdb_creation_log.txt delete mode 100644 docs/multitenant/usecase03/pdb_secret.yaml delete mode 100644 docs/multitenant/usecase03/runOrdsSSL.sh create mode 100644 docs/oraclerestart/README.md create mode 100644 docs/oraclerestart/provisioning/add_asm_disk_to_an_existing_restart_database.md create mode 100644 docs/oraclerestart/provisioning/asm_addition_autoupdate_false.txt create mode 100644 docs/oraclerestart/provisioning/asm_addition_autoupdate_true.txt create mode 100644 docs/oraclerestart/provisioning/asm_disk_deletion.txt create mode 100644 docs/oraclerestart/provisioning/asm_disk_deletion1.txt create mode 100644 docs/oraclerestart/provisioning/change_memory_cpu_for_oracle_restart_db.md create mode 100644 docs/oraclerestart/provisioning/change_sw_storage_size_for_oracle_restart_db.md create mode 100644 docs/oraclerestart/provisioning/cleanup.md create mode 100644 docs/oraclerestart/provisioning/create_kubernetes_secret_for_db_user.md create mode 100644 docs/oraclerestart/provisioning/create_kubernetes_secret_for_ssh_setup.md create mode 100644 docs/oraclerestart/provisioning/database_connection.md create mode 100644 docs/oraclerestart/provisioning/debugging.md create mode 100644 docs/oraclerestart/provisioning/delete_asm_disks_from_an_existing_restart_database.md create mode 100644 docs/oraclerestart/provisioning/known_issues.md create mode 100644 docs/oraclerestart/provisioning/nfs_pv_stage_vol.yaml create mode 100644 docs/oraclerestart/provisioning/oraclerestart_prov.yaml create mode 100644 docs/oraclerestart/provisioning/oraclerestart_prov_loadbalancer.yaml create mode 100644 docs/oraclerestart/provisioning/oraclerestart_prov_multiple_diskgroups.txt create mode 100644 docs/oraclerestart/provisioning/oraclerestart_prov_multiple_diskgroups.yaml create mode 100644 docs/oraclerestart/provisioning/oraclerestart_prov_multiple_diskgroups_with_redundancy.txt create mode 100644 docs/oraclerestart/provisioning/oraclerestart_prov_multiple_diskgroups_with_redundancy.yaml create mode 100644 docs/oraclerestart/provisioning/oraclerestart_prov_multiple_diskgroups_with_redundancy_with_separate_storage_class.md create mode 100644 docs/oraclerestart/provisioning/oraclerestart_prov_multiple_diskgroups_with_redundancy_with_separate_storage_class.txt create mode 100644 docs/oraclerestart/provisioning/oraclerestart_prov_multiple_diskgroups_with_redundancy_with_separate_storage_class.yaml create mode 100644 docs/oraclerestart/provisioning/oraclerestart_prov_nodeports.yaml create mode 100644 docs/oraclerestart/provisioning/oraclerestart_prov_nodeports_mcpu_change.txt create mode 100644 docs/oraclerestart/provisioning/oraclerestart_prov_nodeports_mcpu_change.yaml create mode 100644 docs/oraclerestart/provisioning/oraclerestart_prov_rupatch.yaml create mode 100644 docs/oraclerestart/provisioning/oraclerestart_prov_rupatch_oneoff_storageclass.yaml create mode 100644 docs/oraclerestart/provisioning/oraclerestart_prov_rupatch_pvc.yaml create mode 100644 docs/oraclerestart/provisioning/oraclerestart_prov_storage_class.yaml create mode 100644 docs/oraclerestart/provisioning/oraclerestart_prov_storage_class_after_sw_home_resize.yaml create mode 100644 docs/oraclerestart/provisioning/oraclerestart_prov_storage_class_before_sw_home_resize.yaml create mode 100644 docs/oraclerestart/provisioning/orestart_db_rupatch_oneoffs_object.txt create mode 100644 docs/oraclerestart/provisioning/orestart_loadbalancer_object.txt create mode 100644 docs/oraclerestart/provisioning/orestart_nodeport_object.txt create mode 100644 docs/oraclerestart/provisioning/orestart_object.txt create mode 100644 docs/oraclerestart/provisioning/orestart_prov_asm_disk_addition.yaml create mode 100644 docs/oraclerestart/provisioning/orestart_prov_asm_disk_addition_autoupdate_false.yaml create mode 100644 docs/oraclerestart/provisioning/orestart_prov_asm_disk_deletion.yaml create mode 100644 docs/oraclerestart/provisioning/orestart_rupatch_object.txt create mode 100644 docs/oraclerestart/provisioning/orestart_rupatch_pvc_object.txt create mode 100644 docs/oraclerestart/provisioning/orestart_storage_class_object.txt create mode 100644 docs/oraclerestart/provisioning/orestart_storage_class_object_after_sw_home_resize.txt create mode 100644 docs/oraclerestart/provisioning/orestart_storage_class_object_before_sw_home_resize.txt create mode 100644 docs/oraclerestart/provisioning/prerequisites_oracle_restart_db.md create mode 100644 docs/oraclerestart/provisioning/provisioning_oracle_restart_db.md create mode 100644 docs/oraclerestart/provisioning/provisioning_oracle_restart_db_loadbalancer.md create mode 100644 docs/oraclerestart/provisioning/provisioning_oracle_restart_db_nodeport.md create mode 100644 docs/oraclerestart/provisioning/provisioning_oracle_restart_db_rupatch.md create mode 100644 docs/oraclerestart/provisioning/provisioning_oracle_restart_db_rupatch_oneoffs.md create mode 100644 docs/oraclerestart/provisioning/provisioning_oracle_restart_multiple_diskgroups.md create mode 100644 docs/oraclerestart/provisioning/provisioning_oracle_restart_multiple_diskgroups_with_redundancy.md create mode 100644 docs/oraclerestart/provisioning/provisioning_oracle_restart_rupatch_pvc.md create mode 100644 docs/oraclerestart/provisioning/provisioning_oracle_restart_storage_class.md create mode 100644 docs/ordsservices/examples/ordssrvs-sa-scc.yaml create mode 100644 docs/ordsservices/examples/ordssrvs-sa.yaml create mode 100644 docs/ordsservices/examples/ordssrvs.yaml delete mode 100644 docs/sharding/provisioning/snr_system_sharding/snr_ssharding_provisioning_by_cloning_db_from_gold_image_across_ads.md delete mode 100644 docs/sharding/provisioning/snr_system_sharding/snr_ssharding_provisioning_by_cloning_db_gold_image_in_same_ad.md delete mode 100644 docs/sharding/provisioning/snr_system_sharding/snr_ssharding_provisioning_with_chunks_specified.md delete mode 100644 docs/sharding/provisioning/snr_system_sharding/snr_ssharding_scale_in_delete_an_existing_shard.md delete mode 100644 docs/sharding/provisioning/snr_system_sharding/snr_ssharding_scale_out_add_shards.md delete mode 100644 docs/sharding/provisioning/snr_system_sharding/snr_ssharding_shard_prov_chunks.yaml delete mode 100644 docs/sharding/provisioning/snr_system_sharding/snr_ssharding_shard_prov_clone.yaml delete mode 100644 docs/sharding/provisioning/snr_system_sharding/snr_ssharding_shard_prov_clone_across_ads.yaml delete mode 100644 docs/sharding/provisioning/snr_system_sharding/snr_ssharding_shard_prov_delshard.yaml delete mode 100644 docs/sharding/provisioning/snr_system_sharding/snr_ssharding_shard_prov_extshard.yaml delete mode 100644 ords/Dockerfile create mode 100644 ords/ords_start.sh delete mode 100644 ords/runOrdsSSL.sh diff --git a/.gitignore b/.gitignore index 51923538..a830fdda 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ operator.tgz cover.out bin +bin/* testbin/* onpremtest/* ords/*zip @@ -10,4 +11,4 @@ ords/*zip .DS_Store # development .idea -.local +.local \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 00000000..15a73ac1 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,38 @@ +build-operator: + stage: build + variables: + IMAGE: "$DOCKER_REPO:$CI_COMMIT_SHORT_SHA" + OP_YAML: oracle-database-operator.yaml + BUILD_INTERNAL: "true" + script: + - export GOLANG_VERSION=1.25.1 + - export GOROOT=$(go${GOLANG_VERSION} env GOROOT) + - export PATH="${GOROOT}/bin:${PATH}" + - make operator-yaml IMG=$IMAGE GOLANG_VERSION=$GOLANG_VERSION + - if [ "$CI_COMMIT_BRANCH" = "master" ]; then + podman run --rm --privileged multiarch/qemu-user-static --reset -p yes; + make image-build image-push IMG="$IMAGE" BUILD_MANIFEST=true GOLANG_VERSION=$GOLANG_VERSION; + podman manifest rm "$IMAGE"; + else + make image-build image-push IMG="$IMAGE" GOLANG_VERSION=$GOLANG_VERSION; + podman rmi "$IMAGE"; + sed -i "s/\(replicas.\) 3/\1 1/g" ./$OP_YAML; + fi + - buildah containers -q | xargs -n1 buildah rm || true + - podman system prune -f + - curl -s --netrc-file $HOME/.netrc_gitlab $ARTIFACTORY_REPO/$CI_COMMIT_BRANCH/$OP_YAML -T ./$OP_YAML + only: + variables: + - $CI_COMMIT_MESSAGE =~ /\#run-pipeline/ + - $CI_COMMIT_BRANCH =~ /master/ + - $CI_MERGE_REQUEST_ID != "" + except: + variables: + - $CI_COMMIT_MESSAGE =~ /\#skip-pipeline/ + - $CI_COMMIT_TAG != null + +cleanup: + stage: .post + script: + - echo "Clean up downloaded binaries" + - rm -rf bin/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index f444d508..299e0e6f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,8 +3,9 @@ # # Build the manager binary -ARG BUILDER_IMG -FROM ${BUILDER_IMG} as builder +ARG BUILDER_IMG="oraclelinux:9" +ARG RUNNER_IMG="oraclelinux:9-slim" +FROM ${BUILDER_IMG} AS builder ARG TARGETARCH # Download golang if INSTALL_GO is set to true @@ -18,28 +19,38 @@ RUN if [ "$INSTALL_GO" = "true" ]; then \ echo "Go Arch: $(/usr/local/go/bin/go env GOARCH)"; \ fi ENV PATH=${GOLANG_VERSION:+"${PATH}:/usr/local/go/bin"} +ENV GOCACHE=/go-cache +ENV GOMODCACHE=/gomod-cache WORKDIR /workspace # Copy the Go Modules manifests COPY go.mod go.mod COPY go.sum go.sum -# cache deps before building and copying source so that we don't need to re-download as much -# and so that source changes don't invalidate our downloaded layer -RUN go mod download # Copy the go source +COPY LICENSE.txt LICENSE.txt +COPY THIRD_PARTY_LICENSES_DOCKER.txt THIRD_PARTY_LICENSES_DOCKER.txt COPY main.go main.go COPY apis/ apis/ -COPY controllers/ controllers/ COPY commons/ commons/ -COPY LICENSE.txt LICENSE.txt -COPY THIRD_PARTY_LICENSES_DOCKER.txt THIRD_PARTY_LICENSES_DOCKER.txt +COPY controllers/ controllers/ # Build -RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} GO111MODULE=on go build -a -o manager main.go +RUN --mount=type=cache,target=/go-cache --mount=type=cache,target=/gomod-cache CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} GO111MODULE=on go build -o manager main.go -# Use oraclelinux:9 as base image to package the manager binary -FROM oraclelinux:9 +# Use oraclelinux:9-slim as default base image to package the manager binary +FROM ${RUNNER_IMG} +# Labels +# ------ +LABEL "provider"="Oracle" \ + "issues"="https://github.com/oracle/oracle-database-operator/issues" \ + "maintainer"="paramdeep.saini@oracle.com, sanjay.singh@oracle.com, kuassi.mensah@oracle.com" \ + "version"="2.0" \ + "description"="DB Operator Image V2.0" \ + "vendor"="Oracle Coporation" \ + "release"="2.0" \ + "summary"="Oracle Database Operator 2.0" \ + "name"="oracle-database-operator.v2.0" ARG CI_COMMIT_SHA ARG CI_COMMIT_BRANCH ENV COMMIT_SHA=${CI_COMMIT_SHA} \ @@ -47,6 +58,10 @@ ENV COMMIT_SHA=${CI_COMMIT_SHA} \ WORKDIR / COPY --from=builder /workspace/manager . COPY ords/ords_init.sh . +COPY ords/ords_start.sh . +COPY LICENSE.txt /licenses/ +COPY THIRD_PARTY_LICENSES_DOCKER.txt /licenses/ +COPY THIRD_PARTY_LICENSES.txt /licenses/ RUN useradd -u 1002 nonroot USER nonroot diff --git a/Makefile b/Makefile index b9755e6f..d27bb5f2 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ # # Current Operator version -VERSION ?= 0.0.1 +VERSION ?= 2.0 # Default bundle image tag BUNDLE_IMG ?= controller-bundle:$(VERSION) # Options for 'bundle-build' @@ -23,7 +23,7 @@ IMG ?= controller:latest # https://github.com/kubernetes-sigs/kubebuilder/issues/1140 CRD_OPTIONS ?= "crd:maxDescLen=0,allowDangerousTypes=true" # ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. -ENVTEST_K8S_VERSION = 1.29.0 +ENVTEST_K8S_VERSION = 1.31.0 # Operator YAML file OPERATOR_YAML=$$(basename $$(pwd)).yaml # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) @@ -70,12 +70,14 @@ build: generate fmt vet ## Build manager binary. run: manifests generate fmt vet ## Run a controller from your host. go run ./main.go -GOLANG_VERSION ?= 1.23.3 +GOLANG_VERSION ?= 1.25.1 +DOCKER ?= podman ## Download golang in the Dockerfile if BUILD_INTERNAL is set to true. ## Otherwise, use golang image from docker hub as the builder. ifeq ($(BUILD_INTERNAL), true) BUILDER_IMG = oraclelinux:9 BUILD_ARGS = --build-arg BUILDER_IMG=$(BUILDER_IMG) --build-arg GOLANG_VERSION=$(GOLANG_VERSION) --build-arg INSTALL_GO=true +DOCKER = podman else BUILDER_IMG = golang:$(GOLANG_VERSION) BUILD_ARGS = --build-arg BUILDER_IMG=$(BUILDER_IMG) --build-arg INSTALL_GO="false" --build-arg GOLANG_VERSION=$(GOLANG_VERSION) @@ -86,18 +88,18 @@ PUSH_ARGS := manifest else BUILD_ARGS := $(BUILD_ARGS) --platform=linux/amd64 --tag endif -docker-build: #manifests generate fmt vet #test ## Build docker image with the manager. Disable the test but keep the validations to fail fast - docker build --no-cache=true --build-arg http_proxy=$(HTTP_PROXY) --build-arg https_proxy=$(HTTPS_PROXY) \ +image-build: #manifests generate fmt vet #test ## Build docker image with the manager. Disable the test but keep the validations to fail fast + $(DOCKER) build --build-arg http_proxy=$(HTTP_PROXY) --build-arg https_proxy=$(HTTPS_PROXY) \ --build-arg CI_COMMIT_SHA=$(CI_COMMIT_SHA) --build-arg CI_COMMIT_BRANCH=$(CI_COMMIT_BRANCH) \ $(BUILD_ARGS) $(IMG) . - -docker-push: ## Push docker image with the manager. - docker $(PUSH_ARGS) push $(IMG) + +image-push: ## Push docker image with the manager. + $(DOCKER) $(PUSH_ARGS) push $(IMG) # Push to minikube's local registry enabled by registry add-on minikube-push: - docker tag $(IMG) $$(minikube ip):5000/$(IMG) - docker push --tls-verify=false $$(minikube ip):5000/$(IMG) + $(DOCKER) tag $(IMG) $$(minikube ip):5000/$(IMG) + $(DOCKER) push --tls-verify=false $$(minikube ip):5000/$(IMG) ##@ Deployment @@ -123,7 +125,6 @@ operator-yaml: manifests kustomize (echo --- && sed '/^apiVersion: apps\/v1/,/---/!d' "$(OPERATOR_YAML).bak") >> "$(OPERATOR_YAML)" rm "$(OPERATOR_YAML).bak" -minikube-operator-yaml: IMG:=localhost:5000/$(IMG) minikube-operator-yaml: operator-yaml sed -i.bak 's/\(replicas.\) 3/\1 1/g' "$(OPERATOR_YAML)" rm "$(OPERATOR_YAML).bak" @@ -144,8 +145,8 @@ CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen ENVTEST ?= $(LOCALBIN)/setup-envtest ## Tool Versions -KUSTOMIZE_VERSION ?= v5.3.0 -CONTROLLER_TOOLS_VERSION ?= v0.16.5 +KUSTOMIZE_VERSION ?= v5.7.1 +CONTROLLER_TOOLS_VERSION ?= v0.17 KUSTOMIZE_INSTALL_SCRIPT ?= "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" .PHONY: kustomize @@ -173,11 +174,11 @@ bundle: manifests kustomize ## Generate bundle manifests and metadata, then vali .PHONY: bundle-build bundle-build: ## Build the bundle image. - docker build -f bundle.Dockerfile -t $(BUNDLE_IMG) . + $(DOCKER) build -f bundle.Dockerfile -t $(BUNDLE_IMG) . .PHONY: bundle-push bundle-push: ## Push the bundle image. - $(MAKE) docker-push IMG=$(BUNDLE_IMG) + $(MAKE) image-push IMG=$(BUNDLE_IMG) .PHONY: opm OPM = ./bin/opm @@ -218,4 +219,4 @@ catalog-build: opm ## Build a catalog image. # Push the catalog image. .PHONY: catalog-push catalog-push: ## Push a catalog image. - $(MAKE) docker-push IMG=$(CATALOG_IMG) + $(MAKE) image-push IMG=$(CATALOG_IMG) diff --git a/PROJECT b/PROJECT index 97e9409c..6d4826f8 100644 --- a/PROJECT +++ b/PROJECT @@ -4,7 +4,7 @@ # More info: https://book.kubebuilder.io/reference/project-config.html domain: oracle.com layout: -- go.kubebuilder.io/v2 +- go.kubebuilder.io/v4 multigroup: true plugins: manifests.sdk.operatorframework.io/v2: {} @@ -259,4 +259,30 @@ resources: webhooks: conversion: true webhookVersion: v1beta1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: oracle.com + group: omlai + kind: PrivateAi + path: github.com/oracle/oracle-database-operator/api/omlai/v4 + version: v4 + webhooks: + defaulting: true + validation: true + webhookVersion: v1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: oracle.com + group: database + kind: OracleRestart + path: github.com/oracle/oracle-database-operator/api/database/v4 + version: v4 + webhooks: + defaulting: true + validation: true + webhookVersion: v1 version: "3" diff --git a/README.md b/README.md index 936ae23a..da6a179e 100644 --- a/README.md +++ b/README.md @@ -4,79 +4,117 @@ As part of Oracle's resolution to make Oracle Database Kubernetes native (that is, observable and operable by Kubernetes), Oracle released the _Oracle Database Operator for Kubernetes_ (`OraOperator` or the operator). OraOperator extends the Kubernetes API with custom resources and controllers for automating the management of the Oracle Database lifecycle. -## Supported Database Configurations in V1.2.0 -In this v1.2.0 production release, `OraOperator` supports the following database configurations, and controllers: - -* Oracle Autonomous Database: +## What's New in v2.0.0 + +* **RedHat OpenShift Certification** + * Validation of OraOperator and Controllers + * Inclusion in RedHat Operators Catalog +* **Restart Controller** + * Provision, add & delete asm disks, and more +* **ORDS Service** + * ServiceAccount and OpenShift support + * Auto download of APEX installation files and APEX image on a Persistent Volume +* **Integrations** + * Private Cloud Appliance (PCA) + * Compute Cloud@Customer (C3) +* **Bug fixes** + * Bugs filed through Oracle Support + * GitHub issues + + +## Supported Database Configurations v2.0.0 +In this v2.0 production release, `OraOperator` supports the following database configurations and controllers: + +* **Oracle Autonomous Database:** * Oracle Autonomous Database shared Oracle Cloud Infrastructure (OCI) (ADB-S) * Oracle Autonomous Database on dedicated Cloud infrastructure (ADB-D) - * Oracle Autonomous Container Database (ACD), the infrastructure for provisioning Autonomous Databases. -* Containerized Single Instance databases (SIDB) deployed in the Oracle Kubernetes Engine (OKE) and any k8s where OraOperator is deployed -* Containerized Oracle Globally Distributed Databases(GDD) deployed in OKE and any k8s where OraOperator is deployed -* Oracle Multitenant Databases (CDB/PDBs) -* Oracle Base Database Service (OBDS) on Oracle Cloud Infrastructure (OCI) -* Oracle Data Guard -* Oracle Database Observability -* Oracle Database Rest Service (ORDS) instances - -## New Lifecycle Features in V1.2.0 Release (Controllers Enhancements) -* ORDSSERVICES + * Oracle Autonomous Container Database (ACD), the infrastructure for provisioning Autonomous Databases +* **Containerized Single Instance databases (SIDB)** deployed in the Oracle Kubernetes Engine (OKE) and any Kubernetes where OraOperator is deployed +* **Containerized Oracle Globally Distributed Databases (GDD)** deployed in OKE and any Kubernetes where OraOperator is deployed +* **Oracle Multitenant Databases (CDB/PDBs)** +* **Oracle Base Database Service (OBDS)** on Oracle Cloud Infrastructure (OCI) +* **Oracle Data Guard** +* **Oracle Database Observability** +* **Oracle Database Rest Service (ORDS) instances** +* **Oracle Restart** +* **Oracle Globally Distributed Database** + +--- + +## Lifecycle Features (Controllers Enhancements) +* **ORDS Services** - Install on SIDB and ADB - - Provision and Delete ORDS instances -* SIDB + - Provision and delete ORDS instances +* **SIDB** - Oracle Database 23ai Free support - Oracle Database 23ai Free-lite support - SIDB resource management - True Cache support for Free SIDB databases (Preview) - Observer for FastStartFailover with Data Guard - Snapshot Standby support in Data Guard setup -* Globally Distributed Database : Support for Oracle Database 23ai Raft replication -* Autonomous Database: support for Database cloning -* Multitenant DB: - - ORDS-based Controller: assertive deletion policy. - - New LRES based Controller (ARM & AM) +* **Globally Distributed Database** + - Support for Oracle Database 23ai Raft replication + - Oracle Database 23ai Free support +* **Autonomous Database** + - Support for Database manual failover and switchover +* **Multitenant DB** + - ORDS-based Controller: Assertive deletion policy + - New LRES-based Controller (ARM & AM) - PDBs settings with init parameters config map - - Assertive deletion policy. -* Database Observability (preview) - - Support for Database Logs (in addition to Metrics) + - Assertive deletion policy +* **Database Observability ** + - Support for Database Logs and Metrics - Support for the latest Exporter container images - - Bug Fix: Prometheus label config -* Oracle Base Database Service: support for Oracle Database 23ai Cloning, using KMS Vaults, PDB creation. - -## New Product Features -*The Operator itself, as a product, brings the following new features: -* Published on `operatorhub.io` + +* **Oracle Base Database Service** + - Support for Oracle Database 23ai Cloning, using KMS Vaults + - PDB creation + - Clone, Backup, Restore + - Data Guard Setup + - Patching and Upgrade +* **Oracle Restart** + - Support for Oracle Database 19c + +--- + +## Product Features +* Upgraded Kubernetes API version to v4 +* Published on `operatorhub.io` * Operator Lifecycle Manager (OLM) support (install from `operatorhub.io`) -* Validated on Google Kubernetes Engine +* OpenShift certified -## Overall Features Summary +--- -This release of Oracle Database Operator for Kubernetes (the operator) supports the following lifecycle operations: +## Overall Features Summary +As of v2.0.0, the Oracle Database Operator for Kubernetes (`OraOperator`) supports the following lifecycle operations: -* ADB-S/ADB-D: Provision, bind, start, stop, terminate (soft/hard), scale (up/down), long-term backup, manual restore, cloning. -* ACD: Provision, bind, restart, terminate (soft/hard) -* SIDB: Provision, clone, patch (in-place/out-of-place), update database initialization parameters, update database configuration (Flashback, archiving), Oracle Enterprise Manager (EM) Express (basic console), Oracle REST Data Service (ORDS) to support REST based SQL, PDB management, SQL Developer Web, Application Express (Apex), Resource management, True Cache, Observer for FastStartFailover (Data Guard), and Snapshot Standby (Data Guard) -* ORDS Services: Provision and delete ORDS instances -* Globally Distrib. (Sharded): Provision/deploy sharded databases and the shard topology, Add a new shard, Delete an existing shard, Raft replication. -* Oracle Multitenant Database (choice of controller): Bind to a CDB, Create a  PDB, Plug a  PDB, Unplug a PDB, Delete a PDB, Clone a PDB, Open/Close a PDB, Assertive deletion policy -* Oracle Base Database Service (OBDS): Provision, bind, scale shape Up/Down, Scale Storage Up, Terminate and Update License, Cloning, PDB creation, using KMS Vaults on Oracle Cloud Infrastructure (OCI) -* Oracle Data Guard: Provision a Standby for the SIDB resource, Create a Data Guard Configuration, Perform a Switchover, Patch Primary and Standby databases in Data Guard Configuration -* Oracle Database Observability: create, patch, delete `databaseObserver` resources (Logs and Metrics) -* Watch over a set of namespaces or all the namespaces in the cluster using the `WATCH_NAMESPACE` environment variable of the operator deployment +* **ADB-S/ADB-D**: Provision, bind, start, stop, terminate (soft/hard), scale (up/down), long-term backup, manual restore, cloning, manual failover, switchover +* **ACD**: Provision, bind, restart, terminate (soft/hard) +* **SIDB**: Provision, clone, patch (in-place/out-of-place), update database initialization parameters, update database configuration (Flashback, archiving), Oracle Enterprise Manager (EM) Express (basic console), Oracle REST Data Service (ORDS), PDB management, SQL Developer Web, Application Express (Apex), Resource management, True Cache, Observer for FastStartFailover (Data Guard), Snapshot Standby (Data Guard) +* **ORDS Services**: Provision and delete ORDS instances +* **Globally Distributed (Sharded)**: Provision/deploy sharded databases and topology, add/delete shards, Raft replication +* **Oracle Multitenant Database**: Bind to a CDB, create/plug/unplug/delete/clone/open/close PDBs, assertive deletion policy +* **Oracle Base Database Service (OBDS)**: Provision, scale shape/storage, terminate/update license, cloning, PDB creation, using KMS Vaults, backup/restore, Data Guard setup, patching/upgrade +* **Oracle Data Guard**: Provision standby for SIDB, create Data Guard configuration, perform switchover, patch primary/standby +* **Oracle Database Observability**: Create, patch, delete `databaseObserver` (logs and metrics) +* **Oracle Restart**: Provision, add & delete asm disks, enable ons ports, custom storage class support, existing pvc support, load balancer support +* Watch namespaces using the `WATCH_NAMESPACE` environment variable ## Release Status -This production release has been installed and tested on the following Kubernetes platforms: - -* [Oracle Container Engine for Kubernetes (OKE)](https://www.oracle.com/cloud-native/container-engine-kubernetes/) with Kubernetes 1.24 -* [Oracle Linux Cloud Native Environment(OLCNE)](https://docs.oracle.com/en/operating-systems/olcne/) 1.6 +This production release has been installed and tested on: +* [Oracle Container Engine for Kubernetes (OKE)](https://www.oracle.com/cloud-native/container-engine-kubernetes/) with Kubernetes 1.30 or later +* [Redhat Openshift](https://www.redhat.com/en/technologies/cloud-computing/openshift) with version v4.16 or later +* [Oracle Linux Cloud Native Environment(OLCNE)](https://docs.oracle.com/en/operating-systems/olcne/) 1.9 or later +* [Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine/docs) * [Azure Kubernetes Service](https://azure.microsoft.com/en-us/services/kubernetes-service/) * [Amazon Elastic Kubernetes Service](https://aws.amazon.com/eks/) * [Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine/docs) * [Red Hat OKD](https://www.okd.io/) * [Minikube](https://minikube.sigs.k8s.io/docs/) with version v1.29.0 + ## Prerequisites Oracle strongly recommends that you ensure your system meets the following [Prerequisites](./PREREQUISITES.md). @@ -140,26 +178,20 @@ Oracle strongly recommends that you ensure your system meets the following [Prer ```sh kubectl apply -f rbac/node-rbac.yaml ``` -## Installation -### Install Oracle DB Operator - - After you have completed the preceding prerequisite changes, you can install the operator. To install the operator in the cluster quickly, you can apply the modified `oracle-database-operator.yaml` file from the preceding step. - Run the following command +## Install Oracle DB Operator - ```sh - kubectl apply -f oracle-database-operator.yaml - ``` + After you have completed the preceding prerequisite changes, you can install the operator using one of the following methods: -## Install Oracle DB Operator +### Option 1: Install Using `oracle-database-operator.yaml` - After you have completed the preceding prerequisite changes, you can install the operator. To install the operator in the cluster quickly, you can apply the modified `oracle-database-operator.yaml` file from the preceding step. + To install the operator in the cluster quickly, you can apply the modified `oracle-database-operator.yaml` file from the preceding step. - Run the following command + Run the following command - ```sh - kubectl apply -f oracle-database-operator.yaml - ``` + ```sh + kubectl apply -f oracle-database-operator.yaml + ``` Ensure that the operator pods are up and running. For high availability, operator pod replicas are set to a default of 3. You can scale this setting up or down. @@ -173,12 +205,20 @@ Oracle strongly recommends that you ensure your system meets the following [Prer ``` -* Check the resources +### Option 2: Install via OperatorHub.io + + You can also install the Oracle DB Operator from [OperatorHub.io](https://operatorhub.io/operator/oracle-database-operator). + + 1. Visit the [Oracle Database Operator](https://operatorhub.io/operator/oracle-database-operator) page on OperatorHub.io. + + 2. Click the **Install** button to view and follow the step-by-step installation instructions for your Kubernetes environment. + +### Check the resources You should see that the operator is up and running, along with the shipped controllers. For more details, see [Oracle Database Operator Installation Instructions](./docs/installation/OPERATOR_INSTALLATION_README.md). -## Documentation + ## Getting Started with the Operator (Quickstart) The following quickstarts are designed for specific database configurations: @@ -190,6 +230,7 @@ The following quickstarts are designed for specific database configurations: * [Oracle Multitenant Database](./docs/multitenant/README.md) * [Oracle Base Database Service (OBDS)](./docs/dbcs/README.md) * [ORDS Services (ORDSSRVS)](./docs/ordsservices/README.md) +* [Oracle Restart](./docs/oraclerestart/README.md) The following quickstart is designed for non-database configurations: @@ -229,6 +270,10 @@ YAML file templates are available under [`/config/samples`](./config/samples/). * ### Delete the Deployment + #### Option1: Delete `oracle-database-operator.yaml` + + Use this option if you install the operator using `oracle-database-operator.yaml` + After all CRD instances are deleted, it is safe to remove the CRDs, APIServices and operator deployment. To remove these files, use the following command: ```sh @@ -237,6 +282,25 @@ YAML file templates are available under [`/config/samples`](./config/samples/). Note: If the CRD instances are not deleted, and the operator is deleted by using the preceding command, then operator deployment and instance objects (pods, services, PVCs, and so on) are deleted. However, if that happens, then the CRD deletion stops responding. This is because the CRD instances have properties that prevent their deletion, and that can only be removed by the operator pod, which is deleted when the APIServices are deleted. + #### Option2: Delete the Operator’s ClusterServiceVersion (CSV) + + Use this option if you install the operation from OperatorHub.io. + + First, identify the name of the installed operator’s ClusterServiceVersion (CSV) using the following command: + + ```sh + kubectl clusterserviceversion -n operators + ``` + + Look for a CSV name similar to oracle-database-operator.vx.x.x. + + Once identified, delete the ClusterServiceVersion with the following command (replace the placeholder with the actual CSV name): + + ```sh + kubectl delete clusterserviceversion oracle-database-operator.vx.x.x -n operators + ``` + + ## Documentation for the supported Oracle Database configurations * [Oracle Autonomous Database](https://docs.oracle.com/en-us/iaas/Content/Database/Concepts/adboverview.htm) @@ -245,6 +309,7 @@ YAML file templates are available under [`/config/samples`](./config/samples/). * [Oracle Globally Distributed Database](https://docs.oracle.com/en/database/oracle/oracle-database/21/shard/index.html) * [Oracle Database Cloud Service](https://docs.oracle.com/en/database/database-cloud-services.html) + ## Contributing This project welcomes contributions from the community. Before submitting a pull request, please [review our contribution guide](./CONTRIBUTING.md) @@ -270,6 +335,10 @@ The following is an example of a YAML file fragment for specifying Oracle Cloud Examples in this repository where passwords are entered on the command line are for demonstration purposes only. +### Reporting a Security Issue + +See [Reporting security vulnerabilities](./SECURITY.md) + ## License Copyright (c) 2022, 2025 Oracle and/or its affiliates. diff --git a/THIRD_PARTY_LICENSES.txt b/THIRD_PARTY_LICENSES.txt index 14e4308f..d03e4432 100644 --- a/THIRD_PARTY_LICENSES.txt +++ b/THIRD_PARTY_LICENSES.txt @@ -1,5 +1,5 @@ ------------------------------------- -Operator SDK 1.37.0 +Operator SDK 1.40.0 https://github.com/operator-framework/operator-sdk Apache 2.0 @@ -208,7 +208,7 @@ Apache License: limitations under the License. ------------------------------ - GO lang 1.23.3 + GO lang 1.25.1 https://github.com/golang @@ -241,18 +241,18 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ------------------------- -apimachinery 0.31.3 +apimachinery 0.32.2 https://github.com/kubernetes/apimachinery/tr Apache 2.0 ------------------------- -controller-runtime 0.19.3 +controller-runtime 0.21.0 https://github.com/kubernetes-sigs/controller-runtime/releases/tag/v0.16.3 Apache 2.0 ------------------------- -golang 1.23.3 -https://github.com/golang/go/releases/tag/go1.21.4 +golang 1.25.1 +https://github.com/golang/go/releases/tag/go1.25.1 BSD 2-clause or 3-clause License: @@ -1006,13 +1006,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. limitations under the License. -------------------------- -Logr 1.4.2 +Logr 1.4.3 https://pkg.go.dev/github.com/go-logr/logr -https://github.com/go-logr/logr/tree/v1.3.0 +https://github.com/go-logr/logr/tree/v1.4.3 Apache 2.0 License ------------------------- -OCI Go SDK 65.77.1 +OCI Go SDK 65.95.0 https://github.com/oracle/oci-go-sdk/releases/tag/v65.53.0 Dual-License: UPL + Apache 2.0 @@ -1063,7 +1063,7 @@ The Universal Permissive License (UPL), Version 1.0 ------------------------- -ginkgo 2.202. +ginkgo 2.23.4 https://github.com/onsi/ginkgo/releases/tag/v2.13.1 MIT ------------------------------------ @@ -1073,48 +1073,48 @@ MIT License Copyright (c) 2013-2014 Onsi Fakhouri ---------------------------- -gomega 1.34.2 +gomega 1.37.0 http://onsi.github.io/gomega/ MIT ------------------------- -Kubernetes api 0.31.3 +Kubernetes api 0.33.2 https://pkg.go.dev/k8s.io/api Apache 2.0 ---------------------------------- -Kubernetes apimachinery 0.31.3 +Kubernetes apimachinery 0.32.2 https://pkg.go.dev/k8s.io/apimachinery Apache 2.0 ----------------------------------- -Kubernetes client-go 0.31.3 +Kubernetes client-go 0.33.2 https://pkg.go.dev/k8s.io/client-go Apache 2.0 ------------------------------------- -Kubernetes controller-runtime project 0.19.3 +Kubernetes controller-runtime project 0.21.0 https://pkg.go.dev/sigs.k8s.io/controller-runtime Apache 2.0 ------------------------------------ -kubernetes-sigs/yaml 1.4.0 -https://github.com/kubernetes-sigs/yaml/tree/v1.3.0 +kubernetes-sigs/yaml 1.5.0 +https://github.com/kubernetes-sigs/yaml/tree/v1.5.0 MIT ------------------------- -OCI SDK for Go 65.77.1 +OCI SDK for Go 65.95.0 https://github.com/oracle/oci-go-sdk Multiple Licenses: Apache 2.0, UPL ------------------------------ -Operator Lifecycle Manager (OLM) 0.30.0 +Operator Lifecycle Manager (OLM) 0.32.0 github.com/operator-framework/operator-lifecycle-manager Apache 2.0 ------------------------------------ -Prometheus Operator 0.78.2 +Prometheus Operator 0.83.0 https://github.com/prometheus-operator/prometheus-operator Apache 2.0 diff --git a/apis/database/v1alpha1/adbfamily_common_spec.go b/apis/database/v1alpha1/adbfamily_common_spec.go index 74eb9f94..e7a9b8c4 100644 --- a/apis/database/v1alpha1/adbfamily_common_spec.go +++ b/apis/database/v1alpha1/adbfamily_common_spec.go @@ -57,11 +57,11 @@ type K8sAdbSpec struct { } type OciAdbSpec struct { - Ocid *string `json:"ocid,omitempty"` + Id *string `json:"id,omitempty"` } // TargetSpec defines the spec of the target for backup/restore runs. type TargetSpec struct { - K8sAdb K8sAdbSpec `json:"k8sADB,omitempty"` - OciAdb OciAdbSpec `json:"ociADB,omitempty"` + K8sAdb K8sAdbSpec `json:"k8sAdb,omitempty"` + OciAdb OciAdbSpec `json:"ociAdb,omitempty"` } diff --git a/apis/database/v1alpha1/autonomouscontainerdatabase_webhook.go b/apis/database/v1alpha1/autonomouscontainerdatabase_webhook.go index 10a16cd1..fc46cfb3 100644 --- a/apis/database/v1alpha1/autonomouscontainerdatabase_webhook.go +++ b/apis/database/v1alpha1/autonomouscontainerdatabase_webhook.go @@ -39,6 +39,8 @@ package v1alpha1 import ( + "context" + dbv4 "github.com/oracle/oracle-database-operator/apis/database/v4" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -56,39 +58,42 @@ var autonomouscontainerdatabaselog = logf.Log.WithName("autonomouscontainerdatab func (r *AutonomousContainerDatabase) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). For(r). + WithValidator(r). Complete() } //+kubebuilder:webhook:verbs=create;update,path=/validate-database-oracle-com-v1alpha1-autonomouscontainerdatabase,mutating=false,failurePolicy=fail,sideEffects=None,groups=database.oracle.com,resources=autonomouscontainerdatabases,versions=v1alpha1,name=vautonomouscontainerdatabasev1alpha1.kb.io,admissionReviewVersions=v1 -var _ webhook.Validator = &AutonomousContainerDatabase{} +var _ webhook.CustomValidator = &AutonomousContainerDatabase{} // ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (r *AutonomousContainerDatabase) ValidateCreate() (admission.Warnings, error) { - autonomouscontainerdatabaselog.Info("validate create", "name", r.Name) +func (r *AutonomousContainerDatabase) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { return nil, nil } // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *AutonomousContainerDatabase) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - var allErrs field.ErrorList - var oldACD *AutonomousContainerDatabase = old.(*AutonomousContainerDatabase) +func (r *AutonomousContainerDatabase) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { + var ( + allErrs field.ErrorList + oldAcd *AutonomousContainerDatabase = oldObj.(*AutonomousContainerDatabase) + newAcd *AutonomousContainerDatabase = newObj.(*AutonomousContainerDatabase) + ) - autonomouscontainerdatabaselog.Info("validate update", "name", r.Name) + autonomouscontainerdatabaselog.Info("validate update", "name", newAcd.Name) // skip the update of adding ADB OCID or binding - if oldACD.Status.LifecycleState == "" { + if oldAcd.Status.LifecycleState == "" { return nil, nil } // cannot update when the old state is in intermediate state, except for the terminate operatrion - var copiedSpec *AutonomousContainerDatabaseSpec = r.Spec.DeepCopy() - changed, err := dbv4.RemoveUnchangedFields(oldACD.Spec, copiedSpec) + var copiedSpec *AutonomousContainerDatabaseSpec = newAcd.Spec.DeepCopy() + changed, err := dbv4.RemoveUnchangedFields(oldAcd.Spec, copiedSpec) if err != nil { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), err.Error())) } - if dbv4.IsACDIntermediateState(oldACD.Status.LifecycleState) && changed { + if dbv4.IsACDIntermediateState(oldAcd.Status.LifecycleState) && changed { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), "cannot change the spec when the lifecycleState is in an intermdeiate state")) @@ -99,13 +104,10 @@ func (r *AutonomousContainerDatabase) ValidateUpdate(old runtime.Object) (admiss } return nil, apierrors.NewInvalid( schema.GroupKind{Group: "database.oracle.com", Kind: "AutonomousContainerDatabase"}, - r.Name, allErrs) + newAcd.Name, allErrs) } // ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *AutonomousContainerDatabase) ValidateDelete() (admission.Warnings, error) { - autonomouscontainerdatabaselog.Info("validate delete", "name", r.Name) - - // TODO(user): fill in your validation logic upon object deletion. +func (r *AutonomousContainerDatabase) ValidateDelete(context.Context, runtime.Object) (admission.Warnings, error) { return nil, nil } diff --git a/apis/database/v1alpha1/autonomousdatabase_conversion.go b/apis/database/v1alpha1/autonomousdatabase_conversion.go index ffccc181..fe3cbc7f 100644 --- a/apis/database/v1alpha1/autonomousdatabase_conversion.go +++ b/apis/database/v1alpha1/autonomousdatabase_conversion.go @@ -224,7 +224,7 @@ func (src *AutonomousDatabaseBackup) ConvertTo(dstRaw conversion.Hub) error { dst := dstRaw.(*v4.AutonomousDatabaseBackup) dst.Spec.Target.K8sAdb.Name = src.Spec.Target.K8sAdb.Name - dst.Spec.Target.OciAdb.OCID = src.Spec.Target.OciAdb.Ocid + dst.Spec.Target.OciAdb.Id = src.Spec.Target.OciAdb.Id dst.Spec.DisplayName = src.Spec.DisplayName dst.Spec.AutonomousDatabaseBackupOCID = src.Spec.AutonomousDatabaseBackupOCID dst.Spec.IsLongTermBackup = src.Spec.IsLongTermBackup @@ -250,7 +250,7 @@ func (dst *AutonomousDatabaseBackup) ConvertFrom(srcRaw conversion.Hub) error { src := srcRaw.(*v4.AutonomousDatabaseBackup) dst.Spec.Target.K8sAdb.Name = src.Spec.Target.K8sAdb.Name - dst.Spec.Target.OciAdb.Ocid = src.Spec.Target.OciAdb.OCID + dst.Spec.Target.OciAdb.Id = src.Spec.Target.OciAdb.Id dst.Spec.DisplayName = src.Spec.DisplayName dst.Spec.AutonomousDatabaseBackupOCID = src.Spec.AutonomousDatabaseBackupOCID dst.Spec.IsLongTermBackup = src.Spec.IsLongTermBackup @@ -276,7 +276,7 @@ func (src *AutonomousDatabaseRestore) ConvertTo(dstRaw conversion.Hub) error { dst := dstRaw.(*v4.AutonomousDatabaseRestore) dst.Spec.Target.K8sAdb.Name = src.Spec.Target.K8sAdb.Name - dst.Spec.Target.OciAdb.OCID = src.Spec.Target.OciAdb.Ocid + dst.Spec.Target.OciAdb.Id = src.Spec.Target.OciAdb.Id dst.Spec.Source.K8sAdbBackup.Name = src.Spec.Source.K8sAdbBackup.Name dst.Spec.Source.PointInTime.Timestamp = src.Spec.Source.PointInTime.Timestamp dst.Spec.OCIConfig.ConfigMapName = src.Spec.OCIConfig.ConfigMapName @@ -298,7 +298,7 @@ func (dst *AutonomousDatabaseRestore) ConvertFrom(srcRaw conversion.Hub) error { src := srcRaw.(*v4.AutonomousDatabaseRestore) dst.Spec.Target.K8sAdb.Name = src.Spec.Target.K8sAdb.Name - dst.Spec.Target.OciAdb.Ocid = src.Spec.Target.OciAdb.OCID + dst.Spec.Target.OciAdb.Id = src.Spec.Target.OciAdb.Id dst.Spec.Source.K8sAdbBackup.Name = src.Spec.Source.K8sAdbBackup.Name dst.Spec.Source.PointInTime.Timestamp = src.Spec.Source.PointInTime.Timestamp dst.Spec.OCIConfig.ConfigMapName = src.Spec.OCIConfig.ConfigMapName diff --git a/apis/database/v1alpha1/autonomousdatabase_types.go b/apis/database/v1alpha1/autonomousdatabase_types.go index 099703c2..5cb1aa80 100644 --- a/apis/database/v1alpha1/autonomousdatabase_types.go +++ b/apis/database/v1alpha1/autonomousdatabase_types.go @@ -49,7 +49,7 @@ import ( // AutonomousDatabaseSpec defines the desired state of AutonomousDatabase // Important: Run "make" to regenerate code after modifying this file type AutonomousDatabaseSpec struct { - // +kubebuilder:validation:Enum:="";Create;Sync;Update;Stop;Start;Terminate;Clone + // +kubebuilder:validation:Enum:="";Create;Sync;Update;Stop;Start;Terminate;Clone;Switchover;Failover Action string `json:"action"` Details AutonomousDatabaseDetails `json:"details,omitempty"` Clone AutonomousDatabaseClone `json:"clone,omitempty"` @@ -194,7 +194,7 @@ type ConnectionStringSpec struct { // +kubebuilder:printcolumn:JSONPath=".spec.details.dbName",name="Db Name",type=string // +kubebuilder:printcolumn:JSONPath=".status.lifecycleState",name="State",type=string // +kubebuilder:printcolumn:JSONPath=".spec.details.isDedicated",name="Dedicated",type=string -// +kubebuilder:printcolumn:JSONPath=".spec.details.cpuCoreCount",name="OCPUs",type=integer +// +kubebuilder:printcolumn:JSONPath=".spec.details.computeCount",name="Compute Count",type=number // +kubebuilder:printcolumn:JSONPath=".spec.details.dataStorageSizeInTBs",name="Storage (TB)",type=integer // +kubebuilder:printcolumn:JSONPath=".spec.details.dbWorkload",name="Workload Type",type=string // +kubebuilder:printcolumn:JSONPath=".status.timeCreated",name="Created",type=string diff --git a/apis/database/v1alpha1/autonomousdatabase_webhook.go b/apis/database/v1alpha1/autonomousdatabase_webhook.go index e209ae7a..8ab9b5e8 100644 --- a/apis/database/v1alpha1/autonomousdatabase_webhook.go +++ b/apis/database/v1alpha1/autonomousdatabase_webhook.go @@ -39,6 +39,8 @@ package v1alpha1 import ( + "context" + dbcommons "github.com/oracle/oracle-database-operator/commons/database" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -56,29 +58,32 @@ var autonomousdatabaselog = logf.Log.WithName("autonomousdatabase-resource") func (r *AutonomousDatabase) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). For(r). + WithValidator(&AutonomousDatabase{}). Complete() } //+kubebuilder:webhook:verbs=create;update,path=/validate-database-oracle-com-v1alpha1-autonomousdatabase,mutating=false,failurePolicy=fail,sideEffects=None,groups=database.oracle.com,resources=autonomousdatabases,versions=v1alpha1,name=vautonomousdatabasev1alpha1.kb.io,admissionReviewVersions=v1 -var _ webhook.Validator = &AutonomousDatabase{} +var _ webhook.CustomValidator = &AutonomousDatabase{} // ValidateCreate implements webhook.Validator so a webhook will be registered for the type // ValidateCreate checks if the spec is valid for a provisioning or a binding operation -func (r *AutonomousDatabase) ValidateCreate() (admission.Warnings, error) { +func (r *AutonomousDatabase) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { var allErrs field.ErrorList - autonomousdatabaselog.Info("validate create", "name", r.Name) + adb := obj.(*AutonomousDatabase) + + autonomousdatabaselog.Info("validate create", "name", adb.Name) namespaces := dbcommons.GetWatchNamespaces() _, hasEmptyString := namespaces[""] isClusterScoped := len(namespaces) == 1 && hasEmptyString if !isClusterScoped { - _, containsNamespace := namespaces[r.Namespace] + _, containsNamespace := namespaces[adb.Namespace] // Check if the allowed namespaces maps contains the required namespace if len(namespaces) != 0 && !containsNamespace { allErrs = append(allErrs, - field.Invalid(field.NewPath("metadata").Child("namespace"), r.Namespace, + field.Invalid(field.NewPath("metadata").Child("namespace"), adb.Namespace, "Oracle database operator doesn't watch over this namespace")) } } @@ -88,47 +93,17 @@ func (r *AutonomousDatabase) ValidateCreate() (admission.Warnings, error) { } return nil, apierrors.NewInvalid( schema.GroupKind{Group: "database.oracle.com", Kind: "AutonomousDatabase"}, - r.Name, allErrs) + adb.Name, allErrs) } // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *AutonomousDatabase) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - var allErrs field.ErrorList - var oldAdb *AutonomousDatabase = old.(*AutonomousDatabase) - - autonomousdatabaselog.Info("validate update", "name", r.Name) - - // skip the update of adding ADB OCID or binding - // if oldAdb.Status.LifecycleState == "" { - // return nil, nil - // } - - // cannot update when the old state is in intermediate, except for the change to the hardLink or the terminate operatrion during valid lifecycleState - // var copySpec *AutonomousDatabaseSpec = r.Spec.DeepCopy() - // specChanged, err := dbv4.RemoveUnchangedFields(oldAdb.Spec, copySpec) - // if err != nil { - // allErrs = append(allErrs, - // field.Forbidden(field.NewPath("spec"), err.Error())) - // } - - // hardLinkChanged := copySpec.HardLink != nil +func (r *AutonomousDatabase) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { + var ( + allErrs field.ErrorList + newAdb = newObj.(*AutonomousDatabase) + ) - // isTerminateOp := dbv4.CanBeTerminated(oldAdb.Status.LifecycleState) && copySpec.Action == "Terminate" - - // if specChanged && dbv4.IsAdbIntermediateState(oldAdb.Status.LifecycleState) && !isTerminateOp && !hardLinkChanged { - // allErrs = append(allErrs, - // field.Forbidden(field.NewPath("spec"), - // "cannot change the spec when the lifecycleState is in an intermdeiate state")) - // } - - // cannot modify autonomousDatabaseOCID - if r.Spec.Details.Id != nil && - oldAdb.Spec.Details.Id != nil && - *r.Spec.Details.Id != *oldAdb.Spec.Details.Id { - allErrs = append(allErrs, - field.Forbidden(field.NewPath("spec").Child("details").Child("autonomousDatabaseOCID"), - "autonomousDatabaseOCID cannot be modified")) - } + autonomousdatabaselog.Info("validate update", "name", newAdb.Name) allErrs = validateCommon(r, allErrs) @@ -137,7 +112,7 @@ func (r *AutonomousDatabase) ValidateUpdate(old runtime.Object) (admission.Warni } return nil, apierrors.NewInvalid( schema.GroupKind{Group: "database.oracle.com", Kind: "AutonomousDatabase"}, - r.Name, allErrs) + newAdb.Name, allErrs) } func validateCommon(adb *AutonomousDatabase, allErrs field.ErrorList) field.ErrorList { @@ -150,7 +125,7 @@ func validateCommon(adb *AutonomousDatabase, allErrs field.ErrorList) field.Erro if adb.Spec.Wallet.Password.K8sSecret.Name != nil && adb.Spec.Wallet.Password.OciSecret.Id != nil { allErrs = append(allErrs, - field.Forbidden(field.NewPath("spec").Child("details").Child("wallet").Child("password"), + field.Forbidden(field.NewPath("spec").Child("wallet").Child("password"), "cannot apply k8sSecret.name and ociSecret.ocid at the same time")) } @@ -158,14 +133,6 @@ func validateCommon(adb *AutonomousDatabase, allErrs field.ErrorList) field.Erro } // ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *AutonomousDatabase) ValidateDelete() (admission.Warnings, error) { - autonomousdatabaselog.Info("validate delete", "name", r.Name) +func (r *AutonomousDatabase) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { return nil, nil } - -// Returns true if AutonomousContainerDatabaseOCID has value. -// We don't use Details.IsDedicated because the parameter might be null when it's a provision operation. -func isDedicated(adb *AutonomousDatabase) bool { - return adb.Spec.Details.AutonomousContainerDatabase.K8sAcd.Name != nil || - adb.Spec.Details.AutonomousContainerDatabase.OciAcd.Id != nil -} diff --git a/apis/database/v1alpha1/autonomousdatabasebackup_webhook.go b/apis/database/v1alpha1/autonomousdatabasebackup_webhook.go index ffa9b888..d4291135 100644 --- a/apis/database/v1alpha1/autonomousdatabasebackup_webhook.go +++ b/apis/database/v1alpha1/autonomousdatabasebackup_webhook.go @@ -39,6 +39,8 @@ package v1alpha1 import ( + "context" + dbcommons "github.com/oracle/oracle-database-operator/commons/database" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -56,25 +58,19 @@ var autonomousdatabasebackuplog = logf.Log.WithName("autonomousdatabasebackup-re func (r *AutonomousDatabaseBackup) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). For(r). + WithValidator(r). Complete() } -//+kubebuilder:webhook:path=/mutate-database-oracle-com-v1alpha1-autonomousdatabasebackup,mutating=true,failurePolicy=fail,sideEffects=None,groups=database.oracle.com,resources=autonomousdatabasebackups,verbs=create;update,versions=v1alpha1,name=mautonomousdatabasebackupv1alpha1.kb.io,admissionReviewVersions=v1 - -var _ webhook.Defaulter = &AutonomousDatabaseBackup{} - -// Default implements webhook.Defaulter so a webhook will be registered for the type -func (r *AutonomousDatabaseBackup) Default() { - autonomousdatabasebackuplog.Info("default", "name", r.Name) -} - //+kubebuilder:webhook:verbs=create;update,path=/validate-database-oracle-com-v1alpha1-autonomousdatabasebackup,mutating=false,failurePolicy=fail,sideEffects=None,groups=database.oracle.com,resources=autonomousdatabasebackups,versions=v1alpha1,name=vautonomousdatabasebackupv1alpha1.kb.io,admissionReviewVersions=v1 -var _ webhook.Validator = &AutonomousDatabaseBackup{} +var _ webhook.CustomValidator = &AutonomousDatabaseBackup{} // ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (r *AutonomousDatabaseBackup) ValidateCreate() (admission.Warnings, error) { - autonomousdatabasebackuplog.Info("validate create", "name", r.Name) +func (r *AutonomousDatabaseBackup) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + backup := obj.(*AutonomousDatabaseBackup) + + autonomousdatabasebackuplog.Info("validate create", "name", backup.Name) var allErrs field.ErrorList @@ -82,61 +78,59 @@ func (r *AutonomousDatabaseBackup) ValidateCreate() (admission.Warnings, error) _, hasEmptyString := namespaces[""] isClusterScoped := len(namespaces) == 1 && hasEmptyString if !isClusterScoped { - _, containsNamespace := namespaces[r.Namespace] + _, containsNamespace := namespaces[backup.Namespace] // Check if the allowed namespaces maps contains the required namespace if len(namespaces) != 0 && !containsNamespace { allErrs = append(allErrs, - field.Invalid(field.NewPath("metadata").Child("namespace"), r.Namespace, + field.Invalid(field.NewPath("metadata").Child("namespace"), backup.Namespace, "Oracle database operator doesn't watch over this namespace")) } } - if r.Spec.Target.K8sAdb.Name == nil && r.Spec.Target.OciAdb.Ocid == nil { + if backup.Spec.Target.K8sAdb.Name == nil && backup.Spec.Target.OciAdb.Id == nil { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("target"), "target ADB is empty")) } - if r.Spec.Target.K8sAdb.Name != nil && r.Spec.Target.OciAdb.Ocid != nil { - allErrs = append(allErrs, - field.Forbidden(field.NewPath("spec").Child("target"), "specify either k8sADB or ociADB, but not both")) - } - if len(allErrs) == 0 { return nil, nil } return nil, apierrors.NewInvalid( schema.GroupKind{Group: "database.oracle.com", Kind: "AutonomousDatabaseBackup"}, - r.Name, allErrs) + backup.Name, allErrs) } // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *AutonomousDatabaseBackup) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - autonomousdatabasebackuplog.Info("validate update", "name", r.Name) +func (r *AutonomousDatabaseBackup) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { + var ( + allErrs field.ErrorList + oldBackup = oldObj.(*AutonomousDatabaseBackup) + newBackup = oldObj.(*AutonomousDatabaseBackup) + ) - var allErrs field.ErrorList - oldBackup := old.(*AutonomousDatabaseBackup) + autonomousdatabasebackuplog.Info("validate update", "name", newBackup.Name) - if oldBackup.Spec.AutonomousDatabaseBackupOCID != nil && r.Spec.AutonomousDatabaseBackupOCID != nil && - *oldBackup.Spec.AutonomousDatabaseBackupOCID != *r.Spec.AutonomousDatabaseBackupOCID { + if oldBackup.Spec.AutonomousDatabaseBackupOCID != nil && newBackup.Spec.AutonomousDatabaseBackupOCID != nil && + *oldBackup.Spec.AutonomousDatabaseBackupOCID != *newBackup.Spec.AutonomousDatabaseBackupOCID { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("autonomousDatabaseBackupOCID"), "cannot assign a new autonomousDatabaseBackupOCID to this backup")) } - if oldBackup.Spec.Target.K8sAdb.Name != nil && r.Spec.Target.K8sAdb.Name != nil && - *oldBackup.Spec.Target.K8sAdb.Name != *r.Spec.Target.K8sAdb.Name { + if oldBackup.Spec.Target.K8sAdb.Name != nil && newBackup.Spec.Target.K8sAdb.Name != nil && + *oldBackup.Spec.Target.K8sAdb.Name != *newBackup.Spec.Target.K8sAdb.Name { allErrs = append(allErrs, - field.Forbidden(field.NewPath("spec").Child("target").Child("k8sADB").Child("name"), "cannot assign a new name to the target")) + field.Forbidden(field.NewPath("spec").Child("target").Child("k8sAdb").Child("name"), "cannot assign a new name to the target")) } - if oldBackup.Spec.Target.OciAdb.Ocid != nil && r.Spec.Target.OciAdb.Ocid != nil && - *oldBackup.Spec.Target.OciAdb.Ocid != *r.Spec.Target.OciAdb.Ocid { + if oldBackup.Spec.Target.OciAdb.Id != nil && newBackup.Spec.Target.OciAdb.Id != nil && + *oldBackup.Spec.Target.OciAdb.Id != *newBackup.Spec.Target.OciAdb.Id { allErrs = append(allErrs, - field.Forbidden(field.NewPath("spec").Child("target").Child("ociADB").Child("ocid"), "cannot assign a new ocid to the target")) + field.Forbidden(field.NewPath("spec").Child("target").Child("ociAdb").Child("id"), "cannot assign a new ocid to the target")) } - if oldBackup.Spec.DisplayName != nil && r.Spec.DisplayName != nil && - *oldBackup.Spec.DisplayName != *r.Spec.DisplayName { + if oldBackup.Spec.DisplayName != nil && newBackup.Spec.DisplayName != nil && + *oldBackup.Spec.DisplayName != *newBackup.Spec.DisplayName { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("displayName"), "cannot assign a new displayName to this backup")) } @@ -146,13 +140,10 @@ func (r *AutonomousDatabaseBackup) ValidateUpdate(old runtime.Object) (admission } return nil, apierrors.NewInvalid( schema.GroupKind{Group: "database.oracle.com", Kind: "AutonomousDatabaseBackup"}, - r.Name, allErrs) + newBackup.Name, allErrs) } // ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *AutonomousDatabaseBackup) ValidateDelete() (admission.Warnings, error) { - autonomousdatabasebackuplog.Info("validate delete", "name", r.Name) - - // TODO(user): fill in your validation logic upon object deletion. +func (r *AutonomousDatabaseBackup) ValidateDelete(context.Context, runtime.Object) (admission.Warnings, error) { return nil, nil } diff --git a/apis/database/v1alpha1/autonomousdatabasebackup_webhook_test.go b/apis/database/v1alpha1/autonomousdatabasebackup_webhook_test.go index 87eb1618..f36b1fff 100644 --- a/apis/database/v1alpha1/autonomousdatabasebackup_webhook_test.go +++ b/apis/database/v1alpha1/autonomousdatabasebackup_webhook_test.go @@ -72,20 +72,11 @@ var _ = Describe("test AutonomousDatabaseBackup webhook", func() { } }) - It("Should specify at least one of the k8sADB and ociADB", func() { + It("Should specify at least one of the k8sAdb and ociAdb", func() { var errMsg string = "target ADB is empty" backup.Spec.Target.K8sAdb.Name = nil - backup.Spec.Target.OciAdb.Ocid = nil - - validateInvalidTest(backup, false, errMsg) - }) - - It("Should specify either k8sADB or ociADB, but not both", func() { - var errMsg string = "specify either k8sADB or ociADB, but not both" - - backup.Spec.Target.K8sAdb.Name = common.String("fake-target-adb") - backup.Spec.Target.OciAdb.Ocid = common.String("fake.ocid1.autonomousdatabase.oc1...") + backup.Spec.Target.OciAdb.Id = nil validateInvalidTest(backup, false, errMsg) }) @@ -155,15 +146,15 @@ var _ = Describe("test AutonomousDatabaseBackup webhook", func() { }) }) - Context("The bakcup is using target.ociADB.ocid", func() { + Context("The bakcup is using target.ociAdb.id", func() { BeforeEach(func() { - backup.Spec.Target.OciAdb.Ocid = common.String("fake.ocid1.autonomousdatabase.oc1...") + backup.Spec.Target.OciAdb.Id = common.String("fake.ocid1.autonomousdatabase.oc1...") }) It("Cannot assign a new ocid to the target", func() { var errMsg string = "cannot assign a new ocid to the target" - backup.Spec.Target.OciAdb.Ocid = common.String("modified.ocid1.autonomousdatabase.oc1...") + backup.Spec.Target.OciAdb.Id = common.String("modified.ocid1.autonomousdatabase.oc1...") validateInvalidTest(backup, true, errMsg) }) diff --git a/apis/database/v1alpha1/autonomousdatabaserestore_types.go b/apis/database/v1alpha1/autonomousdatabaserestore_types.go index ef8698b2..74eb2eda 100644 --- a/apis/database/v1alpha1/autonomousdatabaserestore_types.go +++ b/apis/database/v1alpha1/autonomousdatabaserestore_types.go @@ -61,7 +61,7 @@ type PitSpec struct { } type SourceSpec struct { - K8sAdbBackup K8sAdbBackupSpec `json:"k8sADBBackup,omitempty"` + K8sAdbBackup K8sAdbBackupSpec `json:"k8sAdbBackup,omitempty"` PointInTime PitSpec `json:"pointInTime,omitempty"` } diff --git a/apis/database/v1alpha1/autonomousdatabaserestore_webhook.go b/apis/database/v1alpha1/autonomousdatabaserestore_webhook.go index dcd57137..e8696045 100644 --- a/apis/database/v1alpha1/autonomousdatabaserestore_webhook.go +++ b/apis/database/v1alpha1/autonomousdatabaserestore_webhook.go @@ -39,6 +39,8 @@ package v1alpha1 import ( + "context" + dbv4 "github.com/oracle/oracle-database-operator/apis/database/v4" dbcommons "github.com/oracle/oracle-database-operator/commons/database" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -57,59 +59,63 @@ var autonomousdatabaserestorelog = logf.Log.WithName("autonomousdatabaserestore- func (r *AutonomousDatabaseRestore) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). For(r). + WithValidator(r). Complete() } //+kubebuilder:webhook:verbs=create;update,path=/validate-database-oracle-com-v1alpha1-autonomousdatabaserestore,mutating=false,failurePolicy=fail,sideEffects=None,groups=database.oracle.com,resources=autonomousdatabaserestores,versions=v1alpha1,name=vautonomousdatabaserestorev1alpha1.kb.io,admissionReviewVersions=v1 -var _ webhook.Validator = &AutonomousDatabaseRestore{} +var _ webhook.CustomValidator = &AutonomousDatabaseRestore{} // ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (r *AutonomousDatabaseRestore) ValidateCreate() (admission.Warnings, error) { - autonomousdatabaserestorelog.Info("validate create", "name", r.Name) +func (r *AutonomousDatabaseRestore) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + var ( + allErrs field.ErrorList + restore = obj.(*AutonomousDatabaseRestore) + ) - var allErrs field.ErrorList + autonomousdatabaserestorelog.Info("validate create", "name", restore.Name) namespaces := dbcommons.GetWatchNamespaces() _, hasEmptyString := namespaces[""] isClusterScoped := len(namespaces) == 1 && hasEmptyString if !isClusterScoped { - _, containsNamespace := namespaces[r.Namespace] + _, containsNamespace := namespaces[restore.Namespace] // Check if the allowed namespaces maps contains the required namespace if len(namespaces) != 0 && !containsNamespace { allErrs = append(allErrs, - field.Invalid(field.NewPath("metadata").Child("namespace"), r.Namespace, + field.Invalid(field.NewPath("metadata").Child("namespace"), restore.Namespace, "Oracle database operator doesn't watch over this namespace")) } } // Validate the target ADB - if r.Spec.Target.K8sAdb.Name == nil && r.Spec.Target.OciAdb.Ocid == nil { + if restore.Spec.Target.K8sAdb.Name == nil && restore.Spec.Target.OciAdb.Id == nil { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("target"), "target ADB is empty")) } - if r.Spec.Target.K8sAdb.Name != nil && r.Spec.Target.OciAdb.Ocid != nil { + if restore.Spec.Target.K8sAdb.Name != nil && restore.Spec.Target.OciAdb.Id != nil { allErrs = append(allErrs, - field.Forbidden(field.NewPath("spec").Child("target"), "specify either k8sADB.name or ociADB.ocid, but not both")) + field.Forbidden(field.NewPath("spec").Child("target"), "specify either k8sAdb.name or ociAdb.id, but not both")) } // Validate the restore source - if r.Spec.Source.K8sAdbBackup.Name == nil && - r.Spec.Source.PointInTime.Timestamp == nil { + if restore.Spec.Source.K8sAdbBackup.Name == nil && + restore.Spec.Source.PointInTime.Timestamp == nil { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("source"), "retore source is empty")) } - if r.Spec.Source.K8sAdbBackup.Name != nil && - r.Spec.Source.PointInTime.Timestamp != nil { + if restore.Spec.Source.K8sAdbBackup.Name != nil && + restore.Spec.Source.PointInTime.Timestamp != nil { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("source"), "cannot apply backupName and the PITR parameters at the same time")) } // Verify the timestamp format if it's PITR - if r.Spec.Source.PointInTime.Timestamp != nil { - _, err := dbv4.ParseDisplayTime(*r.Spec.Source.PointInTime.Timestamp) + if restore.Spec.Source.PointInTime.Timestamp != nil { + _, err := dbv4.ParseDisplayTime(*restore.Spec.Source.PointInTime.Timestamp) if err != nil { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("source").Child("pointInTime").Child("timestamp"), "invalid timestamp format")) @@ -121,27 +127,15 @@ func (r *AutonomousDatabaseRestore) ValidateCreate() (admission.Warnings, error) } return nil, apierrors.NewInvalid( schema.GroupKind{Group: "database.oracle.com", Kind: "AutonomousDatabaseRestore"}, - r.Name, allErrs) + restore.Name, allErrs) } // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *AutonomousDatabaseRestore) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - autonomousdatabaserestorelog.Info("validate update", "name", r.Name) - - var allErrs field.ErrorList - - if len(allErrs) == 0 { - return nil, nil - } - return nil, apierrors.NewInvalid( - schema.GroupKind{Group: "database.oracle.com", Kind: "AutonomousDatabaseRestore"}, - r.Name, allErrs) +func (r *AutonomousDatabaseRestore) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { + return nil, nil } // ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *AutonomousDatabaseRestore) ValidateDelete() (admission.Warnings, error) { - autonomousdatabaserestorelog.Info("validate delete", "name", r.Name) - - // TODO(user): fill in your validation logic upon object deletion. +func (r *AutonomousDatabaseRestore) ValidateDelete(context.Context, runtime.Object) (admission.Warnings, error) { return nil, nil } diff --git a/apis/database/v1alpha1/autonomousdatabaserestore_webhook_test.go b/apis/database/v1alpha1/autonomousdatabaserestore_webhook_test.go index 0cc9b692..ae10816f 100644 --- a/apis/database/v1alpha1/autonomousdatabaserestore_webhook_test.go +++ b/apis/database/v1alpha1/autonomousdatabaserestore_webhook_test.go @@ -68,20 +68,20 @@ var _ = Describe("test AutonomousDatabaseRestore webhook", func() { } }) - It("Should specify at least one of the k8sADB and ociADB", func() { + It("Should specify at least one of the k8sAdb and ociAdb", func() { var errMsg string = "target ADB is empty" restore.Spec.Target.K8sAdb.Name = nil - restore.Spec.Target.OciAdb.Ocid = nil + restore.Spec.Target.OciAdb.Id = nil validateInvalidTest(restore, false, errMsg) }) - It("Should specify either k8sADB.name or ociADB.ocid, but not both", func() { - var errMsg string = "specify either k8sADB.name or ociADB.ocid, but not both" + It("Should specify either k8sAdb.name or ociAdb.id, but not both", func() { + var errMsg string = "specify either k8sAdb.name or ociAdb.id, but not both" restore.Spec.Target.K8sAdb.Name = common.String("fake-target-adb") - restore.Spec.Target.OciAdb.Ocid = common.String("fake.ocid1.autonomousdatabase.oc1...") + restore.Spec.Target.OciAdb.Id = common.String("fake.ocid1.autonomousdatabase.oc1...") validateInvalidTest(restore, false, errMsg) }) diff --git a/apis/database/v1alpha1/cdb_types.go b/apis/database/v1alpha1/cdb_types.go deleted file mode 100644 index f97df391..00000000 --- a/apis/database/v1alpha1/cdb_types.go +++ /dev/null @@ -1,190 +0,0 @@ -/* -** Copyright (c) 2022 Oracle and/or its affiliates. -** -** The Universal Permissive License (UPL), Version 1.0 -** -** Subject to the condition set forth below, permission is hereby granted to any -** person obtaining a copy of this software, associated documentation and/or data -** (collectively the "Software"), free of charge and under any and all copyright -** rights in the Software, and any and all patent rights owned or freely -** licensable by each licensor hereunder covering either (i) the unmodified -** Software as contributed to or provided by such licensor, or (ii) the Larger -** Works (as defined below), to deal in both -** -** (a) the Software, and -** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if -** one is included with the Software (each a "Larger Work" to which the Software -** is contributed by such licensors), -** -** without restriction, including without limitation the rights to copy, create -** derivative works of, display, perform, and distribute the Software and make, -** use, sell, offer for sale, import, export, have made, and have sold the -** Software and the Larger Work(s), and to sublicense the foregoing rights on -** either these or other terms. -** -** This license is subject to the following condition: -** The above copyright notice and either this complete permission notice or at -** a minimum a reference to the UPL must be included in all copies or -** substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -** SOFTWARE. - */ - -package v1alpha1 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// CDBSpec defines the desired state of CDB -type CDBSpec struct { - // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster - // Important: Run "make" to regenerate code after modifying this file - - // Name of the CDB - CDBName string `json:"cdbName,omitempty"` - // Name of the CDB Service - ServiceName string `json:"serviceName,omitempty"` - - // Password for the CDB System Administrator - SysAdminPwd CDBSysAdminPassword `json:"sysAdminPwd,omitempty"` - // User in the root container with sysdba priviledges to manage PDB lifecycle - CDBAdminUser CDBAdminUser `json:"cdbAdminUser,omitempty"` - // Password for the CDB Administrator to manage PDB lifecycle - CDBAdminPwd CDBAdminPassword `json:"cdbAdminPwd,omitempty"` - - CDBTlsKey CDBTLSKEY `json:"cdbTlsKey,omitempty"` - CDBTlsCrt CDBTLSCRT `json:"cdbTlsCrt,omitempty"` - - // Password for user ORDS_PUBLIC_USER - ORDSPwd ORDSPassword `json:"ordsPwd,omitempty"` - // ORDS server port. For now, keep it as 8888. TO BE USED IN FUTURE RELEASE. - ORDSPort int `json:"ordsPort,omitempty"` - // ORDS Image Name - ORDSImage string `json:"ordsImage,omitempty"` - // The name of the image pull secret in case of a private docker repository. - ORDSImagePullSecret string `json:"ordsImagePullSecret,omitempty"` - // ORDS Image Pull Policy - // +kubebuilder:validation:Enum=Always;Never - ORDSImagePullPolicy string `json:"ordsImagePullPolicy,omitempty"` - // Number of ORDS Containers to create - Replicas int `json:"replicas,omitempty"` - // Web Server User with SQL Administrator role to allow us to authenticate to the PDB Lifecycle Management REST endpoints - WebServerUser WebServerUser `json:"webServerUser,omitempty"` - // Password for the Web Server User - WebServerPwd WebServerPassword `json:"webServerPwd,omitempty"` - // Name of the DB server - DBServer string `json:"dbServer,omitempty"` - // DB server port - DBPort int `json:"dbPort,omitempty"` - // Node Selector for running the Pod - NodeSelector map[string]string `json:"nodeSelector,omitempty"` - DeletePDBCascade bool `json:"deletePdbCascade,omitempty"` - DBTnsurl string `json:"dbTnsurl,omitempty"` - CDBPubKey CDBPUBKEY `json:"cdbOrdsPubKey,omitempty"` - CDBPriKey CDBPRIVKEY `json:"cdbOrdsPrvKey,omitempty"` -} - -// CDBSecret defines the secretName -type CDBSecret struct { - SecretName string `json:"secretName"` - Key string `json:"key"` -} - -// CDBSysAdminPassword defines the secret containing SysAdmin Password mapped to key 'sysAdminPwd' for CDB -type CDBSysAdminPassword struct { - Secret CDBSecret `json:"secret"` -} - -// CDBAdminUser defines the secret containing CDB Administrator User mapped to key 'cdbAdminUser' to manage PDB lifecycle -type CDBAdminUser struct { - Secret CDBSecret `json:"secret"` -} - -// CDBAdminPassword defines the secret containing CDB Administrator Password mapped to key 'cdbAdminPwd' to manage PDB lifecycle -type CDBAdminPassword struct { - Secret CDBSecret `json:"secret"` -} - -// ORDSPassword defines the secret containing ORDS_PUBLIC_USER Password mapped to key 'ordsPwd' -type ORDSPassword struct { - Secret CDBSecret `json:"secret"` -} - -// WebServerUser defines the secret containing Web Server User mapped to key 'webServerUser' to manage PDB lifecycle -type WebServerUser struct { - Secret CDBSecret `json:"secret"` -} - -// WebServerPassword defines the secret containing password for Web Server User mapped to key 'webServerPwd' to manage PDB lifecycle -type WebServerPassword struct { - Secret CDBSecret `json:"secret"` -} - -type CDBTLSKEY struct { - Secret CDBSecret `json:"secret"` -} - -type CDBTLSCRT struct { - Secret CDBSecret `json:"secret"` -} - -type CDBPUBKEY struct { - Secret CDBSecret `json:"secret"` -} - -type CDBPRIVKEY struct { - Secret CDBSecret `json:"secret"` -} - -// CDBStatus defines the observed state of CDB -type CDBStatus struct { - // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - // Important: Run "make" to regenerate code after modifying this file - - // Phase of the CDB Resource - Phase string `json:"phase"` - // CDB Resource Status - Status bool `json:"status"` - // Message - Msg string `json:"msg,omitempty"` -} - -// +kubebuilder:object:root=true -// +kubebuilder:subresource:status -// +kubebuilder:printcolumn:JSONPath=".spec.cdbName",name="CDB Name",type="string",description="Name of the CDB" -// +kubebuilder:printcolumn:JSONPath=".spec.dbServer",name="DB Server",type="string",description=" Name of the DB Server" -// +kubebuilder:printcolumn:JSONPath=".spec.dbPort",name="DB Port",type="integer",description="DB server port" -// +kubebuilder:printcolumn:JSONPath=".spec.replicas",name="Replicas",type="integer",description="Replicas" -// +kubebuilder:printcolumn:JSONPath=".status.phase",name="Status",type="string",description="Status of the CDB Resource" -// +kubebuilder:printcolumn:JSONPath=".status.msg",name="Message",type="string",description="Error message, if any" -// +kubebuilder:printcolumn:JSONPath=".spec.dbTnsurl",name="TNS STRING",type="string",description=" string of the tnsalias" -// +kubebuilder:resource:path=cdbs,scope=Namespaced - -// CDB is the Schema for the cdbs API -type CDB struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec CDBSpec `json:"spec,omitempty"` - Status CDBStatus `json:"status,omitempty"` -} - -//+kubebuilder:object:root=true - -// CDBList contains a list of CDB -type CDBList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []CDB `json:"items"` -} - -func init() { - SchemeBuilder.Register(&CDB{}, &CDBList{}) -} diff --git a/apis/database/v1alpha1/cdb_webhook.go b/apis/database/v1alpha1/cdb_webhook.go deleted file mode 100644 index e93e216e..00000000 --- a/apis/database/v1alpha1/cdb_webhook.go +++ /dev/null @@ -1,224 +0,0 @@ -/* -** Copyright (c) 2022 Oracle and/or its affiliates. -** -** The Universal Permissive License (UPL), Version 1.0 -** -** Subject to the condition set forth below, permission is hereby granted to any -** person obtaining a copy of this software, associated documentation and/or data -** (collectively the "Software"), free of charge and under any and all copyright -** rights in the Software, and any and all patent rights owned or freely -** licensable by each licensor hereunder covering either (i) the unmodified -** Software as contributed to or provided by such licensor, or (ii) the Larger -** Works (as defined below), to deal in both -** -** (a) the Software, and -** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if -** one is included with the Software (each a "Larger Work" to which the Software -** is contributed by such licensors), -** -** without restriction, including without limitation the rights to copy, create -** derivative works of, display, perform, and distribute the Software and make, -** use, sell, offer for sale, import, export, have made, and have sold the -** Software and the Larger Work(s), and to sublicense the foregoing rights on -** either these or other terms. -** -** This license is subject to the following condition: -** The above copyright notice and either this complete permission notice or at -** a minimum a reference to the UPL must be included in all copies or -** substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -** SOFTWARE. - */ - -package v1alpha1 - -import ( - "reflect" - "strings" - - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/validation/field" - ctrl "sigs.k8s.io/controller-runtime" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/webhook" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" -) - -// log is for logging in this package. -var cdblog = logf.Log.WithName("cdb-webhook") - -func (r *CDB) SetupWebhookWithManager(mgr ctrl.Manager) error { - return ctrl.NewWebhookManagedBy(mgr). - For(r). - Complete() -} - -//+kubebuilder:webhook:path=/mutate-database-oracle-com-v4-cdb,mutating=true,failurePolicy=fail,sideEffects=None,groups=database.oracle.com,resources=cdbs,verbs=create;update,versions=v4,name=mcdb.kb.io,admissionReviewVersions={v1,v1beta1} - -var _ webhook.Defaulter = &CDB{} - -// Default implements webhook.Defaulter so a webhook will be registered for the type -func (r *CDB) Default() { - cdblog.Info("Setting default values in CDB spec for : " + r.Name) - - if r.Spec.ORDSPort == 0 { - r.Spec.ORDSPort = 8888 - } - - if r.Spec.Replicas == 0 { - r.Spec.Replicas = 1 - } -} - -// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. -//+kubebuilder:webhook:path=/validate-database-oracle-com-v4-cdb,mutating=false,failurePolicy=fail,sideEffects=None,groups=database.oracle.com,resources=cdbs,verbs=create;update,versions=v4,name=vcdb.kb.io,admissionReviewVersions={v1,v1beta1} - -var _ webhook.Validator = &CDB{} - -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (r *CDB) ValidateCreate() (admission.Warnings, error) { - cdblog.Info("ValidateCreate", "name", r.Name) - - var allErrs field.ErrorList - - if r.Spec.ServiceName == "" && r.Spec.DBServer != "" { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("serviceName"), "Please specify CDB Service name")) - } - - if reflect.ValueOf(r.Spec.CDBTlsKey).IsZero() { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("cdbTlsKey"), "Please specify CDB Tls key(secret)")) - } - - if reflect.ValueOf(r.Spec.CDBTlsCrt).IsZero() { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("cdbTlsCrt"), "Please specify CDB Tls Certificate(secret)")) - } - - if reflect.ValueOf(r.Spec.CDBPriKey).IsZero() { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("CDBPriKey"), "Please specify CDB CDBPriKey(secret)")) - } - - /*if r.Spec.SCANName == "" { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("scanName"), "Please specify SCAN Name for CDB")) - }*/ - - if (r.Spec.DBServer == "" && r.Spec.DBTnsurl == "") || (r.Spec.DBServer != "" && r.Spec.DBTnsurl != "") { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("dbServer"), "Please specify Database Server Name/IP Address or tnsalias string")) - } - - if r.Spec.DBTnsurl != "" && (r.Spec.DBServer != "" || r.Spec.DBPort != 0 || r.Spec.ServiceName != "") { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("dbServer"), "DBtnsurl is orthogonal to (DBServer,DBport,Services)")) - } - - if r.Spec.DBPort == 0 && r.Spec.DBServer != "" { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("dbPort"), "Please specify DB Server Port")) - } - if r.Spec.DBPort < 0 && r.Spec.DBServer != "" { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("dbPort"), "Please specify a valid DB Server Port")) - } - if r.Spec.ORDSPort < 0 { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("ordsPort"), "Please specify a valid ORDS Port")) - } - if r.Spec.Replicas < 0 { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("replicas"), "Please specify a valid value for Replicas")) - } - if r.Spec.ORDSImage == "" { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("ordsImage"), "Please specify name of ORDS Image to be used")) - } - if reflect.ValueOf(r.Spec.CDBAdminUser).IsZero() { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("cdbAdminUser"), "Please specify user in the root container with sysdba priviledges to manage PDB lifecycle")) - } - if reflect.ValueOf(r.Spec.CDBAdminPwd).IsZero() { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("cdbAdminPwd"), "Please specify password for the CDB Administrator to manage PDB lifecycle")) - } - if reflect.ValueOf(r.Spec.ORDSPwd).IsZero() { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("ordsPwd"), "Please specify password for user ORDS_PUBLIC_USER")) - } - if reflect.ValueOf(r.Spec.WebServerUser).IsZero() { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("webServerUser"), "Please specify the Web Server User having SQL Administrator role")) - } - if reflect.ValueOf(r.Spec.WebServerPwd).IsZero() { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("webServerPwd"), "Please specify password for the Web Server User having SQL Administrator role")) - } - if len(allErrs) == 0 { - return nil, nil - } - return nil, apierrors.NewInvalid( - schema.GroupKind{Group: "database.oracle.com", Kind: "CDB"}, - r.Name, allErrs) -} - -// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *CDB) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - cdblog.Info("validate update", "name", r.Name) - - isCDBMarkedToBeDeleted := r.GetDeletionTimestamp() != nil - if isCDBMarkedToBeDeleted { - return nil, nil - } - - var allErrs field.ErrorList - - // Check for updation errors - oldCDB, ok := old.(*CDB) - if !ok { - return nil, nil - } - - if r.Spec.DBPort < 0 { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("dbPort"), "Please specify a valid DB Server Port")) - } - if r.Spec.ORDSPort < 0 { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("ordsPort"), "Please specify a valid ORDS Port")) - } - if r.Spec.Replicas < 0 { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("replicas"), "Please specify a valid value for Replicas")) - } - if !strings.EqualFold(oldCDB.Spec.ServiceName, r.Spec.ServiceName) { - allErrs = append(allErrs, - field.Forbidden(field.NewPath("spec").Child("replicas"), "cannot be changed")) - } - - if len(allErrs) == 0 { - return nil, nil - } - - return nil, apierrors.NewInvalid( - schema.GroupKind{Group: "database.oracle.com", Kind: "CDB"}, - r.Name, allErrs) -} - -// ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *CDB) ValidateDelete() (admission.Warnings, error) { - cdblog.Info("validate delete", "name", r.Name) - - // TODO(user): fill in your validation logic upon object deletion. - return nil, nil -} diff --git a/apis/database/v1alpha1/dataguardbroker_webhook.go b/apis/database/v1alpha1/dataguardbroker_webhook.go index 89a9d3fd..c9cc7bc7 100644 --- a/apis/database/v1alpha1/dataguardbroker_webhook.go +++ b/apis/database/v1alpha1/dataguardbroker_webhook.go @@ -39,8 +39,10 @@ package v1alpha1 import ( + "context" "strconv" "strings" + "fmt" dbcommons "github.com/oracle/oracle-database-operator/commons/database" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -59,6 +61,8 @@ var dataguardbrokerlog = logf.Log.WithName("dataguardbroker-resource") func (r *DataguardBroker) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). For(r). + WithDefaulter(r). + WithValidator(r). Complete() } @@ -66,52 +70,61 @@ func (r *DataguardBroker) SetupWebhookWithManager(mgr ctrl.Manager) error { //+kubebuilder:webhook:path=/mutate-database-oracle-com-v1alpha1-dataguardbroker,mutating=true,failurePolicy=fail,sideEffects=None,groups=database.oracle.com,resources=dataguardbrokers,verbs=create;update,versions=v1alpha1,name=mdataguardbroker.kb.io,admissionReviewVersions={v1,v1beta1} -var _ webhook.Defaulter = &DataguardBroker{} +var _ webhook.CustomDefaulter = &DataguardBroker{} // Default implements webhook.Defaulter so a webhook will be registered for the type -func (r *DataguardBroker) Default() { - dataguardbrokerlog.Info("default", "name", r.Name) +func (r *DataguardBroker) Default(ctx context.Context, obj runtime.Object) error { + dg, ok := obj.(*DataguardBroker) + if !ok { + return apierrors.NewInternalError(fmt.Errorf("failed to cast obj object to DataguardBroker")) + } + dataguardbrokerlog.Info("default", "name", dg.Name) - if r.Spec.LoadBalancer { - if r.Spec.ServiceAnnotations == nil { - r.Spec.ServiceAnnotations = make(map[string]string) + if dg.Spec.LoadBalancer { + if dg.Spec.ServiceAnnotations == nil { + dg.Spec.ServiceAnnotations = make(map[string]string) } // Annotations required for a flexible load balancer on oci - _, ok := r.Spec.ServiceAnnotations["service.beta.kubernetes.io/oci-load-balancer-shape"] + _, ok := dg.Spec.ServiceAnnotations["service.beta.kubernetes.io/oci-load-balancer-shape"] if !ok { - r.Spec.ServiceAnnotations["service.beta.kubernetes.io/oci-load-balancer-shape"] = "flexible" + dg.Spec.ServiceAnnotations["service.beta.kubernetes.io/oci-load-balancer-shape"] = "flexible" } - _, ok = r.Spec.ServiceAnnotations["service.beta.kubernetes.io/oci-load-balancer-shape-flex-min"] + _, ok = dg.Spec.ServiceAnnotations["service.beta.kubernetes.io/oci-load-balancer-shape-flex-min"] if !ok { - r.Spec.ServiceAnnotations["service.beta.kubernetes.io/oci-load-balancer-shape-flex-min"] = "10" + dg.Spec.ServiceAnnotations["service.beta.kubernetes.io/oci-load-balancer-shape-flex-min"] = "10" } - _, ok = r.Spec.ServiceAnnotations["service.beta.kubernetes.io/oci-load-balancer-shape-flex-max"] + _, ok = dg.Spec.ServiceAnnotations["service.beta.kubernetes.io/oci-load-balancer-shape-flex-max"] if !ok { - r.Spec.ServiceAnnotations["service.beta.kubernetes.io/oci-load-balancer-shape-flex-max"] = "100" + dg.Spec.ServiceAnnotations["service.beta.kubernetes.io/oci-load-balancer-shape-flex-max"] = "100" } } - if r.Spec.SetAsPrimaryDatabase != "" { - r.Spec.SetAsPrimaryDatabase = strings.ToUpper(r.Spec.SetAsPrimaryDatabase) + if dg.Spec.SetAsPrimaryDatabase != "" { + dg.Spec.SetAsPrimaryDatabase = strings.ToUpper(dg.Spec.SetAsPrimaryDatabase) } + + return nil } // TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. //+kubebuilder:webhook:verbs=create;update,path=/validate-database-oracle-com-v1alpha1-dataguardbroker,mutating=false,failurePolicy=fail,sideEffects=None,groups=database.oracle.com,resources=dataguardbrokers,versions=v1alpha1,name=vdataguardbroker.kb.io,admissionReviewVersions={v1,v1beta1} -var _ webhook.Validator = &DataguardBroker{} +var _ webhook.CustomValidator = &DataguardBroker{} // ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (r *DataguardBroker) ValidateCreate() (admission.Warnings, error) { - - dataguardbrokerlog.Info("validate create", "name", r.Name) +func (r *DataguardBroker) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + dg, ok := obj.(*DataguardBroker) + if !ok { + return nil, apierrors.NewInternalError(fmt.Errorf("failed to cast obj object to DataguardBroker")) + } + dataguardbrokerlog.Info("validate create", "name", dg.Name) var allErrs field.ErrorList namespaces := dbcommons.GetWatchNamespaces() - _, containsNamespace := namespaces[r.Namespace] + _, containsNamespace := namespaces[dg.Namespace] // Check if the allowed namespaces maps contains the required namespace if len(namespaces) != 0 && !containsNamespace { allErrs = append(allErrs, - field.Invalid(field.NewPath("metadata").Child("namespace"), r.Namespace, + field.Invalid(field.NewPath("metadata").Child("namespace"), dg.Namespace, "Oracle database operator doesn't watch over this namespace")) } @@ -121,46 +134,49 @@ func (r *DataguardBroker) ValidateCreate() (admission.Warnings, error) { return nil, apierrors.NewInvalid( schema.GroupKind{Group: "database.oracle.com", Kind: "Dataguard"}, - r.Name, allErrs) + dg.Name, allErrs) } // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *DataguardBroker) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - dataguardbrokerlog.Info("validate update", "name", r.Name) +func (r *DataguardBroker) ValidateUpdate(ctx context.Context, old, newObj runtime.Object) (admission.Warnings, error) { + new, ok := newObj.(*DataguardBroker) + if !ok { + return nil, apierrors.NewInternalError(fmt.Errorf("failed to cast newObj object to DataguardBroker")) + } - dataguardbrokerlog.Info("validate update", "name", r.Name) + dataguardbrokerlog.Info("validate update", "name", new.Name) var allErrs field.ErrorList // check creation validations first - _, err := r.ValidateCreate() + _, err := new.ValidateCreate(ctx, newObj) if err != nil { return nil, err } // Validate Deletion - if r.GetDeletionTimestamp() != nil { - warnings, err := r.ValidateDelete() + if new.GetDeletionTimestamp() != nil { + warnings, err := new.ValidateDelete(ctx, newObj) if err != nil { return warnings, err } } // Now check for updation errors - oldObj, ok := old.(*DataguardBroker) - if !ok { - return nil, nil + oldObj, okay := old.(*DataguardBroker) + if !okay { + return nil, apierrors.NewInternalError(fmt.Errorf("failed to cast old object to DataguardBroker")) } - if oldObj.Status.ProtectionMode != "" && !strings.EqualFold(r.Spec.ProtectionMode, oldObj.Status.ProtectionMode) { + if oldObj.Status.ProtectionMode != "" && !strings.EqualFold(new.Spec.ProtectionMode, oldObj.Status.ProtectionMode) { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("protectionMode"), "cannot be changed")) } - if oldObj.Status.PrimaryDatabaseRef != "" && !strings.EqualFold(oldObj.Status.PrimaryDatabaseRef, r.Spec.PrimaryDatabaseRef) { + if oldObj.Status.PrimaryDatabaseRef != "" && !strings.EqualFold(oldObj.Status.PrimaryDatabaseRef, new.Spec.PrimaryDatabaseRef) { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("primaryDatabaseRef"), "cannot be changed")) } fastStartFailoverStatus, _ := strconv.ParseBool(oldObj.Status.FastStartFailover) - if (fastStartFailoverStatus || r.Spec.FastStartFailover) && r.Spec.SetAsPrimaryDatabase != "" { + if (fastStartFailoverStatus || new.Spec.FastStartFailover) && new.Spec.SetAsPrimaryDatabase != "" { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("setAsPrimaryDatabase"), "switchover not supported when fastStartFailover is true")) } @@ -170,12 +186,12 @@ func (r *DataguardBroker) ValidateUpdate(old runtime.Object) (admission.Warnings } return nil, apierrors.NewInvalid( schema.GroupKind{Group: "database.oracle.com", Kind: "DataguardBroker"}, - r.Name, allErrs) + new.Name, allErrs) } // ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *DataguardBroker) ValidateDelete() (admission.Warnings, error) { +func (r *DataguardBroker) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { dataguardbrokerlog.Info("validate delete", "name", r.Name) // TODO(user): fill in your validation logic upon object deletion. diff --git a/apis/database/v1alpha1/dbcssystem_webhook.go b/apis/database/v1alpha1/dbcssystem_webhook.go index dc9f8934..0f9fe27c 100644 --- a/apis/database/v1alpha1/dbcssystem_webhook.go +++ b/apis/database/v1alpha1/dbcssystem_webhook.go @@ -39,6 +39,8 @@ package v1alpha1 import ( + "context" + "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" logf "sigs.k8s.io/controller-runtime/pkg/log" @@ -52,6 +54,8 @@ var dbcssystemlog = logf.Log.WithName("dbcssystem-resource") func (r *DbcsSystem) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). For(r). + WithDefaulter(r). + WithValidator(r). Complete() } @@ -59,22 +63,23 @@ func (r *DbcsSystem) SetupWebhookWithManager(mgr ctrl.Manager) error { //+kubebuilder:webhook:path=/mutate-database-oracle-com-v4-dbcssystem,mutating=true,failurePolicy=fail,sideEffects=none,groups=database.oracle.com,resources=dbcssystems,verbs=create;update,versions=v4,name=mdbcssystemv1alpha1.kb.io,admissionReviewVersions=v1 -var _ webhook.Defaulter = &DbcsSystem{} +var _ webhook.CustomDefaulter = &DbcsSystem{} // Default implements webhook.Defaulter so a webhook will be registered for the type -func (r *DbcsSystem) Default() { +func (r *DbcsSystem) Default(ctx context.Context, obj runtime.Object) error { dbcssystemlog.Info("default", "name", r.Name) // TODO(user): fill in your defaulting logic. + return nil } // TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. // +kubebuilder:webhook:verbs=create;update;delete,path=/validate-database-oracle-com-v4-dbcssystem,mutating=false,failurePolicy=fail,sideEffects=None,groups=database.oracle.com,resources=dbcssystems,versions=v4,name=vdbcssystemv1alpha1.kb.io,admissionReviewVersions=v1 -var _ webhook.Validator = &DbcsSystem{} +var _ webhook.CustomValidator = &DbcsSystem{} // ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (r *DbcsSystem) ValidateCreate() (admission.Warnings, error) { +func (r *DbcsSystem) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { dbcssystemlog.Info("validate create", "name", r.Name) // // TODO(user): fill in your validation logic upon object creation. @@ -82,7 +87,7 @@ func (r *DbcsSystem) ValidateCreate() (admission.Warnings, error) { } // // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *DbcsSystem) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { +func (r *DbcsSystem) ValidateUpdate(ctx context.Context, old, newObj runtime.Object) (admission.Warnings, error) { dbcssystemlog.Info("validate update", "name", r.Name) // // TODO(user): fill in your validation logic upon object update. @@ -90,7 +95,7 @@ func (r *DbcsSystem) ValidateUpdate(old runtime.Object) (admission.Warnings, err } // ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *DbcsSystem) ValidateDelete() (admission.Warnings, error) { +func (r *DbcsSystem) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { dbcssystemlog.Info("validate delete", "name", r.Name) // TODO(user): fill in your validation logic upon object deletion. diff --git a/apis/database/v1alpha1/oraclerestdataservice_webhook.go b/apis/database/v1alpha1/oraclerestdataservice_webhook.go index c5ecde1c..72dcedea 100644 --- a/apis/database/v1alpha1/oraclerestdataservice_webhook.go +++ b/apis/database/v1alpha1/oraclerestdataservice_webhook.go @@ -39,6 +39,9 @@ package v1alpha1 import ( + "context" + "fmt" + dbcommons "github.com/oracle/oracle-database-operator/commons/database" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -56,6 +59,8 @@ var oraclerestdataservicelog = logf.Log.WithName("oraclerestdataservice-resource func (r *OracleRestDataService) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). For(r). + WithDefaulter(r). + WithValidator(r). Complete() } @@ -63,68 +68,78 @@ func (r *OracleRestDataService) SetupWebhookWithManager(mgr ctrl.Manager) error //+kubebuilder:webhook:path=/mutate-database-oracle-com-v1alpha1-oraclerestdataservice,mutating=true,failurePolicy=fail,sideEffects=None,groups=database.oracle.com,resources=oraclerestdataservices,verbs=create;update,versions=v1alpha1,name=moraclerestdataservice.kb.io,admissionReviewVersions={v1,v1beta1} -var _ webhook.Defaulter = &OracleRestDataService{} +var _ webhook.CustomDefaulter = &OracleRestDataService{} // Default implements webhook.Defaulter so a webhook will be registered for the type -func (r *OracleRestDataService) Default() { - oraclerestdataservicelog.Info("default", "name", r.Name) +func (r *OracleRestDataService) Default(ctx context.Context, obj runtime.Object) error { + ords, ok := obj.(*OracleRestDataService) + if !ok { + return apierrors.NewInternalError(fmt.Errorf("failed to cast obj object to OracleRestDataService")) + } + oraclerestdataservicelog.Info("default", "name", ords.Name) // OracleRestDataService Currently supports single replica - r.Spec.Replicas = 1 + ords.Spec.Replicas = 1 keepSecret := true - if r.Spec.OrdsPassword.KeepSecret == nil { - r.Spec.OrdsPassword.KeepSecret = &keepSecret + if ords.Spec.OrdsPassword.KeepSecret == nil { + ords.Spec.OrdsPassword.KeepSecret = &keepSecret } - if r.Spec.AdminPassword.KeepSecret == nil { - r.Spec.AdminPassword.KeepSecret = &keepSecret + if ords.Spec.AdminPassword.KeepSecret == nil { + ords.Spec.AdminPassword.KeepSecret = &keepSecret } + + return nil } // TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. //+kubebuilder:webhook:verbs=create;update,path=/validate-database-oracle-com-v1alpha1-oraclerestdataservice,mutating=false,failurePolicy=fail,sideEffects=None,groups=database.oracle.com,resources=oraclerestdataservices,versions=v1alpha1,name=voraclerestdataservice.kb.io,admissionReviewVersions={v1,v1beta1} -var _ webhook.Validator = &OracleRestDataService{} +var _ webhook.CustomValidator = &OracleRestDataService{} // ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (r *OracleRestDataService) ValidateCreate() (admission.Warnings, error) { - oraclerestdataservicelog.Info("validate create", "name", r.Name) +func (r *OracleRestDataService) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + ords, ok := obj.(*OracleRestDataService) + if !ok { + return nil, apierrors.NewInternalError(fmt.Errorf("failed to cast obj object to OracleRestDataService")) + } + oraclerestdataservicelog.Info("validate create", "name", ords.Name) var allErrs field.ErrorList namespaces := dbcommons.GetWatchNamespaces() - _, containsNamespace := namespaces[r.Namespace] + _, containsNamespace := namespaces[ords.Namespace] // Check if the allowed namespaces maps contains the required namespace if len(namespaces) != 0 && !containsNamespace { allErrs = append(allErrs, - field.Invalid(field.NewPath("metadata").Child("namespace"), r.Namespace, + field.Invalid(field.NewPath("metadata").Child("namespace"), ords.Namespace, "Oracle database operator doesn't watch over this namespace")) } // Persistence spec validation - if r.Spec.Persistence.Size == "" && (r.Spec.Persistence.AccessMode != "" || - r.Spec.Persistence.StorageClass != "" || r.Spec.Persistence.VolumeName != "") { + if ords.Spec.Persistence.Size == "" && (ords.Spec.Persistence.AccessMode != "" || + ords.Spec.Persistence.StorageClass != "" || ords.Spec.Persistence.VolumeName != "") { allErrs = append(allErrs, - field.Invalid(field.NewPath("spec").Child("persistence").Child("size"), r.Spec.Persistence, + field.Invalid(field.NewPath("spec").Child("persistence").Child("size"), ords.Spec.Persistence, "invalid persistence specification, specify required size")) } - if r.Spec.Persistence.Size != "" { - if r.Spec.Persistence.AccessMode == "" { + if ords.Spec.Persistence.Size != "" { + if ords.Spec.Persistence.AccessMode == "" { allErrs = append(allErrs, - field.Invalid(field.NewPath("spec").Child("persistence").Child("size"), r.Spec.Persistence, + field.Invalid(field.NewPath("spec").Child("persistence").Child("size"), ords.Spec.Persistence, "invalid persistence specification, specify accessMode")) } - if r.Spec.Persistence.AccessMode != "ReadWriteMany" && r.Spec.Persistence.AccessMode != "ReadWriteOnce" { + if ords.Spec.Persistence.AccessMode != "ReadWriteMany" && ords.Spec.Persistence.AccessMode != "ReadWriteOnce" { allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("persistence").Child("accessMode"), - r.Spec.Persistence.AccessMode, "should be either \"ReadWriteOnce\" or \"ReadWriteMany\"")) + ords.Spec.Persistence.AccessMode, "should be either \"ReadWriteOnce\" or \"ReadWriteMany\"")) } } // Validating databaseRef and ORDS kind name not to be same - if r.Spec.DatabaseRef == r.Name { + if ords.Spec.DatabaseRef == ords.Name { allErrs = append(allErrs, field.Forbidden(field.NewPath("Name"), - "cannot be same as DatabaseRef: "+r.Spec.DatabaseRef)) + "cannot be same as DatabaseRef: "+ords.Spec.DatabaseRef)) } @@ -133,32 +148,36 @@ func (r *OracleRestDataService) ValidateCreate() (admission.Warnings, error) { } return nil, apierrors.NewInvalid( schema.GroupKind{Group: "database.oracle.com", Kind: "OracleRestDataService"}, - r.Name, allErrs) + ords.Name, allErrs) } // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *OracleRestDataService) ValidateUpdate(oldRuntimeObject runtime.Object) (admission.Warnings, error) { - oraclerestdataservicelog.Info("validate update", "name", r.Name) +func (r *OracleRestDataService) ValidateUpdate(ctx context.Context, oldRuntimeObject, newRuntimeObject runtime.Object) (admission.Warnings, error) { + new, ok := newRuntimeObject.(*OracleRestDataService) + if !ok { + return nil, apierrors.NewInternalError(fmt.Errorf("failed to cast newRuntimeObject object to OracleRestDataService")) + } + oraclerestdataservicelog.Info("validate update", "name", new.Name) var allErrs field.ErrorList // check creation validations first - warnings, err := r.ValidateCreate() + warnings, err := new.ValidateCreate(ctx, newRuntimeObject) if err != nil { return warnings, err } // Now check for updation errors - old, ok := oldRuntimeObject.(*OracleRestDataService) - if !ok { - return nil, nil + old, okay := oldRuntimeObject.(*OracleRestDataService) + if !okay { + return nil, apierrors.NewInternalError(fmt.Errorf("failed to cast oldRuntimeObject object to OracleRestDataService")) } - if old.Status.DatabaseRef != "" && old.Status.DatabaseRef != r.Spec.DatabaseRef { + if old.Status.DatabaseRef != "" && old.Status.DatabaseRef != new.Spec.DatabaseRef { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("databaseRef"), "cannot be changed")) } - if old.Status.Image.PullFrom != "" && old.Status.Image != r.Spec.Image { + if old.Status.Image.PullFrom != "" && old.Status.Image != new.Spec.Image { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("image"), "cannot be changed")) } @@ -168,12 +187,12 @@ func (r *OracleRestDataService) ValidateUpdate(oldRuntimeObject runtime.Object) } return nil, apierrors.NewInvalid( schema.GroupKind{Group: "database.oracle.com", Kind: "OracleRestDataService"}, - r.Name, allErrs) + new.Name, allErrs) } // ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *OracleRestDataService) ValidateDelete() (admission.Warnings, error) { +func (r *OracleRestDataService) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { oraclerestdataservicelog.Info("validate delete", "name", r.Name) // TODO(user): fill in your validation logic upon object deletion. diff --git a/apis/database/v1alpha1/pdb_types.go b/apis/database/v1alpha1/pdb_types.go deleted file mode 100644 index 8b966c38..00000000 --- a/apis/database/v1alpha1/pdb_types.go +++ /dev/null @@ -1,236 +0,0 @@ -/* -** Copyright (c) 2022 Oracle and/or its affiliates. -** -** The Universal Permissive License (UPL), Version 1.0 -** -** Subject to the condition set forth below, permission is hereby granted to any -** person obtaining a copy of this software, associated documentation and/or data -** (collectively the "Software"), free of charge and under any and all copyright -** rights in the Software, and any and all patent rights owned or freely -** licensable by each licensor hereunder covering either (i) the unmodified -** Software as contributed to or provided by such licensor, or (ii) the Larger -** Works (as defined below), to deal in both -** -** (a) the Software, and -** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if -** one is included with the Software (each a "Larger Work" to which the Software -** is contributed by such licensors), -** -** without restriction, including without limitation the rights to copy, create -** derivative works of, display, perform, and distribute the Software and make, -** use, sell, offer for sale, import, export, have made, and have sold the -** Software and the Larger Work(s), and to sublicense the foregoing rights on -** either these or other terms. -** -** This license is subject to the following condition: -** The above copyright notice and either this complete permission notice or at -** a minimum a reference to the UPL must be included in all copies or -** substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -** SOFTWARE. - */ - -package v1alpha1 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// PDBSpec defines the desired state of PDB -type PDBSpec struct { - // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster - // Important: Run "make" to regenerate code after modifying this file - - PDBTlsKey PDBTLSKEY `json:"pdbTlsKey,omitempty"` - PDBTlsCrt PDBTLSCRT `json:"pdbTlsCrt,omitempty"` - PDBTlsCat PDBTLSCAT `json:"pdbTlsCat,omitempty"` - - // CDB Namespace - CDBNamespace string `json:"cdbNamespace,omitempty"` - // Name of the CDB Custom Resource that runs the ORDS container - CDBResName string `json:"cdbResName,omitempty"` - // Name of the CDB - CDBName string `json:"cdbName,omitempty"` - // The name of the new PDB. Relevant for both Create and Plug Actions. - PDBName string `json:"pdbName,omitempty"` - // Name of the Source PDB from which to clone - SrcPDBName string `json:"srcPdbName,omitempty"` - // The administrator username for the new PDB. This property is required when the Action property is Create. - AdminName PDBAdminName `json:"adminName,omitempty"` - // The administrator password for the new PDB. This property is required when the Action property is Create. - AdminPwd PDBAdminPassword `json:"adminPwd,omitempty"` - // Web Server User with SQL Administrator role to allow us to authenticate to the PDB Lifecycle Management REST endpoints - WebServerUsr WebServerUserPDB `json:"webServerUser,omitempty"` - // Password for the Web ServerPDB User - WebServerPwd WebServerPasswordPDB `json:"webServerPwd,omitempty"` - // Relevant for Create and Plug operations. As defined in the Oracle Multitenant Database documentation. Values can be a filename convert pattern or NONE. - FileNameConversions string `json:"fileNameConversions,omitempty"` - // This property is required when the Action property is Plug. As defined in the Oracle Multitenant Database documentation. Values can be a source filename convert pattern or NONE. - SourceFileNameConversions string `json:"sourceFileNameConversions,omitempty"` - // XML metadata filename to be used for Plug or Unplug operations - XMLFileName string `json:"xmlFileName,omitempty"` - // To copy files or not while cloning a PDB - // +kubebuilder:validation:Enum=COPY;NOCOPY;MOVE - CopyAction string `json:"copyAction,omitempty"` - // Specify if datafiles should be removed or not. The value can be INCLUDING or KEEP (default). - // +kubebuilder:validation:Enum=INCLUDING;KEEP - DropAction string `json:"dropAction,omitempty"` - // A Path specified for sparse clone snapshot copy. (Optional) - SparseClonePath string `json:"sparseClonePath,omitempty"` - // Whether to reuse temp file - ReuseTempFile *bool `json:"reuseTempFile,omitempty"` - // Relevant for Create and Plug operations. True for unlimited storage. Even when set to true, totalSize and tempSize MUST be specified in the request if Action is Create. - UnlimitedStorage *bool `json:"unlimitedStorage,omitempty"` - // Indicate if 'AS CLONE' option should be used in the command to plug in a PDB. This property is applicable when the Action property is PLUG but not required. - AsClone *bool `json:"asClone,omitempty"` - // Relevant for create and plug operations. Total size as defined in the Oracle Multitenant Database documentation. See size_clause description in Database SQL Language Reference documentation. - TotalSize string `json:"totalSize,omitempty"` - // Relevant for Create and Clone operations. Total size for temporary tablespace as defined in the Oracle Multitenant Database documentation. See size_clause description in Database SQL Language Reference documentation. - TempSize string `json:"tempSize,omitempty"` - // TDE import for plug operations - TDEImport *bool `json:"tdeImport,omitempty"` - // TDE export for unplug operations - TDEExport *bool `json:"tdeExport,omitempty"` - // TDE password if the tdeImport or tdeExport flag is set to true. Can be used in create, plug or unplug operations - TDEPassword TDEPwd `json:"tdePassword,omitempty"` - // TDE keystore path is required if the tdeImport or tdeExport flag is set to true. Can be used in plug or unplug operations. - TDEKeystorePath string `json:"tdeKeystorePath,omitempty"` - // TDE secret is required if the tdeImport or tdeExport flag is set to true. Can be used in plug or unplug operations. - TDESecret TDESecret `json:"tdeSecret,omitempty"` - // Whether you need the script only or execute the script - GetScript *bool `json:"getScript,omitempty"` - // Action to be taken: Create/Clone/Plug/Unplug/Delete/Modify/Status/Map. Map is used to map a Databse PDB to a Kubernetes PDB CR. - // +kubebuilder:validation:Enum=Create;Clone;Plug;Unplug;Delete;Modify;Status;Map - Action string `json:"action"` - // Extra options for opening and closing a PDB - // +kubebuilder:validation:Enum=IMMEDIATE;NORMAL;READ ONLY;READ WRITE;RESTRICTED - ModifyOption string `json:"modifyOption,omitempty"` - // The target state of the PDB - // +kubebuilder:validation:Enum=OPEN;CLOSE - PDBState string `json:"pdbState,omitempty"` - // turn on the assertive approach to delete pdb resource - // kubectl delete pdb ..... automatically triggers the pluggable database - // deletion - AssertivePdbDeletion bool `json:"assertivePdbDeletion,omitempty"` - PDBPubKey PDBPUBKEY `json:"pdbOrdsPubKey,omitempty"` - PDBPriKey PDBPRIVKEY `json:"pdbOrdsPrvKey,omitempty"` -} - -// PDBAdminName defines the secret containing Sys Admin User mapped to key 'adminName' for PDB -type PDBAdminName struct { - Secret PDBSecret `json:"secret"` -} - -// PDBAdminPassword defines the secret containing Sys Admin Password mapped to key 'adminPwd' for PDB -type PDBAdminPassword struct { - Secret PDBSecret `json:"secret"` -} - -// TDEPwd defines the secret containing TDE Wallet Password mapped to key 'tdePassword' for PDB -type TDEPwd struct { - Secret PDBSecret `json:"secret"` -} - -// TDESecret defines the secret containing TDE Secret to key 'tdeSecret' for PDB -type TDESecret struct { - Secret PDBSecret `json:"secret"` -} - -// WebServerUser defines the secret containing Web Server User mapped to key 'webServerUser' to manage PDB lifecycle - -type WebServerUserPDB struct { - Secret PDBSecret `json:"secret"` -} - -// WebServerPassword defines the secret containing password for Web Server User mapped to key 'webServerPwd' to manage PDB lifecycle -type WebServerPasswordPDB struct { - Secret PDBSecret `json:"secret"` -} - -// PDBSecret defines the secretName -type PDBSecret struct { - SecretName string `json:"secretName"` - Key string `json:"key"` -} - -type PDBTLSKEY struct { - Secret PDBSecret `json:"secret"` -} - -type PDBTLSCRT struct { - Secret PDBSecret `json:"secret"` -} - -type PDBTLSCAT struct { - Secret PDBSecret `json:"secret"` -} - -// PDBStatus defines the observed state of PDB -type PDBStatus struct { - // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - // Important: Run "make" to regenerate code after modifying this file - - // PDB Connect String - ConnString string `json:"connString,omitempty"` - // Phase of the PDB Resource - Phase string `json:"phase"` - // PDB Resource Status - Status bool `json:"status"` - // Total size of the PDB - TotalSize string `json:"totalSize,omitempty"` - // Open mode of the PDB - OpenMode string `json:"openMode,omitempty"` - // Modify Option of the PDB - ModifyOption string `json:"modifyOption,omitempty"` - // Message - Msg string `json:"msg,omitempty"` - // Last Completed Action - Action string `json:"action,omitempty"` -} - -// +kubebuilder:object:root=true -// +kubebuilder:subresource:status -// +kubebuilder:printcolumn:JSONPath=".spec.cdbName",name="CDB Name",type="string",description="Name of the CDB" -// +kubebuilder:printcolumn:JSONPath=".spec.pdbName",name="PDB Name",type="string",description="Name of the PDB" -// +kubebuilder:printcolumn:JSONPath=".status.openMode",name="PDB State",type="string",description="PDB Open Mode" -// +kubebuilder:printcolumn:JSONPath=".status.totalSize",name="PDB Size",type="string",description="Total Size of the PDB" -// +kubebuilder:printcolumn:JSONPath=".status.phase",name="Status",type="string",description="Status of the PDB Resource" -// +kubebuilder:printcolumn:JSONPath=".status.msg",name="Message",type="string",description="Error message, if any" -// +kubebuilder:printcolumn:JSONPath=".status.connString",name="Connect_String",type="string",description="The connect string to be used" -// +kubebuilder:resource:path=pdbs,scope=Namespaced - -// PDB is the Schema for the pdbs API -type PDB struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec PDBSpec `json:"spec,omitempty"` - Status PDBStatus `json:"status,omitempty"` -} - -//+kubebuilder:object:root=true - -// PDBList contains a list of PDB -type PDBList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []PDB `json:"items"` -} - -type PDBPUBKEY struct { - Secret PDBSecret `json:"secret"` -} - -type PDBPRIVKEY struct { - Secret PDBSecret `json:"secret"` -} - -func init() { - SchemeBuilder.Register(&PDB{}, &PDBList{}) -} diff --git a/apis/database/v1alpha1/pdb_webhook.go b/apis/database/v1alpha1/pdb_webhook.go deleted file mode 100644 index 1f115c9b..00000000 --- a/apis/database/v1alpha1/pdb_webhook.go +++ /dev/null @@ -1,369 +0,0 @@ -/* -** Copyright (c) 2022 Oracle and/or its affiliates. -** -** The Universal Permissive License (UPL), Version 1.0 -** -** Subject to the condition set forth below, permission is hereby granted to any -** person obtaining a copy of this software, associated documentation and/or data -** (collectively the "Software"), free of charge and under any and all copyright -** rights in the Software, and any and all patent rights owned or freely -** licensable by each licensor hereunder covering either (i) the unmodified -** Software as contributed to or provided by such licensor, or (ii) the Larger -** Works (as defined below), to deal in both -** -** (a) the Software, and -** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if -** one is included with the Software (each a "Larger Work" to which the Software -** is contributed by such licensors), -** -** without restriction, including without limitation the rights to copy, create -** derivative works of, display, perform, and distribute the Software and make, -** use, sell, offer for sale, import, export, have made, and have sold the -** Software and the Larger Work(s), and to sublicense the foregoing rights on -** either these or other terms. -** -** This license is subject to the following condition: -** The above copyright notice and either this complete permission notice or at -** a minimum a reference to the UPL must be included in all copies or -** substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -** SOFTWARE. - */ - -/* MODIFIED (MM/DD/YY) -** rcitton 07/14/22 - 33822886 - */ - -package v1alpha1 - -import ( - "reflect" - "strconv" - "strings" - - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/validation/field" - ctrl "sigs.k8s.io/controller-runtime" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/webhook" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" -) - -// log is for logging in this package. -var pdblog = logf.Log.WithName("pdb-webhook") - -func (r *PDB) SetupWebhookWithManager(mgr ctrl.Manager) error { - return ctrl.NewWebhookManagedBy(mgr). - For(r). - Complete() -} - -//+kubebuilder:webhook:path=/mutate-database-oracle-com-v4-pdb,mutating=true,failurePolicy=fail,sideEffects=None,groups=database.oracle.com,resources=pdbs,verbs=create;update,versions=v4,name=mpdb.kb.io,admissionReviewVersions={v1,v1beta1} - -var _ webhook.Defaulter = &PDB{} - -// Default implements webhook.Defaulter so a webhook will be registered for the type -func (r *PDB) Default() { - pdblog.Info("Setting default values in PDB spec for : " + r.Name) - - action := strings.ToUpper(r.Spec.Action) - - if action == "DELETE" { - if r.Spec.DropAction == "" { - r.Spec.DropAction = "INCLUDING" - pdblog.Info(" - dropAction : INCLUDING") - } - } else if action != "MODIFY" && action != "STATUS" { - if r.Spec.ReuseTempFile == nil { - r.Spec.ReuseTempFile = new(bool) - *r.Spec.ReuseTempFile = true - pdblog.Info(" - reuseTempFile : " + strconv.FormatBool(*(r.Spec.ReuseTempFile))) - } - if r.Spec.UnlimitedStorage == nil { - r.Spec.UnlimitedStorage = new(bool) - *r.Spec.UnlimitedStorage = true - pdblog.Info(" - unlimitedStorage : " + strconv.FormatBool(*(r.Spec.UnlimitedStorage))) - } - if r.Spec.TDEImport == nil { - r.Spec.TDEImport = new(bool) - *r.Spec.TDEImport = false - pdblog.Info(" - tdeImport : " + strconv.FormatBool(*(r.Spec.TDEImport))) - } - if r.Spec.TDEExport == nil { - r.Spec.TDEExport = new(bool) - *r.Spec.TDEExport = false - pdblog.Info(" - tdeExport : " + strconv.FormatBool(*(r.Spec.TDEExport))) - } - if r.Spec.AsClone == nil { - r.Spec.AsClone = new(bool) - *r.Spec.AsClone = false - pdblog.Info(" - asClone : " + strconv.FormatBool(*(r.Spec.AsClone))) - } - - } - - if r.Spec.GetScript == nil { - r.Spec.GetScript = new(bool) - *r.Spec.GetScript = false - pdblog.Info(" - getScript : " + strconv.FormatBool(*(r.Spec.GetScript))) - } -} - -// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. -//+kubebuilder:webhook:path=/validate-database-oracle-com-v4-pdb,mutating=false,failurePolicy=fail,sideEffects=None,groups=database.oracle.com,resources=pdbs,verbs=create;update,versions=v4,name=vpdb.kb.io,admissionReviewVersions={v1,v1beta1} - -var _ webhook.Validator = &PDB{} - -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (r *PDB) ValidateCreate() (admission.Warnings, error) { - pdblog.Info("ValidateCreate-Validating PDB spec for : " + r.Name) - - var allErrs field.ErrorList - - r.validateCommon(&allErrs) - - r.validateAction(&allErrs) - - action := strings.ToUpper(r.Spec.Action) - - if len(allErrs) == 0 { - pdblog.Info("PDB Resource : " + r.Name + " successfully validated for Action : " + action) - return nil, nil - } - return nil, apierrors.NewInvalid( - schema.GroupKind{Group: "database.oracle.com", Kind: "PDB"}, - r.Name, allErrs) -} - -// Validate Action for required parameters -func (r *PDB) validateAction(allErrs *field.ErrorList) { - action := strings.ToUpper(r.Spec.Action) - - pdblog.Info("Valdiating PDB Resource Action : " + action) - - if reflect.ValueOf(r.Spec.PDBTlsKey).IsZero() { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("pdbTlsKey"), "Please specify PDB Tls Key(secret)")) - } - - if reflect.ValueOf(r.Spec.PDBTlsCrt).IsZero() { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("pdbTlsCrt"), "Please specify PDB Tls Certificate(secret)")) - } - - if reflect.ValueOf(r.Spec.PDBTlsCat).IsZero() { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("pdbTlsCat"), "Please specify PDB Tls Certificate Authority(secret)")) - } - if reflect.ValueOf(r.Spec.PDBPriKey).IsZero() { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("pdbOrdsPrvKey"), "Please specify PDB Tls Certificate Authority(secret)")) - } - - switch action { - case "DELETE": - /* BUG 36752336 - LREST OPERATOR - DELETE NON-EXISTENT PDB SHOWS LRPDB CREATED MESSAGE */ - if r.Status.OpenMode == "READ WRITE" { - pdblog.Info("Cannot delete: pdb is open ") - *allErrs = append(*allErrs, field.Invalid(field.NewPath("status").Child("OpenMode"), "READ WRITE", "pdb "+r.Spec.PDBName+" "+r.Status.OpenMode)) - } - r.CheckObjExistence("DELETE", allErrs, r) - case "CREATE": - if reflect.ValueOf(r.Spec.AdminName).IsZero() { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("adminName"), "Please specify PDB System Administrator user")) - } - if reflect.ValueOf(r.Spec.AdminPwd).IsZero() { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("adminPwd"), "Please specify PDB System Administrator Password")) - } - if reflect.ValueOf(r.Spec.WebServerUsr).IsZero() { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("WebServerUser"), "Please specify the http webServerUser")) - } - if reflect.ValueOf(r.Spec.WebServerPwd).IsZero() { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("webServerPwd"), "Please specify the http webserverPassword")) - } - - if r.Spec.FileNameConversions == "" { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("fileNameConversions"), "Please specify a value for fileNameConversions. Values can be a filename convert pattern or NONE")) - } - if r.Spec.TotalSize == "" { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("totalSize"), "When the storage is not UNLIMITED the Total Size must be specified")) - } - if r.Spec.TempSize == "" { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("tempSize"), "When the storage is not UNLIMITED the Temp Size must be specified")) - } - if *(r.Spec.TDEImport) { - r.validateTDEInfo(allErrs) - } - case "CLONE": - // Sample Err: The PDB "pdb1-clone" is invalid: spec.srcPdbName: Required value: Please specify source PDB for Cloning - if r.Spec.SrcPDBName == "" { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("srcPdbName"), "Please specify source PDB name for Cloning")) - } - if r.Spec.TotalSize == "" { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("totalSize"), "When the storage is not UNLIMITED the Total Size must be specified")) - } - if r.Spec.TempSize == "" { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("tempSize"), "When the storage is not UNLIMITED the Temp Size must be specified")) - } - /* We don't need this check as ords open the pdb before cloninig */ - /* - if r.Status.OpenMode == "MOUNTED" { - pdblog.Info("Cannot clone: pdb is mount ") - *allErrs = append(*allErrs, field.Invalid(field.NewPath("status").Child("OpenMode"), "READ WRITE", "pdb "+r.Spec.PDBName+" "+r.Status.OpenMode)) - } - */ - case "PLUG": - if r.Spec.XMLFileName == "" { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("xmlFileName"), "Please specify XML metadata filename")) - } - if r.Spec.FileNameConversions == "" { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("fileNameConversions"), "Please specify a value for fileNameConversions. Values can be a filename convert pattern or NONE")) - } - if r.Spec.SourceFileNameConversions == "" { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("sourceFileNameConversions"), "Please specify a value for sourceFileNameConversions. Values can be a filename convert pattern or NONE")) - } - if r.Spec.CopyAction == "" { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("copyAction"), "Please specify a value for copyAction. Values can be COPY, NOCOPY or MOVE")) - } - if *(r.Spec.TDEImport) { - r.validateTDEInfo(allErrs) - } - case "UNPLUG": - if r.Spec.XMLFileName == "" { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("xmlFileName"), "Please specify XML metadata filename")) - } - if *(r.Spec.TDEExport) { - r.validateTDEInfo(allErrs) - } - if r.Status.OpenMode == "READ WRITE" { - pdblog.Info("Cannot unplug: pdb is open ") - *allErrs = append(*allErrs, field.Invalid(field.NewPath("status").Child("OpenMode"), "READ WRITE", "pdb "+r.Spec.PDBName+" "+r.Status.OpenMode)) - } - r.CheckObjExistence("UNPLUG", allErrs, r) - case "MODIFY": - if r.Spec.PDBState == "" { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("pdbState"), "Please specify target state of PDB")) - } - if r.Spec.ModifyOption == "" { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("modifyOption"), "Please specify an option for opening/closing a PDB")) - } - r.CheckObjExistence("MODIY", allErrs, r) - } -} - -// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *PDB) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - pdblog.Info("ValidateUpdate-Validating PDB spec for : " + r.Name) - - isPDBMarkedToBeDeleted := r.GetDeletionTimestamp() != nil - if isPDBMarkedToBeDeleted { - return nil, nil - } - - var allErrs field.ErrorList - action := strings.ToUpper(r.Spec.Action) - - // If PDB CR has been created and in Ready state, only allow updates if the "action" value has changed as well - if (r.Status.Phase == "Ready") && (r.Status.Action != "MODIFY") && (r.Status.Action != "STATUS") && (r.Status.Action == action) { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("action"), "New action also needs to be specified after PDB is in Ready state")) - } else { - - // Check Common Validations - r.validateCommon(&allErrs) - - // Validate required parameters for Action specified - r.validateAction(&allErrs) - - // Check TDE requirements - if (action != "DELETE") && (action != "MODIFY") && (action != "STATUS") && (*(r.Spec.TDEImport) || *(r.Spec.TDEExport)) { - r.validateTDEInfo(&allErrs) - } - } - - if len(allErrs) == 0 { - return nil, nil - } - return nil, apierrors.NewInvalid( - schema.GroupKind{Group: "database.oracle.com", Kind: "PDB"}, - r.Name, allErrs) -} - -// ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *PDB) ValidateDelete() (admission.Warnings, error) { - pdblog.Info("ValidateDelete-Validating PDB spec for : " + r.Name) - - // TODO(user): fill in your validation logic upon object deletion. - return nil, nil -} - -// Validate common specs needed for all PDB Actions -func (r *PDB) validateCommon(allErrs *field.ErrorList) { - pdblog.Info("validateCommon", "name", r.Name) - - if r.Spec.Action == "" { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("action"), "Please specify PDB operation to be performed")) - } - if r.Spec.CDBResName == "" { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("cdbResName"), "Please specify the name of the CDB Kubernetes resource to use for PDB operations")) - } - if r.Spec.PDBName == "" { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("pdbName"), "Please specify name of the PDB to be created")) - } -} - -// Validate TDE information for Create, Plug and Unplug Actions -func (r *PDB) validateTDEInfo(allErrs *field.ErrorList) { - pdblog.Info("validateTDEInfo", "name", r.Name) - - if reflect.ValueOf(r.Spec.TDEPassword).IsZero() { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("tdePassword"), "Please specify a value for tdePassword.")) - } - if r.Spec.TDEKeystorePath == "" { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("tdeKeystorePath"), "Please specify a value for tdeKeystorePath.")) - } - if reflect.ValueOf(r.Spec.TDESecret).IsZero() { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("tdeSecret"), "Please specify a value for tdeSecret.")) - } - -} - -func (r *PDB) CheckObjExistence(action string, allErrs *field.ErrorList, pdb *PDB) { - /* BUG 36752465 - lrest operator - open non-existent pdb creates a lrpdb with status failed */ - pdblog.Info("Action [" + action + "] checkin " + pdb.Spec.PDBName + " existence") - if pdb.Status.OpenMode == "" { - *allErrs = append(*allErrs, field.NotFound(field.NewPath("Spec").Child("PDBName"), " "+pdb.Spec.PDBName+" does not exist : action "+action+" failure")) - - } -} diff --git a/apis/database/v1alpha1/shardingdatabase_webhook.go b/apis/database/v1alpha1/shardingdatabase_webhook.go index 4e7ea2e7..f5f25ecc 100644 --- a/apis/database/v1alpha1/shardingdatabase_webhook.go +++ b/apis/database/v1alpha1/shardingdatabase_webhook.go @@ -39,16 +39,9 @@ package v1alpha1 import ( - "strings" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/validation/field" ctrl "sigs.k8s.io/controller-runtime" logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/webhook" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" ) // log is for logging in this package. @@ -59,256 +52,3 @@ func (r *ShardingDatabase) SetupWebhookWithManager(mgr ctrl.Manager) error { For(r). Complete() } - -// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! - -//+kubebuilder:webhook:path=/mutate-database-oracle-com-v1alpha1-shardingdatabase,mutating=true,failurePolicy=fail,sideEffects=none,groups=database.oracle.com,resources=shardingdatabases,verbs=create;update,versions=v1alpha1,name=mshardingdatabasev1alpha1.kb.io,admissionReviewVersions=v1 - -var _ webhook.Defaulter = &ShardingDatabase{} - -// Default implements webhook.Defaulter so a webhook will be registered for the type -func (r *ShardingDatabase) Default() { - shardingdatabaselog.Info("default", "name", r.Name) - - // TODO(user): fill in your defaulting logic. - if r.Spec.GsmDevMode != "" { - r.Spec.GsmDevMode = "dev" - } - - if r.Spec.IsTdeWallet == "" { - r.Spec.IsTdeWallet = "disable" - } - for pindex := range r.Spec.Shard { - if strings.ToLower(r.Spec.Shard[pindex].IsDelete) == "" { - r.Spec.Shard[pindex].IsDelete = "disable" - } - } - -} - -// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. -//+kubebuilder:webhook:verbs=create;update;delete,path=/validate-database-oracle-com-v1alpha1-shardingdatabase,mutating=false,failurePolicy=fail,sideEffects=None,groups=database.oracle.com,resources=shardingdatabases,versions=v1alpha1,name=vshardingdatabasev1alpha1.kb.io,admissionReviewVersions=v1 - -var _ webhook.Validator = &ShardingDatabase{} - -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (r *ShardingDatabase) ValidateCreate() (admission.Warnings, error) { - shardingdatabaselog.Info("validate create", "name", r.Name) - - // TODO(user): fill in your validation logic upon object creation. - // Check Secret configuration - var validationErr field.ErrorList - var validationErrs1 field.ErrorList - - //namespaces := db.GetWatchNamespaces() - //_, containsNamespace := namespaces[r.Namespace] - // Check if the allowed namespaces maps contains the required namespace - // if len(namespaces) != 0 && !containsNamespace { - // validationErr = append(validationErr, - // field.Invalid(field.NewPath("metadata").Child("namespace"), r.Namespace, - // "Oracle database operator doesn't watch over this namespace")) - //} - - if r.Spec.DbSecret == nil { - validationErr = append(validationErr, - field.Invalid(field.NewPath("spec").Child("DbSecret"), r.Spec.DbSecret, - "DbSecret cannot be set to nil")) - } else { - if len(r.Spec.DbSecret.Name) == 0 { - validationErr = append(validationErr, - field.Invalid(field.NewPath("spec").Child("DbSecret").Child("Name"), r.Spec.DbSecret.Name, - "Secret name cannot be set empty")) - } - if len(r.Spec.DbSecret.PwdFileName) == 0 { - validationErr = append(validationErr, - field.Invalid(field.NewPath("spec").Child("DbSecret").Child("PwdFileName"), r.Spec.DbSecret.PwdFileName, - "Password file name cannot be set empty")) - } - if strings.ToLower(r.Spec.DbSecret.EncryptionType) != "base64" { - if strings.ToLower(r.Spec.DbSecret.KeyFileName) == "" { - validationErr = append(validationErr, - field.Invalid(field.NewPath("spec").Child("DbSecret").Child("KeyFileName"), r.Spec.DbSecret.KeyFileName, - "Key file name cannot be empty")) - } - } - - /** - if len(r.Spec.DbSecret.PwdFileMountLocation) == 0 { - validationErr = append(validationErr, - field.Invalid(field.NewPath("spec").Child("DbSecret").Child("PwdFileMountLocation"), r.Spec.DbSecret.PwdFileMountLocation, - "Password file mount location cannot be empty")) - } - - if len(r.Spec.DbSecret.KeyFileMountLocation) == 0 { - validationErr = append(validationErr, - field.Invalid(field.NewPath("spec").Child("DbSecret").Child("KeyFileMountLocation"), r.Spec.DbSecret.KeyFileMountLocation, - "KeyFileMountLocation file mount location cannot be empty")) - } - **/ - } - - if r.Spec.IsTdeWallet == "enable" { - if (len(r.Spec.FssStorageClass) == 0) && (len(r.Spec.TdeWalletPvc) == 0) { - validationErr = append(validationErr, - field.Invalid(field.NewPath("spec").Child("FssStorageClass"), r.Spec.FssStorageClass, - "FssStorageClass or TdeWalletPvc cannot be set empty if isTdeWallet set to true")) - - validationErr = append(validationErr, - field.Invalid(field.NewPath("spec").Child("TdeWalletPvc"), r.Spec.TdeWalletPvc, - "FssStorageClass or TdeWalletPvc cannot be set empty if isTdeWallet set to true")) - } - } - - if r.Spec.IsTdeWallet != "" { - if (strings.ToLower(strings.TrimSpace(r.Spec.IsTdeWallet)) != "enable") && (strings.ToLower(strings.TrimSpace(r.Spec.IsTdeWallet)) != "disable") { - validationErr = append(validationErr, - field.Invalid(field.NewPath("spec").Child("isTdeWallet"), r.Spec.IsTdeWallet, - "isTdeWallet can be set to only \"enable\" or \"disable\"")) - } - } - - validationErrs1 = r.validateShardIsDelete() - if validationErrs1 != nil { - validationErr = append(validationErr, validationErrs1...) - } - - validationErrs1 = r.validateFreeEdition() - if validationErrs1 != nil { - validationErr = append(validationErr, validationErrs1...) - } - - validationErrs1 = r.validateCatalogName() - if validationErrs1 != nil { - validationErr = append(validationErr, validationErrs1...) - } - - validationErrs1 = r.validateShardName() - if validationErrs1 != nil { - validationErr = append(validationErr, validationErrs1...) - } - - // TODO(user): fill in your validation logic upon object creation. - if len(validationErr) == 0 { - return nil, nil - } - - return nil, apierrors.NewInvalid( - schema.GroupKind{Group: "database.oracle.com", Kind: "ShardingDatabase"}, - r.Name, validationErr) -} - -// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *ShardingDatabase) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - shardingdatabaselog.Info("validate update", "name", r.Name) - - // TODO(user): fill in your validation logic upon object update. - return nil, nil -} - -// ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *ShardingDatabase) ValidateDelete() (admission.Warnings, error) { - shardingdatabaselog.Info("validate delete", "name", r.Name) - - // TODO(user): fill in your validation logic upon object deletion. - return nil, nil -} - -// ###### Vlaidation Block ################# - -func (r *ShardingDatabase) validateShardIsDelete() field.ErrorList { - - var validationErrs field.ErrorList - - for pindex := range r.Spec.Shard { - if (strings.ToLower(strings.TrimSpace(r.Spec.Shard[pindex].IsDelete)) != "enable") && (strings.ToLower(strings.TrimSpace(r.Spec.Shard[pindex].IsDelete)) != "disable") && (strings.ToLower(strings.TrimSpace(r.Spec.Shard[pindex].IsDelete)) != "failed") { - validationErrs = append(validationErrs, - field.Invalid(field.NewPath("spec").Child("shard").Child("isDelete"), r.Spec.Shard[pindex].IsDelete, - "r.Spec.Shard[pindex].IsDelete can be set to only enable|disable|failed")) - } - } - - if len(validationErrs) > 0 { - return validationErrs - } - return nil -} - -func (r *ShardingDatabase) validateFreeEdition() field.ErrorList { - - var validationErrs field.ErrorList - if strings.ToLower(r.Spec.DbEdition) == "free" { - // Shard Spec Checks - for i := 0; i < len(r.Spec.Shard); i++ { - for index, variable := range r.Spec.Shard[i].EnvVars { - if variable.Name == "ORACLE_SID" { - if strings.ToLower(variable.Value) != "free" { - validationErrs = append(validationErrs, field.Invalid(field.NewPath("spec").Child("shard").Child("EnvVars"), r.Spec.Shard[i].EnvVars[index].Name, - "r.Spec.Shard[i].EnvVars[index].Name ORACLE_SID value can only be set to free")) - } - } - if variable.Name == "ORACLE_PDB" { - if strings.ToLower(variable.Value) != "freepdb" { - validationErrs = append(validationErrs, field.Invalid(field.NewPath("spec").Child("shard").Child("EnvVars"), r.Spec.Shard[i].EnvVars[index].Name, - "r.Spec.Shard[i].EnvVars[index].Name ORACLE_PDB value can only be set to freepdb")) - } - } - } - } - // Catalog Spec Checks - for i := 0; i < len(r.Spec.Catalog); i++ { - for index, variable := range r.Spec.Catalog[i].EnvVars { - if variable.Name == "ORACLE_SID" { - if strings.ToLower(variable.Value) != "free" { - validationErrs = append(validationErrs, field.Invalid(field.NewPath("spec").Child("catalog").Child("EnvVars"), r.Spec.Catalog[i].EnvVars[index].Name, - "r.Spec.Catalog[i].EnvVars[index].Name ORACLE_SID value can only be set to free")) - } - } - if variable.Name == "ORACLE_PDB" { - if strings.ToLower(variable.Value) != "freepdb" { - validationErrs = append(validationErrs, field.Invalid(field.NewPath("spec").Child("catalog").Child("EnvVars"), r.Spec.Catalog[i].EnvVars[index].Name, - "r.Spec.Catalog[i].EnvVars[index].Name ORACLE_PDB value can only be set to freepdb")) - } - } - } - } - } - - if len(validationErrs) > 0 { - return validationErrs - } - return nil -} - -func (r *ShardingDatabase) validateShardName() field.ErrorList { - var validationErrs field.ErrorList - - for pindex := range r.Spec.Shard { - if len(r.Spec.Shard[pindex].Name) > 9 { - validationErrs = append(validationErrs, - field.Invalid(field.NewPath("spec").Child("shard").Child("Name"), r.Spec.Shard[pindex].Name, - "Shard Name cannot be greater than 9 characters.")) - } - } - - if len(validationErrs) > 0 { - return validationErrs - } - return nil -} - -func (r *ShardingDatabase) validateCatalogName() field.ErrorList { - var validationErrs field.ErrorList - - for pindex := range r.Spec.Catalog { - if len(r.Spec.Catalog[pindex].Name) > 9 { - validationErrs = append(validationErrs, - field.Invalid(field.NewPath("spec").Child("catalog").Child("Name"), r.Spec.Catalog[pindex].Name, - "Catalog Name cannot be greater than 9 characters.")) - } - } - - if len(validationErrs) > 0 { - return validationErrs - } - return nil -} diff --git a/apis/database/v1alpha1/singleinstancedatabase_webhook.go b/apis/database/v1alpha1/singleinstancedatabase_webhook.go index bc095f7c..d0cd88aa 100644 --- a/apis/database/v1alpha1/singleinstancedatabase_webhook.go +++ b/apis/database/v1alpha1/singleinstancedatabase_webhook.go @@ -39,9 +39,11 @@ package v1alpha1 import ( + "context" "strconv" "strings" "time" + "fmt" dbcommons "github.com/oracle/oracle-database-operator/commons/database" @@ -61,6 +63,8 @@ var singleinstancedatabaselog = logf.Log.WithName("singleinstancedatabase-resour func (r *SingleInstanceDatabase) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). For(r). + WithDefaulter(r). + WithValidator(r). Complete() } @@ -68,330 +72,341 @@ func (r *SingleInstanceDatabase) SetupWebhookWithManager(mgr ctrl.Manager) error //+kubebuilder:webhook:path=/mutate-database-oracle-com-v1alpha1-singleinstancedatabase,mutating=true,failurePolicy=fail,sideEffects=None,groups=database.oracle.com,resources=singleinstancedatabases,verbs=create;update,versions=v1alpha1,name=msingleinstancedatabase.kb.io,admissionReviewVersions={v1,v1beta1} -var _ webhook.Defaulter = &SingleInstanceDatabase{} +var _ webhook.CustomDefaulter = &SingleInstanceDatabase{} // Default implements webhook.Defaulter so a webhook will be registered for the type -func (r *SingleInstanceDatabase) Default() { - singleinstancedatabaselog.Info("default", "name", r.Name) +func (r *SingleInstanceDatabase) Default(ctx context.Context, obj runtime.Object) error { + sidb, ok := obj.(*SingleInstanceDatabase) + if !ok { + return apierrors.NewInternalError(fmt.Errorf("failed to cast obj object to SingleInstanceDatabase")) + } + + singleinstancedatabaselog.Info("default", "name", sidb.Name) - if r.Spec.LoadBalancer { + if sidb.Spec.LoadBalancer { // Annotations required for a flexible load balancer on oci - if r.Spec.ServiceAnnotations == nil { - r.Spec.ServiceAnnotations = make(map[string]string) + if sidb.Spec.ServiceAnnotations == nil { + sidb.Spec.ServiceAnnotations = make(map[string]string) } - _, ok := r.Spec.ServiceAnnotations["service.beta.kubernetes.io/oci-load-balancer-shape"] + _, ok := sidb.Spec.ServiceAnnotations["service.beta.kubernetes.io/oci-load-balancer-shape"] if !ok { - r.Spec.ServiceAnnotations["service.beta.kubernetes.io/oci-load-balancer-shape"] = "flexible" + sidb.Spec.ServiceAnnotations["service.beta.kubernetes.io/oci-load-balancer-shape"] = "flexible" } - _, ok = r.Spec.ServiceAnnotations["service.beta.kubernetes.io/oci-load-balancer-shape-flex-min"] + _, ok = sidb.Spec.ServiceAnnotations["service.beta.kubernetes.io/oci-load-balancer-shape-flex-min"] if !ok { - r.Spec.ServiceAnnotations["service.beta.kubernetes.io/oci-load-balancer-shape-flex-min"] = "10" + sidb.Spec.ServiceAnnotations["service.beta.kubernetes.io/oci-load-balancer-shape-flex-min"] = "10" } - _, ok = r.Spec.ServiceAnnotations["service.beta.kubernetes.io/oci-load-balancer-shape-flex-max"] + _, ok = sidb.Spec.ServiceAnnotations["service.beta.kubernetes.io/oci-load-balancer-shape-flex-max"] if !ok { - r.Spec.ServiceAnnotations["service.beta.kubernetes.io/oci-load-balancer-shape-flex-max"] = "100" + sidb.Spec.ServiceAnnotations["service.beta.kubernetes.io/oci-load-balancer-shape-flex-max"] = "100" } } - if r.Spec.AdminPassword.KeepSecret == nil { + if sidb.Spec.AdminPassword.KeepSecret == nil { keepSecret := true - r.Spec.AdminPassword.KeepSecret = &keepSecret + sidb.Spec.AdminPassword.KeepSecret = &keepSecret } - if r.Spec.Edition == "" { - if r.Spec.CreateAs == "clone" && !r.Spec.Image.PrebuiltDB { - r.Spec.Edition = "enterprise" + if sidb.Spec.Edition == "" { + if sidb.Spec.CreateAs == "clone" && !sidb.Spec.Image.PrebuiltDB { + sidb.Spec.Edition = "enterprise" } } - if r.Spec.CreateAs == "" { - r.Spec.CreateAs = "primary" + if sidb.Spec.CreateAs == "" { + sidb.Spec.CreateAs = "primary" } - if r.Spec.Sid == "" { - if r.Spec.Edition == "express" { - r.Spec.Sid = "XE" - } else if r.Spec.Edition == "free" { - r.Spec.Sid = "FREE" + if sidb.Spec.Sid == "" { + if sidb.Spec.Edition == "express" { + sidb.Spec.Sid = "XE" + } else if sidb.Spec.Edition == "free" { + sidb.Spec.Sid = "FREE" } else { - r.Spec.Sid = "ORCLCDB" + sidb.Spec.Sid = "ORCLCDB" } } - if r.Spec.Pdbname == "" { - if r.Spec.Edition == "express" { - r.Spec.Pdbname = "XEPDB1" - } else if r.Spec.Edition == "free" { - r.Spec.Pdbname = "FREEPDB1" + if sidb.Spec.Pdbname == "" { + if sidb.Spec.Edition == "express" { + sidb.Spec.Pdbname = "XEPDB1" + } else if sidb.Spec.Edition == "free" { + sidb.Spec.Pdbname = "FREEPDB1" } else { - r.Spec.Pdbname = "ORCLPDB1" + sidb.Spec.Pdbname = "ORCLPDB1" } } - if r.Spec.Edition == "express" || r.Spec.Edition == "free" { + if sidb.Spec.Edition == "express" || sidb.Spec.Edition == "free" { // Allow zero replicas as a means to bounce the DB - if r.Status.Replicas == 1 && r.Spec.Replicas > 1 { + if sidb.Status.Replicas == 1 && sidb.Spec.Replicas > 1 { // If not zero, default the replicas to 1 - r.Spec.Replicas = 1 + sidb.Spec.Replicas = 1 } } - if r.Spec.TrueCacheServices == nil { - r.Spec.TrueCacheServices = make([]string, 0) + if sidb.Spec.TrueCacheServices == nil { + sidb.Spec.TrueCacheServices = make([]string, 0) } + + return nil } // TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. //+kubebuilder:webhook:verbs=create;update;delete,path=/validate-database-oracle-com-v1alpha1-singleinstancedatabase,mutating=false,failurePolicy=fail,sideEffects=None,groups=database.oracle.com,resources=singleinstancedatabases,versions=v1alpha1,name=vsingleinstancedatabase.kb.io,admissionReviewVersions={v1,v1beta1} -var _ webhook.Validator = &SingleInstanceDatabase{} +var _ webhook.CustomValidator = &SingleInstanceDatabase{} // ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (r *SingleInstanceDatabase) ValidateCreate() (admission.Warnings, error) { - singleinstancedatabaselog.Info("validate create", "name", r.Name) +func (r *SingleInstanceDatabase) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + sidb, ok := obj.(*SingleInstanceDatabase) + if !ok { + return nil, apierrors.NewInternalError(fmt.Errorf("failed to cast obj object to SingleInstanceDatabase")) + } + singleinstancedatabaselog.Info("validate create", "name", sidb.Name) var allErrs field.ErrorList namespaces := dbcommons.GetWatchNamespaces() - _, containsNamespace := namespaces[r.Namespace] + _, containsNamespace := namespaces[sidb.Namespace] // Check if the allowed namespaces maps contains the required namespace if len(namespaces) != 0 && !containsNamespace { allErrs = append(allErrs, - field.Invalid(field.NewPath("metadata").Child("namespace"), r.Namespace, + field.Invalid(field.NewPath("metadata").Child("namespace"), sidb.Namespace, "Oracle database operator doesn't watch over this namespace")) } // Persistence spec validation - if r.Spec.Persistence.Size == "" && (r.Spec.Persistence.AccessMode != "" || - r.Spec.Persistence.StorageClass != "" || r.Spec.Persistence.DatafilesVolumeName != "") { + if sidb.Spec.Persistence.Size == "" && (sidb.Spec.Persistence.AccessMode != "" || + sidb.Spec.Persistence.StorageClass != "" || sidb.Spec.Persistence.DatafilesVolumeName != "") { allErrs = append(allErrs, - field.Invalid(field.NewPath("spec").Child("persistence").Child("size"), r.Spec.Persistence, + field.Invalid(field.NewPath("spec").Child("persistence").Child("size"), sidb.Spec.Persistence, "invalid persistence specification, specify required size")) } - if r.Spec.Persistence.Size != "" { - if r.Spec.Persistence.AccessMode == "" { + if sidb.Spec.Persistence.Size != "" { + if sidb.Spec.Persistence.AccessMode == "" { allErrs = append(allErrs, - field.Invalid(field.NewPath("spec").Child("persistence").Child("size"), r.Spec.Persistence, + field.Invalid(field.NewPath("spec").Child("persistence").Child("size"), sidb.Spec.Persistence, "invalid persistence specification, specify accessMode")) } - if r.Spec.Persistence.AccessMode != "ReadWriteMany" && r.Spec.Persistence.AccessMode != "ReadWriteOnce" { + if sidb.Spec.Persistence.AccessMode != "ReadWriteMany" && sidb.Spec.Persistence.AccessMode != "ReadWriteOnce" { allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("persistence").Child("accessMode"), - r.Spec.Persistence.AccessMode, "should be either \"ReadWriteOnce\" or \"ReadWriteMany\"")) + sidb.Spec.Persistence.AccessMode, "should be either \"ReadWriteOnce\" or \"ReadWriteMany\"")) } } - if r.Spec.CreateAs == "standby" { - if r.Spec.ArchiveLog != nil { + if sidb.Spec.CreateAs == "standby" { + if sidb.Spec.ArchiveLog != nil { allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("archiveLog"), - r.Spec.ArchiveLog, "archiveLog cannot be specified for standby databases")) + sidb.Spec.ArchiveLog, "archiveLog cannot be specified for standby databases")) } - if r.Spec.FlashBack != nil { + if sidb.Spec.FlashBack != nil { allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("flashBack"), - r.Spec.FlashBack, "flashBack cannot be specified for standby databases")) + sidb.Spec.FlashBack, "flashBack cannot be specified for standby databases")) } - if r.Spec.ForceLogging != nil { + if sidb.Spec.ForceLogging != nil { allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("forceLog"), - r.Spec.ForceLogging, "forceLog cannot be specified for standby databases")) + sidb.Spec.ForceLogging, "forceLog cannot be specified for standby databases")) } - if r.Spec.InitParams != nil { + if sidb.Spec.InitParams != nil { allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("initParams"), - r.Spec.InitParams, "initParams cannot be specified for standby databases")) + sidb.Spec.InitParams, "initParams cannot be specified for standby databases")) } - if r.Spec.Persistence.ScriptsVolumeName != "" { + if sidb.Spec.Persistence.ScriptsVolumeName != "" { allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("persistence").Child("scriptsVolumeName"), - r.Spec.Persistence.ScriptsVolumeName, "scriptsVolumeName cannot be specified for standby databases")) + sidb.Spec.Persistence.ScriptsVolumeName, "scriptsVolumeName cannot be specified for standby databases")) } - if r.Spec.EnableTCPS { + if sidb.Spec.EnableTCPS { allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("enableTCPS"), - r.Spec.EnableTCPS, "enableTCPS cannot be specified for standby databases")) + sidb.Spec.EnableTCPS, "enableTCPS cannot be specified for standby databases")) } } // Replica validation - if r.Spec.Replicas > 1 { + if sidb.Spec.Replicas > 1 { valMsg := "" - if r.Spec.Edition == "express" || r.Spec.Edition == "free" { - valMsg = "should be 1 for " + r.Spec.Edition + " edition" + if sidb.Spec.Edition == "express" || sidb.Spec.Edition == "free" { + valMsg = "should be 1 for " + sidb.Spec.Edition + " edition" } - if r.Spec.Persistence.Size == "" { + if sidb.Spec.Persistence.Size == "" { valMsg = "should be 1 if no persistence is specified" } if valMsg != "" { allErrs = append(allErrs, - field.Invalid(field.NewPath("spec").Child("replicas"), r.Spec.Replicas, valMsg)) + field.Invalid(field.NewPath("spec").Child("replicas"), sidb.Spec.Replicas, valMsg)) } } - if (r.Spec.CreateAs == "clone" || r.Spec.CreateAs == "standby") && r.Spec.PrimaryDatabaseRef == "" { + if (sidb.Spec.CreateAs == "clone" || sidb.Spec.CreateAs == "standby") && sidb.Spec.PrimaryDatabaseRef == "" { allErrs = append(allErrs, - field.Invalid(field.NewPath("spec").Child("primaryDatabaseRef"), r.Spec.PrimaryDatabaseRef, "Primary Database reference cannot be null for a secondary database")) + field.Invalid(field.NewPath("spec").Child("primaryDatabaseRef"), sidb.Spec.PrimaryDatabaseRef, "Primary Database reference cannot be null for a secondary database")) } - if r.Spec.Edition == "express" || r.Spec.Edition == "free" { - if r.Spec.CreateAs == "clone" { + if sidb.Spec.Edition == "express" || sidb.Spec.Edition == "free" { + if sidb.Spec.CreateAs == "clone" { allErrs = append(allErrs, - field.Invalid(field.NewPath("spec").Child("createAs"), r.Spec.CreateAs, - "Cloning not supported for "+r.Spec.Edition+" edition")) + field.Invalid(field.NewPath("spec").Child("createAs"), sidb.Spec.CreateAs, + "Cloning not supported for "+sidb.Spec.Edition+" edition")) } - if r.Spec.CreateAs == "standby" { + if sidb.Spec.CreateAs == "standby" { allErrs = append(allErrs, - field.Invalid(field.NewPath("spec").Child("createAs"), r.Spec.CreateAs, - "Physical Standby Database creation is not supported for "+r.Spec.Edition+" edition")) + field.Invalid(field.NewPath("spec").Child("createAs"), sidb.Spec.CreateAs, + "Physical Standby Database creation is not supported for "+sidb.Spec.Edition+" edition")) } - if r.Spec.Edition == "express" && strings.ToUpper(r.Spec.Sid) != "XE" { + if sidb.Spec.Edition == "express" && strings.ToUpper(sidb.Spec.Sid) != "XE" { allErrs = append(allErrs, - field.Invalid(field.NewPath("spec").Child("sid"), r.Spec.Sid, + field.Invalid(field.NewPath("spec").Child("sid"), sidb.Spec.Sid, "Express edition SID must only be XE")) } - if r.Spec.Edition == "free" && strings.ToUpper(r.Spec.Sid) != "FREE" { + if sidb.Spec.Edition == "free" && strings.ToUpper(sidb.Spec.Sid) != "FREE" { allErrs = append(allErrs, - field.Invalid(field.NewPath("spec").Child("sid"), r.Spec.Sid, + field.Invalid(field.NewPath("spec").Child("sid"), sidb.Spec.Sid, "Free edition SID must only be FREE")) } - if r.Spec.Edition == "express" && strings.ToUpper(r.Spec.Pdbname) != "XEPDB1" { + if sidb.Spec.Edition == "express" && strings.ToUpper(sidb.Spec.Pdbname) != "XEPDB1" { allErrs = append(allErrs, - field.Invalid(field.NewPath("spec").Child("pdbName"), r.Spec.Pdbname, + field.Invalid(field.NewPath("spec").Child("pdbName"), sidb.Spec.Pdbname, "Express edition PDB must be XEPDB1")) } - if r.Spec.Edition == "free" && strings.ToUpper(r.Spec.Pdbname) != "FREEPDB1" { + if sidb.Spec.Edition == "free" && strings.ToUpper(sidb.Spec.Pdbname) != "FREEPDB1" { allErrs = append(allErrs, - field.Invalid(field.NewPath("spec").Child("pdbName"), r.Spec.Pdbname, + field.Invalid(field.NewPath("spec").Child("pdbName"), sidb.Spec.Pdbname, "Free edition PDB must be FREEPDB1")) } - if r.Spec.InitParams != nil { + if sidb.Spec.InitParams != nil { allErrs = append(allErrs, - field.Invalid(field.NewPath("spec").Child("initParams"), *r.Spec.InitParams, - r.Spec.Edition+" edition does not support changing init parameters")) + field.Invalid(field.NewPath("spec").Child("initParams"), *sidb.Spec.InitParams, + sidb.Spec.Edition+" edition does not support changing init parameters")) } } else { - if r.Spec.Sid == "XE" { + if sidb.Spec.Sid == "XE" { allErrs = append(allErrs, - field.Invalid(field.NewPath("spec").Child("sid"), r.Spec.Sid, + field.Invalid(field.NewPath("spec").Child("sid"), sidb.Spec.Sid, "XE is reserved as the SID for Express edition of the database")) } - if r.Spec.Sid == "FREE" { + if sidb.Spec.Sid == "FREE" { allErrs = append(allErrs, - field.Invalid(field.NewPath("spec").Child("sid"), r.Spec.Sid, + field.Invalid(field.NewPath("spec").Child("sid"), sidb.Spec.Sid, "FREE is reserved as the SID for FREE edition of the database")) } } - if r.Spec.CreateAs == "clone" { - if r.Spec.Image.PrebuiltDB { + if sidb.Spec.CreateAs == "clone" { + if sidb.Spec.Image.PrebuiltDB { allErrs = append(allErrs, - field.Invalid(field.NewPath("spec").Child("createAs"), r.Spec.CreateAs, + field.Invalid(field.NewPath("spec").Child("createAs"), sidb.Spec.CreateAs, "cannot clone to create a prebuilt db")) - } else if strings.Contains(r.Spec.PrimaryDatabaseRef, ":") && strings.Contains(r.Spec.PrimaryDatabaseRef, "/") && r.Spec.Edition == "" { + } else if strings.Contains(sidb.Spec.PrimaryDatabaseRef, ":") && strings.Contains(sidb.Spec.PrimaryDatabaseRef, "/") && sidb.Spec.Edition == "" { //Edition must be passed when cloning from a source database other than same k8s cluster allErrs = append(allErrs, - field.Invalid(field.NewPath("spec").Child("edition"), r.Spec.CreateAs, + field.Invalid(field.NewPath("spec").Child("edition"), sidb.Spec.CreateAs, "Edition must be passed when cloning from a source database other than same k8s cluster")) } } - if r.Spec.CreateAs != "truecache" { - if len(r.Spec.TrueCacheServices) > 0 { + if sidb.Spec.CreateAs != "truecache" { + if len(sidb.Spec.TrueCacheServices) > 0 { allErrs = append(allErrs, - field.Invalid(field.NewPath("spec").Child("trueCacheServices"), r.Spec.TrueCacheServices, + field.Invalid(field.NewPath("spec").Child("trueCacheServices"), sidb.Spec.TrueCacheServices, "Creation of trueCacheServices only supported with True Cache instances")) } } - if r.Status.FlashBack == "true" && r.Spec.FlashBack != nil && *r.Spec.FlashBack { - if r.Spec.ArchiveLog != nil && !*r.Spec.ArchiveLog { + if sidb.Status.FlashBack == "true" && sidb.Spec.FlashBack != nil && *sidb.Spec.FlashBack { + if sidb.Spec.ArchiveLog != nil && !*sidb.Spec.ArchiveLog { allErrs = append(allErrs, - field.Invalid(field.NewPath("spec").Child("archiveLog"), r.Spec.ArchiveLog, + field.Invalid(field.NewPath("spec").Child("archiveLog"), sidb.Spec.ArchiveLog, "Cannot disable Archivelog. Please disable Flashback first.")) } } - if r.Status.ArchiveLog == "false" && r.Spec.ArchiveLog != nil && !*r.Spec.ArchiveLog { - if *r.Spec.FlashBack { + if sidb.Status.ArchiveLog == "false" && sidb.Spec.ArchiveLog != nil && !*sidb.Spec.ArchiveLog { + if sidb.Spec.FlashBack != nil && *sidb.Spec.FlashBack { allErrs = append(allErrs, - field.Invalid(field.NewPath("spec").Child("flashBack"), r.Spec.FlashBack, + field.Invalid(field.NewPath("spec").Child("flashBack"), sidb.Spec.FlashBack, "Cannot enable Flashback. Please enable Archivelog first.")) } } - if r.Spec.Persistence.VolumeClaimAnnotation != "" { - strParts := strings.Split(r.Spec.Persistence.VolumeClaimAnnotation, ":") + if sidb.Spec.Persistence.VolumeClaimAnnotation != "" { + strParts := strings.Split(sidb.Spec.Persistence.VolumeClaimAnnotation, ":") if len(strParts) != 2 { allErrs = append(allErrs, - field.Invalid(field.NewPath("spec").Child("persistence").Child("volumeClaimAnnotation"), r.Spec.Persistence.VolumeClaimAnnotation, + field.Invalid(field.NewPath("spec").Child("persistence").Child("volumeClaimAnnotation"), sidb.Spec.Persistence.VolumeClaimAnnotation, "volumeClaimAnnotation should be in : format.")) } } // servicePort and tcpServicePort validation - if !r.Spec.LoadBalancer { + if !sidb.Spec.LoadBalancer { // NodePort service is expected. In this case servicePort should be in range 30000-32767 - if r.Spec.ListenerPort != 0 && (r.Spec.ListenerPort < 30000 || r.Spec.ListenerPort > 32767) { + if sidb.Spec.ListenerPort != 0 && (sidb.Spec.ListenerPort < 30000 || sidb.Spec.ListenerPort > 32767) { allErrs = append(allErrs, - field.Invalid(field.NewPath("spec").Child("listenerPort"), r.Spec.ListenerPort, + field.Invalid(field.NewPath("spec").Child("listenerPort"), sidb.Spec.ListenerPort, "listenerPort should be in 30000-32767 range.")) } - if r.Spec.EnableTCPS && r.Spec.TcpsListenerPort != 0 && (r.Spec.TcpsListenerPort < 30000 || r.Spec.TcpsListenerPort > 32767) { + if sidb.Spec.EnableTCPS && sidb.Spec.TcpsListenerPort != 0 && (sidb.Spec.TcpsListenerPort < 30000 || sidb.Spec.TcpsListenerPort > 32767) { allErrs = append(allErrs, - field.Invalid(field.NewPath("spec").Child("tcpsListenerPort"), r.Spec.TcpsListenerPort, + field.Invalid(field.NewPath("spec").Child("tcpsListenerPort"), sidb.Spec.TcpsListenerPort, "tcpsListenerPort should be in 30000-32767 range.")) } } else { // LoadBalancer Service is expected. - if r.Spec.EnableTCPS && r.Spec.TcpsListenerPort == 0 && r.Spec.ListenerPort == int(dbcommons.CONTAINER_TCPS_PORT) { + if sidb.Spec.EnableTCPS && sidb.Spec.TcpsListenerPort == 0 && sidb.Spec.ListenerPort == int(dbcommons.CONTAINER_TCPS_PORT) { allErrs = append(allErrs, - field.Invalid(field.NewPath("spec").Child("listenerPort"), r.Spec.ListenerPort, + field.Invalid(field.NewPath("spec").Child("listenerPort"), sidb.Spec.ListenerPort, "listenerPort can not be 2484 as the default port for tcpsListenerPort is 2484.")) } } - if r.Spec.EnableTCPS && r.Spec.ListenerPort != 0 && r.Spec.TcpsListenerPort != 0 && r.Spec.ListenerPort == r.Spec.TcpsListenerPort { + if sidb.Spec.EnableTCPS && sidb.Spec.ListenerPort != 0 && sidb.Spec.TcpsListenerPort != 0 && sidb.Spec.ListenerPort == sidb.Spec.TcpsListenerPort { allErrs = append(allErrs, - field.Invalid(field.NewPath("spec").Child("tcpsListenerPort"), r.Spec.TcpsListenerPort, + field.Invalid(field.NewPath("spec").Child("tcpsListenerPort"), sidb.Spec.TcpsListenerPort, "listenerPort and tcpsListenerPort can not be equal.")) } // Certificate Renew Duration Validation - if r.Spec.EnableTCPS && r.Spec.TcpsCertRenewInterval != "" { - duration, err := time.ParseDuration(r.Spec.TcpsCertRenewInterval) + if sidb.Spec.EnableTCPS && sidb.Spec.TcpsCertRenewInterval != "" { + duration, err := time.ParseDuration(sidb.Spec.TcpsCertRenewInterval) if err != nil { allErrs = append(allErrs, - field.Invalid(field.NewPath("spec").Child("tcpsCertRenewInterval"), r.Spec.TcpsCertRenewInterval, + field.Invalid(field.NewPath("spec").Child("tcpsCertRenewInterval"), sidb.Spec.TcpsCertRenewInterval, "Please provide valid string to parse the tcpsCertRenewInterval.")) } maxLimit, _ := time.ParseDuration("8760h") minLimit, _ := time.ParseDuration("24h") if duration > maxLimit || duration < minLimit { allErrs = append(allErrs, - field.Invalid(field.NewPath("spec").Child("tcpsCertRenewInterval"), r.Spec.TcpsCertRenewInterval, + field.Invalid(field.NewPath("spec").Child("tcpsCertRenewInterval"), sidb.Spec.TcpsCertRenewInterval, "Please specify tcpsCertRenewInterval in the range: 24h to 8760h")) } } // tcpsTlsSecret validations - if !r.Spec.EnableTCPS && r.Spec.TcpsTlsSecret != "" { + if !sidb.Spec.EnableTCPS && sidb.Spec.TcpsTlsSecret != "" { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("tcpsTlsSecret"), " is allowed only if enableTCPS is true")) } - if r.Spec.TcpsTlsSecret != "" && r.Spec.TcpsCertRenewInterval != "" { + if sidb.Spec.TcpsTlsSecret != "" && sidb.Spec.TcpsCertRenewInterval != "" { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("tcpsCertRenewInterval"), " is applicable only for self signed certs")) } - if r.Spec.InitParams != nil { - if (r.Spec.InitParams.PgaAggregateTarget != 0 && r.Spec.InitParams.SgaTarget == 0) || (r.Spec.InitParams.PgaAggregateTarget == 0 && r.Spec.InitParams.SgaTarget != 0) { + if sidb.Spec.InitParams != nil { + if (sidb.Spec.InitParams.PgaAggregateTarget != 0 && sidb.Spec.InitParams.SgaTarget == 0) || (sidb.Spec.InitParams.PgaAggregateTarget == 0 && sidb.Spec.InitParams.SgaTarget != 0) { allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("initParams"), - r.Spec.InitParams, "initParams value invalid : Provide values for both pgaAggregateTarget and SgaTarget")) + sidb.Spec.InitParams, "initParams value invalid : Provide values for both pgaAggregateTarget and SgaTarget")) } } @@ -401,41 +416,45 @@ func (r *SingleInstanceDatabase) ValidateCreate() (admission.Warnings, error) { return nil, apierrors.NewInvalid( schema.GroupKind{Group: "database.oracle.com", Kind: "SingleInstanceDatabase"}, - r.Name, allErrs) + sidb.Name, allErrs) } // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *SingleInstanceDatabase) ValidateUpdate(oldRuntimeObject runtime.Object) (admission.Warnings, error) { - singleinstancedatabaselog.Info("validate update", "name", r.Name) +func (r *SingleInstanceDatabase) ValidateUpdate(ctx context.Context, oldRuntimeObject, newRuntimeObj runtime.Object) (admission.Warnings, error) { + new, ok := newRuntimeObj.(*SingleInstanceDatabase) + if !ok { + return nil, apierrors.NewInternalError(fmt.Errorf("failed to cast newRuntimeObj object to SingleInstanceDatabase")) + } + singleinstancedatabaselog.Info("validate update", "name", new.Name) var allErrs field.ErrorList // check creation validations first - warnings, err := r.ValidateCreate() + warnings, err := new.ValidateCreate(ctx, newRuntimeObj) if err != nil { return warnings, err } // Validate Deletion - if r.GetDeletionTimestamp() != nil { - warnings, err := r.ValidateDelete() + if new.GetDeletionTimestamp() != nil { + warnings, err := new.ValidateDelete(ctx, newRuntimeObj) if err != nil { return warnings, err } } // Now check for updation errors - old, ok := oldRuntimeObject.(*SingleInstanceDatabase) - if !ok { - return nil, nil + old, okay := oldRuntimeObject.(*SingleInstanceDatabase) + if !okay { + return nil, apierrors.NewInternalError(fmt.Errorf("failed to cast oldRuntimeObject object to SingleInstanceDatabase")) } if old.Status.CreatedAs == "clone" { - if r.Spec.Edition != "" && old.Status.Edition != "" && !strings.EqualFold(old.Status.Edition, r.Spec.Edition) { + if new.Spec.Edition != "" && old.Status.Edition != "" && !strings.EqualFold(old.Status.Edition, new.Spec.Edition) { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("edition"), "Edition of a cloned singleinstancedatabase cannot be changed post creation")) } - if !strings.EqualFold(old.Status.PrimaryDatabase, r.Spec.PrimaryDatabaseRef) { + if !strings.EqualFold(old.Status.PrimaryDatabase, new.Spec.PrimaryDatabaseRef) { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("primaryDatabaseRef"), "Primary database of a cloned singleinstancedatabase cannot be changed post creation")) } @@ -444,36 +463,36 @@ func (r *SingleInstanceDatabase) ValidateUpdate(oldRuntimeObject runtime.Object) if old.Status.Role != dbcommons.ValueUnavailable && old.Status.Role != "PRIMARY" { // Restriciting Patching of secondary databases archiveLog, forceLog, flashBack statusArchiveLog, _ := strconv.ParseBool(old.Status.ArchiveLog) - if r.Spec.ArchiveLog != nil && (statusArchiveLog != *r.Spec.ArchiveLog) { + if new.Spec.ArchiveLog != nil && (statusArchiveLog != *new.Spec.ArchiveLog) { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("archiveLog"), "cannot be changed")) } statusFlashBack, _ := strconv.ParseBool(old.Status.FlashBack) - if r.Spec.FlashBack != nil && (statusFlashBack != *r.Spec.FlashBack) { + if new.Spec.FlashBack != nil && (statusFlashBack != *new.Spec.FlashBack) { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("flashBack"), "cannot be changed")) } statusForceLogging, _ := strconv.ParseBool(old.Status.ForceLogging) - if r.Spec.ForceLogging != nil && (statusForceLogging != *r.Spec.ForceLogging) { + if new.Spec.ForceLogging != nil && (statusForceLogging != *new.Spec.ForceLogging) { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("forceLog"), "cannot be changed")) } // Restriciting Patching of secondary databases InitParams - if r.Spec.InitParams != nil { - if old.Status.InitParams.SgaTarget != r.Spec.InitParams.SgaTarget { + if new.Spec.InitParams != nil { + if old.Status.InitParams.SgaTarget != new.Spec.InitParams.SgaTarget { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("initParams").Child("sgaTarget"), "cannot be changed")) } - if old.Status.InitParams.PgaAggregateTarget != r.Spec.InitParams.PgaAggregateTarget { + if old.Status.InitParams.PgaAggregateTarget != new.Spec.InitParams.PgaAggregateTarget { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("initParams").Child("pgaAggregateTarget"), "cannot be changed")) } - if old.Status.InitParams.CpuCount != r.Spec.InitParams.CpuCount { + if old.Status.InitParams.CpuCount != new.Spec.InitParams.CpuCount { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("initParams").Child("cpuCount"), "cannot be changed")) } - if old.Status.InitParams.Processes != r.Spec.InitParams.Processes { + if old.Status.InitParams.Processes != new.Spec.InitParams.Processes { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("initParams").Child("processes"), "cannot be changed")) } @@ -481,7 +500,7 @@ func (r *SingleInstanceDatabase) ValidateUpdate(oldRuntimeObject runtime.Object) } // if Db is in a dataguard configuration or referred by Standby databases then Restrict enabling Tcps on the Primary DB - if r.Spec.EnableTCPS { + if new.Spec.EnableTCPS { if old.Status.DgBroker != nil { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("enableTCPS"), "cannot enable tcps as database is in a dataguard configuration")) @@ -491,38 +510,38 @@ func (r *SingleInstanceDatabase) ValidateUpdate(oldRuntimeObject runtime.Object) } } - if old.Status.DatafilesCreated == "true" && (old.Status.PrebuiltDB != r.Spec.Image.PrebuiltDB) { + if old.Status.DatafilesCreated == "true" && (old.Status.PrebuiltDB != new.Spec.Image.PrebuiltDB) { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("image").Child("prebuiltDB"), "cannot be changed")) } - if r.Spec.Edition != "" && old.Status.Edition != "" && !strings.EqualFold(old.Status.Edition, r.Spec.Edition) { + if new.Spec.Edition != "" && old.Status.Edition != "" && !strings.EqualFold(old.Status.Edition, new.Spec.Edition) { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("edition"), "cannot be changed")) } - if old.Status.Charset != "" && !strings.EqualFold(old.Status.Charset, r.Spec.Charset) { + if old.Status.Charset != "" && !strings.EqualFold(old.Status.Charset, new.Spec.Charset) { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("charset"), "cannot be changed")) } - if old.Status.Sid != "" && !strings.EqualFold(r.Spec.Sid, old.Status.Sid) { + if old.Status.Sid != "" && !strings.EqualFold(new.Spec.Sid, old.Status.Sid) { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("sid"), "cannot be changed")) } - if old.Status.Pdbname != "" && !strings.EqualFold(old.Status.Pdbname, r.Spec.Pdbname) { + if old.Status.Pdbname != "" && !strings.EqualFold(old.Status.Pdbname, new.Spec.Pdbname) { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("pdbname"), "cannot be changed")) } if old.Status.CreatedAs == "clone" && - (old.Status.PrimaryDatabase == dbcommons.ValueUnavailable && r.Spec.PrimaryDatabaseRef != "" || - old.Status.PrimaryDatabase != dbcommons.ValueUnavailable && old.Status.PrimaryDatabase != r.Spec.PrimaryDatabaseRef) { + (old.Status.PrimaryDatabase == dbcommons.ValueUnavailable && new.Spec.PrimaryDatabaseRef != "" || + old.Status.PrimaryDatabase != dbcommons.ValueUnavailable && old.Status.PrimaryDatabase != new.Spec.PrimaryDatabaseRef) { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("primaryDatabaseRef"), "cannot be changed")) } - if old.Status.OrdsReference != "" && r.Status.Persistence != r.Spec.Persistence { + if old.Status.OrdsReference != "" && new.Status.Persistence != new.Spec.Persistence { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("persistence"), "uninstall ORDS to change Persistence")) } - if old.Status.Replicas != r.Spec.Replicas && old.Status.DgBroker != nil { + if old.Status.Replicas != new.Spec.Replicas && old.Status.DgBroker != nil { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("replicas"), "cannot be updated for a database in a Data Guard configuration")) } @@ -532,22 +551,27 @@ func (r *SingleInstanceDatabase) ValidateUpdate(oldRuntimeObject runtime.Object) } return nil, apierrors.NewInvalid( schema.GroupKind{Group: "database.oracle.com", Kind: "SingleInstanceDatabase"}, - r.Name, allErrs) + new.Name, allErrs) } // ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *SingleInstanceDatabase) ValidateDelete() (admission.Warnings, error) { - singleinstancedatabaselog.Info("validate delete", "name", r.Name) +func (r *SingleInstanceDatabase) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + sidb, ok := obj.(*SingleInstanceDatabase) + if !ok { + return nil, apierrors.NewInternalError(fmt.Errorf("failed to cast obj object to SingleInstanceDatabase")) + } + + singleinstancedatabaselog.Info("validate delete", "name", sidb.Name) var allErrs field.ErrorList - if r.Status.OrdsReference != "" { + if sidb.Status.OrdsReference != "" { allErrs = append(allErrs, - field.Forbidden(field.NewPath("status").Child("ordsReference"), "delete "+r.Status.OrdsReference+" to cleanup this SIDB")) + field.Forbidden(field.NewPath("status").Child("ordsReference"), "delete "+sidb.Status.OrdsReference+" to cleanup this SIDB")) } if len(allErrs) == 0 { return nil, nil } return nil, apierrors.NewInvalid( schema.GroupKind{Group: "database.oracle.com", Kind: "SingleInstanceDatabase"}, - r.Name, allErrs) + sidb.Name, allErrs) } diff --git a/apis/database/v1alpha1/zz_generated.deepcopy.go b/apis/database/v1alpha1/zz_generated.deepcopy.go index b20cf834..511a2912 100644 --- a/apis/database/v1alpha1/zz_generated.deepcopy.go +++ b/apis/database/v1alpha1/zz_generated.deepcopy.go @@ -683,239 +683,6 @@ func (in *Backupconfig) DeepCopy() *Backupconfig { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CDB) DeepCopyInto(out *CDB) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - out.Status = in.Status -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CDB. -func (in *CDB) DeepCopy() *CDB { - if in == nil { - return nil - } - out := new(CDB) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *CDB) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CDBAdminPassword) DeepCopyInto(out *CDBAdminPassword) { - *out = *in - out.Secret = in.Secret -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CDBAdminPassword. -func (in *CDBAdminPassword) DeepCopy() *CDBAdminPassword { - if in == nil { - return nil - } - out := new(CDBAdminPassword) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CDBAdminUser) DeepCopyInto(out *CDBAdminUser) { - *out = *in - out.Secret = in.Secret -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CDBAdminUser. -func (in *CDBAdminUser) DeepCopy() *CDBAdminUser { - if in == nil { - return nil - } - out := new(CDBAdminUser) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CDBList) DeepCopyInto(out *CDBList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]CDB, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CDBList. -func (in *CDBList) DeepCopy() *CDBList { - if in == nil { - return nil - } - out := new(CDBList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *CDBList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CDBPRIVKEY) DeepCopyInto(out *CDBPRIVKEY) { - *out = *in - out.Secret = in.Secret -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CDBPRIVKEY. -func (in *CDBPRIVKEY) DeepCopy() *CDBPRIVKEY { - if in == nil { - return nil - } - out := new(CDBPRIVKEY) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CDBPUBKEY) DeepCopyInto(out *CDBPUBKEY) { - *out = *in - out.Secret = in.Secret -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CDBPUBKEY. -func (in *CDBPUBKEY) DeepCopy() *CDBPUBKEY { - if in == nil { - return nil - } - out := new(CDBPUBKEY) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CDBSecret) DeepCopyInto(out *CDBSecret) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CDBSecret. -func (in *CDBSecret) DeepCopy() *CDBSecret { - if in == nil { - return nil - } - out := new(CDBSecret) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CDBSpec) DeepCopyInto(out *CDBSpec) { - *out = *in - out.SysAdminPwd = in.SysAdminPwd - out.CDBAdminUser = in.CDBAdminUser - out.CDBAdminPwd = in.CDBAdminPwd - out.CDBTlsKey = in.CDBTlsKey - out.CDBTlsCrt = in.CDBTlsCrt - out.ORDSPwd = in.ORDSPwd - out.WebServerUser = in.WebServerUser - out.WebServerPwd = in.WebServerPwd - if in.NodeSelector != nil { - in, out := &in.NodeSelector, &out.NodeSelector - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - out.CDBPubKey = in.CDBPubKey - out.CDBPriKey = in.CDBPriKey -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CDBSpec. -func (in *CDBSpec) DeepCopy() *CDBSpec { - if in == nil { - return nil - } - out := new(CDBSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CDBStatus) DeepCopyInto(out *CDBStatus) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CDBStatus. -func (in *CDBStatus) DeepCopy() *CDBStatus { - if in == nil { - return nil - } - out := new(CDBStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CDBSysAdminPassword) DeepCopyInto(out *CDBSysAdminPassword) { - *out = *in - out.Secret = in.Secret -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CDBSysAdminPassword. -func (in *CDBSysAdminPassword) DeepCopy() *CDBSysAdminPassword { - if in == nil { - return nil - } - out := new(CDBSysAdminPassword) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CDBTLSCRT) DeepCopyInto(out *CDBTLSCRT) { - *out = *in - out.Secret = in.Secret -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CDBTLSCRT. -func (in *CDBTLSCRT) DeepCopy() *CDBTLSCRT { - if in == nil { - return nil - } - out := new(CDBTLSCRT) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CDBTLSKEY) DeepCopyInto(out *CDBTLSKEY) { - *out = *in - out.Secret = in.Secret -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CDBTLSKEY. -func (in *CDBTLSKEY) DeepCopy() *CDBTLSKEY { - if in == nil { - return nil - } - out := new(CDBTLSKEY) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CatalogSpec) DeepCopyInto(out *CatalogSpec) { *out = *in @@ -1703,22 +1470,6 @@ func (in *KMSDetailsStatus) DeepCopy() *KMSDetailsStatus { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ORDSPassword) DeepCopyInto(out *ORDSPassword) { - *out = *in - out.Secret = in.Secret -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ORDSPassword. -func (in *ORDSPassword) DeepCopy() *ORDSPassword { - if in == nil { - return nil - } - out := new(ORDSPassword) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OciAcdSpec) DeepCopyInto(out *OciAcdSpec) { *out = *in @@ -1742,8 +1493,8 @@ func (in *OciAcdSpec) DeepCopy() *OciAcdSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OciAdbSpec) DeepCopyInto(out *OciAdbSpec) { *out = *in - if in.Ocid != nil { - in, out := &in.Ocid, &out.Ocid + if in.Id != nil { + in, out := &in.Id, &out.Id *out = new(string) **out = **in } @@ -1987,65 +1738,6 @@ func (in *OracleRestDataServiceStatus) DeepCopy() *OracleRestDataServiceStatus { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PDB) DeepCopyInto(out *PDB) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - out.Status = in.Status -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PDB. -func (in *PDB) DeepCopy() *PDB { - if in == nil { - return nil - } - out := new(PDB) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *PDB) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PDBAdminName) DeepCopyInto(out *PDBAdminName) { - *out = *in - out.Secret = in.Secret -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PDBAdminName. -func (in *PDBAdminName) DeepCopy() *PDBAdminName { - if in == nil { - return nil - } - out := new(PDBAdminName) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PDBAdminPassword) DeepCopyInto(out *PDBAdminPassword) { - *out = *in - out.Secret = in.Secret -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PDBAdminPassword. -func (in *PDBAdminPassword) DeepCopy() *PDBAdminPassword { - if in == nil { - return nil - } - out := new(PDBAdminPassword) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PDBConfig) DeepCopyInto(out *PDBConfig) { *out = *in @@ -2157,204 +1849,6 @@ func (in *PDBDetailsStatus) DeepCopy() *PDBDetailsStatus { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PDBList) DeepCopyInto(out *PDBList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]PDB, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PDBList. -func (in *PDBList) DeepCopy() *PDBList { - if in == nil { - return nil - } - out := new(PDBList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *PDBList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PDBPRIVKEY) DeepCopyInto(out *PDBPRIVKEY) { - *out = *in - out.Secret = in.Secret -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PDBPRIVKEY. -func (in *PDBPRIVKEY) DeepCopy() *PDBPRIVKEY { - if in == nil { - return nil - } - out := new(PDBPRIVKEY) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PDBPUBKEY) DeepCopyInto(out *PDBPUBKEY) { - *out = *in - out.Secret = in.Secret -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PDBPUBKEY. -func (in *PDBPUBKEY) DeepCopy() *PDBPUBKEY { - if in == nil { - return nil - } - out := new(PDBPUBKEY) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PDBSecret) DeepCopyInto(out *PDBSecret) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PDBSecret. -func (in *PDBSecret) DeepCopy() *PDBSecret { - if in == nil { - return nil - } - out := new(PDBSecret) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PDBSpec) DeepCopyInto(out *PDBSpec) { - *out = *in - out.PDBTlsKey = in.PDBTlsKey - out.PDBTlsCrt = in.PDBTlsCrt - out.PDBTlsCat = in.PDBTlsCat - out.AdminName = in.AdminName - out.AdminPwd = in.AdminPwd - out.WebServerUsr = in.WebServerUsr - out.WebServerPwd = in.WebServerPwd - if in.ReuseTempFile != nil { - in, out := &in.ReuseTempFile, &out.ReuseTempFile - *out = new(bool) - **out = **in - } - if in.UnlimitedStorage != nil { - in, out := &in.UnlimitedStorage, &out.UnlimitedStorage - *out = new(bool) - **out = **in - } - if in.AsClone != nil { - in, out := &in.AsClone, &out.AsClone - *out = new(bool) - **out = **in - } - if in.TDEImport != nil { - in, out := &in.TDEImport, &out.TDEImport - *out = new(bool) - **out = **in - } - if in.TDEExport != nil { - in, out := &in.TDEExport, &out.TDEExport - *out = new(bool) - **out = **in - } - out.TDEPassword = in.TDEPassword - out.TDESecret = in.TDESecret - if in.GetScript != nil { - in, out := &in.GetScript, &out.GetScript - *out = new(bool) - **out = **in - } - out.PDBPubKey = in.PDBPubKey - out.PDBPriKey = in.PDBPriKey -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PDBSpec. -func (in *PDBSpec) DeepCopy() *PDBSpec { - if in == nil { - return nil - } - out := new(PDBSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PDBStatus) DeepCopyInto(out *PDBStatus) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PDBStatus. -func (in *PDBStatus) DeepCopy() *PDBStatus { - if in == nil { - return nil - } - out := new(PDBStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PDBTLSCAT) DeepCopyInto(out *PDBTLSCAT) { - *out = *in - out.Secret = in.Secret -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PDBTLSCAT. -func (in *PDBTLSCAT) DeepCopy() *PDBTLSCAT { - if in == nil { - return nil - } - out := new(PDBTLSCAT) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PDBTLSCRT) DeepCopyInto(out *PDBTLSCRT) { - *out = *in - out.Secret = in.Secret -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PDBTLSCRT. -func (in *PDBTLSCRT) DeepCopy() *PDBTLSCRT { - if in == nil { - return nil - } - out := new(PDBTLSCRT) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PDBTLSKEY) DeepCopyInto(out *PDBTLSKEY) { - *out = *in - out.Secret = in.Secret -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PDBTLSKEY. -func (in *PDBTLSKEY) DeepCopy() *PDBTLSKEY { - if in == nil { - return nil - } - out := new(PDBTLSKEY) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PasswordSpec) DeepCopyInto(out *PasswordSpec) { *out = *in @@ -2920,38 +2414,6 @@ func (in *SourceSpec) DeepCopy() *SourceSpec { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TDEPwd) DeepCopyInto(out *TDEPwd) { - *out = *in - out.Secret = in.Secret -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TDEPwd. -func (in *TDEPwd) DeepCopy() *TDEPwd { - if in == nil { - return nil - } - out := new(TDEPwd) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TDESecret) DeepCopyInto(out *TDESecret) { - *out = *in - out.Secret = in.Secret -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TDESecret. -func (in *TDESecret) DeepCopy() *TDESecret { - if in == nil { - return nil - } - out := new(TDESecret) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TargetSpec) DeepCopyInto(out *TargetSpec) { *out = *in @@ -3024,67 +2486,3 @@ func (in *WalletSpec) DeepCopy() *WalletSpec { in.DeepCopyInto(out) return out } - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *WebServerPassword) DeepCopyInto(out *WebServerPassword) { - *out = *in - out.Secret = in.Secret -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebServerPassword. -func (in *WebServerPassword) DeepCopy() *WebServerPassword { - if in == nil { - return nil - } - out := new(WebServerPassword) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *WebServerPasswordPDB) DeepCopyInto(out *WebServerPasswordPDB) { - *out = *in - out.Secret = in.Secret -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebServerPasswordPDB. -func (in *WebServerPasswordPDB) DeepCopy() *WebServerPasswordPDB { - if in == nil { - return nil - } - out := new(WebServerPasswordPDB) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *WebServerUser) DeepCopyInto(out *WebServerUser) { - *out = *in - out.Secret = in.Secret -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebServerUser. -func (in *WebServerUser) DeepCopy() *WebServerUser { - if in == nil { - return nil - } - out := new(WebServerUser) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *WebServerUserPDB) DeepCopyInto(out *WebServerUserPDB) { - *out = *in - out.Secret = in.Secret -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebServerUserPDB. -func (in *WebServerUserPDB) DeepCopy() *WebServerUserPDB { - if in == nil { - return nil - } - out := new(WebServerUserPDB) - in.DeepCopyInto(out) - return out -} diff --git a/apis/database/v4/adbfamily_common_spec.go b/apis/database/v4/adbfamily_common_spec.go index 87434852..57b80352 100644 --- a/apis/database/v4/adbfamily_common_spec.go +++ b/apis/database/v4/adbfamily_common_spec.go @@ -57,11 +57,11 @@ type K8sAdbSpec struct { } type OciAdbSpec struct { - OCID *string `json:"ocid,omitempty"` + Id *string `json:"id,omitempty"` } // TargetSpec defines the spec of the target for backup/restore runs. type TargetSpec struct { - K8sAdb K8sAdbSpec `json:"k8sADB,omitempty"` - OciAdb OciAdbSpec `json:"ociADB,omitempty"` + K8sAdb K8sAdbSpec `json:"k8sAdb,omitempty"` + OciAdb OciAdbSpec `json:"ociAdb,omitempty"` } diff --git a/apis/database/v4/autonomouscontainerdatabase_webhook.go b/apis/database/v4/autonomouscontainerdatabase_webhook.go index 9fcb9d8b..1cd8b2b4 100644 --- a/apis/database/v4/autonomouscontainerdatabase_webhook.go +++ b/apis/database/v4/autonomouscontainerdatabase_webhook.go @@ -39,6 +39,8 @@ package v4 import ( + "context" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -55,39 +57,42 @@ var autonomouscontainerdatabaselog = logf.Log.WithName("autonomouscontainerdatab func (r *AutonomousContainerDatabase) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). For(r). + WithValidator(r). Complete() } //+kubebuilder:webhook:verbs=create;update,path=/validate-database-oracle-com-v4-autonomouscontainerdatabase,mutating=false,failurePolicy=fail,sideEffects=None,groups=database.oracle.com,resources=autonomouscontainerdatabases,versions=v4,name=vautonomouscontainerdatabasev4.kb.io,admissionReviewVersions=v1 -var _ webhook.Validator = &AutonomousContainerDatabase{} +var _ webhook.CustomValidator = &AutonomousContainerDatabase{} // ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (r *AutonomousContainerDatabase) ValidateCreate() (admission.Warnings, error) { - autonomouscontainerdatabaselog.Info("validate create", "name", r.Name) +func (r *AutonomousContainerDatabase) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { return nil, nil } // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *AutonomousContainerDatabase) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - var allErrs field.ErrorList - var oldACD *AutonomousContainerDatabase = old.(*AutonomousContainerDatabase) +func (r *AutonomousContainerDatabase) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { + var ( + allErrs field.ErrorList + oldAcd *AutonomousContainerDatabase = oldObj.(*AutonomousContainerDatabase) + newAcd *AutonomousContainerDatabase = newObj.(*AutonomousContainerDatabase) + ) - autonomouscontainerdatabaselog.Info("validate update", "name", r.Name) + autonomouscontainerdatabaselog.Info("validate update", "name", newAcd.Name) // skip the update of adding ADB OCID or binding - if oldACD.Status.LifecycleState == "" { + if oldAcd.Status.LifecycleState == "" { return nil, nil } // cannot update when the old state is in intermediate state, except for the terminate operatrion - var copiedSpec *AutonomousContainerDatabaseSpec = r.Spec.DeepCopy() - changed, err := RemoveUnchangedFields(oldACD.Spec, copiedSpec) + var copiedSpec *AutonomousContainerDatabaseSpec = newAcd.Spec.DeepCopy() + changed, err := RemoveUnchangedFields(oldAcd.Spec, copiedSpec) if err != nil { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), err.Error())) } - if IsACDIntermediateState(oldACD.Status.LifecycleState) && changed { + if IsACDIntermediateState(oldAcd.Status.LifecycleState) && changed { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), "cannot change the spec when the lifecycleState is in an intermdeiate state")) @@ -98,13 +103,10 @@ func (r *AutonomousContainerDatabase) ValidateUpdate(old runtime.Object) (admiss } return nil, apierrors.NewInvalid( schema.GroupKind{Group: "database.oracle.com", Kind: "AutonomousContainerDatabase"}, - r.Name, allErrs) + newAcd.Name, allErrs) } // ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *AutonomousContainerDatabase) ValidateDelete() (admission.Warnings, error) { - autonomouscontainerdatabaselog.Info("validate delete", "name", r.Name) - - // TODO(user): fill in your validation logic upon object deletion. +func (r *AutonomousContainerDatabase) ValidateDelete(context.Context, runtime.Object) (admission.Warnings, error) { return nil, nil } diff --git a/apis/database/v4/autonomousdatabase_types.go b/apis/database/v4/autonomousdatabase_types.go index 628dd882..6643e55c 100644 --- a/apis/database/v4/autonomousdatabase_types.go +++ b/apis/database/v4/autonomousdatabase_types.go @@ -52,7 +52,7 @@ import ( // AutonomousDatabaseSpec defines the desired state of AutonomousDatabase // Important: Run "make" to regenerate code after modifying this file type AutonomousDatabaseSpec struct { - // +kubebuilder:validation:Enum:="";Create;Sync;Update;Stop;Start;Terminate;Clone + // +kubebuilder:validation:Enum:="";Create;Sync;Update;Stop;Start;Terminate;Clone;Switchover;Failover Action string `json:"action"` Details AutonomousDatabaseDetails `json:"details,omitempty"` Clone AutonomousDatabaseClone `json:"clone,omitempty"` @@ -197,7 +197,7 @@ type ConnectionStringSpec struct { // +kubebuilder:printcolumn:JSONPath=".spec.details.dbName",name="Db Name",type=string // +kubebuilder:printcolumn:JSONPath=".status.lifecycleState",name="State",type=string // +kubebuilder:printcolumn:JSONPath=".spec.details.isDedicated",name="Dedicated",type=string -// +kubebuilder:printcolumn:JSONPath=".spec.details.cpuCoreCount",name="OCPUs",type=integer +// +kubebuilder:printcolumn:JSONPath=".spec.details.computeCount",name="Compute Count",type=number // +kubebuilder:printcolumn:JSONPath=".spec.details.dataStorageSizeInTBs",name="Storage (TB)",type=integer // +kubebuilder:printcolumn:JSONPath=".spec.details.dbWorkload",name="Workload Type",type=string // +kubebuilder:printcolumn:JSONPath=".status.timeCreated",name="Created",type=string diff --git a/apis/database/v4/autonomousdatabase_webhook.go b/apis/database/v4/autonomousdatabase_webhook.go index f7eb60aa..d925fd12 100644 --- a/apis/database/v4/autonomousdatabase_webhook.go +++ b/apis/database/v4/autonomousdatabase_webhook.go @@ -39,6 +39,8 @@ package v4 import ( + "context" + dbcommons "github.com/oracle/oracle-database-operator/commons/database" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -56,28 +58,32 @@ var autonomousdatabaselog = logf.Log.WithName("autonomousdatabase-resource") func (r *AutonomousDatabase) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). For(r). + WithValidator(r). Complete() } // +kubebuilder:webhook:verbs=create;update,path=/validate-database-oracle-com-v4-autonomousdatabase,mutating=false,failurePolicy=fail,sideEffects=None,groups=database.oracle.com,resources=autonomousdatabases,versions=v4,name=vautonomousdatabasev4.kb.io,admissionReviewVersions=v1 -var _ webhook.Validator = &AutonomousDatabase{} + +var _ webhook.CustomValidator = &AutonomousDatabase{} // ValidateCreate implements webhook.Validator so a webhook will be registered for the type // ValidateCreate checks if the spec is valid for a provisioning or a binding operation -func (r *AutonomousDatabase) ValidateCreate() (admission.Warnings, error) { +func (r *AutonomousDatabase) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { var allErrs field.ErrorList - autonomousdatabaselog.Info("validate create", "name", r.Name) + adb := obj.(*AutonomousDatabase) + + autonomousdatabaselog.Info("validate create", "name", adb.Name) namespaces := dbcommons.GetWatchNamespaces() _, hasEmptyString := namespaces[""] isClusterScoped := len(namespaces) == 1 && hasEmptyString if !isClusterScoped { - _, containsNamespace := namespaces[r.Namespace] + _, containsNamespace := namespaces[adb.Namespace] // Check if the allowed namespaces maps contains the required namespace if len(namespaces) != 0 && !containsNamespace { allErrs = append(allErrs, - field.Invalid(field.NewPath("metadata").Child("namespace"), r.Namespace, + field.Invalid(field.NewPath("metadata").Child("namespace"), adb.Namespace, "Oracle database operator doesn't watch over this namespace")) } } @@ -87,47 +93,17 @@ func (r *AutonomousDatabase) ValidateCreate() (admission.Warnings, error) { } return nil, apierrors.NewInvalid( schema.GroupKind{Group: "database.oracle.com", Kind: "AutonomousDatabase"}, - r.Name, allErrs) + adb.Name, allErrs) } // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *AutonomousDatabase) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - var allErrs field.ErrorList - var oldADB *AutonomousDatabase = old.(*AutonomousDatabase) - - autonomousdatabaselog.Info("validate update", "name", r.Name) - - // skip the verification of adding ADB OCID or binding - // if oldADB.Status.LifecycleState == "" { - // return nil, nil - // } - - // cannot update when the old state is in intermediate, except for the change to the hardLink or the terminate operatrion during valid lifecycleState - // var copySpec *AutonomousDatabaseSpec = r.Spec.DeepCopy() - // specChanged, err := RemoveUnchangedFields(oldADB.Spec, copySpec) - // if err != nil { - // allErrs = append(allErrs, - // field.Forbidden(field.NewPath("spec"), err.Error())) - // } +func (r *AutonomousDatabase) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { + var ( + allErrs field.ErrorList + newAdb = newObj.(*AutonomousDatabase) + ) - // hardLinkChanged := copySpec.HardLink != nil - - // isTerminateOp := CanBeTerminated(oldADB.Status.LifecycleState) && copySpec.Action == "Terminate" - - // if specChanged && IsAdbIntermediateState(oldADB.Status.LifecycleState) && !isTerminateOp && !hardLinkChanged { - // allErrs = append(allErrs, - // field.Forbidden(field.NewPath("spec"), - // "cannot change the spec when the lifecycleState is in an intermdeiate state")) - // } - - // cannot modify autonomousDatabaseOCID - if r.Spec.Details.Id != nil && - oldADB.Spec.Details.Id != nil && - *r.Spec.Details.Id != *oldADB.Spec.Details.Id { - allErrs = append(allErrs, - field.Forbidden(field.NewPath("spec").Child("details").Child("autonomousDatabaseOCID"), - "autonomousDatabaseOCID cannot be modified")) - } + autonomousdatabaselog.Info("validate update", "name", newAdb.Name) allErrs = validateCommon(r, allErrs) @@ -136,7 +112,7 @@ func (r *AutonomousDatabase) ValidateUpdate(old runtime.Object) (admission.Warni } return nil, apierrors.NewInvalid( schema.GroupKind{Group: "database.oracle.com", Kind: "AutonomousDatabase"}, - r.Name, allErrs) + newAdb.Name, allErrs) } func validateCommon(adb *AutonomousDatabase, allErrs field.ErrorList) field.ErrorList { @@ -149,7 +125,7 @@ func validateCommon(adb *AutonomousDatabase, allErrs field.ErrorList) field.Erro if adb.Spec.Wallet.Password.K8sSecret.Name != nil && adb.Spec.Wallet.Password.OciSecret.Id != nil { allErrs = append(allErrs, - field.Forbidden(field.NewPath("spec").Child("details").Child("wallet").Child("password"), + field.Forbidden(field.NewPath("spec").Child("wallet").Child("password"), "cannot apply k8sSecret.name and ociSecret.ocid at the same time")) } @@ -157,14 +133,6 @@ func validateCommon(adb *AutonomousDatabase, allErrs field.ErrorList) field.Erro } // ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *AutonomousDatabase) ValidateDelete() (admission.Warnings, error) { - autonomousdatabaselog.Info("validate delete", "name", r.Name) +func (r *AutonomousDatabase) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { return nil, nil } - -// Returns true if AutonomousContainerDatabaseOCID has value. -// We don't use Details.IsDedicated because the parameter might be null when it's a provision operation. -func isDedicated(adb *AutonomousDatabase) bool { - return adb.Spec.Details.AutonomousContainerDatabase.K8sAcd.Name != nil || - adb.Spec.Details.AutonomousContainerDatabase.OciAcd.Id != nil -} diff --git a/apis/database/v4/autonomousdatabasebackup_types.go b/apis/database/v4/autonomousdatabasebackup_types.go index 925256c0..c38c079b 100644 --- a/apis/database/v4/autonomousdatabasebackup_types.go +++ b/apis/database/v4/autonomousdatabasebackup_types.go @@ -108,7 +108,7 @@ func init() { // Implement conversion.Hub interface, which means any resource version can convert into v4 func (*AutonomousDatabaseBackup) Hub() {} -func (b *AutonomousDatabaseBackup) UpdateStatusFromOCIBackup(ociBackup database.AutonomousDatabaseBackup, ociADB database.AutonomousDatabase) { +func (b *AutonomousDatabaseBackup) UpdateStatusFromOCIBackup(ociBackup database.AutonomousDatabaseBackup, ociAdb database.AutonomousDatabase) { b.Status.AutonomousDatabaseOCID = *ociBackup.AutonomousDatabaseId b.Status.CompartmentOCID = *ociBackup.CompartmentId b.Status.Type = ociBackup.Type @@ -119,8 +119,8 @@ func (b *AutonomousDatabaseBackup) UpdateStatusFromOCIBackup(ociBackup database. b.Status.TimeStarted = FormatSDKTime(ociBackup.TimeStarted) b.Status.TimeEnded = FormatSDKTime(ociBackup.TimeEnded) - b.Status.DBDisplayName = *ociADB.DisplayName - b.Status.DBName = *ociADB.DbName + b.Status.DBDisplayName = *ociAdb.DisplayName + b.Status.DBName = *ociAdb.DbName } // GetTimeEnded returns the status.timeEnded in SDKTime format diff --git a/apis/database/v4/autonomousdatabasebackup_webhook.go b/apis/database/v4/autonomousdatabasebackup_webhook.go index 7858adce..345d186a 100644 --- a/apis/database/v4/autonomousdatabasebackup_webhook.go +++ b/apis/database/v4/autonomousdatabasebackup_webhook.go @@ -39,6 +39,8 @@ package v4 import ( + "context" + dbcommons "github.com/oracle/oracle-database-operator/commons/database" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -56,25 +58,20 @@ var autonomousdatabasebackuplog = logf.Log.WithName("autonomousdatabasebackup-re func (r *AutonomousDatabaseBackup) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). For(r). + WithValidator(r). + WithValidator(r). Complete() } -//+kubebuilder:webhook:path=/mutate-database-oracle-com-v4-autonomousdatabasebackup,mutating=true,failurePolicy=fail,sideEffects=None,groups=database.oracle.com,resources=autonomousdatabasebackups,verbs=create;update,versions=v4,name=mautonomousdatabasebackupv4.kb.io,admissionReviewVersions=v1 - -var _ webhook.Defaulter = &AutonomousDatabaseBackup{} - -// Default implements webhook.Defaulter so a webhook will be registered for the type -func (r *AutonomousDatabaseBackup) Default() { - autonomousdatabasebackuplog.Info("default", "name", r.Name) -} - //+kubebuilder:webhook:verbs=create;update,path=/validate-database-oracle-com-v4-autonomousdatabasebackup,mutating=false,failurePolicy=fail,sideEffects=None,groups=database.oracle.com,resources=autonomousdatabasebackups,versions=v4,name=vautonomousdatabasebackupv4.kb.io,admissionReviewVersions=v1 -var _ webhook.Validator = &AutonomousDatabaseBackup{} +var _ webhook.CustomValidator = &AutonomousDatabaseBackup{} // ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (r *AutonomousDatabaseBackup) ValidateCreate() (admission.Warnings, error) { - autonomousdatabasebackuplog.Info("validate create", "name", r.Name) +func (r *AutonomousDatabaseBackup) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + backup := obj.(*AutonomousDatabaseBackup) + + autonomousdatabasebackuplog.Info("validate create", "name", backup.Name) var allErrs field.ErrorList @@ -82,61 +79,59 @@ func (r *AutonomousDatabaseBackup) ValidateCreate() (admission.Warnings, error) _, hasEmptyString := namespaces[""] isClusterScoped := len(namespaces) == 1 && hasEmptyString if !isClusterScoped { - _, containsNamespace := namespaces[r.Namespace] + _, containsNamespace := namespaces[backup.Namespace] // Check if the allowed namespaces maps contains the required namespace if len(namespaces) != 0 && !containsNamespace { allErrs = append(allErrs, - field.Invalid(field.NewPath("metadata").Child("namespace"), r.Namespace, + field.Invalid(field.NewPath("metadata").Child("namespace"), backup.Namespace, "Oracle database operator doesn't watch over this namespace")) } } - if r.Spec.Target.K8sAdb.Name == nil && r.Spec.Target.OciAdb.OCID == nil { + if backup.Spec.Target.K8sAdb.Name == nil && backup.Spec.Target.OciAdb.Id == nil { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("target"), "target ADB is empty")) } - if r.Spec.Target.K8sAdb.Name != nil && r.Spec.Target.OciAdb.OCID != nil { - allErrs = append(allErrs, - field.Forbidden(field.NewPath("spec").Child("target"), "specify either k8sADB or ociADB, but not both")) - } - if len(allErrs) == 0 { return nil, nil } return nil, apierrors.NewInvalid( schema.GroupKind{Group: "database.oracle.com", Kind: "AutonomousDatabaseBackup"}, - r.Name, allErrs) + backup.Name, allErrs) } // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *AutonomousDatabaseBackup) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - autonomousdatabasebackuplog.Info("validate update", "name", r.Name) +func (r *AutonomousDatabaseBackup) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { + var ( + allErrs field.ErrorList + oldBackup = oldObj.(*AutonomousDatabaseBackup) + newBackup = oldObj.(*AutonomousDatabaseBackup) + ) - var allErrs field.ErrorList - oldBackup := old.(*AutonomousDatabaseBackup) + autonomousdatabasebackuplog.Info("validate update", "name", newBackup.Name) - if oldBackup.Spec.AutonomousDatabaseBackupOCID != nil && r.Spec.AutonomousDatabaseBackupOCID != nil && - *oldBackup.Spec.AutonomousDatabaseBackupOCID != *r.Spec.AutonomousDatabaseBackupOCID { + if oldBackup.Spec.AutonomousDatabaseBackupOCID != nil && newBackup.Spec.AutonomousDatabaseBackupOCID != nil && + *oldBackup.Spec.AutonomousDatabaseBackupOCID != *newBackup.Spec.AutonomousDatabaseBackupOCID { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("autonomousDatabaseBackupOCID"), "cannot assign a new autonomousDatabaseBackupOCID to this backup")) } - if oldBackup.Spec.Target.K8sAdb.Name != nil && r.Spec.Target.K8sAdb.Name != nil && - *oldBackup.Spec.Target.K8sAdb.Name != *r.Spec.Target.K8sAdb.Name { + if oldBackup.Spec.Target.K8sAdb.Name != nil && newBackup.Spec.Target.K8sAdb.Name != nil && + *oldBackup.Spec.Target.K8sAdb.Name != *newBackup.Spec.Target.K8sAdb.Name { allErrs = append(allErrs, - field.Forbidden(field.NewPath("spec").Child("target").Child("k8sADB").Child("name"), "cannot assign a new name to the target")) + field.Forbidden(field.NewPath("spec").Child("target").Child("k8sAdb").Child("name"), "cannot assign a new name to the target")) } - if oldBackup.Spec.Target.OciAdb.OCID != nil && r.Spec.Target.OciAdb.OCID != nil && - *oldBackup.Spec.Target.OciAdb.OCID != *r.Spec.Target.OciAdb.OCID { + if oldBackup.Spec.Target.OciAdb.Id != nil && newBackup.Spec.Target.OciAdb.Id != nil && + *oldBackup.Spec.Target.OciAdb.Id != *newBackup.Spec.Target.OciAdb.Id { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("target").Child("ociADB").Child("ocid"), "cannot assign a new ocid to the target")) } - if oldBackup.Spec.DisplayName != nil && r.Spec.DisplayName != nil && - *oldBackup.Spec.DisplayName != *r.Spec.DisplayName { + if oldBackup.Spec.DisplayName != nil && newBackup.Spec.DisplayName != nil && + *oldBackup.Spec.DisplayName != *newBackup.Spec.DisplayName { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("displayName"), "cannot assign a new displayName to this backup")) } @@ -146,13 +141,10 @@ func (r *AutonomousDatabaseBackup) ValidateUpdate(old runtime.Object) (admission } return nil, apierrors.NewInvalid( schema.GroupKind{Group: "database.oracle.com", Kind: "AutonomousDatabaseBackup"}, - r.Name, allErrs) + newBackup.Name, allErrs) } // ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *AutonomousDatabaseBackup) ValidateDelete() (admission.Warnings, error) { - autonomousdatabasebackuplog.Info("validate delete", "name", r.Name) - - // TODO(user): fill in your validation logic upon object deletion. +func (r *AutonomousDatabaseBackup) ValidateDelete(context.Context, runtime.Object) (admission.Warnings, error) { return nil, nil } diff --git a/apis/database/v4/autonomousdatabaserestore_types.go b/apis/database/v4/autonomousdatabaserestore_types.go index 3337c983..f88b7f79 100644 --- a/apis/database/v4/autonomousdatabaserestore_types.go +++ b/apis/database/v4/autonomousdatabaserestore_types.go @@ -50,7 +50,7 @@ import ( // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. -type K8sADBBackupSpec struct { +type K8sAdbBackupSpec struct { Name *string `json:"name,omitempty"` } @@ -60,7 +60,7 @@ type PITSpec struct { } type SourceSpec struct { - K8sAdbBackup K8sADBBackupSpec `json:"k8sADBBackup,omitempty"` + K8sAdbBackup K8sAdbBackupSpec `json:"k8sAdbBackup,omitempty"` PointInTime PITSpec `json:"pointInTime,omitempty"` } diff --git a/apis/database/v4/autonomousdatabaserestore_webhook.go b/apis/database/v4/autonomousdatabaserestore_webhook.go index 6e3b4656..0df1288d 100644 --- a/apis/database/v4/autonomousdatabaserestore_webhook.go +++ b/apis/database/v4/autonomousdatabaserestore_webhook.go @@ -39,6 +39,8 @@ package v4 import ( + "context" + dbcommons "github.com/oracle/oracle-database-operator/commons/database" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -56,59 +58,64 @@ var autonomousdatabaserestorelog = logf.Log.WithName("autonomousdatabaserestore- func (r *AutonomousDatabaseRestore) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). For(r). + WithValidator(r). + WithValidator(r). Complete() } //+kubebuilder:webhook:verbs=create;update,path=/validate-database-oracle-com-v4-autonomousdatabaserestore,mutating=false,failurePolicy=fail,sideEffects=None,groups=database.oracle.com,resources=autonomousdatabaserestores,versions=v4,name=vautonomousdatabaserestorev4.kb.io,admissionReviewVersions=v1 -var _ webhook.Validator = &AutonomousDatabaseRestore{} +var _ webhook.CustomValidator = &AutonomousDatabaseRestore{} // ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (r *AutonomousDatabaseRestore) ValidateCreate() (admission.Warnings, error) { - autonomousdatabaserestorelog.Info("validate create", "name", r.Name) +func (r *AutonomousDatabaseRestore) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + var ( + allErrs field.ErrorList + restore = obj.(*AutonomousDatabaseRestore) + ) - var allErrs field.ErrorList + autonomousdatabaserestorelog.Info("validate create", "name", restore.Name) namespaces := dbcommons.GetWatchNamespaces() _, hasEmptyString := namespaces[""] isClusterScoped := len(namespaces) == 1 && hasEmptyString if !isClusterScoped { - _, containsNamespace := namespaces[r.Namespace] + _, containsNamespace := namespaces[restore.Namespace] // Check if the allowed namespaces maps contains the required namespace if len(namespaces) != 0 && !containsNamespace { allErrs = append(allErrs, - field.Invalid(field.NewPath("metadata").Child("namespace"), r.Namespace, + field.Invalid(field.NewPath("metadata").Child("namespace"), restore.Namespace, "Oracle database operator doesn't watch over this namespace")) } } // Validate the target ADB - if r.Spec.Target.K8sAdb.Name == nil && r.Spec.Target.OciAdb.OCID == nil { + if restore.Spec.Target.K8sAdb.Name == nil && restore.Spec.Target.OciAdb.Id == nil { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("target"), "target ADB is empty")) } - if r.Spec.Target.K8sAdb.Name != nil && r.Spec.Target.OciAdb.OCID != nil { + if restore.Spec.Target.K8sAdb.Name != nil && restore.Spec.Target.OciAdb.Id != nil { allErrs = append(allErrs, - field.Forbidden(field.NewPath("spec").Child("target"), "specify either k8sADB.name or ociADB.ocid, but not both")) + field.Forbidden(field.NewPath("spec").Child("target"), "specify either k8sAdb.name or ociAdb.id, but not both")) } // Validate the restore source - if r.Spec.Source.K8sAdbBackup.Name == nil && - r.Spec.Source.PointInTime.Timestamp == nil { + if restore.Spec.Source.K8sAdbBackup.Name == nil && + restore.Spec.Source.PointInTime.Timestamp == nil { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("source"), "retore source is empty")) } - if r.Spec.Source.K8sAdbBackup.Name != nil && - r.Spec.Source.PointInTime.Timestamp != nil { + if restore.Spec.Source.K8sAdbBackup.Name != nil && + restore.Spec.Source.PointInTime.Timestamp != nil { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("source"), "cannot apply backupName and the PITR parameters at the same time")) } // Verify the timestamp format if it's PITR - if r.Spec.Source.PointInTime.Timestamp != nil { - _, err := ParseDisplayTime(*r.Spec.Source.PointInTime.Timestamp) + if restore.Spec.Source.PointInTime.Timestamp != nil { + _, err := ParseDisplayTime(*restore.Spec.Source.PointInTime.Timestamp) if err != nil { allErrs = append(allErrs, field.Forbidden(field.NewPath("spec").Child("source").Child("pointInTime").Child("timestamp"), "invalid timestamp format")) @@ -120,27 +127,15 @@ func (r *AutonomousDatabaseRestore) ValidateCreate() (admission.Warnings, error) } return nil, apierrors.NewInvalid( schema.GroupKind{Group: "database.oracle.com", Kind: "AutonomousDatabaseRestore"}, - r.Name, allErrs) + restore.Name, allErrs) } // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *AutonomousDatabaseRestore) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - autonomousdatabaserestorelog.Info("validate update", "name", r.Name) - - var allErrs field.ErrorList - - if len(allErrs) == 0 { - return nil, nil - } - return nil, apierrors.NewInvalid( - schema.GroupKind{Group: "database.oracle.com", Kind: "AutonomousDatabaseRestore"}, - r.Name, allErrs) +func (r *AutonomousDatabaseRestore) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { + return nil, nil } // ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *AutonomousDatabaseRestore) ValidateDelete() (admission.Warnings, error) { - autonomousdatabaserestorelog.Info("validate delete", "name", r.Name) - - // TODO(user): fill in your validation logic upon object deletion. +func (r *AutonomousDatabaseRestore) ValidateDelete(context.Context, runtime.Object) (admission.Warnings, error) { return nil, nil } diff --git a/apis/database/v4/cdb_types.go b/apis/database/v4/cdb_types.go deleted file mode 100644 index ce3f6f28..00000000 --- a/apis/database/v4/cdb_types.go +++ /dev/null @@ -1,191 +0,0 @@ -/* -** Copyright (c) 2022 Oracle and/or its affiliates. -** -** The Universal Permissive License (UPL), Version 1.0 -** -** Subject to the condition set forth below, permission is hereby granted to any -** person obtaining a copy of this software, associated documentation and/or data -** (collectively the "Software"), free of charge and under any and all copyright -** rights in the Software, and any and all patent rights owned or freely -** licensable by each licensor hereunder covering either (i) the unmodified -** Software as contributed to or provided by such licensor, or (ii) the Larger -** Works (as defined below), to deal in both -** -** (a) the Software, and -** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if -** one is included with the Software (each a "Larger Work" to which the Software -** is contributed by such licensors), -** -** without restriction, including without limitation the rights to copy, create -** derivative works of, display, perform, and distribute the Software and make, -** use, sell, offer for sale, import, export, have made, and have sold the -** Software and the Larger Work(s), and to sublicense the foregoing rights on -** either these or other terms. -** -** This license is subject to the following condition: -** The above copyright notice and either this complete permission notice or at -** a minimum a reference to the UPL must be included in all copies or -** substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -** SOFTWARE. - */ - -package v4 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// CDBSpec defines the desired state of CDB -type CDBSpec struct { - // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster - // Important: Run "make" to regenerate code after modifying this file - - // Name of the CDB - CDBName string `json:"cdbName,omitempty"` - // Name of the CDB Service - ServiceName string `json:"serviceName,omitempty"` - - // Password for the CDB System Administrator - SysAdminPwd CDBSysAdminPassword `json:"sysAdminPwd,omitempty"` - // User in the root container with sysdba priviledges to manage PDB lifecycle - CDBAdminUser CDBAdminUser `json:"cdbAdminUser,omitempty"` - // Password for the CDB Administrator to manage PDB lifecycle - CDBAdminPwd CDBAdminPassword `json:"cdbAdminPwd,omitempty"` - - CDBTlsKey CDBTLSKEY `json:"cdbTlsKey,omitempty"` - CDBTlsCrt CDBTLSCRT `json:"cdbTlsCrt,omitempty"` - - // Password for user ORDS_PUBLIC_USER - ORDSPwd ORDSPassword `json:"ordsPwd,omitempty"` - // ORDS server port. For now, keep it as 8888. TO BE USED IN FUTURE RELEASE. - ORDSPort int `json:"ordsPort,omitempty"` - // ORDS Image Name - ORDSImage string `json:"ordsImage,omitempty"` - // The name of the image pull secret in case of a private docker repository. - ORDSImagePullSecret string `json:"ordsImagePullSecret,omitempty"` - // ORDS Image Pull Policy - // +kubebuilder:validation:Enum=Always;Never - ORDSImagePullPolicy string `json:"ordsImagePullPolicy,omitempty"` - // Number of ORDS Containers to create - Replicas int `json:"replicas,omitempty"` - // Web Server User with SQL Administrator role to allow us to authenticate to the PDB Lifecycle Management REST endpoints - WebServerUser WebServerUser `json:"webServerUser,omitempty"` - // Password for the Web Server User - WebServerPwd WebServerPassword `json:"webServerPwd,omitempty"` - // Name of the DB server - DBServer string `json:"dbServer,omitempty"` - // DB server port - DBPort int `json:"dbPort,omitempty"` - // Node Selector for running the Pod - NodeSelector map[string]string `json:"nodeSelector,omitempty"` - DeletePDBCascade bool `json:"deletePdbCascade,omitempty"` - DBTnsurl string `json:"dbTnsurl,omitempty"` - CDBPubKey CDBPUBKEY `json:"cdbOrdsPubKey,omitempty"` - CDBPriKey CDBPRIVKEY `json:"cdbOrdsPrvKey,omitempty"` -} - -// CDBSecret defines the secretName -type CDBSecret struct { - SecretName string `json:"secretName"` - Key string `json:"key"` -} - -// CDBSysAdminPassword defines the secret containing SysAdmin Password mapped to key 'sysAdminPwd' for CDB -type CDBSysAdminPassword struct { - Secret CDBSecret `json:"secret"` -} - -// CDBAdminUser defines the secret containing CDB Administrator User mapped to key 'cdbAdminUser' to manage PDB lifecycle -type CDBAdminUser struct { - Secret CDBSecret `json:"secret"` -} - -// CDBAdminPassword defines the secret containing CDB Administrator Password mapped to key 'cdbAdminPwd' to manage PDB lifecycle -type CDBAdminPassword struct { - Secret CDBSecret `json:"secret"` -} - -// ORDSPassword defines the secret containing ORDS_PUBLIC_USER Password mapped to key 'ordsPwd' -type ORDSPassword struct { - Secret CDBSecret `json:"secret"` -} - -// WebServerUser defines the secret containing Web Server User mapped to key 'webServerUser' to manage PDB lifecycle -type WebServerUser struct { - Secret CDBSecret `json:"secret"` -} - -// WebServerPassword defines the secret containing password for Web Server User mapped to key 'webServerPwd' to manage PDB lifecycle -type WebServerPassword struct { - Secret CDBSecret `json:"secret"` -} - -type CDBTLSKEY struct { - Secret CDBSecret `json:"secret"` -} - -type CDBTLSCRT struct { - Secret CDBSecret `json:"secret"` -} - -type CDBPUBKEY struct { - Secret CDBSecret `json:"secret"` -} - -type CDBPRIVKEY struct { - Secret CDBSecret `json:"secret"` -} - -// CDBStatus defines the observed state of CDB -type CDBStatus struct { - // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - // Important: Run "make" to regenerate code after modifying this file - - // Phase of the CDB Resource - Phase string `json:"phase"` - // CDB Resource Status - Status bool `json:"status"` - // Message - Msg string `json:"msg,omitempty"` -} - -// +kubebuilder:object:root=true -// +kubebuilder:subresource:status -// +kubebuilder:printcolumn:JSONPath=".spec.cdbName",name="CDB Name",type="string",description="Name of the CDB" -// +kubebuilder:printcolumn:JSONPath=".spec.dbServer",name="DB Server",type="string",description=" Name of the DB Server" -// +kubebuilder:printcolumn:JSONPath=".spec.dbPort",name="DB Port",type="integer",description="DB server port" -// +kubebuilder:printcolumn:JSONPath=".spec.replicas",name="Replicas",type="integer",description="Replicas" -// +kubebuilder:printcolumn:JSONPath=".status.phase",name="Status",type="string",description="Status of the CDB Resource" -// +kubebuilder:printcolumn:JSONPath=".status.msg",name="Message",type="string",description="Error message, if any" -// +kubebuilder:printcolumn:JSONPath=".spec.dbTnsurl",name="TNS STRING",type="string",description=" string of the tnsalias" -// +kubebuilder:resource:path=cdbs,scope=Namespaced -// +kubebuilder:storageversion - -// CDB is the Schema for the cdbs API -type CDB struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec CDBSpec `json:"spec,omitempty"` - Status CDBStatus `json:"status,omitempty"` -} - -//+kubebuilder:object:root=true - -// CDBList contains a list of CDB -type CDBList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []CDB `json:"items"` -} - -func init() { - SchemeBuilder.Register(&CDB{}, &CDBList{}) -} diff --git a/apis/database/v4/cdb_webhook.go b/apis/database/v4/cdb_webhook.go deleted file mode 100644 index 235b2627..00000000 --- a/apis/database/v4/cdb_webhook.go +++ /dev/null @@ -1,224 +0,0 @@ -/* -** Copyright (c) 2022 Oracle and/or its affiliates. -** -** The Universal Permissive License (UPL), Version 1.0 -** -** Subject to the condition set forth below, permission is hereby granted to any -** person obtaining a copy of this software, associated documentation and/or data -** (collectively the "Software"), free of charge and under any and all copyright -** rights in the Software, and any and all patent rights owned or freely -** licensable by each licensor hereunder covering either (i) the unmodified -** Software as contributed to or provided by such licensor, or (ii) the Larger -** Works (as defined below), to deal in both -** -** (a) the Software, and -** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if -** one is included with the Software (each a "Larger Work" to which the Software -** is contributed by such licensors), -** -** without restriction, including without limitation the rights to copy, create -** derivative works of, display, perform, and distribute the Software and make, -** use, sell, offer for sale, import, export, have made, and have sold the -** Software and the Larger Work(s), and to sublicense the foregoing rights on -** either these or other terms. -** -** This license is subject to the following condition: -** The above copyright notice and either this complete permission notice or at -** a minimum a reference to the UPL must be included in all copies or -** substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -** SOFTWARE. - */ - -package v4 - -import ( - "reflect" - "strings" - - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/validation/field" - ctrl "sigs.k8s.io/controller-runtime" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/webhook" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" -) - -// log is for logging in this package. -var cdblog = logf.Log.WithName("cdb-webhook") - -func (r *CDB) SetupWebhookWithManager(mgr ctrl.Manager) error { - return ctrl.NewWebhookManagedBy(mgr). - For(r). - Complete() -} - -//+kubebuilder:webhook:path=/mutate-database-oracle-com-v4-cdb,mutating=true,failurePolicy=fail,sideEffects=None,groups=database.oracle.com,resources=cdbs,verbs=create;update,versions=v4,name=mcdb.kb.io,admissionReviewVersions={v1,v1beta1} - -var _ webhook.Defaulter = &CDB{} - -// Default implements webhook.Defaulter so a webhook will be registered for the type -func (r *CDB) Default() { - cdblog.Info("Setting default values in CDB spec for : " + r.Name) - - if r.Spec.ORDSPort == 0 { - r.Spec.ORDSPort = 8888 - } - - if r.Spec.Replicas == 0 { - r.Spec.Replicas = 1 - } -} - -// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. -//+kubebuilder:webhook:path=/validate-database-oracle-com-v4-cdb,mutating=false,failurePolicy=fail,sideEffects=None,groups=database.oracle.com,resources=cdbs,verbs=create;update,versions=v4,name=vcdb.kb.io,admissionReviewVersions={v1,v1beta1} - -var _ webhook.Validator = &CDB{} - -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (r *CDB) ValidateCreate() (admission.Warnings, error) { - cdblog.Info("ValidateCreate", "name", r.Name) - - var allErrs field.ErrorList - - if r.Spec.ServiceName == "" && r.Spec.DBServer != "" { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("serviceName"), "Please specify CDB Service name")) - } - - if reflect.ValueOf(r.Spec.CDBTlsKey).IsZero() { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("cdbTlsKey"), "Please specify CDB Tls key(secret)")) - } - - if reflect.ValueOf(r.Spec.CDBTlsCrt).IsZero() { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("cdbTlsCrt"), "Please specify CDB Tls Certificate(secret)")) - } - - if reflect.ValueOf(r.Spec.CDBPriKey).IsZero() { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("CDBPriKey"), "Please specify CDB CDBPriKey(secret)")) - } - - /*if r.Spec.SCANName == "" { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("scanName"), "Please specify SCAN Name for CDB")) - }*/ - - if (r.Spec.DBServer == "" && r.Spec.DBTnsurl == "") || (r.Spec.DBServer != "" && r.Spec.DBTnsurl != "") { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("dbServer"), "Please specify Database Server Name/IP Address or tnsalias string")) - } - - if r.Spec.DBTnsurl != "" && (r.Spec.DBServer != "" || r.Spec.DBPort != 0 || r.Spec.ServiceName != "") { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("dbServer"), "DBtnsurl is orthogonal to (DBServer,DBport,Services)")) - } - - if r.Spec.DBPort == 0 && r.Spec.DBServer != "" { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("dbPort"), "Please specify DB Server Port")) - } - if r.Spec.DBPort < 0 && r.Spec.DBServer != "" { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("dbPort"), "Please specify a valid DB Server Port")) - } - if r.Spec.ORDSPort < 0 { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("ordsPort"), "Please specify a valid ORDS Port")) - } - if r.Spec.Replicas < 0 { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("replicas"), "Please specify a valid value for Replicas")) - } - if r.Spec.ORDSImage == "" { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("ordsImage"), "Please specify name of ORDS Image to be used")) - } - if reflect.ValueOf(r.Spec.CDBAdminUser).IsZero() { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("cdbAdminUser"), "Please specify user in the root container with sysdba priviledges to manage PDB lifecycle")) - } - if reflect.ValueOf(r.Spec.CDBAdminPwd).IsZero() { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("cdbAdminPwd"), "Please specify password for the CDB Administrator to manage PDB lifecycle")) - } - if reflect.ValueOf(r.Spec.ORDSPwd).IsZero() { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("ordsPwd"), "Please specify password for user ORDS_PUBLIC_USER")) - } - if reflect.ValueOf(r.Spec.WebServerUser).IsZero() { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("webServerUser"), "Please specify the Web Server User having SQL Administrator role")) - } - if reflect.ValueOf(r.Spec.WebServerPwd).IsZero() { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("webServerPwd"), "Please specify password for the Web Server User having SQL Administrator role")) - } - if len(allErrs) == 0 { - return nil, nil - } - return nil, apierrors.NewInvalid( - schema.GroupKind{Group: "database.oracle.com", Kind: "CDB"}, - r.Name, allErrs) -} - -// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *CDB) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - cdblog.Info("validate update", "name", r.Name) - - isCDBMarkedToBeDeleted := r.GetDeletionTimestamp() != nil - if isCDBMarkedToBeDeleted { - return nil, nil - } - - var allErrs field.ErrorList - - // Check for updation errors - oldCDB, ok := old.(*CDB) - if !ok { - return nil, nil - } - - if r.Spec.DBPort < 0 { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("dbPort"), "Please specify a valid DB Server Port")) - } - if r.Spec.ORDSPort < 0 { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("ordsPort"), "Please specify a valid ORDS Port")) - } - if r.Spec.Replicas < 0 { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("replicas"), "Please specify a valid value for Replicas")) - } - if !strings.EqualFold(oldCDB.Spec.ServiceName, r.Spec.ServiceName) { - allErrs = append(allErrs, - field.Forbidden(field.NewPath("spec").Child("replicas"), "cannot be changed")) - } - - if len(allErrs) == 0 { - return nil, nil - } - - return nil, apierrors.NewInvalid( - schema.GroupKind{Group: "database.oracle.com", Kind: "CDB"}, - r.Name, allErrs) -} - -// ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *CDB) ValidateDelete() (admission.Warnings, error) { - cdblog.Info("validate delete", "name", r.Name) - - // TODO(user): fill in your validation logic upon object deletion. - return nil, nil -} diff --git a/apis/database/v4/dataguardbroker_types.go b/apis/database/v4/dataguardbroker_types.go index cec11ca4..80c2afe6 100644 --- a/apis/database/v4/dataguardbroker_types.go +++ b/apis/database/v4/dataguardbroker_types.go @@ -80,6 +80,7 @@ type DataguardBrokerStatus struct { } // +kubebuilder:object:root=true +// +kubebuilder:resource:shortName=dgbroker;dgbrokers // +kubebuilder:subresource:status // +kubebuilder:printcolumn:JSONPath=".status.primaryDatabase",name="Primary",type="string" // +kubebuilder:printcolumn:JSONPath=".status.standbyDatabases",name="Standbys",type="string" diff --git a/apis/database/v4/dbcssystem_types.go b/apis/database/v4/dbcssystem_types.go index 9810a3b7..7f163c81 100644 --- a/apis/database/v4/dbcssystem_types.go +++ b/apis/database/v4/dbcssystem_types.go @@ -53,31 +53,36 @@ import ( // DbcsSystemSpec defines the desired state of DbcsSystem type DbcsSystemSpec struct { - DbSystem DbSystemDetails `json:"dbSystem,omitempty"` - Id *string `json:"id,omitempty"` - OCIConfigMap *string `json:"ociConfigMap"` - OCISecret *string `json:"ociSecret,omitempty"` - DbClone *DbCloneConfig `json:"dbClone,omitempty"` - HardLink bool `json:"hardLink,omitempty"` - PdbConfigs []PDBConfig `json:"pdbConfigs,omitempty"` - SetupDBCloning bool `json:"setupDBCloning,omitempty"` - DbBackupId *string `json:"dbBackupId,omitempty"` - DatabaseId *string `json:"databaseId,omitempty"` - KMSConfig KMSConfig `json:"kmsConfig,omitempty"` + DbSystem *DbSystemDetails `json:"dbSystem,omitempty"` + Id *string `json:"id,omitempty"` + OCIConfigMap *string `json:"ociConfigMap"` + OCISecret *string `json:"ociSecret,omitempty"` + DbClone *DbCloneConfig `json:"dbClone,omitempty"` + HardLink bool `json:"hardLink,omitempty"` + PdbConfigs []PDBConfig `json:"pdbConfigs,omitempty"` + SetupDBCloning bool `json:"setupDBCloning,omitempty"` + DbBackupId *string `json:"dbBackupId,omitempty"` + DatabaseId *string `json:"databaseId,omitempty"` + KMSConfig *KMSConfig `json:"kmsConfig,omitempty"` + EnableBackup bool `json:"enableBackup,omitempty"` + IsPatch bool `json:"isPatch,omitempty"` + IsUpgrade bool `json:"isUpgrade,omitempty"` + DataGuard DataGuardConfig `json:"dataGuard,omitempty"` } // DbSystemDetails Spec type DbSystemDetails struct { - CompartmentId string `json:"compartmentId"` - AvailabilityDomain string `json:"availabilityDomain"` - SubnetId string `json:"subnetId"` - Shape string `json:"shape"` + CompartmentId string `json:"compartmentId,omitempty"` + AvailabilityDomain string `json:"availabilityDomain,omitempty"` + SubnetId string `json:"subnetId,omitempty"` + Shape string `json:"shape,omitempty"` SshPublicKeys []string `json:"sshPublicKeys,omitempty"` - HostName string `json:"hostName"` + HostName string `json:"hostName,omitempty"` CpuCoreCount int `json:"cpuCoreCount,omitempty"` FaultDomains []string `json:"faultDomains,omitempty"` DisplayName string `json:"displayName,omitempty"` + BackupDisplayName string `json:"backupDisplayName,omitempty"` BackupSubnetId string `json:"backupSubnetId,omitempty"` TimeZone string `json:"timeZone,omitempty"` NodeCount *int `json:"nodeCount,omitempty"` @@ -85,8 +90,9 @@ type DbSystemDetails struct { Domain string `json:"domain,omitempty"` InitialDataStorageSizeInGB int `json:"initialDataStorageSizeInGB,omitempty"` ClusterName string `json:"clusterName,omitempty"` - DbAdminPasswordSecret string `json:"dbAdminPasswordSecret"` + DbAdminPasswordSecret string `json:"dbAdminPasswordSecret,omitempty"` DbName string `json:"dbName,omitempty"` + DbHomeId string `json:"dbHomeId,omitempty"` PdbName string `json:"pdbName,omitempty"` DbDomain string `json:"dbDomain,omitempty"` DbUniqueName string `json:"dbUniqueName,omitempty"` @@ -98,8 +104,31 @@ type DbSystemDetails struct { LicenseModel string `json:"licenseModel,omitempty"` TdeWalletPasswordSecret string `json:"tdeWalletPasswordSecret,omitempty"` Tags map[string]string `json:"tags,omitempty"` - DbBackupConfig Backupconfig `json:"dbBackupConfig,omitempty"` - KMSConfig KMSConfig `json:"kmsConfig,omitempty"` + DbBackupConfig *Backupconfig `json:"dbBackupConfig,omitempty"` + KMSConfig *KMSConfig `json:"kmsConfig,omitempty"` + RestoreConfig *RestoreConfig `json:"restoreConfig,omitempty"` + PatchOCID string `json:"dbPatchOcid,omitempty"` + UpgradeVersion string `json:"dbUpgradeVersion,omitempty"` +} + +type DataGuardConfig struct { + Enabled bool `json:"enabled,omitempty"` + ProtectionMode *string `json:"protectionMode,omitempty"` // Options: "MAXIMUM_PROTECTION", "MAXIMUM_AVAILABILITY", "MAXIMUM_PERFORMANCE" + TransportType *string `json:"transportType,omitempty"` // Options: "ASYNC", "SYNC" + PeerRole *string `json:"peerRole,omitempty"` // Options: "STANDBY", "PRIMARY" + DbAdminPasswordSecret *string `json:"dbAdminPasswordSecret,omitempty"` + DbName *string `json:"dbName,omitempty"` + HostName *string `json:"hostName,omitempty"` + DisplayName *string `json:"displayName,omitempty"` + PeerSidPrefix *string `json:"sidPrefix,omitempty"` + PeerDbSystemId *string `json:"peerDbSystemId,omitempty"` + PeerDbHomeId *string `json:"peerDbHomeId,omitempty"` + PrimaryDatabaseId *string `json:"primaryDatabaseId,omitempty"` + AvailabilityDomain *string `json:"availabilityDomain,omitempty"` // Availability domain for the new DB system + SubnetId *string `json:"subnetId,omitempty"` // Subnet ID for the new DB system + Shape *string `json:"shape,omitempty"` // Shape of the new DB system + DbSystemFreeformTags map[string]string `json:"dbSystemFreeformTags,omitempty"` + IsDelete bool `json:"isDelete,omitempty"` } // DB Backup Config Network Struct @@ -110,6 +139,19 @@ type Backupconfig struct { BackupDestinationDetails *string `json:"backupDestinationDetails,omitempty"` } +// Manual backup information +type BackupInfo struct { + Name string `json:"name"` + BackupID string `json:"backupId"` + Timestamp string `json:"timestamp"` // Optional: for sorting, audit, GC +} + +type RestoreConfig struct { + Timestamp *metav1.Time `json:"timestamp,omitempty"` // Restore to specific point in time + SCN *string `json:"scn,omitempty"` // Restore to specific SCN (as string) + Latest bool `json:"latest,omitempty"` // Restore to latest state +} + // DbcsSystemStatus defines the observed state of DbcsSystem type DbcsSystemStatus struct { Id *string `json:"id,omitempty"` @@ -119,6 +161,7 @@ type DbcsSystemStatus struct { StorageManagement string `json:"storageManagement,omitempty"` NodeCount int `json:"nodeCount,omitempty"` CpuCoreCount int `json:"cpuCoreCount,omitempty"` + DbVersion string `json:"dbVersion,omitempty"` DbEdition string `json:"dbEdition,omitempty"` TimeZone string `json:"timeZone,omitempty"` @@ -135,20 +178,25 @@ type DbcsSystemStatus struct { KMSDetailsStatus KMSDetailsStatus `json:"kmsDetailsStatus,omitempty"` DbCloneStatus DbCloneStatus `json:"dbCloneStatus,omitempty"` PdbDetailsStatus []PDBDetailsStatus `json:"pdbDetailsStatus,omitempty"` + DataGuardStatus *DataGuardStatus `json:"dataGuardStatus,omitempty"` + Backups []BackupInfo `json:"backups,omitempty"` + Message string `json:"message,omitempty"` } // DbcsSystemStatus defines the observed state of DbcsSystem type DbStatus struct { - Id *string `json:"id,omitempty"` - DbName string `json:"dbName,omitempty"` - DbUniqueName string `json:"dbUniqueName,omitempty"` - DbWorkload string `json:"dbWorkload,omitempty"` - DbHomeId string `json:"dbHomeId,omitempty"` + Id *string `json:"id,omitempty"` + DbName string `json:"dbName,omitempty"` + DbUniqueName string `json:"dbUniqueName,omitempty"` + DbWorkload string `json:"dbWorkload,omitempty"` + DbHomeId string `json:"dbHomeId,omitempty"` + ConnectionString string `json:"connectionString,omitempty"` + ConnectionStringLong string `json:"connectionStringLong,omitempty"` } type DbWorkrequests struct { - OperationType *string `json:"operationType,omitmpty"` - OperationId *string `json:"operationId,omitemty"` + OperationType *string `json:"operationType,omitempty"` + OperationId *string `json:"operationId,omitempty"` PercentComplete string `json:"percentComplete,omitempty"` TimeAccepted string `json:"timeAccepted,omitempty"` TimeStarted string `json:"timeStarted,omitempty"` @@ -171,7 +219,7 @@ type DbCloneConfig struct { TdeWalletPasswordSecret string `json:"tdeWalletPasswordSecret,omitempty"` DbName string `json:"dbName"` HostName string `json:"hostName"` - DbUniqueName string `json:"dbDbUniqueName"` + DbUniqueName string `json:"dbUniqueName,omitempty"` DisplayName string `json:"displayName"` LicenseModel string `json:"licenseModel,omitempty"` Domain string `json:"domain,omitempty"` @@ -184,27 +232,49 @@ type DbCloneConfig struct { PrivateIp string `json:"privateIp,omitempty"` } +type DataGuardStatus struct { + Id *string `json:"id,omitempty"` + IsActiveDataGuardEnabled bool `json:"isActiveDataGuardEnabled,omitempty"` + PeerDbSystemId *string `json:"peerDbSystemId,omitempty"` + PeerDatabaseId *string `json:"peerDatabaseId,omitempty"` + DbName *string `json:"dbName,omitempty"` + DbWorkload string `json:"dbWorkload,omitempty"` + PeerDbHomeId *string `json:"peerDbHomeId,omitempty"` + PeerRole *string `json:"peerRole,omitempty"` // Options: "STANDBY", "PRIMARY" + Shape *string `json:"shape,omitempty"` + SubnetId *string `json:"subnetId,omitempty"` + PrimaryDatabaseId *string `json:"primaryDatabaseId,omitempty"` + DbAdminPasswordSecret *string `json:"dbAdminPasswordSecret,omitempty"` + TransportType *string `json:"transportType,omitempty"` + ProtectionMode *string `json:"protectionMode,omitempty"` // Options: "MAXIMUM_PROTECTION", "MAXIMUM_AVAILABILITY", "MAXIMUM_PERFORMANCE" + LifecycleState *string `json:"lifecycleState,omitempty"` + PeerDataGuardAssociationId *string `json:"peerDataGuardAssociationId,omitempty"` + LifecycleDetails *string `json:"lifecycleDetails,omitempty"` +} + // DbCloneStatus defines the observed state of DbClone type DbCloneStatus struct { - Id *string `json:"id,omitempty"` - DbAdminPaswordSecret string `json:"dbAdminPaswordSecret,omitempty"` - DbName string `json:"dbName,omitempty"` - HostName string `json:"hostName"` - DbUniqueName string `json:"dbDbUniqueName"` - DisplayName string `json:"displayName,omitempty"` - LicenseModel string `json:"licenseModel,omitempty"` - Domain string `json:"domain,omitempty"` - SshPublicKeys []string `json:"sshPublicKeys,omitempty"` - SubnetId string `json:"subnetId,omitempty"` + Id *string `json:"id,omitempty"` + DbAdminPasswordSecret string `json:"dbAdminPasswordSecret,omitempty"` + DbName string `json:"dbName,omitempty"` + HostName string `json:"hostName,omitempty"` + DbUniqueName string `json:"dbUniqueName,omitempty"` + DisplayName string `json:"displayName,omitempty"` + LicenseModel string `json:"licenseModel,omitempty"` + Domain string `json:"domain,omitempty"` + SshPublicKeys []string `json:"sshPublicKeys,omitempty"` + SubnetId string `json:"subnetId,omitempty"` } // +kubebuilder:object:root=true // +kubebuilder:subresource:status // +kubebuilder:resource:path=dbcssystems,scope=Namespaced // +kubebuilder:storageversion -// +kubebuilder:storageversion - -// DbcsSystem is the Schema for the dbcssystems API +// +kubebuilder:printcolumn:name="Display Name",type="string",JSONPath=".status.displayName" +// +kubebuilder:printcolumn:name="DB Name",type="string",JSONPath=".status.dbInfo[0].dbName" +// +kubebuilder:printcolumn:name="State",type="string",JSONPath=".status.state" +// +kubebuilder:printcolumn:name="DB Version",type="string",JSONPath=".status.dbInfo[0].dbVersion" +// +kubebuilder:printcolumn:name="ConnString",type="string",JSONPath=".status.dbInfo[0].connectionString" type DbcsSystem struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -224,11 +294,13 @@ type DbcsSystemList struct { type LifecycleState string const ( - Available LifecycleState = "AVAILABLE" - Failed LifecycleState = "FAILED" - Update LifecycleState = "UPDATING" - Provision LifecycleState = "PROVISIONING" - Terminate LifecycleState = "TERMINATED" + Available LifecycleState = "AVAILABLE" + Failed LifecycleState = "FAILED" + Update LifecycleState = "UPDATING" + Provision LifecycleState = "PROVISIONING" + Terminate LifecycleState = "TERMINATING" + TerminatedState LifecycleState = "TERMINATED" + Upgrade LifecycleState = "UPGRADING" ) const lastSuccessfulSpec = "lastSuccessfulSpec" diff --git a/apis/database/v4/dbcssystem_webhook.go b/apis/database/v4/dbcssystem_webhook.go index c3ff8ddb..1fc85f80 100644 --- a/apis/database/v4/dbcssystem_webhook.go +++ b/apis/database/v4/dbcssystem_webhook.go @@ -1,98 +1,110 @@ -/* -** Copyright (c) 2022 Oracle and/or its affiliates. -** -** The Universal Permissive License (UPL), Version 1.0 -** -** Subject to the condition set forth below, permission is hereby granted to any -** person obtaining a copy of this software, associated documentation and/or data -** (collectively the "Software"), free of charge and under any and all copyright -** rights in the Software, and any and all patent rights owned or freely -** licensable by each licensor hereunder covering either (i) the unmodified -** Software as contributed to or provided by such licensor, or (ii) the Larger -** Works (as defined below), to deal in both -** -** (a) the Software, and -** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if -** one is included with the Software (each a "Larger Work" to which the Software -** is contributed by such licensors), -** -** without restriction, including without limitation the rights to copy, create -** derivative works of, display, perform, and distribute the Software and make, -** use, sell, offer for sale, import, export, have made, and have sold the -** Software and the Larger Work(s), and to sublicense the foregoing rights on -** either these or other terms. -** -** This license is subject to the following condition: -** The above copyright notice and either this complete permission notice or at -** a minimum a reference to the UPL must be included in all copies or -** substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -** SOFTWARE. - */ - package v4 import ( + "context" + "fmt" + "reflect" + + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" ctrl "sigs.k8s.io/controller-runtime" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/webhook" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + admission "sigs.k8s.io/controller-runtime/pkg/webhook/admission" ) // log is for logging in this package. var dbcssystemlog = logf.Log.WithName("dbcssystem-resource") +// SetupWebhookWithManager registers the webhook with the manager. func (r *DbcsSystem) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). - For(r). + For(&DbcsSystem{}). + WithDefaulter(r). + WithValidator(r). Complete() } -// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// Ensure our CRD type implements the webhook interfaces +var _ webhook.CustomValidator = &DbcsSystem{} +var _ webhook.CustomDefaulter = &DbcsSystem{} -//+kubebuilder:webhook:path=/mutate-database-oracle-com-v4-dbcssystem,mutating=true,failurePolicy=fail,sideEffects=none,groups=database.oracle.com,resources=dbcssystems,verbs=create;update,versions=v4,name=mdbcssystemv4.kb.io,admissionReviewVersions=v1 +// +kubebuilder:webhook:path=/mutate-database-oracle-com-v4-dbcssystem,mutating=true,failurePolicy=fail,sideEffects=none,groups=database.oracle.com,resources=dbcssystems,verbs=create;update,versions=v4,name=mdbcssystemv4.kb.io,admissionReviewVersions=v1 -var _ webhook.Defaulter = &DbcsSystem{} +// Default implements webhook.CustomDefaulter +func (r *DbcsSystem) Default(ctx context.Context, obj runtime.Object) error { + cr, ok := obj.(*DbcsSystem) + if !ok { + return fmt.Errorf("expected *DbcsSystem but got %T", obj) + } -// Default implements webhook.Defaulter so a webhook will be registered for the type -func (r *DbcsSystem) Default() { - dbcssystemlog.Info("default", "name", r.Name) + dbcssystemlog.Info("default", "name", cr.Name) - // TODO(user): fill in your defaulting logic. + // TODO: add your defaulting logic here + return nil } -// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. - // +kubebuilder:webhook:verbs=create;update;delete,path=/validate-database-oracle-com-v4-dbcssystem,mutating=false,failurePolicy=fail,sideEffects=None,groups=database.oracle.com,resources=dbcssystems,versions=v4,name=vdbcssystemv4.kb.io,admissionReviewVersions=v1 -var _ webhook.Validator = &DbcsSystem{} -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (r *DbcsSystem) ValidateCreate() (admission.Warnings, error) { - dbcssystemlog.Info("validate create", "name", r.Name) +// ValidateCreate implements webhook.CustomValidator +func (r *DbcsSystem) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + dbcssystemlog.Info("validate create") + + cr, ok := obj.(*DbcsSystem) + if !ok { + return nil, apierrors.NewInternalError(fmt.Errorf("expected *DbcsSystem but got %T", obj)) + } + + blockedStates := map[string]bool{ + "PROVISIONING": true, + "UPDATING": true, + "TERMINATING": true, + } + + if blockedStates[string(cr.Status.State)] { + return nil, apierrors.NewForbidden( + schema.GroupResource{Group: "database.oracle.com", Resource: "DbcsSystem"}, + cr.Name, + fmt.Errorf("creation not allowed while resource is in state %q", cr.Status.State), + ) + } - // // TODO(user): fill in your validation logic upon object creation. return nil, nil } -// // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *DbcsSystem) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - dbcssystemlog.Info("validate update", "name", r.Name) +// ValidateUpdate implements webhook.CustomValidator +func (r *DbcsSystem) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { + dbcssystemlog.Info("validate update") + + oldCr, ok1 := oldObj.(*DbcsSystem) + newCr, ok2 := newObj.(*DbcsSystem) + if !ok1 || !ok2 { + return nil, apierrors.NewInternalError(fmt.Errorf("expected *DbcsSystem but got %T/%T", oldObj, newObj)) + } + + blockedStates := map[string]bool{ + "UPDATING": true, + "PROVISIONING": true, + "TERMINATING": true, + } + + if blockedStates[string(newCr.Status.State)] { + if !reflect.DeepEqual(oldCr.Spec, newCr.Spec) { + return nil, apierrors.NewForbidden( + schema.GroupResource{Group: "database.oracle.com", Resource: "DbcsSystem"}, + newCr.Name, + fmt.Errorf("spec updates not allowed while resource is in state %q", newCr.Status.State), + ) + } + } - // // TODO(user): fill in your validation logic upon object update. return nil, nil } -// ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *DbcsSystem) ValidateDelete() (admission.Warnings, error) { - dbcssystemlog.Info("validate delete", "name", r.Name) - - // TODO(user): fill in your validation logic upon object deletion. +// ValidateDelete implements webhook.CustomValidator +func (r *DbcsSystem) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + dbcssystemlog.Info("validate delete") + // TODO: Add delete validation if needed return nil, nil } diff --git a/apis/database/v4/lrest_types.go b/apis/database/v4/lrest_types.go index 421a3ea1..ebe61704 100644 --- a/apis/database/v4/lrest_types.go +++ b/apis/database/v4/lrest_types.go @@ -51,22 +51,26 @@ type LRESTSpec struct { LRESTName string `json:"cdbName,omitempty"` // Name of the LREST Service ServiceName string `json:"serviceName,omitempty"` - // Password for the LREST System Administrator SysAdminPwd LRESTSysAdminPassword `json:"sysAdminPwd,omitempty"` // User in the root container with sysdba priviledges to manage PDB lifecycle LRESTAdminUser LRESTAdminUser `json:"cdbAdminUser,omitempty"` // Password for the LREST Administrator to manage PDB lifecycle LRESTAdminPwd LRESTAdminPassword `json:"cdbAdminPwd,omitempty"` - + // Secret: tls.key LRESTTlsKey LRESTTLSKEY `json:"cdbTlsKey,omitempty"` + // Secret: tls.crt LRESTTlsCrt LRESTTLSCRT `json:"cdbTlsCrt,omitempty"` + // Secret: Pub.key LRESTPubKey LRESTPUBKEY `json:"cdbPubKey,omitempty"` + // Secret: Priv.key LRESTPriKey LRESTPRVKEY `json:"cdbPrvKey,omitempty"` - + // Secret: Tls.cat + LRESTTlsCat LRPDBTLSCAT `json:"cdbTlsCat,omitempty"` // Password for user LREST_PUBLIC_USER LRESTPwd LRESTPassword `json:"lrestPwd,omitempty"` // LREST server port. For now, keep it as 8888. TO BE USED IN FUTURE RELEASE. + // +kubebuilder:default=8888 LRESTPort int `json:"lrestPort,omitempty"` // LREST Image Name LRESTImage string `json:"lrestImage,omitempty"` @@ -86,9 +90,30 @@ type LRESTSpec struct { // DB server port DBPort int `json:"dbPort,omitempty"` // Node Selector for running the Pod - NodeSelector map[string]string `json:"nodeSelector,omitempty"` - DBTnsurl string `json:"dbTnsurl,omitempty"` - DeletePDBCascade bool `json:"deletePdbCascade,omitempty"` + NodeSelector map[string]string `json:"nodeSelector,omitempty"` + // Container database connect string + DBTnsurl string `json:"dbTnsurl,omitempty"` + // lrest server deletion automatically triggers associated pdb deletion + DeletePDBCascade bool `json:"deletePdbCascade,omitempty"` + // Name of the service account + SrvAccountName string `json:"serviceAccountName,omitempty"` + // Detection of pdb created manually via sqlplus or other cli db interface + // It automatically associates a resource to the new pdb + PdbAutoDiscover bool `json:"autodiscover,omitempty"` + // The namespace assigned by default to the new resource when autodiscover is turned on + NamesSpaceAutoDiscover string `json:"namespaceAutoDiscover,omitempty"` + // Specify if cluster ip is required when corev1.Service starts. Note the lrest server + // it's an internal component that should never be visible from outside. Use this parameter + // only if you need to run the operator local. + // +kubebuilder:default=false + ClusterIP bool `json:"clusterIp,omitempty"` + // Create a load balancer: Use this parameter in conjunction with ClusterIP only if you need + // to run the operator local + // +kubebuilder:default=false + LoadBalancer bool `json:"loadBalancer,omitempty"` + // Turn on the sqlnet.trace_level_client + // +kubebuilder:default=0 + SqlNetTrace int `json:"trace_level_client,omitempty"` } // LRESTSecret defines the secretName diff --git a/apis/database/v4/lrest_webhook.go b/apis/database/v4/lrest_webhook.go index 9d65a1d6..2a91a17d 100644 --- a/apis/database/v4/lrest_webhook.go +++ b/apis/database/v4/lrest_webhook.go @@ -39,6 +39,7 @@ package v4 import ( + "context" "reflect" "strings" @@ -58,15 +59,17 @@ var lrestlog = logf.Log.WithName("lrest-webhook") func (r *LREST) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). For(r). + WithDefaulter(r). + WithValidator(r). Complete() } //+kubebuilder:webhook:path=/mutate-database-oracle-com-v4-lrest,mutating=true,failurePolicy=fail,sideEffects=None,groups=database.oracle.com,resources=lrests,verbs=create;update,versions=v4,name=mlrest.kb.io,admissionReviewVersions={v4,v1beta1} -var _ webhook.Defaulter = &LREST{} +var _ webhook.CustomDefaulter = &LREST{} // Default implements webhook.Defaulter so a webhook will be registered for the type -func (r *LREST) Default() { +func (r *LREST) Default(ctx context.Context, obj runtime.Object) error { lrestlog.Info("Setting default values in LREST spec for : " + r.Name) if r.Spec.LRESTPort == 0 { @@ -76,30 +79,33 @@ func (r *LREST) Default() { if r.Spec.Replicas == 0 { r.Spec.Replicas = 1 } + + return nil } // TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. //+kubebuilder:webhook:path=/validate-database-oracle-com-v4-lrest,mutating=false,failurePolicy=fail,sideEffects=None,groups=database.oracle.com,resources=lrests,verbs=create;update,versions=v4,name=vlrest.kb.io,admissionReviewVersions={v4,v1beta1} -var _ webhook.Validator = &LREST{} +var _ webhook.CustomValidator = &LREST{} // ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (r *LREST) ValidateCreate() (admission.Warnings, error) { +func (r *LREST) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { lrestlog.Info("ValidateCreate", "name", r.Name) + lrest := obj.(*LREST) var allErrs field.ErrorList - if r.Spec.ServiceName == "" && r.Spec.DBServer != "" { + if lrest.Spec.ServiceName == "" && lrest.Spec.DBServer != "" { allErrs = append(allErrs, field.Required(field.NewPath("spec").Child("serviceName"), "Please specify LREST Service name")) } - if reflect.ValueOf(r.Spec.LRESTTlsKey).IsZero() { + if reflect.ValueOf(lrest.Spec.LRESTTlsKey).IsZero() { allErrs = append(allErrs, field.Required(field.NewPath("spec").Child("lrestTlsKey"), "Please specify LREST Tls key(secret)")) } - if reflect.ValueOf(r.Spec.LRESTTlsCrt).IsZero() { + if reflect.ValueOf(lrest.Spec.LRESTTlsCrt).IsZero() { allErrs = append(allErrs, field.Required(field.NewPath("spec").Child("lrestTlsCrt"), "Please specify LREST Tls Certificate(secret)")) } @@ -109,41 +115,41 @@ func (r *LREST) ValidateCreate() (admission.Warnings, error) { field.Required(field.NewPath("spec").Child("scanName"), "Please specify SCAN Name for LREST")) }*/ - if (r.Spec.DBServer == "" && r.Spec.DBTnsurl == "") || (r.Spec.DBServer != "" && r.Spec.DBTnsurl != "") { + if (lrest.Spec.DBServer == "" && lrest.Spec.DBTnsurl == "") || (lrest.Spec.DBServer != "" && lrest.Spec.DBTnsurl != "") { allErrs = append(allErrs, field.Required(field.NewPath("spec").Child("dbServer"), "Please specify Database Server Name/IP Address or tnsalias string")) } - if r.Spec.DBTnsurl != "" && (r.Spec.DBServer != "" || r.Spec.DBPort != 0 || r.Spec.ServiceName != "") { + if lrest.Spec.DBTnsurl != "" && (lrest.Spec.DBServer != "" || lrest.Spec.DBPort != 0 || lrest.Spec.ServiceName != "") { allErrs = append(allErrs, field.Required(field.NewPath("spec").Child("dbServer"), "DBtnsurl is orthogonal to (DBServer,DBport,Services)")) } - if r.Spec.DBPort == 0 && r.Spec.DBServer != "" { + if lrest.Spec.DBPort == 0 && lrest.Spec.DBServer != "" { allErrs = append(allErrs, field.Required(field.NewPath("spec").Child("dbPort"), "Please specify DB Server Port")) } - if r.Spec.DBPort < 0 && r.Spec.DBServer != "" { + if lrest.Spec.DBPort < 0 && lrest.Spec.DBServer != "" { allErrs = append(allErrs, field.Required(field.NewPath("spec").Child("dbPort"), "Please specify a valid DB Server Port")) } - if r.Spec.LRESTPort < 0 { + if lrest.Spec.LRESTPort < 0 { allErrs = append(allErrs, field.Required(field.NewPath("spec").Child("ordsPort"), "Please specify a valid LREST Port")) } - if r.Spec.Replicas < 0 { + if lrest.Spec.Replicas < 0 { allErrs = append(allErrs, field.Required(field.NewPath("spec").Child("replicas"), "Please specify a valid value for Replicas")) } - if r.Spec.LRESTImage == "" { + if lrest.Spec.LRESTImage == "" { allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("ordsImage"), "Please specify name of LREST Image to be used")) + field.Required(field.NewPath("spec").Child("lrestImage"), "Please specify name of LREST Image to be used")) } - if reflect.ValueOf(r.Spec.LRESTAdminUser).IsZero() { + if reflect.ValueOf(lrest.Spec.LRESTAdminUser).IsZero() { allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("lrestAdminUser"), "Please specify user in the root container with sysdba priviledges to manage PDB lifecycle")) + field.Required(field.NewPath("spec").Child("cdbAdminUser"), "Please specify user in the root container with sysdba priviledges to manage PDB lifecycle")) } - if reflect.ValueOf(r.Spec.LRESTAdminPwd).IsZero() { + if reflect.ValueOf(lrest.Spec.LRESTAdminPwd).IsZero() { allErrs = append(allErrs, field.Required(field.NewPath("spec").Child("lrestAdminPwd"), "Please specify password for the LREST Administrator to manage PDB lifecycle")) } @@ -151,11 +157,11 @@ func (r *LREST) ValidateCreate() (admission.Warnings, error) { allErrs = append(allErrs, field.Required(field.NewPath("spec").Child("ordsPwd"), "Please specify password for user LREST_PUBLIC_USER")) } */ - if reflect.ValueOf(r.Spec.WebLrestServerUser).IsZero() { + if reflect.ValueOf(lrest.Spec.WebLrestServerUser).IsZero() { allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("webLrestServerUser"), "Please specify the Web Server User having SQL Administrator role")) + field.Required(field.NewPath("spec").Child("webServerUser"), "Please specify the Web Server User having SQL Administrator role")) } - if reflect.ValueOf(r.Spec.WebLrestServerPwd).IsZero() { + if reflect.ValueOf(lrest.Spec.WebLrestServerPwd).IsZero() { allErrs = append(allErrs, field.Required(field.NewPath("spec").Child("webServerPwd"), "Please specify password for the Web Server User having SQL Administrator role")) } @@ -168,7 +174,7 @@ func (r *LREST) ValidateCreate() (admission.Warnings, error) { } // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *LREST) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { +func (r *LREST) ValidateUpdate(ctx context.Context, old, newObj runtime.Object) (admission.Warnings, error) { lrestlog.Info("validate update", "name", r.Name) isLRESTMarkedToBeDeleted := r.GetDeletionTimestamp() != nil @@ -211,7 +217,7 @@ func (r *LREST) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { } // ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *LREST) ValidateDelete() (admission.Warnings, error) { +func (r *LREST) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { lrestlog.Info("validate delete", "name", r.Name) // TODO(user): fill in your validation logic upon object deletion. diff --git a/apis/database/v4/lrpdb_types.go b/apis/database/v4/lrpdb_types.go index d37bebdc..4a6467de 100644 --- a/apis/database/v4/lrpdb_types.go +++ b/apis/database/v4/lrpdb_types.go @@ -44,14 +44,14 @@ import ( // LRPDBSpec defines the desired state of LRPDB type LRPDBSpec struct { - // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster - // Important: Run "make" to regenerate code after modifying this file - + // Secret: tls.key LRPDBTlsKey LRPDBTLSKEY `json:"lrpdbTlsKey,omitempty"` + // Secret: tls.crt LRPDBTlsCrt LRPDBTLSCRT `json:"lrpdbTlsCrt,omitempty"` + // Secret: ca.crt LRPDBTlsCat LRPDBTLSCAT `json:"lrpdbTlsCat,omitempty"` + // Secret for private key LRPDBPriKey LRPDBPRVKEY `json:"cdbPrvKey,omitempty"` - // Namespace of the rest server CDBNamespace string `json:"cdbNamespace,omitempty"` // Name of the CDB Custom Resource that runs the LREST container @@ -66,10 +66,11 @@ type LRPDBSpec struct { AdminName LRPDBAdminName `json:"adminName,omitempty"` // The administrator password for the new LRPDB. This property is required when the Action property is Create. AdminPwd LRPDBAdminPassword `json:"adminPwd,omitempty"` - // Relevant for Create and Plug operations. As defined in the Oracle Multitenant Database documentation. Values can be a filename convert pattern or NONE. + // PDB Admin user AdminpdbUser AdminpdbUser `json:"adminpdbUser,omitempty"` + // PDB Admin user password AdminpdbPass AdminpdbPass `json:"adminpdbPass,omitempty"` - + // Use this parameter on non ASM storage '....path....','pdbname' e.g. '/u01/oradata','dborcl' FileNameConversions string `json:"fileNameConversions,omitempty"` // This property is required when the Action property is Plug. As defined in the Oracle Multitenant Database documentation. Values can be a source filename convert pattern or NONE. SourceFileNameConversions string `json:"sourceFileNameConversions,omitempty"` @@ -84,8 +85,10 @@ type LRPDBSpec struct { // A Path specified for sparse clone snapshot copy. (Optional) SparseClonePath string `json:"sparseClonePath,omitempty"` // Whether to reuse temp file + // +kubebuilder:default=true ReuseTempFile *bool `json:"reuseTempFile,omitempty"` // Relevant for Create and Plug operations. True for unlimited storage. Even when set to true, totalSize and tempSize MUST be specified in the request if Action is Create. + // +kubebuilder:default=true UnlimitedStorage *bool `json:"unlimitedStorage,omitempty"` // Indicate if 'AS CLONE' option should be used in the command to plug in a LRPDB. This property is applicable when the Action property is PLUG but not required. AsClone *bool `json:"asClone,omitempty"` @@ -96,41 +99,60 @@ type LRPDBSpec struct { // Web Server User with SQL Administrator role to allow us to authenticate to the PDB Lifecycle Management REST endpoints WebLrpdbServerUser WebLrpdbServerUser `json:"webServerUser,omitempty"` // Password for the Web Server User - WebLrpdbServerPwd WebLrpdbServerPassword `json:"webServerPwd,omitempt"` + WebLrpdbServerPwd WebLrpdbServerPassword `json:"webServerPwd,omitempty"` // TDE import for plug operations + // +hidefromdoc LTDEImport *bool `json:"tdeImport,omitempty"` // LTDE export for unplug operations + // +hidefromdoc LTDEExport *bool `json:"tdeExport,omitempty"` // TDE password if the tdeImport or tdeExport flag is set to true. Can be used in create, plug or unplug operations + // +hidefromdoc LTDEPassword LTDEPwd `json:"tdePassword,omitempty"` // LTDE keystore path is required if the tdeImport or tdeExport flag is set to true. Can be used in plug or unplug operations. + // +hidefromdoc LTDEKeystorePath string `json:"tdeKeystorePath,omitempty"` // LTDE secret is required if the tdeImport or tdeExport flag is set to true. Can be used in plug or unplug operations. + // +hidefromdoc LTDESecret LTDESecret `json:"tdeSecret,omitempty"` - // Whether you need the script only or execute the script + // Whether you need the script only or execute the script - legacy parameter + // +kubebuilder:default=false GetScript *bool `json:"getScript,omitempty"` // Action to be taken: Create/Clone/Plug/Unplug/Delete/Modify/Status/Map/Alter. Map is used to map a Databse LRPDB to a Kubernetes LRPDB CR. - // +kubebuilder:validation:Enum=Create;Clone;Plug;Unplug;Delete;Modify;Status;Map;Alter;Noaction - Action string `json:"action"` + // Mainted for backward compatibility. No longer need + Action string `json:"action,omitempty"` // Extra options for opening and closing a LRPDB // +kubebuilder:validation:Enum=IMMEDIATE;NORMAL;READ ONLY;READ WRITE;RESTRICTED ModifyOption string `json:"modifyOption,omitempty"` + // Modify Option2 of the LRPDB + // +kubebuilder:default=NONE + ModifyOption2 string `json:"modifyOption2,omitempty"` // to be used with ALTER option - obsolete do not use AlterSystem string `json:"alterSystem,omitempty"` - // to be used with ALTER option - the name of the parameter - AlterSystemParameter string `json:"alterSystemParameter"` - // to be used with ALTER option - the value of the parameter - AlterSystemValue string `json:"alterSystemValue"` - // parameter scope + // To be used with ALTER option - the name of the parameter + AlterSystemParameter string `json:"alterSystemParameter,omitempty"` + // To be used with ALTER option - the value of the parameter + AlterSystemValue string `json:"alterSystemValue,omitempty"` + // Init parameter scope ParameterScope string `json:"parameterScope,omitempty"` // The target state of the LRPDB - // +kubebuilder:validation:Enum=OPEN;CLOSE;ALTER + // +kubebuilder:validation:Enum=OPEN;CLOSE;ALTER;DELETE;UNPLUG;PLUG;CLONE;RESET;NONE LRPDBState string `json:"pdbState,omitempty"` - // turn on the assertive approach to delete pdb resource - // kubectl delete pdb ..... automatically triggers the pluggable database + // Turn on the imperative approach to delete pdb resource + // kubectl delete pdb command automatically triggers the pluggable database // deletion - AssertiveLrpdbDeletion bool `json:"assertiveLrpdbDeletion,omitempty"` - PDBConfigMap string `json:"pdbconfigmap,omitempty"` + ImperativeLrpdbDeletion bool `json:"imperativeLrpdbDeletion,omitempty"` + // Config map containing the pdb parameters + PDBConfigMap string `json:"pdbconfigmap,omitempty"` + // Config map containing sql(ddl)/plsql code + PLSQLBlock string `json:"codeconfigmap,omitempty"` + // Spare filed not used + PLSQLExecMode int `json:"plsqlexemode,omitempty"` + // For future use - rest bitmask status + // ++kubebuilder:default=0 + PDBBitMask int `json:"reststate,omitempty"` + // Debug option , not yet implemented + Debug int `json:"debug,omitempty"` } // LRPDBAdminName defines the secret containing Sys Admin User mapped to key 'adminName' for LRPDB @@ -208,16 +230,21 @@ type LRPDBStatus struct { OpenMode string `json:"openMode,omitempty"` // Modify Option of the LRPDB ModifyOption string `json:"modifyOption,omitempty"` + // Restricted + Restricted string `json:"restricted,omitempty"` // Message Msg string `json:"msg,omitempty"` // Last Completed Action Action string `json:"action,omitempty"` // Last Completed alter system - AlterSystem string `json:"alterSystem,omitempty"` + PDBBitMask int `json:"pdbBitMask,omitempty"` + PDBBitMaskStr string `json:"pdbBitMaskStr,omitempty"` + AlterSystem string `json:"alterSystem,omitempty"` // Last ORA- - SqlCode int `json:"sqlCode"` - Bitstat int `json:"bitstat,omitempty"` /* Bitmask */ - BitStatStr string `json:"bitstatstr,omitempty"` /* Decoded bitmask */ + SqlCode int `json:"sqlCode"` + LastPLSQL string `json:"lastplsql,omitempty"` + CmBitstat int `json:"bitstat,omitempty"` /* Bitmask */ + CmBitStatStr string `json:"bitstatstr,omitempty"` /* Decoded bitmask */ } // +kubebuilder:object:root=true @@ -226,9 +253,11 @@ type LRPDBStatus struct { // +kubebuilder:printcolumn:JSONPath=".spec.pdbName",name="PDB Name",type="string",description="Name of the PDB" // +kubebuilder:printcolumn:JSONPath=".status.openMode",name="PDB State",type="string",description="PDB Open Mode" // +kubebuilder:printcolumn:JSONPath=".status.totalSize",name="PDB Size",type="string",description="Total Size of the PDB" -// +kubebuilder:printcolumn:JSONPath=".status.phase",name="Status",type="string",description="Status of the LRPDB Resource" // +kubebuilder:printcolumn:JSONPath=".status.msg",name="Message",type="string",description="Error message, if any" +// +kubebuilder:printcolumn:JSONPath=".status.restricted",name="Restricted",type="string",description="open restricted" // +kubebuilder:printcolumn:JSONPath=".status.sqlCode",name="last sqlcode",type="integer",description="last sqlcode" +// +kubebuilder:printcolumn:JSONPath=".status.lastplsql",name="last PLSQL",type="string",description="last plsql applied" +// +kubebuilder:printcolumn:JSONPath=".status.pdbBitMaskStr",name="BITMASK STATUS",type="string",description="Bitmask status" // +kubebuilder:printcolumn:JSONPath=".status.connString",name="Connect_String",type="string",description="The connect string to be used" // +kubebuilder:resource:path=lrpdbs,scope=Namespaced // +kubebuilder:storageversion diff --git a/apis/database/v4/lrpdb_webhook.go b/apis/database/v4/lrpdb_webhook.go index d6807926..2e1b923d 100644 --- a/apis/database/v4/lrpdb_webhook.go +++ b/apis/database/v4/lrpdb_webhook.go @@ -49,6 +49,7 @@ import ( "strconv" "strings" + . "github.com/oracle/oracle-database-operator/commons/multitenant/lrest" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -150,39 +151,18 @@ func (r *LRPDB) ValidateCreate(ctx context.Context, obj runtime.Object) (admissi return nil, apierrors.NewInvalid( schema.GroupKind{Group: "database.oracle.com", Kind: "LRPDB"}, r.Name, allErrs) - return nil, nil } // Validate Action for required parameters func (r *LRPDB) validateAction(allErrs *field.ErrorList, ctx context.Context, pdb LRPDB) { - action := strings.ToUpper(pdb.Spec.Action) - - lrpdblog.Info("Valdiating LRPDB Resource Action : " + action) - - if reflect.ValueOf(pdb.Spec.LRPDBTlsKey).IsZero() { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("lrpdbTlsKey"), "Please specify LRPDB Tls Key(secret)")) - } - - if reflect.ValueOf(pdb.Spec.LRPDBTlsCrt).IsZero() { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("lrpdbTlsCrt"), "Please specify LRPDB Tls Certificate(secret)")) - } - if reflect.ValueOf(pdb.Spec.LRPDBTlsCat).IsZero() { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("lrpdbTlsCat"), "Please specify LRPDB Tls Certificate Authority(secret)")) - } + pdbstate := strings.ToUpper(pdb.Spec.LRPDBState) + scrdatabase := strings.ToUpper(pdb.Spec.SrcLRPDBName) + //plsql := strings.ToUpper(pdb.Spec.PLSQLBlock) - switch action { - case "DELETE": - /* BUG 36752336 - LREST OPERATOR - DELETE NON-EXISTENT PDB SHOWS LRPDB CREATED MESSAGE */ - if pdb.Status.OpenMode == "READ WRITE" { - lrpdblog.Info("Cannot delete: pdb is open ") - *allErrs = append(*allErrs, field.Invalid(field.NewPath("status").Child("OpenMode"), "READ WRITE", "pdb "+pdb.Spec.LRPDBName+" "+pdb.Status.OpenMode)) - } - r.CheckObjExistence("DELETE", allErrs, ctx, pdb) - case "CREATE": + lrpdblog.Info("Valdiating LRPDB Resource ") + /* Parameters required by the creation */ + if Bit(pdb.Status.PDBBitMask, PDBCRT) == false { if reflect.ValueOf(pdb.Spec.AdminpdbUser).IsZero() { *allErrs = append(*allErrs, field.Required(field.NewPath("spec").Child("adminpdbUser"), "Please specify LRPDB System Administrator user")) @@ -199,32 +179,32 @@ func (r *LRPDB) validateAction(allErrs *field.ErrorList, ctx context.Context, pd *allErrs = append(*allErrs, field.Required(field.NewPath("spec").Child("totalSize"), "When the storage is not UNLIMITED the Total Size must be specified")) } - if pdb.Spec.TempSize == "" { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("tempSize"), "When the storage is not UNLIMITED the Temp Size must be specified")) - } - if *(pdb.Spec.LTDEImport) { - r.validateTDEInfo(allErrs, ctx, pdb) - } - case "CLONE": - // Sample Err: The LRPDB "lrpdb1-clone" is invalid: spec.srcPdbName: Required value: Please specify source LRPDB for Cloning - if pdb.Spec.SrcLRPDBName == "" { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("srcPdbName"), "Please specify source LRPDB name for Cloning")) - } - if pdb.Spec.TotalSize == "" { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("totalSize"), "When the storage is not UNLIMITED the Total Size must be specified")) - } - if pdb.Spec.TempSize == "" { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("tempSize"), "When the storage is not UNLIMITED the Temp Size must be specified")) - } - if pdb.Status.OpenMode == "MOUNT" { - lrpdblog.Info("Cannot clone: pdb is mount ") - *allErrs = append(*allErrs, field.Invalid(field.NewPath("status").Child("OpenMode"), "READ WRITE", "pdb "+pdb.Spec.LRPDBName+" "+pdb.Status.OpenMode)) - } - case "PLUG": + /* + if pdb.Spec.TempSize == "" { + *allErrs = append(*allErrs, + field.Required(field.NewPath("spec").Child("tempSize"), "When the storage is not UNLIMITED the Temp Size must be specified")) + } + if *(pdb.Spec.LTDEImport) { + r.validateTDEInfo(allErrs, ctx, pdb) + } + */ + + } + + /* We cannot open|close|delete|unplug a non existing pdb */ + if (pdbstate == "OPEN" || pdbstate == "CLOSE" || pdbstate == "DELETE" || pdbstate == "UNPLUG") && Bit(pdb.Status.PDBBitMask, PDBCRT) == false { + *allErrs = append(*allErrs, + field.Required(field.NewPath("spec").Child("LRPDBState"), "PDB does not exists")) + } + + /* Database already exists + if scrdatabase != "" && Bit(pdb.Status.PDBBitMask, PDBCRT) == true { + *allErrs = append(*allErrs, + field.Required(field.NewPath("spec").Child("SrcLRPDBName"), "PDB already exists/Cannot clone")) + } + */ + + if pdbstate == "PLUG" && pdb.Spec.XMLFileName != "" && Bit(pdb.Status.PDBBitMask, PDBCRT) == false && Bit(pdb.Status.PDBBitMask, PDBPLE) == false { if pdb.Spec.XMLFileName == "" { *allErrs = append(*allErrs, field.Required(field.NewPath("spec").Child("xmlFileName"), "Please specify XML metadata filename")) @@ -244,7 +224,10 @@ func (r *LRPDB) validateAction(allErrs *field.ErrorList, ctx context.Context, pd if *(pdb.Spec.LTDEImport) { r.validateTDEInfo(allErrs, ctx, pdb) } - case "UNPLUG": + + } + + if pdbstate == "UNPLUG" && pdb.Spec.XMLFileName != "" && Bit(pdb.Status.PDBBitMask, PDBCRT) == true && Bit(pdb.Status.PDBBitMask, FNALAZ) == true && Bit(pdb.Status.PDBBitMask, PDBUPE) == false { if pdb.Spec.XMLFileName == "" { *allErrs = append(*allErrs, field.Required(field.NewPath("spec").Child("xmlFileName"), "Please specify XML metadata filename")) @@ -257,17 +240,53 @@ func (r *LRPDB) validateAction(allErrs *field.ErrorList, ctx context.Context, pd *allErrs = append(*allErrs, field.Invalid(field.NewPath("status").Child("OpenMode"), "READ WRITE", "pdb "+pdb.Spec.LRPDBName+" "+pdb.Status.OpenMode)) } r.CheckObjExistence("UNPLUG", allErrs, ctx, pdb) - case "MODIFY": + } + + if reflect.ValueOf(pdb.Spec.LRPDBTlsKey).IsZero() { + *allErrs = append(*allErrs, + field.Required(field.NewPath("spec").Child("lrpdbTlsKey"), "Please specify LRPDB Tls Key(secret)")) + } - if pdb.Spec.LRPDBState == "" { + if reflect.ValueOf(pdb.Spec.LRPDBTlsCrt).IsZero() { + *allErrs = append(*allErrs, + field.Required(field.NewPath("spec").Child("lrpdbTlsCrt"), "Please specify LRPDB Tls Certificate(secret)")) + } + + if reflect.ValueOf(pdb.Spec.LRPDBTlsCat).IsZero() { + *allErrs = append(*allErrs, + field.Required(field.NewPath("spec").Child("lrpdbTlsCat"), "Please specify LRPDB Tls Certificate Authority(secret)")) + } + + /* Check clone parameters */ + if scrdatabase != "" && Bit(pdb.Status.PDBBitMask, PDBCRT|FNALAZ|PDBCRE) == false { + if pdb.Spec.TotalSize == "" { *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("lrpdbState"), "Please specify target state of LRPDB")) + field.Required(field.NewPath("spec").Child("totalSize"), "When the storage is not UNLIMITED the Total Size must be specified")) } - if pdb.Spec.ModifyOption == "" && pdb.Spec.AlterSystem == "" { + if pdb.Spec.TempSize == "" { *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("modifyOption"), "Please specify an option for opening/closing a LRPDB or alter system parameter")) + field.Required(field.NewPath("spec").Child("tempSize"), "When the storage is not UNLIMITED the Temp Size must be specified")) + } + if pdb.Status.OpenMode == "MOUNT" { + lrpdblog.Info("Cannot clone: pdb is mount ") + *allErrs = append(*allErrs, field.Invalid(field.NewPath("status").Child("OpenMode"), "READ WRITE", "pdb "+pdb.Spec.LRPDBName+" "+pdb.Status.OpenMode)) } - r.CheckObjExistence("MODIFY", allErrs, ctx, pdb) + + } + + if pdbstate == "UNPLUG" { + if pdb.Spec.XMLFileName == "" { + *allErrs = append(*allErrs, + field.Required(field.NewPath("spec").Child("xmlFileName"), "Please specify XML metadata filename")) + } + if *(pdb.Spec.LTDEExport) { + r.validateTDEInfo(allErrs, ctx, pdb) + } + if pdb.Status.OpenMode == "READ WRITE" { + lrpdblog.Info("Cannot unplug: pdb is open ") + *allErrs = append(*allErrs, field.Invalid(field.NewPath("status").Child("OpenMode"), "READ WRITE", "pdb "+pdb.Spec.LRPDBName+" "+pdb.Status.OpenMode)) + } + r.CheckObjExistence("UNPLUG", allErrs, ctx, pdb) } } @@ -317,7 +336,6 @@ func (r *LRPDB) ValidateUpdate(ctx context.Context, obj runtime.Object, old runt return nil, apierrors.NewInvalid( schema.GroupKind{Group: "database.oracle.com", Kind: "LRPDB"}, r.Name, allErrs) - return nil, nil } // ValidateDelete implements webhook.Validator so a webhook will be registered for the type @@ -332,10 +350,10 @@ func (r *LRPDB) ValidateDelete(ctx context.Context, obj runtime.Object) (admissi func (r *LRPDB) validateCommon(allErrs *field.ErrorList, ctx context.Context, pdb LRPDB) { lrpdblog.Info("validateCommon", "name", pdb.Name) - if pdb.Spec.Action == "" { + /* if pdb.Spec.Action == "" { *allErrs = append(*allErrs, field.Required(field.NewPath("spec").Child("action"), "Please specify LRPDB operation to be performed")) - } + } */ if pdb.Spec.CDBResName == "" { *allErrs = append(*allErrs, field.Required(field.NewPath("spec").Child("cdbResName"), "Please specify the name of the CDB Kubernetes resource to use for LRPDB operations")) diff --git a/apis/database/v4/oraclerestart_types.go b/apis/database/v4/oraclerestart_types.go new file mode 100644 index 00000000..b0ae127d --- /dev/null +++ b/apis/database/v4/oraclerestart_types.go @@ -0,0 +1,381 @@ +/* +** Copyright (c) 2022 Oracle and/or its affiliates. +** +** The Universal Permissive License (UPL), Version 1.0 +** +** Subject to the condition set forth below, permission is hereby granted to any +** person obtaining a copy of this software, associated documentation and/or data +** (collectively the "Software"), free of charge and under any and all copyright +** rights in the Software, and any and all patent rights owned or freely +** licensable by each licensor hereunder covering either (i) the unmodified +** Software as contributed to or provided by such licensor, or (ii) the Larger +** Works (as defined below), to deal in both +** +** (a) the Software, and +** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +** one is included with the Software (each a "Larger Work" to which the Software +** is contributed by such licensors), +** +** without restriction, including without limitation the rights to copy, create +** derivative works of, display, perform, and distribute the Software and make, +** use, sell, offer for sale, import, export, have made, and have sold the +** Software and the Larger Work(s), and to sublicense the foregoing rights on +** either these or other terms. +** +** This license is subject to the following condition: +** The above copyright notice and either this complete permission notice or at +** a minimum a reference to the UPL must be included in all copies or +** substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +** SOFTWARE. + */ + +package v4 + +import ( + "sync" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// OracleRestartSpec defines the desired state of OracleRestart +type OracleRestartSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + InstDetails OracleRestartInstDetailSpec `json:"instDetails"` + ConfigParams *InitParams `json:"configParams,omitempty"` + AsmStorageDetails *AsmDiskDetails `json:"asmStorageDetails,omitempty"` + Image string `json:"image,omitempty"` + ImagePullSecret string `json:"imagePullSecret,omitempty"` + ScriptsLocation string `json:"scriptsLocation,omitempty"` + SshKeySecret *OracleRestartSshSecretDetails `json:"sshKeySecret,omitempty"` + // +kubebuilder:validation:Enum=Always;IfNotPresent;Never + // +kubebuilder:validation:default="Always" + ImagePullPolicy *corev1.PullPolicy `json:"imagePullPolicy,omitempty"` + ReadinessProbe *corev1.Probe `json:"readinessProbe,omitempty"` + ScriptsGetCmd string `json:"scriptsGetCmd,omitempty"` + IsDebug string `json:"isDebug,omitempty"` + SecurityContext *corev1.PodSecurityContext `json:"securityContext"` + IsDeleteTopolgy string `json:"isDeleteTopology,omitempty"` + DbSecret *OracleRestartDbPwdSecretDetails `json:"dbSecret,omitempty"` + TdeWalletSecret *OracleRestartDbPwdSecretDetails `json:"tdeWalletSecret,omitempty"` + ServiceDetails ServiceSpec `json:"serviceDetails,omitempty"` + Resources *corev1.ResourceRequirements `json:"resources,omitempty" protobuf:"bytes,1,opt,name=resources"` + IsFailed bool `json:"isFailed,omitempty"` + IsManual bool `json:"isManual,omitempty"` + SrvAccountName string `json:"serviceAccountName,omitempty"` + DataDgStorageClass string `json:"dataDgStorageClass,omitempty"` + RedoDgStorageClass string `json:"redoDgStorageClass,omitempty"` + RecoDgStorageClass string `json:"recoDgStorageClass,omitempty"` + SwStorageClass string `json:"swDgStorageClass,omitempty"` + CrsDgStorageClass string `json:"crsDgStorageClass,omitempty"` + LbService OracleRestartNodePortSvc `json:"lbService,omitempty"` + NodePortSvc OracleRestartNodePortSvc `json:"nodePortSvc,omitempty"` // Port mappings for the service that is created. The service is created if + // +kubebuilder:validation:Enum=enable;disable + // +kubebuilder:default="enable" + EnableOns string `json:"enableOns,omitempty"` +} + +type AsmDiskDetails struct { + DisksBySize []DiskBySize `json:"disksBySize,omitempty"` + AutoUpdate string `json:"autoUpdate,omitempty"` +} + +// DiskBySize represents a list of disks grouped by size +type DiskBySize struct { + StorageSizeInGb int `json:"storageSizeInGb,omitempty"` + DiskNames []string `json:"diskNames,omitempty"` +} + +type InitParams struct { + GridHome string `json:"gridHome,omitempty"` + DbHome string `json:"dbHome,omitempty"` + GridBase string `json:"gridBase,omitempty"` + DbBase string `json:"dbBase,omitempty"` + Inventory string `json:"inventory,omitempty"` + GridResponseFile ResponseFile `json:"gridResponseFile,omitempty"` + DbResponseFile ResponseFile `json:"dbResponseFile,omitempty"` + GridSwZipFile string `json:"gridSwZipFile,omitempty"` + DbSwZipFile string `json:"dbSwZipFile,omitempty"` + OPatchSwZipFile string `json:"oPatchSwZipFile,omitempty"` + StagingSoftwareLocation string `json:"stagingSoftwareLocation,omitempty"` + OpType string `json:"opType,omitempty"` + CpuCount int `json:"cpuCount,omitempty"` + SgaSize string `json:"sgaSize,omitempty"` + PgaSize string `json:"pgaSize,omitempty"` + Processes int `json:"processes,omitempty"` + DbUniqueName string `json:"dbUniqueName,omitempty"` + CrsAsmDiskDg string `json:"crsAsmDiskDg,omitempty"` + CrsAsmDeviceList string `json:"crsAsmDeviceList,omitempty"` + DbRecoveryFileDest string `json:"dbRecoveryFileDest,omitempty"` + DbRecoveryFileDestSize string `json:"dbRecoveryFileDestSize,omitempty"` + DbDataFileDestDg string `json:"dbDataFileDestDg,omitempty"` + CrsAsmDiskDgRedundancy string `json:"crsAsmDiskDgRedundancy,omitempty"` + DBAsmDiskDgRedundancy string `json:"dbAsmDiskDgRedundancy,omitempty"` + RecoAsmDiskDgRedundancy string `json:"recoAsmDiskDgRedundancy,omitempty"` + RedoAsmDiskDgRedundancy string `json:"redoAsmDiskDgRedundancy,omitempty"` + DbName string `json:"dbName,omitempty"` + PdbName string `json:"pdbName,omitempty"` + DbStorageType string `json:"dbStorageType,omitempty"` + DbAsmDeviceList string `json:"dbAsmDeviceList,omitempty"` + RecoAsmDeviceList string `json:"recoAsmDeviceList,omitempty"` + RedoAsmDeviceList string `json:"redoAsmDeviceList,omitempty"` + RedoAsmDiskDg string `json:"redoAsmDiskDg,omitempty"` + DbCharSet string `json:"dbCharSet,omitempty"` + DbRedoFileSize string `json:"dbRedoFileSize,omitempty"` + DbType string `json:"dbType,omitempty"` + DbConfigType string `json:"dbConfigType,omitempty"` + EnableArchiveLog string `json:"enableArchiveLog,omitempty"` + SwMountLocation string `json:"swMountLocation,omitempty"` + HostSwStageLocation string `json:"hostSwStageLocation,omitempty"` + RuPatchLocation string `json:"ruPatchLocation,omitempty"` + RuFolderName string `json:"ruFolderName,omitempty"` + OPatchLocation string `json:"oPatchLocation,omitempty"` + SwStagePvc string `json:"swStagePvc,omitempty"` + SwStagePvcMountLocation string `json:"swStagePvcMountLocation,omitempty"` + OneOffLocation string `json:"oneOffLocation,omitempty"` + DbOneOffIds string `json:"dbOneOffIds,omitempty"` + GridOneOffIds string `json:"gridOneOffIds,omitempty"` +} + +type OracleRestartInstDetailSpec struct { + Name string `json:"name,omitempty"` // Name of the Oracle Restart Instance + HostSwLocation string `json:"hostSwLocation,omitempty"` + SwLocStorageSizeInGb int `json:"swLocStorageSizeInGb,omitempty"` + WorkerNode []string `json:"workerNode,omitempty"` + EnvVars []corev1.EnvVar `json:"envVars,omitempty"` //Optional Env variables for Shards + Label string `json:"label,omitempty"` + IsDelete string `json:"isDelete,omitempty"` + IsForceDelete string `json:"isForceDelete,omitempty"` + EnvFile string `json:"envFile,omitempty"` + // +kubebuilder:validation:default="delete" + IsKeepPVC string `json:"isKeepPVC,omitempty"` + PvcName map[string]string `json:"pvcName,omitempty"` +} + +// Responsefile Name +type ResponseFile struct { + ConfigMapName string `json:"configMapName,omitempty"` + Name string `json:"name,omitempty"` +} + +// OracleRestart DB Secret Details +type OracleRestartDbPwdSecretDetails struct { + Name string `json:"name,omitempty"` // Name of the secret. + KeyFileName string `json:"keyFileName,omitempty"` // Name of the key. + PwdFileName string `json:"pwdFileName,omitempty"` + PwdFileMountLocation string `json:"pwdFileMountLocation,omitempty"` + KeyFileMountLocation string `json:"keyFileMountLocation,omitempty"` + KeySecretName string `json:"keySecretName,omitempty"` + EncryptionType string `json:"encryptionType,omitempty"` +} + +// OracleRestart Ssh secret Details +type OracleRestartSshSecretDetails struct { + Name string `json:"name"` // Name of the secret. + KeyMountLocation string `json:"keyMountLocation,omitempty"` + PrivKeySecretName string `json:"privKeySecretName,omitempty"` + PubKeySecretName string `json:"pubKeySecretName,omitempty"` +} + +// Service Definition +type ServiceSpec struct { + Name string `json:"name"` // Name of the shardSpace. + Cardinality string `json:"cardinality,omitempty"` + Preferred []string `json:"preferred,omitempty"` + TafPolicy string `json:"tafPolicy,omitempty"` + Available []string `json:"available,omitempty"` + Role string `json:"role,omitempty"` + Notification string `json:"notification,omitempty"` + CommitOutCome string `json:"commitOutcome,omitempty"` + CommitOutComeFastPath string `json:"commitOutComeFastPath,omitempty"` + Retention int `json:"retenion,omitempty"` + SessionState string `json:"sessionState,omitempty"` + Pdb string `json:"pdb,omitempty"` + StopOption string `json:"stopOption,omitempty"` + DrainTimeOut int `json:"drainTimeOut,omitempty"` + FailOverType string `json:"failOverType,omitempty"` + FailOverDelay int `json:"failOverDelay,omitempty"` + FailOverRetry int `json:"failOverRetry,omitempty"` + FailBack string `json:"failBack,omitempty"` + FailOverRestore string `json:"failOverRestore,omitempty"` + ClbGoal string `json:"clbGoal,omitempty"` + RlbGoal string `json:"rlbGoal,omitempty"` + Dtp string `json:"dtp,omitempty"` + Edition string `json:"edition,omitempty"` + SvcState string `json:"svcState,omitempty"` +} + +// OracleRestartStatus defines the observed state of OracleRestart +type OracleRestartStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file + DbName string `json:"DbName,omitempty"` + ConnectString string `json:"connectString,omitempty"` + PdbConnectString string `json:"pdbConnectString,omitempty"` + ExternalConnectString string `json:"externalConnectString,omitempty"` + OracleRestartNodes []*OracleRestartNodestatus `json:"OracleRestartNodes,omitempty"` + ReleaseUpdate string `json:"releaseUpdate,omitempty"` + Role string `json:"role,omitempty"` + DbState string `json:"dbState,omitempty"` + State string `json:"state,omitempty"` + InstallNode string `json:"installNode,omitempty"` + + // +patchMergeKey=type + // +patchStrategy=merge + // +listType=map + // +listMapKey=type + Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` + InstDetails *OracleRestartInstDetailSpec `json:"instDetails,omitempty"` + ConfigParams *InitParams `json:"configParams,omitempty"` + AsmDetails *AsmInstanceStatus `json:"asmDetails,omitempty"` + NfsStorageDetails *corev1.NFSVolumeSource `json:"nfsStorageDetails,omitempty"` + UseNfsforSwStorage string `json:"useNfsforSwStorage,omitempty"` + StorageClass string `json:"storageClass,omitempty"` + StorageSizeInGB int `json:"storageSizeInGB,omitempty"` + Image string `json:"image,omitempty"` + ImagePullSecret string `json:"imagePullSecret,omitempty"` + ScriptsLocation string `json:"scriptsLocation,omitempty"` + IsDeleteOraPvc string `json:"isDeleteOraPvc,omitempty"` + SshKeySecret *OracleRestartSshSecretDetails `json:"sshKeySecret,omitempty"` + ImagePullPolicy *corev1.PullPolicy `json:"imagePullPolicy,omitempty"` + ReadinessProbe *corev1.Probe `json:"readinessProbe,omitempty"` + ScriptsGetCmd string `json:"scriptsGetCmd,omitempty"` + IsDebug string `json:"isDebug,omitempty"` + SecurityContext *corev1.PodSecurityContext `json:"securityContext,omitempty"` + IsDeleteTopolgy string `json:"isDeleteTopology,omitempty"` + ExternalSvcType *string `json:"externalSvcType,omitempty"` + DbSecret *OracleRestartDbPwdSecretDetails `json:"dbSecret,omitempty"` + TdeWalletSecret *OracleRestartDbPwdSecretDetails `json:"tdeWalletSecret,omitempty"` + ServiceDetails ServiceSpec `json:"serviceDetails,omitempty"` + Resources *corev1.ResourceRequirements `json:"resources,omitempty" protobuf:"bytes,1,opt,name=resources"` + OldSpec string `json:"oldSpec,omitempty"` +} + +type OracleRestartNodePortSvc struct { + PortMappings []OracleRestartPortMapping `json:"portMappings,omitempty"` + SvcName string `json:"name,omitempty"` + SvcType string `json:"svcType,omitempty"` + SvcLBIP string `json:"svcLBIP,omitempty"` + SvcAnnotation map[string]string `json:"svcAnnotation,omitempty"` + OnsTargetPort *int32 `json:"onsTargetPort,omitempty"` // Port that will be exposed on the service. + OnsLocalPort *int32 `json:"onsLocalPort,omitempty"` // Port that will be exposed on the service. +} + +type OracleRestartPortMapping struct { + Port int32 `json:"port,omitempty"` + TargetPort int32 `json:"targetPort,omitempty"` + // +kubebuilder:validation:Enum=TCP;UDP;SCTP + // +kubebuilder:default=TCP + Protocol corev1.Protocol `json:"protocol,omitempty"` + NodePort int32 `json:"nodePort,omitempty"` +} + +type OracleRestartNodestatus struct { + Name string `json:"name,omitempty"` + NodeDetails *OracleRestartNodeDetailedStatus `json:"nodeDetails,omitempty"` +} + +type OracleRestartNodeDetailedStatus struct { + WorkerNode string `json:"workerNode,omitempty"` //Optional Env variables for Shards + PvcName map[string]string `json:"pvcName,omitempty"` + NodePortSvc []OracleRestartNodePortSvc `json:"nodePortSvc,omitempty"` // Port mappings for the service that is created. The service is created if + PortMappings []OracleRestartPortMapping `json:"portMappings,omitempty"` + ClusterState string `json:"clusterState,omitempty"` + InstanceState string `json:"InstanceState,omitempty"` + PodState string `json:"PodState,omitempty"` + IsDelete string `json:"isDelete,omitempty"` + State string `json:"state,omitempty"` + MountedDevices []string `json:"mountedDevices,omitempty"` +} + +type AsmInstanceStatus struct { + Diskgroup []AsmDiskgroupStatus `json:"diskgroup,omitempty"` +} + +type AsmDiskgroupStatus struct { + Name string `json:"name,omitempty"` + Disks []string `json:"disks,omitempty"` + Redundancy string `json:"redundancy,omitempty"` +} + +type OracleRestartLifecycleState string + +const ( + OracleRestartAvailableState OracleRestartLifecycleState = "AVAILABLE" + OracleRestartFailedState OracleRestartLifecycleState = "FAILED" + OracleRestartUpdateState OracleRestartLifecycleState = "UPDATING" + OracleRestartProvisionState OracleRestartLifecycleState = "PROVISIONING" + OracleRestartPendingState OracleRestartLifecycleState = "PENDING" + OracleRestartFieldNotDefined OracleRestartLifecycleState = "NOT_DEFINED" + OracleRestartPodNotReadyState OracleRestartLifecycleState = "PODNOTREADY" + OracleRestartPodFailureState OracleRestartLifecycleState = "PODFAILURE" + OracleRestartPodNotFound OracleRestartLifecycleState = "PODNOTFOUND" + OracleRestartStatefulSetFailure OracleRestartLifecycleState = "STATEFULSETFAILURE" + OracleRestartStatefulSetNotFound OracleRestartLifecycleState = "STATEFULSETNOTFOUND" + OracleRestartPodAvailableState OracleRestartLifecycleState = "PODAVAILABLE" + OracleRestartDeletingState OracleRestartLifecycleState = "DELETING" + OracleRestartDeleteErrorState OracleRestartLifecycleState = "DELETE_ERROR" + OracleRestartTerminated OracleRestartLifecycleState = "TERMINATED" + OracleRestartLabelPatchingError OracleRestartLifecycleState = "LABELPATCHINGERROR" + OracleRestartDeletePVCError OracleRestartLifecycleState = "DELETEPVCERROR" + OracleRestartAddInstState OracleRestartLifecycleState = "OracleRestart_INST_ADDITION" + OracleRestartManualState OracleRestartLifecycleState = "MANUAL" +) + +type OracleRestartCrdReconcileState string + +const ( + OracleRestartCrdReconcileErrorState OracleRestartCrdReconcileState = "ReconcileError" + OracleRestartCrdReconcileErrorReason OracleRestartCrdReconcileState = "LastReconcileCycleFailed" + OracleRestartCrdReconcileQueuedState OracleRestartCrdReconcileState = "ReconcileQueued" + OracleRestartCrdReconcileQueuedReason OracleRestartCrdReconcileState = "LastReconcileCycleQueued" + OracleRestartCrdReconcileCompeleteState OracleRestartCrdReconcileState = "ReconcileComplete" + OracleRestartCrdReconcileCompleteReason OracleRestartCrdReconcileState = "LastReconcileCycleCompleted" + OracleRestartCrdReconcileWaitingState OracleRestartCrdReconcileState = "ReconcileWaiting" + OracleRestartCrdReconcileWaitingReason OracleRestartCrdReconcileState = "LastReconcileCycleWaiting" +) + +// var +var OracleRestartKubeConfigOnce sync.Once + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:storageversion +// +kubebuilder:printcolumn:JSONPath=".status.configParams.dbName",name="DbName",type="string" +// +kubebuilder:printcolumn:JSONPath=".status.dbState",name="DbState",type="string" +// +kubebuilder:printcolumn:JSONPath=".status.role",name="Role",type="string" +// +kubebuilder:printcolumn:JSONPath=".status.releaseUpdate",name="Version",type="string" +// +kubebuilder:printcolumn:JSONPath=".status.pdbConnectString",name="Pdb Connect Str",type="string" +// +kubebuilder:printcolumn:JSONPath=".status.state",name="State",type="string" +// OracleRestart is the Schema for the OracleRestarts API +type OracleRestart struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec OracleRestartSpec `json:"spec,omitempty"` + Status OracleRestartStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// OracleRestartList contains a list of OracleRestart +type OracleRestartList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []OracleRestart `json:"items"` +} + +func init() { + SchemeBuilder.Register(&OracleRestart{}, &OracleRestartList{}) +} diff --git a/apis/database/v4/oraclerestart_webhook.go b/apis/database/v4/oraclerestart_webhook.go new file mode 100644 index 00000000..51e6a7a0 --- /dev/null +++ b/apis/database/v4/oraclerestart_webhook.go @@ -0,0 +1,1194 @@ +/* +** Copyright (c) 2022 Oracle and/or its affiliates. +** +** The Universal Permissive License (UPL), Version 1.0 +** +** Subject to the condition set forth below, permission is hereby granted to any +** person obtaining a copy of this software, associated documentation and/or data +** (collectively the "Software"), free of charge and under any and all copyright +** rights in the Software, and any and all patent rights owned or freely +** licensable by each licensor hereunder covering either (i) the unmodified +** Software as contributed to or provided by such licensor, or (ii) the Larger +** Works (as defined below), to deal in both +** +** (a) the Software, and +** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +** one is included with the Software (each a "Larger Work" to which the Software +** is contributed by such licensors), +** +** without restriction, including without limitation the rights to copy, create +** derivative works of, display, perform, and distribute the Software and make, +** use, sell, offer for sale, import, export, have made, and have sold the +** Software and the Larger Work(s), and to sublicense the foregoing rights on +** either these or other terms. +** +** This license is subject to the following condition: +** The above copyright notice and either this complete permission notice or at +** a minimum a reference to the UPL must be included in all copies or +** substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +** SOFTWARE. + */ + +package v4 + +import ( + "context" + "fmt" + "reflect" + "regexp" + "strings" + + utils "github.com/oracle/oracle-database-operator/commons/oraclerestart/utils" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" + ctrl "sigs.k8s.io/controller-runtime" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + corev1 "k8s.io/api/core/v1" +) + +// log is for logging in this package. +var OracleRestartlog = logf.Log.WithName("OracleRestart-resource") + +func (r *OracleRestart) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + For(&OracleRestart{}). + WithDefaulter(r). + WithValidator(r). + Complete() +} + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! + +//+kubebuilder:webhook:path=/mutate-database-oracle-com-v4-oraclerestart,mutating=true,failurePolicy=fail,sideEffects=None,groups=database.oracle.com,resources=oraclerestarts,verbs=create;update,versions=v4,name=moraclerestart.kb.io,admissionReviewVersions={v1} + +var _ webhook.CustomDefaulter = &OracleRestart{} + +func (r *OracleRestart) Default(ctx context.Context, obj runtime.Object) error { + cr, ok := obj.(*OracleRestart) + if !ok { + return fmt.Errorf("expected *OracleRestart but got %T", obj) + } + + OracleRestartlog.Info("default", "name", cr.Name) + + if cr.Spec.ImagePullPolicy == nil { + policy := corev1.PullAlways + cr.Spec.ImagePullPolicy = &policy + } + + if cr.Spec.SshKeySecret != nil && cr.Spec.SshKeySecret.KeyMountLocation == "" { + cr.Spec.SshKeySecret.KeyMountLocation = utils.OraRacSshSecretMount + } + + if cr.Spec.DbSecret != nil && cr.Spec.DbSecret.Name != "" { + if cr.Spec.DbSecret.PwdFileMountLocation == "" { + cr.Spec.DbSecret.PwdFileMountLocation = utils.OraRacDbPwdFileSecretMount + } + if cr.Spec.DbSecret.KeyFileMountLocation == "" { + cr.Spec.DbSecret.KeyFileMountLocation = utils.OraRacDbKeyFileSecretMount + } + } + + if cr.Spec.TdeWalletSecret != nil && cr.Spec.TdeWalletSecret.Name != "" { + if cr.Spec.TdeWalletSecret.PwdFileMountLocation == "" { + cr.Spec.TdeWalletSecret.PwdFileMountLocation = utils.OraRacTdePwdFileSecretMount + } + if cr.Spec.TdeWalletSecret.KeyFileMountLocation == "" { + cr.Spec.TdeWalletSecret.KeyFileMountLocation = utils.OraRacTdeKeyFileSecretMount + } + } + + if cr.Spec.ConfigParams != nil { + if cr.Spec.ConfigParams.SwMountLocation == "" { + cr.Spec.ConfigParams.SwMountLocation = utils.OraSwLocation + } + + if cr.Spec.ConfigParams.GridResponseFile.ConfigMapName == "" { + if cr.Spec.ConfigParams.CrsAsmDiskDg == "" { + cr.Spec.ConfigParams.CrsAsmDiskDg = "DATA" + } + if cr.Spec.ConfigParams.CrsAsmDiskDgRedundancy == "" { + cr.Spec.ConfigParams.CrsAsmDiskDgRedundancy = "external" + } + } + + if cr.Spec.ConfigParams.DbResponseFile.ConfigMapName == "" { + if cr.Spec.ConfigParams.DbDataFileDestDg == "" { + cr.Spec.ConfigParams.DbDataFileDestDg = cr.Spec.ConfigParams.CrsAsmDiskDg + } + if cr.Spec.ConfigParams.DbRecoveryFileDest == "" { + cr.Spec.ConfigParams.DbRecoveryFileDest = cr.Spec.ConfigParams.DbDataFileDestDg + } + if cr.Spec.ConfigParams.DbCharSet == "" { + cr.Spec.ConfigParams.DbCharSet = "AL32UTF8" + } + } + } + + return nil +} + +// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. +//+kubebuilder:webhook:verbs=create;update;delete,path=/validate-database-oracle-com-v4-oraclerestart,mutating=false,failurePolicy=fail,sideEffects=None,groups=database.oracle.com,resources=oraclerestarts,versions=v4,name=voraclerestart.kb.io,admissionReviewVersions={v1} + +var _ webhook.CustomValidator = &OracleRestart{} + +// ValidateCreate implements webhook.Validator so a webhook will be registered for the type +func (r *OracleRestart) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + cr, ok := obj.(*OracleRestart) + if !ok { + return nil, fmt.Errorf("expected *OracleRestart but got %T", obj) + } + + OracleRestartlog.Info("validate create", "name", cr.Name) + var validationErrs field.ErrorList + var warnings admission.Warnings + + namespaces := utils.GetWatchNamespaces() + _, containsNamespace := namespaces[cr.Namespace] + + if len(namespaces) != 0 && !containsNamespace { + validationErrs = append(validationErrs, + field.Invalid(field.NewPath("metadata").Child("namespace"), cr.Namespace, + "Oracle database operator doesn't watch over this namespace")) + } + + if cr.Spec.Image == "" { + validationErrs = append(validationErrs, + field.Invalid(field.NewPath("spec").Child("image"), cr.Spec.Image, + "image cannot be set to empty")) + } + + validationErrs = append(validationErrs, cr.validateSshSecret()...) + validationErrs = append(validationErrs, cr.validateDbSecret()...) + validationErrs = append(validationErrs, cr.validateTdeSecret()...) + validationErrs = append(validationErrs, cr.validateServiceSpecs()...) + validationErrs = append(validationErrs, cr.validateAsmStorage()...) + validationErrs = append(validationErrs, cr.validateGeneric()...) + + // ASM disk warnings + var deviceWarnings []string + w, errs := cr.validateCrsAsmDeviceListSize() + deviceWarnings = append(deviceWarnings, w...) + validationErrs = append(validationErrs, errs...) + + w, errs = cr.validateDbAsmDeviceList() + deviceWarnings = append(deviceWarnings, w...) + validationErrs = append(validationErrs, errs...) + + w, errs = cr.validateRecoAsmDeviceList() + deviceWarnings = append(deviceWarnings, w...) + validationErrs = append(validationErrs, errs...) + + w, errs = cr.validateRedoAsmDeviceList() + deviceWarnings = append(deviceWarnings, w...) + validationErrs = append(validationErrs, errs...) + + errs = cr.validateRedoAsmDG() + validationErrs = append(validationErrs, errs...) + + errs = cr.validateRecoAsmDG() + validationErrs = append(validationErrs, errs...) + + errs = cr.validateDataAsmDG() + validationErrs = append(validationErrs, errs...) + + errs = cr.validateCrsAsmDG() + validationErrs = append(validationErrs, errs...) + + errs = cr.validateCrsAsmDG() + validationErrs = append(validationErrs, errs...) + + if cr.Spec.ConfigParams != nil { + // CRS + validationErrs = append(validationErrs, + cr.validateAsmRedundancyAndDisks( + cr.Spec.ConfigParams.CrsAsmDeviceList, + cr.Spec.ConfigParams.CrsAsmDiskDgRedundancy, + "crsAsmDeviceList")..., + ) + // DB + validationErrs = append(validationErrs, + cr.validateAsmRedundancyAndDisks( + cr.Spec.ConfigParams.DbAsmDeviceList, + cr.Spec.ConfigParams.DBAsmDiskDgRedundancy, + "dbAsmDeviceList")..., + ) + // RECO + validationErrs = append(validationErrs, + cr.validateAsmRedundancyAndDisks( + cr.Spec.ConfigParams.RecoAsmDeviceList, + cr.Spec.ConfigParams.RecoAsmDiskDgRedundancy, + "recoAsmDeviceList")..., + ) + } + + for _, warning := range deviceWarnings { + warnings = append(warnings, warning) + } + + if len(validationErrs) > 0 { + return warnings, apierrors.NewInvalid( + schema.GroupKind{Group: "database.oracle.com", Kind: "OracleRestart"}, + cr.Name, validationErrs) + } + + return warnings, nil +} + +// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type +func (r *OracleRestart) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { + old, okOld := oldObj.(*OracleRestart) + newCr, okNew := newObj.(*OracleRestart) + if !okOld || !okNew { + return nil, fmt.Errorf("expected *OracleRestart for both old and new objects") + } + + OracleRestartlog.Info("validate update", "name", newCr.Name) + + if newCr.Status.State == "PROVISIONING" || newCr.Status.State == "UPDATING" || newCr.Status.State == "PODAVAILABLE" { + if !reflect.DeepEqual(old.Spec, newCr.Spec) { + return nil, apierrors.NewForbidden( + schema.GroupResource{Group: "database.oracle.com", Resource: "OracleRestart"}, + newCr.Name, fmt.Errorf("updates to Oracle Restart Spec is not allowed while its in state %s", newCr.Status.State)) + } + } + + if old.Spec.DataDgStorageClass != newCr.Spec.DataDgStorageClass { + + return nil, apierrors.NewForbidden( + schema.GroupResource{Group: "database.oracle.com", Resource: "OracleRestart"}, + newCr.Name, fmt.Errorf("updates to the Data storageclass is forbidden: %s", old.Spec.DataDgStorageClass)) + } + + if old.Spec.RecoDgStorageClass != newCr.Spec.RecoDgStorageClass { + + return nil, apierrors.NewForbidden( + schema.GroupResource{Group: "database.oracle.com", Resource: "OracleRestart"}, + newCr.Name, fmt.Errorf("updates to the Reco storageclass is forbidden: %s", old.Spec.RecoDgStorageClass)) + } + + if old.Spec.RedoDgStorageClass != newCr.Spec.RedoDgStorageClass { + + return nil, apierrors.NewForbidden( + schema.GroupResource{Group: "database.oracle.com", Resource: "OracleRestart"}, + newCr.Name, fmt.Errorf("updates to the Redo storageclass is forbidden: %s", old.Spec.RedoDgStorageClass)) + } + + if old.Spec.SwStorageClass != newCr.Spec.SwStorageClass { + + return nil, apierrors.NewForbidden( + schema.GroupResource{Group: "database.oracle.com", Resource: "OracleRestart"}, + newCr.Name, fmt.Errorf("updates to the Swstorageclass is forbidden: %s", old.Spec.SwStorageClass)) + } + + if old.Spec.CrsDgStorageClass != newCr.Spec.CrsDgStorageClass { + + return nil, apierrors.NewForbidden( + schema.GroupResource{Group: "database.oracle.com", Resource: "OracleRestart"}, + newCr.Name, fmt.Errorf("updates to the CrsDgStorageClass is forbidden: %s", old.Spec.CrsDgStorageClass)) + } + + if newCr.Spec.InstDetails.SwLocStorageSizeInGb < old.Spec.InstDetails.SwLocStorageSizeInGb { + + return nil, apierrors.NewForbidden( + schema.GroupResource{Group: "database.oracle.com", Resource: "OracleRestart"}, + newCr.Name, fmt.Errorf("SwLocStorageSizeInGb Storage size shrink is not allowed. Old value : %d and New value: %d. ", old.Spec.InstDetails.SwLocStorageSizeInGb, newCr.Spec.InstDetails.SwLocStorageSizeInGb)) + } + + isDiskChanged := !reflect.DeepEqual(old.Spec.AsmStorageDetails.DisksBySize, newCr.Spec.AsmStorageDetails.DisksBySize) + if isDiskChanged { + if old.Spec.ConfigParams.HostSwStageLocation != newCr.Spec.ConfigParams.HostSwStageLocation || + old.Spec.ConfigParams.GridSwZipFile != newCr.Spec.ConfigParams.GridSwZipFile || + old.Spec.ConfigParams.DbSwZipFile != newCr.Spec.ConfigParams.DbSwZipFile || + old.Spec.Image != newCr.Spec.Image { + + return nil, apierrors.NewForbidden( + schema.GroupResource{Group: "database.oracle.com", Resource: "OracleRestart"}, + newCr.Name, fmt.Errorf("updates to the following fields are not allowed during ASM disk updates: %v", []string{"hostSwStageLocation", "gridSwZipFile", "dbSwZipFile", "image"})) + } + } + + var validationErrs field.ErrorList + + // Re-use create validations on update + warnings, err := r.ValidateCreate(ctx, newObj) + if err != nil { + return warnings, err + } + + // ValidateDelete logic if being deleted + if newCr.GetDeletionTimestamp() != nil { + warnings, err := r.ValidateDelete(ctx, newObj) + if err != nil { + return warnings, err + } + } + + // Skip if only metadata is changing + if reflect.DeepEqual(old.Spec, newCr.Spec) && reflect.DeepEqual(old.Status, newCr.Status) { + return nil, nil + } + + validationErrs = append(validationErrs, newCr.validateUpdateSshSecret(old)...) + validationErrs = append(validationErrs, newCr.validateUpdateDbSecret(old)...) + validationErrs = append(validationErrs, newCr.validateUpdateTdeSecret(old)...) + validationErrs = append(validationErrs, newCr.validateUpdateServiceSpecs(old)...) + validationErrs = append(validationErrs, newCr.validateUpdateAsmStorage(old)...) + validationErrs = append(validationErrs, newCr.validateUpdateGeneric(old)...) + + if old.Spec.ConfigParams != nil && newCr.Spec.ConfigParams != nil { + + // CRS + if err := validateRedundancyOnUpdate(old.Spec.ConfigParams.CrsAsmDiskDgRedundancy, newCr.Spec.ConfigParams.CrsAsmDiskDgRedundancy, "crsAsmDiskDgRedundancy"); err != nil { + validationErrs = append(validationErrs, err) + } + // DB + if err := validateRedundancyOnUpdate(old.Spec.ConfigParams.DBAsmDiskDgRedundancy, newCr.Spec.ConfigParams.DBAsmDiskDgRedundancy, "dbAsmDiskDgRedundancy"); err != nil { + validationErrs = append(validationErrs, err) + } + // RECO + if err := validateRedundancyOnUpdate(old.Spec.ConfigParams.RecoAsmDiskDgRedundancy, newCr.Spec.ConfigParams.RecoAsmDiskDgRedundancy, "recoAsmDiskDgRedundancy"); err != nil { + validationErrs = append(validationErrs, err) + } + } + + if old.Spec.AsmStorageDetails != nil && newCr.Spec.AsmStorageDetails != nil { + errs := validateAsmNoDiskResize( + old.Spec.AsmStorageDetails.DisksBySize, + newCr.Spec.AsmStorageDetails.DisksBySize, + field.NewPath("spec").Child("asmStorageDetails").Child("disksBySize"), + ) + validationErrs = append(validationErrs, errs...) + } + + if len(validationErrs) > 0 { + return nil, apierrors.NewInvalid( + schema.GroupKind{Group: "database.oracle.com", Kind: "OracleRestart"}, + newCr.Name, validationErrs) + } + + return nil, nil +} + +func redundancyChanged(oldRed, newRed string) bool { + // Normalize and compare (case-insensitive, trim spaces) + return strings.ToUpper(strings.TrimSpace(oldRed)) != strings.ToUpper(strings.TrimSpace(newRed)) +} + +func validateRedundancyOnUpdate( + oldRed, newRed, fieldName string, +) *field.Error { + normalizedOld := strings.ToUpper(strings.TrimSpace(oldRed)) + normalizedNew := strings.ToUpper(strings.TrimSpace(newRed)) + + if normalizedOld == "" { + // Old value not set, treat as EXTERNAL only + if normalizedNew != "" && normalizedNew != "EXTERNAL" { + return field.Invalid( + field.NewPath("spec").Child("configParams").Child(fieldName), + newRed, + "Redundancy was not set before (treated as EXTERNAL); it can only be EXTERNAL now.", + ) + } + } else { + // Old value set; do not allow changes + if redundancyChanged(normalizedOld, normalizedNew) { + return field.Invalid( + field.NewPath("spec").Child("configParams").Child(fieldName), + newRed, + fmt.Sprintf("Changing redundancy value for ASM diskgroup (%s) is not allowed on update.", fieldName), + ) + } + } + return nil +} + +func validateAsmNoDiskResize( + oldDisks, newDisks []DiskBySize, + fieldPath *field.Path, +) field.ErrorList { + var errs field.ErrorList + + // Build map of disk name -> size for old and new + oldDiskMap := make(map[string]int) + for _, disk := range oldDisks { + for _, name := range disk.DiskNames { + oldDiskMap[name] = disk.StorageSizeInGb + } + } + newDiskMap := make(map[string]int) + for _, disk := range newDisks { + for _, name := range disk.DiskNames { + newDiskMap[name] = disk.StorageSizeInGb + } + } + + // For disks present in both old and new, size must not change + for name, oldSize := range oldDiskMap { + if newSize, exists := newDiskMap[name]; exists { + if newSize != oldSize { + errs = append(errs, field.Invalid( + fieldPath, + name, + fmt.Sprintf("cannot change size of existing ASM disk %s (old: %dGB, new: %dGB)", name, oldSize, newSize), + )) + } + } + } + return errs +} + +func (r *OracleRestart) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + cr, ok := obj.(*OracleRestart) + if !ok { + return nil, fmt.Errorf("expected *OracleRestart but got %T", obj) + } + + OracleRestartlog.Info("validate delete", "name", cr.Name) + + // TODO: Add any deletion-specific logic if required + return nil, nil +} + +//========== User Functions to check the fields ========== + +func (r *OracleRestart) validateSshSecret() field.ErrorList { + var validationErrs field.ErrorList + sshPath := field.NewPath("spec").Child("SshKeySecret") + + if r.Spec.SshKeySecret == nil { + validationErrs = append(validationErrs, + field.Required(sshPath, "SshKeySecret must be specified")) + return validationErrs + } + + if r.Spec.SshKeySecret.Name == "" { + validationErrs = append(validationErrs, + field.Required(sshPath.Child("Name"), "SshKeySecret.Name cannot be empty")) + } + if r.Spec.SshKeySecret.PrivKeySecretName == "" { + validationErrs = append(validationErrs, + field.Required(sshPath.Child("PrivKeySecretName"), "PrivKeySecretName cannot be empty")) + } + if r.Spec.SshKeySecret.PubKeySecretName == "" { + validationErrs = append(validationErrs, + field.Required(sshPath.Child("PubKeySecretName"), "PubKeySecretName cannot be empty")) + } + + return validationErrs +} +func (r *OracleRestart) validateDbSecret() field.ErrorList { + var validationErrs field.ErrorList + dbPath := field.NewPath("spec").Child("DbSecret") + + if r.Spec.DbSecret.Name != "" && strings.ToLower(r.Spec.DbSecret.EncryptionType) != "base64" { + if r.Spec.DbSecret.KeyFileName == "" { + validationErrs = append(validationErrs, + field.Required(dbPath.Child("KeyFileName"), "KeyFileName cannot be empty when encryptionType is not 'base64'")) + } + if r.Spec.DbSecret.PwdFileName == "" { + validationErrs = append(validationErrs, + field.Required(dbPath.Child("PwdFileName"), "PwdFileName cannot be empty when encryptionType is not 'base64'")) + } + } + + return validationErrs +} +func (r *OracleRestart) validateTdeSecret() field.ErrorList { + var validationErrs field.ErrorList + tdePath := field.NewPath("spec").Child("TdeWalletSecret") + + if r.Spec.TdeWalletSecret != nil && + r.Spec.TdeWalletSecret.Name != "" && + strings.ToLower(r.Spec.TdeWalletSecret.EncryptionType) != "base64" { + + if r.Spec.TdeWalletSecret.KeyFileName == "" { + validationErrs = append(validationErrs, + field.Required(tdePath.Child("KeyFileName"), "KeyFileName cannot be empty when encryptionType is not 'base64'")) + } + if r.Spec.TdeWalletSecret.PwdFileName == "" { + validationErrs = append(validationErrs, + field.Required(tdePath.Child("PwdFileName"), "PwdFileName cannot be empty when encryptionType is not 'base64'")) + } + } + + return validationErrs +} +func (r *OracleRestart) validateServiceSpecs() field.ErrorList { + var validationErrs field.ErrorList + svcPath := field.NewPath("spec").Child("ServiceDetails") + + svc := r.Spec.ServiceDetails + if svc.Name == "" { + return nil + } + + if svc.Cardinality != "" { + if len(svc.Preferred) > 0 { + validationErrs = append(validationErrs, + field.Invalid(svcPath.Child("Preferred"), svc.Preferred, + "Preferred cannot be used with Cardinality. Use one or the other.")) + } + if len(svc.Available) > 0 { + validationErrs = append(validationErrs, + field.Invalid(svcPath.Child("Available"), svc.Available, + "Available cannot be used with Cardinality. Use one or the other.")) + } + if !utils.CheckStringInList(svc.Cardinality, utils.GetServiceCardinality()) { + validationErrs = append(validationErrs, + field.NotSupported(svcPath.Child("Cardinality"), svc.Cardinality, utils.GetServiceCardinality())) + } + } + + if svc.TafPolicy != "" && !utils.CheckStringInList(svc.TafPolicy, utils.GetTafPolicy()) { + validationErrs = append(validationErrs, + field.NotSupported(svcPath.Child("TafPolicy"), svc.TafPolicy, utils.GetTafPolicy())) + } + + if svc.FailOverType != "" && !utils.CheckStringInList(svc.FailOverType, utils.GetServiceFailoverType()) { + validationErrs = append(validationErrs, + field.NotSupported(svcPath.Child("FailOverType"), svc.FailOverType, utils.GetServiceFailoverType())) + } + + if svc.Role != "" && !utils.CheckStringInList(svc.Role, utils.GetServiceRole()) { + validationErrs = append(validationErrs, + field.NotSupported(svcPath.Child("Role"), svc.Role, utils.GetServiceRole())) + } + + return validationErrs +} +func (r *OracleRestart) validateAsmStorage() field.ErrorList { + var validationErrs field.ErrorList + asmPath := field.NewPath("spec").Child("AsmStorageDetails") + + if r.Spec.AsmStorageDetails == nil { + validationErrs = append(validationErrs, + field.Required(asmPath, "ASM storage details must be provided")) + return validationErrs + } + + if len(r.Spec.AsmStorageDetails.DisksBySize) == 0 { + validationErrs = append(validationErrs, + field.Invalid(asmPath.Child("DisksBySize"), r.Spec.AsmStorageDetails.DisksBySize, + "At least one disk size group must be defined")) + } else { + for i, group := range r.Spec.AsmStorageDetails.DisksBySize { + if len(group.DiskNames) == 0 { + validationErrs = append(validationErrs, + field.Invalid(asmPath.Child("DisksBySize").Index(i).Child("DiskNames"), group.DiskNames, + "Each disk size group must have at least one disk name")) + } + } + } + + // Check ASM disks are not duplicate + if !r.validateAsmDiskUnqiueNames() { + validationErrs = append(validationErrs, + field.Invalid(asmPath.Child("DisksBySize"), asmPath, "Each ASM disk must be unique")) + + } + + return validationErrs +} + +func (r *OracleRestart) validateAsmDiskUnqiueNames() bool { + + seenDisks := make(map[string]bool) //store encounterednames + for _, disks := range r.Spec.AsmStorageDetails.DisksBySize { + for _, diskPath := range disks.DiskNames { + if seenDisks[diskPath] { + return false + } + seenDisks[diskPath] = true // disk is seen + } + } + + return true +} + +func (r *OracleRestart) validateGeneric() field.ErrorList { + var validationErrs field.ErrorList + + if !utils.CheckStatusFlag(r.Spec.InstDetails.IsDelete) { + isAlphanumeric := regexp.MustCompile(`^[a-zA-Z0-9]*$`).MatchString(r.Spec.InstDetails.Name) + if !isAlphanumeric { + validationErrs = append(validationErrs, + field.Invalid(field.NewPath("spec").Child("InstDetails").Child("Name"), r.Spec.InstDetails.Name, + "Name must contain only alphanumeric characters")) + } + + if r.Spec.InstDetails.HostSwLocation == "" && r.Spec.SwStorageClass == "" { + validationErrs = append(validationErrs, + field.Invalid(field.NewPath("spec").Child("InstDetails").Child("HostSwLocation"), r.Spec.InstDetails.HostSwLocation, + "Either HostSwLocation or SwStorageClass must be specified")) + } + } + + if r.Spec.SwStorageClass != "" { + if r.Spec.InstDetails.SwLocStorageSizeInGb < 60 { + validationErrs = append(validationErrs, + field.Invalid(field.NewPath("spec").Child("InstDetails").Child("SwLocStorageSizeInGb"), r.Spec.InstDetails.SwLocStorageSizeInGb, + "SwLocStorageSizeInGb must be greater than 60GB")) + } + } + + if r.Spec.ConfigParams == nil { + validationErrs = append(validationErrs, + field.Invalid(field.NewPath("spec").Child("ConfigParams"), r.Spec.ConfigParams, + "ConfigParams cannot be empty")) + return validationErrs + } + + cfg := r.Spec.ConfigParams + cfgPath := field.NewPath("spec").Child("ConfigParams") + + // Grid Response File validation + if cfg.GridResponseFile.ConfigMapName != "" { + if cfg.GridResponseFile.Name == "" { + validationErrs = append(validationErrs, + field.Invalid(cfgPath.Child("GridResponseFile").Child("Name"), cfg.GridResponseFile.Name, + "GridResponseFile name cannot be empty")) + } + + for _, fieldVal := range []struct { + name string + value string + }{ + {"Inventory", cfg.Inventory}, + {"CrsAsmDeviceList", cfg.CrsAsmDeviceList}, + {"GridBase", cfg.GridBase}, + {"CrsAsmDiskDg", cfg.CrsAsmDiskDg}, + {"CrsAsmDiskDgRedundancy", cfg.CrsAsmDiskDgRedundancy}, + } { + if fieldVal.value != "" { + validationErrs = append(validationErrs, + field.Invalid(cfgPath.Child(fieldVal.name), fieldVal.value, + fmt.Sprintf("%s cannot be used when GridResponseFile is set", fieldVal.name))) + } + } + } else { + if cfg.GridBase == "" { + validationErrs = append(validationErrs, + field.Invalid(cfgPath.Child("GridBase"), cfg.GridBase, "GridBase cannot be empty")) + } + if cfg.GridHome == "" { + validationErrs = append(validationErrs, + field.Invalid(cfgPath.Child("GridHome"), cfg.GridHome, "GridHome cannot be empty")) + } + if cfg.Inventory == "" { + validationErrs = append(validationErrs, + field.Invalid(cfgPath.Child("Inventory"), cfg.Inventory, "Inventory cannot be empty")) + } + if cfg.CrsAsmDeviceList == "" { + validationErrs = append(validationErrs, + field.Invalid(cfgPath.Child("CrsAsmDeviceList"), cfg.CrsAsmDeviceList, "CrsAsmDeviceList cannot be empty")) + } + } + + // DB Response File validation + if cfg.DbResponseFile.ConfigMapName != "" { + if cfg.DbResponseFile.Name == "" { + validationErrs = append(validationErrs, + field.Invalid(cfgPath.Child("DbResponseFile").Child("Name"), cfg.DbResponseFile.Name, + "DbResponseFile name cannot be empty")) + } + + for _, fieldVal := range []struct { + name string + value string + }{ + {"DbCharSet", cfg.DbCharSet}, + {"DbConfigType", cfg.DbConfigType}, + {"DbRedoFileSize", cfg.DbRedoFileSize}, + {"DbType", cfg.DbType}, + {"DbUniqueName", cfg.DbUniqueName}, + {"DbStorageType", cfg.DbStorageType}, + {"DbName", cfg.DbName}, + } { + if fieldVal.value != "" { + validationErrs = append(validationErrs, + field.Invalid(cfgPath.Child(fieldVal.name), fieldVal.value, + fmt.Sprintf("%s cannot be used when DbResponseFile is set", fieldVal.name))) + } + } + } else { + if cfg.DbBase == "" { + validationErrs = append(validationErrs, + field.Invalid(cfgPath.Child("DbBase"), cfg.DbBase, "DbBase cannot be empty")) + } + if cfg.DbHome == "" { + validationErrs = append(validationErrs, + field.Invalid(cfgPath.Child("DbHome"), cfg.DbHome, "DbHome cannot be empty")) + } + if cfg.DbName == "" { + validationErrs = append(validationErrs, + field.Invalid(cfgPath.Child("DbName"), cfg.DbName, "DbName cannot be empty")) + } + } + + if cfg.GridSwZipFile == "" { + validationErrs = append(validationErrs, + field.Invalid(cfgPath.Child("GridSwZipFile"), cfg.GridSwZipFile, + "GridSwZipFile cannot be empty")) + } + if cfg.DbSwZipFile == "" { + validationErrs = append(validationErrs, + field.Invalid(cfgPath.Child("DbSwZipFile"), cfg.DbSwZipFile, + "DbSwZipFile cannot be empty")) + } + + if cfg.HostSwStageLocation == "" && r.Spec.SwStorageClass == "" { + validationErrs = append(validationErrs, + field.Invalid(cfgPath.Child("HostSwStageLocation"), cfg.HostSwStageLocation, + "Either HostSwStageLocation or SwDgStorageClass must be specified")) + } + + if r.Spec.ConfigParams.RuPatchLocation != "" { + _, isPVCKey := r.Spec.InstDetails.PvcName[r.Spec.ConfigParams.RuPatchLocation] + if !isPVCKey { + // Not found in PVC map, treat as direct path — validate format + if !strings.HasPrefix(r.Spec.ConfigParams.RuPatchLocation, "/") { + validationErrs = append(validationErrs, + field.Invalid( + field.NewPath("spec").Child("configParams").Child("ruPatchLocation"), + r.Spec.ConfigParams.RuPatchLocation, + "ruPatchLocation must be either a key in instDetails.pvcName or an absolute path starting with '/'")) + } + } + } + + if r.Spec.Image == "" { + validationErrs = append(validationErrs, + field.Invalid(field.NewPath("spec").Child("Image"), r.Spec.Image, + "Image cannot be empty")) + } + + return validationErrs +} + +//========================== Validate inital specs check ends here ================ + +// =========================== Update specs checks block begin Here ======================= + +func (r *OracleRestart) validateUpdateGeneric(old *OracleRestart) field.ErrorList { + var validationErrs field.ErrorList + + check := func(path *field.Path, oldVal, newVal string) { + if oldVal != "" && newVal != "" && !strings.EqualFold(oldVal, newVal) { + validationErrs = append(validationErrs, field.Forbidden(path, path.String()+" cannot be changed post creation")) + } + } + + if r.Spec.ConfigParams != nil && old.Spec.ConfigParams != nil { + cpPath := field.NewPath("spec", "ConfigParams") + check(cpPath.Child("DbName"), old.Spec.ConfigParams.DbName, r.Spec.ConfigParams.DbName) + check(cpPath.Child("GridBase"), old.Spec.ConfigParams.GridBase, r.Spec.ConfigParams.GridBase) + check(cpPath.Child("GridHome"), old.Spec.ConfigParams.GridHome, r.Spec.ConfigParams.GridHome) + check(cpPath.Child("DbBase"), old.Spec.ConfigParams.DbBase, r.Spec.ConfigParams.DbBase) + check(cpPath.Child("DbHome"), old.Spec.ConfigParams.DbHome, r.Spec.ConfigParams.DbHome) + check(cpPath.Child("CrsAsmDiskDg"), old.Spec.ConfigParams.CrsAsmDiskDg, r.Spec.ConfigParams.CrsAsmDiskDg) + check(cpPath.Child("CrsAsmDiskDgRedundancy"), old.Spec.ConfigParams.CrsAsmDiskDgRedundancy, r.Spec.ConfigParams.CrsAsmDiskDgRedundancy) + check(cpPath.Child("DBAsmDiskDgRedundancy"), old.Spec.ConfigParams.DBAsmDiskDgRedundancy, r.Spec.ConfigParams.DBAsmDiskDgRedundancy) + check(cpPath.Child("DbCharSet"), old.Spec.ConfigParams.DbCharSet, r.Spec.ConfigParams.DbCharSet) + check(cpPath.Child("DbConfigType"), old.Spec.ConfigParams.DbConfigType, r.Spec.ConfigParams.DbConfigType) + check(cpPath.Child("DbDataFileDestDg"), old.Spec.ConfigParams.DbDataFileDestDg, r.Spec.ConfigParams.DbDataFileDestDg) + check(cpPath.Child("DbUniqueName"), old.Spec.ConfigParams.DbUniqueName, r.Spec.ConfigParams.DbUniqueName) + check(cpPath.Child("DbRecoveryFileDest"), old.Spec.ConfigParams.DbRecoveryFileDest, r.Spec.ConfigParams.DbRecoveryFileDest) + check(cpPath.Child("DbRedoFileSize"), old.Spec.ConfigParams.DbRedoFileSize, r.Spec.ConfigParams.DbRedoFileSize) + check(cpPath.Child("DbStorageType"), old.Spec.ConfigParams.DbStorageType, r.Spec.ConfigParams.DbStorageType) + check(cpPath.Child("DbSwZipFile"), old.Spec.ConfigParams.DbSwZipFile, r.Spec.ConfigParams.DbSwZipFile) + check(cpPath.Child("GridSwZipFile"), old.Spec.ConfigParams.GridSwZipFile, r.Spec.ConfigParams.GridSwZipFile) + + // Nested response files + check(cpPath.Child("GridResponseFile", "ConfigMapName"), old.Spec.ConfigParams.GridResponseFile.ConfigMapName, r.Spec.ConfigParams.GridResponseFile.ConfigMapName) + check(cpPath.Child("GridResponseFile", "Name"), old.Spec.ConfigParams.GridResponseFile.Name, r.Spec.ConfigParams.GridResponseFile.Name) + check(cpPath.Child("DbResponseFile", "ConfigMapName"), old.Spec.ConfigParams.DbResponseFile.ConfigMapName, r.Spec.ConfigParams.DbResponseFile.ConfigMapName) + check(cpPath.Child("DbResponseFile", "Name"), old.Spec.ConfigParams.DbResponseFile.Name, r.Spec.ConfigParams.DbResponseFile.Name) + } + + return validationErrs +} + +func (r *OracleRestart) validateUpdateServiceSpecs(old *OracleRestart) field.ErrorList { + var validationErrs field.ErrorList + + check := func(path *field.Path, oldVal, newVal string) { + if oldVal != "" && newVal != "" && !strings.EqualFold(oldVal, newVal) { + validationErrs = append(validationErrs, field.Forbidden(path, path.String()+" cannot be changed post creation")) + } + } + + sdPath := field.NewPath("spec", "ServiceDetail") + + check(sdPath.Child("Name"), old.Status.ServiceDetails.Name, r.Spec.ServiceDetails.Name) + check(sdPath.Child("Cardinality"), old.Status.ServiceDetails.Cardinality, r.Spec.ServiceDetails.Cardinality) + check(sdPath.Child("Notification"), old.Status.ServiceDetails.Notification, r.Spec.ServiceDetails.Notification) + check(sdPath.Child("ClbGoal"), old.Status.ServiceDetails.ClbGoal, r.Spec.ServiceDetails.ClbGoal) + check(sdPath.Child("CommitOutCome"), old.Status.ServiceDetails.CommitOutCome, r.Spec.ServiceDetails.CommitOutCome) + check(sdPath.Child("CommitOutComeFastPath"), old.Status.ServiceDetails.CommitOutComeFastPath, r.Spec.ServiceDetails.CommitOutComeFastPath) + check(sdPath.Child("Dtp"), old.Status.ServiceDetails.Dtp, r.Spec.ServiceDetails.Dtp) + check(sdPath.Child("SessionState"), old.Status.ServiceDetails.SessionState, r.Spec.ServiceDetails.SessionState) + check(sdPath.Child("Edition"), old.Status.ServiceDetails.Edition, r.Spec.ServiceDetails.Edition) + check(sdPath.Child("FailBack"), old.Status.ServiceDetails.FailBack, r.Spec.ServiceDetails.FailBack) + check(sdPath.Child("FailOverRestore"), old.Status.ServiceDetails.FailOverRestore, r.Spec.ServiceDetails.FailOverRestore) // ✅ Fixed error message + check(sdPath.Child("FailOverType"), old.Status.ServiceDetails.FailOverType, r.Spec.ServiceDetails.FailOverType) + check(sdPath.Child("TafPolicy"), old.Status.ServiceDetails.TafPolicy, r.Spec.ServiceDetails.TafPolicy) + check(sdPath.Child("RlbGoal"), old.Status.ServiceDetails.RlbGoal, r.Spec.ServiceDetails.RlbGoal) + check(sdPath.Child("Role"), old.Status.ServiceDetails.Role, r.Spec.ServiceDetails.Role) + check(sdPath.Child("Pdb"), old.Status.ServiceDetails.Pdb, r.Spec.ServiceDetails.Pdb) + + return validationErrs +} + +func (r *OracleRestart) validateUpdateAsmStorage(old *OracleRestart) field.ErrorList { + var validationErrs field.ErrorList + // Add actual validation logic here if needed + if !strings.EqualFold(old.Spec.CrsDgStorageClass, r.Spec.CrsDgStorageClass) { + validationErrs = append(validationErrs, + field.Invalid(field.NewPath("spec").Child("CrsDgStorageClass"), + r.Spec.CrsDgStorageClass, "CrsDgStorageClass cannot be changed post creation")) + } + + if !strings.EqualFold(old.Spec.CrsDgStorageClass, r.Spec.CrsDgStorageClass) { + validationErrs = append(validationErrs, + field.Invalid(field.NewPath("spec").Child("CrsDgStorageClass"), + r.Spec.CrsDgStorageClass, "CrsDgStorageClass cannot be changed post creation")) + } + + if !strings.EqualFold(old.Spec.DataDgStorageClass, r.Spec.DataDgStorageClass) { + validationErrs = append(validationErrs, + field.Invalid(field.NewPath("spec").Child("DataDgStorageClass"), + r.Spec.CrsDgStorageClass, "DataDgStorageClass cannot be changed post creation")) + } + + if !strings.EqualFold(old.Spec.RedoDgStorageClass, r.Spec.RedoDgStorageClass) { + validationErrs = append(validationErrs, + field.Invalid(field.NewPath("spec").Child("RedoDgStorageClass"), + r.Spec.CrsDgStorageClass, "RedoDgStorageClass cannot be changed post creation")) + } + + if !strings.EqualFold(old.Spec.RecoDgStorageClass, r.Spec.RecoDgStorageClass) { + validationErrs = append(validationErrs, + field.Invalid(field.NewPath("spec").Child("RecoDgStorageClass"), + r.Spec.CrsDgStorageClass, "RecoDgStorageClass cannot be changed post creation")) + } + + return validationErrs +} + +func (r *OracleRestart) validateUpdateDbSecret(old *OracleRestart) field.ErrorList { + var validationErrs field.ErrorList + + if r.Spec.DbSecret != nil && old.Status.DbSecret != nil { + if r.Spec.DbSecret.Name != "" && old.Status.DbSecret.Name != "" && + !strings.EqualFold(old.Status.DbSecret.Name, r.Spec.DbSecret.Name) { + validationErrs = append(validationErrs, + field.Forbidden(field.NewPath("spec").Child("DbSecret").Child("Name"), + "DbSecret name cannot be changed post creation")) + } + + if r.Spec.DbSecret.KeyFileName != "" && old.Status.DbSecret.KeyFileName != "" && + !strings.EqualFold(old.Status.DbSecret.KeyFileName, r.Spec.DbSecret.KeyFileName) { + validationErrs = append(validationErrs, + field.Invalid(field.NewPath("spec").Child("DbSecret").Child("KeyFileName"), + r.Spec.DbSecret.KeyFileName, "KeyFileName cannot be changed post creation")) + } + + if r.Spec.DbSecret.PwdFileName != "" && old.Status.DbSecret.PwdFileName != "" && + !strings.EqualFold(old.Status.DbSecret.PwdFileName, r.Spec.DbSecret.PwdFileName) { + validationErrs = append(validationErrs, + field.Invalid(field.NewPath("spec").Child("DbSecret").Child("PwdFileName"), + r.Spec.DbSecret.PwdFileName, "PwdFileName cannot be changed post creation")) + } + } + + return validationErrs +} + +func (r *OracleRestart) validateUpdateTdeSecret(old *OracleRestart) field.ErrorList { + var validationErrs field.ErrorList + + if r.Spec.TdeWalletSecret != nil && old.Status.TdeWalletSecret != nil { + if r.Spec.TdeWalletSecret.Name != "" && old.Status.TdeWalletSecret.Name != "" && + !strings.EqualFold(old.Status.TdeWalletSecret.Name, r.Spec.TdeWalletSecret.Name) { + validationErrs = append(validationErrs, + field.Forbidden(field.NewPath("spec").Child("TdeWalletSecret").Child("Name"), + "TdeWalletSecret name cannot be changed post creation")) + } + + if r.Spec.TdeWalletSecret.KeyFileName != "" && old.Status.TdeWalletSecret.KeyFileName != "" && + !strings.EqualFold(old.Status.TdeWalletSecret.KeyFileName, r.Spec.TdeWalletSecret.KeyFileName) { + validationErrs = append(validationErrs, + field.Invalid(field.NewPath("spec").Child("TdeWalletSecret").Child("KeyFileName"), + r.Spec.TdeWalletSecret.KeyFileName, "KeyFileName cannot be changed post creation")) + } + + if r.Spec.TdeWalletSecret.PwdFileName != "" && old.Status.TdeWalletSecret.PwdFileName != "" && + !strings.EqualFold(old.Status.TdeWalletSecret.PwdFileName, r.Spec.TdeWalletSecret.PwdFileName) { + validationErrs = append(validationErrs, + field.Invalid(field.NewPath("spec").Child("TdeWalletSecret").Child("PwdFileName"), + r.Spec.TdeWalletSecret.PwdFileName, "PwdFileName cannot be changed post creation")) + } + } + + return validationErrs +} + +func (r *OracleRestart) validateUpdateSshSecret(old *OracleRestart) field.ErrorList { + var validationErrs field.ErrorList + + if r.Spec.SshKeySecret != nil && old.Status.SshKeySecret != nil { + if r.Spec.SshKeySecret.Name != "" && old.Status.SshKeySecret.Name != "" && + !strings.EqualFold(old.Status.SshKeySecret.Name, r.Spec.SshKeySecret.Name) { + validationErrs = append(validationErrs, + field.Forbidden(field.NewPath("spec").Child("SshKeySecret").Child("Name"), + "SshKeySecret name cannot be changed post creation")) + } + + if r.Spec.SshKeySecret.PrivKeySecretName != "" && old.Status.SshKeySecret.PrivKeySecretName != "" && + !strings.EqualFold(old.Status.SshKeySecret.PrivKeySecretName, r.Spec.SshKeySecret.PrivKeySecretName) { + validationErrs = append(validationErrs, + field.Invalid(field.NewPath("spec").Child("SshKeySecret").Child("PrivKeySecretName"), + r.Spec.SshKeySecret.PrivKeySecretName, "PrivKeySecretName cannot be changed post creation")) + } + + if r.Spec.SshKeySecret.PubKeySecretName != "" && old.Status.SshKeySecret.PubKeySecretName != "" && + !strings.EqualFold(old.Status.SshKeySecret.PubKeySecretName, r.Spec.SshKeySecret.PubKeySecretName) { + validationErrs = append(validationErrs, + field.Invalid(field.NewPath("spec").Child("SshKeySecret").Child("PubKeySecretName"), + r.Spec.SshKeySecret.PubKeySecretName, "PubKeySecretName cannot be changed post creation")) + } + } + + return validationErrs +} + +func (r *OracleRestart) validateAsmDeviceList(deviceListStr, deviceListName string) ([]string, field.ErrorList) { + var warnings []string + var validationErrs field.ErrorList + + // Skip validation if the device list is empty or not provided + if deviceListStr == "" { + return warnings, validationErrs + } + + if r.Spec.AsmStorageDetails == nil { + validationErrs = append(validationErrs, + field.Required(field.NewPath("spec").Child("AsmStorageDetails"), + "ASM storage details must be provided when device list is specified")) + return warnings, validationErrs + } + + deviceList := strings.Split(deviceListStr, ",") + var sizeGroup string // Placeholder for the expected storage size as string + + for _, device := range deviceList { + found := false + for _, diskBySize := range r.Spec.AsmStorageDetails.DisksBySize { + // Check if the device exists in the current size group + if contains(diskBySize.DiskNames, device) { + // Check for storage size mismatch + if sizeGroup == "" { + // Set the expected size group on first match + sizeGroup = fmt.Sprintf("%d", diskBySize.StorageSizeInGb) + } else if sizeGroup != fmt.Sprintf("%d", diskBySize.StorageSizeInGb) { + // Add warning for size mismatch + warnings = append(warnings, + fmt.Sprintf("Disk %s in %s is not of the same storage size as others (%s GB expected, but found %s GB)", + device, deviceListName, sizeGroup, fmt.Sprintf("%d", diskBySize.StorageSizeInGb))) + } + found = true + break + } + } + // Error if a device in the list is not found in any DisksBySize group + if !found { + validationErrs = append(validationErrs, + field.Invalid(field.NewPath("spec").Child("AsmStorageDetails").Child(deviceListName), device, + fmt.Sprintf("Disk %s not found in any storage size group", device))) + } + } + + return warnings, validationErrs +} + +func (r *OracleRestart) validateCrsAsmDeviceListSize() ([]string, field.ErrorList) { + var warnings []string + var validationErrs field.ErrorList + + if r.Spec.ConfigParams == nil || r.Spec.ConfigParams.CrsAsmDeviceList == "" { + return warnings, validationErrs + } + + return r.validateAsmDeviceList(r.Spec.ConfigParams.CrsAsmDeviceList, "CrsAsmDeviceList") +} + +func (r *OracleRestart) validateDbAsmDeviceList() ([]string, field.ErrorList) { + var warnings []string + var validationErrs field.ErrorList + + if r.Spec.ConfigParams == nil || r.Spec.ConfigParams.DbAsmDeviceList == "" { + return warnings, validationErrs + } + + return r.validateAsmDeviceList(r.Spec.ConfigParams.DbAsmDeviceList, "DbAsmDeviceList") +} + +func (r *OracleRestart) validateRecoAsmDeviceList() ([]string, field.ErrorList) { + var warnings []string + var validationErrs field.ErrorList + + if r.Spec.ConfigParams == nil || r.Spec.ConfigParams.RecoAsmDeviceList == "" { + return warnings, validationErrs + } + + return r.validateAsmDeviceList(r.Spec.ConfigParams.RecoAsmDeviceList, "RecoAsmDeviceList") +} + +func (r *OracleRestart) validateRedoAsmDeviceList() ([]string, field.ErrorList) { + var warnings []string + var validationErrs field.ErrorList + + if r.Spec.ConfigParams == nil || r.Spec.ConfigParams.RedoAsmDeviceList == "" { + return warnings, validationErrs + } + + return r.validateAsmDeviceList(r.Spec.ConfigParams.RedoAsmDeviceList, "RedoAsmDeviceList") +} + +func (r *OracleRestart) validateRedoAsmDG() field.ErrorList { + var validationErrs field.ErrorList + + if r.Spec.RedoDgStorageClass != "" { + if r.Spec.ConfigParams == nil || r.Spec.ConfigParams.RedoAsmDeviceList == "" { + validationErrs = append(validationErrs, + field.Invalid(field.NewPath("spec").Child("RedoDgStorageClass"), r.Spec.RedoDgStorageClass, fmt.Sprintf("Redo ASM diskgroup storageclass set but Spec.ConfigParams.RedoAsmDeviceList is set to empty"))) + return validationErrs + } + } + return nil +} + +func (r *OracleRestart) validateRecoAsmDG() field.ErrorList { + var validationErrs field.ErrorList + + if r.Spec.RecoDgStorageClass != "" { + if r.Spec.ConfigParams == nil || r.Spec.ConfigParams.RecoAsmDeviceList == "" { + validationErrs = append(validationErrs, + field.Invalid(field.NewPath("spec").Child("RecoDgStorageClass"), r.Spec.RecoDgStorageClass, fmt.Sprintf("Reco ASM diskgroup storageclass set but Spec.ConfigParams.RecoAsmDeviceList is set to empty"))) + return validationErrs + } + } + return nil +} + +func (r *OracleRestart) validateDataAsmDG() field.ErrorList { + var validationErrs field.ErrorList + + if r.Spec.DataDgStorageClass != "" { + if r.Spec.ConfigParams == nil || r.Spec.ConfigParams.DbAsmDeviceList == "" { + validationErrs = append(validationErrs, + field.Invalid(field.NewPath("spec").Child("DataDgStorageClass"), r.Spec.RedoDgStorageClass, fmt.Sprintf("Data ASM diskgroup storageclass set but Spec.ConfigParams.DataAsmDeviceList is set to empty"))) + return validationErrs + } + } + return nil +} + +func (r *OracleRestart) validateCrsAsmDG() field.ErrorList { + var validationErrs field.ErrorList + + if r.Spec.CrsDgStorageClass != "" { + if r.Spec.ConfigParams == nil || r.Spec.ConfigParams.CrsAsmDeviceList == "" { + validationErrs = append(validationErrs, + field.Invalid(field.NewPath("spec").Child("CrsDgStorageClass"), r.Spec.CrsDgStorageClass, fmt.Sprintf("Crs ASM diskgroup storageclass set but Spec.ConfigParams.CrsAsmDeviceList is set to empty"))) + return validationErrs + } + } + return nil +} + +// Helper function to check if a slice contains a specific element +func contains(slice []string, item string) bool { + for _, elem := range slice { + if elem == item { + return true + } + } + return false +} +func getDeviceCount(deviceList string) int { + if deviceList == "" { + return 0 + } + devices := strings.Split(deviceList, ",") + count := 0 + for _, d := range devices { + if strings.TrimSpace(d) != "" { + count++ + } + } + return count +} + +func (r *OracleRestart) validateAsmRedundancyAndDisks( + devList, redundancy, paramField string, +) field.ErrorList { + var errs field.ErrorList + diskCount := getDeviceCount(devList) + + // Only validate if at least ONE of devList or redundancy is set/non-empty + if strings.TrimSpace(redundancy) == "" { + // Both are empty, nothing to validate + return errs + } + + switch strings.ToUpper(redundancy) { + case "EXTERNAL": + if diskCount < 1 { + errs = append(errs, field.Invalid( + field.NewPath("spec").Child("configParams").Child(paramField), + devList, + "EXTERNAL redundancy requires disk count minimum 1", + )) + } + case "NORMAL": + if diskCount < 2 { + errs = append(errs, field.Invalid( + field.NewPath("spec").Child("configParams").Child(paramField), + devList, + "NORMAL redundancy requires disk count minimum 2", + )) + } + case "HIGH": + if diskCount < 3 { + errs = append(errs, field.Invalid( + field.NewPath("spec").Child("configParams").Child(paramField), + devList, + "HIGH redundancy requires disk count minimum 3", + )) + } + default: + errs = append(errs, field.Invalid( + field.NewPath("spec").Child("configParams").Child(paramField), + redundancy, + "Invalid redundancy type; must be EXTERNAL, NORMAL, or HIGH", + )) + } + return errs +} + +// =========================== Update specs checks block ends Here ======================= diff --git a/apis/database/v4/oraclerestdataservice_types.go b/apis/database/v4/oraclerestdataservice_types.go index 20cc7a74..c58de9f4 100644 --- a/apis/database/v4/oraclerestdataservice_types.go +++ b/apis/database/v4/oraclerestdataservice_types.go @@ -126,6 +126,7 @@ type OracleRestDataServiceStatus struct { } //+kubebuilder:object:root=true +// +kubebuilder:resource:shortName=ords //+kubebuilder:subresource:status // +kubebuilder:printcolumn:JSONPath=".status.status",name="Status",type="string" // +kubebuilder:printcolumn:JSONPath=".spec.databaseRef",name="Database",type="string" diff --git a/apis/database/v4/ordssrvs_types.go b/apis/database/v4/ordssrvs_types.go index 1fbf820a..d7e5156a 100644 --- a/apis/database/v4/ordssrvs_types.go +++ b/apis/database/v4/ordssrvs_types.go @@ -39,8 +39,6 @@ package v4 import ( - "time" - corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -74,7 +72,8 @@ type OrdsSrvsSpec struct { EncPrivKey PasswordSecret `json:"encPrivKey,omitempty"` PoolSettings []*PoolSettings `json:"poolSettings,omitempty"` // +k8s:openapi-gen=true - + // ServiceAccount of the OrdsSrvs Pod + ServiceAccountName string `json:"serviceAccountName,omitempty"` } type GlobalSettings struct { @@ -82,15 +81,14 @@ type GlobalSettings struct { CacheMetadataEnabled *bool `json:"cache.metadata.enabled,omitempty"` // Specifies the duration after a GraphQL schema is not accessed from the cache that it expires. - CacheMetadataGraphQLExpireAfterAccess *time.Duration `json:"cache.metadata.graphql.expireAfterAccess,omitempty"` + CacheMetadataGraphQLExpireAfterAccess string `json:"cache.metadata.graphql.expireAfterAccess,omitempty"` // Specifies the duration after a GraphQL schema is cached that it expires and has to be loaded again. - CacheMetadataGraphQLExpireAfterWrite *time.Duration `json:"cache.metadata.graphql.expireAfterWrite,omitempty"` + CacheMetadataGraphQLExpireAfterWrite string `json:"cache.metadata.graphql.expireAfterWrite,omitempty"` // Specifies the setting to determine for how long a metadata record remains in the cache. // Longer duration means, it takes longer to view the applied changes. - // The formats accepted are based on the ISO-8601 duration format. - CacheMetadataTimeout *time.Duration `json:"cache.metadata.timeout,omitempty"` + CacheMetadataTimeout string `json:"cache.metadata.timeout,omitempty"` // Specifies the setting to enable or disable JWKS caching. CacheMetadataJWKSEnabled *bool `json:"cache.metadata.jwks.enabled,omitempty"` @@ -103,10 +101,10 @@ type GlobalSettings struct { // Specifies the duration after a JWK is not accessed from the cache that it expires. // By default this is disabled. - CacheMetadataJWKSExpireAfterAccess *time.Duration `json:"cache.metadata.jwks.expireAfterAccess,omitempty"` + CacheMetadataJWKSExpireAfterAccess string `json:"cache.metadata.jwks.expireAfterAccess,omitempty"` // Specifies the duration after a JWK is cached, that is, it expires and has to be loaded again. - CacheMetadataJWKSExpireAfterWrite *time.Duration `json:"cache.metadata.jwks.expireAfterWrite,omitempty"` + CacheMetadataJWKSExpireAfterWrite string `json:"cache.metadata.jwks.expireAfterWrite,omitempty"` // Specifies whether the Database API is enabled. DatabaseAPIEnabled *bool `json:"database.api.enabled,omitempty"` @@ -116,7 +114,7 @@ type GlobalSettings struct { DatabaseAPIManagementServicesDisabled *bool `json:"database.api.management.services.disabled,omitempty"` // Specifies how long to wait before retrying an invalid pool. - DBInvalidPoolTimeout *time.Duration `json:"db.invalidPoolTimeout,omitempty"` + DBInvalidPoolTimeout string `json:"db.invalidPoolTimeout,omitempty"` // Specifies the maximum join nesting depth limit for GraphQL queries. FeatureGraphQLMaxNestingDepth *int32 `json:"feature.grahpql.max.nesting.depth,omitempty"` @@ -131,7 +129,7 @@ type GlobalSettings struct { SecurityCredentialsAttempts *int32 `json:"security.credentials.attempts,omitempty"` // Specifies the period to lock the account that has exceeded maximum attempts. - SecurityCredentialsLockTime *time.Duration `json:"security.credentials.lock.time,omitempty"` + SecurityCredentialsLockTime string `json:"security.credentials.lock.time,omitempty"` // Specifies the HTTP listen port. //+kubebuilder:default:=8080 @@ -145,7 +143,7 @@ type GlobalSettings struct { StandaloneHTTPSPort *int32 `json:"standalone.https.port,omitempty"` // Specifies the period for Standalone Mode to wait until it is gracefully shutdown. - StandaloneStopTimeout *time.Duration `json:"standalone.stop.timeout,omitempty"` + StandaloneStopTimeout string `json:"standalone.stop.timeout,omitempty"` // Specifies whether to display error messages on the browser. DebugPrintDebugToScreen *bool `json:"debug.printDebugToScreen,omitempty"` @@ -181,10 +179,10 @@ type GlobalSettings struct { MongoPort *int32 `json:"mongo.port,omitempty"` // Specifies the maximum idle time for a Mongo connection in milliseconds. - MongoIdleTimeout *time.Duration `json:"mongo.idle.timeout,omitempty"` + MongoIdleTimeout string `json:"mongo.idle.timeout,omitempty"` // Specifies the maximum time for a Mongo database operation in milliseconds. - MongoOpTimeout *time.Duration `json:"mongo.op.timeout,omitempty"` + MongoOpTimeout string `json:"mongo.op.timeout,omitempty"` // If this value is set to true, then the Oracle REST Data Services internal exclusion list is not enforced. // Oracle recommends that you do not set this value to true. @@ -207,6 +205,19 @@ type GlobalSettings struct { //+kubebuilder:default:="/ords" StandaloneContextPath string `json:"standalone.context.path,omitempty"` + // Specify whether to download APEX installation files + // This setting will be ignored for ADB + //+kubebuilder:default:=false + APEXDownload bool `json:"apex.download,omitempty"` + + // Specify the url to download APEX installation files + // This setting will be ignored for ADB + //+kubebuilder:default:="https://download.oracle.com/otn_software/apex/apex-latest.zip" + APEXDownloadUrl string `json:"apex.download.url,omitempty"` + + // Specify the storage attributes for PersistenceVolume and PersistenceVolumeClaim + APEXInstallationPersistence Persistence `json:"apex.installation.persistence,omitempty"` + /************************************************* * Undocumented /************************************************/ @@ -303,6 +314,19 @@ type GlobalSettings struct { // HARDCODED to global/logs } +// Specify storage attributes of PV and PVC +type Persistence struct { + //+kubebuilder:default="1Gi" + Size string `json:"size,omitempty"` + StorageClass string `json:"storageClass,omitempty"` + //+kubebuilder:validation:Enum=ReadWriteOnce;ReadWriteMany + //+kubebuilder:default=ReadWriteOnce + AccessMode string `json:"accessMode,omitempty"` + VolumeName string `json:"volumeName,omitempty"` + //VolumeClaimAnnotation string `json:"volumeClaimAnnotation,omitempty"` + //SetWritePermissions *bool `json:"setWritePermissions,omitempty"` +} + type PoolSettings struct { // Specifies the Pool Name PoolName string `json:"poolName"` @@ -372,7 +396,7 @@ type PoolSettings struct { DBCredentialsSource string `json:"db.credentialsSource,omitempty"` // Indicates how long to wait to gracefully destroy a pool before moving to forcefully destroy all connections including borrowed ones. - DBPoolDestroyTimeout *time.Duration `json:"db.poolDestroyTimeout,omitempty"` + DBPoolDestroyTimeout string `json:"db.poolDestroyTimeout,omitempty"` // Specifies to enable tracking of JDBC resources. // If not released causes in resource leaks or exhaustion in the database. @@ -411,21 +435,21 @@ type PoolSettings struct { SecurityJWKSSize *int32 `json:"security.jwks.size,omitempty"` // Specifies the maximum amount of time before timing-out when accessing a JWK url. - SecurityJWKSConnectionTimeout *time.Duration `json:"security.jwks.connection.timeout,omitempty"` + SecurityJWKSConnectionTimeout string `json:"security.jwks.connection.timeout,omitempty"` // Specifies the maximum amount of time reading a response from the JWK url before timing-out. - SecurityJWKSReadTimeout *time.Duration `json:"security.jwks.read.timeout,omitempty"` + SecurityJWKSReadTimeout string `json:"security.jwks.read.timeout,omitempty"` // Specifies the minimum interval between refreshing the JWK cached value. - SecurityJWKSRefreshInterval *time.Duration `json:"security.jwks.refresh.interval,omitempty"` + SecurityJWKSRefreshInterval string `json:"security.jwks.refresh.interval,omitempty"` // Specifies the maximum skew the JWT time claims are accepted. // This is useful if the clock on the JWT issuer and ORDS differs by a few seconds. - SecurityJWTAllowedSkew *time.Duration `json:"security.jwt.allowed.skew,omitempty"` + SecurityJWTAllowedSkew string `json:"security.jwt.allowed.skew,omitempty"` // Specifies the maximum allowed age of a JWT in seconds, regardless of expired claim. // The age of the JWT is taken from the JWT issued at claim. - SecurityJWTAllowedAge *time.Duration `json:"security.jwt.allowed.age,omitempty"` + SecurityJWTAllowedAge string `json:"security.jwt.allowed.age,omitempty"` // Indicates the type of security.requestValidationFunction: javascript or plsql. //+kubebuilder:validation:Enum=plsql;javascript @@ -471,7 +495,7 @@ type PoolSettings struct { JDBCMaxConnectionReuseCount *int32 `json:"jdbc.MaxConnectionReuseCount,omitempty"` // Sets the maximum connection reuse time property. - JDBCMaxConnectionReuseTime *int32 `json:"jdbc.MaxConnectionReuseTime,omitempty"` + JDBCMaxConnectionReuseTime string `json:"jdbc.MaxConnectionReuseTime,omitempty"` // Sets the time in seconds to trust an idle connection to skip a validation test. JDBCSecondsToTrustIdleConnection *int32 `json:"jdbc.SecondsToTrustIdleConnection,omitempty"` diff --git a/apis/database/v4/pdb_types.go b/apis/database/v4/pdb_types.go deleted file mode 100644 index 16021f12..00000000 --- a/apis/database/v4/pdb_types.go +++ /dev/null @@ -1,237 +0,0 @@ -/* -** Copyright (c) 2022 Oracle and/or its affiliates. -** -** The Universal Permissive License (UPL), Version 1.0 -** -** Subject to the condition set forth below, permission is hereby granted to any -** person obtaining a copy of this software, associated documentation and/or data -** (collectively the "Software"), free of charge and under any and all copyright -** rights in the Software, and any and all patent rights owned or freely -** licensable by each licensor hereunder covering either (i) the unmodified -** Software as contributed to or provided by such licensor, or (ii) the Larger -** Works (as defined below), to deal in both -** -** (a) the Software, and -** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if -** one is included with the Software (each a "Larger Work" to which the Software -** is contributed by such licensors), -** -** without restriction, including without limitation the rights to copy, create -** derivative works of, display, perform, and distribute the Software and make, -** use, sell, offer for sale, import, export, have made, and have sold the -** Software and the Larger Work(s), and to sublicense the foregoing rights on -** either these or other terms. -** -** This license is subject to the following condition: -** The above copyright notice and either this complete permission notice or at -** a minimum a reference to the UPL must be included in all copies or -** substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -** SOFTWARE. - */ - -package v4 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// PDBSpec defines the desired state of PDB -type PDBSpec struct { - // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster - // Important: Run "make" to regenerate code after modifying this file - - PDBTlsKey PDBTLSKEY `json:"pdbTlsKey,omitempty"` - PDBTlsCrt PDBTLSCRT `json:"pdbTlsCrt,omitempty"` - PDBTlsCat PDBTLSCAT `json:"pdbTlsCat,omitempty"` - - // CDB Namespace - CDBNamespace string `json:"cdbNamespace,omitempty"` - // Name of the CDB Custom Resource that runs the ORDS container - CDBResName string `json:"cdbResName,omitempty"` - // Name of the CDB - CDBName string `json:"cdbName,omitempty"` - // The name of the new PDB. Relevant for both Create and Plug Actions. - PDBName string `json:"pdbName,omitempty"` - // Name of the Source PDB from which to clone - SrcPDBName string `json:"srcPdbName,omitempty"` - // The administrator username for the new PDB. This property is required when the Action property is Create. - AdminName PDBAdminName `json:"adminName,omitempty"` - // The administrator password for the new PDB. This property is required when the Action property is Create. - AdminPwd PDBAdminPassword `json:"adminPwd,omitempty"` - // Web Server User with SQL Administrator role to allow us to authenticate to the PDB Lifecycle Management REST endpoints - WebServerUsr WebServerUserPDB `json:"webServerUser,omitempty"` - // Password for the Web ServerPDB User - WebServerPwd WebServerPasswordPDB `json:"webServerPwd,omitempty"` - // Relevant for Create and Plug operations. As defined in the Oracle Multitenant Database documentation. Values can be a filename convert pattern or NONE. - FileNameConversions string `json:"fileNameConversions,omitempty"` - // This property is required when the Action property is Plug. As defined in the Oracle Multitenant Database documentation. Values can be a source filename convert pattern or NONE. - SourceFileNameConversions string `json:"sourceFileNameConversions,omitempty"` - // XML metadata filename to be used for Plug or Unplug operations - XMLFileName string `json:"xmlFileName,omitempty"` - // To copy files or not while cloning a PDB - // +kubebuilder:validation:Enum=COPY;NOCOPY;MOVE - CopyAction string `json:"copyAction,omitempty"` - // Specify if datafiles should be removed or not. The value can be INCLUDING or KEEP (default). - // +kubebuilder:validation:Enum=INCLUDING;KEEP - DropAction string `json:"dropAction,omitempty"` - // A Path specified for sparse clone snapshot copy. (Optional) - SparseClonePath string `json:"sparseClonePath,omitempty"` - // Whether to reuse temp file - ReuseTempFile *bool `json:"reuseTempFile,omitempty"` - // Relevant for Create and Plug operations. True for unlimited storage. Even when set to true, totalSize and tempSize MUST be specified in the request if Action is Create. - UnlimitedStorage *bool `json:"unlimitedStorage,omitempty"` - // Indicate if 'AS CLONE' option should be used in the command to plug in a PDB. This property is applicable when the Action property is PLUG but not required. - AsClone *bool `json:"asClone,omitempty"` - // Relevant for create and plug operations. Total size as defined in the Oracle Multitenant Database documentation. See size_clause description in Database SQL Language Reference documentation. - TotalSize string `json:"totalSize,omitempty"` - // Relevant for Create and Clone operations. Total size for temporary tablespace as defined in the Oracle Multitenant Database documentation. See size_clause description in Database SQL Language Reference documentation. - TempSize string `json:"tempSize,omitempty"` - // TDE import for plug operations - TDEImport *bool `json:"tdeImport,omitempty"` - // TDE export for unplug operations - TDEExport *bool `json:"tdeExport,omitempty"` - // TDE password if the tdeImport or tdeExport flag is set to true. Can be used in create, plug or unplug operations - TDEPassword TDEPwd `json:"tdePassword,omitempty"` - // TDE keystore path is required if the tdeImport or tdeExport flag is set to true. Can be used in plug or unplug operations. - TDEKeystorePath string `json:"tdeKeystorePath,omitempty"` - // TDE secret is required if the tdeImport or tdeExport flag is set to true. Can be used in plug or unplug operations. - TDESecret TDESecret `json:"tdeSecret,omitempty"` - // Whether you need the script only or execute the script - GetScript *bool `json:"getScript,omitempty"` - // Action to be taken: Create/Clone/Plug/Unplug/Delete/Modify/Status/Map. Map is used to map a Databse PDB to a Kubernetes PDB CR. - // +kubebuilder:validation:Enum=Create;Clone;Plug;Unplug;Delete;Modify;Status;Map - Action string `json:"action"` - // Extra options for opening and closing a PDB - // +kubebuilder:validation:Enum=IMMEDIATE;NORMAL;READ ONLY;READ WRITE;RESTRICTED - ModifyOption string `json:"modifyOption,omitempty"` - // The target state of the PDB - // +kubebuilder:validation:Enum=OPEN;CLOSE - PDBState string `json:"pdbState,omitempty"` - // turn on the assertive approach to delete pdb resource - // kubectl delete pdb ..... automatically triggers the pluggable database - // deletion - AssertivePdbDeletion bool `json:"assertivePdbDeletion,omitempty"` - PDBPubKey PDBPUBKEY `json:"pdbOrdsPubKey,omitempty"` - PDBPriKey PDBPRIVKEY `json:"pdbOrdsPrvKey,omitempty"` -} - -// PDBAdminName defines the secret containing Sys Admin User mapped to key 'adminName' for PDB -type PDBAdminName struct { - Secret PDBSecret `json:"secret"` -} - -// PDBAdminPassword defines the secret containing Sys Admin Password mapped to key 'adminPwd' for PDB -type PDBAdminPassword struct { - Secret PDBSecret `json:"secret"` -} - -// TDEPwd defines the secret containing TDE Wallet Password mapped to key 'tdePassword' for PDB -type TDEPwd struct { - Secret PDBSecret `json:"secret"` -} - -// TDESecret defines the secret containing TDE Secret to key 'tdeSecret' for PDB -type TDESecret struct { - Secret PDBSecret `json:"secret"` -} - -// WebServerUser defines the secret containing Web Server User mapped to key 'webServerUser' to manage PDB lifecycle - -type WebServerUserPDB struct { - Secret PDBSecret `json:"secret"` -} - -// WebServerPassword defines the secret containing password for Web Server User mapped to key 'webServerPwd' to manage PDB lifecycle -type WebServerPasswordPDB struct { - Secret PDBSecret `json:"secret"` -} - -// PDBSecret defines the secretName -type PDBSecret struct { - SecretName string `json:"secretName"` - Key string `json:"key"` -} - -type PDBTLSKEY struct { - Secret PDBSecret `json:"secret"` -} - -type PDBTLSCRT struct { - Secret PDBSecret `json:"secret"` -} - -type PDBTLSCAT struct { - Secret PDBSecret `json:"secret"` -} - -// PDBStatus defines the observed state of PDB -type PDBStatus struct { - // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - // Important: Run "make" to regenerate code after modifying this file - - // PDB Connect String - ConnString string `json:"connString,omitempty"` - // Phase of the PDB Resource - Phase string `json:"phase"` - // PDB Resource Status - Status bool `json:"status"` - // Total size of the PDB - TotalSize string `json:"totalSize,omitempty"` - // Open mode of the PDB - OpenMode string `json:"openMode,omitempty"` - // Modify Option of the PDB - ModifyOption string `json:"modifyOption,omitempty"` - // Message - Msg string `json:"msg,omitempty"` - // Last Completed Action - Action string `json:"action,omitempty"` -} - -// +kubebuilder:object:root=true -// +kubebuilder:subresource:status -// +kubebuilder:printcolumn:JSONPath=".spec.cdbName",name="CDB Name",type="string",description="Name of the CDB" -// +kubebuilder:printcolumn:JSONPath=".spec.pdbName",name="PDB Name",type="string",description="Name of the PDB" -// +kubebuilder:printcolumn:JSONPath=".status.openMode",name="PDB State",type="string",description="PDB Open Mode" -// +kubebuilder:printcolumn:JSONPath=".status.totalSize",name="PDB Size",type="string",description="Total Size of the PDB" -// +kubebuilder:printcolumn:JSONPath=".status.phase",name="Status",type="string",description="Status of the PDB Resource" -// +kubebuilder:printcolumn:JSONPath=".status.msg",name="Message",type="string",description="Error message, if any" -// +kubebuilder:printcolumn:JSONPath=".status.connString",name="Connect_String",type="string",description="The connect string to be used" -// +kubebuilder:resource:path=pdbs,scope=Namespaced -// +kubebuilder:storageversion - -// PDB is the Schema for the pdbs API -type PDB struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec PDBSpec `json:"spec,omitempty"` - Status PDBStatus `json:"status,omitempty"` -} - -//+kubebuilder:object:root=true - -// PDBList contains a list of PDB -type PDBList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []PDB `json:"items"` -} - -type PDBPUBKEY struct { - Secret PDBSecret `json:"secret"` -} - -type PDBPRIVKEY struct { - Secret PDBSecret `json:"secret"` -} - -func init() { - SchemeBuilder.Register(&PDB{}, &PDBList{}) -} diff --git a/apis/database/v4/pdb_webhook.go b/apis/database/v4/pdb_webhook.go deleted file mode 100644 index f651accf..00000000 --- a/apis/database/v4/pdb_webhook.go +++ /dev/null @@ -1,369 +0,0 @@ -/* -** Copyright (c) 2022 Oracle and/or its affiliates. -** -** The Universal Permissive License (UPL), Version 1.0 -** -** Subject to the condition set forth below, permission is hereby granted to any -** person obtaining a copy of this software, associated documentation and/or data -** (collectively the "Software"), free of charge and under any and all copyright -** rights in the Software, and any and all patent rights owned or freely -** licensable by each licensor hereunder covering either (i) the unmodified -** Software as contributed to or provided by such licensor, or (ii) the Larger -** Works (as defined below), to deal in both -** -** (a) the Software, and -** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if -** one is included with the Software (each a "Larger Work" to which the Software -** is contributed by such licensors), -** -** without restriction, including without limitation the rights to copy, create -** derivative works of, display, perform, and distribute the Software and make, -** use, sell, offer for sale, import, export, have made, and have sold the -** Software and the Larger Work(s), and to sublicense the foregoing rights on -** either these or other terms. -** -** This license is subject to the following condition: -** The above copyright notice and either this complete permission notice or at -** a minimum a reference to the UPL must be included in all copies or -** substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -** SOFTWARE. - */ - -/* MODIFIED (MM/DD/YY) -** rcitton 07/14/22 - 33822886 - */ - -package v4 - -import ( - "reflect" - "strconv" - "strings" - - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/validation/field" - ctrl "sigs.k8s.io/controller-runtime" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/webhook" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" -) - -// log is for logging in this package. -var pdblog = logf.Log.WithName("pdb-webhook") - -func (r *PDB) SetupWebhookWithManager(mgr ctrl.Manager) error { - return ctrl.NewWebhookManagedBy(mgr). - For(r). - Complete() -} - -//+kubebuilder:webhook:path=/mutate-database-oracle-com-v4-pdb,mutating=true,failurePolicy=fail,sideEffects=None,groups=database.oracle.com,resources=pdbs,verbs=create;update,versions=v4,name=mpdb.kb.io,admissionReviewVersions={v1,v1beta1} - -var _ webhook.Defaulter = &PDB{} - -// Default implements webhook.Defaulter so a webhook will be registered for the type -func (r *PDB) Default() { - pdblog.Info("Setting default values in PDB spec for : " + r.Name) - - action := strings.ToUpper(r.Spec.Action) - - if action == "DELETE" { - if r.Spec.DropAction == "" { - r.Spec.DropAction = "INCLUDING" - pdblog.Info(" - dropAction : INCLUDING") - } - } else if action != "MODIFY" && action != "STATUS" { - if r.Spec.ReuseTempFile == nil { - r.Spec.ReuseTempFile = new(bool) - *r.Spec.ReuseTempFile = true - pdblog.Info(" - reuseTempFile : " + strconv.FormatBool(*(r.Spec.ReuseTempFile))) - } - if r.Spec.UnlimitedStorage == nil { - r.Spec.UnlimitedStorage = new(bool) - *r.Spec.UnlimitedStorage = true - pdblog.Info(" - unlimitedStorage : " + strconv.FormatBool(*(r.Spec.UnlimitedStorage))) - } - if r.Spec.TDEImport == nil { - r.Spec.TDEImport = new(bool) - *r.Spec.TDEImport = false - pdblog.Info(" - tdeImport : " + strconv.FormatBool(*(r.Spec.TDEImport))) - } - if r.Spec.TDEExport == nil { - r.Spec.TDEExport = new(bool) - *r.Spec.TDEExport = false - pdblog.Info(" - tdeExport : " + strconv.FormatBool(*(r.Spec.TDEExport))) - } - if r.Spec.AsClone == nil { - r.Spec.AsClone = new(bool) - *r.Spec.AsClone = false - pdblog.Info(" - asClone : " + strconv.FormatBool(*(r.Spec.AsClone))) - } - - } - - if r.Spec.GetScript == nil { - r.Spec.GetScript = new(bool) - *r.Spec.GetScript = false - pdblog.Info(" - getScript : " + strconv.FormatBool(*(r.Spec.GetScript))) - } -} - -// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. -//+kubebuilder:webhook:path=/validate-database-oracle-com-v4-pdb,mutating=false,failurePolicy=fail,sideEffects=None,groups=database.oracle.com,resources=pdbs,verbs=create;update,versions=v4,name=vpdb.kb.io,admissionReviewVersions={v1,v1beta1} - -var _ webhook.Validator = &PDB{} - -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (r *PDB) ValidateCreate() (admission.Warnings, error) { - pdblog.Info("ValidateCreate-Validating PDB spec for : " + r.Name) - - var allErrs field.ErrorList - - r.validateCommon(&allErrs) - - r.validateAction(&allErrs) - - action := strings.ToUpper(r.Spec.Action) - - if len(allErrs) == 0 { - pdblog.Info("PDB Resource : " + r.Name + " successfully validated for Action : " + action) - return nil, nil - } - return nil, apierrors.NewInvalid( - schema.GroupKind{Group: "database.oracle.com", Kind: "PDB"}, - r.Name, allErrs) -} - -// Validate Action for required parameters -func (r *PDB) validateAction(allErrs *field.ErrorList) { - action := strings.ToUpper(r.Spec.Action) - - pdblog.Info("Valdiating PDB Resource Action : " + action) - - if reflect.ValueOf(r.Spec.PDBTlsKey).IsZero() { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("pdbTlsKey"), "Please specify PDB Tls Key(secret)")) - } - - if reflect.ValueOf(r.Spec.PDBTlsCrt).IsZero() { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("pdbTlsCrt"), "Please specify PDB Tls Certificate(secret)")) - } - - if reflect.ValueOf(r.Spec.PDBTlsCat).IsZero() { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("pdbTlsCat"), "Please specify PDB Tls Certificate Authority(secret)")) - } - if reflect.ValueOf(r.Spec.PDBPriKey).IsZero() { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("pdbOrdsPrvKey"), "Please specify PDB Tls Certificate Authority(secret)")) - } - - switch action { - case "DELETE": - /* BUG 36752336 - LREST OPERATOR - DELETE NON-EXISTENT PDB SHOWS LRPDB CREATED MESSAGE */ - if r.Status.OpenMode == "READ WRITE" { - pdblog.Info("Cannot delete: pdb is open ") - *allErrs = append(*allErrs, field.Invalid(field.NewPath("status").Child("OpenMode"), "READ WRITE", "pdb "+r.Spec.PDBName+" "+r.Status.OpenMode)) - } - r.CheckObjExistence("DELETE", allErrs, r) - case "CREATE": - if reflect.ValueOf(r.Spec.AdminName).IsZero() { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("adminName"), "Please specify PDB System Administrator user")) - } - if reflect.ValueOf(r.Spec.AdminPwd).IsZero() { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("adminPwd"), "Please specify PDB System Administrator Password")) - } - if reflect.ValueOf(r.Spec.WebServerUsr).IsZero() { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("WebServerUser"), "Please specify the http webServerUser")) - } - if reflect.ValueOf(r.Spec.WebServerPwd).IsZero() { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("webServerPwd"), "Please specify the http webserverPassword")) - } - - if r.Spec.FileNameConversions == "" { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("fileNameConversions"), "Please specify a value for fileNameConversions. Values can be a filename convert pattern or NONE")) - } - if r.Spec.TotalSize == "" { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("totalSize"), "When the storage is not UNLIMITED the Total Size must be specified")) - } - if r.Spec.TempSize == "" { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("tempSize"), "When the storage is not UNLIMITED the Temp Size must be specified")) - } - if *(r.Spec.TDEImport) { - r.validateTDEInfo(allErrs) - } - case "CLONE": - // Sample Err: The PDB "pdb1-clone" is invalid: spec.srcPdbName: Required value: Please specify source PDB for Cloning - if r.Spec.SrcPDBName == "" { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("srcPdbName"), "Please specify source PDB name for Cloning")) - } - if r.Spec.TotalSize == "" { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("totalSize"), "When the storage is not UNLIMITED the Total Size must be specified")) - } - if r.Spec.TempSize == "" { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("tempSize"), "When the storage is not UNLIMITED the Temp Size must be specified")) - } - /* We don't need this check as ords open the pdb before cloninig */ - /* - if r.Status.OpenMode == "MOUNTED" { - pdblog.Info("Cannot clone: pdb is mount ") - *allErrs = append(*allErrs, field.Invalid(field.NewPath("status").Child("OpenMode"), "READ WRITE", "pdb "+r.Spec.PDBName+" "+r.Status.OpenMode)) - } - */ - case "PLUG": - if r.Spec.XMLFileName == "" { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("xmlFileName"), "Please specify XML metadata filename")) - } - if r.Spec.FileNameConversions == "" { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("fileNameConversions"), "Please specify a value for fileNameConversions. Values can be a filename convert pattern or NONE")) - } - if r.Spec.SourceFileNameConversions == "" { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("sourceFileNameConversions"), "Please specify a value for sourceFileNameConversions. Values can be a filename convert pattern or NONE")) - } - if r.Spec.CopyAction == "" { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("copyAction"), "Please specify a value for copyAction. Values can be COPY, NOCOPY or MOVE")) - } - if *(r.Spec.TDEImport) { - r.validateTDEInfo(allErrs) - } - case "UNPLUG": - if r.Spec.XMLFileName == "" { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("xmlFileName"), "Please specify XML metadata filename")) - } - if *(r.Spec.TDEExport) { - r.validateTDEInfo(allErrs) - } - if r.Status.OpenMode == "READ WRITE" { - pdblog.Info("Cannot unplug: pdb is open ") - *allErrs = append(*allErrs, field.Invalid(field.NewPath("status").Child("OpenMode"), "READ WRITE", "pdb "+r.Spec.PDBName+" "+r.Status.OpenMode)) - } - r.CheckObjExistence("UNPLUG", allErrs, r) - case "MODIFY": - if r.Spec.PDBState == "" { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("pdbState"), "Please specify target state of PDB")) - } - if r.Spec.ModifyOption == "" { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("modifyOption"), "Please specify an option for opening/closing a PDB")) - } - r.CheckObjExistence("MODIY", allErrs, r) - } -} - -// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *PDB) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - pdblog.Info("ValidateUpdate-Validating PDB spec for : " + r.Name) - - isPDBMarkedToBeDeleted := r.GetDeletionTimestamp() != nil - if isPDBMarkedToBeDeleted { - return nil, nil - } - - var allErrs field.ErrorList - action := strings.ToUpper(r.Spec.Action) - - // If PDB CR has been created and in Ready state, only allow updates if the "action" value has changed as well - if (r.Status.Phase == "Ready") && (r.Status.Action != "MODIFY") && (r.Status.Action != "STATUS") && (r.Status.Action == action) { - allErrs = append(allErrs, - field.Required(field.NewPath("spec").Child("action"), "New action also needs to be specified after PDB is in Ready state")) - } else { - - // Check Common Validations - r.validateCommon(&allErrs) - - // Validate required parameters for Action specified - r.validateAction(&allErrs) - - // Check TDE requirements - if (action != "DELETE") && (action != "MODIFY") && (action != "STATUS") && (*(r.Spec.TDEImport) || *(r.Spec.TDEExport)) { - r.validateTDEInfo(&allErrs) - } - } - - if len(allErrs) == 0 { - return nil, nil - } - return nil, apierrors.NewInvalid( - schema.GroupKind{Group: "database.oracle.com", Kind: "PDB"}, - r.Name, allErrs) -} - -// ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *PDB) ValidateDelete() (admission.Warnings, error) { - pdblog.Info("ValidateDelete-Validating PDB spec for : " + r.Name) - - // TODO(user): fill in your validation logic upon object deletion. - return nil, nil -} - -// Validate common specs needed for all PDB Actions -func (r *PDB) validateCommon(allErrs *field.ErrorList) { - pdblog.Info("validateCommon", "name", r.Name) - - if r.Spec.Action == "" { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("action"), "Please specify PDB operation to be performed")) - } - if r.Spec.CDBResName == "" { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("cdbResName"), "Please specify the name of the CDB Kubernetes resource to use for PDB operations")) - } - if r.Spec.PDBName == "" { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("pdbName"), "Please specify name of the PDB to be created")) - } -} - -// Validate TDE information for Create, Plug and Unplug Actions -func (r *PDB) validateTDEInfo(allErrs *field.ErrorList) { - pdblog.Info("validateTDEInfo", "name", r.Name) - - if reflect.ValueOf(r.Spec.TDEPassword).IsZero() { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("tdePassword"), "Please specify a value for tdePassword.")) - } - if r.Spec.TDEKeystorePath == "" { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("tdeKeystorePath"), "Please specify a value for tdeKeystorePath.")) - } - if reflect.ValueOf(r.Spec.TDESecret).IsZero() { - *allErrs = append(*allErrs, - field.Required(field.NewPath("spec").Child("tdeSecret"), "Please specify a value for tdeSecret.")) - } - -} - -func (r *PDB) CheckObjExistence(action string, allErrs *field.ErrorList, pdb *PDB) { - /* BUG 36752465 - lrest operator - open non-existent pdb creates a lrpdb with status failed */ - pdblog.Info("Action [" + action + "] checkin " + pdb.Spec.PDBName + " existence") - if pdb.Status.OpenMode == "" { - *allErrs = append(*allErrs, field.NotFound(field.NewPath("Spec").Child("PDBName"), " "+pdb.Spec.PDBName+" does not exist : action "+action+" failure")) - - } -} diff --git a/apis/database/v4/shardingdatabase_types.go b/apis/database/v4/shardingdatabase_types.go index cc01b24d..3b33f64a 100644 --- a/apis/database/v4/shardingdatabase_types.go +++ b/apis/database/v4/shardingdatabase_types.go @@ -58,7 +58,7 @@ import ( type ShardingDatabaseSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file - Shard []ShardSpec `json:"shard"` + Shard []ShardSpec `json:"shard,omitempty"` Catalog []CatalogSpec `json:"catalog"` // The catalogSpes accept all the catalog parameters Gsm []GsmSpec `json:"gsm"` // The GsmSpec will accept all the Gsm parameter StorageClass string `json:"storageClass,omitempty"` // Optional Accept storage class name @@ -95,6 +95,8 @@ type ShardingDatabaseSpec struct { TdeWalletPvcMountLocation string `json:"tdeWalletPvcMountLocation,omitempty"` DbEdition string `json:"dbEdition,omitempty"` TopicId string `json:"topicId,omitempty"` + SrvAccountName string `json:"serviceAccountName,omitempty"` + ShardInfo []ShardingDetails `json:"shardInfo,omitempty"` } // To understand Metav1.Condition, please refer the link https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1 @@ -157,9 +159,9 @@ type ShardingDatabase struct { Status ShardingDatabaseStatus `json:"status,omitempty"` } -//+kubebuilder:object:root=true - +// +kubebuilder:object:root=true // ShardingDatabaseList contains a list of ShardingDatabase +// +kubebuilder:storageversion type ShardingDatabaseList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` @@ -167,7 +169,6 @@ type ShardingDatabaseList struct { } // ShardSpec is a specification of Shards for an application deployment. -// +k8s:openapi-gen=true type ShardSpec struct { Name string `json:"name"` // Shard name that will be used deploy StatefulSet StorageSizeInGb int32 `json:"storageSizeInGb,omitempty"` // Optional Shard Storage Size @@ -185,26 +186,26 @@ type ShardSpec struct { ShardGroup string `json:"shardGroup,omitempty"` ShardRegion string `json:"shardRegion,omitempty"` DeployAs string `json:"deployAs,omitempty"` + ShardConfigData *ConfigMapData `json:"shardConfigData,omitempty"` } // CatalogSpec defines the desired state of CatalogSpec -// +k8s:openapi-gen=true type CatalogSpec struct { - Name string `json:"name"` // Catalog name that will be used deploy StatefulSet - StorageSizeInGb int32 `json:"storageSizeInGb,omitempty"` // Optional Catalog Storage Size and This parameter will not be used if you use PvcName - EnvVars []EnvironmentVariable `json:"envVars,omitempty"` //Optional Env variables for Catalog - Resources *corev1.ResourceRequirements `json:"resources,omitempty" protobuf:"bytes,1,opt,name=resources"` // Optional resource requirement for the container. - PvcName string `json:"pvcName,omitempty"` - Label string `json:"label,omitempty"` - IsDelete string `json:"isDelete,omitempty"` - NodeSelector map[string]string `json:"nodeSelector,omitempty"` - PvAnnotations map[string]string `json:"pvAnnotations,omitempty"` - PvMatchLabels map[string]string `json:"pvMatchLabels,omitempty"` - ImagePulllPolicy *corev1.PullPolicy `json:"imagePullPolicy,omitempty"` + Name string `json:"name"` // Catalog name that will be used deploy StatefulSet + StorageSizeInGb int32 `json:"storageSizeInGb,omitempty"` // Optional Catalog Storage Size and This parameter will not be used if you use PvcName + EnvVars []EnvironmentVariable `json:"envVars,omitempty"` //Optional Env variables for Catalog + Resources *corev1.ResourceRequirements `json:"resources,omitempty" protobuf:"bytes,1,opt,name=resources"` // Optional resource requirement for the container. + PvcName string `json:"pvcName,omitempty"` + Label string `json:"label,omitempty"` + IsDelete string `json:"isDelete,omitempty"` + NodeSelector map[string]string `json:"nodeSelector,omitempty"` + PvAnnotations map[string]string `json:"pvAnnotations,omitempty"` + PvMatchLabels map[string]string `json:"pvMatchLabels,omitempty"` + ImagePulllPolicy *corev1.PullPolicy `json:"imagePullPolicy,omitempty"` + CatalogConfigData *ConfigMapData `json:"catalogConfigData,omitempty"` } // GsmSpec defines the desired state of GsmSpec -// +k8s:openapi-gen=true type GsmSpec struct { Name string `json:"name"` // Gsm name that will be used deploy StatefulSet @@ -221,10 +222,10 @@ type GsmSpec struct { ImagePulllPolicy *corev1.PullPolicy `json:"imagePullPolicy,omitempty"` Region string `json:"region,omitempty"` DirectorName string `json:"directorName,omitempty"` + GsmConfigData *ConfigMapData `json:"gsmConfigData,omitempty"` } // ShardGroupSpec Specification - type GsmShardGroupSpec struct { Name string `json:"name"` // Name of the shardgroup. Region string `json:"region,omitempty"` @@ -270,6 +271,7 @@ type GsmServiceSpec struct { TableFamily string `json:"tableFamily,omitempty"` Retention string `json:"retention,omitempty"` TfaPolicy string `json:"tfaPolicy,omitempty"` + RuMode string `json:"ruMode,omitempty"` } // Secret Details @@ -283,17 +285,17 @@ type SecretDetails struct { KeyFileMountLocation string `json:"keyFileMountLocation,omitempty"` KeySecretName string `json:"keySecretName,omitempty"` EncryptionType string `json:"encryptionType,omitempty"` + TdeKeyFileName string `json:"tdeKeyFileName,omitempty"` // Name of the key. + TdePwdFileName string `json:"tdePwdFileName"` } // EnvironmentVariable represents a named variable accessible for containers. -// +k8s:openapi-gen=true type EnvironmentVariable struct { Name string `json:"name"` // Name of the variable. Must be a C_IDENTIFIER. Value string `json:"value"` // Value of the variable, as defined in Kubernetes core API. } // PortMapping is a specification of port mapping for an application deployment. -// +k8s:openapi-gen=true type PortMapping struct { Port int32 `json:"port"` // Port that will be exposed on the service. TargetPort int32 `json:"targetPort"` // Docker image port for the application. @@ -308,6 +310,34 @@ const ( ShardingDelLabelFalseValue SfsetLabel = "false" ) +type ConfigMapData struct { + Name string `json:"name,omitempty"` + MountPath string `json:"mountPath,omitempty"` +} + +// Shard structures based on managed Replicas +type ShardingDetails struct { + ShardPreFixName string `json:"shardPreFixName"` + Shape string `json:"shape,omitempty"` + Replicas int32 `json:"replicas,omitempty"` + StorageSizeInGb int32 `json:"storageSizeInGb,omitempty"` + ShardGroupDetails *ShardGroup `json:"shardGroupDetails,omitempty"` + ShardSpaceDetails *ShardSpace `json:"shardSpaceDetails,omitempty"` +} +type ShardGroup struct { + ShardGroupName string `json:"shardGroupName,omitempty"` + Region string `json:"region,omitempty"` + RepFactor int `json:"repFactor,omitempty"` + ShardSpace string `json:"ShardSpace,omitempty"` + DeployAs string `json:"deployAs,omitempty"` + IsDelete string `json:"isDelete,omitempty"` +} +type ShardSpace struct { + ShardSpaceName string `json:"shardSpaceName,omitempty"` + Chunks int `json:"Chnuks,omitempty"` + ProtectMode string `json:"protectMode,omitempty"` //Possible Values MAXPROTECTION, MAXAVAILABILITY,MAXPERFORMANCE +} + type ShardStatusMapKeys string const ( diff --git a/apis/database/v4/shardingdatabase_webhook.go b/apis/database/v4/shardingdatabase_webhook.go index 1ac74d08..56922a7b 100644 --- a/apis/database/v4/shardingdatabase_webhook.go +++ b/apis/database/v4/shardingdatabase_webhook.go @@ -39,8 +39,12 @@ package v4 import ( + "context" + "fmt" + "strconv" "strings" + corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -51,54 +55,100 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" ) +var totalShard int32 = 0 + // log is for logging in this package. var shardingdatabaselog = logf.Log.WithName("shardingdatabase-resource") func (r *ShardingDatabase) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). - For(r). + For(&ShardingDatabase{}). + WithDefaulter(r). + WithValidator(r). Complete() } +var _ webhook.CustomDefaulter = &ShardingDatabase{} + + // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! //+kubebuilder:webhook:path=/mutate-database-oracle-com-v4-shardingdatabase,mutating=true,failurePolicy=fail,sideEffects=none,groups=database.oracle.com,resources=shardingdatabases,verbs=create;update,versions=v4,name=mshardingdatabasev4.kb.io,admissionReviewVersions=v1 -var _ webhook.Defaulter = &ShardingDatabase{} - // Default implements webhook.Defaulter so a webhook will be registered for the type -func (r *ShardingDatabase) Default() { - shardingdatabaselog.Info("default", "name", r.Name) +func (r *ShardingDatabase) Default(ctx context.Context, obj runtime.Object) error { + + cr , ok := obj.(*ShardingDatabase) + + if !ok { + return fmt.Errorf("xpected obj.*ShardingDatabase but got %T", obj) + } + + shardingdatabaselog.Info("default", "name", cr.Name) + + var replicas int32 + // TODO(user): fill in your defaulting logic. - if r.Spec.GsmDevMode != "" { - r.Spec.GsmDevMode = "dev" + if cr.Spec.GsmDevMode != "" { + cr.Spec.GsmDevMode = "dev" } - if r.Spec.IsTdeWallet == "" { - r.Spec.IsTdeWallet = "disable" + if cr.Spec.IsTdeWallet == "" { + cr.Spec.IsTdeWallet = "disable" } - for pindex := range r.Spec.Shard { - if strings.ToLower(r.Spec.Shard[pindex].IsDelete) == "" { - r.Spec.Shard[pindex].IsDelete = "disable" + for pindex := range cr.Spec.Shard { + if strings.ToLower(cr.Spec.Shard[pindex].IsDelete) == "" { + cr.Spec.Shard[pindex].IsDelete = "disable" + } + } + + for pindex := range cr.Spec.ShardInfo { + if strings.ToLower(cr.Spec.ShardInfo[pindex].ShardGroupDetails.IsDelete) == "" { + cr.Spec.ShardInfo[pindex].ShardGroupDetails.IsDelete = "disable" + } + } + + totalShard = 0 + for pindex := range cr.Spec.ShardInfo { + replicas = 2 + if cr.Spec.ShardInfo[pindex].Replicas != 0 { + replicas = cr.Spec.ShardInfo[pindex].Replicas } + totalShard = totalShard + replicas + } + + if totalShard > 0 { + cr.Spec.Shard = make([]ShardSpec, totalShard) + cr.initShardsSpec() } + return nil } // TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. //+kubebuilder:webhook:verbs=create;update;delete,path=/validate-database-oracle-com-v4-shardingdatabase,mutating=false,failurePolicy=fail,sideEffects=None,groups=database.oracle.com,resources=shardingdatabases,versions=v4,name=vshardingdatabasev4.kb.io,admissionReviewVersions={v1} -var _ webhook.Validator = &ShardingDatabase{} +var _ webhook.CustomValidator = &ShardingDatabase{} // ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (r *ShardingDatabase) ValidateCreate() (admission.Warnings, error) { +func (r *ShardingDatabase) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { shardingdatabaselog.Info("validate create", "name", r.Name) // TODO(user): fill in your validation logic upon object creation. // Check Secret configuration var validationErr field.ErrorList var validationErrs1 field.ErrorList + cr , ok := obj.(*ShardingDatabase) + + if !ok { +// return fmt.Errorf("xpected obj.*ShardingDatabase but got %T", obj) + validationErr = append(validationErr,field.Invalid(field.NewPath("obj"),"obj","Expected obj.*ShardingDatabase.")) + return nil, apierrors.NewInvalid( + schema.GroupKind{Group: "database.oracle.com", Kind: "ShardingDatabase"}, + cr.Name, validationErr) + } + //namespaces := db.GetWatchNamespaces() //_, containsNamespace := namespaces[r.Namespace] @@ -109,25 +159,25 @@ func (r *ShardingDatabase) ValidateCreate() (admission.Warnings, error) { // "Oracle database operator doesn't watch over this namespace")) //} - if r.Spec.DbSecret == nil { + if cr.Spec.DbSecret == nil { validationErr = append(validationErr, - field.Invalid(field.NewPath("spec").Child("DbSecret"), r.Spec.DbSecret, + field.Invalid(field.NewPath("spec").Child("DbSecret"), cr.Spec.DbSecret, "DbSecret cannot be set to nil")) } else { - if len(r.Spec.DbSecret.Name) == 0 { + if len(cr.Spec.DbSecret.Name) == 0 { validationErr = append(validationErr, - field.Invalid(field.NewPath("spec").Child("DbSecret").Child("Name"), r.Spec.DbSecret.Name, + field.Invalid(field.NewPath("spec").Child("DbSecret").Child("Name"), cr.Spec.DbSecret.Name, "Secret name cannot be set empty")) } - if len(r.Spec.DbSecret.PwdFileName) == 0 { + if len(cr.Spec.DbSecret.PwdFileName) == 0 { validationErr = append(validationErr, - field.Invalid(field.NewPath("spec").Child("DbSecret").Child("PwdFileName"), r.Spec.DbSecret.PwdFileName, + field.Invalid(field.NewPath("spec").Child("DbSecret").Child("PwdFileName"), cr.Spec.DbSecret.PwdFileName, "Password file name cannot be set empty")) } - if strings.ToLower(r.Spec.DbSecret.EncryptionType) != "base64" { - if strings.ToLower(r.Spec.DbSecret.KeyFileName) == "" { + if strings.ToLower(cr.Spec.DbSecret.EncryptionType) != "base64" { + if strings.ToLower(cr.Spec.DbSecret.KeyFileName) == "" { validationErr = append(validationErr, - field.Invalid(field.NewPath("spec").Child("DbSecret").Child("KeyFileName"), r.Spec.DbSecret.KeyFileName, + field.Invalid(field.NewPath("spec").Child("DbSecret").Child("KeyFileName"), cr.Spec.DbSecret.KeyFileName, "Key file name cannot be empty")) } } @@ -147,46 +197,55 @@ func (r *ShardingDatabase) ValidateCreate() (admission.Warnings, error) { **/ } - if r.Spec.IsTdeWallet == "enable" { - if (len(r.Spec.FssStorageClass) == 0) && (len(r.Spec.TdeWalletPvc) == 0) { + if cr.Spec.IsTdeWallet == "enable" { + if (len(cr.Spec.FssStorageClass) == 0) && (len(cr.Spec.TdeWalletPvc) == 0) { validationErr = append(validationErr, - field.Invalid(field.NewPath("spec").Child("FssStorageClass"), r.Spec.FssStorageClass, + field.Invalid(field.NewPath("spec").Child("FssStorageClass"), cr.Spec.FssStorageClass, "FssStorageClass or TdeWalletPvc cannot be set empty if isTdeWallet set to true")) validationErr = append(validationErr, - field.Invalid(field.NewPath("spec").Child("TdeWalletPvc"), r.Spec.TdeWalletPvc, + field.Invalid(field.NewPath("spec").Child("TdeWalletPvc"), cr.Spec.TdeWalletPvc, "FssStorageClass or TdeWalletPvc cannot be set empty if isTdeWallet set to true")) } } - if r.Spec.IsTdeWallet != "" { - if (strings.ToLower(strings.TrimSpace(r.Spec.IsTdeWallet)) != "enable") && (strings.ToLower(strings.TrimSpace(r.Spec.IsTdeWallet)) != "disable") { + if cr.Spec.IsTdeWallet != "" { + if (strings.ToLower(strings.TrimSpace(cr.Spec.IsTdeWallet)) != "enable") && (strings.ToLower(strings.TrimSpace(cr.Spec.IsTdeWallet)) != "disable") { validationErr = append(validationErr, - field.Invalid(field.NewPath("spec").Child("isTdeWallet"), r.Spec.IsTdeWallet, + field.Invalid(field.NewPath("spec").Child("isTdeWallet"), cr.Spec.IsTdeWallet, "isTdeWallet can be set to only \"enable\" or \"disable\"")) } } - validationErrs1 = r.validateShardIsDelete() + validationErrs1 = cr.validateShardIsDelete() if validationErrs1 != nil { validationErr = append(validationErr, validationErrs1...) } - validationErrs1 = r.validateFreeEdition() + validationErrs1 = cr.validateFreeEdition() if validationErrs1 != nil { validationErr = append(validationErr, validationErrs1...) } - validationErrs1 = r.validateCatalogName() + validationErrs1 = cr.validateCatalogName() if validationErrs1 != nil { validationErr = append(validationErr, validationErrs1...) } - validationErrs1 = r.validateShardName() + // validationErrs1 = r.validateShardName() + // if validationErrs1 != nil { + // validationErr = append(validationErr, validationErrs1...) + // } + + validationErrs1 = cr.validateShardInfo() if validationErrs1 != nil { validationErr = append(validationErr, validationErrs1...) } + fmt.Println("TotalShard=[" + strconv.Itoa(int(totalShard)) + "]") + fmt.Println("Original shard buffer len=[" + strconv.Itoa(len(cr.Spec.Shard)) + "]") + fmt.Println("Original shard buffer capacity=[" + strconv.Itoa(cap(cr.Spec.Shard)) + "]") + // TODO(user): fill in your validation logic upon object creation. if len(validationErr) == 0 { return nil, nil @@ -194,11 +253,11 @@ func (r *ShardingDatabase) ValidateCreate() (admission.Warnings, error) { return nil, apierrors.NewInvalid( schema.GroupKind{Group: "database.oracle.com", Kind: "ShardingDatabase"}, - r.Name, validationErr) + cr.Name, validationErr) } // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *ShardingDatabase) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { +func (r *ShardingDatabase) ValidateUpdate(ctx context.Context, old, newObj runtime.Object) (admission.Warnings, error) { shardingdatabaselog.Info("validate update", "name", r.Name) // TODO(user): fill in your validation logic upon object update. @@ -206,7 +265,7 @@ func (r *ShardingDatabase) ValidateUpdate(old runtime.Object) (admission.Warning } // ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *ShardingDatabase) ValidateDelete() (admission.Warnings, error) { +func (r *ShardingDatabase) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { shardingdatabaselog.Info("validate delete", "name", r.Name) // TODO(user): fill in your validation logic upon object deletion. @@ -312,3 +371,68 @@ func (r *ShardingDatabase) validateCatalogName() field.ErrorList { } return nil } + +func (r *ShardingDatabase) validateShardInfo() field.ErrorList { + var validationErrs field.ErrorList + var replicas int32 + + totalShard = 0 + for pindex := range r.Spec.ShardInfo { + replicas = 2 + if r.Spec.ShardInfo[pindex].Replicas != 0 { + replicas = r.Spec.ShardInfo[pindex].Replicas + } else { + r.Spec.ShardInfo[pindex].Replicas = replicas + } + + totalShard = totalShard + replicas + if r.Spec.ShardInfo[pindex].ShardGroupDetails != nil { + if r.Spec.ShardInfo[pindex].ShardGroupDetails.DeployAs == "" { + r.Spec.ShardInfo[pindex].ShardGroupDetails.DeployAs = "primary" + } + if (r.Spec.ShardInfo[pindex].ShardGroupDetails.DeployAs == "primary") && (replicas > 1) { + validationErrs = append(validationErrs, + field.Invalid(field.NewPath("spec").Child("shardInfo").Child("replicas"), r.Spec.ShardInfo[pindex].Replicas, + "Primary++ Shard Group can have only one replicas")) + } + } else { + if replicas > 1 { + validationErrs = append(validationErrs, + field.Invalid(field.NewPath("spec").Child("shardInfo").Child("replicas"), r.Spec.ShardInfo[pindex].Replicas, + "Primary!! Shard Group can have only one replicas")) + } + } + } + + if len(validationErrs) > 0 { + return validationErrs + } + return nil +} + +func (r *ShardingDatabase) initShardsSpec() error { + var shardIndex int + + shardIndex = 0 + for pindex := range r.Spec.ShardInfo { + for i := 0; i < int(r.Spec.ShardInfo[pindex].Replicas); i++ { + r.Spec.Shard[shardIndex].Name = r.Spec.ShardInfo[pindex].ShardPreFixName + strconv.Itoa(shardIndex+1) + r.Spec.Shard[shardIndex].StorageSizeInGb = r.Spec.ShardInfo[pindex].StorageSizeInGb + r.Spec.Shard[shardIndex].ShardGroup = r.Spec.ShardInfo[pindex].ShardGroupDetails.ShardGroupName + r.Spec.Shard[shardIndex].ShardRegion = r.Spec.ShardInfo[pindex].ShardGroupDetails.Region + r.Spec.Shard[shardIndex].DeployAs = r.Spec.ShardInfo[pindex].ShardGroupDetails.DeployAs + r.Spec.Shard[shardIndex].IsDelete = r.Spec.ShardInfo[pindex].ShardGroupDetails.IsDelete + r.Spec.Shard[shardIndex].ImagePulllPolicy = new(corev1.PullPolicy) + *(r.Spec.Shard[shardIndex].ImagePulllPolicy) = corev1.PullPolicy("Always") + fmt.Println("ShardName=[" + r.Spec.Shard[shardIndex].Name + "]") + if r.Spec.ShardInfo[pindex].ShardSpaceDetails != nil { + r.Spec.Shard[shardIndex].ShardSpace = r.Spec.ShardInfo[pindex].ShardSpaceDetails.ShardSpaceName + } + + shardIndex++ + } + + } + + return nil +} diff --git a/apis/database/v4/singleinstancedatabase_types.go b/apis/database/v4/singleinstancedatabase_types.go index 4f4836d7..a8d004c7 100644 --- a/apis/database/v4/singleinstancedatabase_types.go +++ b/apis/database/v4/singleinstancedatabase_types.go @@ -193,8 +193,9 @@ type SingleInstanceDatabaseStatus struct { ConvertToSnapshotStandby bool `json:"convertToSnapshotStandby,omitempty"` } -//+kubebuilder:object:root=true -//+kubebuilder:subresource:status +// +kubebuilder:object:root=true +// +kubebuilder:resource:shortName=sidb;sidbs +// +kubebuilder:subresource:status // +kubebuilder:subresource:scale:specpath=.spec.replicas,statuspath=.status.replicas // +kubebuilder:printcolumn:JSONPath=".status.edition",name="Edition",type="string" // +kubebuilder:printcolumn:JSONPath=".status.sid",name="Sid",type="string",priority=1 diff --git a/apis/database/v4/zz_generated.deepcopy.go b/apis/database/v4/zz_generated.deepcopy.go index 4eb9425d..c69f656a 100644 --- a/apis/database/v4/zz_generated.deepcopy.go +++ b/apis/database/v4/zz_generated.deepcopy.go @@ -46,7 +46,6 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - timex "time" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. @@ -98,6 +97,70 @@ func (in *AdminpdbUser) DeepCopy() *AdminpdbUser { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AsmDiskDetails) DeepCopyInto(out *AsmDiskDetails) { + *out = *in + if in.DisksBySize != nil { + in, out := &in.DisksBySize, &out.DisksBySize + *out = make([]DiskBySize, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AsmDiskDetails. +func (in *AsmDiskDetails) DeepCopy() *AsmDiskDetails { + if in == nil { + return nil + } + out := new(AsmDiskDetails) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AsmDiskgroupStatus) DeepCopyInto(out *AsmDiskgroupStatus) { + *out = *in + if in.Disks != nil { + in, out := &in.Disks, &out.Disks + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AsmDiskgroupStatus. +func (in *AsmDiskgroupStatus) DeepCopy() *AsmDiskgroupStatus { + if in == nil { + return nil + } + out := new(AsmDiskgroupStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AsmInstanceStatus) DeepCopyInto(out *AsmInstanceStatus) { + *out = *in + if in.Diskgroup != nil { + in, out := &in.Diskgroup, &out.Diskgroup + *out = make([]AsmDiskgroupStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AsmInstanceStatus. +func (in *AsmInstanceStatus) DeepCopy() *AsmInstanceStatus { + if in == nil { + return nil + } + out := new(AsmInstanceStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AutonomousContainerDatabase) DeepCopyInto(out *AutonomousContainerDatabase) { *out = *in @@ -681,6 +744,21 @@ func (in *AutonomousDatabaseStatus) DeepCopy() *AutonomousDatabaseStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackupInfo) DeepCopyInto(out *BackupInfo) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupInfo. +func (in *BackupInfo) DeepCopy() *BackupInfo { + if in == nil { + return nil + } + out := new(BackupInfo) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Backupconfig) DeepCopyInto(out *Backupconfig) { *out = *in @@ -716,239 +794,6 @@ func (in *Backupconfig) DeepCopy() *Backupconfig { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CDB) DeepCopyInto(out *CDB) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - out.Status = in.Status -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CDB. -func (in *CDB) DeepCopy() *CDB { - if in == nil { - return nil - } - out := new(CDB) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *CDB) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CDBAdminPassword) DeepCopyInto(out *CDBAdminPassword) { - *out = *in - out.Secret = in.Secret -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CDBAdminPassword. -func (in *CDBAdminPassword) DeepCopy() *CDBAdminPassword { - if in == nil { - return nil - } - out := new(CDBAdminPassword) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CDBAdminUser) DeepCopyInto(out *CDBAdminUser) { - *out = *in - out.Secret = in.Secret -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CDBAdminUser. -func (in *CDBAdminUser) DeepCopy() *CDBAdminUser { - if in == nil { - return nil - } - out := new(CDBAdminUser) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CDBList) DeepCopyInto(out *CDBList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]CDB, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CDBList. -func (in *CDBList) DeepCopy() *CDBList { - if in == nil { - return nil - } - out := new(CDBList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *CDBList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CDBPRIVKEY) DeepCopyInto(out *CDBPRIVKEY) { - *out = *in - out.Secret = in.Secret -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CDBPRIVKEY. -func (in *CDBPRIVKEY) DeepCopy() *CDBPRIVKEY { - if in == nil { - return nil - } - out := new(CDBPRIVKEY) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CDBPUBKEY) DeepCopyInto(out *CDBPUBKEY) { - *out = *in - out.Secret = in.Secret -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CDBPUBKEY. -func (in *CDBPUBKEY) DeepCopy() *CDBPUBKEY { - if in == nil { - return nil - } - out := new(CDBPUBKEY) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CDBSecret) DeepCopyInto(out *CDBSecret) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CDBSecret. -func (in *CDBSecret) DeepCopy() *CDBSecret { - if in == nil { - return nil - } - out := new(CDBSecret) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CDBSpec) DeepCopyInto(out *CDBSpec) { - *out = *in - out.SysAdminPwd = in.SysAdminPwd - out.CDBAdminUser = in.CDBAdminUser - out.CDBAdminPwd = in.CDBAdminPwd - out.CDBTlsKey = in.CDBTlsKey - out.CDBTlsCrt = in.CDBTlsCrt - out.ORDSPwd = in.ORDSPwd - out.WebServerUser = in.WebServerUser - out.WebServerPwd = in.WebServerPwd - if in.NodeSelector != nil { - in, out := &in.NodeSelector, &out.NodeSelector - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - out.CDBPubKey = in.CDBPubKey - out.CDBPriKey = in.CDBPriKey -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CDBSpec. -func (in *CDBSpec) DeepCopy() *CDBSpec { - if in == nil { - return nil - } - out := new(CDBSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CDBStatus) DeepCopyInto(out *CDBStatus) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CDBStatus. -func (in *CDBStatus) DeepCopy() *CDBStatus { - if in == nil { - return nil - } - out := new(CDBStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CDBSysAdminPassword) DeepCopyInto(out *CDBSysAdminPassword) { - *out = *in - out.Secret = in.Secret -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CDBSysAdminPassword. -func (in *CDBSysAdminPassword) DeepCopy() *CDBSysAdminPassword { - if in == nil { - return nil - } - out := new(CDBSysAdminPassword) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CDBTLSCRT) DeepCopyInto(out *CDBTLSCRT) { - *out = *in - out.Secret = in.Secret -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CDBTLSCRT. -func (in *CDBTLSCRT) DeepCopy() *CDBTLSCRT { - if in == nil { - return nil - } - out := new(CDBTLSCRT) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CDBTLSKEY) DeepCopyInto(out *CDBTLSKEY) { - *out = *in - out.Secret = in.Secret -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CDBTLSKEY. -func (in *CDBTLSKEY) DeepCopy() *CDBTLSKEY { - if in == nil { - return nil - } - out := new(CDBTLSKEY) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CatalogSpec) DeepCopyInto(out *CatalogSpec) { *out = *in @@ -988,6 +833,11 @@ func (in *CatalogSpec) DeepCopyInto(out *CatalogSpec) { *out = new(corev1.PullPolicy) **out = **in } + if in.CatalogConfigData != nil { + in, out := &in.CatalogConfigData, &out.CatalogConfigData + *out = new(ConfigMapData) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CatalogSpec. @@ -1015,6 +865,21 @@ func (in *CertificateSecret) DeepCopy() *CertificateSecret { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConfigMapData) DeepCopyInto(out *ConfigMapData) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigMapData. +func (in *ConfigMapData) DeepCopy() *ConfigMapData { + if in == nil { + return nil + } + out := new(ConfigMapData) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ConnectionStringProfile) DeepCopyInto(out *ConnectionStringProfile) { *out = *in @@ -1066,21 +931,203 @@ func (in *DBWalletSecret) DeepCopy() *DBWalletSecret { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DataguardBroker) DeepCopyInto(out *DataguardBroker) { +func (in *DataGuardConfig) DeepCopyInto(out *DataGuardConfig) { *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DataguardBroker. -func (in *DataguardBroker) DeepCopy() *DataguardBroker { - if in == nil { - return nil + if in.ProtectionMode != nil { + in, out := &in.ProtectionMode, &out.ProtectionMode + *out = new(string) + **out = **in } - out := new(DataguardBroker) - in.DeepCopyInto(out) + if in.TransportType != nil { + in, out := &in.TransportType, &out.TransportType + *out = new(string) + **out = **in + } + if in.PeerRole != nil { + in, out := &in.PeerRole, &out.PeerRole + *out = new(string) + **out = **in + } + if in.DbAdminPasswordSecret != nil { + in, out := &in.DbAdminPasswordSecret, &out.DbAdminPasswordSecret + *out = new(string) + **out = **in + } + if in.DbName != nil { + in, out := &in.DbName, &out.DbName + *out = new(string) + **out = **in + } + if in.HostName != nil { + in, out := &in.HostName, &out.HostName + *out = new(string) + **out = **in + } + if in.DisplayName != nil { + in, out := &in.DisplayName, &out.DisplayName + *out = new(string) + **out = **in + } + if in.PeerSidPrefix != nil { + in, out := &in.PeerSidPrefix, &out.PeerSidPrefix + *out = new(string) + **out = **in + } + if in.PeerDbSystemId != nil { + in, out := &in.PeerDbSystemId, &out.PeerDbSystemId + *out = new(string) + **out = **in + } + if in.PeerDbHomeId != nil { + in, out := &in.PeerDbHomeId, &out.PeerDbHomeId + *out = new(string) + **out = **in + } + if in.PrimaryDatabaseId != nil { + in, out := &in.PrimaryDatabaseId, &out.PrimaryDatabaseId + *out = new(string) + **out = **in + } + if in.AvailabilityDomain != nil { + in, out := &in.AvailabilityDomain, &out.AvailabilityDomain + *out = new(string) + **out = **in + } + if in.SubnetId != nil { + in, out := &in.SubnetId, &out.SubnetId + *out = new(string) + **out = **in + } + if in.Shape != nil { + in, out := &in.Shape, &out.Shape + *out = new(string) + **out = **in + } + if in.DbSystemFreeformTags != nil { + in, out := &in.DbSystemFreeformTags, &out.DbSystemFreeformTags + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DataGuardConfig. +func (in *DataGuardConfig) DeepCopy() *DataGuardConfig { + if in == nil { + return nil + } + out := new(DataGuardConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DataGuardStatus) DeepCopyInto(out *DataGuardStatus) { + *out = *in + if in.Id != nil { + in, out := &in.Id, &out.Id + *out = new(string) + **out = **in + } + if in.PeerDbSystemId != nil { + in, out := &in.PeerDbSystemId, &out.PeerDbSystemId + *out = new(string) + **out = **in + } + if in.PeerDatabaseId != nil { + in, out := &in.PeerDatabaseId, &out.PeerDatabaseId + *out = new(string) + **out = **in + } + if in.DbName != nil { + in, out := &in.DbName, &out.DbName + *out = new(string) + **out = **in + } + if in.PeerDbHomeId != nil { + in, out := &in.PeerDbHomeId, &out.PeerDbHomeId + *out = new(string) + **out = **in + } + if in.PeerRole != nil { + in, out := &in.PeerRole, &out.PeerRole + *out = new(string) + **out = **in + } + if in.Shape != nil { + in, out := &in.Shape, &out.Shape + *out = new(string) + **out = **in + } + if in.SubnetId != nil { + in, out := &in.SubnetId, &out.SubnetId + *out = new(string) + **out = **in + } + if in.PrimaryDatabaseId != nil { + in, out := &in.PrimaryDatabaseId, &out.PrimaryDatabaseId + *out = new(string) + **out = **in + } + if in.DbAdminPasswordSecret != nil { + in, out := &in.DbAdminPasswordSecret, &out.DbAdminPasswordSecret + *out = new(string) + **out = **in + } + if in.TransportType != nil { + in, out := &in.TransportType, &out.TransportType + *out = new(string) + **out = **in + } + if in.ProtectionMode != nil { + in, out := &in.ProtectionMode, &out.ProtectionMode + *out = new(string) + **out = **in + } + if in.LifecycleState != nil { + in, out := &in.LifecycleState, &out.LifecycleState + *out = new(string) + **out = **in + } + if in.PeerDataGuardAssociationId != nil { + in, out := &in.PeerDataGuardAssociationId, &out.PeerDataGuardAssociationId + *out = new(string) + **out = **in + } + if in.LifecycleDetails != nil { + in, out := &in.LifecycleDetails, &out.LifecycleDetails + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DataGuardStatus. +func (in *DataGuardStatus) DeepCopy() *DataGuardStatus { + if in == nil { + return nil + } + out := new(DataGuardStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DataguardBroker) DeepCopyInto(out *DataguardBroker) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DataguardBroker. +func (in *DataguardBroker) DeepCopy() *DataguardBroker { + if in == nil { + return nil + } + out := new(DataguardBroker) + in.DeepCopyInto(out) return out } @@ -1270,8 +1317,21 @@ func (in *DbSystemDetails) DeepCopyInto(out *DbSystemDetails) { (*out)[key] = val } } - in.DbBackupConfig.DeepCopyInto(&out.DbBackupConfig) - out.KMSConfig = in.KMSConfig + if in.DbBackupConfig != nil { + in, out := &in.DbBackupConfig, &out.DbBackupConfig + *out = new(Backupconfig) + (*in).DeepCopyInto(*out) + } + if in.KMSConfig != nil { + in, out := &in.KMSConfig, &out.KMSConfig + *out = new(KMSConfig) + **out = **in + } + if in.RestoreConfig != nil { + in, out := &in.RestoreConfig, &out.RestoreConfig + *out = new(RestoreConfig) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DbSystemDetails. @@ -1371,7 +1431,11 @@ func (in *DbcsSystemList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DbcsSystemSpec) DeepCopyInto(out *DbcsSystemSpec) { *out = *in - in.DbSystem.DeepCopyInto(&out.DbSystem) + if in.DbSystem != nil { + in, out := &in.DbSystem, &out.DbSystem + *out = new(DbSystemDetails) + (*in).DeepCopyInto(*out) + } if in.Id != nil { in, out := &in.Id, &out.Id *out = new(string) @@ -1409,7 +1473,12 @@ func (in *DbcsSystemSpec) DeepCopyInto(out *DbcsSystemSpec) { *out = new(string) **out = **in } - out.KMSConfig = in.KMSConfig + if in.KMSConfig != nil { + in, out := &in.KMSConfig, &out.KMSConfig + *out = new(KMSConfig) + **out = **in + } + in.DataGuard.DeepCopyInto(&out.DataGuard) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DbcsSystemSpec. @@ -1474,6 +1543,16 @@ func (in *DbcsSystemStatus) DeepCopyInto(out *DbcsSystemStatus) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.DataGuardStatus != nil { + in, out := &in.DataGuardStatus, &out.DataGuardStatus + *out = new(DataGuardStatus) + (*in).DeepCopyInto(*out) + } + if in.Backups != nil { + in, out := &in.Backups, &out.Backups + *out = make([]BackupInfo, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DbcsSystemStatus. @@ -1486,6 +1565,26 @@ func (in *DbcsSystemStatus) DeepCopy() *DbcsSystemStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DiskBySize) DeepCopyInto(out *DiskBySize) { + *out = *in + if in.DiskNames != nil { + in, out := &in.DiskNames, &out.DiskNames + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DiskBySize. +func (in *DiskBySize) DeepCopy() *DiskBySize { + if in == nil { + return nil + } + out := new(DiskBySize) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *EnvironmentVariable) DeepCopyInto(out *EnvironmentVariable) { *out = *in @@ -1509,21 +1608,6 @@ func (in *GlobalSettings) DeepCopyInto(out *GlobalSettings) { *out = new(bool) **out = **in } - if in.CacheMetadataGraphQLExpireAfterAccess != nil { - in, out := &in.CacheMetadataGraphQLExpireAfterAccess, &out.CacheMetadataGraphQLExpireAfterAccess - *out = new(timex.Duration) - **out = **in - } - if in.CacheMetadataGraphQLExpireAfterWrite != nil { - in, out := &in.CacheMetadataGraphQLExpireAfterWrite, &out.CacheMetadataGraphQLExpireAfterWrite - *out = new(timex.Duration) - **out = **in - } - if in.CacheMetadataTimeout != nil { - in, out := &in.CacheMetadataTimeout, &out.CacheMetadataTimeout - *out = new(timex.Duration) - **out = **in - } if in.CacheMetadataJWKSEnabled != nil { in, out := &in.CacheMetadataJWKSEnabled, &out.CacheMetadataJWKSEnabled *out = new(bool) @@ -1539,16 +1623,6 @@ func (in *GlobalSettings) DeepCopyInto(out *GlobalSettings) { *out = new(int32) **out = **in } - if in.CacheMetadataJWKSExpireAfterAccess != nil { - in, out := &in.CacheMetadataJWKSExpireAfterAccess, &out.CacheMetadataJWKSExpireAfterAccess - *out = new(timex.Duration) - **out = **in - } - if in.CacheMetadataJWKSExpireAfterWrite != nil { - in, out := &in.CacheMetadataJWKSExpireAfterWrite, &out.CacheMetadataJWKSExpireAfterWrite - *out = new(timex.Duration) - **out = **in - } if in.DatabaseAPIEnabled != nil { in, out := &in.DatabaseAPIEnabled, &out.DatabaseAPIEnabled *out = new(bool) @@ -1559,11 +1633,6 @@ func (in *GlobalSettings) DeepCopyInto(out *GlobalSettings) { *out = new(bool) **out = **in } - if in.DBInvalidPoolTimeout != nil { - in, out := &in.DBInvalidPoolTimeout, &out.DBInvalidPoolTimeout - *out = new(timex.Duration) - **out = **in - } if in.FeatureGraphQLMaxNestingDepth != nil { in, out := &in.FeatureGraphQLMaxNestingDepth, &out.FeatureGraphQLMaxNestingDepth *out = new(int32) @@ -1574,11 +1643,6 @@ func (in *GlobalSettings) DeepCopyInto(out *GlobalSettings) { *out = new(int32) **out = **in } - if in.SecurityCredentialsLockTime != nil { - in, out := &in.SecurityCredentialsLockTime, &out.SecurityCredentialsLockTime - *out = new(timex.Duration) - **out = **in - } if in.StandaloneHTTPPort != nil { in, out := &in.StandaloneHTTPPort, &out.StandaloneHTTPPort *out = new(int32) @@ -1589,11 +1653,6 @@ func (in *GlobalSettings) DeepCopyInto(out *GlobalSettings) { *out = new(int32) **out = **in } - if in.StandaloneStopTimeout != nil { - in, out := &in.StandaloneStopTimeout, &out.StandaloneStopTimeout - *out = new(timex.Duration) - **out = **in - } if in.DebugPrintDebugToScreen != nil { in, out := &in.DebugPrintDebugToScreen, &out.DebugPrintDebugToScreen *out = new(bool) @@ -1614,16 +1673,6 @@ func (in *GlobalSettings) DeepCopyInto(out *GlobalSettings) { *out = new(int32) **out = **in } - if in.MongoIdleTimeout != nil { - in, out := &in.MongoIdleTimeout, &out.MongoIdleTimeout - *out = new(timex.Duration) - **out = **in - } - if in.MongoOpTimeout != nil { - in, out := &in.MongoOpTimeout, &out.MongoOpTimeout - *out = new(timex.Duration) - **out = **in - } if in.SecurityDisableDefaultExclusionList != nil { in, out := &in.SecurityDisableDefaultExclusionList, &out.SecurityDisableDefaultExclusionList *out = new(bool) @@ -1639,6 +1688,7 @@ func (in *GlobalSettings) DeepCopyInto(out *GlobalSettings) { *out = new(bool) **out = **in } + out.APEXInstallationPersistence = in.APEXInstallationPersistence if in.CertSecret != nil { in, out := &in.CertSecret, &out.CertSecret *out = new(CertificateSecret) @@ -1755,6 +1805,11 @@ func (in *GsmSpec) DeepCopyInto(out *GsmSpec) { *out = new(corev1.PullPolicy) **out = **in } + if in.GsmConfigData != nil { + in, out := &in.GsmConfigData, &out.GsmConfigData + *out = new(ConfigMapData) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GsmSpec. @@ -1812,7 +1867,24 @@ func (in *GsmStatusDetails) DeepCopy() *GsmStatusDetails { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *K8sADBBackupSpec) DeepCopyInto(out *K8sADBBackupSpec) { +func (in *InitParams) DeepCopyInto(out *InitParams) { + *out = *in + out.GridResponseFile = in.GridResponseFile + out.DbResponseFile = in.DbResponseFile +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InitParams. +func (in *InitParams) DeepCopy() *InitParams { + if in == nil { + return nil + } + out := new(InitParams) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *K8sAcdSpec) DeepCopyInto(out *K8sAcdSpec) { *out = *in if in.Name != nil { in, out := &in.Name, &out.Name @@ -1821,18 +1893,18 @@ func (in *K8sADBBackupSpec) DeepCopyInto(out *K8sADBBackupSpec) { } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new K8sADBBackupSpec. -func (in *K8sADBBackupSpec) DeepCopy() *K8sADBBackupSpec { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new K8sAcdSpec. +func (in *K8sAcdSpec) DeepCopy() *K8sAcdSpec { if in == nil { return nil } - out := new(K8sADBBackupSpec) + out := new(K8sAcdSpec) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *K8sAcdSpec) DeepCopyInto(out *K8sAcdSpec) { +func (in *K8sAdbBackupSpec) DeepCopyInto(out *K8sAdbBackupSpec) { *out = *in if in.Name != nil { in, out := &in.Name, &out.Name @@ -1841,12 +1913,12 @@ func (in *K8sAcdSpec) DeepCopyInto(out *K8sAcdSpec) { } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new K8sAcdSpec. -func (in *K8sAcdSpec) DeepCopy() *K8sAcdSpec { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new K8sAdbBackupSpec. +func (in *K8sAdbBackupSpec) DeepCopy() *K8sAdbBackupSpec { if in == nil { return nil } - out := new(K8sAcdSpec) + out := new(K8sAdbBackupSpec) in.DeepCopyInto(out) return out } @@ -2085,6 +2157,7 @@ func (in *LRESTSpec) DeepCopyInto(out *LRESTSpec) { out.LRESTTlsCrt = in.LRESTTlsCrt out.LRESTPubKey = in.LRESTPubKey out.LRESTPriKey = in.LRESTPriKey + out.LRESTTlsCat = in.LRESTTlsCat out.LRESTPwd = in.LRESTPwd out.WebLrestServerUser = in.WebLrestServerUser out.WebLrestServerPwd = in.WebLrestServerPwd @@ -2444,22 +2517,6 @@ func (in *LTDESecret) DeepCopy() *LTDESecret { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ORDSPassword) DeepCopyInto(out *ORDSPassword) { - *out = *in - out.Secret = in.Secret -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ORDSPassword. -func (in *ORDSPassword) DeepCopy() *ORDSPassword { - if in == nil { - return nil - } - out := new(ORDSPassword) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OciAcdSpec) DeepCopyInto(out *OciAcdSpec) { *out = *in @@ -2483,8 +2540,8 @@ func (in *OciAcdSpec) DeepCopy() *OciAcdSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OciAdbSpec) DeepCopyInto(out *OciAdbSpec) { *out = *in - if in.OCID != nil { - in, out := &in.OCID, &out.OCID + if in.Id != nil { + in, out := &in.Id, &out.Id *out = new(string) **out = **in } @@ -2729,7 +2786,7 @@ func (in *OracleRestDataServiceStatus) DeepCopy() *OracleRestDataServiceStatus { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *OrdsSrvs) DeepCopyInto(out *OrdsSrvs) { +func (in *OracleRestart) DeepCopyInto(out *OracleRestart) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) @@ -2737,18 +2794,18 @@ func (in *OrdsSrvs) DeepCopyInto(out *OrdsSrvs) { in.Status.DeepCopyInto(&out.Status) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OrdsSrvs. -func (in *OrdsSrvs) DeepCopy() *OrdsSrvs { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OracleRestart. +func (in *OracleRestart) DeepCopy() *OracleRestart { if in == nil { return nil } - out := new(OrdsSrvs) + out := new(OracleRestart) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *OrdsSrvs) DeepCopyObject() runtime.Object { +func (in *OracleRestart) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } @@ -2756,293 +2813,423 @@ func (in *OrdsSrvs) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *OrdsSrvsList) DeepCopyInto(out *OrdsSrvsList) { +func (in *OracleRestartDbPwdSecretDetails) DeepCopyInto(out *OracleRestartDbPwdSecretDetails) { *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]OrdsSrvs, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OrdsSrvsList. -func (in *OrdsSrvsList) DeepCopy() *OrdsSrvsList { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OracleRestartDbPwdSecretDetails. +func (in *OracleRestartDbPwdSecretDetails) DeepCopy() *OracleRestartDbPwdSecretDetails { if in == nil { return nil } - out := new(OrdsSrvsList) + out := new(OracleRestartDbPwdSecretDetails) in.DeepCopyInto(out) return out } -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *OrdsSrvsList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *OrdsSrvsSpec) DeepCopyInto(out *OrdsSrvsSpec) { +func (in *OracleRestartInstDetailSpec) DeepCopyInto(out *OracleRestartInstDetailSpec) { *out = *in - in.GlobalSettings.DeepCopyInto(&out.GlobalSettings) - out.EncPrivKey = in.EncPrivKey - if in.PoolSettings != nil { - in, out := &in.PoolSettings, &out.PoolSettings - *out = make([]*PoolSettings, len(*in)) + if in.WorkerNode != nil { + in, out := &in.WorkerNode, &out.WorkerNode + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.EnvVars != nil { + in, out := &in.EnvVars, &out.EnvVars + *out = make([]corev1.EnvVar, len(*in)) for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(PoolSettings) - (*in).DeepCopyInto(*out) - } + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.PvcName != nil { + in, out := &in.PvcName, &out.PvcName + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val } } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OrdsSrvsSpec. -func (in *OrdsSrvsSpec) DeepCopy() *OrdsSrvsSpec { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OracleRestartInstDetailSpec. +func (in *OracleRestartInstDetailSpec) DeepCopy() *OracleRestartInstDetailSpec { if in == nil { return nil } - out := new(OrdsSrvsSpec) + out := new(OracleRestartInstDetailSpec) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *OrdsSrvsStatus) DeepCopyInto(out *OrdsSrvsStatus) { +func (in *OracleRestartList) DeepCopyInto(out *OracleRestartList) { *out = *in - if in.HTTPPort != nil { - in, out := &in.HTTPPort, &out.HTTPPort - *out = new(int32) - **out = **in - } - if in.HTTPSPort != nil { - in, out := &in.HTTPSPort, &out.HTTPSPort - *out = new(int32) - **out = **in - } - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]v1.Condition, len(*in)) + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]OracleRestart, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OrdsSrvsStatus. -func (in *OrdsSrvsStatus) DeepCopy() *OrdsSrvsStatus { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OracleRestartList. +func (in *OracleRestartList) DeepCopy() *OracleRestartList { if in == nil { return nil } - out := new(OrdsSrvsStatus) + out := new(OracleRestartList) in.DeepCopyInto(out) return out } +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *OracleRestartList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PDB) DeepCopyInto(out *PDB) { +func (in *OracleRestartNodeDetailedStatus) DeepCopyInto(out *OracleRestartNodeDetailedStatus) { *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - out.Status = in.Status + if in.PvcName != nil { + in, out := &in.PvcName, &out.PvcName + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.NodePortSvc != nil { + in, out := &in.NodePortSvc, &out.NodePortSvc + *out = make([]OracleRestartNodePortSvc, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.PortMappings != nil { + in, out := &in.PortMappings, &out.PortMappings + *out = make([]OracleRestartPortMapping, len(*in)) + copy(*out, *in) + } + if in.MountedDevices != nil { + in, out := &in.MountedDevices, &out.MountedDevices + *out = make([]string, len(*in)) + copy(*out, *in) + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PDB. -func (in *PDB) DeepCopy() *PDB { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OracleRestartNodeDetailedStatus. +func (in *OracleRestartNodeDetailedStatus) DeepCopy() *OracleRestartNodeDetailedStatus { if in == nil { return nil } - out := new(PDB) + out := new(OracleRestartNodeDetailedStatus) in.DeepCopyInto(out) return out } -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *PDB) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OracleRestartNodePortSvc) DeepCopyInto(out *OracleRestartNodePortSvc) { + *out = *in + if in.PortMappings != nil { + in, out := &in.PortMappings, &out.PortMappings + *out = make([]OracleRestartPortMapping, len(*in)) + copy(*out, *in) } - return nil + if in.SvcAnnotation != nil { + in, out := &in.SvcAnnotation, &out.SvcAnnotation + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.OnsTargetPort != nil { + in, out := &in.OnsTargetPort, &out.OnsTargetPort + *out = new(int32) + **out = **in + } + if in.OnsLocalPort != nil { + in, out := &in.OnsLocalPort, &out.OnsLocalPort + *out = new(int32) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OracleRestartNodePortSvc. +func (in *OracleRestartNodePortSvc) DeepCopy() *OracleRestartNodePortSvc { + if in == nil { + return nil + } + out := new(OracleRestartNodePortSvc) + in.DeepCopyInto(out) + return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PDBAdminName) DeepCopyInto(out *PDBAdminName) { +func (in *OracleRestartNodestatus) DeepCopyInto(out *OracleRestartNodestatus) { *out = *in - out.Secret = in.Secret + if in.NodeDetails != nil { + in, out := &in.NodeDetails, &out.NodeDetails + *out = new(OracleRestartNodeDetailedStatus) + (*in).DeepCopyInto(*out) + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PDBAdminName. -func (in *PDBAdminName) DeepCopy() *PDBAdminName { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OracleRestartNodestatus. +func (in *OracleRestartNodestatus) DeepCopy() *OracleRestartNodestatus { if in == nil { return nil } - out := new(PDBAdminName) + out := new(OracleRestartNodestatus) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PDBAdminPassword) DeepCopyInto(out *PDBAdminPassword) { +func (in *OracleRestartPortMapping) DeepCopyInto(out *OracleRestartPortMapping) { *out = *in - out.Secret = in.Secret } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PDBAdminPassword. -func (in *PDBAdminPassword) DeepCopy() *PDBAdminPassword { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OracleRestartPortMapping. +func (in *OracleRestartPortMapping) DeepCopy() *OracleRestartPortMapping { if in == nil { return nil } - out := new(PDBAdminPassword) + out := new(OracleRestartPortMapping) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PDBConfig) DeepCopyInto(out *PDBConfig) { +func (in *OracleRestartSpec) DeepCopyInto(out *OracleRestartSpec) { *out = *in - if in.PdbName != nil { - in, out := &in.PdbName, &out.PdbName - *out = new(string) + in.InstDetails.DeepCopyInto(&out.InstDetails) + if in.ConfigParams != nil { + in, out := &in.ConfigParams, &out.ConfigParams + *out = new(InitParams) **out = **in } - if in.PdbAdminPassword != nil { - in, out := &in.PdbAdminPassword, &out.PdbAdminPassword - *out = new(string) - **out = **in + if in.AsmStorageDetails != nil { + in, out := &in.AsmStorageDetails, &out.AsmStorageDetails + *out = new(AsmDiskDetails) + (*in).DeepCopyInto(*out) } - if in.TdeWalletPassword != nil { - in, out := &in.TdeWalletPassword, &out.TdeWalletPassword - *out = new(string) + if in.SshKeySecret != nil { + in, out := &in.SshKeySecret, &out.SshKeySecret + *out = new(OracleRestartSshSecretDetails) **out = **in } - if in.ShouldPdbAdminAccountBeLocked != nil { - in, out := &in.ShouldPdbAdminAccountBeLocked, &out.ShouldPdbAdminAccountBeLocked - *out = new(bool) + if in.ImagePullPolicy != nil { + in, out := &in.ImagePullPolicy, &out.ImagePullPolicy + *out = new(corev1.PullPolicy) **out = **in } - if in.FreeformTags != nil { - in, out := &in.FreeformTags, &out.FreeformTags - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } + if in.ReadinessProbe != nil { + in, out := &in.ReadinessProbe, &out.ReadinessProbe + *out = new(corev1.Probe) + (*in).DeepCopyInto(*out) } - if in.IsDelete != nil { - in, out := &in.IsDelete, &out.IsDelete - *out = new(bool) + if in.SecurityContext != nil { + in, out := &in.SecurityContext, &out.SecurityContext + *out = new(corev1.PodSecurityContext) + (*in).DeepCopyInto(*out) + } + if in.DbSecret != nil { + in, out := &in.DbSecret, &out.DbSecret + *out = new(OracleRestartDbPwdSecretDetails) **out = **in } - if in.PluggableDatabaseId != nil { - in, out := &in.PluggableDatabaseId, &out.PluggableDatabaseId - *out = new(string) + if in.TdeWalletSecret != nil { + in, out := &in.TdeWalletSecret, &out.TdeWalletSecret + *out = new(OracleRestartDbPwdSecretDetails) **out = **in } + in.ServiceDetails.DeepCopyInto(&out.ServiceDetails) + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = new(corev1.ResourceRequirements) + (*in).DeepCopyInto(*out) + } + in.LbService.DeepCopyInto(&out.LbService) + in.NodePortSvc.DeepCopyInto(&out.NodePortSvc) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PDBConfig. -func (in *PDBConfig) DeepCopy() *PDBConfig { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OracleRestartSpec. +func (in *OracleRestartSpec) DeepCopy() *OracleRestartSpec { if in == nil { return nil } - out := new(PDBConfig) + out := new(OracleRestartSpec) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PDBConfigStatus) DeepCopyInto(out *PDBConfigStatus) { +func (in *OracleRestartSshSecretDetails) DeepCopyInto(out *OracleRestartSshSecretDetails) { *out = *in - if in.PdbName != nil { - in, out := &in.PdbName, &out.PdbName - *out = new(string) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OracleRestartSshSecretDetails. +func (in *OracleRestartSshSecretDetails) DeepCopy() *OracleRestartSshSecretDetails { + if in == nil { + return nil + } + out := new(OracleRestartSshSecretDetails) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OracleRestartStatus) DeepCopyInto(out *OracleRestartStatus) { + *out = *in + if in.OracleRestartNodes != nil { + in, out := &in.OracleRestartNodes, &out.OracleRestartNodes + *out = make([]*OracleRestartNodestatus, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(OracleRestartNodestatus) + (*in).DeepCopyInto(*out) + } + } + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.InstDetails != nil { + in, out := &in.InstDetails, &out.InstDetails + *out = new(OracleRestartInstDetailSpec) + (*in).DeepCopyInto(*out) + } + if in.ConfigParams != nil { + in, out := &in.ConfigParams, &out.ConfigParams + *out = new(InitParams) **out = **in } - if in.ShouldPdbAdminAccountBeLocked != nil { - in, out := &in.ShouldPdbAdminAccountBeLocked, &out.ShouldPdbAdminAccountBeLocked - *out = new(bool) + if in.AsmDetails != nil { + in, out := &in.AsmDetails, &out.AsmDetails + *out = new(AsmInstanceStatus) + (*in).DeepCopyInto(*out) + } + if in.NfsStorageDetails != nil { + in, out := &in.NfsStorageDetails, &out.NfsStorageDetails + *out = new(corev1.NFSVolumeSource) **out = **in } - if in.FreeformTags != nil { - in, out := &in.FreeformTags, &out.FreeformTags - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } + if in.SshKeySecret != nil { + in, out := &in.SshKeySecret, &out.SshKeySecret + *out = new(OracleRestartSshSecretDetails) + **out = **in } - if in.PluggableDatabaseId != nil { - in, out := &in.PluggableDatabaseId, &out.PluggableDatabaseId + if in.ImagePullPolicy != nil { + in, out := &in.ImagePullPolicy, &out.ImagePullPolicy + *out = new(corev1.PullPolicy) + **out = **in + } + if in.ReadinessProbe != nil { + in, out := &in.ReadinessProbe, &out.ReadinessProbe + *out = new(corev1.Probe) + (*in).DeepCopyInto(*out) + } + if in.SecurityContext != nil { + in, out := &in.SecurityContext, &out.SecurityContext + *out = new(corev1.PodSecurityContext) + (*in).DeepCopyInto(*out) + } + if in.ExternalSvcType != nil { + in, out := &in.ExternalSvcType, &out.ExternalSvcType *out = new(string) **out = **in } + if in.DbSecret != nil { + in, out := &in.DbSecret, &out.DbSecret + *out = new(OracleRestartDbPwdSecretDetails) + **out = **in + } + if in.TdeWalletSecret != nil { + in, out := &in.TdeWalletSecret, &out.TdeWalletSecret + *out = new(OracleRestartDbPwdSecretDetails) + **out = **in + } + in.ServiceDetails.DeepCopyInto(&out.ServiceDetails) + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = new(corev1.ResourceRequirements) + (*in).DeepCopyInto(*out) + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PDBConfigStatus. -func (in *PDBConfigStatus) DeepCopy() *PDBConfigStatus { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OracleRestartStatus. +func (in *OracleRestartStatus) DeepCopy() *OracleRestartStatus { if in == nil { return nil } - out := new(PDBConfigStatus) + out := new(OracleRestartStatus) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PDBDetailsStatus) DeepCopyInto(out *PDBDetailsStatus) { +func (in *OrdsSrvs) DeepCopyInto(out *OrdsSrvs) { *out = *in - if in.PDBConfigStatus != nil { - in, out := &in.PDBConfigStatus, &out.PDBConfigStatus - *out = make([]PDBConfigStatus, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PDBDetailsStatus. -func (in *PDBDetailsStatus) DeepCopy() *PDBDetailsStatus { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OrdsSrvs. +func (in *OrdsSrvs) DeepCopy() *OrdsSrvs { if in == nil { return nil } - out := new(PDBDetailsStatus) + out := new(OrdsSrvs) in.DeepCopyInto(out) return out } +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *OrdsSrvs) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PDBList) DeepCopyInto(out *PDBList) { +func (in *OrdsSrvsList) DeepCopyInto(out *OrdsSrvsList) { *out = *in out.TypeMeta = in.TypeMeta in.ListMeta.DeepCopyInto(&out.ListMeta) if in.Items != nil { in, out := &in.Items, &out.Items - *out = make([]PDB, len(*in)) + *out = make([]OrdsSrvs, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PDBList. -func (in *PDBList) DeepCopy() *PDBList { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OrdsSrvsList. +func (in *OrdsSrvsList) DeepCopy() *OrdsSrvsList { if in == nil { return nil } - out := new(PDBList) + out := new(OrdsSrvsList) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *PDBList) DeepCopyObject() runtime.Object { +func (in *OrdsSrvsList) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } @@ -3050,167 +3237,172 @@ func (in *PDBList) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PDBPRIVKEY) DeepCopyInto(out *PDBPRIVKEY) { +func (in *OrdsSrvsSpec) DeepCopyInto(out *OrdsSrvsSpec) { *out = *in - out.Secret = in.Secret -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PDBPRIVKEY. -func (in *PDBPRIVKEY) DeepCopy() *PDBPRIVKEY { - if in == nil { - return nil + in.GlobalSettings.DeepCopyInto(&out.GlobalSettings) + out.EncPrivKey = in.EncPrivKey + if in.PoolSettings != nil { + in, out := &in.PoolSettings, &out.PoolSettings + *out = make([]*PoolSettings, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(PoolSettings) + (*in).DeepCopyInto(*out) + } + } } - out := new(PDBPRIVKEY) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PDBPUBKEY) DeepCopyInto(out *PDBPUBKEY) { - *out = *in - out.Secret = in.Secret } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PDBPUBKEY. -func (in *PDBPUBKEY) DeepCopy() *PDBPUBKEY { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OrdsSrvsSpec. +func (in *OrdsSrvsSpec) DeepCopy() *OrdsSrvsSpec { if in == nil { return nil } - out := new(PDBPUBKEY) + out := new(OrdsSrvsSpec) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PDBSecret) DeepCopyInto(out *PDBSecret) { +func (in *OrdsSrvsStatus) DeepCopyInto(out *OrdsSrvsStatus) { *out = *in + if in.HTTPPort != nil { + in, out := &in.HTTPPort, &out.HTTPPort + *out = new(int32) + **out = **in + } + if in.HTTPSPort != nil { + in, out := &in.HTTPSPort, &out.HTTPSPort + *out = new(int32) + **out = **in + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PDBSecret. -func (in *PDBSecret) DeepCopy() *PDBSecret { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OrdsSrvsStatus. +func (in *OrdsSrvsStatus) DeepCopy() *OrdsSrvsStatus { if in == nil { return nil } - out := new(PDBSecret) + out := new(OrdsSrvsStatus) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PDBSpec) DeepCopyInto(out *PDBSpec) { +func (in *PDBConfig) DeepCopyInto(out *PDBConfig) { *out = *in - out.PDBTlsKey = in.PDBTlsKey - out.PDBTlsCrt = in.PDBTlsCrt - out.PDBTlsCat = in.PDBTlsCat - out.AdminName = in.AdminName - out.AdminPwd = in.AdminPwd - out.WebServerUsr = in.WebServerUsr - out.WebServerPwd = in.WebServerPwd - if in.ReuseTempFile != nil { - in, out := &in.ReuseTempFile, &out.ReuseTempFile - *out = new(bool) + if in.PdbName != nil { + in, out := &in.PdbName, &out.PdbName + *out = new(string) **out = **in } - if in.UnlimitedStorage != nil { - in, out := &in.UnlimitedStorage, &out.UnlimitedStorage - *out = new(bool) + if in.PdbAdminPassword != nil { + in, out := &in.PdbAdminPassword, &out.PdbAdminPassword + *out = new(string) **out = **in } - if in.AsClone != nil { - in, out := &in.AsClone, &out.AsClone - *out = new(bool) + if in.TdeWalletPassword != nil { + in, out := &in.TdeWalletPassword, &out.TdeWalletPassword + *out = new(string) **out = **in } - if in.TDEImport != nil { - in, out := &in.TDEImport, &out.TDEImport + if in.ShouldPdbAdminAccountBeLocked != nil { + in, out := &in.ShouldPdbAdminAccountBeLocked, &out.ShouldPdbAdminAccountBeLocked *out = new(bool) **out = **in } - if in.TDEExport != nil { - in, out := &in.TDEExport, &out.TDEExport + if in.FreeformTags != nil { + in, out := &in.FreeformTags, &out.FreeformTags + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.IsDelete != nil { + in, out := &in.IsDelete, &out.IsDelete *out = new(bool) **out = **in } - out.TDEPassword = in.TDEPassword - out.TDESecret = in.TDESecret - if in.GetScript != nil { - in, out := &in.GetScript, &out.GetScript - *out = new(bool) + if in.PluggableDatabaseId != nil { + in, out := &in.PluggableDatabaseId, &out.PluggableDatabaseId + *out = new(string) **out = **in } - out.PDBPubKey = in.PDBPubKey - out.PDBPriKey = in.PDBPriKey } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PDBSpec. -func (in *PDBSpec) DeepCopy() *PDBSpec { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PDBConfig. +func (in *PDBConfig) DeepCopy() *PDBConfig { if in == nil { return nil } - out := new(PDBSpec) + out := new(PDBConfig) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PDBStatus) DeepCopyInto(out *PDBStatus) { +func (in *PDBConfigStatus) DeepCopyInto(out *PDBConfigStatus) { *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PDBStatus. -func (in *PDBStatus) DeepCopy() *PDBStatus { - if in == nil { - return nil + if in.PdbName != nil { + in, out := &in.PdbName, &out.PdbName + *out = new(string) + **out = **in } - out := new(PDBStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PDBTLSCAT) DeepCopyInto(out *PDBTLSCAT) { - *out = *in - out.Secret = in.Secret -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PDBTLSCAT. -func (in *PDBTLSCAT) DeepCopy() *PDBTLSCAT { - if in == nil { - return nil + if in.ShouldPdbAdminAccountBeLocked != nil { + in, out := &in.ShouldPdbAdminAccountBeLocked, &out.ShouldPdbAdminAccountBeLocked + *out = new(bool) + **out = **in + } + if in.FreeformTags != nil { + in, out := &in.FreeformTags, &out.FreeformTags + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.PluggableDatabaseId != nil { + in, out := &in.PluggableDatabaseId, &out.PluggableDatabaseId + *out = new(string) + **out = **in } - out := new(PDBTLSCAT) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PDBTLSCRT) DeepCopyInto(out *PDBTLSCRT) { - *out = *in - out.Secret = in.Secret } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PDBTLSCRT. -func (in *PDBTLSCRT) DeepCopy() *PDBTLSCRT { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PDBConfigStatus. +func (in *PDBConfigStatus) DeepCopy() *PDBConfigStatus { if in == nil { return nil } - out := new(PDBTLSCRT) + out := new(PDBConfigStatus) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PDBTLSKEY) DeepCopyInto(out *PDBTLSKEY) { +func (in *PDBDetailsStatus) DeepCopyInto(out *PDBDetailsStatus) { *out = *in - out.Secret = in.Secret + if in.PDBConfigStatus != nil { + in, out := &in.PDBConfigStatus, &out.PDBConfigStatus + *out = make([]PDBConfigStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PDBTLSKEY. -func (in *PDBTLSKEY) DeepCopy() *PDBTLSKEY { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PDBDetailsStatus. +func (in *PDBDetailsStatus) DeepCopy() *PDBDetailsStatus { if in == nil { return nil } - out := new(PDBTLSKEY) + out := new(PDBDetailsStatus) in.DeepCopyInto(out) return out } @@ -3267,17 +3459,27 @@ func (in *PasswordSpec) DeepCopy() *PasswordSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Persistence) DeepCopyInto(out *Persistence) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Persistence. +func (in *Persistence) DeepCopy() *Persistence { + if in == nil { + return nil + } + out := new(Persistence) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PoolSettings) DeepCopyInto(out *PoolSettings) { *out = *in out.DBSecret = in.DBSecret out.DBAdminUserSecret = in.DBAdminUserSecret out.DBCDBAdminUserSecret = in.DBCDBAdminUserSecret - if in.DBPoolDestroyTimeout != nil { - in, out := &in.DBPoolDestroyTimeout, &out.DBPoolDestroyTimeout - *out = new(timex.Duration) - **out = **in - } if in.DebugTrackResources != nil { in, out := &in.DebugTrackResources, &out.DebugTrackResources *out = new(bool) @@ -3308,31 +3510,6 @@ func (in *PoolSettings) DeepCopyInto(out *PoolSettings) { *out = new(int32) **out = **in } - if in.SecurityJWKSConnectionTimeout != nil { - in, out := &in.SecurityJWKSConnectionTimeout, &out.SecurityJWKSConnectionTimeout - *out = new(timex.Duration) - **out = **in - } - if in.SecurityJWKSReadTimeout != nil { - in, out := &in.SecurityJWKSReadTimeout, &out.SecurityJWKSReadTimeout - *out = new(timex.Duration) - **out = **in - } - if in.SecurityJWKSRefreshInterval != nil { - in, out := &in.SecurityJWKSRefreshInterval, &out.SecurityJWKSRefreshInterval - *out = new(timex.Duration) - **out = **in - } - if in.SecurityJWTAllowedSkew != nil { - in, out := &in.SecurityJWTAllowedSkew, &out.SecurityJWTAllowedSkew - *out = new(timex.Duration) - **out = **in - } - if in.SecurityJWTAllowedAge != nil { - in, out := &in.SecurityJWTAllowedAge, &out.SecurityJWTAllowedAge - *out = new(timex.Duration) - **out = **in - } if in.DBPort != nil { in, out := &in.DBPort, &out.DBPort *out = new(int32) @@ -3353,11 +3530,6 @@ func (in *PoolSettings) DeepCopyInto(out *PoolSettings) { *out = new(int32) **out = **in } - if in.JDBCMaxConnectionReuseTime != nil { - in, out := &in.JDBCMaxConnectionReuseTime, &out.JDBCMaxConnectionReuseTime - *out = new(int32) - **out = **in - } if in.JDBCSecondsToTrustIdleConnection != nil { in, out := &in.JDBCSecondsToTrustIdleConnection, &out.JDBCSecondsToTrustIdleConnection *out = new(int32) @@ -3451,6 +3623,45 @@ func (in *PriVKey) DeepCopy() *PriVKey { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResponseFile) DeepCopyInto(out *ResponseFile) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResponseFile. +func (in *ResponseFile) DeepCopy() *ResponseFile { + if in == nil { + return nil + } + out := new(ResponseFile) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RestoreConfig) DeepCopyInto(out *RestoreConfig) { + *out = *in + if in.Timestamp != nil { + in, out := &in.Timestamp, &out.Timestamp + *out = (*in).DeepCopy() + } + if in.SCN != nil { + in, out := &in.SCN, &out.SCN + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RestoreConfig. +func (in *RestoreConfig) DeepCopy() *RestoreConfig { + if in == nil { + return nil + } + out := new(RestoreConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SecretDetails) DeepCopyInto(out *SecretDetails) { *out = *in @@ -3466,6 +3677,61 @@ func (in *SecretDetails) DeepCopy() *SecretDetails { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceSpec) DeepCopyInto(out *ServiceSpec) { + *out = *in + if in.Preferred != nil { + in, out := &in.Preferred, &out.Preferred + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Available != nil { + in, out := &in.Available, &out.Available + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceSpec. +func (in *ServiceSpec) DeepCopy() *ServiceSpec { + if in == nil { + return nil + } + out := new(ServiceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ShardGroup) DeepCopyInto(out *ShardGroup) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ShardGroup. +func (in *ShardGroup) DeepCopy() *ShardGroup { + if in == nil { + return nil + } + out := new(ShardGroup) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ShardSpace) DeepCopyInto(out *ShardSpace) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ShardSpace. +func (in *ShardSpace) DeepCopy() *ShardSpace { + if in == nil { + return nil + } + out := new(ShardSpace) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ShardSpec) DeepCopyInto(out *ShardSpec) { *out = *in @@ -3505,6 +3771,11 @@ func (in *ShardSpec) DeepCopyInto(out *ShardSpec) { *out = new(corev1.PullPolicy) **out = **in } + if in.ShardConfigData != nil { + in, out := &in.ShardConfigData, &out.ShardConfigData + *out = new(ConfigMapData) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ShardSpec. @@ -3630,6 +3901,13 @@ func (in *ShardingDatabaseSpec) DeepCopyInto(out *ShardingDatabaseSpec) { *out = new(SecretDetails) **out = **in } + if in.ShardInfo != nil { + in, out := &in.ShardInfo, &out.ShardInfo + *out = make([]ShardingDetails, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ShardingDatabaseSpec. @@ -3679,6 +3957,31 @@ func (in *ShardingDatabaseStatus) DeepCopy() *ShardingDatabaseStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ShardingDetails) DeepCopyInto(out *ShardingDetails) { + *out = *in + if in.ShardGroupDetails != nil { + in, out := &in.ShardGroupDetails, &out.ShardGroupDetails + *out = new(ShardGroup) + **out = **in + } + if in.ShardSpaceDetails != nil { + in, out := &in.ShardSpaceDetails, &out.ShardSpaceDetails + *out = new(ShardSpace) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ShardingDetails. +func (in *ShardingDetails) DeepCopy() *ShardingDetails { + if in == nil { + return nil + } + out := new(ShardingDetails) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SingleInstanceDatabase) DeepCopyInto(out *SingleInstanceDatabase) { *out = *in @@ -3964,38 +4267,6 @@ func (in *SourceSpec) DeepCopy() *SourceSpec { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TDEPwd) DeepCopyInto(out *TDEPwd) { - *out = *in - out.Secret = in.Secret -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TDEPwd. -func (in *TDEPwd) DeepCopy() *TDEPwd { - if in == nil { - return nil - } - out := new(TDEPwd) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TDESecret) DeepCopyInto(out *TDESecret) { - *out = *in - out.Secret = in.Secret -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TDESecret. -func (in *TDESecret) DeepCopy() *TDESecret { - if in == nil { - return nil - } - out := new(TDESecret) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TNSAdminSecret) DeepCopyInto(out *TNSAdminSecret) { *out = *in @@ -4147,67 +4418,3 @@ func (in *WebLrpdbServerUser) DeepCopy() *WebLrpdbServerUser { in.DeepCopyInto(out) return out } - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *WebServerPassword) DeepCopyInto(out *WebServerPassword) { - *out = *in - out.Secret = in.Secret -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebServerPassword. -func (in *WebServerPassword) DeepCopy() *WebServerPassword { - if in == nil { - return nil - } - out := new(WebServerPassword) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *WebServerPasswordPDB) DeepCopyInto(out *WebServerPasswordPDB) { - *out = *in - out.Secret = in.Secret -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebServerPasswordPDB. -func (in *WebServerPasswordPDB) DeepCopy() *WebServerPasswordPDB { - if in == nil { - return nil - } - out := new(WebServerPasswordPDB) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *WebServerUser) DeepCopyInto(out *WebServerUser) { - *out = *in - out.Secret = in.Secret -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebServerUser. -func (in *WebServerUser) DeepCopy() *WebServerUser { - if in == nil { - return nil - } - out := new(WebServerUser) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *WebServerUserPDB) DeepCopyInto(out *WebServerUserPDB) { - *out = *in - out.Secret = in.Secret -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebServerUserPDB. -func (in *WebServerUserPDB) DeepCopy() *WebServerUserPDB { - if in == nil { - return nil - } - out := new(WebServerUserPDB) - in.DeepCopyInto(out) - return out -} diff --git a/apis/observability/v1/databaseobserver_types.go b/apis/observability/v1/databaseobserver_types.go index 642ff18b..10a6c913 100644 --- a/apis/observability/v1/databaseobserver_types.go +++ b/apis/observability/v1/databaseobserver_types.go @@ -48,78 +48,107 @@ type StatusEnum string // DatabaseObserverSpec defines the desired state of DatabaseObserver type DatabaseObserverSpec struct { - Database DatabaseObserverDatabase `json:"database,omitempty"` - Exporter DatabaseObserverExporterConfig `json:"exporter,omitempty"` - ExporterConfig DatabaseObserverConfigMap `json:"configuration,omitempty"` - Prometheus PrometheusConfig `json:"prometheus,omitempty"` - OCIConfig OCIConfigSpec `json:"ociConfig,omitempty"` - Replicas int32 `json:"replicas,omitempty"` - Log LogConfig `json:"log,omitempty"` - InheritLabels []string `json:"inheritLabels,omitempty"` - ExporterSidecars []corev1.Container `json:"sidecars,omitempty"` - SideCarVolumes []corev1.Volume `json:"sidecarVolumes,omitempty"` + Database DatabaseConfig `json:"database,omitempty"` + Databases map[string]MultiDatabaseConfig `json:"databases,omitempty"` + Wallet WalletSecret `json:"wallet,omitempty"` + Deployment ExporterDeployment `json:"deployment,omitempty"` + Service ExporterService `json:"service,omitempty"` + ServiceMonitor ExporterServiceMonitor `json:"serviceMonitor,omitempty"` + ExporterConfig ExporterConfig `json:"exporterConfig,omitempty"` + OCIConfig OCIConfig `json:"ociConfig,omitempty"` + AzureConfig AzureConfig `json:"azureConfig,omitempty"` + Metrics MetricsConfig `json:"metrics,omitempty"` + Log LogConfig `json:"log,omitempty"` + InheritLabels []string `json:"inheritLabels,omitempty"` + Sidecar SidecarConfig `json:"sidecar,omitempty"` + Replicas int32 `json:"replicas,omitempty"` +} + +// SidecarConfig defines sidecar containers and volumes to add +type SidecarConfig struct { + Containers []corev1.Container `json:"containers,omitempty"` + Volumes []corev1.Volume `json:"volumes,omitempty"` +} + +// ExporterConfig defines configMap used for exporter configuration +type ExporterConfig struct { + ConfigMap ConfigMapDetails `json:"configMap,omitempty"` + MountPath string `json:"mountPath,omitempty"` } // LogConfig defines the configuration details relation to the logs of DatabaseObserver type LogConfig struct { - Path string `json:"path,omitempty"` - Filename string `json:"filename,omitempty"` - Volume LogVolume `json:"volume,omitempty"` + Disable bool `json:"disable,omitempty"` + Destination string `json:"destination,omitempty"` + Filename string `json:"filename,omitempty"` + Volume LogVolume `json:"volume,omitempty"` } +// LogVolume defines the shared volume between the exporter container and other containers type LogVolume struct { - Name string `json:"name,omitempty"` - PersistentVolumeClaim LogVolumePVClaim `json:"persistentVolumeClaim,omitempty"` + Name string `json:"name,omitempty"` + PersistentVolumeClaim LogVolumePVC `json:"persistentVolumeClaim,omitempty"` } -type LogVolumePVClaim struct { +// LogVolumePVC defines the PVC in which to store the logs +type LogVolumePVC struct { ClaimName string `json:"claimName,omitempty"` } -// DatabaseObserverDatabase defines the database details used for DatabaseObserver -type DatabaseObserverDatabase struct { - DBUser DBSecret `json:"dbUser,omitempty"` - DBPassword DBSecretWithVault `json:"dbPassword,omitempty"` - DBWallet DBSecret `json:"dbWallet,omitempty"` - DBConnectionString DBSecret `json:"dbConnectionString,omitempty"` +// DatabaseConfig defines the database details used for DatabaseObserver +type DatabaseConfig struct { + DBUser DBSecret `json:"dbUser,omitempty"` + DBPassword DBSecret `json:"dbPassword,omitempty"` + DBConnectionString DBSecret `json:"dbConnectionString,omitempty"` + OCIVault DBOCIVault `json:"oci,omitempty"` + AzureVault DBAzureVault `json:"azure,omitempty"` } -// DatabaseObserverExporterConfig defines the configuration details related to the exporters of DatabaseObserver -type DatabaseObserverExporterConfig struct { - Deployment DatabaseObserverDeployment `json:"deployment,omitempty"` - Service DatabaseObserverService `json:"service,omitempty"` +// MultiDatabaseConfig defines each database details used for DatabaseObserver +type MultiDatabaseConfig struct { + DBUser DBSecret `json:"dbUser,omitempty"` + DBPassword DBSecret `json:"dbPassword,omitempty"` + DBConnectionString DBSecret `json:"dbConnectionString,omitempty"` } -// DatabaseObserverDeployment defines the exporter deployment component of DatabaseObserver -type DatabaseObserverDeployment struct { - ExporterImage string `json:"image,omitempty"` - SecurityContext *corev1.SecurityContext `json:"securityContext,omitempty"` - ExporterArgs []string `json:"args,omitempty"` - ExporterCommands []string `json:"commands,omitempty"` - ExporterEnvs map[string]string `json:"env,omitempty"` - Labels map[string]string `json:"labels,omitempty"` - DeploymentPodTemplate DeploymentPodTemplate `json:"podTemplate,omitempty"` +// DBAzureVault defines Azure Vault details +type DBAzureVault struct { + VaultID string `json:"vaultID,omitempty"` + VaultUsernameSecret string `json:"vaultUsernameSecret,omitempty"` + VaultPasswordSecret string `json:"vaultPasswordSecret,omitempty"` +} + +// DBOCIVault defines OCI Vault details +type DBOCIVault struct { + VaultID string `json:"vaultID,omitempty"` + VaultPasswordSecret string `json:"vaultPasswordSecret,omitempty"` +} + +// ExporterDeployment defines the exporter deployment component of DatabaseObserver +type ExporterDeployment struct { + ExporterImage string `json:"image,omitempty"` + SecurityContext *corev1.SecurityContext `json:"securityContext,omitempty"` + PodSecurityContext *corev1.PodSecurityContext `json:"podSecurityContext,omitempty"` + ExporterArgs []string `json:"args,omitempty"` + ExporterCommands []string `json:"commands,omitempty"` + ExporterEnvs map[string]string `json:"env,omitempty"` + Labels map[string]string `json:"labels,omitempty"` + DeploymentPodTemplate DeploymentPodTemplate `json:"podTemplate,omitempty"` } // DeploymentPodTemplate defines the labels for the DatabaseObserver pods component of a deployment type DeploymentPodTemplate struct { - SecurityContext *corev1.PodSecurityContext `json:"securityContext,omitempty"` - Labels map[string]string `json:"labels,omitempty"` + Labels map[string]string `json:"labels,omitempty"` } -// DatabaseObserverService defines the exporter service component of DatabaseObserver -type DatabaseObserverService struct { +// ExporterService defines the exporter service component of DatabaseObserver +type ExporterService struct { Ports []corev1.ServicePort `json:"ports,omitempty"` Labels map[string]string `json:"labels,omitempty"` } -// PrometheusConfig defines the generated resources for Prometheus -type PrometheusConfig struct { - ServiceMonitor PrometheusServiceMonitor `json:"serviceMonitor,omitempty"` -} - -// PrometheusServiceMonitor defines DatabaseObserver servicemonitor spec -type PrometheusServiceMonitor struct { +// ExporterServiceMonitor defines DatabaseObserver servicemonitor spec +type ExporterServiceMonitor struct { Labels map[string]string `json:"labels,omitempty"` NamespaceSelector *monitorv1.NamespaceSelector `json:"namespaceSelector,omitempty"` Endpoints []monitorv1.Endpoint `json:"endpoints,omitempty"` @@ -129,40 +158,57 @@ type PrometheusServiceMonitor struct { type DBSecret struct { Key string `json:"key,omitempty"` SecretName string `json:"secret,omitempty"` + EnvName string `json:"envName,omitempty"` +} + +// WalletSecret defines secret and where the wallet will be mounted if provided +type WalletSecret struct { + SecretName string `json:"secret,omitempty"` + MountPath string `json:"mountPath,omitempty"` + AdditionalWallets []AdditionalWalletSecrets `json:"additional,omitempty"` } -// DBSecretWithVault defines secrets used in reference with vault fields -type DBSecretWithVault struct { - Key string `json:"key,omitempty"` - SecretName string `json:"secret,omitempty"` - VaultOCID string `json:"vaultOCID,omitempty"` - VaultSecretName string `json:"vaultSecretName,omitempty"` +// AdditionalWalletSecrets defines multiple other secrets and where the wallet will be mounted if provided +type AdditionalWalletSecrets struct { + Name string `json:"name,omitempty"` + SecretName string `json:"secret,omitempty"` + MountPath string `json:"mountPath,omitempty"` } -// DatabaseObserverConfigMap defines configMap used for metrics configuration -type DatabaseObserverConfigMap struct { - Configmap ConfigMapDetails `json:"configMap,omitempty"` +// MetricsConfig defines configMap used for multiple metrics TOML configuration +type MetricsConfig struct { + Configmap []ConfigMapDetails `json:"configMap,omitempty"` } -// ConfigMapDetails defines the configmap name +// ConfigMapDetails defines the configmap name used by the exporterConfig and metricsConfig type ConfigMapDetails struct { Key string `json:"key,omitempty"` Name string `json:"name,omitempty"` } -// OCIConfigSpec defines the configmap name and secret name used for connecting to OCI -type OCIConfigSpec struct { - ConfigMapName string `json:"configMapName,omitempty"` - SecretName string `json:"secretName,omitempty"` +// OCIConfig defines the configmap name and secret name used for connecting to OCI +type OCIConfig struct { + ConfigMap ConfigMapDetails `json:"configMap,omitempty"` + PrivateKey ConfigPrivateKey `json:"privateKey,omitempty"` + MountPath string `json:"mountPath,omitempty"` +} + +type ConfigPrivateKey struct { + SecretName string `json:"secret,omitempty"` +} + +// AzureConfig defines the configmap name and secret name used for connecting to Azure +type AzureConfig struct { + ConfigMap ConfigMapDetails `json:"configMap,omitempty"` } // DatabaseObserverStatus defines the observed state of DatabaseObserver type DatabaseObserverStatus struct { - Conditions []metav1.Condition `json:"conditions"` - Status string `json:"status,omitempty"` - ExporterConfig string `json:"exporterConfig"` - Version string `json:"version"` - Replicas int `json:"replicas,omitempty"` + Conditions []metav1.Condition `json:"conditions"` + Status string `json:"status,omitempty"` + MetricsConfig string `json:"metricsConfig"` + Version string `json:"version"` + Replicas int `json:"replicas,omitempty"` } //+kubebuilder:object:root=true @@ -170,7 +216,7 @@ type DatabaseObserverStatus struct { // +kubebuilder:resource:shortName="dbobserver";"dbobservers" // DatabaseObserver is the Schema for the databaseobservers API -// +kubebuilder:printcolumn:JSONPath=".status.exporterConfig",name="ExporterConfig",type=string +// +kubebuilder:printcolumn:JSONPath=".status.metricsConfig",name="MetricsConfig",type=string // +kubebuilder:printcolumn:JSONPath=".status.status",name="Status",type=string // +kubebuilder:printcolumn:JSONPath=".status.version",name="Version",type=string type DatabaseObserver struct { diff --git a/apis/observability/v1/databaseobserver_webhook.go b/apis/observability/v1/databaseobserver_webhook.go index 286d6ed6..809549d3 100644 --- a/apis/observability/v1/databaseobserver_webhook.go +++ b/apis/observability/v1/databaseobserver_webhook.go @@ -39,6 +39,7 @@ package v1 import ( + "context" dbcommons "github.com/oracle/oracle-database-operator/commons/database" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -48,7 +49,6 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/webhook" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - "strings" ) // log is for logging in this package. @@ -58,7 +58,7 @@ const ( AllowedExporterImage = "container-registry.oracle.com/database/observability-exporter" ErrorSpecValidationMissingConnString = "a required field for database connection string secret is missing or does not have a value" ErrorSpecValidationMissingDBUser = "a required field for database user secret is missing or does not have a value" - ErrorSpecValidationMissingDBVaultField = "a field for the OCI vault has a value but the other required field is missing or does not have a value" + ErrorSpecValidationMissingVaultField = "a field for configuring the vault has a value but the other required field(s) is missing or does not have a value" ErrorSpecValidationMissingOCIConfig = "a field(s) for the OCI Config is missing or does not have a value when fields for the OCI vault has values" ErrorSpecValidationMissingDBPasswordSecret = "a required field for the database password secret is missing or does not have a value" ErrorSpecExporterImageNotAllowed = "a different exporter image was found, only official database exporter container images are currently supported" @@ -67,6 +67,8 @@ const ( func (r *DatabaseObserver) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). For(r). + WithDefaulter(r). + WithValidator(r). Complete() } @@ -74,112 +76,94 @@ func (r *DatabaseObserver) SetupWebhookWithManager(mgr ctrl.Manager) error { //+kubebuilder:webhook:path=/mutate-observability-oracle-com-v1-databaseobserver,mutating=true,sideEffects=none,failurePolicy=fail,groups=observability.oracle.com,resources=databaseobservers,verbs=create;update,versions=v1,name=mdatabaseobserver.kb.io,admissionReviewVersions=v1 -var _ webhook.Defaulter = &DatabaseObserver{} +var _ webhook.CustomDefaulter = &DatabaseObserver{} -// Default implements webhook.Defaulter so a webhook will be registered for the type -func (r *DatabaseObserver) Default() { - databaseobserverlog.Info("default", "name", r.Name) +// Default implements webhook.CustomDefaulter so a webhook will be registered for the type +func (r *DatabaseObserver) Default(ctx context.Context, obj runtime.Object) error { + obs := obj.(*DatabaseObserver) + databaseobserverlog.Info("default", "name", obs.Name) // TODO(user): fill in your defaulting logic. + return nil } // TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. //+kubebuilder:webhook:verbs=create;update,path=/validate-observability-oracle-com-v1-databaseobserver,mutating=false,sideEffects=none,failurePolicy=fail,groups=observability.oracle.com,resources=databaseobservers,versions=v1,name=vdatabaseobserver.kb.io,admissionReviewVersions=v1 -var _ webhook.Validator = &DatabaseObserver{} +var _ webhook.CustomValidator = &DatabaseObserver{} -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (r *DatabaseObserver) ValidateCreate() (admission.Warnings, error) { - databaseobserverlog.Info("validate create", "name", r.Name) +// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type +func (r *DatabaseObserver) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + obs := obj.(*DatabaseObserver) + databaseobserverlog.Info("validate create", "name", obs.Name) var e field.ErrorList ns := dbcommons.GetWatchNamespaces() // Check for namespace/cluster scope access - if _, isDesiredNamespaceWithinScope := ns[r.Namespace]; !isDesiredNamespaceWithinScope && len(ns) > 0 { + if _, isDesiredNamespaceWithinScope := ns[obs.Namespace]; !isDesiredNamespaceWithinScope && len(ns) > 0 { e = append(e, - field.Invalid(field.NewPath("metadata").Child("namespace"), r.Namespace, + field.Invalid(field.NewPath("metadata").Child("namespace"), obs.Namespace, "Oracle database operator doesn't watch over this namespace")) } - // Check required secret for db user has value - if r.Spec.Database.DBUser.SecretName == "" { - e = append(e, - field.Invalid(field.NewPath("spec").Child("database").Child("dbUser").Child("secret"), r.Spec.Database.DBUser.SecretName, - ErrorSpecValidationMissingDBUser)) - } - - // Check required secret for db connection string has value - if r.Spec.Database.DBConnectionString.SecretName == "" { - e = append(e, - field.Invalid(field.NewPath("spec").Child("database").Child("dbConnectionString").Child("secret"), r.Spec.Database.DBConnectionString.SecretName, - ErrorSpecValidationMissingConnString)) - } - // The other vault field must have value if one does - if (r.Spec.Database.DBPassword.VaultOCID != "" && r.Spec.Database.DBPassword.VaultSecretName == "") || - (r.Spec.Database.DBPassword.VaultSecretName != "" && r.Spec.Database.DBPassword.VaultOCID == "") { - - e = append(e, - field.Invalid(field.NewPath("spec").Child("database").Child("dbPassword"), r.Spec.Database.DBPassword, - ErrorSpecValidationMissingDBVaultField)) - } - - // if vault fields have value, ociConfig must have values - if r.Spec.Database.DBPassword.VaultOCID != "" && r.Spec.Database.DBPassword.VaultSecretName != "" && - (r.Spec.OCIConfig.SecretName == "" || r.Spec.OCIConfig.ConfigMapName == "") { + if (obs.Spec.Database.OCIVault.VaultID != "" && obs.Spec.Database.OCIVault.VaultPasswordSecret == "") || + (obs.Spec.Database.OCIVault.VaultPasswordSecret != "" && obs.Spec.Database.OCIVault.VaultID == "") { e = append(e, - field.Invalid(field.NewPath("spec").Child("ociConfig"), r.Spec.OCIConfig, - ErrorSpecValidationMissingOCIConfig)) + field.Invalid(field.NewPath("spec").Child("database").Child("oci"), obs.Spec.Database.OCIVault, + ErrorSpecValidationMissingVaultField)) } - // If all of {DB Password Secret Name and vaultOCID+vaultSecretName} have no value, then error out - if r.Spec.Database.DBPassword.SecretName == "" && - r.Spec.Database.DBPassword.VaultOCID == "" && - r.Spec.Database.DBPassword.VaultSecretName == "" { + // The other vault field must have value if one does + if (obs.Spec.Database.AzureVault.VaultID != "" && (obs.Spec.Database.AzureVault.VaultPasswordSecret == "" && obs.Spec.Database.AzureVault.VaultUsernameSecret == "")) || + (obs.Spec.Database.AzureVault.VaultPasswordSecret != "" && obs.Spec.Database.AzureVault.VaultID == "") || + (obs.Spec.Database.AzureVault.VaultUsernameSecret != "" && obs.Spec.Database.AzureVault.VaultID == "") { e = append(e, - field.Invalid(field.NewPath("spec").Child("database").Child("dbPassword").Child("secret"), r.Spec.Database.DBPassword.SecretName, - ErrorSpecValidationMissingDBPasswordSecret)) + field.Invalid(field.NewPath("spec").Child("database").Child("azure"), obs.Spec.Database.AzureVault, + ErrorSpecValidationMissingVaultField)) } // disallow usage of any other image than the observability-exporter - if r.Spec.Exporter.Deployment.ExporterImage != "" && !strings.HasPrefix(r.Spec.Exporter.Deployment.ExporterImage, AllowedExporterImage) { - e = append(e, - field.Invalid(field.NewPath("spec").Child("exporter").Child("image"), r.Spec.Exporter.Deployment.ExporterImage, - ErrorSpecExporterImageNotAllowed)) - } - + // temporarily disabled + //if obs.Spec.Deployment.ExporterImage != "" && !strings.HasPrefix(obs.Spec.Deployment.ExporterImage, AllowedExporterImage) { + // e = append(e, + // field.Invalid(field.NewPath("spec").Child("exporter").Child("image"), obs.Spec.Deployment.ExporterImage, + // ErrorSpecExporterImageNotAllowed)) + //} // Return if any errors if len(e) > 0 { - return nil, apierrors.NewInvalid(schema.GroupKind{Group: "observability.oracle.com", Kind: "DatabaseObserver"}, r.Name, e) + return nil, apierrors.NewInvalid(schema.GroupKind{Group: "observability.oracle.com", Kind: "DatabaseObserver"}, obs.Name, e) } return nil, nil } -// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *DatabaseObserver) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - databaseobserverlog.Info("validate update", "name", r.Name) +// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type +func (r *DatabaseObserver) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { + obs := newObj.(*DatabaseObserver) + databaseobserverlog.Info("validate update", "name", obs.Name) var e field.ErrorList // disallow usage of any other image than the observability-exporter - if r.Spec.Exporter.Deployment.ExporterImage != "" && !strings.HasPrefix(r.Spec.Exporter.Deployment.ExporterImage, AllowedExporterImage) { - e = append(e, - field.Invalid(field.NewPath("spec").Child("exporter").Child("image"), r.Spec.Exporter.Deployment.ExporterImage, - ErrorSpecExporterImageNotAllowed)) - } + //if obs.Spec.Deployment.ExporterImage != "" && !strings.HasPrefix(obs.Spec.Deployment.ExporterImage, AllowedExporterImage) { + // e = append(e, + // field.Invalid(field.NewPath("spec").Child("exporter").Child("image"), obs.Spec.Deployment.ExporterImage, + // ErrorSpecExporterImageNotAllowed)) + //} // Return if any errors if len(e) > 0 { - return nil, apierrors.NewInvalid(schema.GroupKind{Group: "observability.oracle.com", Kind: "DatabaseObserver"}, r.Name, e) + return nil, apierrors.NewInvalid(schema.GroupKind{Group: "observability.oracle.com", Kind: "DatabaseObserver"}, obs.Name, e) } return nil, nil } -// ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *DatabaseObserver) ValidateDelete() (admission.Warnings, error) { - databaseobserverlog.Info("validate delete", "name", r.Name) +// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type +func (r *DatabaseObserver) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + obs := obj.(*DatabaseObserver) + databaseobserverlog.Info("validate delete", "name", obs.Name) return nil, nil } diff --git a/apis/observability/v1/zz_generated.deepcopy.go b/apis/observability/v1/zz_generated.deepcopy.go index 4924216f..0991c186 100644 --- a/apis/observability/v1/zz_generated.deepcopy.go +++ b/apis/observability/v1/zz_generated.deepcopy.go @@ -50,174 +50,158 @@ import ( ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ConfigMapDetails) DeepCopyInto(out *ConfigMapDetails) { +func (in *AdditionalWalletSecrets) DeepCopyInto(out *AdditionalWalletSecrets) { *out = *in } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigMapDetails. -func (in *ConfigMapDetails) DeepCopy() *ConfigMapDetails { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalWalletSecrets. +func (in *AdditionalWalletSecrets) DeepCopy() *AdditionalWalletSecrets { if in == nil { return nil } - out := new(ConfigMapDetails) + out := new(AdditionalWalletSecrets) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DBSecret) DeepCopyInto(out *DBSecret) { +func (in *AzureConfig) DeepCopyInto(out *AzureConfig) { *out = *in + out.ConfigMap = in.ConfigMap } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DBSecret. -func (in *DBSecret) DeepCopy() *DBSecret { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AzureConfig. +func (in *AzureConfig) DeepCopy() *AzureConfig { if in == nil { return nil } - out := new(DBSecret) + out := new(AzureConfig) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DBSecretWithVault) DeepCopyInto(out *DBSecretWithVault) { +func (in *ConfigMapDetails) DeepCopyInto(out *ConfigMapDetails) { *out = *in } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DBSecretWithVault. -func (in *DBSecretWithVault) DeepCopy() *DBSecretWithVault { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigMapDetails. +func (in *ConfigMapDetails) DeepCopy() *ConfigMapDetails { if in == nil { return nil } - out := new(DBSecretWithVault) + out := new(ConfigMapDetails) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DatabaseObserver) DeepCopyInto(out *DatabaseObserver) { +func (in *ConfigPrivateKey) DeepCopyInto(out *ConfigPrivateKey) { *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseObserver. -func (in *DatabaseObserver) DeepCopy() *DatabaseObserver { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigPrivateKey. +func (in *ConfigPrivateKey) DeepCopy() *ConfigPrivateKey { if in == nil { return nil } - out := new(DatabaseObserver) + out := new(ConfigPrivateKey) in.DeepCopyInto(out) return out } -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *DatabaseObserver) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DBAzureVault) DeepCopyInto(out *DBAzureVault) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DBAzureVault. +func (in *DBAzureVault) DeepCopy() *DBAzureVault { + if in == nil { + return nil } - return nil + out := new(DBAzureVault) + in.DeepCopyInto(out) + return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DatabaseObserverConfigMap) DeepCopyInto(out *DatabaseObserverConfigMap) { +func (in *DBOCIVault) DeepCopyInto(out *DBOCIVault) { *out = *in - out.Configmap = in.Configmap } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseObserverConfigMap. -func (in *DatabaseObserverConfigMap) DeepCopy() *DatabaseObserverConfigMap { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DBOCIVault. +func (in *DBOCIVault) DeepCopy() *DBOCIVault { if in == nil { return nil } - out := new(DatabaseObserverConfigMap) + out := new(DBOCIVault) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DatabaseObserverDatabase) DeepCopyInto(out *DatabaseObserverDatabase) { +func (in *DBSecret) DeepCopyInto(out *DBSecret) { *out = *in - out.DBUser = in.DBUser - out.DBPassword = in.DBPassword - out.DBWallet = in.DBWallet - out.DBConnectionString = in.DBConnectionString } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseObserverDatabase. -func (in *DatabaseObserverDatabase) DeepCopy() *DatabaseObserverDatabase { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DBSecret. +func (in *DBSecret) DeepCopy() *DBSecret { if in == nil { return nil } - out := new(DatabaseObserverDatabase) + out := new(DBSecret) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DatabaseObserverDeployment) DeepCopyInto(out *DatabaseObserverDeployment) { +func (in *DatabaseConfig) DeepCopyInto(out *DatabaseConfig) { *out = *in - if in.SecurityContext != nil { - in, out := &in.SecurityContext, &out.SecurityContext - *out = new(corev1.SecurityContext) - (*in).DeepCopyInto(*out) - } - if in.ExporterArgs != nil { - in, out := &in.ExporterArgs, &out.ExporterArgs - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.ExporterCommands != nil { - in, out := &in.ExporterCommands, &out.ExporterCommands - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.ExporterEnvs != nil { - in, out := &in.ExporterEnvs, &out.ExporterEnvs - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.Labels != nil { - in, out := &in.Labels, &out.Labels - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - in.DeploymentPodTemplate.DeepCopyInto(&out.DeploymentPodTemplate) + out.DBUser = in.DBUser + out.DBPassword = in.DBPassword + out.DBConnectionString = in.DBConnectionString + out.OCIVault = in.OCIVault + out.AzureVault = in.AzureVault } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseObserverDeployment. -func (in *DatabaseObserverDeployment) DeepCopy() *DatabaseObserverDeployment { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseConfig. +func (in *DatabaseConfig) DeepCopy() *DatabaseConfig { if in == nil { return nil } - out := new(DatabaseObserverDeployment) + out := new(DatabaseConfig) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DatabaseObserverExporterConfig) DeepCopyInto(out *DatabaseObserverExporterConfig) { +func (in *DatabaseObserver) DeepCopyInto(out *DatabaseObserver) { *out = *in - in.Deployment.DeepCopyInto(&out.Deployment) - in.Service.DeepCopyInto(&out.Service) + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseObserverExporterConfig. -func (in *DatabaseObserverExporterConfig) DeepCopy() *DatabaseObserverExporterConfig { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseObserver. +func (in *DatabaseObserver) DeepCopy() *DatabaseObserver { if in == nil { return nil } - out := new(DatabaseObserverExporterConfig) + out := new(DatabaseObserver) in.DeepCopyInto(out) return out } +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DatabaseObserver) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DatabaseObserverList) DeepCopyInto(out *DatabaseObserverList) { *out = *in @@ -251,15 +235,68 @@ func (in *DatabaseObserverList) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DatabaseObserverService) DeepCopyInto(out *DatabaseObserverService) { +func (in *DatabaseObserverSpec) DeepCopyInto(out *DatabaseObserverSpec) { *out = *in - if in.Ports != nil { - in, out := &in.Ports, &out.Ports - *out = make([]corev1.ServicePort, len(*in)) + out.Database = in.Database + if in.Databases != nil { + in, out := &in.Databases, &out.Databases + *out = make(map[string]MultiDatabaseConfig, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + in.Wallet.DeepCopyInto(&out.Wallet) + in.Deployment.DeepCopyInto(&out.Deployment) + in.Service.DeepCopyInto(&out.Service) + in.ServiceMonitor.DeepCopyInto(&out.ServiceMonitor) + out.ExporterConfig = in.ExporterConfig + out.OCIConfig = in.OCIConfig + out.AzureConfig = in.AzureConfig + in.Metrics.DeepCopyInto(&out.Metrics) + out.Log = in.Log + if in.InheritLabels != nil { + in, out := &in.InheritLabels, &out.InheritLabels + *out = make([]string, len(*in)) + copy(*out, *in) + } + in.Sidecar.DeepCopyInto(&out.Sidecar) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseObserverSpec. +func (in *DatabaseObserverSpec) DeepCopy() *DatabaseObserverSpec { + if in == nil { + return nil + } + out := new(DatabaseObserverSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DatabaseObserverStatus) DeepCopyInto(out *DatabaseObserverStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseObserverStatus. +func (in *DatabaseObserverStatus) DeepCopy() *DatabaseObserverStatus { + if in == nil { + return nil + } + out := new(DatabaseObserverStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DeploymentPodTemplate) DeepCopyInto(out *DeploymentPodTemplate) { + *out = *in if in.Labels != nil { in, out := &in.Labels, &out.Labels *out = make(map[string]string, len(*in)) @@ -269,86 +306,114 @@ func (in *DatabaseObserverService) DeepCopyInto(out *DatabaseObserverService) { } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseObserverService. -func (in *DatabaseObserverService) DeepCopy() *DatabaseObserverService { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeploymentPodTemplate. +func (in *DeploymentPodTemplate) DeepCopy() *DeploymentPodTemplate { if in == nil { return nil } - out := new(DatabaseObserverService) + out := new(DeploymentPodTemplate) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DatabaseObserverSpec) DeepCopyInto(out *DatabaseObserverSpec) { +func (in *ExporterConfig) DeepCopyInto(out *ExporterConfig) { *out = *in - out.Database = in.Database - in.Exporter.DeepCopyInto(&out.Exporter) - out.ExporterConfig = in.ExporterConfig - in.Prometheus.DeepCopyInto(&out.Prometheus) - out.OCIConfig = in.OCIConfig - out.Log = in.Log - if in.InheritLabels != nil { - in, out := &in.InheritLabels, &out.InheritLabels + out.ConfigMap = in.ConfigMap +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExporterConfig. +func (in *ExporterConfig) DeepCopy() *ExporterConfig { + if in == nil { + return nil + } + out := new(ExporterConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExporterDeployment) DeepCopyInto(out *ExporterDeployment) { + *out = *in + if in.SecurityContext != nil { + in, out := &in.SecurityContext, &out.SecurityContext + *out = new(corev1.SecurityContext) + (*in).DeepCopyInto(*out) + } + if in.PodSecurityContext != nil { + in, out := &in.PodSecurityContext, &out.PodSecurityContext + *out = new(corev1.PodSecurityContext) + (*in).DeepCopyInto(*out) + } + if in.ExporterArgs != nil { + in, out := &in.ExporterArgs, &out.ExporterArgs *out = make([]string, len(*in)) copy(*out, *in) } - if in.ExporterSidecars != nil { - in, out := &in.ExporterSidecars, &out.ExporterSidecars - *out = make([]corev1.Container, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) + if in.ExporterCommands != nil { + in, out := &in.ExporterCommands, &out.ExporterCommands + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ExporterEnvs != nil { + in, out := &in.ExporterEnvs, &out.ExporterEnvs + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val } } - if in.SideCarVolumes != nil { - in, out := &in.SideCarVolumes, &out.SideCarVolumes - *out = make([]corev1.Volume, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val } } + in.DeploymentPodTemplate.DeepCopyInto(&out.DeploymentPodTemplate) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseObserverSpec. -func (in *DatabaseObserverSpec) DeepCopy() *DatabaseObserverSpec { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExporterDeployment. +func (in *ExporterDeployment) DeepCopy() *ExporterDeployment { if in == nil { return nil } - out := new(DatabaseObserverSpec) + out := new(ExporterDeployment) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DatabaseObserverStatus) DeepCopyInto(out *DatabaseObserverStatus) { +func (in *ExporterService) DeepCopyInto(out *ExporterService) { *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]metav1.Condition, len(*in)) + if in.Ports != nil { + in, out := &in.Ports, &out.Ports + *out = make([]corev1.ServicePort, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseObserverStatus. -func (in *DatabaseObserverStatus) DeepCopy() *DatabaseObserverStatus { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExporterService. +func (in *ExporterService) DeepCopy() *ExporterService { if in == nil { return nil } - out := new(DatabaseObserverStatus) + out := new(ExporterService) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DeploymentPodTemplate) DeepCopyInto(out *DeploymentPodTemplate) { +func (in *ExporterServiceMonitor) DeepCopyInto(out *ExporterServiceMonitor) { *out = *in - if in.SecurityContext != nil { - in, out := &in.SecurityContext, &out.SecurityContext - *out = new(corev1.PodSecurityContext) - (*in).DeepCopyInto(*out) - } if in.Labels != nil { in, out := &in.Labels, &out.Labels *out = make(map[string]string, len(*in)) @@ -356,14 +421,26 @@ func (in *DeploymentPodTemplate) DeepCopyInto(out *DeploymentPodTemplate) { (*out)[key] = val } } + if in.NamespaceSelector != nil { + in, out := &in.NamespaceSelector, &out.NamespaceSelector + *out = new(monitoringv1.NamespaceSelector) + (*in).DeepCopyInto(*out) + } + if in.Endpoints != nil { + in, out := &in.Endpoints, &out.Endpoints + *out = make([]monitoringv1.Endpoint, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeploymentPodTemplate. -func (in *DeploymentPodTemplate) DeepCopy() *DeploymentPodTemplate { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExporterServiceMonitor. +func (in *ExporterServiceMonitor) DeepCopy() *ExporterServiceMonitor { if in == nil { return nil } - out := new(DeploymentPodTemplate) + out := new(ExporterServiceMonitor) in.DeepCopyInto(out) return out } @@ -401,81 +478,120 @@ func (in *LogVolume) DeepCopy() *LogVolume { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *LogVolumePVClaim) DeepCopyInto(out *LogVolumePVClaim) { +func (in *LogVolumePVC) DeepCopyInto(out *LogVolumePVC) { *out = *in } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LogVolumePVClaim. -func (in *LogVolumePVClaim) DeepCopy() *LogVolumePVClaim { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LogVolumePVC. +func (in *LogVolumePVC) DeepCopy() *LogVolumePVC { if in == nil { return nil } - out := new(LogVolumePVClaim) + out := new(LogVolumePVC) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *OCIConfigSpec) DeepCopyInto(out *OCIConfigSpec) { +func (in *MetricsConfig) DeepCopyInto(out *MetricsConfig) { *out = *in + if in.Configmap != nil { + in, out := &in.Configmap, &out.Configmap + *out = make([]ConfigMapDetails, len(*in)) + copy(*out, *in) + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OCIConfigSpec. -func (in *OCIConfigSpec) DeepCopy() *OCIConfigSpec { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricsConfig. +func (in *MetricsConfig) DeepCopy() *MetricsConfig { if in == nil { return nil } - out := new(OCIConfigSpec) + out := new(MetricsConfig) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PrometheusConfig) DeepCopyInto(out *PrometheusConfig) { +func (in *MultiDatabaseConfig) DeepCopyInto(out *MultiDatabaseConfig) { *out = *in - in.ServiceMonitor.DeepCopyInto(&out.ServiceMonitor) + out.DBUser = in.DBUser + out.DBPassword = in.DBPassword + out.DBConnectionString = in.DBConnectionString } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PrometheusConfig. -func (in *PrometheusConfig) DeepCopy() *PrometheusConfig { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MultiDatabaseConfig. +func (in *MultiDatabaseConfig) DeepCopy() *MultiDatabaseConfig { if in == nil { return nil } - out := new(PrometheusConfig) + out := new(MultiDatabaseConfig) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PrometheusServiceMonitor) DeepCopyInto(out *PrometheusServiceMonitor) { +func (in *OCIConfig) DeepCopyInto(out *OCIConfig) { *out = *in - if in.Labels != nil { - in, out := &in.Labels, &out.Labels - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } + out.ConfigMap = in.ConfigMap + out.PrivateKey = in.PrivateKey +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OCIConfig. +func (in *OCIConfig) DeepCopy() *OCIConfig { + if in == nil { + return nil } - if in.NamespaceSelector != nil { - in, out := &in.NamespaceSelector, &out.NamespaceSelector - *out = new(monitoringv1.NamespaceSelector) - (*in).DeepCopyInto(*out) + out := new(OCIConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SidecarConfig) DeepCopyInto(out *SidecarConfig) { + *out = *in + if in.Containers != nil { + in, out := &in.Containers, &out.Containers + *out = make([]corev1.Container, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } } - if in.Endpoints != nil { - in, out := &in.Endpoints, &out.Endpoints - *out = make([]monitoringv1.Endpoint, len(*in)) + if in.Volumes != nil { + in, out := &in.Volumes, &out.Volumes + *out = make([]corev1.Volume, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PrometheusServiceMonitor. -func (in *PrometheusServiceMonitor) DeepCopy() *PrometheusServiceMonitor { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SidecarConfig. +func (in *SidecarConfig) DeepCopy() *SidecarConfig { + if in == nil { + return nil + } + out := new(SidecarConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WalletSecret) DeepCopyInto(out *WalletSecret) { + *out = *in + if in.AdditionalWallets != nil { + in, out := &in.AdditionalWallets, &out.AdditionalWallets + *out = make([]AdditionalWalletSecrets, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WalletSecret. +func (in *WalletSecret) DeepCopy() *WalletSecret { if in == nil { return nil } - out := new(PrometheusServiceMonitor) + out := new(WalletSecret) in.DeepCopyInto(out) return out } diff --git a/apis/observability/v1alpha1/databaseobserver_types.go b/apis/observability/v1alpha1/databaseobserver_types.go index f4c62900..8d72b7b4 100644 --- a/apis/observability/v1alpha1/databaseobserver_types.go +++ b/apis/observability/v1alpha1/databaseobserver_types.go @@ -48,78 +48,107 @@ type StatusEnum string // DatabaseObserverSpec defines the desired state of DatabaseObserver type DatabaseObserverSpec struct { - Database DatabaseObserverDatabase `json:"database,omitempty"` - Exporter DatabaseObserverExporterConfig `json:"exporter,omitempty"` - ExporterConfig DatabaseObserverConfigMap `json:"configuration,omitempty"` - Prometheus PrometheusConfig `json:"prometheus,omitempty"` - OCIConfig OCIConfigSpec `json:"ociConfig,omitempty"` - Replicas int32 `json:"replicas,omitempty"` - Log LogConfig `json:"log,omitempty"` - InheritLabels []string `json:"inheritLabels,omitempty"` - ExporterSidecars []corev1.Container `json:"sidecars,omitempty"` - SideCarVolumes []corev1.Volume `json:"sidecarVolumes,omitempty"` + Database DatabaseConfig `json:"database,omitempty"` + Databases map[string]MultiDatabaseConfig `json:"databases,omitempty"` + Wallet WalletSecret `json:"wallet,omitempty"` + Deployment ExporterDeployment `json:"deployment,omitempty"` + Service ExporterService `json:"service,omitempty"` + ServiceMonitor ExporterServiceMonitor `json:"serviceMonitor,omitempty"` + ExporterConfig ExporterConfig `json:"exporterConfig,omitempty"` + OCIConfig OCIConfig `json:"ociConfig,omitempty"` + AzureConfig AzureConfig `json:"azureConfig,omitempty"` + Metrics MetricsConfig `json:"metrics,omitempty"` + InheritLabels []string `json:"inheritLabels,omitempty"` + Log LogConfig `json:"log,omitempty"` + Sidecar SidecarConfig `json:"sidecar,omitempty"` + Replicas int32 `json:"replicas,omitempty"` +} + +// SidecarConfig defines sidecar containers and volumes to add +type SidecarConfig struct { + Containers []corev1.Container `json:"containers,omitempty"` + Volumes []corev1.Volume `json:"volumes,omitempty"` +} + +// ExporterConfig defines configMap used for exporter configuration +type ExporterConfig struct { + ConfigMap ConfigMapDetails `json:"configMap,omitempty"` + MountPath string `json:"mountPath,omitempty"` } // LogConfig defines the configuration details relation to the logs of DatabaseObserver type LogConfig struct { - Path string `json:"path,omitempty"` - Filename string `json:"filename,omitempty"` - Volume LogVolume `json:"volume,omitempty"` + Disable bool `json:"disable,omitempty"` + Destination string `json:"destination,omitempty"` + Filename string `json:"filename,omitempty"` + Volume LogVolume `json:"volume,omitempty"` } +// LogVolume defines the shared volume between the exporter container and other containers type LogVolume struct { - Name string `json:"name,omitempty"` - PersistentVolumeClaim LogVolumePVClaim `json:"persistentVolumeClaim,omitempty"` + Name string `json:"name,omitempty"` + PersistentVolumeClaim LogVolumePVC `json:"persistentVolumeClaim,omitempty"` } -type LogVolumePVClaim struct { +// LogVolumePVC defines the PVC in which to store the logs +type LogVolumePVC struct { ClaimName string `json:"claimName,omitempty"` } -// DatabaseObserverDatabase defines the database details used for DatabaseObserver -type DatabaseObserverDatabase struct { - DBUser DBSecret `json:"dbUser,omitempty"` - DBPassword DBSecretWithVault `json:"dbPassword,omitempty"` - DBWallet DBSecret `json:"dbWallet,omitempty"` - DBConnectionString DBSecret `json:"dbConnectionString,omitempty"` +// DatabaseConfig defines the database details used for DatabaseObserver +type DatabaseConfig struct { + DBUser DBSecret `json:"dbUser,omitempty"` + DBPassword DBSecret `json:"dbPassword,omitempty"` + DBConnectionString DBSecret `json:"dbConnectionString,omitempty"` + OCIVault DBOCIVault `json:"oci,omitempty"` + AzureVault DBAzureVault `json:"azure,omitempty"` } -// DatabaseObserverExporterConfig defines the configuration details related to the exporters of DatabaseObserver -type DatabaseObserverExporterConfig struct { - Deployment DatabaseObserverDeployment `json:"deployment,omitempty"` - Service DatabaseObserverService `json:"service,omitempty"` +// MultiDatabaseConfig defines each database details used for DatabaseObserver +type MultiDatabaseConfig struct { + DBUser DBSecret `json:"dbUser,omitempty"` + DBPassword DBSecret `json:"dbPassword,omitempty"` + DBConnectionString DBSecret `json:"dbConnectionString,omitempty"` } -// DatabaseObserverDeployment defines the exporter deployment component of DatabaseObserver -type DatabaseObserverDeployment struct { - ExporterImage string `json:"image,omitempty"` - SecurityContext *corev1.SecurityContext `json:"securityContext,omitempty"` - ExporterArgs []string `json:"args,omitempty"` - ExporterCommands []string `json:"commands,omitempty"` - ExporterEnvs map[string]string `json:"env,omitempty"` - Labels map[string]string `json:"labels,omitempty"` - DeploymentPodTemplate DeploymentPodTemplate `json:"podTemplate,omitempty"` +// DBAzureVault defines Azure Vault details +type DBAzureVault struct { + VaultID string `json:"vaultID,omitempty"` + VaultUsernameSecret string `json:"vaultUsernameSecret,omitempty"` + VaultPasswordSecret string `json:"vaultPasswordSecret,omitempty"` +} + +// DBOCIVault defines OCI Vault details +type DBOCIVault struct { + VaultID string `json:"vaultID,omitempty"` + VaultPasswordSecret string `json:"vaultPasswordSecret,omitempty"` +} + +// ExporterDeployment defines the exporter deployment component of DatabaseObserver +type ExporterDeployment struct { + ExporterImage string `json:"image,omitempty"` + SecurityContext *corev1.SecurityContext `json:"securityContext,omitempty"` + PodSecurityContext *corev1.PodSecurityContext `json:"podSecurityContext,omitempty"` + ExporterArgs []string `json:"args,omitempty"` + ExporterCommands []string `json:"commands,omitempty"` + ExporterEnvs map[string]string `json:"env,omitempty"` + Labels map[string]string `json:"labels,omitempty"` + DeploymentPodTemplate DeploymentPodTemplate `json:"podTemplate,omitempty"` } // DeploymentPodTemplate defines the labels for the DatabaseObserver pods component of a deployment type DeploymentPodTemplate struct { - SecurityContext *corev1.PodSecurityContext `json:"securityContext,omitempty"` - Labels map[string]string `json:"labels,omitempty"` + Labels map[string]string `json:"labels,omitempty"` } -// DatabaseObserverService defines the exporter service component of DatabaseObserver -type DatabaseObserverService struct { +// ExporterService defines the exporter service component of DatabaseObserver +type ExporterService struct { Ports []corev1.ServicePort `json:"ports,omitempty"` Labels map[string]string `json:"labels,omitempty"` } -// PrometheusConfig defines the generated resources for Prometheus -type PrometheusConfig struct { - ServiceMonitor PrometheusServiceMonitor `json:"serviceMonitor,omitempty"` -} - -// PrometheusServiceMonitor defines DatabaseObserver servicemonitor spec -type PrometheusServiceMonitor struct { +// ExporterServiceMonitor defines DatabaseObserver servicemonitor spec +type ExporterServiceMonitor struct { Labels map[string]string `json:"labels,omitempty"` NamespaceSelector *monitorv1.NamespaceSelector `json:"namespaceSelector,omitempty"` Endpoints []monitorv1.Endpoint `json:"endpoints,omitempty"` @@ -129,40 +158,57 @@ type PrometheusServiceMonitor struct { type DBSecret struct { Key string `json:"key,omitempty"` SecretName string `json:"secret,omitempty"` + EnvName string `json:"envName,omitempty"` +} + +// WalletSecret defines secret and where the wallet will be mounted if provided +type WalletSecret struct { + SecretName string `json:"secret,omitempty"` + MountPath string `json:"mountPath,omitempty"` + AdditionalWallets []AdditionalWalletSecrets `json:"additional,omitempty"` } -// DBSecretWithVault defines secrets used in reference with vault fields -type DBSecretWithVault struct { - Key string `json:"key,omitempty"` - SecretName string `json:"secret,omitempty"` - VaultOCID string `json:"vaultOCID,omitempty"` - VaultSecretName string `json:"vaultSecretName,omitempty"` +// AdditionalWalletSecrets defines multiple other secrets and where the wallet will be mounted if provided +type AdditionalWalletSecrets struct { + Name string `json:"name,omitempty"` + SecretName string `json:"secret,omitempty"` + MountPath string `json:"mountPath,omitempty"` } -// DatabaseObserverConfigMap defines configMap used for metrics configuration -type DatabaseObserverConfigMap struct { - Configmap ConfigMapDetails `json:"configMap,omitempty"` +// MetricsConfig defines configMap used for multiple metrics TOML configuration +type MetricsConfig struct { + Configmap []ConfigMapDetails `json:"configMap,omitempty"` } -// ConfigMapDetails defines the configmap name +// ConfigMapDetails defines the configmap name used by the exporterConfig and metricsConfig type ConfigMapDetails struct { Key string `json:"key,omitempty"` Name string `json:"name,omitempty"` } -// OCIConfigSpec defines the configmap name and secret name used for connecting to OCI -type OCIConfigSpec struct { - ConfigMapName string `json:"configMapName,omitempty"` - SecretName string `json:"secretName,omitempty"` +// OCIConfig defines the configmap name and secret name used for connecting to OCI +type OCIConfig struct { + ConfigMap ConfigMapDetails `json:"configMap,omitempty"` + PrivateKey ConfigPrivateKey `json:"privateKey,omitempty"` + MountPath string `json:"mountPath,omitempty"` +} + +type ConfigPrivateKey struct { + SecretName string `json:"secret,omitempty"` +} + +// AzureConfig defines the configmap name and secret name used for connecting to Azure +type AzureConfig struct { + ConfigMap ConfigMapDetails `json:"configMap,omitempty"` } // DatabaseObserverStatus defines the observed state of DatabaseObserver type DatabaseObserverStatus struct { - Conditions []metav1.Condition `json:"conditions"` - Status string `json:"status,omitempty"` - ExporterConfig string `json:"exporterConfig"` - Version string `json:"version"` - Replicas int `json:"replicas,omitempty"` + Conditions []metav1.Condition `json:"conditions"` + Status string `json:"status,omitempty"` + MetricsConfig string `json:"metricsConfig"` + Version string `json:"version"` + Replicas int `json:"replicas,omitempty"` } //+kubebuilder:object:root=true @@ -170,7 +216,7 @@ type DatabaseObserverStatus struct { // +kubebuilder:resource:shortName="dbobserver";"dbobservers" // DatabaseObserver is the Schema for the databaseobservers API -// +kubebuilder:printcolumn:JSONPath=".status.exporterConfig",name="ExporterConfig",type=string +// +kubebuilder:printcolumn:JSONPath=".status.metricsConfig",name="MetricsConfig",type=string // +kubebuilder:printcolumn:JSONPath=".status.status",name="Status",type=string // +kubebuilder:printcolumn:JSONPath=".status.version",name="Version",type=string type DatabaseObserver struct { diff --git a/apis/observability/v1alpha1/databaseobserver_webhook.go b/apis/observability/v1alpha1/databaseobserver_webhook.go index 585ad3bf..da683b6b 100644 --- a/apis/observability/v1alpha1/databaseobserver_webhook.go +++ b/apis/observability/v1alpha1/databaseobserver_webhook.go @@ -39,6 +39,7 @@ package v1alpha1 import ( + "context" dbcommons "github.com/oracle/oracle-database-operator/commons/database" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -48,7 +49,6 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/webhook" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - "strings" ) // log is for logging in this package. @@ -58,7 +58,7 @@ const ( AllowedExporterImage = "container-registry.oracle.com/database/observability-exporter" ErrorSpecValidationMissingConnString = "a required field for database connection string secret is missing or does not have a value" ErrorSpecValidationMissingDBUser = "a required field for database user secret is missing or does not have a value" - ErrorSpecValidationMissingDBVaultField = "a field for the OCI vault has a value but the other required field is missing or does not have a value" + ErrorSpecValidationMissingVaultField = "a field for configuring the vault has a value but the other required field(s) is missing or does not have a value" ErrorSpecValidationMissingOCIConfig = "a field(s) for the OCI Config is missing or does not have a value when fields for the OCI vault has values" ErrorSpecValidationMissingDBPasswordSecret = "a required field for the database password secret is missing or does not have a value" ErrorSpecExporterImageNotAllowed = "a different exporter image was found, only official database exporter container images are currently supported" @@ -67,6 +67,8 @@ const ( func (r *DatabaseObserver) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). For(r). + WithDefaulter(r). + WithValidator(r). Complete() } @@ -74,112 +76,95 @@ func (r *DatabaseObserver) SetupWebhookWithManager(mgr ctrl.Manager) error { //+kubebuilder:webhook:path=/mutate-observability-oracle-com-v1alpha1-databaseobserver,mutating=true,sideEffects=none,failurePolicy=fail,groups=observability.oracle.com,resources=databaseobservers,verbs=create;update,versions=v1alpha1,name=mdatabaseobserver.kb.io,admissionReviewVersions=v1 -var _ webhook.Defaulter = &DatabaseObserver{} +var _ webhook.CustomDefaulter = &DatabaseObserver{} -// Default implements webhook.Defaulter so a webhook will be registered for the type -func (r *DatabaseObserver) Default() { - databaseobserverlog.Info("default", "name", r.Name) +// Default implements webhook.CustomDefaulter so a webhook will be registered for the type +func (r *DatabaseObserver) Default(ctx context.Context, obj runtime.Object) error { + obs := obj.(*DatabaseObserver) + databaseobserverlog.Info("default", "name", obs.Name) // TODO(user): fill in your defaulting logic. + return nil } // TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. //+kubebuilder:webhook:verbs=create;update,path=/validate-observability-oracle-com-v1alpha1-databaseobserver,mutating=false,sideEffects=none,failurePolicy=fail,groups=observability.oracle.com,resources=databaseobservers,versions=v1alpha1,name=vdatabaseobserver.kb.io,admissionReviewVersions=v1 -var _ webhook.Validator = &DatabaseObserver{} +var _ webhook.CustomValidator = &DatabaseObserver{} -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (r *DatabaseObserver) ValidateCreate() (admission.Warnings, error) { - databaseobserverlog.Info("validate create", "name", r.Name) +// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type +func (r *DatabaseObserver) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + obs := obj.(*DatabaseObserver) + databaseobserverlog.Info("validate create", "name", obs.Name) var e field.ErrorList ns := dbcommons.GetWatchNamespaces() // Check for namespace/cluster scope access - if _, isDesiredNamespaceWithinScope := ns[r.Namespace]; !isDesiredNamespaceWithinScope && len(ns) > 0 { + if _, isDesiredNamespaceWithinScope := ns[obs.Namespace]; !isDesiredNamespaceWithinScope && len(ns) > 0 { e = append(e, - field.Invalid(field.NewPath("metadata").Child("namespace"), r.Namespace, + field.Invalid(field.NewPath("metadata").Child("namespace"), obs.Namespace, "Oracle database operator doesn't watch over this namespace")) } - // Check required secret for db user has value - if r.Spec.Database.DBUser.SecretName == "" { - e = append(e, - field.Invalid(field.NewPath("spec").Child("database").Child("dbUser").Child("secret"), r.Spec.Database.DBUser.SecretName, - ErrorSpecValidationMissingDBUser)) - } - - // Check required secret for db connection string has value - if r.Spec.Database.DBConnectionString.SecretName == "" { - e = append(e, - field.Invalid(field.NewPath("spec").Child("database").Child("dbConnectionString").Child("secret"), r.Spec.Database.DBConnectionString.SecretName, - ErrorSpecValidationMissingConnString)) - } - // The other vault field must have value if one does - if (r.Spec.Database.DBPassword.VaultOCID != "" && r.Spec.Database.DBPassword.VaultSecretName == "") || - (r.Spec.Database.DBPassword.VaultSecretName != "" && r.Spec.Database.DBPassword.VaultOCID == "") { + if (obs.Spec.Database.OCIVault.VaultID != "" && obs.Spec.Database.OCIVault.VaultPasswordSecret == "") || + (obs.Spec.Database.OCIVault.VaultPasswordSecret != "" && obs.Spec.Database.OCIVault.VaultID == "") { e = append(e, - field.Invalid(field.NewPath("spec").Child("database").Child("dbPassword"), r.Spec.Database.DBPassword, - ErrorSpecValidationMissingDBVaultField)) + field.Invalid(field.NewPath("spec").Child("database").Child("oci"), obs.Spec.Database.OCIVault, + ErrorSpecValidationMissingVaultField)) } - // if vault fields have value, ociConfig must have values - if r.Spec.Database.DBPassword.VaultOCID != "" && r.Spec.Database.DBPassword.VaultSecretName != "" && - (r.Spec.OCIConfig.SecretName == "" || r.Spec.OCIConfig.ConfigMapName == "") { - - e = append(e, - field.Invalid(field.NewPath("spec").Child("ociConfig"), r.Spec.OCIConfig, - ErrorSpecValidationMissingOCIConfig)) - } - - // If all of {DB Password Secret Name and vaultOCID+vaultSecretName} have no value, then error out - if r.Spec.Database.DBPassword.SecretName == "" && - r.Spec.Database.DBPassword.VaultOCID == "" && - r.Spec.Database.DBPassword.VaultSecretName == "" { + // The other vault field must have value if one does + if (obs.Spec.Database.AzureVault.VaultID != "" && (obs.Spec.Database.AzureVault.VaultPasswordSecret == "" && obs.Spec.Database.AzureVault.VaultUsernameSecret == "")) || + (obs.Spec.Database.AzureVault.VaultPasswordSecret != "" && obs.Spec.Database.AzureVault.VaultID == "") || + (obs.Spec.Database.AzureVault.VaultUsernameSecret != "" && obs.Spec.Database.AzureVault.VaultID == "") { e = append(e, - field.Invalid(field.NewPath("spec").Child("database").Child("dbPassword").Child("secret"), r.Spec.Database.DBPassword.SecretName, - ErrorSpecValidationMissingDBPasswordSecret)) + field.Invalid(field.NewPath("spec").Child("database").Child("azure"), obs.Spec.Database.AzureVault, + ErrorSpecValidationMissingVaultField)) } // disallow usage of any other image than the observability-exporter - if r.Spec.Exporter.Deployment.ExporterImage != "" && !strings.HasPrefix(r.Spec.Exporter.Deployment.ExporterImage, AllowedExporterImage) { - e = append(e, - field.Invalid(field.NewPath("spec").Child("exporter").Child("image"), r.Spec.Exporter.Deployment.ExporterImage, - ErrorSpecExporterImageNotAllowed)) - } + // temporarily disabled + //if obs.Spec.Deployment.ExporterImage != "" && !strings.HasPrefix(obs.Spec.Deployment.ExporterImage, AllowedExporterImage) { + // e = append(e, + // field.Invalid(field.NewPath("spec").Child("exporter").Child("image"), obs.Spec.Deployment.ExporterImage, + // ErrorSpecExporterImageNotAllowed)) + //} // Return if any errors if len(e) > 0 { - return nil, apierrors.NewInvalid(schema.GroupKind{Group: "observability.oracle.com", Kind: "DatabaseObserver"}, r.Name, e) + return nil, apierrors.NewInvalid(schema.GroupKind{Group: "observability.oracle.com", Kind: "DatabaseObserver"}, obs.Name, e) } return nil, nil } -// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *DatabaseObserver) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - databaseobserverlog.Info("validate update", "name", r.Name) +// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type +func (r *DatabaseObserver) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { + obs := newObj.(*DatabaseObserver) + databaseobserverlog.Info("validate update", "name", obs.Name) var e field.ErrorList // disallow usage of any other image than the observability-exporter - if r.Spec.Exporter.Deployment.ExporterImage != "" && !strings.HasPrefix(r.Spec.Exporter.Deployment.ExporterImage, AllowedExporterImage) { - e = append(e, - field.Invalid(field.NewPath("spec").Child("exporter").Child("image"), r.Spec.Exporter.Deployment.ExporterImage, - ErrorSpecExporterImageNotAllowed)) - } + //if obs.Spec.Deployment.ExporterImage != "" && !strings.HasPrefix(obs.Spec.Deployment.ExporterImage, AllowedExporterImage) { + // e = append(e, + // field.Invalid(field.NewPath("spec").Child("exporter").Child("image"), obs.Spec.Deployment.ExporterImage, + // ErrorSpecExporterImageNotAllowed)) + //} // Return if any errors if len(e) > 0 { - return nil, apierrors.NewInvalid(schema.GroupKind{Group: "observability.oracle.com", Kind: "DatabaseObserver"}, r.Name, e) + return nil, apierrors.NewInvalid(schema.GroupKind{Group: "observability.oracle.com", Kind: "DatabaseObserver"}, obs.Name, e) } return nil, nil } -// ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *DatabaseObserver) ValidateDelete() (admission.Warnings, error) { - databaseobserverlog.Info("validate delete", "name", r.Name) +// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type +func (r *DatabaseObserver) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + obs := obj.(*DatabaseObserver) + databaseobserverlog.Info("validate delete", "name", obs.Name) return nil, nil } diff --git a/apis/observability/v1alpha1/zz_generated.deepcopy.go b/apis/observability/v1alpha1/zz_generated.deepcopy.go index 4b2a29b0..c5e76104 100644 --- a/apis/observability/v1alpha1/zz_generated.deepcopy.go +++ b/apis/observability/v1alpha1/zz_generated.deepcopy.go @@ -50,174 +50,158 @@ import ( ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ConfigMapDetails) DeepCopyInto(out *ConfigMapDetails) { +func (in *AdditionalWalletSecrets) DeepCopyInto(out *AdditionalWalletSecrets) { *out = *in } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigMapDetails. -func (in *ConfigMapDetails) DeepCopy() *ConfigMapDetails { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalWalletSecrets. +func (in *AdditionalWalletSecrets) DeepCopy() *AdditionalWalletSecrets { if in == nil { return nil } - out := new(ConfigMapDetails) + out := new(AdditionalWalletSecrets) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DBSecret) DeepCopyInto(out *DBSecret) { +func (in *AzureConfig) DeepCopyInto(out *AzureConfig) { *out = *in + out.ConfigMap = in.ConfigMap } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DBSecret. -func (in *DBSecret) DeepCopy() *DBSecret { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AzureConfig. +func (in *AzureConfig) DeepCopy() *AzureConfig { if in == nil { return nil } - out := new(DBSecret) + out := new(AzureConfig) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DBSecretWithVault) DeepCopyInto(out *DBSecretWithVault) { +func (in *ConfigMapDetails) DeepCopyInto(out *ConfigMapDetails) { *out = *in } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DBSecretWithVault. -func (in *DBSecretWithVault) DeepCopy() *DBSecretWithVault { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigMapDetails. +func (in *ConfigMapDetails) DeepCopy() *ConfigMapDetails { if in == nil { return nil } - out := new(DBSecretWithVault) + out := new(ConfigMapDetails) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DatabaseObserver) DeepCopyInto(out *DatabaseObserver) { +func (in *ConfigPrivateKey) DeepCopyInto(out *ConfigPrivateKey) { *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseObserver. -func (in *DatabaseObserver) DeepCopy() *DatabaseObserver { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigPrivateKey. +func (in *ConfigPrivateKey) DeepCopy() *ConfigPrivateKey { if in == nil { return nil } - out := new(DatabaseObserver) + out := new(ConfigPrivateKey) in.DeepCopyInto(out) return out } -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *DatabaseObserver) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DBAzureVault) DeepCopyInto(out *DBAzureVault) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DBAzureVault. +func (in *DBAzureVault) DeepCopy() *DBAzureVault { + if in == nil { + return nil } - return nil + out := new(DBAzureVault) + in.DeepCopyInto(out) + return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DatabaseObserverConfigMap) DeepCopyInto(out *DatabaseObserverConfigMap) { +func (in *DBOCIVault) DeepCopyInto(out *DBOCIVault) { *out = *in - out.Configmap = in.Configmap } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseObserverConfigMap. -func (in *DatabaseObserverConfigMap) DeepCopy() *DatabaseObserverConfigMap { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DBOCIVault. +func (in *DBOCIVault) DeepCopy() *DBOCIVault { if in == nil { return nil } - out := new(DatabaseObserverConfigMap) + out := new(DBOCIVault) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DatabaseObserverDatabase) DeepCopyInto(out *DatabaseObserverDatabase) { +func (in *DBSecret) DeepCopyInto(out *DBSecret) { *out = *in - out.DBUser = in.DBUser - out.DBPassword = in.DBPassword - out.DBWallet = in.DBWallet - out.DBConnectionString = in.DBConnectionString } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseObserverDatabase. -func (in *DatabaseObserverDatabase) DeepCopy() *DatabaseObserverDatabase { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DBSecret. +func (in *DBSecret) DeepCopy() *DBSecret { if in == nil { return nil } - out := new(DatabaseObserverDatabase) + out := new(DBSecret) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DatabaseObserverDeployment) DeepCopyInto(out *DatabaseObserverDeployment) { +func (in *DatabaseConfig) DeepCopyInto(out *DatabaseConfig) { *out = *in - if in.SecurityContext != nil { - in, out := &in.SecurityContext, &out.SecurityContext - *out = new(v1.SecurityContext) - (*in).DeepCopyInto(*out) - } - if in.ExporterArgs != nil { - in, out := &in.ExporterArgs, &out.ExporterArgs - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.ExporterCommands != nil { - in, out := &in.ExporterCommands, &out.ExporterCommands - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.ExporterEnvs != nil { - in, out := &in.ExporterEnvs, &out.ExporterEnvs - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.Labels != nil { - in, out := &in.Labels, &out.Labels - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - in.DeploymentPodTemplate.DeepCopyInto(&out.DeploymentPodTemplate) + out.DBUser = in.DBUser + out.DBPassword = in.DBPassword + out.DBConnectionString = in.DBConnectionString + out.OCIVault = in.OCIVault + out.AzureVault = in.AzureVault } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseObserverDeployment. -func (in *DatabaseObserverDeployment) DeepCopy() *DatabaseObserverDeployment { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseConfig. +func (in *DatabaseConfig) DeepCopy() *DatabaseConfig { if in == nil { return nil } - out := new(DatabaseObserverDeployment) + out := new(DatabaseConfig) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DatabaseObserverExporterConfig) DeepCopyInto(out *DatabaseObserverExporterConfig) { +func (in *DatabaseObserver) DeepCopyInto(out *DatabaseObserver) { *out = *in - in.Deployment.DeepCopyInto(&out.Deployment) - in.Service.DeepCopyInto(&out.Service) + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseObserverExporterConfig. -func (in *DatabaseObserverExporterConfig) DeepCopy() *DatabaseObserverExporterConfig { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseObserver. +func (in *DatabaseObserver) DeepCopy() *DatabaseObserver { if in == nil { return nil } - out := new(DatabaseObserverExporterConfig) + out := new(DatabaseObserver) in.DeepCopyInto(out) return out } +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DatabaseObserver) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DatabaseObserverList) DeepCopyInto(out *DatabaseObserverList) { *out = *in @@ -251,15 +235,68 @@ func (in *DatabaseObserverList) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DatabaseObserverService) DeepCopyInto(out *DatabaseObserverService) { +func (in *DatabaseObserverSpec) DeepCopyInto(out *DatabaseObserverSpec) { *out = *in - if in.Ports != nil { - in, out := &in.Ports, &out.Ports - *out = make([]v1.ServicePort, len(*in)) + out.Database = in.Database + if in.Databases != nil { + in, out := &in.Databases, &out.Databases + *out = make(map[string]MultiDatabaseConfig, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + in.Wallet.DeepCopyInto(&out.Wallet) + in.Deployment.DeepCopyInto(&out.Deployment) + in.Service.DeepCopyInto(&out.Service) + in.ServiceMonitor.DeepCopyInto(&out.ServiceMonitor) + out.ExporterConfig = in.ExporterConfig + out.OCIConfig = in.OCIConfig + out.AzureConfig = in.AzureConfig + in.Metrics.DeepCopyInto(&out.Metrics) + if in.InheritLabels != nil { + in, out := &in.InheritLabels, &out.InheritLabels + *out = make([]string, len(*in)) + copy(*out, *in) + } + out.Log = in.Log + in.Sidecar.DeepCopyInto(&out.Sidecar) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseObserverSpec. +func (in *DatabaseObserverSpec) DeepCopy() *DatabaseObserverSpec { + if in == nil { + return nil + } + out := new(DatabaseObserverSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DatabaseObserverStatus) DeepCopyInto(out *DatabaseObserverStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseObserverStatus. +func (in *DatabaseObserverStatus) DeepCopy() *DatabaseObserverStatus { + if in == nil { + return nil + } + out := new(DatabaseObserverStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DeploymentPodTemplate) DeepCopyInto(out *DeploymentPodTemplate) { + *out = *in if in.Labels != nil { in, out := &in.Labels, &out.Labels *out = make(map[string]string, len(*in)) @@ -269,86 +306,114 @@ func (in *DatabaseObserverService) DeepCopyInto(out *DatabaseObserverService) { } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseObserverService. -func (in *DatabaseObserverService) DeepCopy() *DatabaseObserverService { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeploymentPodTemplate. +func (in *DeploymentPodTemplate) DeepCopy() *DeploymentPodTemplate { if in == nil { return nil } - out := new(DatabaseObserverService) + out := new(DeploymentPodTemplate) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DatabaseObserverSpec) DeepCopyInto(out *DatabaseObserverSpec) { +func (in *ExporterConfig) DeepCopyInto(out *ExporterConfig) { *out = *in - out.Database = in.Database - in.Exporter.DeepCopyInto(&out.Exporter) - out.ExporterConfig = in.ExporterConfig - in.Prometheus.DeepCopyInto(&out.Prometheus) - out.OCIConfig = in.OCIConfig - out.Log = in.Log - if in.InheritLabels != nil { - in, out := &in.InheritLabels, &out.InheritLabels + out.ConfigMap = in.ConfigMap +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExporterConfig. +func (in *ExporterConfig) DeepCopy() *ExporterConfig { + if in == nil { + return nil + } + out := new(ExporterConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExporterDeployment) DeepCopyInto(out *ExporterDeployment) { + *out = *in + if in.SecurityContext != nil { + in, out := &in.SecurityContext, &out.SecurityContext + *out = new(v1.SecurityContext) + (*in).DeepCopyInto(*out) + } + if in.PodSecurityContext != nil { + in, out := &in.PodSecurityContext, &out.PodSecurityContext + *out = new(v1.PodSecurityContext) + (*in).DeepCopyInto(*out) + } + if in.ExporterArgs != nil { + in, out := &in.ExporterArgs, &out.ExporterArgs *out = make([]string, len(*in)) copy(*out, *in) } - if in.ExporterSidecars != nil { - in, out := &in.ExporterSidecars, &out.ExporterSidecars - *out = make([]v1.Container, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) + if in.ExporterCommands != nil { + in, out := &in.ExporterCommands, &out.ExporterCommands + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ExporterEnvs != nil { + in, out := &in.ExporterEnvs, &out.ExporterEnvs + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val } } - if in.SideCarVolumes != nil { - in, out := &in.SideCarVolumes, &out.SideCarVolumes - *out = make([]v1.Volume, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val } } + in.DeploymentPodTemplate.DeepCopyInto(&out.DeploymentPodTemplate) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseObserverSpec. -func (in *DatabaseObserverSpec) DeepCopy() *DatabaseObserverSpec { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExporterDeployment. +func (in *ExporterDeployment) DeepCopy() *ExporterDeployment { if in == nil { return nil } - out := new(DatabaseObserverSpec) + out := new(ExporterDeployment) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DatabaseObserverStatus) DeepCopyInto(out *DatabaseObserverStatus) { +func (in *ExporterService) DeepCopyInto(out *ExporterService) { *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]metav1.Condition, len(*in)) + if in.Ports != nil { + in, out := &in.Ports, &out.Ports + *out = make([]v1.ServicePort, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseObserverStatus. -func (in *DatabaseObserverStatus) DeepCopy() *DatabaseObserverStatus { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExporterService. +func (in *ExporterService) DeepCopy() *ExporterService { if in == nil { return nil } - out := new(DatabaseObserverStatus) + out := new(ExporterService) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DeploymentPodTemplate) DeepCopyInto(out *DeploymentPodTemplate) { +func (in *ExporterServiceMonitor) DeepCopyInto(out *ExporterServiceMonitor) { *out = *in - if in.SecurityContext != nil { - in, out := &in.SecurityContext, &out.SecurityContext - *out = new(v1.PodSecurityContext) - (*in).DeepCopyInto(*out) - } if in.Labels != nil { in, out := &in.Labels, &out.Labels *out = make(map[string]string, len(*in)) @@ -356,14 +421,26 @@ func (in *DeploymentPodTemplate) DeepCopyInto(out *DeploymentPodTemplate) { (*out)[key] = val } } + if in.NamespaceSelector != nil { + in, out := &in.NamespaceSelector, &out.NamespaceSelector + *out = new(monitoringv1.NamespaceSelector) + (*in).DeepCopyInto(*out) + } + if in.Endpoints != nil { + in, out := &in.Endpoints, &out.Endpoints + *out = make([]monitoringv1.Endpoint, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeploymentPodTemplate. -func (in *DeploymentPodTemplate) DeepCopy() *DeploymentPodTemplate { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExporterServiceMonitor. +func (in *ExporterServiceMonitor) DeepCopy() *ExporterServiceMonitor { if in == nil { return nil } - out := new(DeploymentPodTemplate) + out := new(ExporterServiceMonitor) in.DeepCopyInto(out) return out } @@ -401,81 +478,120 @@ func (in *LogVolume) DeepCopy() *LogVolume { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *LogVolumePVClaim) DeepCopyInto(out *LogVolumePVClaim) { +func (in *LogVolumePVC) DeepCopyInto(out *LogVolumePVC) { *out = *in } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LogVolumePVClaim. -func (in *LogVolumePVClaim) DeepCopy() *LogVolumePVClaim { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LogVolumePVC. +func (in *LogVolumePVC) DeepCopy() *LogVolumePVC { if in == nil { return nil } - out := new(LogVolumePVClaim) + out := new(LogVolumePVC) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *OCIConfigSpec) DeepCopyInto(out *OCIConfigSpec) { +func (in *MetricsConfig) DeepCopyInto(out *MetricsConfig) { *out = *in + if in.Configmap != nil { + in, out := &in.Configmap, &out.Configmap + *out = make([]ConfigMapDetails, len(*in)) + copy(*out, *in) + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OCIConfigSpec. -func (in *OCIConfigSpec) DeepCopy() *OCIConfigSpec { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricsConfig. +func (in *MetricsConfig) DeepCopy() *MetricsConfig { if in == nil { return nil } - out := new(OCIConfigSpec) + out := new(MetricsConfig) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PrometheusConfig) DeepCopyInto(out *PrometheusConfig) { +func (in *MultiDatabaseConfig) DeepCopyInto(out *MultiDatabaseConfig) { *out = *in - in.ServiceMonitor.DeepCopyInto(&out.ServiceMonitor) + out.DBUser = in.DBUser + out.DBPassword = in.DBPassword + out.DBConnectionString = in.DBConnectionString } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PrometheusConfig. -func (in *PrometheusConfig) DeepCopy() *PrometheusConfig { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MultiDatabaseConfig. +func (in *MultiDatabaseConfig) DeepCopy() *MultiDatabaseConfig { if in == nil { return nil } - out := new(PrometheusConfig) + out := new(MultiDatabaseConfig) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PrometheusServiceMonitor) DeepCopyInto(out *PrometheusServiceMonitor) { +func (in *OCIConfig) DeepCopyInto(out *OCIConfig) { *out = *in - if in.Labels != nil { - in, out := &in.Labels, &out.Labels - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } + out.ConfigMap = in.ConfigMap + out.PrivateKey = in.PrivateKey +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OCIConfig. +func (in *OCIConfig) DeepCopy() *OCIConfig { + if in == nil { + return nil } - if in.NamespaceSelector != nil { - in, out := &in.NamespaceSelector, &out.NamespaceSelector - *out = new(monitoringv1.NamespaceSelector) - (*in).DeepCopyInto(*out) + out := new(OCIConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SidecarConfig) DeepCopyInto(out *SidecarConfig) { + *out = *in + if in.Containers != nil { + in, out := &in.Containers, &out.Containers + *out = make([]v1.Container, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } } - if in.Endpoints != nil { - in, out := &in.Endpoints, &out.Endpoints - *out = make([]monitoringv1.Endpoint, len(*in)) + if in.Volumes != nil { + in, out := &in.Volumes, &out.Volumes + *out = make([]v1.Volume, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PrometheusServiceMonitor. -func (in *PrometheusServiceMonitor) DeepCopy() *PrometheusServiceMonitor { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SidecarConfig. +func (in *SidecarConfig) DeepCopy() *SidecarConfig { + if in == nil { + return nil + } + out := new(SidecarConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WalletSecret) DeepCopyInto(out *WalletSecret) { + *out = *in + if in.AdditionalWallets != nil { + in, out := &in.AdditionalWallets, &out.AdditionalWallets + *out = make([]AdditionalWalletSecrets, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WalletSecret. +func (in *WalletSecret) DeepCopy() *WalletSecret { if in == nil { return nil } - out := new(PrometheusServiceMonitor) + out := new(WalletSecret) in.DeepCopyInto(out) return out } diff --git a/apis/observability/v4/databaseobserver_types.go b/apis/observability/v4/databaseobserver_types.go index 2b9df606..c37c67e2 100644 --- a/apis/observability/v4/databaseobserver_types.go +++ b/apis/observability/v4/databaseobserver_types.go @@ -48,78 +48,107 @@ type StatusEnum string // DatabaseObserverSpec defines the desired state of DatabaseObserver type DatabaseObserverSpec struct { - Database DatabaseObserverDatabase `json:"database,omitempty"` - Exporter DatabaseObserverExporterConfig `json:"exporter,omitempty"` - ExporterConfig DatabaseObserverConfigMap `json:"configuration,omitempty"` - Prometheus PrometheusConfig `json:"prometheus,omitempty"` - OCIConfig OCIConfigSpec `json:"ociConfig,omitempty"` - Replicas int32 `json:"replicas,omitempty"` - Log LogConfig `json:"log,omitempty"` - InheritLabels []string `json:"inheritLabels,omitempty"` - ExporterSidecars []corev1.Container `json:"sidecars,omitempty"` - SideCarVolumes []corev1.Volume `json:"sidecarVolumes,omitempty"` + Database DatabaseConfig `json:"database,omitempty"` + Databases map[string]MultiDatabaseConfig `json:"databases,omitempty"` + Wallet WalletSecret `json:"wallet,omitempty"` + Deployment ExporterDeployment `json:"deployment,omitempty"` + Service ExporterService `json:"service,omitempty"` + ServiceMonitor ExporterServiceMonitor `json:"serviceMonitor,omitempty"` + ExporterConfig ExporterConfig `json:"exporterConfig,omitempty"` + OCIConfig OCIConfig `json:"ociConfig,omitempty"` + AzureConfig AzureConfig `json:"azureConfig,omitempty"` + Metrics MetricsConfig `json:"metrics,omitempty"` + Log LogConfig `json:"log,omitempty"` + InheritLabels []string `json:"inheritLabels,omitempty"` + Sidecar SidecarConfig `json:"sidecar,omitempty"` + Replicas int32 `json:"replicas,omitempty"` +} + +// SidecarConfig defines sidecar containers and volumes to add +type SidecarConfig struct { + Containers []corev1.Container `json:"containers,omitempty"` + Volumes []corev1.Volume `json:"volumes,omitempty"` +} + +// ExporterConfig defines configMap used for exporter configuration +type ExporterConfig struct { + ConfigMap ConfigMapDetails `json:"configMap,omitempty"` + MountPath string `json:"mountPath,omitempty"` } // LogConfig defines the configuration details relation to the logs of DatabaseObserver type LogConfig struct { - Path string `json:"path,omitempty"` - Filename string `json:"filename,omitempty"` - Volume LogVolume `json:"volume,omitempty"` + Disable bool `json:"disable,omitempty"` + Destination string `json:"destination,omitempty"` + Filename string `json:"filename,omitempty"` + Volume LogVolume `json:"volume,omitempty"` } +// LogVolume defines the shared volume between the exporter container and other containers type LogVolume struct { - Name string `json:"name,omitempty"` - PersistentVolumeClaim LogVolumePVClaim `json:"persistentVolumeClaim,omitempty"` + Name string `json:"name,omitempty"` + PersistentVolumeClaim LogVolumePVC `json:"persistentVolumeClaim,omitempty"` } -type LogVolumePVClaim struct { +// LogVolumePVC defines the PVC in which to store the logs +type LogVolumePVC struct { ClaimName string `json:"claimName,omitempty"` } -// DatabaseObserverDatabase defines the database details used for DatabaseObserver -type DatabaseObserverDatabase struct { - DBUser DBSecret `json:"dbUser,omitempty"` - DBPassword DBSecretWithVault `json:"dbPassword,omitempty"` - DBWallet DBSecret `json:"dbWallet,omitempty"` - DBConnectionString DBSecret `json:"dbConnectionString,omitempty"` +// DatabaseConfig defines the database details used for DatabaseObserver +type DatabaseConfig struct { + DBUser DBSecret `json:"dbUser,omitempty"` + DBPassword DBSecret `json:"dbPassword,omitempty"` + DBConnectionString DBSecret `json:"dbConnectionString,omitempty"` + OCIVault DBOCIVault `json:"oci,omitempty"` + AzureVault DBAzureVault `json:"azure,omitempty"` } -// DatabaseObserverExporterConfig defines the configuration details related to the exporters of DatabaseObserver -type DatabaseObserverExporterConfig struct { - Deployment DatabaseObserverDeployment `json:"deployment,omitempty"` - Service DatabaseObserverService `json:"service,omitempty"` +// MultiDatabaseConfig defines each database details used for DatabaseObserver +type MultiDatabaseConfig struct { + DBUser DBSecret `json:"dbUser,omitempty"` + DBPassword DBSecret `json:"dbPassword,omitempty"` + DBConnectionString DBSecret `json:"dbConnectionString,omitempty"` } -// DatabaseObserverDeployment defines the exporter deployment component of DatabaseObserver -type DatabaseObserverDeployment struct { - ExporterImage string `json:"image,omitempty"` - SecurityContext *corev1.SecurityContext `json:"securityContext,omitempty"` - ExporterArgs []string `json:"args,omitempty"` - ExporterCommands []string `json:"commands,omitempty"` - ExporterEnvs map[string]string `json:"env,omitempty"` - Labels map[string]string `json:"labels,omitempty"` - DeploymentPodTemplate DeploymentPodTemplate `json:"podTemplate,omitempty"` +// DBAzureVault defines Azure Vault details +type DBAzureVault struct { + VaultID string `json:"vaultID,omitempty"` + VaultUsernameSecret string `json:"vaultUsernameSecret,omitempty"` + VaultPasswordSecret string `json:"vaultPasswordSecret,omitempty"` +} + +// DBOCIVault defines OCI Vault details +type DBOCIVault struct { + VaultID string `json:"vaultID,omitempty"` + VaultPasswordSecret string `json:"vaultPasswordSecret,omitempty"` +} + +// ExporterDeployment defines the exporter deployment component of DatabaseObserver +type ExporterDeployment struct { + ExporterImage string `json:"image,omitempty"` + SecurityContext *corev1.SecurityContext `json:"securityContext,omitempty"` + PodSecurityContext *corev1.PodSecurityContext `json:"podSecurityContext,omitempty"` + ExporterArgs []string `json:"args,omitempty"` + ExporterCommands []string `json:"commands,omitempty"` + ExporterEnvs map[string]string `json:"env,omitempty"` + Labels map[string]string `json:"labels,omitempty"` + DeploymentPodTemplate DeploymentPodTemplate `json:"podTemplate,omitempty"` } // DeploymentPodTemplate defines the labels for the DatabaseObserver pods component of a deployment type DeploymentPodTemplate struct { - SecurityContext *corev1.PodSecurityContext `json:"securityContext,omitempty"` - Labels map[string]string `json:"labels,omitempty"` + Labels map[string]string `json:"labels,omitempty"` } -// DatabaseObserverService defines the exporter service component of DatabaseObserver -type DatabaseObserverService struct { +// ExporterService defines the exporter service component of DatabaseObserver +type ExporterService struct { Ports []corev1.ServicePort `json:"ports,omitempty"` Labels map[string]string `json:"labels,omitempty"` } -// PrometheusConfig defines the generated resources for Prometheus -type PrometheusConfig struct { - ServiceMonitor PrometheusServiceMonitor `json:"serviceMonitor,omitempty"` -} - -// PrometheusServiceMonitor defines DatabaseObserver servicemonitor spec -type PrometheusServiceMonitor struct { +// ExporterServiceMonitor defines DatabaseObserver servicemonitor spec +type ExporterServiceMonitor struct { Labels map[string]string `json:"labels,omitempty"` NamespaceSelector *monitorv1.NamespaceSelector `json:"namespaceSelector,omitempty"` Endpoints []monitorv1.Endpoint `json:"endpoints,omitempty"` @@ -129,40 +158,57 @@ type PrometheusServiceMonitor struct { type DBSecret struct { Key string `json:"key,omitempty"` SecretName string `json:"secret,omitempty"` + EnvName string `json:"envName,omitempty"` +} + +// WalletSecret defines secret and where the wallet will be mounted if provided +type WalletSecret struct { + SecretName string `json:"secret,omitempty"` + MountPath string `json:"mountPath,omitempty"` + AdditionalWallets []AdditionalWalletSecrets `json:"additional,omitempty"` } -// DBSecretWithVault defines secrets used in reference with vault fields -type DBSecretWithVault struct { - Key string `json:"key,omitempty"` - SecretName string `json:"secret,omitempty"` - VaultOCID string `json:"vaultOCID,omitempty"` - VaultSecretName string `json:"vaultSecretName,omitempty"` +// AdditionalWalletSecrets defines multiple other secrets and where the wallet will be mounted if provided +type AdditionalWalletSecrets struct { + Name string `json:"name,omitempty"` + SecretName string `json:"secret,omitempty"` + MountPath string `json:"mountPath,omitempty"` } -// DatabaseObserverConfigMap defines configMap used for metrics configuration -type DatabaseObserverConfigMap struct { - Configmap ConfigMapDetails `json:"configMap,omitempty"` +// MetricsConfig defines configMap used for multiple metrics TOML configuration +type MetricsConfig struct { + Configmap []ConfigMapDetails `json:"configMap,omitempty"` } -// ConfigMapDetails defines the configmap name +// ConfigMapDetails defines the configmap name used by the exporterConfig and metricsConfig type ConfigMapDetails struct { Key string `json:"key,omitempty"` Name string `json:"name,omitempty"` } -// OCIConfigSpec defines the configmap name and secret name used for connecting to OCI -type OCIConfigSpec struct { - ConfigMapName string `json:"configMapName,omitempty"` - SecretName string `json:"secretName,omitempty"` +// OCIConfig defines the configmap name and secret name used for connecting to OCI +type OCIConfig struct { + ConfigMap ConfigMapDetails `json:"configMap,omitempty"` + PrivateKey ConfigPrivateKey `json:"privateKey,omitempty"` + MountPath string `json:"mountPath,omitempty"` +} + +type ConfigPrivateKey struct { + SecretName string `json:"secret,omitempty"` +} + +// AzureConfig defines the configmap name and secret name used for connecting to Azure +type AzureConfig struct { + ConfigMap ConfigMapDetails `json:"configMap,omitempty"` } // DatabaseObserverStatus defines the observed state of DatabaseObserver type DatabaseObserverStatus struct { - Conditions []metav1.Condition `json:"conditions"` - Status string `json:"status,omitempty"` - ExporterConfig string `json:"exporterConfig"` - Version string `json:"version"` - Replicas int `json:"replicas,omitempty"` + Conditions []metav1.Condition `json:"conditions"` + Status string `json:"status,omitempty"` + MetricsConfig string `json:"metricsConfig"` + Version string `json:"version"` + Replicas int `json:"replicas,omitempty"` } //+kubebuilder:object:root=true @@ -170,7 +216,7 @@ type DatabaseObserverStatus struct { // +kubebuilder:resource:shortName="dbobserver";"dbobservers" // DatabaseObserver is the Schema for the databaseobservers API -// +kubebuilder:printcolumn:JSONPath=".status.exporterConfig",name="ExporterConfig",type=string +// +kubebuilder:printcolumn:JSONPath=".status.metricsConfig",name="MetricsConfig",type=string // +kubebuilder:printcolumn:JSONPath=".status.status",name="Status",type=string // +kubebuilder:printcolumn:JSONPath=".status.version",name="Version",type=string // +kubebuilder:storageversion diff --git a/apis/observability/v4/databaseobserver_webhook.go b/apis/observability/v4/databaseobserver_webhook.go index c0a5d8b7..3cb8be87 100644 --- a/apis/observability/v4/databaseobserver_webhook.go +++ b/apis/observability/v4/databaseobserver_webhook.go @@ -39,6 +39,7 @@ package v4 import ( + "context" dbcommons "github.com/oracle/oracle-database-operator/commons/database" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -48,7 +49,6 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/webhook" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - "strings" ) // log is for logging in this package. @@ -58,7 +58,7 @@ const ( AllowedExporterImage = "container-registry.oracle.com/database/observability-exporter" ErrorSpecValidationMissingConnString = "a required field for database connection string secret is missing or does not have a value" ErrorSpecValidationMissingDBUser = "a required field for database user secret is missing or does not have a value" - ErrorSpecValidationMissingDBVaultField = "a field for the OCI vault has a value but the other required field is missing or does not have a value" + ErrorSpecValidationMissingVaultField = "a field for configuring the vault has a value but the other required field(s) is missing or does not have a value" ErrorSpecValidationMissingOCIConfig = "a field(s) for the OCI Config is missing or does not have a value when fields for the OCI vault has values" ErrorSpecValidationMissingDBPasswordSecret = "a required field for the database password secret is missing or does not have a value" ErrorSpecExporterImageNotAllowed = "a different exporter image was found, only official database exporter container images are currently supported" @@ -67,116 +67,102 @@ const ( func (r *DatabaseObserver) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). For(r). + WithDefaulter(r). + WithValidator(r). Complete() } //+kubebuilder:webhook:path=/mutate-observability-oracle-com-v4-databaseobserver,mutating=true,sideEffects=none,failurePolicy=fail,groups=observability.oracle.com,resources=databaseobservers,verbs=create;update,versions=v4,name=mdatabaseobserver.kb.io,admissionReviewVersions=v1 -var _ webhook.Defaulter = &DatabaseObserver{} +var _ webhook.CustomDefaulter = &DatabaseObserver{} // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! -// Default implements webhook.Defaulter so a webhook will be registered for the type -func (r *DatabaseObserver) Default() { - databaseobserverlog.Info("default", "name", r.Name) +// Default implements webhook.CustomDefaulter so a webhook will be registered for the type +func (r *DatabaseObserver) Default(ctx context.Context, obj runtime.Object) error { + obs := obj.(*DatabaseObserver) + databaseobserverlog.Info("default", "name", obs.Name) + + return nil } //+kubebuilder:webhook:verbs=create;update,path=/validate-observability-oracle-com-v4-databaseobserver,mutating=false,sideEffects=none,failurePolicy=fail,groups=observability.oracle.com,resources=databaseobservers,versions=v4,name=vdatabaseobserver.kb.io,admissionReviewVersions=v1 -var _ webhook.Validator = &DatabaseObserver{} +var _ webhook.CustomValidator = &DatabaseObserver{} -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (r *DatabaseObserver) ValidateCreate() (admission.Warnings, error) { - databaseobserverlog.Info("validate create", "name", r.Name) +// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type +func (r *DatabaseObserver) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + obs := obj.(*DatabaseObserver) + databaseobserverlog.Info("validate create", "name", obs.Name) var e field.ErrorList ns := dbcommons.GetWatchNamespaces() // Check for namespace/cluster scope access - if _, isDesiredNamespaceWithinScope := ns[r.Namespace]; !isDesiredNamespaceWithinScope && len(ns) > 0 { + if _, isDesiredNamespaceWithinScope := ns[obs.Namespace]; !isDesiredNamespaceWithinScope && len(ns) > 0 { e = append(e, - field.Invalid(field.NewPath("metadata").Child("namespace"), r.Namespace, + field.Invalid(field.NewPath("metadata").Child("namespace"), obs.Namespace, "Oracle database operator doesn't watch over this namespace")) } - // Check required secret for db user has value - if r.Spec.Database.DBUser.SecretName == "" { - e = append(e, - field.Invalid(field.NewPath("spec").Child("database").Child("dbUser").Child("secret"), r.Spec.Database.DBUser.SecretName, - ErrorSpecValidationMissingDBUser)) - } - - // Check required secret for db connection string has value - if r.Spec.Database.DBConnectionString.SecretName == "" { - e = append(e, - field.Invalid(field.NewPath("spec").Child("database").Child("dbConnectionString").Child("secret"), r.Spec.Database.DBConnectionString.SecretName, - ErrorSpecValidationMissingConnString)) - } - // The other vault field must have value if one does - if (r.Spec.Database.DBPassword.VaultOCID != "" && r.Spec.Database.DBPassword.VaultSecretName == "") || - (r.Spec.Database.DBPassword.VaultSecretName != "" && r.Spec.Database.DBPassword.VaultOCID == "") { - - e = append(e, - field.Invalid(field.NewPath("spec").Child("database").Child("dbPassword"), r.Spec.Database.DBPassword, - ErrorSpecValidationMissingDBVaultField)) - } - - // if vault fields have value, ociConfig must have values - if r.Spec.Database.DBPassword.VaultOCID != "" && r.Spec.Database.DBPassword.VaultSecretName != "" && - (r.Spec.OCIConfig.SecretName == "" || r.Spec.OCIConfig.ConfigMapName == "") { + if (obs.Spec.Database.OCIVault.VaultID != "" && obs.Spec.Database.OCIVault.VaultPasswordSecret == "") || + (obs.Spec.Database.OCIVault.VaultPasswordSecret != "" && obs.Spec.Database.OCIVault.VaultID == "") { e = append(e, - field.Invalid(field.NewPath("spec").Child("ociConfig"), r.Spec.OCIConfig, - ErrorSpecValidationMissingOCIConfig)) + field.Invalid(field.NewPath("spec").Child("database").Child("oci"), obs.Spec.Database.OCIVault, + ErrorSpecValidationMissingVaultField)) } - // If all of {DB Password Secret Name and vaultOCID+vaultSecretName} have no value, then error out - if r.Spec.Database.DBPassword.SecretName == "" && - r.Spec.Database.DBPassword.VaultOCID == "" && - r.Spec.Database.DBPassword.VaultSecretName == "" { + // The other vault field must have value if one does + if (obs.Spec.Database.AzureVault.VaultID != "" && (obs.Spec.Database.AzureVault.VaultPasswordSecret == "" && obs.Spec.Database.AzureVault.VaultUsernameSecret == "")) || + (obs.Spec.Database.AzureVault.VaultPasswordSecret != "" && obs.Spec.Database.AzureVault.VaultID == "") || + (obs.Spec.Database.AzureVault.VaultUsernameSecret != "" && obs.Spec.Database.AzureVault.VaultID == "") { e = append(e, - field.Invalid(field.NewPath("spec").Child("database").Child("dbPassword").Child("secret"), r.Spec.Database.DBPassword.SecretName, - ErrorSpecValidationMissingDBPasswordSecret)) + field.Invalid(field.NewPath("spec").Child("database").Child("azure"), obs.Spec.Database.AzureVault, + ErrorSpecValidationMissingVaultField)) } // disallow usage of any other image than the observability-exporter - if r.Spec.Exporter.Deployment.ExporterImage != "" && !strings.HasPrefix(r.Spec.Exporter.Deployment.ExporterImage, AllowedExporterImage) { - e = append(e, - field.Invalid(field.NewPath("spec").Child("exporter").Child("image"), r.Spec.Exporter.Deployment.ExporterImage, - ErrorSpecExporterImageNotAllowed)) - } + // temporarily disabled + //if r.Spec.Deployment.ExporterImage != "" && !strings.HasPrefix(r.Spec.Deployment.ExporterImage, AllowedExporterImage) { + // e = append(e, + // field.Invalid(field.NewPath("spec").Child("exporter").Child("image"), r.Spec.Deployment.ExporterImage, + // ErrorSpecExporterImageNotAllowed)) + //} // Return if any errors if len(e) > 0 { - return nil, apierrors.NewInvalid(schema.GroupKind{Group: "observability.oracle.com", Kind: "DatabaseObserver"}, r.Name, e) + return nil, apierrors.NewInvalid(schema.GroupKind{Group: "observability.oracle.com", Kind: "DatabaseObserver"}, obs.Name, e) } return nil, nil } -// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *DatabaseObserver) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - databaseobserverlog.Info("validate update", "name", r.Name) +// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type +func (r *DatabaseObserver) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { + obs := newObj.(*DatabaseObserver) + databaseobserverlog.Info("validate update", "name", obs.Name) var e field.ErrorList // disallow usage of any other image than the observability-exporter - if r.Spec.Exporter.Deployment.ExporterImage != "" && !strings.HasPrefix(r.Spec.Exporter.Deployment.ExporterImage, AllowedExporterImage) { - e = append(e, - field.Invalid(field.NewPath("spec").Child("exporter").Child("image"), r.Spec.Exporter.Deployment.ExporterImage, - ErrorSpecExporterImageNotAllowed)) - } + //if r.Spec.Deployment.ExporterImage != "" && !strings.HasPrefix(obs.Spec.Deployment.ExporterImage, AllowedExporterImage) { + // e = append(e, + // field.Invalid(field.NewPath("spec").Child("exporter").Child("image"), obs.Spec.Deployment.ExporterImage, + // ErrorSpecExporterImageNotAllowed)) + //} // Return if any errors if len(e) > 0 { - return nil, apierrors.NewInvalid(schema.GroupKind{Group: "observability.oracle.com", Kind: "DatabaseObserver"}, r.Name, e) + return nil, apierrors.NewInvalid(schema.GroupKind{Group: "observability.oracle.com", Kind: "DatabaseObserver"}, obs.Name, e) } return nil, nil } -// ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *DatabaseObserver) ValidateDelete() (admission.Warnings, error) { - databaseobserverlog.Info("validate delete", "name", r.Name) +// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type +func (r *DatabaseObserver) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + obs := obj.(*DatabaseObserver) + databaseobserverlog.Info("validate delete", "name", obs.Name) return nil, nil } diff --git a/apis/observability/v4/zz_generated.deepcopy.go b/apis/observability/v4/zz_generated.deepcopy.go index d9892643..1d3e1c11 100644 --- a/apis/observability/v4/zz_generated.deepcopy.go +++ b/apis/observability/v4/zz_generated.deepcopy.go @@ -50,174 +50,158 @@ import ( ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ConfigMapDetails) DeepCopyInto(out *ConfigMapDetails) { +func (in *AdditionalWalletSecrets) DeepCopyInto(out *AdditionalWalletSecrets) { *out = *in } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigMapDetails. -func (in *ConfigMapDetails) DeepCopy() *ConfigMapDetails { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalWalletSecrets. +func (in *AdditionalWalletSecrets) DeepCopy() *AdditionalWalletSecrets { if in == nil { return nil } - out := new(ConfigMapDetails) + out := new(AdditionalWalletSecrets) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DBSecret) DeepCopyInto(out *DBSecret) { +func (in *AzureConfig) DeepCopyInto(out *AzureConfig) { *out = *in + out.ConfigMap = in.ConfigMap } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DBSecret. -func (in *DBSecret) DeepCopy() *DBSecret { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AzureConfig. +func (in *AzureConfig) DeepCopy() *AzureConfig { if in == nil { return nil } - out := new(DBSecret) + out := new(AzureConfig) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DBSecretWithVault) DeepCopyInto(out *DBSecretWithVault) { +func (in *ConfigMapDetails) DeepCopyInto(out *ConfigMapDetails) { *out = *in } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DBSecretWithVault. -func (in *DBSecretWithVault) DeepCopy() *DBSecretWithVault { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigMapDetails. +func (in *ConfigMapDetails) DeepCopy() *ConfigMapDetails { if in == nil { return nil } - out := new(DBSecretWithVault) + out := new(ConfigMapDetails) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DatabaseObserver) DeepCopyInto(out *DatabaseObserver) { +func (in *ConfigPrivateKey) DeepCopyInto(out *ConfigPrivateKey) { *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseObserver. -func (in *DatabaseObserver) DeepCopy() *DatabaseObserver { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigPrivateKey. +func (in *ConfigPrivateKey) DeepCopy() *ConfigPrivateKey { if in == nil { return nil } - out := new(DatabaseObserver) + out := new(ConfigPrivateKey) in.DeepCopyInto(out) return out } -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *DatabaseObserver) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DBAzureVault) DeepCopyInto(out *DBAzureVault) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DBAzureVault. +func (in *DBAzureVault) DeepCopy() *DBAzureVault { + if in == nil { + return nil } - return nil + out := new(DBAzureVault) + in.DeepCopyInto(out) + return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DatabaseObserverConfigMap) DeepCopyInto(out *DatabaseObserverConfigMap) { +func (in *DBOCIVault) DeepCopyInto(out *DBOCIVault) { *out = *in - out.Configmap = in.Configmap } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseObserverConfigMap. -func (in *DatabaseObserverConfigMap) DeepCopy() *DatabaseObserverConfigMap { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DBOCIVault. +func (in *DBOCIVault) DeepCopy() *DBOCIVault { if in == nil { return nil } - out := new(DatabaseObserverConfigMap) + out := new(DBOCIVault) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DatabaseObserverDatabase) DeepCopyInto(out *DatabaseObserverDatabase) { +func (in *DBSecret) DeepCopyInto(out *DBSecret) { *out = *in - out.DBUser = in.DBUser - out.DBPassword = in.DBPassword - out.DBWallet = in.DBWallet - out.DBConnectionString = in.DBConnectionString } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseObserverDatabase. -func (in *DatabaseObserverDatabase) DeepCopy() *DatabaseObserverDatabase { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DBSecret. +func (in *DBSecret) DeepCopy() *DBSecret { if in == nil { return nil } - out := new(DatabaseObserverDatabase) + out := new(DBSecret) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DatabaseObserverDeployment) DeepCopyInto(out *DatabaseObserverDeployment) { +func (in *DatabaseConfig) DeepCopyInto(out *DatabaseConfig) { *out = *in - if in.SecurityContext != nil { - in, out := &in.SecurityContext, &out.SecurityContext - *out = new(v1.SecurityContext) - (*in).DeepCopyInto(*out) - } - if in.ExporterArgs != nil { - in, out := &in.ExporterArgs, &out.ExporterArgs - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.ExporterCommands != nil { - in, out := &in.ExporterCommands, &out.ExporterCommands - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.ExporterEnvs != nil { - in, out := &in.ExporterEnvs, &out.ExporterEnvs - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.Labels != nil { - in, out := &in.Labels, &out.Labels - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - in.DeploymentPodTemplate.DeepCopyInto(&out.DeploymentPodTemplate) + out.DBUser = in.DBUser + out.DBPassword = in.DBPassword + out.DBConnectionString = in.DBConnectionString + out.OCIVault = in.OCIVault + out.AzureVault = in.AzureVault } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseObserverDeployment. -func (in *DatabaseObserverDeployment) DeepCopy() *DatabaseObserverDeployment { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseConfig. +func (in *DatabaseConfig) DeepCopy() *DatabaseConfig { if in == nil { return nil } - out := new(DatabaseObserverDeployment) + out := new(DatabaseConfig) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DatabaseObserverExporterConfig) DeepCopyInto(out *DatabaseObserverExporterConfig) { +func (in *DatabaseObserver) DeepCopyInto(out *DatabaseObserver) { *out = *in - in.Deployment.DeepCopyInto(&out.Deployment) - in.Service.DeepCopyInto(&out.Service) + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseObserverExporterConfig. -func (in *DatabaseObserverExporterConfig) DeepCopy() *DatabaseObserverExporterConfig { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseObserver. +func (in *DatabaseObserver) DeepCopy() *DatabaseObserver { if in == nil { return nil } - out := new(DatabaseObserverExporterConfig) + out := new(DatabaseObserver) in.DeepCopyInto(out) return out } +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DatabaseObserver) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DatabaseObserverList) DeepCopyInto(out *DatabaseObserverList) { *out = *in @@ -251,15 +235,68 @@ func (in *DatabaseObserverList) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DatabaseObserverService) DeepCopyInto(out *DatabaseObserverService) { +func (in *DatabaseObserverSpec) DeepCopyInto(out *DatabaseObserverSpec) { *out = *in - if in.Ports != nil { - in, out := &in.Ports, &out.Ports - *out = make([]v1.ServicePort, len(*in)) + out.Database = in.Database + if in.Databases != nil { + in, out := &in.Databases, &out.Databases + *out = make(map[string]MultiDatabaseConfig, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + in.Wallet.DeepCopyInto(&out.Wallet) + in.Deployment.DeepCopyInto(&out.Deployment) + in.Service.DeepCopyInto(&out.Service) + in.ServiceMonitor.DeepCopyInto(&out.ServiceMonitor) + out.ExporterConfig = in.ExporterConfig + out.OCIConfig = in.OCIConfig + out.AzureConfig = in.AzureConfig + in.Metrics.DeepCopyInto(&out.Metrics) + out.Log = in.Log + if in.InheritLabels != nil { + in, out := &in.InheritLabels, &out.InheritLabels + *out = make([]string, len(*in)) + copy(*out, *in) + } + in.Sidecar.DeepCopyInto(&out.Sidecar) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseObserverSpec. +func (in *DatabaseObserverSpec) DeepCopy() *DatabaseObserverSpec { + if in == nil { + return nil + } + out := new(DatabaseObserverSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DatabaseObserverStatus) DeepCopyInto(out *DatabaseObserverStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseObserverStatus. +func (in *DatabaseObserverStatus) DeepCopy() *DatabaseObserverStatus { + if in == nil { + return nil + } + out := new(DatabaseObserverStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DeploymentPodTemplate) DeepCopyInto(out *DeploymentPodTemplate) { + *out = *in if in.Labels != nil { in, out := &in.Labels, &out.Labels *out = make(map[string]string, len(*in)) @@ -269,86 +306,114 @@ func (in *DatabaseObserverService) DeepCopyInto(out *DatabaseObserverService) { } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseObserverService. -func (in *DatabaseObserverService) DeepCopy() *DatabaseObserverService { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeploymentPodTemplate. +func (in *DeploymentPodTemplate) DeepCopy() *DeploymentPodTemplate { if in == nil { return nil } - out := new(DatabaseObserverService) + out := new(DeploymentPodTemplate) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DatabaseObserverSpec) DeepCopyInto(out *DatabaseObserverSpec) { +func (in *ExporterConfig) DeepCopyInto(out *ExporterConfig) { *out = *in - out.Database = in.Database - in.Exporter.DeepCopyInto(&out.Exporter) - out.ExporterConfig = in.ExporterConfig - in.Prometheus.DeepCopyInto(&out.Prometheus) - out.OCIConfig = in.OCIConfig - out.Log = in.Log - if in.InheritLabels != nil { - in, out := &in.InheritLabels, &out.InheritLabels + out.ConfigMap = in.ConfigMap +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExporterConfig. +func (in *ExporterConfig) DeepCopy() *ExporterConfig { + if in == nil { + return nil + } + out := new(ExporterConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExporterDeployment) DeepCopyInto(out *ExporterDeployment) { + *out = *in + if in.SecurityContext != nil { + in, out := &in.SecurityContext, &out.SecurityContext + *out = new(v1.SecurityContext) + (*in).DeepCopyInto(*out) + } + if in.PodSecurityContext != nil { + in, out := &in.PodSecurityContext, &out.PodSecurityContext + *out = new(v1.PodSecurityContext) + (*in).DeepCopyInto(*out) + } + if in.ExporterArgs != nil { + in, out := &in.ExporterArgs, &out.ExporterArgs *out = make([]string, len(*in)) copy(*out, *in) } - if in.ExporterSidecars != nil { - in, out := &in.ExporterSidecars, &out.ExporterSidecars - *out = make([]v1.Container, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) + if in.ExporterCommands != nil { + in, out := &in.ExporterCommands, &out.ExporterCommands + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ExporterEnvs != nil { + in, out := &in.ExporterEnvs, &out.ExporterEnvs + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val } } - if in.SideCarVolumes != nil { - in, out := &in.SideCarVolumes, &out.SideCarVolumes - *out = make([]v1.Volume, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val } } + in.DeploymentPodTemplate.DeepCopyInto(&out.DeploymentPodTemplate) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseObserverSpec. -func (in *DatabaseObserverSpec) DeepCopy() *DatabaseObserverSpec { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExporterDeployment. +func (in *ExporterDeployment) DeepCopy() *ExporterDeployment { if in == nil { return nil } - out := new(DatabaseObserverSpec) + out := new(ExporterDeployment) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DatabaseObserverStatus) DeepCopyInto(out *DatabaseObserverStatus) { +func (in *ExporterService) DeepCopyInto(out *ExporterService) { *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]metav1.Condition, len(*in)) + if in.Ports != nil { + in, out := &in.Ports, &out.Ports + *out = make([]v1.ServicePort, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseObserverStatus. -func (in *DatabaseObserverStatus) DeepCopy() *DatabaseObserverStatus { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExporterService. +func (in *ExporterService) DeepCopy() *ExporterService { if in == nil { return nil } - out := new(DatabaseObserverStatus) + out := new(ExporterService) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DeploymentPodTemplate) DeepCopyInto(out *DeploymentPodTemplate) { +func (in *ExporterServiceMonitor) DeepCopyInto(out *ExporterServiceMonitor) { *out = *in - if in.SecurityContext != nil { - in, out := &in.SecurityContext, &out.SecurityContext - *out = new(v1.PodSecurityContext) - (*in).DeepCopyInto(*out) - } if in.Labels != nil { in, out := &in.Labels, &out.Labels *out = make(map[string]string, len(*in)) @@ -356,14 +421,26 @@ func (in *DeploymentPodTemplate) DeepCopyInto(out *DeploymentPodTemplate) { (*out)[key] = val } } + if in.NamespaceSelector != nil { + in, out := &in.NamespaceSelector, &out.NamespaceSelector + *out = new(monitoringv1.NamespaceSelector) + (*in).DeepCopyInto(*out) + } + if in.Endpoints != nil { + in, out := &in.Endpoints, &out.Endpoints + *out = make([]monitoringv1.Endpoint, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeploymentPodTemplate. -func (in *DeploymentPodTemplate) DeepCopy() *DeploymentPodTemplate { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExporterServiceMonitor. +func (in *ExporterServiceMonitor) DeepCopy() *ExporterServiceMonitor { if in == nil { return nil } - out := new(DeploymentPodTemplate) + out := new(ExporterServiceMonitor) in.DeepCopyInto(out) return out } @@ -401,81 +478,120 @@ func (in *LogVolume) DeepCopy() *LogVolume { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *LogVolumePVClaim) DeepCopyInto(out *LogVolumePVClaim) { +func (in *LogVolumePVC) DeepCopyInto(out *LogVolumePVC) { *out = *in } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LogVolumePVClaim. -func (in *LogVolumePVClaim) DeepCopy() *LogVolumePVClaim { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LogVolumePVC. +func (in *LogVolumePVC) DeepCopy() *LogVolumePVC { if in == nil { return nil } - out := new(LogVolumePVClaim) + out := new(LogVolumePVC) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *OCIConfigSpec) DeepCopyInto(out *OCIConfigSpec) { +func (in *MetricsConfig) DeepCopyInto(out *MetricsConfig) { *out = *in + if in.Configmap != nil { + in, out := &in.Configmap, &out.Configmap + *out = make([]ConfigMapDetails, len(*in)) + copy(*out, *in) + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OCIConfigSpec. -func (in *OCIConfigSpec) DeepCopy() *OCIConfigSpec { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricsConfig. +func (in *MetricsConfig) DeepCopy() *MetricsConfig { if in == nil { return nil } - out := new(OCIConfigSpec) + out := new(MetricsConfig) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PrometheusConfig) DeepCopyInto(out *PrometheusConfig) { +func (in *MultiDatabaseConfig) DeepCopyInto(out *MultiDatabaseConfig) { *out = *in - in.ServiceMonitor.DeepCopyInto(&out.ServiceMonitor) + out.DBUser = in.DBUser + out.DBPassword = in.DBPassword + out.DBConnectionString = in.DBConnectionString } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PrometheusConfig. -func (in *PrometheusConfig) DeepCopy() *PrometheusConfig { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MultiDatabaseConfig. +func (in *MultiDatabaseConfig) DeepCopy() *MultiDatabaseConfig { if in == nil { return nil } - out := new(PrometheusConfig) + out := new(MultiDatabaseConfig) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PrometheusServiceMonitor) DeepCopyInto(out *PrometheusServiceMonitor) { +func (in *OCIConfig) DeepCopyInto(out *OCIConfig) { *out = *in - if in.Labels != nil { - in, out := &in.Labels, &out.Labels - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } + out.ConfigMap = in.ConfigMap + out.PrivateKey = in.PrivateKey +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OCIConfig. +func (in *OCIConfig) DeepCopy() *OCIConfig { + if in == nil { + return nil } - if in.NamespaceSelector != nil { - in, out := &in.NamespaceSelector, &out.NamespaceSelector - *out = new(monitoringv1.NamespaceSelector) - (*in).DeepCopyInto(*out) + out := new(OCIConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SidecarConfig) DeepCopyInto(out *SidecarConfig) { + *out = *in + if in.Containers != nil { + in, out := &in.Containers, &out.Containers + *out = make([]v1.Container, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } } - if in.Endpoints != nil { - in, out := &in.Endpoints, &out.Endpoints - *out = make([]monitoringv1.Endpoint, len(*in)) + if in.Volumes != nil { + in, out := &in.Volumes, &out.Volumes + *out = make([]v1.Volume, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PrometheusServiceMonitor. -func (in *PrometheusServiceMonitor) DeepCopy() *PrometheusServiceMonitor { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SidecarConfig. +func (in *SidecarConfig) DeepCopy() *SidecarConfig { + if in == nil { + return nil + } + out := new(SidecarConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WalletSecret) DeepCopyInto(out *WalletSecret) { + *out = *in + if in.AdditionalWallets != nil { + in, out := &in.AdditionalWallets, &out.AdditionalWallets + *out = make([]AdditionalWalletSecrets, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WalletSecret. +func (in *WalletSecret) DeepCopy() *WalletSecret { if in == nil { return nil } - out := new(PrometheusServiceMonitor) + out := new(WalletSecret) in.DeepCopyInto(out) return out } diff --git a/bundle.Dockerfile b/bundle.Dockerfile index d591c4ef..609d09c0 100644 --- a/bundle.Dockerfile +++ b/bundle.Dockerfile @@ -1,7 +1,3 @@ -# Copyright (c) 2022, Oracle and/or its affiliates. -# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. -# - FROM scratch # Core bundle labels. @@ -10,16 +6,10 @@ LABEL operators.operatorframework.io.bundle.manifests.v1=manifests/ LABEL operators.operatorframework.io.bundle.metadata.v1=metadata/ LABEL operators.operatorframework.io.bundle.package.v1=oracle-database-operator LABEL operators.operatorframework.io.bundle.channels.v1=alpha -LABEL operators.operatorframework.io.bundle.channel.default.v1=alpha +LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.40.0 LABEL operators.operatorframework.io.metrics.mediatype.v1=metrics+v1 -LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.6.1+git -LABEL operators.operatorframework.io.metrics.project_layout=go.kubebuilder.io/v2 - -# Labels for testing. -LABEL operators.operatorframework.io.test.mediatype.v1=scorecard+v1 -LABEL operators.operatorframework.io.test.config.v1=tests/scorecard/ +LABEL operators.operatorframework.io.metrics.project_layout=go.kubebuilder.io/v4 # Copy files to locations specified by labels. COPY bundle/manifests /manifests/ COPY bundle/metadata /metadata/ -COPY bundle/tests/scorecard /tests/scorecard/ diff --git a/config/database.oracle.com_autonomouscontainerdatabases.yaml b/bundle/manifests/database.oracle.com_autonomouscontainerdatabases.yaml similarity index 50% rename from config/database.oracle.com_autonomouscontainerdatabases.yaml rename to bundle/manifests/database.oracle.com_autonomouscontainerdatabases.yaml index bac3a28c..1cae1263 100644 --- a/config/database.oracle.com_autonomouscontainerdatabases.yaml +++ b/bundle/manifests/database.oracle.com_autonomouscontainerdatabases.yaml @@ -1,13 +1,24 @@ - ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.1 + cert-manager.io/inject-ca-from: oracle-database-operator-system/oracle-database-operator-serving-cert + controller-gen.kubebuilder.io/version: v0.16.5 creationTimestamp: null name: autonomouscontainerdatabases.database.oracle.com spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + name: oracle-database-operator-webhook-service + namespace: oracle-database-operator-system + path: /convert + conversionReviewVersions: + - v1alpha1 + - v1 + - v4 group: database.oracle.com names: kind: AutonomousContainerDatabase @@ -32,24 +43,14 @@ spec: name: v1alpha1 schema: openAPIV3Schema: - description: AutonomousContainerDatabase is the Schema for the autonomouscontainerdatabases - API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object spec: - description: AutonomousContainerDatabaseSpec defines the desired state - of AutonomousContainerDatabase properties: action: enum: @@ -58,8 +59,6 @@ spec: - TERMINATE type: string autonomousContainerDatabaseOCID: - description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster - Important: Run "make" to regenerate code after modifying this file' type: string autonomousExadataVMClusterOCID: type: string @@ -75,7 +74,6 @@ spec: default: false type: boolean ociConfig: - description: "*********************** *\tOCI config ***********************" properties: configMapName: type: string @@ -83,21 +81,84 @@ spec: type: string type: object patchModel: - description: 'AutonomousContainerDatabasePatchModelEnum Enum with - underlying type: string' enum: - RELEASE_UPDATES - RELEASE_UPDATE_REVISIONS type: string type: object status: - description: AutonomousContainerDatabaseStatus defines the observed state - of AutonomousContainerDatabase properties: lifecycleState: - description: 'INSERT ADDITIONAL STATUS FIELD - define observed state - of cluster Important: Run "make" to regenerate code after modifying - this file' + type: string + timeCreated: + type: string + required: + - lifecycleState + type: object + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.displayName + name: DisplayName + type: string + - jsonPath: .status.lifecycleState + name: State + type: string + - jsonPath: .status.timeCreated + name: Created + type: string + name: v4 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + action: + enum: + - SYNC + - RESTART + - TERMINATE + type: string + autonomousContainerDatabaseOCID: + type: string + autonomousExadataVMClusterOCID: + type: string + compartmentOCID: + type: string + displayName: + type: string + freeformTags: + additionalProperties: + type: string + type: object + hardLink: + default: false + type: boolean + ociConfig: + properties: + configMapName: + type: string + secretName: + type: string + type: object + patchModel: + enum: + - RELEASE_UPDATES + - RELEASE_UPDATE_REVISIONS + type: string + type: object + status: + properties: + lifecycleState: type: string timeCreated: type: string @@ -113,5 +174,5 @@ status: acceptedNames: kind: "" plural: "" - conditions: [] - storedVersions: [] + conditions: null + storedVersions: null diff --git a/config/database.oracle.com_autonomousdatabasebackups.yaml b/bundle/manifests/database.oracle.com_autonomousdatabasebackups.yaml similarity index 50% rename from config/database.oracle.com_autonomousdatabasebackups.yaml rename to bundle/manifests/database.oracle.com_autonomousdatabasebackups.yaml index a5c37507..06df19c3 100644 --- a/config/database.oracle.com_autonomousdatabasebackups.yaml +++ b/bundle/manifests/database.oracle.com_autonomousdatabasebackups.yaml @@ -1,13 +1,24 @@ - ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.1 + cert-manager.io/inject-ca-from: oracle-database-operator-system/oracle-database-operator-serving-cert + controller-gen.kubebuilder.io/version: v0.16.5 creationTimestamp: null name: autonomousdatabasebackups.database.oracle.com spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + name: oracle-database-operator-webhook-service + namespace: oracle-database-operator-system + path: /convert + conversionReviewVersions: + - v1alpha1 + - v1 + - v4 group: database.oracle.com names: kind: AutonomousDatabaseBackup @@ -38,24 +49,14 @@ spec: name: v1alpha1 schema: openAPIV3Schema: - description: AutonomousDatabaseBackup is the Schema for the autonomousdatabasebackups - API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object spec: - description: AutonomousDatabaseBackupSpec defines the desired state of - AutonomousDatabaseBackup properties: autonomousDatabaseBackupOCID: type: string @@ -64,7 +65,6 @@ spec: isLongTermBackup: type: boolean ociConfig: - description: "*********************** *\tOCI config ***********************" properties: configMapName: type: string @@ -74,11 +74,8 @@ spec: retentionPeriodInDays: type: integer target: - description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster - Important: Run "make" to regenerate code after modifying this file' properties: k8sADB: - description: "*********************** *\tADB spec ***********************" properties: name: type: string @@ -91,8 +88,6 @@ spec: type: object type: object status: - description: AutonomousDatabaseBackupStatus defines the observed state - of AutonomousDatabaseBackup properties: autonomousDatabaseOCID: type: string @@ -105,16 +100,103 @@ spec: isAutomatic: type: boolean lifecycleState: - description: 'AutonomousDatabaseBackupLifecycleStateEnum Enum with - underlying type: string' type: string timeEnded: type: string timeStarted: type: string type: - description: 'AutonomousDatabaseBackupTypeEnum Enum with underlying - type: string' + type: string + required: + - autonomousDatabaseOCID + - compartmentOCID + - dbDisplayName + - dbName + - isAutomatic + - lifecycleState + - type + type: object + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .status.lifecycleState + name: State + type: string + - jsonPath: .status.dbDisplayName + name: DB DisplayName + type: string + - jsonPath: .status.type + name: Type + type: string + - jsonPath: .status.timeStarted + name: Started + type: string + - jsonPath: .status.timeEnded + name: Ended + type: string + name: v4 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + autonomousDatabaseBackupOCID: + type: string + displayName: + type: string + isLongTermBackup: + type: boolean + ociConfig: + properties: + configMapName: + type: string + secretName: + type: string + type: object + retentionPeriodInDays: + type: integer + target: + properties: + k8sADB: + properties: + name: + type: string + type: object + ociADB: + properties: + ocid: + type: string + type: object + type: object + type: object + status: + properties: + autonomousDatabaseOCID: + type: string + compartmentOCID: + type: string + dbDisplayName: + type: string + dbName: + type: string + isAutomatic: + type: boolean + lifecycleState: + type: string + timeEnded: + type: string + timeStarted: + type: string + type: type: string required: - autonomousDatabaseOCID @@ -134,5 +216,5 @@ status: acceptedNames: kind: "" plural: "" - conditions: [] - storedVersions: [] + conditions: null + storedVersions: null diff --git a/config/database.oracle.com_autonomousdatabaserestores.yaml b/bundle/manifests/database.oracle.com_autonomousdatabaserestores.yaml similarity index 50% rename from config/database.oracle.com_autonomousdatabaserestores.yaml rename to bundle/manifests/database.oracle.com_autonomousdatabaserestores.yaml index 5e9f2c73..7f2d386d 100644 --- a/config/database.oracle.com_autonomousdatabaserestores.yaml +++ b/bundle/manifests/database.oracle.com_autonomousdatabaserestores.yaml @@ -1,13 +1,24 @@ - ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.1 + cert-manager.io/inject-ca-from: oracle-database-operator-system/oracle-database-operator-serving-cert + controller-gen.kubebuilder.io/version: v0.16.5 creationTimestamp: null name: autonomousdatabaserestores.database.oracle.com spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + name: oracle-database-operator-webhook-service + namespace: oracle-database-operator-system + path: /convert + conversionReviewVersions: + - v1alpha1 + - v1 + - v4 group: database.oracle.com names: kind: AutonomousDatabaseRestore @@ -32,27 +43,16 @@ spec: name: v1alpha1 schema: openAPIV3Schema: - description: AutonomousDatabaseRestore is the Schema for the autonomousdatabaserestores - API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object spec: - description: AutonomousDatabaseRestoreSpec defines the desired state of - AutonomousDatabaseRestore properties: ociConfig: - description: "*********************** *\tOCI config ***********************" properties: configMapName: type: string @@ -62,9 +62,6 @@ spec: source: properties: k8sADBBackup: - description: 'EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO - OWN! NOTE: json tags are required. Any new fields you add must - have json tags for the fields to be serialized.' properties: name: type: string @@ -72,17 +69,12 @@ spec: pointInTime: properties: timestamp: - description: 'The timestamp must follow this format: YYYY-MM-DD - HH:MM:SS GMT' type: string type: object type: object target: - description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster - Important: Run "make" to regenerate code after modifying this file' properties: k8sADB: - description: "*********************** *\tADB spec ***********************" properties: name: type: string @@ -98,18 +90,98 @@ spec: - target type: object status: - description: AutonomousDatabaseRestoreStatus defines the observed state - of AutonomousDatabaseRestore properties: dbName: type: string displayName: - description: 'INSERT ADDITIONAL STATUS FIELD - define observed state - of cluster Important: Run "make" to regenerate code after modifying - this file' type: string status: - description: 'WorkRequestStatusEnum Enum with underlying type: string' + type: string + timeAccepted: + type: string + timeEnded: + type: string + timeStarted: + type: string + workRequestOCID: + type: string + required: + - dbName + - displayName + - status + - workRequestOCID + type: object + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .status.status + name: Status + type: string + - jsonPath: .status.displayName + name: DbDisplayName + type: string + - jsonPath: .status.dbName + name: DbName + type: string + name: v4 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + ociConfig: + properties: + configMapName: + type: string + secretName: + type: string + type: object + source: + properties: + k8sADBBackup: + properties: + name: + type: string + type: object + pointInTime: + properties: + timestamp: + type: string + type: object + type: object + target: + properties: + k8sADB: + properties: + name: + type: string + type: object + ociADB: + properties: + ocid: + type: string + type: object + type: object + required: + - source + - target + type: object + status: + properties: + dbName: + type: string + displayName: + type: string + status: type: string timeAccepted: type: string @@ -134,5 +206,5 @@ status: acceptedNames: kind: "" plural: "" - conditions: [] - storedVersions: [] + conditions: null + storedVersions: null diff --git a/bundle/manifests/database.oracle.com_autonomousdatabases.yaml b/bundle/manifests/database.oracle.com_autonomousdatabases.yaml new file mode 100644 index 00000000..f2e82d0e --- /dev/null +++ b/bundle/manifests/database.oracle.com_autonomousdatabases.yaml @@ -0,0 +1,704 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: oracle-database-operator-system/oracle-database-operator-serving-cert + controller-gen.kubebuilder.io/version: v0.16.5 + creationTimestamp: null + name: autonomousdatabases.database.oracle.com +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + name: oracle-database-operator-webhook-service + namespace: oracle-database-operator-system + path: /convert + conversionReviewVersions: + - v1alpha1 + - v1 + - v4 + group: database.oracle.com + names: + kind: AutonomousDatabase + listKind: AutonomousDatabaseList + plural: autonomousdatabases + shortNames: + - adb + - adbs + singular: autonomousdatabase + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.details.displayName + name: Display Name + type: string + - jsonPath: .spec.details.dbName + name: Db Name + type: string + - jsonPath: .status.lifecycleState + name: State + type: string + - jsonPath: .spec.details.isDedicated + name: Dedicated + type: string + - jsonPath: .spec.details.cpuCoreCount + name: OCPUs + type: integer + - jsonPath: .spec.details.dataStorageSizeInTBs + name: Storage (TB) + type: integer + - jsonPath: .spec.details.dbWorkload + name: Workload Type + type: string + - jsonPath: .status.timeCreated + name: Created + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + action: + enum: + - "" + - Create + - Sync + - Update + - Stop + - Start + - Terminate + - Clone + type: string + clone: + properties: + adminPassword: + properties: + k8sSecret: + properties: + name: + type: string + type: object + ociSecret: + properties: + id: + type: string + type: object + type: object + autonomousContainerDatabase: + properties: + k8sAcd: + properties: + name: + type: string + type: object + ociAcd: + properties: + id: + type: string + type: object + type: object + cloneType: + enum: + - FULL + - METADATA + type: string + compartmentId: + type: string + computeCount: + type: number + computeModel: + enum: + - ECPU + - OCPU + type: string + cpuCoreCount: + type: integer + dataStorageSizeInTBs: + type: integer + dbName: + type: string + dbVersion: + type: string + dbWorkload: + enum: + - OLTP + - DW + - AJD + - APEX + type: string + displayName: + type: string + freeformTags: + additionalProperties: + type: string + type: object + isAccessControlEnabled: + type: boolean + isAutoScalingEnabled: + type: boolean + isDedicated: + type: boolean + isFreeTier: + type: boolean + isMtlsConnectionRequired: + type: boolean + licenseModel: + enum: + - LICENSE_INCLUDED + - BRING_YOUR_OWN_LICENSE + type: string + nsgIds: + items: + type: string + type: array + ocpuCount: + type: number + privateEndpointLabel: + type: string + subnetId: + type: string + whitelistedIps: + items: + type: string + type: array + type: object + details: + properties: + adminPassword: + properties: + k8sSecret: + properties: + name: + type: string + type: object + ociSecret: + properties: + id: + type: string + type: object + type: object + autonomousContainerDatabase: + properties: + k8sAcd: + properties: + name: + type: string + type: object + ociAcd: + properties: + id: + type: string + type: object + type: object + compartmentId: + type: string + computeCount: + type: number + computeModel: + enum: + - ECPU + - OCPU + type: string + cpuCoreCount: + type: integer + dataStorageSizeInTBs: + type: integer + dbName: + type: string + dbVersion: + type: string + dbWorkload: + enum: + - OLTP + - DW + - AJD + - APEX + type: string + displayName: + type: string + freeformTags: + additionalProperties: + type: string + type: object + id: + type: string + isAccessControlEnabled: + type: boolean + isAutoScalingEnabled: + type: boolean + isDedicated: + type: boolean + isFreeTier: + type: boolean + isMtlsConnectionRequired: + type: boolean + licenseModel: + enum: + - LICENSE_INCLUDED + - BRING_YOUR_OWN_LICENSE + type: string + nsgIds: + items: + type: string + type: array + ocpuCount: + type: number + privateEndpointLabel: + type: string + subnetId: + type: string + whitelistedIps: + items: + type: string + type: array + type: object + hardLink: + default: false + type: boolean + ociConfig: + properties: + configMapName: + type: string + secretName: + type: string + type: object + wallet: + properties: + name: + type: string + password: + properties: + k8sSecret: + properties: + name: + type: string + type: object + ociSecret: + properties: + id: + type: string + type: object + type: object + type: object + required: + - action + type: object + status: + properties: + allConnectionStrings: + items: + properties: + connectionStrings: + items: + properties: + connectionString: + type: string + tnsName: + type: string + type: object + type: array + tlsAuthentication: + type: string + required: + - connectionStrings + type: object + type: array + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + maxLength: 32768 + type: string + observedGeneration: + format: int64 + minimum: 0 + type: integer + reason: + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + enum: + - "True" + - "False" + - Unknown + type: string + type: + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + lifecycleState: + type: string + timeCreated: + type: string + walletExpiringDate: + type: string + type: object + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.details.displayName + name: Display Name + type: string + - jsonPath: .spec.details.dbName + name: Db Name + type: string + - jsonPath: .status.lifecycleState + name: State + type: string + - jsonPath: .spec.details.isDedicated + name: Dedicated + type: string + - jsonPath: .spec.details.cpuCoreCount + name: OCPUs + type: integer + - jsonPath: .spec.details.dataStorageSizeInTBs + name: Storage (TB) + type: integer + - jsonPath: .spec.details.dbWorkload + name: Workload Type + type: string + - jsonPath: .status.timeCreated + name: Created + type: string + name: v4 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + action: + enum: + - "" + - Create + - Sync + - Update + - Stop + - Start + - Terminate + - Clone + type: string + clone: + properties: + adminPassword: + properties: + k8sSecret: + properties: + name: + type: string + type: object + ociSecret: + properties: + id: + type: string + type: object + type: object + autonomousContainerDatabase: + properties: + k8sAcd: + properties: + name: + type: string + type: object + ociAcd: + properties: + id: + type: string + type: object + type: object + cloneType: + enum: + - FULL + - METADATA + type: string + compartmentId: + type: string + computeCount: + type: number + computeModel: + enum: + - ECPU + - OCPU + type: string + cpuCoreCount: + type: integer + dataStorageSizeInTBs: + type: integer + dbName: + type: string + dbVersion: + type: string + dbWorkload: + enum: + - OLTP + - DW + - AJD + - APEX + type: string + displayName: + type: string + freeformTags: + additionalProperties: + type: string + type: object + isAccessControlEnabled: + type: boolean + isAutoScalingEnabled: + type: boolean + isDedicated: + type: boolean + isFreeTier: + type: boolean + isMtlsConnectionRequired: + type: boolean + licenseModel: + enum: + - LICENSE_INCLUDED + - BRING_YOUR_OWN_LICENSE + type: string + nsgIds: + items: + type: string + type: array + ocpuCount: + type: number + privateEndpointLabel: + type: string + subnetId: + type: string + whitelistedIps: + items: + type: string + type: array + type: object + details: + properties: + adminPassword: + properties: + k8sSecret: + properties: + name: + type: string + type: object + ociSecret: + properties: + id: + type: string + type: object + type: object + autonomousContainerDatabase: + properties: + k8sAcd: + properties: + name: + type: string + type: object + ociAcd: + properties: + id: + type: string + type: object + type: object + compartmentId: + type: string + computeCount: + type: number + computeModel: + enum: + - ECPU + - OCPU + type: string + cpuCoreCount: + type: integer + dataStorageSizeInTBs: + type: integer + dbName: + type: string + dbVersion: + type: string + dbWorkload: + enum: + - OLTP + - DW + - AJD + - APEX + type: string + displayName: + type: string + freeformTags: + additionalProperties: + type: string + type: object + id: + type: string + isAccessControlEnabled: + type: boolean + isAutoScalingEnabled: + type: boolean + isDedicated: + type: boolean + isFreeTier: + type: boolean + isMtlsConnectionRequired: + type: boolean + licenseModel: + enum: + - LICENSE_INCLUDED + - BRING_YOUR_OWN_LICENSE + type: string + nsgIds: + items: + type: string + type: array + ocpuCount: + type: number + privateEndpointLabel: + type: string + subnetId: + type: string + whitelistedIps: + items: + type: string + type: array + type: object + hardLink: + default: false + type: boolean + ociConfig: + properties: + configMapName: + type: string + secretName: + type: string + type: object + wallet: + properties: + name: + type: string + password: + properties: + k8sSecret: + properties: + name: + type: string + type: object + ociSecret: + properties: + id: + type: string + type: object + type: object + type: object + required: + - action + type: object + status: + properties: + allConnectionStrings: + items: + properties: + connectionStrings: + items: + properties: + connectionString: + type: string + tnsName: + type: string + type: object + type: array + tlsAuthentication: + type: string + required: + - connectionStrings + type: object + type: array + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + maxLength: 32768 + type: string + observedGeneration: + format: int64 + minimum: 0 + type: integer + reason: + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + enum: + - "True" + - "False" + - Unknown + type: string + type: + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + lifecycleState: + type: string + timeCreated: + type: string + walletExpiringDate: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/config/crd/bases/database.oracle.com_cdbs.yaml b/bundle/manifests/database.oracle.com_cdbs.yaml similarity index 98% rename from config/crd/bases/database.oracle.com_cdbs.yaml rename to bundle/manifests/database.oracle.com_cdbs.yaml index 924946ee..5ad34bbf 100644 --- a/config/crd/bases/database.oracle.com_cdbs.yaml +++ b/bundle/manifests/database.oracle.com_cdbs.yaml @@ -1,9 +1,10 @@ ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: + cert-manager.io/inject-ca-from: oracle-database-operator-system/oracle-database-operator-serving-cert controller-gen.kubebuilder.io/version: v0.16.5 + creationTimestamp: null name: cdbs.database.oracle.com spec: group: database.oracle.com @@ -490,3 +491,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/bundle/manifests/database.oracle.com_dataguardbrokers.yaml b/bundle/manifests/database.oracle.com_dataguardbrokers.yaml new file mode 100644 index 00000000..029fb91e --- /dev/null +++ b/bundle/manifests/database.oracle.com_dataguardbrokers.yaml @@ -0,0 +1,217 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: oracle-database-operator-system/oracle-database-operator-serving-cert + controller-gen.kubebuilder.io/version: v0.16.5 + creationTimestamp: null + name: dataguardbrokers.database.oracle.com +spec: + group: database.oracle.com + names: + kind: DataguardBroker + listKind: DataguardBrokerList + plural: dataguardbrokers + singular: dataguardbroker + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.primaryDatabase + name: Primary + type: string + - jsonPath: .status.standbyDatabases + name: Standbys + type: string + - jsonPath: .spec.protectionMode + name: Protection Mode + type: string + - jsonPath: .status.clusterConnectString + name: Cluster Connect Str + priority: 1 + type: string + - jsonPath: .status.externalConnectString + name: Connect Str + type: string + - jsonPath: .spec.primaryDatabaseRef + name: Primary Database + priority: 1 + type: string + - jsonPath: .status.status + name: Status + type: string + - jsonPath: .status.fastStartFailover + name: FSFO + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + fastStartFailover: + type: boolean + loadBalancer: + type: boolean + nodeSelector: + additionalProperties: + type: string + type: object + primaryDatabaseRef: + type: string + protectionMode: + enum: + - MaxPerformance + - MaxAvailability + type: string + serviceAnnotations: + additionalProperties: + type: string + type: object + setAsPrimaryDatabase: + type: string + standbyDatabaseRefs: + items: + type: string + type: array + required: + - primaryDatabaseRef + - protectionMode + - standbyDatabaseRefs + type: object + status: + properties: + clusterConnectString: + type: string + databasesInDataguardConfig: + additionalProperties: + type: string + type: object + externalConnectString: + type: string + fastStartFailover: + type: string + primaryDatabase: + type: string + primaryDatabaseRef: + type: string + protectionMode: + type: string + standbyDatabases: + type: string + status: + type: string + type: object + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .status.primaryDatabase + name: Primary + type: string + - jsonPath: .status.standbyDatabases + name: Standbys + type: string + - jsonPath: .spec.protectionMode + name: Protection Mode + type: string + - jsonPath: .status.clusterConnectString + name: Cluster Connect Str + priority: 1 + type: string + - jsonPath: .status.externalConnectString + name: Connect Str + type: string + - jsonPath: .spec.primaryDatabaseRef + name: Primary Database + priority: 1 + type: string + - jsonPath: .status.status + name: Status + type: string + - jsonPath: .status.fastStartFailover + name: FSFO + type: string + name: v4 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + fastStartFailover: + type: boolean + loadBalancer: + type: boolean + nodeSelector: + additionalProperties: + type: string + type: object + primaryDatabaseRef: + type: string + protectionMode: + enum: + - MaxPerformance + - MaxAvailability + type: string + serviceAnnotations: + additionalProperties: + type: string + type: object + setAsPrimaryDatabase: + type: string + standbyDatabaseRefs: + items: + type: string + type: array + required: + - primaryDatabaseRef + - protectionMode + - standbyDatabaseRefs + type: object + status: + properties: + clusterConnectString: + type: string + databasesInDataguardConfig: + additionalProperties: + type: string + type: object + externalConnectString: + type: string + fastStartFailover: + type: string + primaryDatabase: + type: string + primaryDatabaseRef: + type: string + protectionMode: + type: string + standbyDatabases: + type: string + status: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/config/database.oracle.com_DbcsSystem.yaml b/bundle/manifests/database.oracle.com_dbcssystems.yaml similarity index 50% rename from config/database.oracle.com_DbcsSystem.yaml rename to bundle/manifests/database.oracle.com_dbcssystems.yaml index c342c363..b925812d 100644 --- a/config/database.oracle.com_DbcsSystem.yaml +++ b/bundle/manifests/database.oracle.com_dbcssystems.yaml @@ -1,48 +1,37 @@ - ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: + cert-manager.io/inject-ca-from: oracle-database-operator-system/oracle-database-operator-serving-cert controller-gen.kubebuilder.io/version: v0.16.5 creationTimestamp: null - name: DbcsSystem.database.oracle.com + name: dbcssystems.database.oracle.com spec: group: database.oracle.com names: kind: DbcsSystem listKind: DbcsSystemList - plural: DbcsSystem + plural: dbcssystems singular: dbcssystem scope: Namespaced versions: - - name: v4 + - name: v1alpha1 schema: openAPIV3Schema: - description: DbcsSystem is the Schema for the dbcssystems API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object spec: - description: DbcsSystemSpec defines the desired state of DbcsSystem properties: databaseId: type: string dbBackupId: type: string dbClone: - description: DbCloneConfig defines the configuration for the database - clone properties: dbAdminPaswordSecret: type: string @@ -98,7 +87,6 @@ spec: dbAdminPaswordSecret: type: string dbBackupConfig: - description: DB Backup Config Network Struct properties: autoBackupEnabled: type: boolean @@ -205,44 +193,22 @@ spec: type: string pdbConfigs: items: - description: PDBConfig defines details of PDB struct for DBCS systems properties: freeformTags: additionalProperties: type: string - description: '// Free-form tags for this resource. Each tag - is a simple key-value pair with no predefined name, type, - or namespace. // For more information, see Resource Tags (https://docs.cloud.oracle.com/Content/General/Concepts/resourcetags.htm). - // Example: `{"Department": "Finance"}`' type: object isDelete: - description: To specify whether to delete the PDB type: boolean pdbAdminPassword: - description: // A strong password for PDB Admin. The password - must be at least nine characters and contain at least two - uppercase, two lowercase, two numbers, and two special characters. - The special characters must be _, \#, or -. type: string pdbName: - description: The name for the pluggable database (PDB). The - name is unique in the context of a Database. The name must - begin with an alphabetic character and can contain a maximum - of thirty alphanumeric characters. Special characters are - not permitted. The pluggable database name should not be same - as the container database name. type: string pluggableDatabaseId: - description: The OCID of the PDB for deletion purposes. type: string shouldPdbAdminAccountBeLocked: - description: // The locked mode of the pluggable database admin - account. If false, the user needs to provide the PDB Admin - Password to connect to it. // If true, the pluggable database - will be locked and user cannot login to it. type: boolean tdeWalletPassword: - description: // The existing TDE wallet password of the CDB. type: string required: - freeformTags @@ -258,7 +224,6 @@ spec: - ociConfigMap type: object status: - description: DbcsSystemStatus defines the observed state of DbcsSystem properties: availabilityDomain: type: string @@ -269,7 +234,6 @@ spec: dataStorageSizeInGBs: type: integer dbCloneStatus: - description: DbCloneStatus defines the observed state of DbClone properties: dbAdminPaswordSecret: type: string @@ -301,7 +265,6 @@ spec: type: string dbInfo: items: - description: DbcsSystemStatus defines the observed state of DbcsSystem properties: dbHomeId: type: string @@ -422,6 +385,375 @@ spec: type: object type: object served: true + storage: false + subresources: + status: {} + - name: v4 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + databaseId: + type: string + dbBackupId: + type: string + dbClone: + properties: + dbAdminPasswordSecret: + type: string + dbDbUniqueName: + type: string + dbName: + type: string + displayName: + type: string + domain: + type: string + hostName: + type: string + initialDataStorageSizeInGB: + type: integer + kmsKeyId: + type: string + kmsKeyVersionId: + type: string + licenseModel: + type: string + privateIp: + type: string + sidPrefix: + type: string + sshPublicKeys: + items: + type: string + type: array + subnetId: + type: string + tdeWalletPasswordSecret: + type: string + required: + - dbDbUniqueName + - dbName + - displayName + - hostName + - subnetId + type: object + dbSystem: + properties: + availabilityDomain: + type: string + backupSubnetId: + type: string + clusterName: + type: string + compartmentId: + type: string + cpuCoreCount: + type: integer + dbAdminPasswordSecret: + type: string + dbBackupConfig: + properties: + autoBackupEnabled: + type: boolean + autoBackupWindow: + type: string + backupDestinationDetails: + type: string + recoveryWindowsInDays: + type: integer + type: object + dbDomain: + type: string + dbEdition: + type: string + dbName: + type: string + dbUniqueName: + type: string + dbVersion: + type: string + dbWorkload: + type: string + diskRedundancy: + type: string + displayName: + type: string + domain: + type: string + faultDomains: + items: + type: string + type: array + hostName: + type: string + initialDataStorageSizeInGB: + type: integer + kmsConfig: + properties: + compartmentId: + type: string + encryptionAlgo: + type: string + keyName: + type: string + vaultName: + type: string + vaultType: + type: string + type: object + licenseModel: + type: string + nodeCount: + type: integer + pdbName: + type: string + privateIp: + type: string + shape: + type: string + sshPublicKeys: + items: + type: string + type: array + storageManagement: + type: string + subnetId: + type: string + tags: + additionalProperties: + type: string + type: object + tdeWalletPasswordSecret: + type: string + timeZone: + type: string + required: + - availabilityDomain + - compartmentId + - dbAdminPasswordSecret + - hostName + - shape + - subnetId + type: object + hardLink: + type: boolean + id: + type: string + kmsConfig: + properties: + compartmentId: + type: string + encryptionAlgo: + type: string + keyName: + type: string + vaultName: + type: string + vaultType: + type: string + type: object + ociConfigMap: + type: string + ociSecret: + type: string + pdbConfigs: + items: + properties: + freeformTags: + additionalProperties: + type: string + type: object + isDelete: + type: boolean + pdbAdminPassword: + type: string + pdbName: + type: string + pluggableDatabaseId: + type: string + shouldPdbAdminAccountBeLocked: + type: boolean + tdeWalletPassword: + type: string + required: + - freeformTags + - pdbAdminPassword + - pdbName + - shouldPdbAdminAccountBeLocked + - tdeWalletPassword + type: object + type: array + setupDBCloning: + type: boolean + required: + - ociConfigMap + type: object + status: + properties: + availabilityDomain: + type: string + cpuCoreCount: + type: integer + dataStoragePercentage: + type: integer + dataStorageSizeInGBs: + type: integer + dbCloneStatus: + properties: + dbAdminPaswordSecret: + type: string + dbDbUniqueName: + type: string + dbName: + type: string + displayName: + type: string + domain: + type: string + hostName: + type: string + id: + type: string + licenseModel: + type: string + sshPublicKeys: + items: + type: string + type: array + subnetId: + type: string + required: + - dbDbUniqueName + - hostName + type: object + dbEdition: + type: string + dbInfo: + items: + properties: + dbHomeId: + type: string + dbName: + type: string + dbUniqueName: + type: string + dbWorkload: + type: string + id: + type: string + type: object + type: array + displayName: + type: string + id: + type: string + kmsDetailsStatus: + properties: + compartmentId: + type: string + encryptionAlgo: + type: string + keyId: + type: string + keyName: + type: string + managementEndpoint: + type: string + vaultId: + type: string + vaultName: + type: string + vaultType: + type: string + type: object + licenseModel: + type: string + network: + properties: + clientSubnet: + type: string + domainName: + type: string + hostName: + type: string + listenerPort: + type: integer + networkSG: + type: string + scanDnsName: + type: string + vcnName: + type: string + type: object + nodeCount: + type: integer + pdbDetailsStatus: + items: + properties: + pdbConfigStatus: + items: + properties: + freeformTags: + additionalProperties: + type: string + type: object + pdbName: + type: string + pdbState: + type: string + pluggableDatabaseId: + type: string + shouldPdbAdminAccountBeLocked: + type: boolean + type: object + type: array + type: object + type: array + recoStorageSizeInGB: + type: integer + shape: + type: string + state: + type: string + storageManagement: + type: string + subnetId: + type: string + timeZone: + type: string + workRequests: + items: + properties: + operationId: + type: string + operationType: + type: string + percentComplete: + type: string + timeAccepted: + type: string + timeFinished: + type: string + timeStarted: + type: string + required: + - operationId + - operationType + type: object + type: array + required: + - state + type: object + type: object + served: true storage: true subresources: status: {} @@ -429,5 +761,5 @@ status: acceptedNames: kind: "" plural: "" - conditions: [] - storedVersions: [] + conditions: null + storedVersions: null diff --git a/config/database.oracle.com_cdbs.yaml b/bundle/manifests/database.oracle.com_lrests.yaml similarity index 63% rename from config/database.oracle.com_cdbs.yaml rename to bundle/manifests/database.oracle.com_lrests.yaml index 6b1c350c..fb2d9300 100644 --- a/config/database.oracle.com_cdbs.yaml +++ b/bundle/manifests/database.oracle.com_lrests.yaml @@ -1,25 +1,24 @@ - ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.1 + cert-manager.io/inject-ca-from: oracle-database-operator-system/oracle-database-operator-serving-cert + controller-gen.kubebuilder.io/version: v0.16.5 creationTimestamp: null - name: cdbs.database.oracle.com + name: lrests.database.oracle.com spec: group: database.oracle.com names: - kind: CDB - listKind: CDBList - plural: cdbs - singular: cdb + kind: LREST + listKind: LRESTList + plural: lrests + singular: lrest scope: Namespaced versions: - additionalPrinterColumns: - - description: Name of the CDB + - description: Name of the LREST jsonPath: .spec.cdbName - name: CDB Name + name: CDB NAME type: string - description: ' Name of the DB Server' jsonPath: .spec.dbServer @@ -29,47 +28,37 @@ spec: jsonPath: .spec.dbPort name: DB Port type: integer - - description: ' string of the tnsalias' - jsonPath: .spec.dbTnsurl - name: TNS STRING - type: string - description: Replicas jsonPath: .spec.replicas name: Replicas type: integer - - description: Status of the CDB Resource + - description: Status of the LREST Resource jsonPath: .status.phase name: Status type: string - - description: Error message, if any + - description: Error message if any jsonPath: .status.msg name: Message type: string - name: v1alpha1 + - description: string of the tnsalias + jsonPath: .spec.dbTnsurl + name: TNS STRING + type: string + name: v4 schema: openAPIV3Schema: - description: CDB is the Schema for the cdbs API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object spec: - description: CDBSpec defines the desired state of CDB properties: cdbAdminPwd: - description: Password for the CDB Administrator to manage PDB lifecycle properties: secret: - description: CDBSecret defines the secretName properties: key: type: string @@ -83,11 +72,8 @@ spec: - secret type: object cdbAdminUser: - description: User in the root container with sysdba priviledges to - manage PDB lifecycle properties: secret: - description: CDBSecret defines the secretName properties: key: type: string @@ -101,12 +87,40 @@ spec: - secret type: object cdbName: - description: Name of the CDB type: string + cdbPrvKey: + properties: + secret: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - secret + type: object + cdbPubKey: + properties: + secret: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - secret + type: object cdbTlsCrt: properties: secret: - description: CDBSecret defines the secretName properties: key: type: string @@ -122,7 +136,6 @@ spec: cdbTlsKey: properties: secret: - description: CDBSecret defines the secretName properties: key: type: string @@ -136,40 +149,27 @@ spec: - secret type: object dbPort: - description: DB server port type: integer dbServer: - description: Name of the DB server type: string dbTnsurl: type: string - nodeSelector: - additionalProperties: - type: string - description: Node Selector for running the Pod - type: object - ordsImage: - description: ORDS Image Name + deletePdbCascade: + type: boolean + lrestImage: type: string - ordsImagePullPolicy: - description: ORDS Image Pull Policy + lrestImagePullPolicy: enum: - Always - Never type: string - ordsImagePullSecret: - description: The name of the image pull secret in case of a private - docker repository. + lrestImagePullSecret: type: string - ordsPort: - description: ORDS server port. For now, keep it as 8888. TO BE USED - IN FUTURE RELEASE. + lrestPort: type: integer - ordsPwd: - description: Password for user ORDS_PUBLIC_USER + lrestPwd: properties: secret: - description: CDBSecret defines the secretName properties: key: type: string @@ -182,17 +182,17 @@ spec: required: - secret type: object + nodeSelector: + additionalProperties: + type: string + type: object replicas: - description: Number of ORDS Containers to create type: integer serviceName: - description: Name of the CDB Service type: string sysAdminPwd: - description: Password for the CDB System Administrator properties: secret: - description: CDBSecret defines the secretName properties: key: type: string @@ -206,10 +206,8 @@ spec: - secret type: object webServerPwd: - description: Password for the Web Server User properties: secret: - description: CDBSecret defines the secretName properties: key: type: string @@ -223,11 +221,8 @@ spec: - secret type: object webServerUser: - description: Web Server User with SQL Administrator role to allow - us to authenticate to the PDB Lifecycle Management REST endpoints properties: secret: - description: CDBSecret defines the secretName properties: key: type: string @@ -242,16 +237,12 @@ spec: type: object type: object status: - description: CDBStatus defines the observed state of CDB properties: msg: - description: Message type: string phase: - description: Phase of the CDB Resource type: string status: - description: CDB Resource Status type: boolean required: - phase @@ -266,5 +257,5 @@ status: acceptedNames: kind: "" plural: "" - conditions: [] - storedVersions: [] + conditions: null + storedVersions: null diff --git a/config/database.oracle.com_pdbs.yaml b/bundle/manifests/database.oracle.com_lrpdbs.yaml similarity index 55% rename from config/database.oracle.com_pdbs.yaml rename to bundle/manifests/database.oracle.com_lrpdbs.yaml index 85af8c1b..c0ccee9d 100644 --- a/config/database.oracle.com_pdbs.yaml +++ b/bundle/manifests/database.oracle.com_lrpdbs.yaml @@ -1,26 +1,21 @@ - ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.1 + cert-manager.io/inject-ca-from: oracle-database-operator-system/oracle-database-operator-serving-cert + controller-gen.kubebuilder.io/version: v0.16.5 creationTimestamp: null - name: pdbs.database.oracle.com + name: lrpdbs.database.oracle.com spec: group: database.oracle.com names: - kind: PDB - listKind: PDBList - plural: pdbs - singular: pdb + kind: LRPDB + listKind: LRPDBList + plural: lrpdbs + singular: lrpdb scope: Namespaced versions: - additionalPrinterColumns: - - description: The connect string to be used - jsonPath: .status.connString - name: Connect_String - type: string - description: Name of the CDB jsonPath: .spec.cdbName name: CDB Name @@ -37,7 +32,7 @@ spec: jsonPath: .status.totalSize name: PDB Size type: string - - description: Status of the PDB Resource + - description: Status of the LRPDB Resource jsonPath: .status.phase name: Status type: string @@ -45,29 +40,27 @@ spec: jsonPath: .status.msg name: Message type: string - name: v1alpha1 + - description: last sqlcode + jsonPath: .status.sqlCode + name: last sqlcode + type: integer + - description: The connect string to be used + jsonPath: .status.connString + name: Connect_String + type: string + name: v4 schema: openAPIV3Schema: - description: PDB is the Schema for the pdbs API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object spec: - description: PDBSpec defines the desired state of PDB properties: action: - description: 'Action to be taken: Create/Clone/Plug/Unplug/Delete/Modify/Status/Map. - Map is used to map a Databse PDB to a Kubernetes PDB CR.' enum: - Create - Clone @@ -77,13 +70,12 @@ spec: - Modify - Status - Map + - Alter + - Noaction type: string adminName: - description: The administrator username for the new PDB. This property - is required when the Action property is Create. properties: secret: - description: PDBSecret defines the secretName properties: key: type: string @@ -97,11 +89,8 @@ spec: - secret type: object adminPwd: - description: The administrator password for the new PDB. This property - is required when the Action property is Create. properties: secret: - description: PDBSecret defines the secretName properties: key: type: string @@ -114,70 +103,85 @@ spec: required: - secret type: object + adminpdbPass: + properties: + secret: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - secret + type: object + adminpdbUser: + properties: + secret: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - secret + type: object + alterSystem: + type: string + alterSystemParameter: + type: string + alterSystemValue: + type: string asClone: - description: Indicate if 'AS CLONE' option should be used in the command - to plug in a PDB. This property is applicable when the Action property - is PLUG but not required. type: boolean - assertivePdbDeletion: - description: turn on the assertive approach to delete pdb resource - kubectl delete pdb ..... automatically triggers the pluggable database - deletion + assertiveLrpdbDeletion: type: boolean cdbName: - description: Name of the CDB type: string cdbNamespace: - description: CDB Namespace type: string + cdbPrvKey: + properties: + secret: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - secret + type: object cdbResName: - description: Name of the CDB Custom Resource that runs the ORDS container type: string copyAction: - description: To copy files or not while cloning a PDB enum: - COPY - NOCOPY - MOVE type: string dropAction: - description: Specify if datafiles should be removed or not. The value - can be INCLUDING or KEEP (default). enum: - INCLUDING - KEEP type: string fileNameConversions: - description: Relevant for Create and Plug operations. As defined in - the Oracle Multitenant Database documentation. Values can be a - filename convert pattern or NONE. type: string getScript: - description: Whether you need the script only or execute the script type: boolean - modifyOption: - description: Extra options for opening and closing a PDB - enum: - - IMMEDIATE - - NORMAL - - READ ONLY - - READ WRITE - - RESTRICTED - type: string - pdbName: - description: The name of the new PDB. Relevant for both Create and - Plug Actions. - type: string - pdbState: - description: The target state of the PDB - enum: - - OPEN - - CLOSE - type: string - pdbTlsCat: + lrpdbTlsCat: properties: secret: - description: PDBSecret defines the secretName properties: key: type: string @@ -190,10 +194,9 @@ spec: required: - secret type: object - pdbTlsCrt: + lrpdbTlsCrt: properties: secret: - description: PDBSecret defines the secretName properties: key: type: string @@ -206,10 +209,9 @@ spec: required: - secret type: object - pdbTlsKey: + lrpdbTlsKey: properties: secret: - description: PDBSecret defines the secretName properties: key: type: string @@ -222,36 +224,43 @@ spec: required: - secret type: object + modifyOption: + enum: + - IMMEDIATE + - NORMAL + - READ ONLY + - READ WRITE + - RESTRICTED + type: string + parameterScope: + type: string + pdbName: + type: string + pdbState: + enum: + - OPEN + - CLOSE + - ALTER + type: string + pdbconfigmap: + type: string reuseTempFile: - description: Whether to reuse temp file type: boolean sourceFileNameConversions: - description: This property is required when the Action property is - Plug. As defined in the Oracle Multitenant Database documentation. - Values can be a source filename convert pattern or NONE. type: string sparseClonePath: - description: A Path specified for sparse clone snapshot copy. (Optional) type: string srcPdbName: - description: Name of the Source PDB from which to clone type: string tdeExport: - description: TDE export for unplug operations type: boolean tdeImport: - description: TDE import for plug operations type: boolean tdeKeystorePath: - description: TDE keystore path is required if the tdeImport or tdeExport - flag is set to true. Can be used in plug or unplug operations. type: string tdePassword: - description: TDE password if the tdeImport or tdeExport flag is set - to true. Can be used in create, plug or unplug operations properties: secret: - description: PDBSecret defines the secretName properties: key: type: string @@ -265,11 +274,8 @@ spec: - secret type: object tdeSecret: - description: TDE secret is required if the tdeImport or tdeExport - flag is set to true. Can be used in plug or unplug operations. properties: secret: - description: PDBSecret defines the secretName properties: key: type: string @@ -283,26 +289,14 @@ spec: - secret type: object tempSize: - description: Relevant for Create and Clone operations. Total size - for temporary tablespace as defined in the Oracle Multitenant Database - documentation. See size_clause description in Database SQL Language - Reference documentation. type: string totalSize: - description: Relevant for create and plug operations. Total size as - defined in the Oracle Multitenant Database documentation. See size_clause - description in Database SQL Language Reference documentation. type: string unlimitedStorage: - description: Relevant for Create and Plug operations. True for unlimited - storage. Even when set to true, totalSize and tempSize MUST be specified - in the request if Action is Create. type: boolean webServerPwd: - description: Password for the Web ServerPDB User properties: secret: - description: PDBSecret defines the secretName properties: key: type: string @@ -316,11 +310,8 @@ spec: - secret type: object webServerUser: - description: Web Server User with SQL Administrator role to allow - us to authenticate to the PDB Lifecycle Management REST endpoints properties: secret: - description: PDBSecret defines the secretName properties: key: type: string @@ -334,40 +325,42 @@ spec: - secret type: object xmlFileName: - description: XML metadata filename to be used for Plug or Unplug operations type: string required: - action + - alterSystemParameter + - alterSystemValue + - webServerPwd type: object status: - description: PDBStatus defines the observed state of PDB properties: action: - description: Last Completed Action + type: string + alterSystem: + type: string + bitstat: + type: integer + bitstatstr: type: string connString: - description: PDB Connect String type: string modifyOption: - description: Modify Option of the PDB type: string msg: - description: Message type: string openMode: - description: Open mode of the PDB type: string phase: - description: Phase of the PDB Resource type: string + sqlCode: + type: integer status: - description: PDB Resource Status type: boolean totalSize: - description: Total size of the PDB type: string required: - phase + - sqlCode - status type: object type: object @@ -379,5 +372,5 @@ status: acceptedNames: kind: "" plural: "" - conditions: [] - storedVersions: [] + conditions: null + storedVersions: null diff --git a/config/database.oracle.com_oraclerestdataservices.yaml b/bundle/manifests/database.oracle.com_oraclerestdataservices.yaml similarity index 50% rename from config/database.oracle.com_oraclerestdataservices.yaml rename to bundle/manifests/database.oracle.com_oraclerestdataservices.yaml index 121383fd..acc46597 100644 --- a/config/database.oracle.com_oraclerestdataservices.yaml +++ b/bundle/manifests/database.oracle.com_oraclerestdataservices.yaml @@ -1,10 +1,9 @@ - ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.6.1 + cert-manager.io/inject-ca-from: oracle-database-operator-system/oracle-database-operator-serving-cert + controller-gen.kubebuilder.io/version: v0.16.5 creationTimestamp: null name: oraclerestdataservices.database.oracle.com spec: @@ -32,30 +31,22 @@ spec: - jsonPath: .status.apexUrl name: Apex URL type: string + - jsonPath: .status.mongoDbApiAccessUrl + name: MongoDbApi Access URL + type: string name: v1alpha1 schema: openAPIV3Schema: - description: OracleRestDataService is the Schema for the oraclerestdataservices - API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object spec: - description: OracleRestDataServiceSpec defines the desired state of OracleRestDataService properties: adminPassword: - description: OracleRestDataServicePassword defines the secret containing - Password mapped to secretKey properties: keepSecret: type: boolean @@ -67,9 +58,30 @@ spec: required: - secretName type: object - apexPassword: - description: OracleRestDataServicePassword defines the secret containing - Password mapped to secretKey + databaseRef: + type: string + image: + properties: + pullFrom: + type: string + pullSecrets: + type: string + version: + type: string + required: + - pullFrom + type: object + loadBalancer: + type: boolean + mongoDbApi: + type: boolean + nodeSelector: + additionalProperties: + type: string + type: object + oracleService: + type: string + ordsPassword: properties: keepSecret: type: boolean @@ -81,11 +93,71 @@ spec: required: - secretName type: object + ordsUser: + type: string + persistence: + properties: + accessMode: + enum: + - ReadWriteOnce + - ReadWriteMany + type: string + setWritePermissions: + type: boolean + size: + type: string + storageClass: + type: string + volumeName: + type: string + type: object + readinessCheckPeriod: + type: integer + replicas: + minimum: 1 + type: integer + restEnableSchemas: + items: + properties: + enable: + type: boolean + pdbName: + type: string + schemaName: + type: string + urlMapping: + type: string + required: + - enable + - schemaName + type: object + type: array + serviceAccountName: + type: string + serviceAnnotations: + additionalProperties: + type: string + type: object + required: + - adminPassword + - databaseRef + - ordsPassword + type: object + status: + properties: + apexConfigured: + type: boolean + apexUrl: + type: string + commonUsersCreated: + type: boolean + databaseActionsUrl: + type: string + databaseApiUrl: + type: string databaseRef: type: string image: - description: OracleRestDataServiceImage defines the Image source and - pullSecrets for POD properties: pullFrom: type: string @@ -97,6 +169,84 @@ spec: - pullFrom type: object loadBalancer: + type: string + mongoDbApi: + type: boolean + mongoDbApiAccessUrl: + type: string + ordsInstalled: + type: boolean + replicas: + type: integer + serviceIP: + type: string + status: + type: string + type: object + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .status.status + name: Status + type: string + - jsonPath: .spec.databaseRef + name: Database + type: string + - jsonPath: .status.databaseApiUrl + name: Database API URL + type: string + - jsonPath: .status.databaseActionsUrl + name: Database Actions URL + type: string + - jsonPath: .status.apexUrl + name: Apex URL + type: string + - jsonPath: .status.mongoDbApiAccessUrl + name: MongoDbApi Access URL + type: string + name: v4 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + adminPassword: + properties: + keepSecret: + type: boolean + secretKey: + default: oracle_pwd + type: string + secretName: + type: string + required: + - secretName + type: object + databaseRef: + type: string + image: + properties: + pullFrom: + type: string + pullSecrets: + type: string + version: + type: string + required: + - pullFrom + type: object + loadBalancer: + type: boolean + mongoDbApi: type: boolean nodeSelector: additionalProperties: @@ -105,8 +255,6 @@ spec: oracleService: type: string ordsPassword: - description: OracleRestDataServicePassword defines the secret containing - Password mapped to secretKey properties: keepSecret: type: boolean @@ -121,14 +269,14 @@ spec: ordsUser: type: string persistence: - description: OracleRestDataServicePersistence defines the storage - releated params properties: accessMode: enum: - ReadWriteOnce - ReadWriteMany type: string + setWritePermissions: + type: boolean size: type: string storageClass: @@ -136,13 +284,13 @@ spec: volumeName: type: string type: object + readinessCheckPeriod: + type: integer replicas: minimum: 1 type: integer restEnableSchemas: items: - description: OracleRestDataServicePDBSchemas defines the PDB Schemas - to be ORDS Enabled properties: enable: type: boolean @@ -169,8 +317,6 @@ spec: - ordsPassword type: object status: - description: OracleRestDataServiceStatus defines the observed state of - OracleRestDataService properties: apexConfigured: type: boolean @@ -185,8 +331,6 @@ spec: databaseRef: type: string image: - description: OracleRestDataServiceImage defines the Image source and - pullSecrets for POD properties: pullFrom: type: string @@ -199,6 +343,10 @@ spec: type: object loadBalancer: type: string + mongoDbApi: + type: boolean + mongoDbApiAccessUrl: + type: string ordsInstalled: type: boolean replicas: @@ -206,9 +354,6 @@ spec: serviceIP: type: string status: - description: 'INSERT ADDITIONAL STATUS FIELD - define observed state - of cluster Important: Run "make" to regenerate code after modifying - this file' type: string type: object type: object @@ -220,5 +365,5 @@ status: acceptedNames: kind: "" plural: "" - conditions: [] - storedVersions: [] + conditions: null + storedVersions: null diff --git a/bundle/manifests/database.oracle.com_ordssrvs.yaml b/bundle/manifests/database.oracle.com_ordssrvs.yaml new file mode 100644 index 00000000..b97ace72 --- /dev/null +++ b/bundle/manifests/database.oracle.com_ordssrvs.yaml @@ -0,0 +1,495 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: oracle-database-operator-system/oracle-database-operator-serving-cert + controller-gen.kubebuilder.io/version: v0.16.5 + creationTimestamp: null + name: ordssrvs.database.oracle.com +spec: + group: database.oracle.com + names: + kind: OrdsSrvs + listKind: OrdsSrvsList + plural: ordssrvs + singular: ordssrvs + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.status + name: status + type: string + - jsonPath: .status.workloadType + name: workloadType + type: string + - jsonPath: .status.ordsVersion + name: ordsVersion + type: string + - jsonPath: .status.httpPort + name: httpPort + type: integer + - jsonPath: .status.httpsPort + name: httpsPort + type: integer + - jsonPath: .status.mongoPort + name: MongoPort + type: integer + - jsonPath: .status.restartRequired + name: restartRequired + type: boolean + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + - jsonPath: .status.ordsInstalled + name: OrdsInstalled + type: boolean + name: v4 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + encPrivKey: + properties: + passwordKey: + default: password + type: string + secretName: + type: string + required: + - secretName + type: object + forceRestart: + type: boolean + globalSettings: + properties: + cache.metadata.enabled: + type: boolean + cache.metadata.graphql.expireAfterAccess: + format: int64 + type: integer + cache.metadata.graphql.expireAfterWrite: + format: int64 + type: integer + cache.metadata.jwks.enabled: + type: boolean + cache.metadata.jwks.expireAfterAccess: + format: int64 + type: integer + cache.metadata.jwks.expireAfterWrite: + format: int64 + type: integer + cache.metadata.jwks.initialCapacity: + format: int32 + type: integer + cache.metadata.jwks.maximumSize: + format: int32 + type: integer + cache.metadata.timeout: + format: int64 + type: integer + certSecret: + properties: + cert: + type: string + key: + type: string + secretName: + type: string + required: + - cert + - key + - secretName + type: object + database.api.enabled: + type: boolean + database.api.management.services.disabled: + type: boolean + db.invalidPoolTimeout: + format: int64 + type: integer + debug.printDebugToScreen: + type: boolean + enable.mongo.access.log: + default: false + type: boolean + enable.standalone.access.log: + default: false + type: boolean + error.responseFormat: + type: string + feature.grahpql.max.nesting.depth: + format: int32 + type: integer + icap.port: + format: int32 + type: integer + icap.secure.port: + format: int32 + type: integer + icap.server: + type: string + log.procedure: + type: boolean + mongo.enabled: + type: boolean + mongo.idle.timeout: + format: int64 + type: integer + mongo.op.timeout: + format: int64 + type: integer + mongo.port: + default: 27017 + format: int32 + type: integer + request.traceHeaderName: + type: string + security.credentials.attempts: + format: int32 + type: integer + security.credentials.lock.time: + format: int64 + type: integer + security.disableDefaultExclusionList: + type: boolean + security.exclusionList: + type: string + security.externalSessionTrustedOrigins: + type: string + security.forceHTTPS: + type: boolean + security.httpsHeaderCheck: + type: string + security.inclusionList: + type: string + security.maxEntries: + format: int32 + type: integer + security.verifySSL: + type: boolean + standalone.context.path: + default: /ords + type: string + standalone.http.port: + default: 8080 + format: int32 + type: integer + standalone.https.host: + type: string + standalone.https.port: + default: 8443 + format: int32 + type: integer + standalone.stop.timeout: + format: int64 + type: integer + type: object + image: + type: string + imagePullPolicy: + default: IfNotPresent + enum: + - IfNotPresent + - Always + - Never + type: string + imagePullSecrets: + type: string + poolSettings: + items: + properties: + apex.security.administrator.roles: + type: string + apex.security.user.roles: + type: string + autoUpgradeAPEX: + default: false + type: boolean + autoUpgradeORDS: + default: false + type: boolean + db.adminUser: + type: string + db.adminUser.secret: + properties: + passwordKey: + default: password + type: string + secretName: + type: string + required: + - secretName + type: object + db.cdb.adminUser: + type: string + db.cdb.adminUser.secret: + properties: + passwordKey: + default: password + type: string + secretName: + type: string + required: + - secretName + type: object + db.connectionType: + enum: + - basic + - tns + - customurl + type: string + db.credentialsSource: + enum: + - pool + - request + type: string + db.customURL: + type: string + db.hostname: + type: string + db.poolDestroyTimeout: + format: int64 + type: integer + db.port: + format: int32 + type: integer + db.secret: + properties: + passwordKey: + default: password + type: string + secretName: + type: string + required: + - secretName + type: object + db.servicename: + type: string + db.sid: + type: string + db.tnsAliasName: + type: string + db.username: + default: ORDS_PUBLIC_USER + type: string + db.wallet.zip.service: + type: string + dbWalletSecret: + properties: + secretName: + type: string + walletName: + type: string + required: + - secretName + - walletName + type: object + debug.trackResources: + type: boolean + feature.openservicebroker.exclude: + type: boolean + feature.sdw: + type: boolean + http.cookie.filter: + type: string + jdbc.DriverType: + enum: + - thin + - oci8 + type: string + jdbc.InactivityTimeout: + format: int32 + type: integer + jdbc.InitialLimit: + format: int32 + type: integer + jdbc.MaxConnectionReuseCount: + format: int32 + type: integer + jdbc.MaxConnectionReuseTime: + format: int32 + type: integer + jdbc.MaxLimit: + format: int32 + type: integer + jdbc.MaxStatementsLimit: + format: int32 + type: integer + jdbc.MinLimit: + format: int32 + type: integer + jdbc.SecondsToTrustIdleConnection: + format: int32 + type: integer + jdbc.auth.admin.role: + type: string + jdbc.auth.enabled: + type: boolean + jdbc.cleanup.mode: + type: string + jdbc.statementTimeout: + format: int32 + type: integer + misc.defaultPage: + type: string + misc.pagination.maxRows: + format: int32 + type: integer + owa.trace.sql: + type: boolean + plsql.gateway.mode: + enum: + - disabled + - direct + - proxied + type: string + poolName: + type: string + procedure.preProcess: + type: string + procedure.rest.preHook: + type: string + procedurePostProcess: + type: string + restEnabledSql.active: + type: boolean + security.jwks.connection.timeout: + format: int64 + type: integer + security.jwks.read.timeout: + format: int64 + type: integer + security.jwks.refresh.interval: + format: int64 + type: integer + security.jwks.size: + format: int32 + type: integer + security.jwt.allowed.age: + format: int64 + type: integer + security.jwt.allowed.skew: + format: int64 + type: integer + security.jwt.profile.enabled: + type: boolean + security.requestAuthenticationFunction: + type: string + security.requestValidationFunction: + default: ords_util.authorize_plsql_gateway + type: string + security.validationFunctionType: + enum: + - plsql + - javascript + type: string + soda.defaultLimit: + type: string + soda.maxLimit: + type: string + tnsAdminSecret: + properties: + secretName: + type: string + required: + - secretName + type: object + required: + - db.secret + - poolName + type: object + type: array + replicas: + default: 1 + format: int32 + minimum: 1 + type: integer + workloadType: + default: Deployment + enum: + - Deployment + - StatefulSet + - DaemonSet + type: string + required: + - globalSettings + - image + type: object + status: + properties: + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + maxLength: 32768 + type: string + observedGeneration: + format: int64 + minimum: 0 + type: integer + reason: + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + enum: + - "True" + - "False" + - Unknown + type: string + type: + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + httpPort: + format: int32 + type: integer + httpsPort: + format: int32 + type: integer + mongoPort: + format: int32 + type: integer + ordsInstalled: + type: boolean + ordsVersion: + type: string + restartRequired: + type: boolean + status: + type: string + workloadType: + type: string + required: + - restartRequired + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/config/crd/bases/database.oracle.com_pdbs.yaml b/bundle/manifests/database.oracle.com_pdbs.yaml similarity index 98% rename from config/crd/bases/database.oracle.com_pdbs.yaml rename to bundle/manifests/database.oracle.com_pdbs.yaml index b2f37ac9..b1e1032f 100644 --- a/config/crd/bases/database.oracle.com_pdbs.yaml +++ b/bundle/manifests/database.oracle.com_pdbs.yaml @@ -1,9 +1,10 @@ ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: + cert-manager.io/inject-ca-from: oracle-database-operator-system/oracle-database-operator-serving-cert controller-gen.kubebuilder.io/version: v0.16.5 + creationTimestamp: null name: pdbs.database.oracle.com spec: group: database.oracle.com @@ -632,3 +633,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/bundle/manifests/database.oracle.com_shardingdatabases.yaml b/bundle/manifests/database.oracle.com_shardingdatabases.yaml new file mode 100644 index 00000000..f8cd38a8 --- /dev/null +++ b/bundle/manifests/database.oracle.com_shardingdatabases.yaml @@ -0,0 +1,1115 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: oracle-database-operator-system/oracle-database-operator-serving-cert + controller-gen.kubebuilder.io/version: v0.16.5 + creationTimestamp: null + name: shardingdatabases.database.oracle.com +spec: + group: database.oracle.com + names: + kind: ShardingDatabase + listKind: ShardingDatabaseList + plural: shardingdatabases + singular: shardingdatabase + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.gsm.state + name: Gsm State + type: string + - jsonPath: .status.gsm.services + name: Services + type: string + - jsonPath: .status.gsm.shards + name: shards + priority: 1 + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + InvitedNodeSubnet: + type: string + catalog: + items: + properties: + envVars: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + imagePullPolicy: + type: string + isDelete: + type: string + label: + type: string + name: + type: string + nodeSelector: + additionalProperties: + type: string + type: object + pvAnnotations: + additionalProperties: + type: string + type: object + pvMatchLabels: + additionalProperties: + type: string + type: object + pvcName: + type: string + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + storageSizeInGb: + format: int32 + type: integer + required: + - name + type: object + type: array + dbEdition: + type: string + dbImage: + type: string + dbImagePullSecret: + type: string + dbSecret: + properties: + encryptionType: + type: string + keyFileMountLocation: + type: string + keyFileName: + type: string + keySecretName: + type: string + name: + type: string + nsConfigMap: + type: string + nsSecret: + type: string + pwdFileMountLocation: + type: string + pwdFileName: + type: string + required: + - name + - pwdFileName + type: object + fssStorageClass: + type: string + gsm: + items: + properties: + directorName: + type: string + envVars: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + imagePullPolicy: + type: string + isDelete: + type: string + label: + type: string + name: + type: string + nodeSelector: + additionalProperties: + type: string + type: object + pvAnnotations: + additionalProperties: + type: string + type: object + pvMatchLabels: + additionalProperties: + type: string + type: object + pvcName: + type: string + region: + type: string + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + storageSizeInGb: + format: int32 + type: integer + required: + - name + type: object + type: array + gsmDevMode: + type: string + gsmImage: + type: string + gsmImagePullSecret: + type: string + gsmService: + items: + properties: + available: + type: string + clbGoal: + type: string + commitOutcome: + type: string + drainTimeout: + type: string + dtp: + type: string + edition: + type: string + failoverDelay: + type: string + failoverMethod: + type: string + failoverPrimary: + type: string + failoverRestore: + type: string + failoverRetry: + type: string + failoverType: + type: string + gdsPool: + type: string + lag: + type: integer + locality: + type: string + name: + type: string + notification: + type: string + pdbName: + type: string + policy: + type: string + preferred: + type: string + prferredAll: + type: string + regionFailover: + type: string + retention: + type: string + role: + type: string + sessionState: + type: string + sqlTransactionProfile: + type: string + stopOption: + type: string + tableFamily: + type: string + tfaPolicy: + type: string + required: + - name + type: object + type: array + gsmShardGroup: + items: + properties: + deployAs: + type: string + name: + type: string + region: + type: string + required: + - name + type: object + type: array + gsmShardSpace: + items: + properties: + chunks: + type: integer + name: + type: string + protectionMode: + type: string + shardGroup: + type: string + required: + - name + type: object + type: array + invitedNodeSubnetFlag: + type: string + isClone: + type: boolean + isDataGuard: + type: boolean + isDebug: + type: boolean + isDeleteOraPvc: + type: boolean + isDownloadScripts: + type: boolean + isExternalSvc: + type: boolean + isTdeWallet: + type: string + liveinessCheckPeriod: + type: integer + portMappings: + items: + properties: + port: + format: int32 + type: integer + protocol: + type: string + targetPort: + format: int32 + type: integer + required: + - port + - protocol + - targetPort + type: object + type: array + readinessCheckPeriod: + type: integer + replicationType: + type: string + scriptsLocation: + type: string + shard: + items: + properties: + deployAs: + type: string + envVars: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + imagePullPolicy: + type: string + isDelete: + enum: + - enable + - disable + - failed + - force + type: string + label: + type: string + name: + type: string + nodeSelector: + additionalProperties: + type: string + type: object + pvAnnotations: + additionalProperties: + type: string + type: object + pvMatchLabels: + additionalProperties: + type: string + type: object + pvcName: + type: string + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + shardGroup: + type: string + shardRegion: + type: string + shardSpace: + type: string + storageSizeInGb: + format: int32 + type: integer + required: + - name + type: object + type: array + shardBuddyRegion: + type: string + shardConfigName: + type: string + shardRegion: + items: + type: string + type: array + shardingType: + type: string + stagePvcName: + type: string + storageClass: + type: string + tdeWalletPvc: + type: string + tdeWalletPvcMountLocation: + type: string + topicId: + type: string + required: + - catalog + - dbImage + - gsm + - gsmImage + - shard + type: object + status: + properties: + catalogs: + additionalProperties: + type: string + type: object + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + maxLength: 32768 + type: string + observedGeneration: + format: int64 + minimum: 0 + type: integer + reason: + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + enum: + - "True" + - "False" + - Unknown + type: string + type: + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + gsm: + properties: + details: + additionalProperties: + type: string + type: object + externalConnectStr: + type: string + internalConnectStr: + type: string + services: + type: string + shards: + additionalProperties: + type: string + type: object + state: + type: string + type: object + shards: + additionalProperties: + type: string + type: object + type: object + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .status.gsm.state + name: Gsm State + type: string + - jsonPath: .status.gsm.services + name: Services + type: string + - jsonPath: .status.gsm.shards + name: shards + priority: 1 + type: string + name: v4 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + InvitedNodeSubnet: + type: string + catalog: + items: + properties: + envVars: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + imagePullPolicy: + type: string + isDelete: + type: string + label: + type: string + name: + type: string + nodeSelector: + additionalProperties: + type: string + type: object + pvAnnotations: + additionalProperties: + type: string + type: object + pvMatchLabels: + additionalProperties: + type: string + type: object + pvcName: + type: string + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + storageSizeInGb: + format: int32 + type: integer + required: + - name + type: object + type: array + dbEdition: + type: string + dbImage: + type: string + dbImagePullSecret: + type: string + dbSecret: + properties: + encryptionType: + type: string + keyFileMountLocation: + type: string + keyFileName: + type: string + keySecretName: + type: string + name: + type: string + nsConfigMap: + type: string + nsSecret: + type: string + pwdFileMountLocation: + type: string + pwdFileName: + type: string + required: + - name + - pwdFileName + type: object + fssStorageClass: + type: string + gsm: + items: + properties: + directorName: + type: string + envVars: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + imagePullPolicy: + type: string + isDelete: + type: string + label: + type: string + name: + type: string + nodeSelector: + additionalProperties: + type: string + type: object + pvAnnotations: + additionalProperties: + type: string + type: object + pvMatchLabels: + additionalProperties: + type: string + type: object + pvcName: + type: string + region: + type: string + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + storageSizeInGb: + format: int32 + type: integer + required: + - name + type: object + type: array + gsmDevMode: + type: string + gsmImage: + type: string + gsmImagePullSecret: + type: string + gsmService: + items: + properties: + available: + type: string + clbGoal: + type: string + commitOutcome: + type: string + drainTimeout: + type: string + dtp: + type: string + edition: + type: string + failoverDelay: + type: string + failoverMethod: + type: string + failoverPrimary: + type: string + failoverRestore: + type: string + failoverRetry: + type: string + failoverType: + type: string + gdsPool: + type: string + lag: + type: integer + locality: + type: string + name: + type: string + notification: + type: string + pdbName: + type: string + policy: + type: string + preferred: + type: string + prferredAll: + type: string + regionFailover: + type: string + retention: + type: string + role: + type: string + sessionState: + type: string + sqlTransactionProfile: + type: string + stopOption: + type: string + tableFamily: + type: string + tfaPolicy: + type: string + required: + - name + type: object + type: array + gsmShardGroup: + items: + properties: + deployAs: + type: string + name: + type: string + region: + type: string + required: + - name + type: object + type: array + gsmShardSpace: + items: + properties: + chunks: + type: integer + name: + type: string + protectionMode: + type: string + shardGroup: + type: string + required: + - name + type: object + type: array + invitedNodeSubnetFlag: + type: string + isClone: + type: boolean + isDataGuard: + type: boolean + isDebug: + type: boolean + isDeleteOraPvc: + type: boolean + isDownloadScripts: + type: boolean + isExternalSvc: + type: boolean + isTdeWallet: + type: string + liveinessCheckPeriod: + type: integer + portMappings: + items: + properties: + port: + format: int32 + type: integer + protocol: + type: string + targetPort: + format: int32 + type: integer + required: + - port + - protocol + - targetPort + type: object + type: array + readinessCheckPeriod: + type: integer + replicationType: + type: string + scriptsLocation: + type: string + shard: + items: + properties: + deployAs: + type: string + envVars: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + imagePullPolicy: + type: string + isDelete: + enum: + - enable + - disable + - failed + - force + type: string + label: + type: string + name: + type: string + nodeSelector: + additionalProperties: + type: string + type: object + pvAnnotations: + additionalProperties: + type: string + type: object + pvMatchLabels: + additionalProperties: + type: string + type: object + pvcName: + type: string + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + shardGroup: + type: string + shardRegion: + type: string + shardSpace: + type: string + storageSizeInGb: + format: int32 + type: integer + required: + - name + type: object + type: array + shardBuddyRegion: + type: string + shardConfigName: + type: string + shardRegion: + items: + type: string + type: array + shardingType: + type: string + stagePvcName: + type: string + storageClass: + type: string + tdeWalletPvc: + type: string + tdeWalletPvcMountLocation: + type: string + topicId: + type: string + required: + - catalog + - dbImage + - gsm + - gsmImage + - shard + type: object + status: + properties: + catalogs: + additionalProperties: + type: string + type: object + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + maxLength: 32768 + type: string + observedGeneration: + format: int64 + minimum: 0 + type: integer + reason: + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + enum: + - "True" + - "False" + - Unknown + type: string + type: + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + gsm: + properties: + details: + additionalProperties: + type: string + type: object + externalConnectStr: + type: string + internalConnectStr: + type: string + services: + type: string + shards: + additionalProperties: + type: string + type: object + state: + type: string + type: object + shards: + additionalProperties: + type: string + type: object + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/bundle/manifests/database.oracle.com_singleinstancedatabases.yaml b/bundle/manifests/database.oracle.com_singleinstancedatabases.yaml new file mode 100644 index 00000000..2a633c70 --- /dev/null +++ b/bundle/manifests/database.oracle.com_singleinstancedatabases.yaml @@ -0,0 +1,717 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: oracle-database-operator-system/oracle-database-operator-serving-cert + controller-gen.kubebuilder.io/version: v0.16.5 + creationTimestamp: null + name: singleinstancedatabases.database.oracle.com +spec: + group: database.oracle.com + names: + kind: SingleInstanceDatabase + listKind: SingleInstanceDatabaseList + plural: singleinstancedatabases + singular: singleinstancedatabase + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.edition + name: Edition + type: string + - jsonPath: .status.sid + name: Sid + priority: 1 + type: string + - jsonPath: .status.status + name: Status + type: string + - jsonPath: .status.role + name: Role + type: string + - jsonPath: .status.releaseUpdate + name: Version + type: string + - jsonPath: .status.connectString + name: Connect Str + type: string + - jsonPath: .status.pdbConnectString + name: Pdb Connect Str + priority: 1 + type: string + - jsonPath: .status.tcpsConnectString + name: TCPS Connect Str + type: string + - jsonPath: .status.tcpsPdbConnectString + name: TCPS Pdb Connect Str + priority: 1 + type: string + - jsonPath: .status.oemExpressUrl + name: Oem Express Url + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + adminPassword: + properties: + keepSecret: + type: boolean + secretKey: + default: oracle_pwd + type: string + secretName: + type: string + required: + - secretName + type: object + archiveLog: + type: boolean + charset: + type: string + convertToSnapshotStandby: + type: boolean + createAs: + enum: + - primary + - standby + - clone + - truecache + type: string + edition: + enum: + - standard + - enterprise + - express + - free + type: string + enableTCPS: + type: boolean + flashBack: + type: boolean + forceLog: + type: boolean + image: + properties: + prebuiltDB: + type: boolean + pullFrom: + type: string + pullSecrets: + type: string + version: + type: string + required: + - pullFrom + type: object + initParams: + properties: + cpuCount: + type: integer + pgaAggregateTarget: + type: integer + processes: + type: integer + sgaTarget: + type: integer + type: object + listenerPort: + type: integer + loadBalancer: + type: boolean + nodeSelector: + additionalProperties: + type: string + type: object + pdbName: + type: string + persistence: + properties: + accessMode: + enum: + - ReadWriteOnce + - ReadWriteMany + type: string + datafilesVolumeName: + type: string + scriptsVolumeName: + type: string + setWritePermissions: + type: boolean + size: + type: string + storageClass: + type: string + volumeClaimAnnotation: + type: string + type: object + primaryDatabaseRef: + type: string + readinessCheckPeriod: + type: integer + replicas: + type: integer + resources: + properties: + limits: + properties: + cpu: + type: string + memory: + type: string + type: object + requests: + properties: + cpu: + type: string + memory: + type: string + type: object + type: object + serviceAccountName: + type: string + serviceAnnotations: + additionalProperties: + type: string + type: object + sid: + maxLength: 12 + pattern: ^[a-zA-Z0-9]+$ + type: string + tcpsCertRenewInterval: + type: string + tcpsListenerPort: + type: integer + tcpsTlsSecret: + type: string + trueCacheServices: + items: + type: string + type: array + required: + - image + type: object + status: + properties: + apexInstalled: + type: boolean + archiveLog: + type: string + certCreationTimestamp: + type: string + certRenewInterval: + type: string + charset: + type: string + clientWalletLoc: + type: string + clusterConnectString: + type: string + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + maxLength: 32768 + type: string + observedGeneration: + format: int64 + minimum: 0 + type: integer + reason: + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + enum: + - "True" + - "False" + - Unknown + type: string + type: + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + connectString: + type: string + convertToSnapshotStandby: + type: boolean + createdAs: + type: string + datafilesCreated: + default: "false" + type: string + datafilesPatched: + default: "false" + type: string + dgBroker: + type: string + edition: + type: string + flashBack: + type: string + forceLog: + type: string + initParams: + properties: + cpuCount: + type: integer + pgaAggregateTarget: + type: integer + processes: + type: integer + sgaTarget: + type: integer + type: object + initPgaSize: + type: integer + initSgaSize: + type: integer + isTcpsEnabled: + default: false + type: boolean + nodes: + items: + type: string + type: array + oemExpressUrl: + type: string + ordsReference: + type: string + pdbConnectString: + type: string + pdbName: + type: string + persistence: + properties: + accessMode: + enum: + - ReadWriteOnce + - ReadWriteMany + type: string + datafilesVolumeName: + type: string + scriptsVolumeName: + type: string + setWritePermissions: + type: boolean + size: + type: string + storageClass: + type: string + volumeClaimAnnotation: + type: string + type: object + prebuiltDB: + type: boolean + primaryDatabase: + type: string + releaseUpdate: + type: string + replicas: + type: integer + role: + type: string + sid: + type: string + standbyDatabases: + additionalProperties: + type: string + type: object + status: + type: string + tcpsConnectString: + type: string + tcpsPdbConnectString: + type: string + tcpsTlsSecret: + default: "" + type: string + required: + - isTcpsEnabled + - persistence + - tcpsTlsSecret + type: object + type: object + served: true + storage: false + subresources: + scale: + specReplicasPath: .spec.replicas + statusReplicasPath: .status.replicas + status: {} + - additionalPrinterColumns: + - jsonPath: .status.edition + name: Edition + type: string + - jsonPath: .status.sid + name: Sid + priority: 1 + type: string + - jsonPath: .status.status + name: Status + type: string + - jsonPath: .status.role + name: Role + type: string + - jsonPath: .status.releaseUpdate + name: Version + type: string + - jsonPath: .status.connectString + name: Connect Str + type: string + - jsonPath: .status.pdbConnectString + name: Pdb Connect Str + priority: 1 + type: string + - jsonPath: .status.tcpsConnectString + name: TCPS Connect Str + type: string + - jsonPath: .status.tcpsPdbConnectString + name: TCPS Pdb Connect Str + priority: 1 + type: string + - jsonPath: .status.oemExpressUrl + name: Oem Express Url + type: string + name: v4 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + adminPassword: + properties: + keepSecret: + type: boolean + secretKey: + default: oracle_pwd + type: string + secretName: + type: string + required: + - secretName + type: object + archiveLog: + type: boolean + charset: + type: string + convertToSnapshotStandby: + type: boolean + createAs: + enum: + - primary + - standby + - clone + - truecache + type: string + edition: + enum: + - standard + - enterprise + - express + - free + type: string + enableTCPS: + type: boolean + flashBack: + type: boolean + forceLog: + type: boolean + image: + properties: + prebuiltDB: + type: boolean + pullFrom: + type: string + pullSecrets: + type: string + version: + type: string + required: + - pullFrom + type: object + initParams: + properties: + cpuCount: + type: integer + pgaAggregateTarget: + type: integer + processes: + type: integer + sgaTarget: + type: integer + type: object + listenerPort: + type: integer + loadBalancer: + type: boolean + nodeSelector: + additionalProperties: + type: string + type: object + pdbName: + type: string + persistence: + properties: + accessMode: + enum: + - ReadWriteOnce + - ReadWriteMany + type: string + datafilesVolumeName: + type: string + scriptsVolumeName: + type: string + setWritePermissions: + type: boolean + size: + type: string + storageClass: + type: string + volumeClaimAnnotation: + type: string + type: object + primaryDatabaseRef: + type: string + readinessCheckPeriod: + type: integer + replicas: + type: integer + resources: + properties: + limits: + properties: + cpu: + type: string + memory: + type: string + type: object + requests: + properties: + cpu: + type: string + memory: + type: string + type: object + type: object + serviceAccountName: + type: string + serviceAnnotations: + additionalProperties: + type: string + type: object + sid: + maxLength: 12 + pattern: ^[a-zA-Z0-9]+$ + type: string + tcpsCertRenewInterval: + type: string + tcpsListenerPort: + type: integer + tcpsTlsSecret: + type: string + trueCacheServices: + items: + type: string + type: array + required: + - image + type: object + status: + properties: + apexInstalled: + type: boolean + archiveLog: + type: string + certCreationTimestamp: + type: string + certRenewInterval: + type: string + charset: + type: string + clientWalletLoc: + type: string + clusterConnectString: + type: string + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + maxLength: 32768 + type: string + observedGeneration: + format: int64 + minimum: 0 + type: integer + reason: + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + enum: + - "True" + - "False" + - Unknown + type: string + type: + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + connectString: + type: string + convertToSnapshotStandby: + type: boolean + createdAs: + type: string + datafilesCreated: + default: "false" + type: string + datafilesPatched: + default: "false" + type: string + dgBroker: + type: string + edition: + type: string + flashBack: + type: string + forceLog: + type: string + initParams: + properties: + cpuCount: + type: integer + pgaAggregateTarget: + type: integer + processes: + type: integer + sgaTarget: + type: integer + type: object + initPgaSize: + type: integer + initSgaSize: + type: integer + isTcpsEnabled: + default: false + type: boolean + nodes: + items: + type: string + type: array + oemExpressUrl: + type: string + ordsReference: + type: string + pdbConnectString: + type: string + pdbName: + type: string + persistence: + properties: + accessMode: + enum: + - ReadWriteOnce + - ReadWriteMany + type: string + datafilesVolumeName: + type: string + scriptsVolumeName: + type: string + setWritePermissions: + type: boolean + size: + type: string + storageClass: + type: string + volumeClaimAnnotation: + type: string + type: object + prebuiltDB: + type: boolean + primaryDatabase: + type: string + releaseUpdate: + type: string + replicas: + type: integer + role: + type: string + sid: + type: string + standbyDatabases: + additionalProperties: + type: string + type: object + status: + type: string + tcpsConnectString: + type: string + tcpsPdbConnectString: + type: string + tcpsTlsSecret: + default: "" + type: string + required: + - isTcpsEnabled + - persistence + - tcpsTlsSecret + type: object + type: object + served: true + storage: true + subresources: + scale: + specReplicasPath: .spec.replicas + statusReplicasPath: .status.replicas + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/bundle/manifests/observability.oracle.com_databaseobservers.yaml b/bundle/manifests/observability.oracle.com_databaseobservers.yaml new file mode 100644 index 00000000..652d773e --- /dev/null +++ b/bundle/manifests/observability.oracle.com_databaseobservers.yaml @@ -0,0 +1,6995 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: oracle-database-operator-system/oracle-database-operator-serving-cert + controller-gen.kubebuilder.io/version: v0.16.5 + creationTimestamp: null + name: databaseobservers.observability.oracle.com +spec: + group: observability.oracle.com + names: + kind: DatabaseObserver + listKind: DatabaseObserverList + plural: databaseobservers + shortNames: + - dbobserver + - dbobservers + singular: databaseobserver + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.exporterConfig + name: ExporterConfig + type: string + - jsonPath: .status.status + name: Status + type: string + - jsonPath: .status.version + name: Version + type: string + name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + configuration: + properties: + configMap: + properties: + key: + type: string + name: + type: string + type: object + type: object + database: + properties: + dbConnectionString: + properties: + key: + type: string + secret: + type: string + type: object + dbPassword: + properties: + key: + type: string + secret: + type: string + vaultOCID: + type: string + vaultSecretName: + type: string + type: object + dbUser: + properties: + key: + type: string + secret: + type: string + type: object + dbWallet: + properties: + key: + type: string + secret: + type: string + type: object + type: object + exporter: + properties: + deployment: + properties: + args: + items: + type: string + type: array + commands: + items: + type: string + type: array + env: + additionalProperties: + type: string + type: object + image: + type: string + labels: + additionalProperties: + type: string + type: object + podTemplate: + properties: + labels: + additionalProperties: + type: string + type: object + securityContext: + properties: + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + fsGroup: + format: int64 + type: integer + fsGroupChangePolicy: + type: string + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + supplementalGroups: + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + supplementalGroupsPolicy: + type: string + sysctls: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + type: object + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + capabilities: + properties: + add: + items: + type: string + type: array + x-kubernetes-list-type: atomic + drop: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + type: object + service: + properties: + labels: + additionalProperties: + type: string + type: object + ports: + items: + properties: + appProtocol: + type: string + name: + type: string + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + default: TCP + type: string + targetPort: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + type: object + type: object + inheritLabels: + items: + type: string + type: array + log: + properties: + filename: + type: string + path: + type: string + volume: + properties: + name: + type: string + persistentVolumeClaim: + properties: + claimName: + type: string + type: object + type: object + type: object + ociConfig: + properties: + configMapName: + type: string + secretName: + type: string + type: object + prometheus: + properties: + serviceMonitor: + properties: + endpoints: + items: + properties: + authorization: + properties: + credentials: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: + type: string + type: object + basicAuth: + properties: + password: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + username: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + bearerTokenFile: + type: string + bearerTokenSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + enableHttp2: + type: boolean + filterRunning: + type: boolean + followRedirects: + type: boolean + honorLabels: + type: boolean + honorTimestamps: + type: boolean + interval: + pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ + type: string + metricRelabelings: + items: + properties: + action: + default: replace + enum: + - replace + - Replace + - keep + - Keep + - drop + - Drop + - hashmod + - HashMod + - labelmap + - LabelMap + - labeldrop + - LabelDrop + - labelkeep + - LabelKeep + - lowercase + - Lowercase + - uppercase + - Uppercase + - keepequal + - KeepEqual + - dropequal + - DropEqual + type: string + modulus: + format: int64 + type: integer + regex: + type: string + replacement: + type: string + separator: + type: string + sourceLabels: + items: + pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ + type: string + type: array + targetLabel: + type: string + type: object + type: array + oauth2: + properties: + clientId: + properties: + configMap: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + secret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + clientSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + endpointParams: + additionalProperties: + type: string + type: object + noProxy: + type: string + proxyConnectHeader: + additionalProperties: + items: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: array + type: object + x-kubernetes-map-type: atomic + proxyFromEnvironment: + type: boolean + proxyUrl: + pattern: ^http(s)?://.+$ + type: string + scopes: + items: + type: string + type: array + tlsConfig: + properties: + ca: + properties: + configMap: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + secret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + cert: + properties: + configMap: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + secret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + insecureSkipVerify: + type: boolean + keySecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + maxVersion: + enum: + - TLS10 + - TLS11 + - TLS12 + - TLS13 + type: string + minVersion: + enum: + - TLS10 + - TLS11 + - TLS12 + - TLS13 + type: string + serverName: + type: string + type: object + tokenUrl: + minLength: 1 + type: string + required: + - clientId + - clientSecret + - tokenUrl + type: object + params: + additionalProperties: + items: + type: string + type: array + type: object + path: + type: string + port: + type: string + proxyUrl: + type: string + relabelings: + items: + properties: + action: + default: replace + enum: + - replace + - Replace + - keep + - Keep + - drop + - Drop + - hashmod + - HashMod + - labelmap + - LabelMap + - labeldrop + - LabelDrop + - labelkeep + - LabelKeep + - lowercase + - Lowercase + - uppercase + - Uppercase + - keepequal + - KeepEqual + - dropequal + - DropEqual + type: string + modulus: + format: int64 + type: integer + regex: + type: string + replacement: + type: string + separator: + type: string + sourceLabels: + items: + pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ + type: string + type: array + targetLabel: + type: string + type: object + type: array + scheme: + enum: + - http + - https + type: string + scrapeTimeout: + pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ + type: string + targetPort: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + tlsConfig: + properties: + ca: + properties: + configMap: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + secret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + caFile: + type: string + cert: + properties: + configMap: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + secret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + certFile: + type: string + insecureSkipVerify: + type: boolean + keyFile: + type: string + keySecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + maxVersion: + enum: + - TLS10 + - TLS11 + - TLS12 + - TLS13 + type: string + minVersion: + enum: + - TLS10 + - TLS11 + - TLS12 + - TLS13 + type: string + serverName: + type: string + type: object + trackTimestampsStaleness: + type: boolean + type: object + type: array + labels: + additionalProperties: + type: string + type: object + namespaceSelector: + properties: + any: + type: boolean + matchNames: + items: + type: string + type: array + type: object + type: object + type: object + replicas: + format: int32 + type: integer + sidecarVolumes: + items: + properties: + awsElasticBlockStore: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + azureDisk: + properties: + cachingMode: + type: string + diskName: + type: string + diskURI: + type: string + fsType: + default: ext4 + type: string + kind: + type: string + readOnly: + default: false + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + properties: + readOnly: + type: boolean + secretName: + type: string + shareName: + type: string + required: + - secretName + - shareName + type: object + cephfs: + properties: + monitors: + items: + type: string + type: array + x-kubernetes-list-type: atomic + path: + type: string + readOnly: + type: boolean + secretFile: + type: string + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + user: + type: string + required: + - monitors + type: object + cinder: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + volumeID: + type: string + required: + - volumeID + type: object + configMap: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + csi: + properties: + driver: + type: string + fsType: + type: string + nodePublishSecretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: + type: boolean + volumeAttributes: + additionalProperties: + type: string + type: object + required: + - driver + type: object + downwardAPI: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + emptyDir: + properties: + medium: + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + properties: + volumeClaimTemplate: + properties: + metadata: + type: object + spec: + properties: + accessModes: + items: + type: string + type: array + x-kubernetes-list-type: atomic + dataSource: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + dataSourceRef: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + required: + - kind + - name + type: object + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + storageClassName: + type: string + volumeAttributesClassName: + type: string + volumeMode: + type: string + volumeName: + type: string + type: object + required: + - spec + type: object + type: object + fc: + properties: + fsType: + type: string + lun: + format: int32 + type: integer + readOnly: + type: boolean + targetWWNs: + items: + type: string + type: array + x-kubernetes-list-type: atomic + wwids: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + flexVolume: + properties: + driver: + type: string + fsType: + type: string + options: + additionalProperties: + type: string + type: object + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + required: + - driver + type: object + flocker: + properties: + datasetName: + type: string + datasetUUID: + type: string + type: object + gcePersistentDisk: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + pdName: + type: string + readOnly: + type: boolean + required: + - pdName + type: object + gitRepo: + properties: + directory: + type: string + repository: + type: string + revision: + type: string + required: + - repository + type: object + glusterfs: + properties: + endpoints: + type: string + path: + type: string + readOnly: + type: boolean + required: + - endpoints + - path + type: object + hostPath: + properties: + path: + type: string + type: + type: string + required: + - path + type: object + image: + properties: + pullPolicy: + type: string + reference: + type: string + type: object + iscsi: + properties: + chapAuthDiscovery: + type: boolean + chapAuthSession: + type: boolean + fsType: + type: string + initiatorName: + type: string + iqn: + type: string + iscsiInterface: + default: default + type: string + lun: + format: int32 + type: integer + portals: + items: + type: string + type: array + x-kubernetes-list-type: atomic + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + targetPortal: + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + type: string + nfs: + properties: + path: + type: string + readOnly: + type: boolean + server: + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + properties: + claimName: + type: string + readOnly: + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + properties: + fsType: + type: string + pdID: + type: string + required: + - pdID + type: object + portworxVolume: + properties: + fsType: + type: string + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + projected: + properties: + defaultMode: + format: int32 + type: integer + sources: + items: + properties: + clusterTrustBundle: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + name: + type: string + optional: + type: boolean + path: + type: string + signerName: + type: string + required: + - path + type: object + configMap: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + downwardAPI: + properties: + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + secret: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + serviceAccountToken: + properties: + audience: + type: string + expirationSeconds: + format: int64 + type: integer + path: + type: string + required: + - path + type: object + type: object + type: array + x-kubernetes-list-type: atomic + type: object + quobyte: + properties: + group: + type: string + readOnly: + type: boolean + registry: + type: string + tenant: + type: string + user: + type: string + volume: + type: string + required: + - registry + - volume + type: object + rbd: + properties: + fsType: + type: string + image: + type: string + keyring: + default: /etc/ceph/keyring + type: string + monitors: + items: + type: string + type: array + x-kubernetes-list-type: atomic + pool: + default: rbd + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + user: + default: admin + type: string + required: + - image + - monitors + type: object + scaleIO: + properties: + fsType: + default: xfs + type: string + gateway: + type: string + protectionDomain: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + sslEnabled: + type: boolean + storageMode: + default: ThinProvisioned + type: string + storagePool: + type: string + system: + type: string + volumeName: + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + optional: + type: boolean + secretName: + type: string + type: object + storageos: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + volumeName: + type: string + volumeNamespace: + type: string + type: object + vsphereVolume: + properties: + fsType: + type: string + storagePolicyID: + type: string + storagePolicyName: + type: string + volumePath: + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + sidecars: + items: + properties: + args: + items: + type: string + type: array + x-kubernetes-list-type: atomic + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + envFrom: + items: + properties: + configMapRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + type: string + secretRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array + x-kubernetes-list-type: atomic + image: + type: string + imagePullPolicy: + type: string + lifecycle: + properties: + postStart: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + name: + type: string + ports: + items: + properties: + containerPort: + format: int32 + type: integer + hostIP: + type: string + hostPort: + format: int32 + type: integer + name: + type: string + protocol: + default: TCP + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + resizePolicy: + items: + properties: + resourceName: + type: string + restartPolicy: + type: string + required: + - resourceName + - restartPolicy + type: object + type: array + x-kubernetes-list-type: atomic + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + restartPolicy: + type: string + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + capabilities: + properties: + add: + items: + type: string + type: array + x-kubernetes-list-type: atomic + drop: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + startupProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + stdin: + type: boolean + stdinOnce: + type: boolean + terminationMessagePath: + type: string + terminationMessagePolicy: + type: string + tty: + type: boolean + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + required: + - devicePath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - devicePath + x-kubernetes-list-type: map + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + recursiveReadOnly: + type: string + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - mountPath + x-kubernetes-list-type: map + workingDir: + type: string + required: + - name + type: object + type: array + type: object + status: + properties: + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + maxLength: 32768 + type: string + observedGeneration: + format: int64 + minimum: 0 + type: integer + reason: + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + enum: + - "True" + - "False" + - Unknown + type: string + type: + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + exporterConfig: + type: string + replicas: + type: integer + status: + type: string + version: + type: string + required: + - conditions + - exporterConfig + - version + type: object + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .status.exporterConfig + name: ExporterConfig + type: string + - jsonPath: .status.status + name: Status + type: string + - jsonPath: .status.version + name: Version + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + configuration: + properties: + configMap: + properties: + key: + type: string + name: + type: string + type: object + type: object + database: + properties: + dbConnectionString: + properties: + key: + type: string + secret: + type: string + type: object + dbPassword: + properties: + key: + type: string + secret: + type: string + vaultOCID: + type: string + vaultSecretName: + type: string + type: object + dbUser: + properties: + key: + type: string + secret: + type: string + type: object + dbWallet: + properties: + key: + type: string + secret: + type: string + type: object + type: object + exporter: + properties: + deployment: + properties: + args: + items: + type: string + type: array + commands: + items: + type: string + type: array + env: + additionalProperties: + type: string + type: object + image: + type: string + labels: + additionalProperties: + type: string + type: object + podTemplate: + properties: + labels: + additionalProperties: + type: string + type: object + securityContext: + properties: + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + fsGroup: + format: int64 + type: integer + fsGroupChangePolicy: + type: string + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + supplementalGroups: + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + supplementalGroupsPolicy: + type: string + sysctls: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + type: object + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + capabilities: + properties: + add: + items: + type: string + type: array + x-kubernetes-list-type: atomic + drop: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + type: object + service: + properties: + labels: + additionalProperties: + type: string + type: object + ports: + items: + properties: + appProtocol: + type: string + name: + type: string + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + default: TCP + type: string + targetPort: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + type: object + type: object + inheritLabels: + items: + type: string + type: array + log: + properties: + filename: + type: string + path: + type: string + volume: + properties: + name: + type: string + persistentVolumeClaim: + properties: + claimName: + type: string + type: object + type: object + type: object + ociConfig: + properties: + configMapName: + type: string + secretName: + type: string + type: object + prometheus: + properties: + serviceMonitor: + properties: + endpoints: + items: + properties: + authorization: + properties: + credentials: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: + type: string + type: object + basicAuth: + properties: + password: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + username: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + bearerTokenFile: + type: string + bearerTokenSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + enableHttp2: + type: boolean + filterRunning: + type: boolean + followRedirects: + type: boolean + honorLabels: + type: boolean + honorTimestamps: + type: boolean + interval: + pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ + type: string + metricRelabelings: + items: + properties: + action: + default: replace + enum: + - replace + - Replace + - keep + - Keep + - drop + - Drop + - hashmod + - HashMod + - labelmap + - LabelMap + - labeldrop + - LabelDrop + - labelkeep + - LabelKeep + - lowercase + - Lowercase + - uppercase + - Uppercase + - keepequal + - KeepEqual + - dropequal + - DropEqual + type: string + modulus: + format: int64 + type: integer + regex: + type: string + replacement: + type: string + separator: + type: string + sourceLabels: + items: + pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ + type: string + type: array + targetLabel: + type: string + type: object + type: array + oauth2: + properties: + clientId: + properties: + configMap: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + secret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + clientSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + endpointParams: + additionalProperties: + type: string + type: object + noProxy: + type: string + proxyConnectHeader: + additionalProperties: + items: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: array + type: object + x-kubernetes-map-type: atomic + proxyFromEnvironment: + type: boolean + proxyUrl: + pattern: ^http(s)?://.+$ + type: string + scopes: + items: + type: string + type: array + tlsConfig: + properties: + ca: + properties: + configMap: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + secret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + cert: + properties: + configMap: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + secret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + insecureSkipVerify: + type: boolean + keySecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + maxVersion: + enum: + - TLS10 + - TLS11 + - TLS12 + - TLS13 + type: string + minVersion: + enum: + - TLS10 + - TLS11 + - TLS12 + - TLS13 + type: string + serverName: + type: string + type: object + tokenUrl: + minLength: 1 + type: string + required: + - clientId + - clientSecret + - tokenUrl + type: object + params: + additionalProperties: + items: + type: string + type: array + type: object + path: + type: string + port: + type: string + proxyUrl: + type: string + relabelings: + items: + properties: + action: + default: replace + enum: + - replace + - Replace + - keep + - Keep + - drop + - Drop + - hashmod + - HashMod + - labelmap + - LabelMap + - labeldrop + - LabelDrop + - labelkeep + - LabelKeep + - lowercase + - Lowercase + - uppercase + - Uppercase + - keepequal + - KeepEqual + - dropequal + - DropEqual + type: string + modulus: + format: int64 + type: integer + regex: + type: string + replacement: + type: string + separator: + type: string + sourceLabels: + items: + pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ + type: string + type: array + targetLabel: + type: string + type: object + type: array + scheme: + enum: + - http + - https + type: string + scrapeTimeout: + pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ + type: string + targetPort: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + tlsConfig: + properties: + ca: + properties: + configMap: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + secret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + caFile: + type: string + cert: + properties: + configMap: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + secret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + certFile: + type: string + insecureSkipVerify: + type: boolean + keyFile: + type: string + keySecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + maxVersion: + enum: + - TLS10 + - TLS11 + - TLS12 + - TLS13 + type: string + minVersion: + enum: + - TLS10 + - TLS11 + - TLS12 + - TLS13 + type: string + serverName: + type: string + type: object + trackTimestampsStaleness: + type: boolean + type: object + type: array + labels: + additionalProperties: + type: string + type: object + namespaceSelector: + properties: + any: + type: boolean + matchNames: + items: + type: string + type: array + type: object + type: object + type: object + replicas: + format: int32 + type: integer + sidecarVolumes: + items: + properties: + awsElasticBlockStore: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + azureDisk: + properties: + cachingMode: + type: string + diskName: + type: string + diskURI: + type: string + fsType: + default: ext4 + type: string + kind: + type: string + readOnly: + default: false + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + properties: + readOnly: + type: boolean + secretName: + type: string + shareName: + type: string + required: + - secretName + - shareName + type: object + cephfs: + properties: + monitors: + items: + type: string + type: array + x-kubernetes-list-type: atomic + path: + type: string + readOnly: + type: boolean + secretFile: + type: string + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + user: + type: string + required: + - monitors + type: object + cinder: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + volumeID: + type: string + required: + - volumeID + type: object + configMap: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + csi: + properties: + driver: + type: string + fsType: + type: string + nodePublishSecretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: + type: boolean + volumeAttributes: + additionalProperties: + type: string + type: object + required: + - driver + type: object + downwardAPI: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + emptyDir: + properties: + medium: + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + properties: + volumeClaimTemplate: + properties: + metadata: + type: object + spec: + properties: + accessModes: + items: + type: string + type: array + x-kubernetes-list-type: atomic + dataSource: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + dataSourceRef: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + required: + - kind + - name + type: object + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + storageClassName: + type: string + volumeAttributesClassName: + type: string + volumeMode: + type: string + volumeName: + type: string + type: object + required: + - spec + type: object + type: object + fc: + properties: + fsType: + type: string + lun: + format: int32 + type: integer + readOnly: + type: boolean + targetWWNs: + items: + type: string + type: array + x-kubernetes-list-type: atomic + wwids: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + flexVolume: + properties: + driver: + type: string + fsType: + type: string + options: + additionalProperties: + type: string + type: object + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + required: + - driver + type: object + flocker: + properties: + datasetName: + type: string + datasetUUID: + type: string + type: object + gcePersistentDisk: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + pdName: + type: string + readOnly: + type: boolean + required: + - pdName + type: object + gitRepo: + properties: + directory: + type: string + repository: + type: string + revision: + type: string + required: + - repository + type: object + glusterfs: + properties: + endpoints: + type: string + path: + type: string + readOnly: + type: boolean + required: + - endpoints + - path + type: object + hostPath: + properties: + path: + type: string + type: + type: string + required: + - path + type: object + image: + properties: + pullPolicy: + type: string + reference: + type: string + type: object + iscsi: + properties: + chapAuthDiscovery: + type: boolean + chapAuthSession: + type: boolean + fsType: + type: string + initiatorName: + type: string + iqn: + type: string + iscsiInterface: + default: default + type: string + lun: + format: int32 + type: integer + portals: + items: + type: string + type: array + x-kubernetes-list-type: atomic + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + targetPortal: + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + type: string + nfs: + properties: + path: + type: string + readOnly: + type: boolean + server: + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + properties: + claimName: + type: string + readOnly: + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + properties: + fsType: + type: string + pdID: + type: string + required: + - pdID + type: object + portworxVolume: + properties: + fsType: + type: string + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + projected: + properties: + defaultMode: + format: int32 + type: integer + sources: + items: + properties: + clusterTrustBundle: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + name: + type: string + optional: + type: boolean + path: + type: string + signerName: + type: string + required: + - path + type: object + configMap: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + downwardAPI: + properties: + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + secret: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + serviceAccountToken: + properties: + audience: + type: string + expirationSeconds: + format: int64 + type: integer + path: + type: string + required: + - path + type: object + type: object + type: array + x-kubernetes-list-type: atomic + type: object + quobyte: + properties: + group: + type: string + readOnly: + type: boolean + registry: + type: string + tenant: + type: string + user: + type: string + volume: + type: string + required: + - registry + - volume + type: object + rbd: + properties: + fsType: + type: string + image: + type: string + keyring: + default: /etc/ceph/keyring + type: string + monitors: + items: + type: string + type: array + x-kubernetes-list-type: atomic + pool: + default: rbd + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + user: + default: admin + type: string + required: + - image + - monitors + type: object + scaleIO: + properties: + fsType: + default: xfs + type: string + gateway: + type: string + protectionDomain: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + sslEnabled: + type: boolean + storageMode: + default: ThinProvisioned + type: string + storagePool: + type: string + system: + type: string + volumeName: + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + optional: + type: boolean + secretName: + type: string + type: object + storageos: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + volumeName: + type: string + volumeNamespace: + type: string + type: object + vsphereVolume: + properties: + fsType: + type: string + storagePolicyID: + type: string + storagePolicyName: + type: string + volumePath: + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + sidecars: + items: + properties: + args: + items: + type: string + type: array + x-kubernetes-list-type: atomic + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + envFrom: + items: + properties: + configMapRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + type: string + secretRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array + x-kubernetes-list-type: atomic + image: + type: string + imagePullPolicy: + type: string + lifecycle: + properties: + postStart: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + name: + type: string + ports: + items: + properties: + containerPort: + format: int32 + type: integer + hostIP: + type: string + hostPort: + format: int32 + type: integer + name: + type: string + protocol: + default: TCP + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + resizePolicy: + items: + properties: + resourceName: + type: string + restartPolicy: + type: string + required: + - resourceName + - restartPolicy + type: object + type: array + x-kubernetes-list-type: atomic + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + restartPolicy: + type: string + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + capabilities: + properties: + add: + items: + type: string + type: array + x-kubernetes-list-type: atomic + drop: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + startupProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + stdin: + type: boolean + stdinOnce: + type: boolean + terminationMessagePath: + type: string + terminationMessagePolicy: + type: string + tty: + type: boolean + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + required: + - devicePath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - devicePath + x-kubernetes-list-type: map + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + recursiveReadOnly: + type: string + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - mountPath + x-kubernetes-list-type: map + workingDir: + type: string + required: + - name + type: object + type: array + type: object + status: + properties: + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + maxLength: 32768 + type: string + observedGeneration: + format: int64 + minimum: 0 + type: integer + reason: + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + enum: + - "True" + - "False" + - Unknown + type: string + type: + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + exporterConfig: + type: string + replicas: + type: integer + status: + type: string + version: + type: string + required: + - conditions + - exporterConfig + - version + type: object + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .status.exporterConfig + name: ExporterConfig + type: string + - jsonPath: .status.status + name: Status + type: string + - jsonPath: .status.version + name: Version + type: string + name: v4 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + configuration: + properties: + configMap: + properties: + key: + type: string + name: + type: string + type: object + type: object + database: + properties: + dbConnectionString: + properties: + key: + type: string + secret: + type: string + type: object + dbPassword: + properties: + key: + type: string + secret: + type: string + vaultOCID: + type: string + vaultSecretName: + type: string + type: object + dbUser: + properties: + key: + type: string + secret: + type: string + type: object + dbWallet: + properties: + key: + type: string + secret: + type: string + type: object + type: object + exporter: + properties: + deployment: + properties: + args: + items: + type: string + type: array + commands: + items: + type: string + type: array + env: + additionalProperties: + type: string + type: object + image: + type: string + labels: + additionalProperties: + type: string + type: object + podTemplate: + properties: + labels: + additionalProperties: + type: string + type: object + securityContext: + properties: + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + fsGroup: + format: int64 + type: integer + fsGroupChangePolicy: + type: string + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + supplementalGroups: + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + supplementalGroupsPolicy: + type: string + sysctls: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + type: object + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + capabilities: + properties: + add: + items: + type: string + type: array + x-kubernetes-list-type: atomic + drop: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + type: object + service: + properties: + labels: + additionalProperties: + type: string + type: object + ports: + items: + properties: + appProtocol: + type: string + name: + type: string + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + default: TCP + type: string + targetPort: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + type: object + type: object + inheritLabels: + items: + type: string + type: array + log: + properties: + filename: + type: string + path: + type: string + volume: + properties: + name: + type: string + persistentVolumeClaim: + properties: + claimName: + type: string + type: object + type: object + type: object + ociConfig: + properties: + configMapName: + type: string + secretName: + type: string + type: object + prometheus: + properties: + serviceMonitor: + properties: + endpoints: + items: + properties: + authorization: + properties: + credentials: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: + type: string + type: object + basicAuth: + properties: + password: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + username: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + bearerTokenFile: + type: string + bearerTokenSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + enableHttp2: + type: boolean + filterRunning: + type: boolean + followRedirects: + type: boolean + honorLabels: + type: boolean + honorTimestamps: + type: boolean + interval: + pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ + type: string + metricRelabelings: + items: + properties: + action: + default: replace + enum: + - replace + - Replace + - keep + - Keep + - drop + - Drop + - hashmod + - HashMod + - labelmap + - LabelMap + - labeldrop + - LabelDrop + - labelkeep + - LabelKeep + - lowercase + - Lowercase + - uppercase + - Uppercase + - keepequal + - KeepEqual + - dropequal + - DropEqual + type: string + modulus: + format: int64 + type: integer + regex: + type: string + replacement: + type: string + separator: + type: string + sourceLabels: + items: + pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ + type: string + type: array + targetLabel: + type: string + type: object + type: array + oauth2: + properties: + clientId: + properties: + configMap: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + secret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + clientSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + endpointParams: + additionalProperties: + type: string + type: object + noProxy: + type: string + proxyConnectHeader: + additionalProperties: + items: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: array + type: object + x-kubernetes-map-type: atomic + proxyFromEnvironment: + type: boolean + proxyUrl: + pattern: ^http(s)?://.+$ + type: string + scopes: + items: + type: string + type: array + tlsConfig: + properties: + ca: + properties: + configMap: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + secret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + cert: + properties: + configMap: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + secret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + insecureSkipVerify: + type: boolean + keySecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + maxVersion: + enum: + - TLS10 + - TLS11 + - TLS12 + - TLS13 + type: string + minVersion: + enum: + - TLS10 + - TLS11 + - TLS12 + - TLS13 + type: string + serverName: + type: string + type: object + tokenUrl: + minLength: 1 + type: string + required: + - clientId + - clientSecret + - tokenUrl + type: object + params: + additionalProperties: + items: + type: string + type: array + type: object + path: + type: string + port: + type: string + proxyUrl: + type: string + relabelings: + items: + properties: + action: + default: replace + enum: + - replace + - Replace + - keep + - Keep + - drop + - Drop + - hashmod + - HashMod + - labelmap + - LabelMap + - labeldrop + - LabelDrop + - labelkeep + - LabelKeep + - lowercase + - Lowercase + - uppercase + - Uppercase + - keepequal + - KeepEqual + - dropequal + - DropEqual + type: string + modulus: + format: int64 + type: integer + regex: + type: string + replacement: + type: string + separator: + type: string + sourceLabels: + items: + pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ + type: string + type: array + targetLabel: + type: string + type: object + type: array + scheme: + enum: + - http + - https + type: string + scrapeTimeout: + pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ + type: string + targetPort: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + tlsConfig: + properties: + ca: + properties: + configMap: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + secret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + caFile: + type: string + cert: + properties: + configMap: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + secret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + certFile: + type: string + insecureSkipVerify: + type: boolean + keyFile: + type: string + keySecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + maxVersion: + enum: + - TLS10 + - TLS11 + - TLS12 + - TLS13 + type: string + minVersion: + enum: + - TLS10 + - TLS11 + - TLS12 + - TLS13 + type: string + serverName: + type: string + type: object + trackTimestampsStaleness: + type: boolean + type: object + type: array + labels: + additionalProperties: + type: string + type: object + namespaceSelector: + properties: + any: + type: boolean + matchNames: + items: + type: string + type: array + type: object + type: object + type: object + replicas: + format: int32 + type: integer + sidecarVolumes: + items: + properties: + awsElasticBlockStore: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + azureDisk: + properties: + cachingMode: + type: string + diskName: + type: string + diskURI: + type: string + fsType: + default: ext4 + type: string + kind: + type: string + readOnly: + default: false + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + properties: + readOnly: + type: boolean + secretName: + type: string + shareName: + type: string + required: + - secretName + - shareName + type: object + cephfs: + properties: + monitors: + items: + type: string + type: array + x-kubernetes-list-type: atomic + path: + type: string + readOnly: + type: boolean + secretFile: + type: string + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + user: + type: string + required: + - monitors + type: object + cinder: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + volumeID: + type: string + required: + - volumeID + type: object + configMap: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + csi: + properties: + driver: + type: string + fsType: + type: string + nodePublishSecretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: + type: boolean + volumeAttributes: + additionalProperties: + type: string + type: object + required: + - driver + type: object + downwardAPI: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + emptyDir: + properties: + medium: + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + properties: + volumeClaimTemplate: + properties: + metadata: + type: object + spec: + properties: + accessModes: + items: + type: string + type: array + x-kubernetes-list-type: atomic + dataSource: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + dataSourceRef: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + required: + - kind + - name + type: object + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + storageClassName: + type: string + volumeAttributesClassName: + type: string + volumeMode: + type: string + volumeName: + type: string + type: object + required: + - spec + type: object + type: object + fc: + properties: + fsType: + type: string + lun: + format: int32 + type: integer + readOnly: + type: boolean + targetWWNs: + items: + type: string + type: array + x-kubernetes-list-type: atomic + wwids: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + flexVolume: + properties: + driver: + type: string + fsType: + type: string + options: + additionalProperties: + type: string + type: object + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + required: + - driver + type: object + flocker: + properties: + datasetName: + type: string + datasetUUID: + type: string + type: object + gcePersistentDisk: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + pdName: + type: string + readOnly: + type: boolean + required: + - pdName + type: object + gitRepo: + properties: + directory: + type: string + repository: + type: string + revision: + type: string + required: + - repository + type: object + glusterfs: + properties: + endpoints: + type: string + path: + type: string + readOnly: + type: boolean + required: + - endpoints + - path + type: object + hostPath: + properties: + path: + type: string + type: + type: string + required: + - path + type: object + image: + properties: + pullPolicy: + type: string + reference: + type: string + type: object + iscsi: + properties: + chapAuthDiscovery: + type: boolean + chapAuthSession: + type: boolean + fsType: + type: string + initiatorName: + type: string + iqn: + type: string + iscsiInterface: + default: default + type: string + lun: + format: int32 + type: integer + portals: + items: + type: string + type: array + x-kubernetes-list-type: atomic + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + targetPortal: + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + type: string + nfs: + properties: + path: + type: string + readOnly: + type: boolean + server: + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + properties: + claimName: + type: string + readOnly: + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + properties: + fsType: + type: string + pdID: + type: string + required: + - pdID + type: object + portworxVolume: + properties: + fsType: + type: string + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + projected: + properties: + defaultMode: + format: int32 + type: integer + sources: + items: + properties: + clusterTrustBundle: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + name: + type: string + optional: + type: boolean + path: + type: string + signerName: + type: string + required: + - path + type: object + configMap: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + downwardAPI: + properties: + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + secret: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + serviceAccountToken: + properties: + audience: + type: string + expirationSeconds: + format: int64 + type: integer + path: + type: string + required: + - path + type: object + type: object + type: array + x-kubernetes-list-type: atomic + type: object + quobyte: + properties: + group: + type: string + readOnly: + type: boolean + registry: + type: string + tenant: + type: string + user: + type: string + volume: + type: string + required: + - registry + - volume + type: object + rbd: + properties: + fsType: + type: string + image: + type: string + keyring: + default: /etc/ceph/keyring + type: string + monitors: + items: + type: string + type: array + x-kubernetes-list-type: atomic + pool: + default: rbd + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + user: + default: admin + type: string + required: + - image + - monitors + type: object + scaleIO: + properties: + fsType: + default: xfs + type: string + gateway: + type: string + protectionDomain: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + sslEnabled: + type: boolean + storageMode: + default: ThinProvisioned + type: string + storagePool: + type: string + system: + type: string + volumeName: + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + optional: + type: boolean + secretName: + type: string + type: object + storageos: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + volumeName: + type: string + volumeNamespace: + type: string + type: object + vsphereVolume: + properties: + fsType: + type: string + storagePolicyID: + type: string + storagePolicyName: + type: string + volumePath: + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + sidecars: + items: + properties: + args: + items: + type: string + type: array + x-kubernetes-list-type: atomic + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + envFrom: + items: + properties: + configMapRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + type: string + secretRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array + x-kubernetes-list-type: atomic + image: + type: string + imagePullPolicy: + type: string + lifecycle: + properties: + postStart: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + name: + type: string + ports: + items: + properties: + containerPort: + format: int32 + type: integer + hostIP: + type: string + hostPort: + format: int32 + type: integer + name: + type: string + protocol: + default: TCP + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + resizePolicy: + items: + properties: + resourceName: + type: string + restartPolicy: + type: string + required: + - resourceName + - restartPolicy + type: object + type: array + x-kubernetes-list-type: atomic + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + restartPolicy: + type: string + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + capabilities: + properties: + add: + items: + type: string + type: array + x-kubernetes-list-type: atomic + drop: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + startupProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + stdin: + type: boolean + stdinOnce: + type: boolean + terminationMessagePath: + type: string + terminationMessagePolicy: + type: string + tty: + type: boolean + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + required: + - devicePath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - devicePath + x-kubernetes-list-type: map + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + recursiveReadOnly: + type: string + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - mountPath + x-kubernetes-list-type: map + workingDir: + type: string + required: + - name + type: object + type: array + type: object + status: + properties: + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + maxLength: 32768 + type: string + observedGeneration: + format: int64 + minimum: 0 + type: integer + reason: + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + enum: + - "True" + - "False" + - Unknown + type: string + type: + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + exporterConfig: + type: string + replicas: + type: integer + status: + type: string + version: + type: string + required: + - conditions + - exporterConfig + - version + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/bundle/manifests/oracle-database-operator-controller-manager-metrics-service_v1_service.yaml b/bundle/manifests/oracle-database-operator-controller-manager-metrics-service_v1_service.yaml new file mode 100644 index 00000000..ea25c27f --- /dev/null +++ b/bundle/manifests/oracle-database-operator-controller-manager-metrics-service_v1_service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + creationTimestamp: null + labels: + control-plane: controller-manager + name: oracle-database-operator-controller-manager-metrics-service +spec: + ports: + - name: https + port: 8443 + targetPort: https + selector: + control-plane: controller-manager +status: + loadBalancer: {} diff --git a/bundle/manifests/oracle-database-operator-metrics-reader_rbac.authorization.k8s.io_v1_clusterrole.yaml b/bundle/manifests/oracle-database-operator-metrics-reader_rbac.authorization.k8s.io_v1_clusterrole.yaml new file mode 100644 index 00000000..f479f494 --- /dev/null +++ b/bundle/manifests/oracle-database-operator-metrics-reader_rbac.authorization.k8s.io_v1_clusterrole.yaml @@ -0,0 +1,10 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + creationTimestamp: null + name: oracle-database-operator-metrics-reader +rules: +- nonResourceURLs: + - /metrics + verbs: + - get diff --git a/bundle/manifests/oracle-database-operator-webhook-service_v1_service.yaml b/bundle/manifests/oracle-database-operator-webhook-service_v1_service.yaml new file mode 100644 index 00000000..53f17480 --- /dev/null +++ b/bundle/manifests/oracle-database-operator-webhook-service_v1_service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + creationTimestamp: null + name: oracle-database-operator-webhook-service +spec: + ports: + - port: 443 + protocol: TCP + targetPort: 9443 + selector: + control-plane: controller-manager +status: + loadBalancer: {} diff --git a/bundle/manifests/oracle-database-operator.clusterserviceversion.yaml b/bundle/manifests/oracle-database-operator.clusterserviceversion.yaml new file mode 100644 index 00000000..b6299ff7 --- /dev/null +++ b/bundle/manifests/oracle-database-operator.clusterserviceversion.yaml @@ -0,0 +1,1442 @@ +apiVersion: operators.coreos.com/v1alpha1 +kind: ClusterServiceVersion +metadata: + annotations: + alm-examples: |- + [ + { + "apiVersion": "database.oracle.com/v1alpha1", + "kind": "CDB", + "metadata": { + "name": "cdb-dev", + "namespace": "oracle-database-operator-system" + }, + "spec": { + "cdbAdminPwd": { + "secret": { + "key": "cdbadmin_pwd", + "secretName": "cdb1-secret" + } + }, + "cdbAdminUser": { + "secret": { + "key": "cdbadmin_user", + "secretName": "cdb1-secret" + } + }, + "cdbName": "devcdb", + "dbPort": 1521, + "dbServer": "172.17.0.4", + "ordsImage": "", + "ordsImagePullPolicy": "Always", + "ordsPwd": { + "secret": { + "key": "ords_pwd", + "secretName": "cdb1-secret" + } + }, + "replicas": 1, + "serviceName": "devdb.example.com", + "sysAdminPwd": { + "secret": { + "key": "sysadmin_pwd", + "secretName": "cdb1-secret" + } + }, + "webServerPwd": { + "secret": { + "key": "webserver_pwd", + "secretName": "cdb1-secret" + } + }, + "webServerUser": { + "secret": { + "key": "webserver_user", + "secretName": "cdb1-secret" + } + } + } + }, + { + "apiVersion": "database.oracle.com/v1alpha1", + "kind": "DbcsSystem", + "metadata": { + "name": "dbcssystem-sample" + }, + "spec": { + "foo": "bar" + } + }, + { + "apiVersion": "database.oracle.com/v1alpha1", + "kind": "PDB", + "metadata": { + "labels": { + "cdb": "cdb-dev" + }, + "name": "pdb1", + "namespace": "oracle-database-operator-system" + }, + "spec": { + "action": "Create", + "adminName": { + "secret": { + "key": "sysadmin_user", + "secretName": "pdb1-secret" + } + }, + "adminPwd": { + "secret": { + "key": "sysadmin_pwd", + "secretName": "pdb1-secret" + } + }, + "cdbName": "devcdb", + "cdbResName": "cdb-dev", + "fileNameConversions": "NONE", + "pdbName": "pdbdev", + "tempSize": "100M", + "totalSize": "1G" + } + }, + { + "apiVersion": "database.oracle.com/v4", + "kind": "AutonomousContainerDatabase", + "metadata": { + "name": "autonomouscontainerdatabase-sample" + }, + "spec": { + "autonomousExadataVMClusterOCID": "ocid1.autonomousexainfrastructure...", + "compartmentOCID": "ocid1.compartment... OR ocid1.tenancy...", + "displayName": "newACD", + "ociConfig": { + "configMapName": "oci-cred", + "secretName": "oci-privatekey" + } + } + }, + { + "apiVersion": "database.oracle.com/v4", + "kind": "AutonomousDatabase", + "metadata": { + "name": "autonomousdatabase-sample" + }, + "spec": { + "action": "Create", + "details": { + "adminPassword": { + "k8sSecret": { + "name": "admin-password" + } + }, + "compartmentId": "ocid1.compartment... OR ocid1.tenancy...", + "cpuCoreCount": 1, + "dataStorageSizeInTBs": 1, + "dbName": "NewADB", + "displayName": "NewADB" + }, + "ociConfig": { + "configMapName": "oci-cred", + "secretName": "oci-privatekey" + } + } + }, + { + "apiVersion": "database.oracle.com/v4", + "kind": "AutonomousDatabaseBackup", + "metadata": { + "name": "autonomousdatabasebackup-sample" + }, + "spec": { + "displayName": "autonomousdatabasebackup-sample", + "isLongTermBackup": true, + "ociConfig": { + "configMapName": "oci-cred", + "secretName": "oci-privatekey" + }, + "retentionPeriodInDays": 90, + "target": { + "k8sADB": { + "name": "autonomousdatabase-sample" + } + } + } + }, + { + "apiVersion": "database.oracle.com/v4", + "kind": "SingleInstanceDatabase", + "metadata": { + "name": "sidb-sample", + "namespace": "default" + }, + "spec": { + "adminPassword": { + "secretName": "db-admin-secret" + }, + "archiveLog": true, + "charset": "AL32UTF8", + "edition": "enterprise", + "image": { + "pullFrom": "container-registry.oracle.com/database/enterprise_ru:19", + "pullSecrets": "oracle-container-registry-secret" + }, + "pdbName": "orclpdb1", + "persistence": { + "accessMode": "ReadWriteOnce", + "size": "100Gi", + "storageClass": "oci-bv" + }, + "replicas": 1, + "sid": "ORCL1" + } + }, + { + "apiVersion": "observability.oracle.com/v1alpha1", + "kind": "DatabaseObserver", + "metadata": { + "name": "obs-sample" + }, + "spec": { + "database": { + "dbConnectionString": { + "key": "connection", + "secret": "db-secret" + }, + "dbPassword": { + "key": "password", + "secret": "db-secret" + }, + "dbUser": { + "key": "username", + "secret": "db-secret" + }, + "dbWallet": { + "secret": "instance-wallet" + } + }, + "exporter": { + "configuration": { + "configmap": { + "configmapName": "devcm-oradevdb-config", + "key": "config.toml" + } + }, + "image": "container-registry.oracle.com/database/observability-exporter:latest", + "service": { + "port": 9161 + } + }, + "ociConfig": { + "configMapName": "oci-cred", + "secretName": "oci-privatekey" + }, + "prometheus": { + "labels": { + "app": "app-sample-label" + }, + "port": "metrics" + }, + "replicas": 1 + } + } + ] + capabilities: Seamless Upgrades + categories: Database + containerImage: container-registry.oracle.com/database/operator:1.2.0 + createdAt: "2025-04-10T20:09:48Z" + operators.operatorframework.io/builder: operator-sdk-v1.39.2 + operators.operatorframework.io/project_layout: go.kubebuilder.io/v4 + name: oracle-database-operator.v2.0 + namespace: oracle-database-operator-system +spec: + apiservicedefinitions: {} + customresourcedefinitions: + owned: + - description: AutonomousContainerDatabase is the Schema for the autonomouscontainerdatabases + API + displayName: Autonomous Container Database + kind: AutonomousContainerDatabase + name: autonomouscontainerdatabases.database.oracle.com + version: v1alpha1 + - description: AutonomousContainerDatabase is the Schema for the autonomouscontainerdatabases + API + displayName: Autonomous Container Database + kind: AutonomousContainerDatabase + name: autonomouscontainerdatabases.database.oracle.com + version: v4 + - description: AutonomousDatabaseBackup is the Schema for the autonomousdatabasebackups + API + displayName: Autonomous Database Backup + kind: AutonomousDatabaseBackup + name: autonomousdatabasebackups.database.oracle.com + version: v1alpha1 + - description: AutonomousDatabaseBackup is the Schema for the autonomousdatabasebackups + API + displayName: Autonomous Database Backup + kind: AutonomousDatabaseBackup + name: autonomousdatabasebackups.database.oracle.com + version: v4 + - description: AutonomousDatabaseRestore is the Schema for the autonomousdatabaserestores + API + displayName: Autonomous Database Restore + kind: AutonomousDatabaseRestore + name: autonomousdatabaserestores.database.oracle.com + version: v1alpha1 + - description: AutonomousDatabaseRestore is the Schema for the autonomousdatabaserestores + API + displayName: Autonomous Database Restore + kind: AutonomousDatabaseRestore + name: autonomousdatabaserestores.database.oracle.com + version: v4 + - description: AutonomousDatabase is the Schema for the autonomousdatabases API + displayName: Autonomous Database + kind: AutonomousDatabase + name: autonomousdatabases.database.oracle.com + version: v1alpha1 + - description: AutonomousDatabase is the Schema for the autonomousdatabases API + displayName: Autonomous Database + kind: AutonomousDatabase + name: autonomousdatabases.database.oracle.com + version: v4 + - description: CDB is the Schema for the cdbs API + displayName: CDB + kind: CDB + name: cdbs.database.oracle.com + version: v1alpha1 + - description: CDB is the Schema for the cdbs API + displayName: CDB + kind: CDB + name: cdbs.database.oracle.com + version: v4 + - description: DatabaseObserver is the Schema for the databaseobservers API + displayName: Database Observer + kind: DatabaseObserver + name: databaseobservers.observability.oracle.com + version: v1 + - description: DatabaseObserver is the Schema for the databaseobservers API + displayName: Database Observer + kind: DatabaseObserver + name: databaseobservers.observability.oracle.com + version: v1alpha1 + - description: DatabaseObserver is the Schema for the databaseobservers API + displayName: Database Observer + kind: DatabaseObserver + name: databaseobservers.observability.oracle.com + version: v4 + - description: DataguardBroker is the Schema for the dataguardbrokers API + displayName: Dataguard Broker + kind: DataguardBroker + name: dataguardbrokers.database.oracle.com + version: v1alpha1 + - description: DataguardBroker is the Schema for the dataguardbrokers API + displayName: Dataguard Broker + kind: DataguardBroker + name: dataguardbrokers.database.oracle.com + version: v4 + - description: DbcsSystem is the Schema for the dbcssystems API + displayName: Dbcs System + kind: DbcsSystem + name: dbcssystems.database.oracle.com + version: v1alpha1 + - description: DbcsSystem is the Schema for the dbcssystems API + displayName: Dbcs System + kind: DbcsSystem + name: dbcssystems.database.oracle.com + version: v4 + - description: LREST is the Schema for the lrests API + displayName: LREST + kind: LREST + name: lrests.database.oracle.com + version: v4 + - description: LRPDB is the Schema for the pdbs API + displayName: LRPDB + kind: LRPDB + name: lrpdbs.database.oracle.com + version: v4 + - description: OracleRestDataService is the Schema for the oraclerestdataservices + API + displayName: Oracle Rest Data Service + kind: OracleRestDataService + name: oraclerestdataservices.database.oracle.com + version: v1alpha1 + - description: OracleRestDataService is the Schema for the oraclerestdataservices + API + displayName: Oracle Rest Data Service + kind: OracleRestDataService + name: oraclerestdataservices.database.oracle.com + version: v4 + - description: OrdsSrvs is the Schema for the ordssrvs API + displayName: Ords Srvs + kind: OrdsSrvs + name: ordssrvs.database.oracle.com + statusDescriptors: + - displayName: Conditions + path: conditions + version: v4 + - description: PDB is the Schema for the pdbs API + displayName: PDB + kind: PDB + name: pdbs.database.oracle.com + version: v1alpha1 + - description: PDB is the Schema for the pdbs API + displayName: PDB + kind: PDB + name: pdbs.database.oracle.com + version: v4 + - description: ShardingDatabase is the Schema for the shardingdatabases API + displayName: Sharding Database + kind: ShardingDatabase + name: shardingdatabases.database.oracle.com + version: v1alpha1 + - description: ShardingDatabase is the Schema for the shardingdatabases API + displayName: Sharding Database + kind: ShardingDatabase + name: shardingdatabases.database.oracle.com + version: v4 + - description: SingleInstanceDatabase is the Schema for the singleinstancedatabases + API + displayName: Single Instance Database + kind: SingleInstanceDatabase + name: singleinstancedatabases.database.oracle.com + version: v1alpha1 + - description: SingleInstanceDatabase is the Schema for the singleinstancedatabases + API + displayName: Single Instance Database + kind: SingleInstanceDatabase + name: singleinstancedatabases.database.oracle.com + version: v4 + description: | + As part of Oracle's resolution to make Oracle Database Kubernetes native (that is, observable and operable by Kubernetes), Oracle released Oracle Database Operator for Kubernetes (OraOperator or the operator). OraOperator extends the Kubernetes API with custom resources and controllers for automating Oracle Database lifecycle management. + In this v1.2.0 production release, OraOperator supports the following database configurations and infrastructure: + ##Supported Database Configurations: + * Oracle Cloud Infrastructure (OCI) Databases + * Oracle Autonomous Database Serverless (ADB-S) + * Oracle Autonomous Database on Dedicated on (ADB-D) + * Oracle Base Database Cloud Service (Base DB) + * Containerized Database deployments on Kubernetes + * Oracle Kubernetes Engine (OKE) and Oracle Cloud Native Environments (OCNE) + * Single Instance databases (SIDB) + * Globally Distributed Databases (Shared) + * Oracle Data Guard + * True Cache (Preview release) + * Oracle Database Observability + * On-Premises Databases + * Oracle Multitenant Databases (CDB/PDBs management) + * ORDS Services + + Refer to full feature list and capabilities: [https://github.com/oracle/oracle-database-operator/blob/main/README.md](https://github.com/oracle/oracle-database-operator/blob/main/README.md) + displayName: Oracle Database Operator + icon: + - base64data: iVBORw0KGgoAAAANSUhEUgAAALQAAAC0CAYAAAA9zQYyAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QAAAAAAAD5Q7t/AAAJjUlEQVR42u3cfcwcRQHH8S9PH0BokZfCVBgpOgjyFjRoQIQQkLeA0PLWqgQMFDVgja9AChIKKCEKSgQEQVsQJGKxtNCAvAi2vJiCqAQMUpQRMKM4vFiCQEUo/jH7kOt19m7vbveK8fdJLukzMzuzczc7OzszWxAREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREZH/X2tVSRStmwi8B5gErN1nWS8DAVhmgl9ZsdwpVc+xoteKc/iTCf7VujKN1o0A+xef5cDPTfCP1XjeY+VsAWwFTATGDZjdPSb4F6J1U9sjTPA31n3uXeq1MfBe4F30376ADo0lWjcBOAa4EHhHzXW4FzgDuNsE/2aHc3gJmFBz2WMuBc4ywT87SCbRunWAB4APtEV92gR/zaAnGa2bBJwInFVz/SeY4F+O1q32/Zvg6+xEyuq1PnA08F1gg7ryHSkpbDrwEnAZ9TdmgD2BxcDj0bptG8i/is8DMVq394D5zGb1xgxwdbRum34zjdaNi9adAjxD/Y15PxP8yzXn2UvdDiPdsX9IjY0ZOHOVKzFaNw64Apgx5DoelrvNNdxDt9rDBP/rXg+K1u0EPNIhyWPADp3uQiX5bggsIX+hDOp6E/z0lrKG1kMXQ7OLgJkNZL8SWO+tHjpatxbwM4bfmAEWRuuOWAPljrmvGGJVVlz8d3ZJth1wXI/5jgeW0UxjhnRnGrqifV1FM40ZYDcT/GujLQGnAUd2OGAu6Qd8oY/CxgGTgWOB3UvSzI/W7WiCf7RLXgcNUOnJwOUlcScCF/SQ10mAqZBubrTuVhP837sljNZB6lQmdUh2AbCUdMvu1Ssm+Of6OK4OXyb9/mV+DNxOf+0LE/yDUDwURuu2A/5YknYWcLEJ/pU6ahWt2wq4FtgjE/08YMZmQUqGHHua4O8boPx1gNuAvTPRI1WGB0UdnsxEPQzsnAm/HTjQBN8t36nAwpLoTwDzTfBv9Fv3kjIbH3JE6xzwREn0bOA7dY3px4Ycl5bEH2CC/1ZdjRnABP8UsBfwg0z0RODgusoqKf810uxNzsbdjm/pRXOOIP1A7Q4ADu2S7yjljXl7E/y8uhvzEF1YEj7FBH9OnQ+oI9G6zYF9MnEzTfB3NFG7ogf+ApDrsr7XRJlt/lYSvmGFY6cDu2XCZ5ngnwC+XXLcTcXDXpmyodheTcxpD0u0bjNgSibqZBP8orrLGyH1HjlXNFnRorc5KhPlioWcJo2WhL/e6aBo3abke+eVFL2QCX4F+eEMpDnXMsdnwu4ywd/T8HfRtLLv4pImChsFDsmEn2aCf73XzPrwUEn4+4Gep9F68LGS8G4PTN8vCf9oMZQBwAS/JFp3PTCtLd2MaN3ckmeAXIP+ZoPfwbCUDSFXFMO3Om03QlrkaLd4GDUtHsByPd7WTZUZrdsLuDUTdX+n5fBo3b6k4Ua7K0zw92fCy6an7o3WVV2seqRiurez3QfPopI5Jvhlo+SniPqaOunT05mwjmPZaN2WwKPAmy0fOvw99u/NOmR7aofyxgO/7OU4E/yz0brjSHOv7c4oPt3U9jC+Bk0aPItKvgZpDJ27zVZ5OKrLFpmwlzodYIL/K2k+egPgncX5bghsRJqp2KT4TAQ2LT6dGvONJvi7O8R/oyR8qgn+xQ7HXUP+wffrxSpjN+v195W+rQxj3vuQsd9hhDRJ326P3vLrTzGGOjoT9Zdux5rg7yWtxA3qsZJzGDvHXYCvZKIWAzd1OceVlE/X3VmsNnayfQ31W9MeaDj/W0zwN4/9MQosYvUv/YJo3UVVt3kOYAfyO/4qTVOZ4JcVu9EeAjbvo/xLSNNH/85FRuvWBsp67guBKRUfbM4jrcSucvqk1caxp/3rgE+2pfkqaWfi/7JfkO8w1m5i4mGUtGrWbhzwKdKKXiOKtf0rM1HPAbFqPib4GK17H2lRYv8Kh7xIWkj6kQned0n7JWB8JnwmaSfiFgzm4mjdomKxaQ6rN+jDo3UfMsH/dsBy1qSy/S4zaGBqeMQE/zTwu0zcT6J1u/WaYRVFr3YOsGsmema3JeJ2xUrmQcDFHZKdU9R3IxP86d0ac7Rua+D8XBRpP0iVi6eKecXFXXYneDBaN7mmsoau2MOyJBN1eTHjVKuxpe/PlMQvjdadUGGsV1mxcjSP8qf8Bf3ka4J/wwT/RdImmJwzSbv6uj5oFQ1sYUn0fkVZjwJn1/CV7ApM77Ik/1S0rq4LaE0o2+G3JFp3UjG0q8Vb49do3fnAyR3SngvcQ3rFqFfjAEtaaJjWId2uJvjftJxTX5uTonWHAzeURQMf7LT7LVp3LHB1Jup8E/ypLenWBVb08X3kbEbanLWU/J0L0uzP2aQ76qBTeq+a4B/ObU4CPlJTnVodTOpUypxH6smXD1JIa4MeBX5FfqFlGGaY4FcZUw+y264YLi3tkGQXE/zvM8dNIr0lkjO+faNWtG5n0sXai1syYfNN8EdF6zYhNeymHWmCv6GkQdftKtIo4GbgwCYLan9jZV1gPvDxIVSy1fEm+KvaAwfdPlqMg//cIcnhJviFbccsIr8dYD8TfLcN/ZVE6y4HPpeJOsAEf0dxUf2BNH/ehMXAPiZ4htSgNzbBLy+27l5Lfg9PLVZ5p7CYvjqU4b3V8B/gw7nGXIdi99umlO/FXRCtmzU29RatO4h8Y15QV2MuzCoJvz1aN8EE/w9gS3p74aAXx/T64D2AKSb45fDW1t1plD+zDWrf1V6SNcG/aYK/jDSmm917npU8Tdo7vH7TU1Im+OeBnShfBDkP+Gm0bgPyQwFIb7PUeU7/pHwx59wizQoT/CmAI793vF+fNcGHlr+P7zun7m4jrXO01h0T/BzSKu7pNZZ1nQn+rqr/L8dkYFvSvGu/T6T/Ap4CHjfBV9orEq07IRN8pwn+yV4LL17Q7PTj3UV+F95zTfw/FcVMSvb9zeIHz53/1sA2pEWZfmeermxfMIvW7Uh6EB3pL8tSN5vgn+mWqNibsw3wbvpvXwuqtisREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREWnxX2ox1/vZSvwPAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDI0LTA4LTEzVDE5OjUyOjMxKzAwOjAwsDIMcAAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyNC0wOC0xM1QxOTo1MjozMSswMDowMMFvtMwAAABVdEVYdHN2Zzpjb21tZW50ACBVcGxvYWRlZCB0bzogU1ZHIFJlcG8sIHd3dy5zdmdyZXBvLmNvbSwgR2VuZXJhdG9yOiBTVkcgUmVwbyBNaXhlciBUb29scyBFB1wTAAAAAElFTkSuQmCC + mediatype: image/png + install: + spec: + clusterPermissions: + - rules: + - apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create + - apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create + serviceAccountName: default + deployments: + - label: + control-plane: controller-manager + name: oracle-database-operator-controller-manager + spec: + replicas: 3 + selector: + matchLabels: + control-plane: controller-manager + strategy: {} + template: + metadata: + labels: + control-plane: controller-manager + spec: + containers: + - args: + - --enable-leader-election + command: + - /manager + env: + - name: WATCH_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.annotations['olm.targetNamespaces'] + image: container-registry.oracle.com/database/operator:1.2.0 + imagePullPolicy: Always + name: manager + ports: + - containerPort: 9443 + name: webhook-server + protocol: TCP + resources: + limits: + cpu: 400m + memory: 400Mi + requests: + cpu: 400m + memory: 400Mi + volumeMounts: + - mountPath: /tmp/k8s-webhook-server/serving-certs + name: cert + readOnly: true + terminationGracePeriodSeconds: 10 + volumes: + - name: cert + secret: + defaultMode: 420 + secretName: webhook-server-cert + permissions: + - rules: + - apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + - apiGroups: + - "" + resources: + - configmaps/status + verbs: + - get + - update + - patch + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - apiGroups: + - "" + resources: + - configmaps + - containers + - deployments + - events + - namespaces + - persistentvolumeclaims + - pods + - pods/exec + - pods/log + - replicasets + - secrets + - services + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - "" + resources: + - configmaps/status + - daemonsets/status + - deployments/status + - services/status + - statefulsets/status + verbs: + - get + - patch + - update + - apiGroups: + - "" + resources: + - persistentvolumes + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - secrets/status + verbs: + - get + - apiGroups: + - '''''' + resources: + - statefulsets/finalizers + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - apps + resources: + - configmaps + verbs: + - get + - list + - apiGroups: + - apps + resources: + - daemonsets + - deployments + - pods + - replicasets + - services + - statefulsets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create + - get + - list + - update + - apiGroups: + - database.oracle.com + resources: + - autonomouscontainerdatabases + - autonomousdatabases + - cdbs + - dataguardbrokers + - dbcssystems + - events + - lrests + - lrpdbs + - oraclerestdataservices + - ordssrvs + - pdbs + - shardingdatabases + - singleinstancedatabases + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - database.oracle.com + resources: + - autonomouscontainerdatabases/status + - autonomousdatabasebackups/status + - autonomousdatabaserestores/status + - cdbs/status + - dataguardbrokers/status + - dbcssystems/status + - lrests/status + - lrpdbs/status + - oraclerestdataservices/status + - ordssrvs/status + - pdbs/status + - shardingdatabases/status + - singleinstancedatabases/status + verbs: + - get + - patch + - update + - apiGroups: + - database.oracle.com + resources: + - autonomousdatabasebackups + - autonomousdatabaserestores + verbs: + - create + - delete + - get + - list + - update + - watch + - apiGroups: + - database.oracle.com + resources: + - autonomousdatabases/status + verbs: + - patch + - update + - apiGroups: + - database.oracle.com + resources: + - cdbs/finalizers + - dataguardbrokers/finalizers + - lrests/finalizers + - oraclerestdataservices/finalizers + - ordssrvs/finalizers + - singleinstancedatabases/finalizers + verbs: + - update + - apiGroups: + - database.oracle.com + resources: + - dbcssystems/finalizers + - lrpdbs/finalizers + - pdbs/finalizers + - shardingdatabases/finalizers + verbs: + - create + - delete + - get + - patch + - update + - apiGroups: + - monitoring.coreos.com + resources: + - servicemonitors + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - observability.oracle.com + resources: + - databaseobservers + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - observability.oracle.com + resources: + - databaseobservers/finalizers + verbs: + - update + - apiGroups: + - observability.oracle.com + resources: + - databaseobservers/status + verbs: + - get + - patch + - update + - apiGroups: + - storage.k8s.io + resources: + - storageclasses + verbs: + - get + - list + - watch + serviceAccountName: default + strategy: deployment + installModes: + - supported: false + type: OwnNamespace + - supported: false + type: SingleNamespace + - supported: false + type: MultiNamespace + - supported: true + type: AllNamespaces + keywords: + - Oracle + - Database + - Operator + links: + - name: Oracle Database Operator + url: https://github.com/oracle/oracle-database-operator + maturity: alpha + provider: + name: Oracle + replaces: oracle-database-operator.v1.1.0 + version: 1.2.0 + webhookdefinitions: + - admissionReviewVersions: + - v1alpha1 + - v1 + - v4 + containerPort: 443 + conversionCRDs: + - autonomouscontainerdatabases.database.oracle.com + - autonomousdatabasebackups.database.oracle.com + - autonomousdatabaserestores.database.oracle.com + - autonomousdatabases.database.oracle.com + deploymentName: oracle-database-operator-controller-manager + generateName: cautonomouscontainerdatabasesautonomousdatabasebackupsautonomousdatabaserestoresautonomousdatabases.kb.io + sideEffects: None + targetPort: 9443 + type: ConversionWebhook + webhookPath: /convert + - admissionReviewVersions: + - v1 + containerPort: 443 + deploymentName: oracle-database-operator-controller-manager + failurePolicy: Fail + generateName: mautonomousdatabasebackupv1alpha1.kb.io + rules: + - apiGroups: + - database.oracle.com + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - autonomousdatabasebackups + sideEffects: None + targetPort: 9443 + type: MutatingAdmissionWebhook + webhookPath: /mutate-database-oracle-com-v1alpha1-autonomousdatabasebackup + - admissionReviewVersions: + - v1 + containerPort: 443 + deploymentName: oracle-database-operator-controller-manager + failurePolicy: Fail + generateName: mautonomousdatabasebackupv4.kb.io + rules: + - apiGroups: + - database.oracle.com + apiVersions: + - v4 + operations: + - CREATE + - UPDATE + resources: + - autonomousdatabasebackups + sideEffects: None + targetPort: 9443 + type: MutatingAdmissionWebhook + webhookPath: /mutate-database-oracle-com-v4-autonomousdatabasebackup + - admissionReviewVersions: + - v1 + - v1beta1 + containerPort: 443 + deploymentName: oracle-database-operator-controller-manager + failurePolicy: Fail + generateName: mcdb.kb.io + rules: + - apiGroups: + - database.oracle.com + apiVersions: + - v4 + operations: + - CREATE + - UPDATE + resources: + - cdbs + sideEffects: None + targetPort: 9443 + type: MutatingAdmissionWebhook + webhookPath: /mutate-database-oracle-com-v4-cdb + - admissionReviewVersions: + - v1 + containerPort: 443 + deploymentName: oracle-database-operator-controller-manager + failurePolicy: Fail + generateName: mdatabaseobserver.kb.io + rules: + - apiGroups: + - observability.oracle.com + apiVersions: + - v4 + operations: + - CREATE + - UPDATE + resources: + - databaseobservers + sideEffects: None + targetPort: 9443 + type: MutatingAdmissionWebhook + webhookPath: /mutate-observability-oracle-com-v4-databaseobserver + - admissionReviewVersions: + - v1 + - v1beta1 + containerPort: 443 + deploymentName: oracle-database-operator-controller-manager + failurePolicy: Fail + generateName: mdataguardbroker.kb.io + rules: + - apiGroups: + - database.oracle.com + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - dataguardbrokers + sideEffects: None + targetPort: 9443 + type: MutatingAdmissionWebhook + webhookPath: /mutate-database-oracle-com-v1alpha1-dataguardbroker + - admissionReviewVersions: + - v1 + containerPort: 443 + deploymentName: oracle-database-operator-controller-manager + failurePolicy: Fail + generateName: mdbcssystemv1alpha1.kb.io + rules: + - apiGroups: + - database.oracle.com + apiVersions: + - v4 + operations: + - CREATE + - UPDATE + resources: + - dbcssystems + sideEffects: None + targetPort: 9443 + type: MutatingAdmissionWebhook + webhookPath: /mutate-database-oracle-com-v4-dbcssystem + - admissionReviewVersions: + - v1 + containerPort: 443 + deploymentName: oracle-database-operator-controller-manager + failurePolicy: Fail + generateName: mdbcssystemv4.kb.io + rules: + - apiGroups: + - database.oracle.com + apiVersions: + - v4 + operations: + - CREATE + - UPDATE + resources: + - dbcssystems + sideEffects: None + targetPort: 9443 + type: MutatingAdmissionWebhook + webhookPath: /mutate-database-oracle-com-v4-dbcssystem + - admissionReviewVersions: + - v4 + - v1beta1 + containerPort: 443 + deploymentName: oracle-database-operator-controller-manager + failurePolicy: Fail + generateName: mlrest.kb.io + rules: + - apiGroups: + - database.oracle.com + apiVersions: + - v4 + operations: + - CREATE + - UPDATE + resources: + - lrests + sideEffects: None + targetPort: 9443 + type: MutatingAdmissionWebhook + webhookPath: /mutate-database-oracle-com-v4-lrest + - admissionReviewVersions: + - v4 + - v1beta1 + containerPort: 443 + deploymentName: oracle-database-operator-controller-manager + failurePolicy: Fail + generateName: mlrpdb.kb.io + rules: + - apiGroups: + - database.oracle.com + apiVersions: + - v4 + operations: + - CREATE + - UPDATE + resources: + - lrpdbs + sideEffects: None + targetPort: 9443 + type: MutatingAdmissionWebhook + webhookPath: /mutate-database-oracle-com-v4-lrpdb + - admissionReviewVersions: + - v1 + - v1beta1 + containerPort: 443 + deploymentName: oracle-database-operator-controller-manager + failurePolicy: Fail + generateName: moraclerestdataservice.kb.io + rules: + - apiGroups: + - database.oracle.com + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - oraclerestdataservices + sideEffects: None + targetPort: 9443 + type: MutatingAdmissionWebhook + webhookPath: /mutate-database-oracle-com-v1alpha1-oraclerestdataservice + - admissionReviewVersions: + - v1 + - v1beta1 + containerPort: 443 + deploymentName: oracle-database-operator-controller-manager + failurePolicy: Fail + generateName: mpdb.kb.io + rules: + - apiGroups: + - database.oracle.com + apiVersions: + - v4 + operations: + - CREATE + - UPDATE + resources: + - pdbs + sideEffects: None + targetPort: 9443 + type: MutatingAdmissionWebhook + webhookPath: /mutate-database-oracle-com-v4-pdb + - admissionReviewVersions: + - v1 + containerPort: 443 + deploymentName: oracle-database-operator-controller-manager + failurePolicy: Fail + generateName: mshardingdatabasev1alpha1.kb.io + rules: + - apiGroups: + - database.oracle.com + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - shardingdatabases + sideEffects: None + targetPort: 9443 + type: MutatingAdmissionWebhook + webhookPath: /mutate-database-oracle-com-v1alpha1-shardingdatabase + - admissionReviewVersions: + - v1 + containerPort: 443 + deploymentName: oracle-database-operator-controller-manager + failurePolicy: Fail + generateName: mshardingdatabasev4.kb.io + rules: + - apiGroups: + - database.oracle.com + apiVersions: + - v4 + operations: + - CREATE + - UPDATE + resources: + - shardingdatabases + sideEffects: None + targetPort: 9443 + type: MutatingAdmissionWebhook + webhookPath: /mutate-database-oracle-com-v4-shardingdatabase + - admissionReviewVersions: + - v1 + - v1beta1 + containerPort: 443 + deploymentName: oracle-database-operator-controller-manager + failurePolicy: Fail + generateName: msingleinstancedatabase.kb.io + rules: + - apiGroups: + - database.oracle.com + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - singleinstancedatabases + sideEffects: None + targetPort: 9443 + type: MutatingAdmissionWebhook + webhookPath: /mutate-database-oracle-com-v1alpha1-singleinstancedatabase + - admissionReviewVersions: + - v1 + containerPort: 443 + deploymentName: oracle-database-operator-controller-manager + failurePolicy: Fail + generateName: vautonomouscontainerdatabasev1alpha1.kb.io + rules: + - apiGroups: + - database.oracle.com + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - autonomouscontainerdatabases + sideEffects: None + targetPort: 9443 + type: ValidatingAdmissionWebhook + webhookPath: /validate-database-oracle-com-v1alpha1-autonomouscontainerdatabase + - admissionReviewVersions: + - v1 + containerPort: 443 + deploymentName: oracle-database-operator-controller-manager + failurePolicy: Fail + generateName: vautonomouscontainerdatabasev4.kb.io + rules: + - apiGroups: + - database.oracle.com + apiVersions: + - v4 + operations: + - CREATE + - UPDATE + resources: + - autonomouscontainerdatabases + sideEffects: None + targetPort: 9443 + type: ValidatingAdmissionWebhook + webhookPath: /validate-database-oracle-com-v4-autonomouscontainerdatabase + - admissionReviewVersions: + - v1 + containerPort: 443 + deploymentName: oracle-database-operator-controller-manager + failurePolicy: Fail + generateName: vautonomousdatabasebackupv1alpha1.kb.io + rules: + - apiGroups: + - database.oracle.com + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - autonomousdatabasebackups + sideEffects: None + targetPort: 9443 + type: ValidatingAdmissionWebhook + webhookPath: /validate-database-oracle-com-v1alpha1-autonomousdatabasebackup + - admissionReviewVersions: + - v1 + containerPort: 443 + deploymentName: oracle-database-operator-controller-manager + failurePolicy: Fail + generateName: vautonomousdatabasebackupv4.kb.io + rules: + - apiGroups: + - database.oracle.com + apiVersions: + - v4 + operations: + - CREATE + - UPDATE + resources: + - autonomousdatabasebackups + sideEffects: None + targetPort: 9443 + type: ValidatingAdmissionWebhook + webhookPath: /validate-database-oracle-com-v4-autonomousdatabasebackup + - admissionReviewVersions: + - v1 + containerPort: 443 + deploymentName: oracle-database-operator-controller-manager + failurePolicy: Fail + generateName: vautonomousdatabaserestorev1alpha1.kb.io + rules: + - apiGroups: + - database.oracle.com + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - autonomousdatabaserestores + sideEffects: None + targetPort: 9443 + type: ValidatingAdmissionWebhook + webhookPath: /validate-database-oracle-com-v1alpha1-autonomousdatabaserestore + - admissionReviewVersions: + - v1 + containerPort: 443 + deploymentName: oracle-database-operator-controller-manager + failurePolicy: Fail + generateName: vautonomousdatabaserestorev4.kb.io + rules: + - apiGroups: + - database.oracle.com + apiVersions: + - v4 + operations: + - CREATE + - UPDATE + resources: + - autonomousdatabaserestores + sideEffects: None + targetPort: 9443 + type: ValidatingAdmissionWebhook + webhookPath: /validate-database-oracle-com-v4-autonomousdatabaserestore + - admissionReviewVersions: + - v1 + containerPort: 443 + deploymentName: oracle-database-operator-controller-manager + failurePolicy: Fail + generateName: vautonomousdatabasev1alpha1.kb.io + rules: + - apiGroups: + - database.oracle.com + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - autonomousdatabases + sideEffects: None + targetPort: 9443 + type: ValidatingAdmissionWebhook + webhookPath: /validate-database-oracle-com-v1alpha1-autonomousdatabase + - admissionReviewVersions: + - v1 + - v1beta1 + containerPort: 443 + deploymentName: oracle-database-operator-controller-manager + failurePolicy: Fail + generateName: vcdb.kb.io + rules: + - apiGroups: + - database.oracle.com + apiVersions: + - v4 + operations: + - CREATE + - UPDATE + resources: + - cdbs + sideEffects: None + targetPort: 9443 + type: ValidatingAdmissionWebhook + webhookPath: /validate-database-oracle-com-v4-cdb + - admissionReviewVersions: + - v1 + containerPort: 443 + deploymentName: oracle-database-operator-controller-manager + failurePolicy: Fail + generateName: vdatabaseobserver.kb.io + rules: + - apiGroups: + - observability.oracle.com + apiVersions: + - v4 + operations: + - CREATE + - UPDATE + resources: + - databaseobservers + sideEffects: None + targetPort: 9443 + type: ValidatingAdmissionWebhook + webhookPath: /validate-observability-oracle-com-v4-databaseobserver + - admissionReviewVersions: + - v1 + - v1beta1 + containerPort: 443 + deploymentName: oracle-database-operator-controller-manager + failurePolicy: Fail + generateName: vdataguardbroker.kb.io + rules: + - apiGroups: + - database.oracle.com + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - dataguardbrokers + sideEffects: None + targetPort: 9443 + type: ValidatingAdmissionWebhook + webhookPath: /validate-database-oracle-com-v1alpha1-dataguardbroker + - admissionReviewVersions: + - v4 + - v1beta1 + containerPort: 443 + deploymentName: oracle-database-operator-controller-manager + failurePolicy: Fail + generateName: vlrest.kb.io + rules: + - apiGroups: + - database.oracle.com + apiVersions: + - v4 + operations: + - CREATE + - UPDATE + resources: + - lrests + sideEffects: None + targetPort: 9443 + type: ValidatingAdmissionWebhook + webhookPath: /validate-database-oracle-com-v4-lrest + - admissionReviewVersions: + - v4 + - v1beta1 + containerPort: 443 + deploymentName: oracle-database-operator-controller-manager + failurePolicy: Fail + generateName: vlrpdb.kb.io + rules: + - apiGroups: + - database.oracle.com + apiVersions: + - v4 + operations: + - CREATE + - UPDATE + resources: + - lrpdbs + sideEffects: None + targetPort: 9443 + type: ValidatingAdmissionWebhook + webhookPath: /validate-database-oracle-com-v4-lrpdb + - admissionReviewVersions: + - v1 + - v1beta1 + containerPort: 443 + deploymentName: oracle-database-operator-controller-manager + failurePolicy: Fail + generateName: voraclerestdataservice.kb.io + rules: + - apiGroups: + - database.oracle.com + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - oraclerestdataservices + sideEffects: None + targetPort: 9443 + type: ValidatingAdmissionWebhook + webhookPath: /validate-database-oracle-com-v1alpha1-oraclerestdataservice + - admissionReviewVersions: + - v1 + - v1beta1 + containerPort: 443 + deploymentName: oracle-database-operator-controller-manager + failurePolicy: Fail + generateName: vpdb.kb.io + rules: + - apiGroups: + - database.oracle.com + apiVersions: + - v4 + operations: + - CREATE + - UPDATE + resources: + - pdbs + sideEffects: None + targetPort: 9443 + type: ValidatingAdmissionWebhook + webhookPath: /validate-database-oracle-com-v4-pdb + - admissionReviewVersions: + - v1 + containerPort: 443 + deploymentName: oracle-database-operator-controller-manager + failurePolicy: Fail + generateName: vshardingdatabasev1alpha1.kb.io + rules: + - apiGroups: + - database.oracle.com + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + - DELETE + resources: + - shardingdatabases + sideEffects: None + targetPort: 9443 + type: ValidatingAdmissionWebhook + webhookPath: /validate-database-oracle-com-v1alpha1-shardingdatabase + - admissionReviewVersions: + - v1 + containerPort: 443 + deploymentName: oracle-database-operator-controller-manager + failurePolicy: Fail + generateName: vshardingdatabasev4.kb.io + rules: + - apiGroups: + - database.oracle.com + apiVersions: + - v4 + operations: + - CREATE + - UPDATE + - DELETE + resources: + - shardingdatabases + sideEffects: None + targetPort: 9443 + type: ValidatingAdmissionWebhook + webhookPath: /validate-database-oracle-com-v4-shardingdatabase + - admissionReviewVersions: + - v1 + - v1beta1 + containerPort: 443 + deploymentName: oracle-database-operator-controller-manager + failurePolicy: Fail + generateName: vsingleinstancedatabase.kb.io + rules: + - apiGroups: + - database.oracle.com + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + - DELETE + resources: + - singleinstancedatabases + sideEffects: None + targetPort: 9443 + type: ValidatingAdmissionWebhook + webhookPath: /validate-database-oracle-com-v1alpha1-singleinstancedatabase diff --git a/bundle/metadata/annotations.yaml b/bundle/metadata/annotations.yaml new file mode 100644 index 00000000..8a5ae70d --- /dev/null +++ b/bundle/metadata/annotations.yaml @@ -0,0 +1,10 @@ +annotations: + # Core bundle annotations. + operators.operatorframework.io.bundle.mediatype.v1: registry+v1 + operators.operatorframework.io.bundle.manifests.v1: manifests/ + operators.operatorframework.io.bundle.metadata.v1: metadata/ + operators.operatorframework.io.bundle.package.v1: oracle-database-operator + operators.operatorframework.io.bundle.channels.v1: stable + operators.operatorframework.io.metrics.builder: operator-sdk-v1.40.0 + operators.operatorframework.io.metrics.mediatype.v1: metrics+v1 + operators.operatorframework.io.metrics.project_layout: go.kubebuilder.io/v4 diff --git a/commons/adb_family/utils.go b/commons/adb_family/utils.go index 591b3130..270e93d1 100644 --- a/commons/adb_family/utils.go +++ b/commons/adb_family/utils.go @@ -62,7 +62,7 @@ func VerifyTargetAdb(kubeClient client.Client, target dbv4.TargetSpec, namespace } else { // Find the target ADB using the ADB OCID - ownerAdb, err = k8s.FetchAutonomousDatabaseWithOCID(kubeClient, namespace, *target.OciAdb.OCID) + ownerAdb, err = k8s.FetchAutonomousDatabaseWithOCID(kubeClient, namespace, *target.OciAdb.Id) if err != nil { return nil, err } diff --git a/commons/dbcssystem/dbcs_reconciler.go b/commons/dbcssystem/dbcs_reconciler.go index 6c498320..4aa6929b 100644 --- a/commons/dbcssystem/dbcs_reconciler.go +++ b/commons/dbcssystem/dbcs_reconciler.go @@ -65,11 +65,13 @@ import ( ) const ( - checkInterval = 30 * time.Second - timeout = 15 * time.Minute + checkInterval = 30 * time.Second + timeout = 15 * time.Minute + PatchHistoryEntrySummaryLifecycleStateInProgress database.PatchHistoryEntrySummaryLifecycleStateEnum = "IN_PROGRESS" + PatchHistoryEntrySummaryLifecycleStateSucceeded database.PatchHistoryEntrySummaryLifecycleStateEnum = "SUCCEEDED" ) -func CreateAndGetDbcsId(logger logr.Logger, kubeClient client.Client, dbClient database.DatabaseClient, dbcs *databasev4.DbcsSystem, nwClient core.VirtualNetworkClient, wrClient workrequests.WorkRequestClient, kmsDetails *databasev4.KMSDetailsStatus) (string, error) { +func CreateAndGetDbcsId(compartmentId string, logger logr.Logger, kubeClient client.Client, dbClient database.DatabaseClient, dbcs *databasev4.DbcsSystem, nwClient core.VirtualNetworkClient, wrClient workrequests.WorkRequestClient, kmsDetails *databasev4.KMSDetailsStatus) (string, error) { ctx := context.TODO() // Check if DBCS system already exists using the displayName @@ -104,7 +106,7 @@ func CreateAndGetDbcsId(logger logr.Logger, kubeClient client.Client, dbClient d licenceModel := getLicenceModel(dbcs) // Get DB Home Details - dbHomeReq, err := GetDbHomeDetails(kubeClient, dbClient, dbcs) + dbHomeReq, err := GetDbHomeDetails(kubeClient, dbClient, dbcs, "") if err != nil { return "", err } @@ -160,7 +162,7 @@ func CreateAndGetDbcsId(logger logr.Logger, kubeClient client.Client, dbClient d dbcs.Spec.Id = resp.DbSystem.Id // Change the phase to "Provisioning" - if statusErr := SetLifecycleState(kubeClient, dbClient, dbcs, databasev4.Provision, nwClient, wrClient); statusErr != nil { + if statusErr := SetLifecycleState(compartmentId, kubeClient, dbClient, dbcs, databasev4.Provision, nwClient, wrClient); statusErr != nil { return "", statusErr } @@ -194,7 +196,7 @@ func convertLicenseModel(licenseModel database.DbSystemLicenseModelEnum) (databa } } -func CloneAndGetDbcsId(logger logr.Logger, kubeClient client.Client, dbClient database.DatabaseClient, dbcs *databasev4.DbcsSystem, nwClient core.VirtualNetworkClient, wrClient workrequests.WorkRequestClient) (string, error) { +func CloneAndGetDbcsId(compartmentId string, logger logr.Logger, kubeClient client.Client, dbClient database.DatabaseClient, dbcs *databasev4.DbcsSystem, nwClient core.VirtualNetworkClient, wrClient workrequests.WorkRequestClient) (string, error) { ctx := context.TODO() var err error dbAdminPassword := "" @@ -229,6 +231,10 @@ func CloneAndGetDbcsId(logger logr.Logger, kubeClient client.Client, dbClient da if err != nil { return "", err } + + if compartmentId == "" { + compartmentId = *existingDbSystem.CompartmentId + } logger.Info("Retrieved existing Db System Details from OCI using Spec.Id") // // Create the clone request payload @@ -297,7 +303,7 @@ func CloneAndGetDbcsId(logger logr.Logger, kubeClient client.Client, dbClient da dbcs.Status.DbCloneStatus.Id = response.DbSystem.Id // Change the phase to "Provisioning" - if statusErr := SetLifecycleState(kubeClient, dbClient, dbcs, databasev4.Provision, nwClient, wrClient); statusErr != nil { + if statusErr := SetLifecycleState(compartmentId, kubeClient, dbClient, dbcs, databasev4.Provision, nwClient, wrClient); statusErr != nil { return "", statusErr } @@ -310,9 +316,429 @@ func CloneAndGetDbcsId(logger logr.Logger, kubeClient client.Client, dbClient da return *response.DbSystem.Id, nil // return "", nil } +func PatchDBSystem( + ctx context.Context, + compartmentId string, + logger logr.Logger, + kubeClient client.Client, + dbClient database.DatabaseClient, + dbcs *databasev4.DbcsSystem, + nwClient core.VirtualNetworkClient, + wrClient workrequests.WorkRequestClient, + dbHomeId, patchId string) error { + + dbSystemId := dbcs.Spec.Id + // Check if patch is already applied or in progress then return + historyResp, err := dbClient.ListDbSystemPatchHistoryEntries(ctx, database.ListDbSystemPatchHistoryEntriesRequest{ + DbSystemId: dbSystemId, + }) + if err != nil { + logger.Error(err, "Failed to get patch history entries", "DBSystemID", dbSystemId) + return fmt.Errorf("failed to get patch history entries: %w", err) + } + + for _, entry := range historyResp.Items { + if entry.PatchId != nil && *entry.PatchId == patchId { + if entry.LifecycleState == database.PatchHistoryEntrySummaryLifecycleStateSucceeded { + logger.Info("Patch already applied, skipping", "PatchID", patchId) + return nil + } + if entry.LifecycleState == database.PatchHistoryEntrySummaryLifecycleStateInProgress { + logger.Info("Patch in progress, waiting until it completes", "PatchID", patchId) + // Change the phase to "Provisioning" + if statusErr := SetLifecycleState(compartmentId, kubeClient, dbClient, dbcs, databasev4.Update, nwClient, wrClient); statusErr != nil { + logger.Error(statusErr, "Failed to update lifecycle state to Provisioning") + return statusErr + } + // Wait for the patch to complete + return CheckPatchState(ctx, logger, dbClient, dbSystemId, patchId) + } + if entry.LifecycleState == database.PatchHistoryEntrySummaryLifecycleStateFailed { + logger.Error(fmt.Errorf("patch failed"), "Patch failed", "PatchID", patchId) + return fmt.Errorf("patch %s failed", patchId) + } + } + } + + logger.Info("Starting patch process for DB System", "DBSystemID", dbSystemId) + + patchesResp, err := dbClient.ListDbSystemPatches(ctx, database.ListDbSystemPatchesRequest{ + DbSystemId: dbSystemId, + }) + + if err != nil { + logger.Error(err, "Failed to list patches for DB System", "DBSystemID", dbSystemId) + return fmt.Errorf("failed to list patches for DB System: %w", err) + } + + found := false + + if len(patchesResp.Items) == 0 { + logger.Info("No patches available for this DB System", "DBSystemID", dbSystemId) + } else { + logger.Info("Available patches for DB System", "count", len(patchesResp.Items)) + for _, patch := range patchesResp.Items { + logger.Info("Patch found", + "PatchID", *patch.Id, + "Description", *patch.Description, + "LifecycleState", patch.LifecycleState, + "ReleaseDate", patch.TimeReleased.String(), + ) + + // Check if patchId matches + if *patch.Id == patchId { + found = true + } + } + } + + if !found { + logger.Error(nil, "Patch ID not found in available patches", "PatchID", patchId) + return fmt.Errorf("patch ID %s not found in available DB System patches", patchId) + } + + updateDetails := database.UpdateDbSystemDetails{ + Version: &database.PatchDetails{ + PatchId: common.String(patchId), + Action: database.PatchDetailsActionApply, + }, + } + + updateReq := database.UpdateDbSystemRequest{ + DbSystemId: dbSystemId, + UpdateDbSystemDetails: updateDetails, + } + + updateResp, err := dbClient.UpdateDbSystem(ctx, updateReq) + if err != nil { + logger.Error(err, "Failed to apply patch to DB System", "PatchID", patchId, "DBSystemID", dbSystemId) + return fmt.Errorf("failed to patch DB System: %w", err) + } + + logger.Info("Patch applied to DB System", "WorkRequestID", *updateResp.OpcWorkRequestId, "PatchID", patchId) + + // Change the phase to "Provisioning" + if statusErr := SetLifecycleState(compartmentId, kubeClient, dbClient, dbcs, databasev4.Update, nwClient, wrClient); statusErr != nil { + logger.Error(statusErr, "Failed to update lifecycle state to Provisioning") + return statusErr + } + + // Check the state + _, err = CheckResourceState(logger, dbClient, *updateResp.DbSystem.Id, string(databasev4.Update), string(databasev4.Available)) + if err != nil { + logger.Error(err, "Failed to verify DB System state post patching") + return err + } + + logger.Info("DB System patching process completed successfully", "DBSystemID", dbSystemId) + dbcs.Status.Message = "Patch applied successfully." + + return nil +} + +// CheckPatchState waits for a specific patch to finish applying. +// It polls the patch history until the patch is SUCCEEDED or FAILED. +func CheckPatchState(ctx context.Context, logger logr.Logger, dbClient database.DatabaseClient, dbSystemId *string, patchId string) error { + // Maximum wait duration: 120 minutes + timeout := 240 * time.Minute + start := time.Now() + + for { + // Timeout guard + if time.Since(start) > timeout { + msg := fmt.Sprintf("timed out after %v waiting for patch %s to complete", timeout, patchId) + logger.Error(fmt.Errorf("timeout"), msg, "DBSystemID", *dbSystemId) + return fmt.Errorf(msg) + } + + historyResp, err := dbClient.ListDbSystemPatchHistoryEntries(ctx, database.ListDbSystemPatchHistoryEntriesRequest{ + DbSystemId: dbSystemId, + }) + if err != nil { + logger.Error(err, "Failed to get patch history entries", "DBSystemID", *dbSystemId) + return fmt.Errorf("failed to get patch history entries: %w", err) + } + + var found bool + for _, entry := range historyResp.Items { + if entry.PatchId != nil && *entry.PatchId == patchId { + found = true + switch entry.LifecycleState { + case database.PatchHistoryEntrySummaryLifecycleStateSucceeded: + logger.Info("Patch succeeded", "PatchID", patchId) + return nil + + case database.PatchHistoryEntrySummaryLifecycleStateFailed: + logger.Error(fmt.Errorf("patch failed"), "Patch failed", "PatchID", patchId) + return fmt.Errorf("patch %s failed", patchId) + + case database.PatchHistoryEntrySummaryLifecycleStateInProgress: + logger.Info("Patch still in progress, waiting", "PatchID", patchId) + time.Sleep(60 * time.Second) + continue + } + } + } + + if !found { + logger.Info("Patch ID not found in history yet, waiting", "PatchID", patchId) + time.Sleep(60 * time.Second) + continue + } + } +} + +func UpgradeDatabaseVersion( + ctx context.Context, + compartmentId string, + logger logr.Logger, + kubeClient client.Client, + dbClient database.DatabaseClient, + dbcs *databasev4.DbcsSystem, + nwClient core.VirtualNetworkClient, + wrClient workrequests.WorkRequestClient, + databaseId, targetVersion string) error { + dbSystemId := dbcs.Spec.Id + logger.Info("Starting GI upgrade", "DbSystemID", dbSystemId, "TargetGI", targetVersion) + + // Step 1: Get current DB system details + getResp, err := dbClient.GetDbSystem(ctx, database.GetDbSystemRequest{ + DbSystemId: dbSystemId, + }) + if err != nil { + logger.Error(err, "Failed to get DB system details", "DbSystemID", dbSystemId) + return fmt.Errorf("failed to get DB system: %w", err) + } + currentGiVersion := getResp.DbSystem.Version + logger.Info("Current GI version", "CurrentGI", *currentGiVersion) + + // Step 1: Check current GI version + if *currentGiVersion == targetVersion { + logger.Info("GI already at target version. Proceeding...") + // Do NOT return — continue to DB upgrade + } else { + // Step 2: Check for ongoing GI upgrade + workReqsResp, err := wrClient.ListWorkRequests(ctx, workrequests.ListWorkRequestsRequest{ + CompartmentId: common.String(compartmentId), + ResourceId: dbSystemId, + }) + if err != nil { + logger.Error(err, "Failed to list work requests") + return fmt.Errorf("failed to list work requests: %w", err) + } + + for _, wr := range workReqsResp.Items { + if wr.OperationType != nil && *wr.OperationType == "Upgrade Db System" && + (wr.Status == workrequests.WorkRequestSummaryStatusAccepted || + wr.Status == workrequests.WorkRequestSummaryStatusInProgress) { + logger.Info("GI upgrade already in progress", "WorkRequestID", *wr.Id) + dbcs.Status.Message = "GI upgrade already in progress" + return nil // Skip further steps while upgrade is ongoing + } + } + // Step 3: Construct the upgrade request + upgradeReq := database.UpgradeDbSystemRequest{ + DbSystemId: dbSystemId, + UpgradeDbSystemDetails: database.UpgradeDbSystemDetails{ + Action: database.UpgradeDbSystemDetailsActionUpgrade, + NewGiVersion: common.String(targetVersion), + SnapshotRetentionPeriodInDays: common.Int(7), + IsSnapshotRetentionDaysForceUpdated: common.Bool(false), + }, + } + + // Step 4: Call the API + upgradeResp, err := dbClient.UpgradeDbSystem(ctx, upgradeReq) + if err != nil { + logger.Error(err, "Failed to initiate GI upgrade") + dbcs.Status.Message = "Failed to initiate GI upgrade" + + return fmt.Errorf("GI upgrade failed: %w", err) + } + + logger.Info("GI upgrade initiated", "WorkRequestID", *upgradeResp.OpcWorkRequestId) + + // Step 3: Update status to upgrading + if statusErr := SetLifecycleState(compartmentId, kubeClient, dbClient, dbcs, databasev4.Upgrade, nwClient, wrClient); statusErr != nil { + logger.Error(statusErr, "Failed to update lifecycle state to Upgrading") + dbcs.Status.Message = "Failed to update lifecycle state to Upgrading" + + return statusErr + } + + // Step 4: Check status + _, err = CheckResourceState(logger, dbClient, *upgradeResp.Id, string(databasev4.Provision), string(databasev4.Available)) + if err != nil { + logger.Error(err, "Failed to verify database state post upgrade") + dbcs.Status.Message = "Failed to verify database state post upgrade" + + return err + } + + // Step 5: Wait for completion + workReqId := upgradeResp.OpcWorkRequestId + for { + time.Sleep(30 * time.Second) + + getWorkReqResp, err := wrClient.GetWorkRequest(ctx, workrequests.GetWorkRequestRequest{ + WorkRequestId: workReqId, + }) + if err != nil { + logger.Error(err, "Error fetching work request status", "WorkRequestID", *workReqId) + dbcs.Status.Message = "Error fetching work request status" + return fmt.Errorf("failed to check GI upgrade status: %w", err) + } + + status := getWorkReqResp.WorkRequest.Status + logger.Info("GI upgrade work request status", "Status", status) + + if status == workrequests.WorkRequestStatusSucceeded { + logger.Info("GI upgrade completed successfully") + dbcs.Status.Message = "GI upgrade completed successfully" + + break + } else if status == workrequests.WorkRequestStatusFailed { + logger.Error(nil, "GI upgrade failed", "WorkRequestID", *workReqId) + dbcs.Status.Message = "GI upgrade failed" + + return fmt.Errorf("GI upgrade failed: work request marked as failed") + } + } + } + + // Step 1: Check if DB is already on the target version + dbResp, err := dbClient.GetDatabase(ctx, database.GetDatabaseRequest{ + DatabaseId: common.String(databaseId), + }) + if err != nil { + logger.Error(err, "Failed to fetch database details", "DatabaseID", databaseId) + dbcs.Status.Message = "Failed to fetch database details" + return fmt.Errorf("failed to get database details: %w", err) + } + // fmt.Printf("%+v\n", dbResp.Database) + dbHomeId := *dbResp.Database.DbHomeId + dbHomeResp, err := dbClient.GetDbHome(ctx, database.GetDbHomeRequest{ + DbHomeId: &dbHomeId, + }) + if err != nil { + dbcs.Status.Message = "Failed to get DB Home" + return fmt.Errorf("failed to get DB Home: %w", err) + + } + + currentDbVersion := dbHomeResp.DbHome.DbVersion + if currentDbVersion != nil && *currentDbVersion == targetVersion { + logger.Info("Database already at target version. Skipping database upgrade.", "Version", *currentDbVersion) + // Continue to post-upgrade or next steps if any + return nil + } + + // Step 2: Check for ongoing DB upgrade work requests + workReqsRespDb, err := wrClient.ListWorkRequests(ctx, workrequests.ListWorkRequestsRequest{ + CompartmentId: common.String(compartmentId), + ResourceId: common.String(databaseId), + }) + if err != nil { + logger.Error(err, "Failed to list database work requests") + dbcs.Status.Message = "Failed to list database work requests" + return fmt.Errorf("failed to list database work requests: %w", err) + } + + for _, wr := range workReqsRespDb.Items { + if wr.OperationType != nil && *wr.OperationType == "Upgrade Database" && + (wr.Status == workrequests.WorkRequestSummaryStatusAccepted || + wr.Status == workrequests.WorkRequestSummaryStatusInProgress) { + + logger.Info("Database upgrade already in progress, waiting for completion", "WorkRequestID", *wr.Id) + dbcs.Status.Message = "Database upgrade already in progress, waiting for completion." + + // Poll the work request status + for { + time.Sleep(60 * time.Second) + + workReqResp, err := wrClient.GetWorkRequest(ctx, workrequests.GetWorkRequestRequest{ + WorkRequestId: wr.Id, + }) + if err != nil { + logger.Error(err, "Failed to fetch work request status", "WorkRequestID", *wr.Id) + dbcs.Status.Message = "Failed to fetch work request status for database" + return fmt.Errorf("failed to get database upgrade work request status: %w", err) + } + + status := workReqResp.WorkRequest.Status + logger.Info("Database upgrade work request status. Checking again in 60 seconds.", "Status", status, "WorkRequestID", *wr.Id) + + if status == workrequests.WorkRequestStatusSucceeded { + logger.Info("Database upgrade completed successfully", "WorkRequestID", *wr.Id) + break + } else if status == workrequests.WorkRequestStatusFailed { + logger.Error(nil, "Database upgrade failed", "WorkRequestID", *wr.Id) + dbcs.Status.Message = "Database upgrade failed" + return fmt.Errorf("database upgrade failed: work request marked as failed") + } + // continue polling if still in progress + } + return nil + } + } + + logger.Info("Starting upgrade process for Database", "DatabaseID", databaseId, "TargetVersion", targetVersion) + + upgradeDetails := database.UpgradeDatabaseDetails{ + DatabaseUpgradeSourceDetails: database.DatabaseUpgradeWithDbVersionDetails{ + DbVersion: common.String(targetVersion), + }, + Action: database.UpgradeDatabaseDetailsActionUpgrade, + } + + // Step 3: Submit the upgrade request + upgradeReqDb := database.UpgradeDatabaseRequest{ + DatabaseId: common.String(databaseId), + UpgradeDatabaseDetails: upgradeDetails, + } + + upgradeRespDb, err := dbClient.UpgradeDatabase(ctx, upgradeReqDb) + if err != nil { + logger.Error(err, "Failed to upgrade database version", "DatabaseID", databaseId) + dbcs.Status.Message = "Failed to upgrade database version" + + return fmt.Errorf("failed to upgrade database: %w", err) + } + + logger.Info("Upgrade initiated", "WorkRequestID", *upgradeRespDb.OpcWorkRequestId) + + // Step 3: Update status to upgrading + if statusErr := SetLifecycleState(compartmentId, kubeClient, dbClient, dbcs, databasev4.Upgrade, nwClient, wrClient); statusErr != nil { + logger.Error(statusErr, "Failed to update lifecycle state to Upgrading") + dbcs.Status.Message = "Failed to update lifecycle state to Upgrading" + + return statusErr + } + + // Step 4: Check status + _, err = CheckResourceState(logger, dbClient, *upgradeRespDb.DbSystemId, string(databasev4.Provision), string(databasev4.Available)) + if err != nil { + logger.Error(err, "Failed to verify database state post upgrade") + dbcs.Status.Message = "Upgrade Database Completed successfully." + + return err + } + + logger.Info("Database upgrade process completed successfully", "DatabaseID", databaseId) + dbcs.Status.Message = fmt.Sprintf("Database upgraded successfully to version %s", targetVersion) + + return nil +} // CloneFromBackupAndGetDbcsId clones a DB system from a backup and returns the new DB system's OCID. -func CloneFromBackupAndGetDbcsId(logger logr.Logger, kubeClient client.Client, dbClient database.DatabaseClient, dbcs *databasev4.DbcsSystem, nwClient core.VirtualNetworkClient, wrClient workrequests.WorkRequestClient) (string, error) { +func CloneFromBackupAndGetDbcsId( + compartmentId string, + logger logr.Logger, + kubeClient client.Client, + dbClient database.DatabaseClient, + dbcs *databasev4.DbcsSystem, + nwClient core.VirtualNetworkClient, + wrClient workrequests.WorkRequestClient) (string, error) { ctx := context.TODO() var err error @@ -327,6 +753,7 @@ func CloneFromBackupAndGetDbcsId(logger logr.Logger, kubeClient client.Client, d fmt.Println("Error getting backup details:", err) return "", err } + // Extract the DatabaseId from the backup details databaseId := backupResp.Backup.DatabaseId // Fetch the existing Database details existingDatabase, err := dbClient.GetDatabase(ctx, database.GetDatabaseRequest{ @@ -343,6 +770,11 @@ func CloneFromBackupAndGetDbcsId(logger logr.Logger, kubeClient client.Client, d logger.Error(err, "DBSystemId not found") return "", err } + dbcs.Spec.Id = dbSystemId + + if compartmentId == "" { + compartmentId = *existingDatabase.CompartmentId + } // Fetch the existing DB system details existingDbSystem, err := dbClient.GetDbSystem(ctx, database.GetDbSystemRequest{ @@ -374,6 +806,11 @@ func CloneFromBackupAndGetDbcsId(logger logr.Logger, kubeClient client.Client, d return "", err } + // Change the phase to "Provisioning" + if statusErr := SetLifecycleState(compartmentId, kubeClient, dbClient, dbcs, databasev4.Provision, nwClient, wrClient); statusErr != nil { + return "", statusErr + } + // Create the clone request payload cloneRequest := database.LaunchDbSystemFromBackupDetails{ CompartmentId: existingDbSystem.DbSystem.CompartmentId, @@ -427,15 +864,19 @@ func CloneFromBackupAndGetDbcsId(logger logr.Logger, kubeClient client.Client, d LaunchDbSystemDetails: cloneRequest, }) if err != nil { + // Change the phase to "Provisioning" + if statusErr := SetLifecycleState(compartmentId, kubeClient, dbClient, dbcs, databasev4.Failed, nwClient, wrClient); statusErr != nil { + return "", err + } return "", err } dbcs.Status.DbCloneStatus.Id = response.DbSystem.Id - // Change the phase to "Provisioning" - if statusErr := SetLifecycleState(kubeClient, dbClient, dbcs, databasev4.Provision, nwClient, wrClient); statusErr != nil { - return "", statusErr - } + // // Change the phase to "Provisioning" + // if statusErr := SetLifecycleState(compartmentId, kubeClient, dbClient, dbcs, databasev4.Provision, nwClient, wrClient); statusErr != nil { + // return "", statusErr + // } // Check the state _, err = CheckResourceState(logger, dbClient, *response.DbSystem.Id, string(databasev4.Provision), string(databasev4.Available)) @@ -447,7 +888,7 @@ func CloneFromBackupAndGetDbcsId(logger logr.Logger, kubeClient client.Client, d } // Sync the DbcsSystem Database details -func CloneFromDatabaseAndGetDbcsId(logger logr.Logger, kubeClient client.Client, dbClient database.DatabaseClient, dbcs *databasev4.DbcsSystem, nwClient core.VirtualNetworkClient, wrClient workrequests.WorkRequestClient) (string, error) { +func CloneFromDatabaseAndGetDbcsId(compartmentId string, logger logr.Logger, kubeClient client.Client, dbClient database.DatabaseClient, dbcs *databasev4.DbcsSystem, nwClient core.VirtualNetworkClient, wrClient workrequests.WorkRequestClient) (string, error) { ctx := context.TODO() var err error dbAdminPassword := "" @@ -488,6 +929,10 @@ func CloneFromDatabaseAndGetDbcsId(logger logr.Logger, kubeClient client.Client, logger.Error(err, "DBSystemId not found") return "", err } + dbcs.Spec.Id = dbSystemId + if compartmentId == "" { + compartmentId = *existingDatabase.CompartmentId + } // Fetch the existing DB system details existingDbSystem, err := dbClient.GetDbSystem(ctx, database.GetDbSystemRequest{ @@ -504,6 +949,51 @@ func CloneFromDatabaseAndGetDbcsId(logger logr.Logger, kubeClient client.Client, logger.Error(err, "Failed to get SSH public key") return "", err } + // Before creating the clone request payload, check if a valid backup exists + backupsResp, err := dbClient.ListBackups(ctx, database.ListBackupsRequest{ + DatabaseId: dbcs.Spec.DatabaseId, + }) + if err != nil { + logger.Error(err, "Failed to list backups for database", "DatabaseId", dbcs.Spec.DatabaseId) + return "", fmt.Errorf("failed to list backups for database %s: %w", dbcs.Spec.DatabaseId, err) + } + + if len(backupsResp.Items) == 0 { + msg := fmt.Sprintf("no backups found for database %s, cannot proceed with cloning", dbcs.Spec.DatabaseId) + logger.Error(nil, msg) + return "", fmt.Errorf(msg) + } + + // Optional: ensure at least one backup is in the same AD + validBackupFound := false + for _, backup := range backupsResp.Items { + if backup.LifecycleState == database.BackupSummaryLifecycleStateActive && + backup.AvailabilityDomain != nil && + *backup.AvailabilityDomain == *existingDbSystem.DbSystem.AvailabilityDomain { + + validBackupFound = true + logger.Info("Found valid backup for cloning", + "BackupId", *backup.Id, + "AvailabilityDomain", *backup.AvailabilityDomain, + "LifecycleState", backup.LifecycleState) + break + } + + } + + if !validBackupFound { + msg := fmt.Sprintf("no valid backups for database %s found in same AD %s, cannot proceed with cloning", + *dbcs.Spec.DatabaseId, *existingDbSystem.DbSystem.AvailabilityDomain) + logger.Error(nil, msg) + return "", fmt.Errorf(msg) + } + + logger.Info("Valid backup found for cloning", "DatabaseId", dbcs.Spec.DatabaseId) + + // Change the phase to "Provisioning" + if statusErr := SetLifecycleState(compartmentId, kubeClient, dbClient, dbcs, databasev4.Provision, nwClient, wrClient); statusErr != nil { + return "", statusErr + } // Create the clone request payload cloneRequest := database.LaunchDbSystemFromDatabaseDetails{ @@ -558,15 +1048,19 @@ func CloneFromDatabaseAndGetDbcsId(logger logr.Logger, kubeClient client.Client, LaunchDbSystemDetails: cloneRequest, }) if err != nil { + // Change the phase to "Provisioning" + if statusErr := SetLifecycleState(compartmentId, kubeClient, dbClient, dbcs, databasev4.Failed, nwClient, wrClient); statusErr != nil { + return "", err + } return "", err } dbcs.Status.DbCloneStatus.Id = response.DbSystem.Id - // Change the phase to "Provisioning" - if statusErr := SetLifecycleState(kubeClient, dbClient, dbcs, databasev4.Provision, nwClient, wrClient); statusErr != nil { - return "", statusErr - } + // // Change the phase to "Provisioning" + // if statusErr := SetLifecycleState(compartmentId, kubeClient, dbClient, dbcs, databasev4.Provision, nwClient, wrClient); statusErr != nil { + // return "", statusErr + // } // Check the state _, err = CheckResourceState(logger, dbClient, *response.DbSystem.Id, string(databasev4.Provision), string(databasev4.Available)) @@ -751,7 +1245,7 @@ func DeleteDbcsSystemSystem(dbClient database.DatabaseClient, Id string) error { } // SetLifecycleState set status.state of the reosurce. -func SetLifecycleState(kubeClient client.Client, dbClient database.DatabaseClient, dbcs *databasev4.DbcsSystem, state databasev4.LifecycleState, nwClient core.VirtualNetworkClient, wrClient workrequests.WorkRequestClient) error { +func SetLifecycleState(compartmentId string, kubeClient client.Client, dbClient database.DatabaseClient, dbcs *databasev4.DbcsSystem, state databasev4.LifecycleState, nwClient core.VirtualNetworkClient, wrClient workrequests.WorkRequestClient) error { maxRetries := 5 retryDelay := time.Second * 2 @@ -771,13 +1265,12 @@ func SetLifecycleState(kubeClient client.Client, dbClient database.DatabaseClien } // Set the status using the dbcs object - if statusErr := SetDBCSStatus(dbClient, dbcs, nwClient, wrClient); statusErr != nil { + if statusErr := SetDBCSStatus(state, compartmentId, dbClient, dbcs, nwClient, wrClient); statusErr != nil { return statusErr } // Update the ResourceVersion of dbcs from latestInstance to avoid conflict dbcs.ResourceVersion = latestInstance.ResourceVersion - // Attempt to patch the status of the instance err = kubeClient.Status().Patch(context.TODO(), dbcs, client.MergeFrom(latestInstance)) if err != nil { @@ -855,6 +1348,7 @@ func mergeInstancesFromLatest(instance, latestInstance *databasev4.DbcsSystem) e func mergeStructFields(instanceField, latestField reflect.Value) { for i := 0; i < instanceField.NumField(); i++ { subField := instanceField.Type().Field(i) + instanceSubField := instanceField.Field(i) latestSubField := latestField.Field(i) @@ -878,6 +1372,7 @@ func mergeStructFields(instanceField, latestField reflect.Value) { } } } + } func isExported(field reflect.StructField) bool { @@ -886,7 +1381,7 @@ func isExported(field reflect.StructField) bool { // SetDBCSSystem LifeCycle state when state is provisioning -func SetDBCSDatabaseLifecycleState(logger logr.Logger, kubeClient client.Client, dbClient database.DatabaseClient, dbcs *databasev4.DbcsSystem, nwClient core.VirtualNetworkClient, wrClient workrequests.WorkRequestClient) error { +func SetDBCSDatabaseLifecycleState(compartmentId string, logger logr.Logger, kubeClient client.Client, dbClient database.DatabaseClient, dbcs *databasev4.DbcsSystem, nwClient core.VirtualNetworkClient, wrClient workrequests.WorkRequestClient) error { dbcsId := *dbcs.Spec.Id @@ -904,12 +1399,12 @@ func SetDBCSDatabaseLifecycleState(logger logr.Logger, kubeClient client.Client, return nil } else if string(resp.LifecycleState) == string(databasev4.Available) { // Change the phase to "Available" - if statusErr := SetLifecycleState(kubeClient, dbClient, dbcs, databasev4.Available, nwClient, wrClient); statusErr != nil { + if statusErr := SetLifecycleState(compartmentId, kubeClient, dbClient, dbcs, databasev4.Available, nwClient, wrClient); statusErr != nil { return statusErr } } else if string(resp.LifecycleState) == string(databasev4.Provision) { // Change the phase to "Provisioning" - if statusErr := SetLifecycleState(kubeClient, dbClient, dbcs, databasev4.Provision, nwClient, wrClient); statusErr != nil { + if statusErr := SetLifecycleState(compartmentId, kubeClient, dbClient, dbcs, databasev4.Provision, nwClient, wrClient); statusErr != nil { return statusErr } // Check the State @@ -919,7 +1414,7 @@ func SetDBCSDatabaseLifecycleState(logger logr.Logger, kubeClient client.Client, } } else if string(resp.LifecycleState) == string(databasev4.Update) { // Change the phase to "Updating" - if statusErr := SetLifecycleState(kubeClient, dbClient, dbcs, databasev4.Update, nwClient, wrClient); statusErr != nil { + if statusErr := SetLifecycleState(compartmentId, kubeClient, dbClient, dbcs, databasev4.Update, nwClient, wrClient); statusErr != nil { return statusErr } // Check the State @@ -929,15 +1424,25 @@ func SetDBCSDatabaseLifecycleState(logger logr.Logger, kubeClient client.Client, } } else if string(resp.LifecycleState) == string(databasev4.Failed) { // Change the phase to "Updating" - if statusErr := SetLifecycleState(kubeClient, dbClient, dbcs, databasev4.Failed, nwClient, wrClient); statusErr != nil { + if statusErr := SetLifecycleState(compartmentId, kubeClient, dbClient, dbcs, databasev4.Failed, nwClient, wrClient); statusErr != nil { return statusErr } return fmt.Errorf("DbSystem is in Failed State") } else if string(resp.LifecycleState) == string(databasev4.Terminated) { // Change the phase to "Terminated" - if statusErr := SetLifecycleState(kubeClient, dbClient, dbcs, databasev4.Terminate, nwClient, wrClient); statusErr != nil { + if statusErr := SetLifecycleState(compartmentId, kubeClient, dbClient, dbcs, databasev4.Terminate, nwClient, wrClient); statusErr != nil { + return statusErr + } + } else if string(resp.LifecycleState) == string(databasev4.Upgrade) { + // Change the phase to "Upgrading" + if statusErr := SetLifecycleState(compartmentId, kubeClient, dbClient, dbcs, databasev4.Upgrade, nwClient, wrClient); statusErr != nil { return statusErr } + // Check the State + _, err = CheckResourceState(logger, dbClient, *resp.DbSystem.Id, string(databasev4.Upgrade), string(databasev4.Available)) + if err != nil { + return err + } } return nil } @@ -954,43 +1459,51 @@ func GetDbSystemId(logger logr.Logger, dbClient database.DatabaseClient, dbcs *d return err } - dbcs.Spec.DbSystem.CompartmentId = *response.CompartmentId - if response.DisplayName != nil { - dbcs.Spec.DbSystem.DisplayName = *response.DisplayName + if dbcs.Spec.DbSystem == nil { + dbcs.Spec.DbSystem = &databasev4.DbSystemDetails{} + } + if response.DbSystem.CompartmentId != nil { + dbcs.Spec.DbSystem.CompartmentId = *response.DbSystem.CompartmentId + } + if response.DbSystem.DisplayName != nil { + dbcs.Spec.DbSystem.DisplayName = *response.DbSystem.DisplayName } - if response.Hostname != nil { - dbcs.Spec.DbSystem.HostName = *response.Hostname + if response.DbSystem.Hostname != nil { + dbcs.Spec.DbSystem.HostName = *response.DbSystem.Hostname } - if response.CpuCoreCount != nil { - dbcs.Spec.DbSystem.CpuCoreCount = *response.CpuCoreCount + if response.DbSystem.CpuCoreCount != nil { + dbcs.Spec.DbSystem.CpuCoreCount = *response.DbSystem.CpuCoreCount } - dbcs.Spec.DbSystem.NodeCount = response.NodeCount - if response.ClusterName != nil { - dbcs.Spec.DbSystem.ClusterName = *response.ClusterName + dbcs.Spec.DbSystem.NodeCount = response.DbSystem.NodeCount + if response.DbSystem.ClusterName != nil { + dbcs.Spec.DbSystem.ClusterName = *response.DbSystem.ClusterName } - //dbcs.Spec.DbSystem.DbUniqueName = *response.DbUniqueName + //dbcs.Spec.DbSystem.DbUniqueName = *response.DbSystem.DbUniqueName if string(response.DbSystem.DatabaseEdition) != "" { - dbcs.Spec.DbSystem.DbEdition = string(response.DatabaseEdition) + dbcs.Spec.DbSystem.DbEdition = string(response.DbSystem.DatabaseEdition) } - if string(response.DiskRedundancy) != "" { - dbcs.Spec.DbSystem.DiskRedundancy = string(response.DiskRedundancy) + if string(response.DbSystem.DiskRedundancy) != "" { + dbcs.Spec.DbSystem.DiskRedundancy = string(response.DbSystem.DiskRedundancy) } - //dbcs.Spec.DbSystem.DbVersion = *response. + //dbcs.Spec.DbSystem.DbVersion = *response.DbSystem. - if response.BackupSubnetId != nil { - dbcs.Spec.DbSystem.BackupSubnetId = *response.BackupSubnetId + if response.DbSystem.BackupSubnetId != nil { + dbcs.Spec.DbSystem.BackupSubnetId = *response.DbSystem.BackupSubnetId + } + dbcs.Spec.DbSystem.Shape = *response.DbSystem.Shape + dbcs.Spec.DbSystem.SshPublicKeys = []string(response.DbSystem.SshPublicKeys) + if response.DbSystem.FaultDomains != nil { + dbcs.Spec.DbSystem.FaultDomains = []string(response.DbSystem.FaultDomains) } - dbcs.Spec.DbSystem.Shape = *response.Shape - dbcs.Spec.DbSystem.SshPublicKeys = []string(response.SshPublicKeys) - if response.FaultDomains != nil { - dbcs.Spec.DbSystem.FaultDomains = []string(response.FaultDomains) + dbcs.Spec.DbSystem.SubnetId = *response.DbSystem.SubnetId + dbcs.Spec.DbSystem.AvailabilityDomain = *response.DbSystem.AvailabilityDomain + if response.DbSystem.KmsKeyId != nil { + dbcs.Status.KMSDetailsStatus.KeyId = *response.DbSystem.KmsKeyId } - dbcs.Spec.DbSystem.SubnetId = *response.SubnetId - dbcs.Spec.DbSystem.AvailabilityDomain = *response.AvailabilityDomain - if response.KmsKeyId != nil { - dbcs.Status.KMSDetailsStatus.KeyId = *response.KmsKeyId + if response.DbSystem.Version != nil { + dbcs.Status.DbVersion = *response.DbSystem.Version } err = PopulateDBDetails(logger, dbClient, dbcs) if err != nil { @@ -1024,7 +1537,26 @@ func PopulateDBDetails(logger logr.Logger, dbClient database.DatabaseClient, dbc func GetListDbHomeRsp(logger logr.Logger, dbClient database.DatabaseClient, dbcs *databasev4.DbcsSystem) (database.ListDbHomesResponse, error) { dbcsId := *dbcs.Spec.Id - CompartmentId := dbcs.Spec.DbSystem.CompartmentId + var CompartmentId string + + // Check if the CompartmentId is defined in dbcs.Spec.DbSystem.CompartmentId + if dbcs.Spec.DbSystem.CompartmentId != "" { + CompartmentId = dbcs.Spec.DbSystem.CompartmentId + } else { + // If not defined, call GetDbSystem to fetch the details + getRequest := database.GetDbSystemRequest{ + DbSystemId: &dbcsId, + } + + // Call GetDbSystem API using the existing dbClient + getResponse, err := dbClient.GetDbSystem(context.TODO(), getRequest) + if err != nil { + return database.ListDbHomesResponse{}, fmt.Errorf("failed to get DB system details: %v", err) + } + + // Extract the compartment ID from the DB system details + CompartmentId = *getResponse.DbSystem.CompartmentId + } dbHomeReq := database.ListDbHomesRequest{ DbSystemId: &dbcsId, @@ -1056,7 +1588,7 @@ func GetListDatabaseRsp(logger logr.Logger, dbClient database.DatabaseClient, db return response, nil } -func UpdateDbcsSystemIdInst(log logr.Logger, dbClient database.DatabaseClient, dbcs *databasev4.DbcsSystem, kubeClient client.Client, nwClient core.VirtualNetworkClient, wrClient workrequests.WorkRequestClient, databaseID string) error { +func UpdateDbcsSystemIdInst(compartmentId string, log logr.Logger, dbClient database.DatabaseClient, dbcs *databasev4.DbcsSystem, kubeClient client.Client, nwClient core.VirtualNetworkClient, wrClient workrequests.WorkRequestClient, databaseID string) error { // log.Info("Existing DB System Getting Updated with new details in UpdateDbcsSystemIdInst") var err error updateFlag := false @@ -1073,35 +1605,89 @@ func UpdateDbcsSystemIdInst(log logr.Logger, dbClient database.DatabaseClient, d } else { log.Info("Details of oldSpec", "oldSpec", oldSpec) } + + if dbcs.Spec.DbSystem == nil { + dbcs.Spec.DbSystem = &databasev4.DbSystemDetails{} + } + if oldSpec.DbSystem == nil { + oldSpec.DbSystem = &databasev4.DbSystemDetails{} + } + + // Fetch latest DB System state from OCI + dbcsId := dbcs.Spec.Id + dbcsReq := database.GetDbSystemRequest{ + DbSystemId: dbcsId, + } + + response, err := dbClient.GetDbSystem(context.TODO(), dbcsReq) + if err != nil { + log.Error(err, "failed to fetch current DB system from OCI") + return err + } + + current := response.DbSystem // OCI's current state log.Info("Details of updateFlag -> " + fmt.Sprint(updateFlag)) + if dbcs.Spec.DbSystem == nil { + dbcs.Spec.DbSystem = &databasev4.DbSystemDetails{} + } + // Compare and update CPU Core Count + if dbcs.Spec.DbSystem.CpuCoreCount > 0 && + (dbcs.Spec.DbSystem.CpuCoreCount != oldSpec.DbSystem.CpuCoreCount || + dbcs.Spec.DbSystem.CpuCoreCount != *current.CpuCoreCount) { + + log.Info("CPU core count change detected", + "desired", dbcs.Spec.DbSystem.CpuCoreCount, + "old", oldSpec.DbSystem.CpuCoreCount, + "current", *current.CpuCoreCount) - if dbcs.Spec.DbSystem.CpuCoreCount > 0 && ((dbcs.Spec.DbSystem.CpuCoreCount != oldSpec.DbSystem.CpuCoreCount) || (dbcs.Spec.DbSystem.CpuCoreCount != *&dbcs.Status.CpuCoreCount)) { - log.Info("DB System cpu core count is: " + fmt.Sprint(dbcs.Spec.DbSystem.CpuCoreCount) + " DB System old cpu count is: " + fmt.Sprint(oldSpec.DbSystem.CpuCoreCount)) updateDbcsDetails.CpuCoreCount = common.Int(dbcs.Spec.DbSystem.CpuCoreCount) updateFlag = true } - if dbcs.Spec.DbSystem.Shape != "" && ((dbcs.Spec.DbSystem.Shape != oldSpec.DbSystem.Shape) || (dbcs.Spec.DbSystem.Shape != *dbcs.Status.Shape)) { - // log.Info("DB System desired shape is :" + string(dbcs.Spec.DbSystem.Shape) + "DB System old shape is " + string(oldSpec.DbSystem.Shape)) + + // Compare and update Shape + if dbcs.Spec.DbSystem.Shape != "" && + (dbcs.Spec.DbSystem.Shape != oldSpec.DbSystem.Shape || + dbcs.Spec.DbSystem.Shape != *current.Shape) { + + log.Info("Shape change detected", + "desired", dbcs.Spec.DbSystem.Shape, + "old", oldSpec.DbSystem.Shape, + "current", *current.Shape) + updateDbcsDetails.Shape = common.String(dbcs.Spec.DbSystem.Shape) updateFlag = true } - if dbcs.Spec.DbSystem.LicenseModel != "" && ((dbcs.Spec.DbSystem.LicenseModel != oldSpec.DbSystem.LicenseModel) || (dbcs.Spec.DbSystem.LicenseModel != *&dbcs.Status.LicenseModel)) { - licenceModel := getLicenceModel(dbcs) - // log.Info("DB System desired License Model is :" + string(dbcs.Spec.DbSystem.LicenseModel) + "DB Sytsem old License Model is " + string(oldSpec.DbSystem.LicenseModel)) - updateDbcsDetails.LicenseModel = database.UpdateDbSystemDetailsLicenseModelEnum(licenceModel) + // Compare and update License Model + if dbcs.Spec.DbSystem.LicenseModel != "" && + (dbcs.Spec.DbSystem.LicenseModel != oldSpec.DbSystem.LicenseModel || + dbcs.Spec.DbSystem.LicenseModel != string(current.LicenseModel)) { + + log.Info("License model change detected", + "desired", dbcs.Spec.DbSystem.LicenseModel, + "old", oldSpec.DbSystem.LicenseModel, + "current", current.LicenseModel) + + licenseModel := getLicenceModel(dbcs) + updateDbcsDetails.LicenseModel = database.UpdateDbSystemDetailsLicenseModelEnum(licenseModel) updateFlag = true } - if dbcs.Spec.DbSystem.InitialDataStorageSizeInGB != 0 && dbcs.Spec.DbSystem.InitialDataStorageSizeInGB != oldSpec.DbSystem.InitialDataStorageSizeInGB { - // log.Info("DB System desired Storage Size is :" + fmt.Sprint(dbcs.Spec.DbSystem.InitialDataStorageSizeInGB) + "DB System old Storage Size is " + fmt.Sprint(oldSpec.DbSystem.InitialDataStorageSizeInGB)) + // Compare Storage Size only against oldSpec (assumes no dynamic resizing supported) + if dbcs.Spec.DbSystem.InitialDataStorageSizeInGB > 0 && + dbcs.Spec.DbSystem.InitialDataStorageSizeInGB != oldSpec.DbSystem.InitialDataStorageSizeInGB { + + log.Info("Data storage size change detected", + "desired", dbcs.Spec.DbSystem.InitialDataStorageSizeInGB, + "old", oldSpec.DbSystem.InitialDataStorageSizeInGB) + updateDbcsDetails.DataStorageSizeInGBs = &dbcs.Spec.DbSystem.InitialDataStorageSizeInGB updateFlag = true } // // Check and update KMS details if necessary - if (dbcs.Spec.KMSConfig != databasev4.KMSConfig{}) { - if dbcs.Spec.KMSConfig != oldSpec.DbSystem.KMSConfig { + if dbcs.Spec.KMSConfig != nil { + if !reflect.DeepEqual(dbcs.Spec.KMSConfig, oldSpec.DbSystem.KMSConfig) { log.Info("Updating KMS details in Existing Database") kmsKeyID := dbcs.Status.KMSDetailsStatus.KeyId @@ -1125,7 +1711,7 @@ func UpdateDbcsSystemIdInst(log logr.Logger, dbClient database.DatabaseClient, d } // Assign all available fields to KMSConfig - dbcs.Spec.DbSystem.KMSConfig = databasev4.KMSConfig{ + dbcs.Spec.DbSystem.KMSConfig = &databasev4.KMSConfig{ VaultName: dbcs.Spec.KMSConfig.VaultName, CompartmentId: dbcs.Spec.KMSConfig.CompartmentId, KeyName: dbcs.Spec.KMSConfig.KeyName, @@ -1148,7 +1734,7 @@ func UpdateDbcsSystemIdInst(log logr.Logger, dbClient database.DatabaseClient, d migrateRequest.AdminPassword = common.String(dbAdminPassword) } // Change the phase to "Updating" - if statusErr := SetLifecycleState(kubeClient, dbClient, dbcs, databasev4.Update, nwClient, wrClient); statusErr != nil { + if statusErr := SetLifecycleState(compartmentId, kubeClient, dbClient, dbcs, databasev4.Update, nwClient, wrClient); statusErr != nil { return statusErr } // Send the request @@ -1168,7 +1754,7 @@ func UpdateDbcsSystemIdInst(log logr.Logger, dbClient database.DatabaseClient, d // // Wait for the database to reach the desired state after migration, timeout for 2 hours // Define timeout and check interval - timeout := 2 * time.Hour + timeout := 4 * time.Hour checkInterval := 1 * time.Minute err = WaitForDatabaseState(log, dbClient, databaseID, "AVAILABLE", timeout, checkInterval) @@ -1177,7 +1763,7 @@ func UpdateDbcsSystemIdInst(log logr.Logger, dbClient database.DatabaseClient, d return err } // Change the phase to "Available" - if statusErr := SetLifecycleState(kubeClient, dbClient, dbcs, databasev4.Available, nwClient, wrClient); statusErr != nil { + if statusErr := SetLifecycleState(compartmentId, kubeClient, dbClient, dbcs, databasev4.Available, nwClient, wrClient); statusErr != nil { return statusErr } @@ -1186,7 +1772,13 @@ func UpdateDbcsSystemIdInst(log logr.Logger, dbClient database.DatabaseClient, d } log.Info("Details of updateFlag after validations is " + fmt.Sprint(updateFlag)) + if updateFlag { + cdbId := *dbcs.Status.DbInfo[0].Id + // Ensure DB system is AVAILABLE + if err := waitForDbSystemAvailable(cdbId, dbClient, *dbcs.Spec.Id, 30*time.Minute, log); err != nil { + return fmt.Errorf("cannot update DB system within 30 minutes, wait failed: %w", err) + } updateDbcsRequest := database.UpdateDbSystemRequest{ DbSystemId: common.String(*dbcs.Spec.Id), UpdateDbSystemDetails: updateDbcsDetails, @@ -1197,7 +1789,7 @@ func UpdateDbcsSystemIdInst(log logr.Logger, dbClient database.DatabaseClient, d } // Change the phase to "Provisioning" - if statusErr := SetLifecycleState(kubeClient, dbClient, dbcs, databasev4.Update, nwClient, wrClient); statusErr != nil { + if statusErr := SetLifecycleState(compartmentId, kubeClient, dbClient, dbcs, databasev4.Update, nwClient, wrClient); statusErr != nil { return statusErr } // Check the State @@ -1210,6 +1802,48 @@ func UpdateDbcsSystemIdInst(log logr.Logger, dbClient database.DatabaseClient, d return nil } +func waitForDbSystemAvailable(cdbId string, dbClient database.DatabaseClient, dbSystemId string, maxWait time.Duration, log logr.Logger) error { + start := time.Now() + + for { + // 1. Check DB System lifecycle state + dbSysResp, err := dbClient.GetDbSystem(context.TODO(), database.GetDbSystemRequest{ + DbSystemId: &dbSystemId, + }) + if err != nil { + return fmt.Errorf("failed to get DB system: %w", err) + } + dbSysState := string(dbSysResp.DbSystem.LifecycleState) + + // 2. Check CDB lifecycle state + dbResp, err := dbClient.GetDatabase(context.TODO(), database.GetDatabaseRequest{ + DatabaseId: &cdbId, + }) + if err != nil { + return fmt.Errorf("failed to get CDB database: %w", err) + } + dbState := string(dbResp.Database.LifecycleState) + + log.Info("DBSystem state: %s, CDB state: %s", dbSysState, dbState) + + if dbSysState == "AVAILABLE" && dbState == "AVAILABLE" { + log.Info("Both Db System and Db home is in available state, proceeding with update") + return nil // both ready + } + + if time.Since(start) > maxWait { + return fmt.Errorf("timeout: DBSystem: %s, CDB: %s", dbSysState, dbState) + } + log.Info("Either of Db System or Db home is not in available state, rechecking in 30 seconds") + + time.Sleep(30 * time.Second) + } +} + +func isFieldUpdated[T comparable](specVal T, oldVal T, currentVal T) bool { + return specVal != oldVal || specVal != currentVal +} + func WaitForDatabaseState( log logr.Logger, dbClient database.DatabaseClient, @@ -1271,58 +1905,139 @@ func UpdateDbcsSystemId(kubeClient client.Client, dbcs *databasev4.DbcsSystem) e return kubeClient.Patch(context.TODO(), dbcs, patch) } -func CheckResourceState(logger logr.Logger, dbClient database.DatabaseClient, Id string, currentState string, expectedState string) (string, error) { - // The database OCID is not available when the provisioning is onging. - // Retry until the new DbcsSystem is ready. +// CheckDataGuardAssociationState will check the lifecycle state of the Data Guard Association +// and wait until it reaches the expected state (e.g., "AVAILABLE"). +func CheckDataGuardAssociationState(logger logr.Logger, dbClient database.DatabaseClient, associationId string, currentState string, expectedState string, databaseId string) (string, error) { + // The DataGuard Association OCID is not available when provisioning is ongoing. + // Retry until the new Data Guard Association is ready. var state string var err error for { - state, err = GetResourceState(logger, dbClient, Id) + state, err = GetDataGuardAssociationState(logger, dbClient, associationId, databaseId) if err != nil { - logger.Info("Error occurred while collecting the resource life cycle state") + logger.Info("Error occurred while collecting the resource lifecycle state") return "", err } if string(state) == expectedState { break - } else if string(state) == currentState { - logger.Info("DB System current state is still:" + string(state) + ". Sleeping for 60 seconds.") + } else if string(state) != expectedState { + logger.Info("Data Guard Association current state is still:" + string(state) + ". Sleeping for 60 seconds.") time.Sleep(60 * time.Second) continue - } else { - msg := "DB System current state " + string(state) + " is not matching " + expectedState - logger.Info(msg) - return "", errors.New(msg) } } return "", nil } -func GetResourceState(logger logr.Logger, dbClient database.DatabaseClient, Id string) (string, error) { +// GetDataGuardAssociationState retrieves the lifecycle state of the Data Guard Association. +func GetDataGuardAssociationState(logger logr.Logger, dbClient database.DatabaseClient, associationId string, databaseID string) (string, error) { // Context with 2-hour timeout + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Hour) + defer cancel() - dbcsId := Id - dbcsReq := database.GetDbSystemRequest{ - DbSystemId: &dbcsId, + request := database.GetDataGuardAssociationRequest{ + DatabaseId: common.String(databaseID), + DataGuardAssociationId: common.String(associationId), } + desiredState := "AVAILABLE" - response, err := dbClient.GetDbSystem(context.TODO(), dbcsReq) - if err != nil { - return "", err + ticker := time.NewTicker(1 * time.Minute) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + // 2-hour timeout reached + return "", context.DeadlineExceeded + case <-ticker.C: + logger.Info("Polling Data Guard association state", "DatabaseID", databaseID, "AssociationID", associationId) + + // Make the request using the context + response, err := dbClient.GetDataGuardAssociation(ctx, request) + if err != nil { + logger.Error(err, "Failed to get Data Guard association state") + return "", err + } + + state := string(response.LifecycleState) + logger.Info("Current state", "State", state) + + if state == desiredState { + return state, nil + } + } } +} - state := string(response.LifecycleState) +// CheckResourceState waits until the resource moves from a transient state (e.g. PROVISIONING, UPDATING) +// to the expected state (e.g. AVAILABLE). It retries until success, timeout (120 minutes), or unexpected state occurs. +func CheckResourceState(logger logr.Logger, dbClient database.DatabaseClient, id string, transientState string, expectedState string) (string, error) { + var state string + var err error - return state, nil + timeout := 240 * time.Minute + start := time.Now() + + for { + // Timeout guard + if time.Since(start) > timeout { + msg := fmt.Sprintf("timed out after %v waiting for DB System %s to reach state %s (last known state: %s)", + timeout, id, expectedState, state) + logger.Error(fmt.Errorf("timeout"), msg, "Id", id) + return state, errors.New(msg) + } + + state, err = GetResourceState(logger, dbClient, id) + if err != nil { + logger.Error(err, "Error occurred while collecting the resource lifecycle state", "Id", id) + return "", err + } + + switch state { + case expectedState: + logger.Info("DB System reached expected state", "State", state, "Id", id) + return state, nil + // Explicitly handle UPDATING as transient state (for patching) + case string(database.DbSystemLifecycleStateUpdating): + logger.Info("DB System is still updating ", "State", state, "Id", id) + time.Sleep(60 * time.Second) + continue + case string(database.DbSystemLifecycleStateUpgrading): + logger.Info("DB System is still upgrading ", "State", state, "Id", id) + time.Sleep(60 * time.Second) + continue + case transientState: + logger.Info("DB System still in transient state", "State", state, "Id", id) + time.Sleep(60 * time.Second) // sleep before re-checking + continue + + default: + msg := fmt.Sprintf("DB System state %s is not matching expected state %s", state, expectedState) + logger.Error(errors.New(msg), "Unexpected DB System state", "Id", id) + return state, errors.New(msg) + } + } } -func SetDBCSStatus(dbClient database.DatabaseClient, dbcs *databasev4.DbcsSystem, nwClient core.VirtualNetworkClient, wrClient workrequests.WorkRequestClient) error { +// GetResourceState fetches the current lifecycle state of the DbSystem from OCI. +func GetResourceState(logger logr.Logger, dbClient database.DatabaseClient, id string) (string, error) { + req := database.GetDbSystemRequest{ + DbSystemId: common.String(id), + } - if dbcs.Spec.Id == nil { - dbcs.Status.State = "FAILED" - return nil + resp, err := dbClient.GetDbSystem(context.TODO(), req) + if err != nil { + return "", err } + state := string(resp.LifecycleState) + logger.Info("Fetched DB System lifecycle state", "State", state, "Id", id) + return state, nil +} + +func SetDBCSStatus(state databasev4.LifecycleState, compartmentId string, dbClient database.DatabaseClient, dbcs *databasev4.DbcsSystem, nwClient core.VirtualNetworkClient, wrClient workrequests.WorkRequestClient) error { + if dbcs.Spec.Id == nil { dbcs.Status.State = "FAILED" return nil @@ -1338,6 +2053,7 @@ func SetDBCSStatus(dbClient database.DatabaseClient, dbcs *databasev4.DbcsSystem if err != nil { return err } + compartmentId = *resp.CompartmentId dbcs.Status.AvailabilityDomain = *resp.AvailabilityDomain dbcs.Status.CpuCoreCount = *resp.CpuCoreCount @@ -1358,12 +2074,22 @@ func SetDBCSStatus(dbClient database.DatabaseClient, dbcs *databasev4.DbcsSystem dbcs.Status.Network.ListenerPort = resp.ListenerPort dbcs.Status.Network.HostName = *resp.Hostname dbcs.Status.Network.DomainName = *resp.Domain - if dbcs.Spec.KMSConfig.CompartmentId != "" { + if resp.Version != nil { + dbcs.Status.DbVersion = *resp.Version + } else { + dbcs.Status.DbVersion = "" + } + + if dbcs.Spec.KMSConfig != nil && dbcs.Spec.KMSConfig.CompartmentId != "" { dbcs.Status.KMSDetailsStatus.CompartmentId = dbcs.Spec.KMSConfig.CompartmentId dbcs.Status.KMSDetailsStatus.VaultName = dbcs.Spec.KMSConfig.VaultName } - dbcs.Status.State = databasev4.LifecycleState(resp.LifecycleState) - if dbcs.Spec.KMSConfig.CompartmentId != "" { + if state == "" { + dbcs.Status.State = databasev4.LifecycleState(resp.LifecycleState) + } else { + dbcs.Status.State = state + } + if dbcs.Spec.KMSConfig != nil && dbcs.Spec.KMSConfig.CompartmentId != "" { dbcs.Status.KMSDetailsStatus.CompartmentId = dbcs.Spec.KMSConfig.CompartmentId dbcs.Status.KMSDetailsStatus.VaultName = dbcs.Spec.KMSConfig.VaultName } @@ -1383,7 +2109,7 @@ func SetDBCSStatus(dbClient database.DatabaseClient, dbcs *databasev4.DbcsSystem // Work Request Ststaus dbWorkRequest := databasev4.DbWorkrequests{} - dbWorks, err := getWorkRequest(*resp.OpcRequestId, wrClient, dbcs) + dbWorks, err := getWorkRequest(compartmentId, *resp.OpcRequestId, wrClient, dbcs) if err == nil { for _, dbWork := range dbWorks { //status := checkValue(dbcs, dbWork.Id) @@ -1418,11 +2144,11 @@ func SetDBCSStatus(dbClient database.DatabaseClient, dbcs *databasev4.DbcsSystem dbcs.Status.DbInfo = dbcs.Status.DbInfo[:0] dbStatus := databasev4.DbStatus{} - dbHomes, err := getDbHomeList(dbClient, dbcs) + dbHomes, err := getDbHomeList(compartmentId, dbClient, dbcs) if err == nil { for _, dbHome := range dbHomes { - dbDetails, err := getDList(dbClient, dbcs, dbHome.Id) + dbDetails, err := getDList(compartmentId, dbClient, dbcs, dbHome.Id) for _, dbDetail := range dbDetails { if err == nil { dbStatus.Id = dbDetail.Id @@ -1430,6 +2156,13 @@ func SetDBCSStatus(dbClient database.DatabaseClient, dbcs *databasev4.DbcsSystem dbStatus.DbName = *dbDetail.DbName dbStatus.DbUniqueName = *dbDetail.DbUniqueName dbStatus.DbWorkload = *dbDetail.DbWorkload + if dbDetail.ConnectionStrings != nil && + dbDetail.ConnectionStrings.CdbDefault != nil && + dbDetail.ConnectionStrings.CdbIpDefault != nil { + + dbStatus.ConnectionString = *dbDetail.ConnectionStrings.CdbDefault + dbStatus.ConnectionStringLong = *dbDetail.ConnectionStrings.CdbIpDefault + } } dbcs.Status.DbInfo = append(dbcs.Status.DbInfo, dbStatus) dbStatus = databasev4.DbStatus{} @@ -1439,14 +2172,14 @@ func SetDBCSStatus(dbClient database.DatabaseClient, dbcs *databasev4.DbcsSystem return nil } -func getDbHomeList(dbClient database.DatabaseClient, dbcs *databasev4.DbcsSystem) ([]database.DbHomeSummary, error) { +func getDbHomeList(compartmentId string, dbClient database.DatabaseClient, dbcs *databasev4.DbcsSystem) ([]database.DbHomeSummary, error) { var items []database.DbHomeSummary dbcsId := *dbcs.Spec.Id dbcsReq := database.ListDbHomesRequest{ DbSystemId: &dbcsId, - CompartmentId: &dbcs.Spec.DbSystem.CompartmentId, + CompartmentId: &compartmentId, } resp, err := dbClient.ListDbHomes(context.TODO(), dbcsReq) @@ -1457,13 +2190,13 @@ func getDbHomeList(dbClient database.DatabaseClient, dbcs *databasev4.DbcsSystem return resp.Items, nil } -func getDList(dbClient database.DatabaseClient, dbcs *databasev4.DbcsSystem, dbHomeId *string) ([]database.DatabaseSummary, error) { +func getDList(compartmentId string, dbClient database.DatabaseClient, dbcs *databasev4.DbcsSystem, dbHomeId *string) ([]database.DatabaseSummary, error) { dbcsId := *dbcs.Spec.Id var items []database.DatabaseSummary dbcsReq := database.ListDatabasesRequest{ SystemId: &dbcsId, - CompartmentId: &dbcs.Spec.DbSystem.CompartmentId, + CompartmentId: &compartmentId, DbHomeId: dbHomeId, } @@ -1517,6 +2250,12 @@ func ValidateSpex(logger logr.Logger, kubeClient client.Client, dbClient databas } // Check if last Successful update nil or not if lastSuccSpec == nil { + if dbcs == nil { + return fmt.Errorf("DbcsSystem is nil") + } + if dbcs.Spec.DbSystem == nil { + return fmt.Errorf("DbSystem spec is missing in DbcsSystem %s", dbcs.Name) + } if dbcs.Spec.DbSystem.DbVersion != "" { _, err = GetDbLatestVersion(dbClient, dbcs, "") if err != nil { @@ -1565,3 +2304,278 @@ func ValidateSpex(logger logr.Logger, kubeClient client.Client, dbClient databas return nil } + +func CreateDbcsBackup( + compartmentId string, + logger logr.Logger, + dbClient database.DatabaseClient, + dbcs *databasev4.DbcsSystem, + kubeClient client.Client, + nwClient core.VirtualNetworkClient, + wrClient workrequests.WorkRequestClient, +) (string, error) { + + ctx := context.TODO() + + // Safety check: ensure DB system OCID is set + if dbcs.Spec.Id == nil || *dbcs.Spec.Id == "" { + return "", fmt.Errorf("cannot create backup: DbcsSystem ID is not set") + } + // Get DB Home Details + listDbHomeRsp, err := GetListDbHomeRsp(logger, dbClient, dbcs) + if err != nil { + logger.Info("Error Occurred while getting List of DBHomes") + return "", err + } + dbHomeId := listDbHomeRsp.Items[0].Id + // Retrieve the list of databases in the DB system + listDbsRequest := database.ListDatabasesRequest{ + CompartmentId: &compartmentId, + SystemId: dbcs.Spec.Id, + DbHomeId: dbHomeId, + } + + listDbsResponse, err := dbClient.ListDatabases(ctx, listDbsRequest) + if err != nil { + logger.Error(err, "Failed to list databases for DB system", "DbcsSystemID", *dbcs.Spec.Id) + return "", err + } + + if len(listDbsResponse.Items) == 0 { + return "", fmt.Errorf("no databases found in DB system with ID: %s", *dbcs.Spec.Id) + } + + // Assume the first database is the one to back up (customize as needed) + databaseId := listDbsResponse.Items[0].Id + + // Generate a unique display name for the backup + // Determine the backup name + var backupPrefixName string + if dbcs.Spec.DbSystem.BackupDisplayName != "" { + backupPrefixName = dbcs.Spec.DbSystem.BackupDisplayName + } else { + backupPrefixName = "backup" + } + backupName := fmt.Sprintf("%s-%s", backupPrefixName, time.Now().Format("20060102-150405")) + + // Check if backup with prefix already exists + listBackupsReq := database.ListBackupsRequest{ + DatabaseId: databaseId, + } + listBackupsResp, err := dbClient.ListBackups(ctx, listBackupsReq) + if err != nil { + logger.Error(err, "Failed to list backups") + return "", err + } + + // Check if a backup with same name already tracked in status + for _, b := range dbcs.Status.Backups { + if strings.HasPrefix(b.Name, backupPrefixName) { + logger.Info("Backup already tracked in status", "BackupName", b.Name) + return b.BackupID, nil + } + } + + // Compare against latest backup stored in status or name prefix + for _, backup := range listBackupsResp.Items { + if backup.DisplayName != nil && strings.HasPrefix(*backup.DisplayName, backupPrefixName) { + logger.Info("Backup already exists, skipping new backup creation", "ExistingBackup", *backup.DisplayName) + return *backup.Id, nil + } + } + + // Build the CreateBackupRequest + createBackupReq := database.CreateBackupRequest{ + CreateBackupDetails: database.CreateBackupDetails{ + DatabaseId: databaseId, + DisplayName: common.String(backupName), + }, + } + + logger.Info("Creating manual backup for database", "DatabaseId", *databaseId, "BackupName", backupName) + + // Change the phase to "Updating" + if statusErr := SetLifecycleState(compartmentId, kubeClient, dbClient, dbcs, databasev4.Update, nwClient, wrClient); statusErr != nil { + return "", statusErr + } + // Send the request + createBackupResp, err := dbClient.CreateBackup(ctx, createBackupReq) + if err != nil { + logger.Error(err, "Failed to create backup", "DatabaseId", *databaseId) + // Change the phase to "Failed" + if statusErr := SetLifecycleState(compartmentId, kubeClient, dbClient, dbcs, databasev4.Failed, nwClient, wrClient); statusErr != nil { + return "", statusErr + } + return "", err + } + + logger.Info("Backup creation initiated", "BackupID", *createBackupResp.Backup.Id) + backupID := createBackupResp.Backup.Id + // ---- Wait for backup to complete ---- + logger.Info("Waiting for backup to reach ACTIVE state...") + + waitDuration := 240 * time.Minute + pollInterval := 30 * time.Second + timeout := time.After(waitDuration) + ticker := time.NewTicker(pollInterval) + defer ticker.Stop() + + for { + select { + case <-timeout: + return *backupID, fmt.Errorf("timeout: backup %s did not complete within %v", *backupID, waitDuration) + case <-ticker.C: + // Get current backup status + getBackupReq := database.GetBackupRequest{ + BackupId: backupID, + } + getBackupResp, err := dbClient.GetBackup(ctx, getBackupReq) + if err != nil { + logger.Error(err, "Failed to fetch backup status", "BackupID", *backupID) + continue + } + + lifecycle := getBackupResp.Backup.LifecycleState + logger.Info("Polling backup status", "BackupID", *backupID, "State", lifecycle) + + if lifecycle == database.BackupLifecycleStateActive { + logger.Info("Backup completed successfully", "BackupID", *backupID) + // After successful creation and backup becomes ACTIVE + listBackupsReq := database.ListBackupsRequest{ + DatabaseId: databaseId, + } + + listBackupsResp, err := dbClient.ListBackups(ctx, listBackupsReq) + if err != nil { + logger.Error(err, "Failed to list backups") + return *backupID, err + } + + // Reset and populate status.Backups with up-to-date backup list + dbcs.Status.Backups = []databasev4.BackupInfo{} + for _, b := range listBackupsResp.Items { + if b.Id != nil && b.DisplayName != nil && b.TimeStarted != nil { + dbcs.Status.Backups = append(dbcs.Status.Backups, databasev4.BackupInfo{ + Name: *b.DisplayName, + BackupID: *b.Id, + Timestamp: b.TimeStarted.Format(time.RFC3339), + }) + } + } + // Change the phase to "Available" + if statusErr := SetLifecycleState(compartmentId, kubeClient, dbClient, dbcs, databasev4.Available, nwClient, wrClient); statusErr != nil { + return "", statusErr + } + return *backupID, nil + } + + if lifecycle == database.BackupLifecycleStateFailed { + return *backupID, fmt.Errorf("backup failed: BackupID=%s", *backupID) + } + } + } + +} + +func RestoreDbcsToPoint( + compartmentId string, + logger logr.Logger, + dbClient database.DatabaseClient, + dbcs *databasev4.DbcsSystem, + restoreOpt databasev4.RestoreConfig, + kubeClient client.Client, + nwClient core.VirtualNetworkClient, + wrClient workrequests.WorkRequestClient, +) error { + ctx := context.TODO() + + if dbcs.Spec.Id == nil || *dbcs.Spec.Id == "" { + return fmt.Errorf("cannot restore: DbcsSystem ID is not set") + } + + // Get DB Home + dbHomeResp, err := GetListDbHomeRsp(logger, dbClient, dbcs) + if err != nil || len(dbHomeResp.Items) == 0 { + return fmt.Errorf("no DB Homes found for DB system: %v", err) + } + dbHomeId := dbHomeResp.Items[0].Id + + // Get DB + listDbsResp, err := dbClient.ListDatabases(ctx, database.ListDatabasesRequest{ + CompartmentId: &compartmentId, + SystemId: dbcs.Spec.Id, + DbHomeId: dbHomeId, + }) + if err != nil || len(listDbsResp.Items) == 0 { + return fmt.Errorf("no databases found to restore") + } + dbID := listDbsResp.Items[0].Id + + // Change the phase to "Updating" + logger.Info("Changing State to Updating for ", "DatabaseID", *dbID, "RestoreOption", restoreOpt) + if statusErr := SetLifecycleState(compartmentId, kubeClient, dbClient, dbcs, databasev4.Update, nwClient, wrClient); statusErr != nil { + return statusErr + } + + restoreDetails := database.RestoreDatabaseDetails{} + + if restoreOpt.Latest { + restoreDetails.Latest = common.Bool(true) + } + if restoreOpt.Timestamp != nil { + restoreDetails.Timestamp = &common.SDKTime{Time: restoreOpt.Timestamp.Time} + } + if restoreOpt.SCN != nil { + restoreDetails.DatabaseSCN = restoreOpt.SCN + } + + restoreReq := database.RestoreDatabaseRequest{ + DatabaseId: dbID, + RestoreDatabaseDetails: restoreDetails, + } + + logger.Info("Initiating restore operation", "DatabaseID", *dbID, "RestoreOption", restoreOpt) + + restoreResp, err := dbClient.RestoreDatabase(ctx, restoreReq) + if err != nil { + logger.Error(err, "Failed to restore database") + SetLifecycleState(compartmentId, kubeClient, dbClient, dbcs, databasev4.Failed, nwClient, wrClient) + return err + } + + // Poll for completion + workRequestId := restoreResp.OpcWorkRequestId + logger.Info("Restore initiated", "WorkRequestID", *workRequestId) + + timeout := time.After(240 * time.Minute) + ticker := time.NewTicker(30 * time.Second) + defer ticker.Stop() + + for { + select { + case <-timeout: + return fmt.Errorf("restore timed out after 60 minutes") + case <-ticker.C: + dbStateResp, err := dbClient.GetDatabase(ctx, database.GetDatabaseRequest{DatabaseId: dbID}) + if err != nil { + logger.Error(err, "Polling error") + continue + } + state := dbStateResp.Database.LifecycleState + logger.Info("Polling Restore Operation", "DatabaseID", *dbID, "State", state) + + if state == database.DatabaseLifecycleStateAvailable { + logger.Info("Database restore completed", "DatabaseID", *dbID) + // Change the phase to "Available" + if statusErr := SetLifecycleState(compartmentId, kubeClient, dbClient, dbcs, databasev4.Available, nwClient, wrClient); statusErr != nil { + return statusErr + } + return nil + } else if state == database.DatabaseLifecycleStateRestoreFailed { + return fmt.Errorf("restore failed: DatabaseID=%s", *dbID) + } else if state == database.DatabaseLifecycleStateFailed { + return fmt.Errorf("restore failed: DatabaseID=%s", *dbID) + } + } + } +} diff --git a/commons/dbcssystem/dcommon.go b/commons/dbcssystem/dcommon.go index beaa7c38..bef676d9 100644 --- a/commons/dbcssystem/dcommon.go +++ b/commons/dbcssystem/dcommon.go @@ -52,14 +52,20 @@ import ( databasev4 "github.com/oracle/oracle-database-operator/apis/database/v4" ) -func GetDbHomeDetails(kubeClient client.Client, dbClient database.DatabaseClient, dbcs *databasev4.DbcsSystem) (database.CreateDbHomeDetails, error) { +func GetDbHomeDetails(kubeClient client.Client, dbClient database.DatabaseClient, dbcs *databasev4.DbcsSystem, id string) (database.CreateDbHomeDetails, error) { dbHomeDetails := database.CreateDbHomeDetails{} - dbHomeReq, err := GetDbLatestVersion(dbClient, dbcs, "") if err != nil { return database.CreateDbHomeDetails{}, err } + if dbcs.Spec.Id != nil { + dbHomeReq, err = GetDbLatestVersion(dbClient, dbcs, *dbcs.Spec.Id) + if err != nil { + return database.CreateDbHomeDetails{}, err + } + } + dbHomeDetails.DbVersion = &dbHomeReq dbDetailsReq, err := GetDBDetails(kubeClient, dbcs) @@ -180,16 +186,18 @@ func GetDBDetails(kubeClient client.Client, dbcs *databasev4.DbcsSystem) (databa dbDetails.PdbName = &dbcs.Spec.DbSystem.PdbName } - //backup configuration - if dbcs.Spec.DbSystem.DbBackupConfig.AutoBackupEnabled != nil { - if *dbcs.Spec.DbSystem.DbBackupConfig.AutoBackupEnabled { - backupConfig, err := getBackupConfig(kubeClient, dbcs) - if err != nil { - return dbDetails, err - } else { - dbDetails.DbBackupConfig = &backupConfig - } + backupCfg := dbcs.Spec.DbSystem.DbBackupConfig + if dbcs != nil && + dbcs.Spec.DbSystem != nil && + backupCfg != nil && + backupCfg.AutoBackupEnabled != nil && + *backupCfg.AutoBackupEnabled { + + backupConfig, err := getBackupConfig(kubeClient, dbcs) + if err != nil { + return dbDetails, err } + dbDetails.DbBackupConfig = &backupConfig } return dbDetails, nil @@ -383,10 +391,10 @@ func GetDBbDiskRedundancy( return database.LaunchDbSystemDetailsDiskRedundancyNormal } -func getWorkRequest(workId string, wrClient workrequests.WorkRequestClient, dbcs *databasev4.DbcsSystem) ([]workrequests.WorkRequestSummary, error) { +func getWorkRequest(compartmentId string, workId string, wrClient workrequests.WorkRequestClient, dbcs *databasev4.DbcsSystem) ([]workrequests.WorkRequestSummary, error) { var workReq []workrequests.WorkRequestSummary - req := workrequests.ListWorkRequestsRequest{CompartmentId: &dbcs.Spec.DbSystem.CompartmentId, OpcRequestId: &workId, ResourceId: dbcs.Spec.Id} + req := workrequests.ListWorkRequestsRequest{CompartmentId: &compartmentId, OpcRequestId: &workId, ResourceId: dbcs.Spec.Id} resp, err := wrClient.ListWorkRequests(context.Background(), req) if err != nil { return workReq, err diff --git a/commons/k8s/create.go b/commons/k8s/create.go index cd836af7..82e100a1 100644 --- a/commons/k8s/create.go +++ b/commons/k8s/create.go @@ -94,6 +94,9 @@ func CreateAutonomousBackup(kubeClient client.Client, K8sAdb: dbv4.K8sAdbSpec{ Name: common.String(ownerAdb.Name), }, + OciAdb: dbv4.OciAdbSpec{ + Id: backupSummary.AutonomousDatabaseId, + }, }, DisplayName: backupSummary.DisplayName, AutonomousDatabaseBackupOCID: backupSummary.Id, diff --git a/commons/k8s/utils.go b/commons/k8s/utils.go index 37ec1a3f..0d6ea83b 100644 --- a/commons/k8s/utils.go +++ b/commons/k8s/utils.go @@ -84,3 +84,11 @@ func Patch(kubeClient client.Client, obj client.Object, path string, value inter patch := client.RawPatch(types.JSONPatchType, payloadBytes) return kubeClient.Patch(context.TODO(), obj, patch) } + +func Int64Pointer(d int64) *int64 { + return &d +} + +func BoolPointer(d bool) *bool { + return &d +} diff --git a/commons/multitenant/lrest/common.go b/commons/multitenant/lrest/common.go index e72e85b0..51194d13 100644 --- a/commons/multitenant/lrest/common.go +++ b/commons/multitenant/lrest/common.go @@ -39,7 +39,9 @@ package lrest import ( + "crypto/rand" "crypto/rsa" + "crypto/sha256" "crypto/x509" "encoding/base64" "encoding/pem" @@ -52,7 +54,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" ) -func CommonDecryptWithPrivKey(Key string, Buffer string, req ctrl.Request) (string, error) { +func CommonDecryptWithPrivKey2(Key string, Buffer string, req ctrl.Request) (string, error) { Debug := 0 block, _ := pem.Decode([]byte(Key)) @@ -73,7 +75,7 @@ func CommonDecryptWithPrivKey(Key string, Buffer string, req ctrl.Request) (stri return "", err } - decryptedB, err := rsa.DecryptPKCS1v15(nil, pkcs8PrivateKey.(*rsa.PrivateKey), encString64) + decryptedB, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, pkcs8PrivateKey.(*rsa.PrivateKey), encString64, nil) if err != nil { fmt.Printf("Failed to decrypt string %s\n", err.Error()) return "", err @@ -111,3 +113,173 @@ func ParseConfigMapData(cfgmap *corev1.ConfigMap) []string { return tokens } + +// * STATE TABLE *// +const ( + PDBCRT = 0x00000001 /* Create pdb */ + PDBOPN = 0x00000002 /* Open pdb read write */ + PDBCLS = 0x00000004 /* Close pdb */ + PDBDIC = 0x00000008 /* Drop pdb include datafiles */ + OCIHDL = 0x00000010 /* OCI handle allocation */ + OCICON = 0x00000020 /* Rdbms connection */ + FNALAZ = 0x00000040 /* Finalizer configured */ + PDBUPL = 0x00000080 /* Unplug pdb */ + PDBPLG = 0x00000100 /* plug pdb */ + /* Error section */ + PDBCRE = 0x00001000 /* PDB creation error */ + PDBOPE = 0x00002000 /* PDB open error */ + PDBCLE = 0x00004000 /* PDB close error */ + OCIHDE = 0x00008000 /* Allocation Handle Error */ + OCICOE = 0x00010000 /* CDD connection Error */ + FNALAE = 0x00020000 + PDBUPE = 0x00040000 /* Unplug Error */ + PDBPLE = 0x00080000 /* Plug Error */ + PDBPLW = 0x00100000 /* Plug Warining */ + /* Autodiscover */ + PDBAUT = 0x01000000 /* Autodisover */ +) + +// * CONFIG MAP STATUS * // +const ( + MPAPPL = 0x00000001 /* The map config has been applyed */ + MPSYNC = 0x00000002 /* The map config is in sync with v$parameters where is default=flase */ + MPEMPT = 0x00000004 /* The map is empty - not specify */ + MPWARN = 0x00000008 /* Map applied with warnings */ + MPINIT = 0x00000010 /* Config map init */ + SPARE3 = 0x00000020 +) + +func ParseTnsAlias2(tns *string, lrpdbsrv *string) { + fmt.Printf("Analyzing string [%s]\n", *tns) + fmt.Printf("Relacing srv [%s]\n", *lrpdbsrv) + var swaptns string + + if strings.Contains(strings.ToUpper(*tns), "SERVICE_NAME") == false { + fmt.Print("Cannot generate tns alias for pdb") + return + } + + if strings.Contains(strings.ToUpper(*tns), "ORACLE_SID") == true { + fmt.Print("Cannot generate tns alias for pdb") + return + } + + *tns = strings.ReplaceAll(*tns, " ", "") + + swaptns = fmt.Sprintf("SERVICE_NAME=%s", *lrpdbsrv) + tnsreg := regexp.MustCompile(`SERVICE_NAME=\w+`) + *tns = tnsreg.ReplaceAllString(*tns, swaptns) + + fmt.Printf("Newstring [%s]\n", *tns) + +} + +func Bid(bitmask int, bitval int) int { + bitmask ^= ((bitval) & (bitmask)) + return bitmask +} + +func Bit(bitmask int, bitval int) bool { + if ((bitmask) & (bitval)) != 0 { + return true + } else { + return false + } +} + +func Bis(bitmask int, bitval int) int { + bitmask = ((bitmask) | (bitval)) + return bitmask +} + +func Bitmaskprint(bitmask int) string { + BitRead := "|" + if Bit(bitmask, PDBCRT) { + BitRead = strings.Join([]string{BitRead, "PDBCRT|"}, "") + } + if Bit(bitmask, PDBOPN) { + BitRead = strings.Join([]string{BitRead, "PDBOPN|"}, "") + } + if Bit(bitmask, PDBCLS) { + BitRead = strings.Join([]string{BitRead, "PDBCLS|"}, "") + } + if Bit(bitmask, PDBDIC) { + BitRead = strings.Join([]string{BitRead, "PDBDIC|"}, "") + } + if Bit(bitmask, OCIHDL) { + BitRead = strings.Join([]string{BitRead, "OCIHDL|"}, "") + } + if Bit(bitmask, OCICON) { + BitRead = strings.Join([]string{BitRead, "OCICON|"}, "") + } + if Bit(bitmask, FNALAZ) { + BitRead = strings.Join([]string{BitRead, "FNALAZ|"}, "") + } + if Bit(bitmask, PDBUPL) { + BitRead = strings.Join([]string{BitRead, "PDBUPL|"}, "") + } + if Bit(bitmask, PDBPLG) { + BitRead = strings.Join([]string{BitRead, "PDBPLG|"}, "") + } + + if Bit(bitmask, PDBCRE) { + BitRead = strings.Join([]string{BitRead, "PDBCRE|"}, "") + } + if Bit(bitmask, PDBOPE) { + BitRead = strings.Join([]string{BitRead, "PDBOPE|"}, "") + } + if Bit(bitmask, PDBCLE) { + BitRead = strings.Join([]string{BitRead, "PDBCLE|"}, "") + } + if Bit(bitmask, OCIHDE) { + BitRead = strings.Join([]string{BitRead, "OCIHDE|"}, "") + } + if Bit(bitmask, OCICOE) { + BitRead = strings.Join([]string{BitRead, "OCICOE|"}, "") + } + if Bit(bitmask, FNALAE) { + BitRead = strings.Join([]string{BitRead, "FNALAE|"}, "") + } + if Bit(bitmask, PDBUPE) { + BitRead = strings.Join([]string{BitRead, "PDBUPE|"}, "") + } + if Bit(bitmask, PDBPLE) { + BitRead = strings.Join([]string{BitRead, "PDBPLE|"}, "") + } + if Bit(bitmask, PDBPLW) { + BitRead = strings.Join([]string{BitRead, "PDBPLW|"}, "") + } + if Bit(bitmask, PDBAUT) { + BitRead = strings.Join([]string{BitRead, "PDBAUT|"}, "") + } + + BitRead = fmt.Sprintf("[%d]%s", bitmask, BitRead) + return BitRead +} + +func CMBitmaskprint(bitmask int) string { + + BitRead := "|" + /*** Bit mask for config map ***/ + if Bit(bitmask, MPAPPL) { + BitRead = strings.Join([]string{BitRead, "MPAPPL|"}, "") + } + if Bit(bitmask, MPSYNC) { + BitRead = strings.Join([]string{BitRead, "MPSYNC|"}, "") + } + if Bit(bitmask, MPEMPT) { + BitRead = strings.Join([]string{BitRead, "MPEMPT|"}, "") + } + if Bit(bitmask, MPWARN) { + BitRead = strings.Join([]string{BitRead, "MPWARN|"}, "") + } + if Bit(bitmask, MPINIT) { + BitRead = strings.Join([]string{BitRead, "MPINIT|"}, "") + } + if Bit(bitmask, SPARE3) { + BitRead = strings.Join([]string{BitRead, "SPARE3|"}, "") + } + + BitRead = fmt.Sprintf("[%d]%s", bitmask, BitRead) + return BitRead +} diff --git a/commons/observability/constants.go b/commons/observability/constants.go index 45f06e49..9146fde4 100644 --- a/commons/observability/constants.go +++ b/commons/observability/constants.go @@ -9,6 +9,13 @@ const ( DefaultValue = "DEFAULT" ) +// Signals +const ( + VaultUsernameInUse = "VAULT_USERNAME" + VaultPasswordInUse = "VAULT_PASSWORD" + VaultIDProvided = "VAULT_ID_PROVIDED" +) + // Observability Status const ( StatusObservabilityPending v4.StatusEnum = "PENDING" @@ -29,61 +36,64 @@ const ( DefaultDbUserKey = "username" DefaultDBPasswordKey = "password" DefaultDBConnectionStringKey = "connection" - DefaultConfigVolumeString = "config-volume" + DefaultConfigVolumeString = "metrics-volume" DefaultLogFilename = "alert.log" DefaultLogVolumeString = "log-volume" + DefaultLogDestination = "/log" + DefaultConfigMountPath = "/config" + DefaultConfigVolumeName = "config-volume" DefaultWalletVolumeString = "creds" - DefaultOCIPrivateKeyVolumeString = "ocikey" + DefaultOCIConfigVolumeName = "oci-config-volume" DefaultOCIConfigFingerprintKey = "fingerprint" DefaultOCIConfigRegionKey = "region" DefaultOCIConfigTenancyKey = "tenancy" DefaultOCIConfigUserKey = "user" - - DefaultExporterImage = "container-registry.oracle.com/database/observability-exporter:1.5.2" - DefaultServicePort = 9161 - DefaultServiceTargetPort = 9161 - DefaultAppPort = 8080 - DefaultPrometheusPort = "metrics" - DefaultServiceType = "ClusterIP" - DefaultReplicaCount = 1 - DefaultExporterConfigMountRootPath = "/oracle/observability" - DefaultOracleHome = "/lib/oracle/21/client64/lib" - DefaultOracleTNSAdmin = DefaultOracleHome + "/network/admin" - DefaultExporterConfigmapFilename = "config.toml" - DefaultVaultPrivateKeyRootPath = "/oracle/config" - DefaultPrivateKeyFileKey = "privatekey" - DefaultPrivateKeyFileName = "private.pem" - DefaultVaultPrivateKeyAbsolutePath = DefaultVaultPrivateKeyRootPath + "/" + DefaultPrivateKeyFileName - DefaultExporterConfigmapAbsolutePath = DefaultExporterConfigMountRootPath + "/" + DefaultExporterConfigmapFilename -) - -// labeling -const ( - DefaultSelectorLabelKey = "app" - DefaultReleaseLabelKey = "release" -) - -// default resource -const ( - DefaultExporterContainerName = "observability-exporter" + DefaultAzureConfigTenantId = "tenantId" + DefaultAzureConfigClientId = "clientId" + DefaultAzureConfigClientSecret = "clientSecret" + DefaultEnvPasswordSuffix = "_PASSWORD" + DefaultEnvUserSuffix = "_USERNAME" + DefaultEnvConnectionStringSuffix = "_CONNECT_STRING" + + DefaultExporterImage = "container-registry.oracle.com/database/observability-exporter:2.0.2" + DefaultServicePort = 9161 + DefaultServiceTargetPort = 9161 + DefaultAppPort = 8080 + DefaultPrometheusPort = "metrics" + DefaultServiceType = "ClusterIP" + DefaultReplicaCount = 1 + DefaultExporterConfigMountRootPath = "/oracle/observability" + DefaultOracleHome = "/lib/oracle/21/client64/lib" + DefaultOracleTNSAdmin = DefaultOracleHome + "/network/admin" + DefaultOCIConfigPath = "/.oci" + DefaultPrivateKeyFileName = "private.pem" + DefaultVaultPrivateKeyAbsolutePath = DefaultOCIConfigPath + "/" + DefaultPrivateKeyFileName + DefaultExporterContainerName = "exporter" + DefaultSelectorLabelKey = "app" ) // Known environment variables const ( EnvVarOracleHome = "ORACLE_HOME" - EnvVarDataSourceUser = "DB_USERNAME" + EnvVarDataSourceUsername = "DB_USERNAME" EnvVarDataSourcePassword = "DB_PASSWORD" EnvVarDataSourceConnectString = "DB_CONNECT_STRING" EnvVarDataSourceLogDestination = "LOG_DESTINATION" - EnvVarDataSourcePwdVaultSecretName = "VAULT_SECRET_NAME" - EnvVarDataSourcePwdVaultId = "VAULT_ID" + EnvVarDataSourcePwdVaultSecretName = "OCI_VAULT_SECRET_NAME" + EnvVarDataSourcePwdVaultId = "OCI_VAULT_ID" EnvVarCustomConfigmap = "CUSTOM_METRICS" EnvVarTNSAdmin = "TNS_ADMIN" - EnvVarVaultTenancyOCID = "vault_tenancy_ocid" - EnvVarVaultUserOCID = "vault_user_ocid" - EnvVarVaultFingerprint = "vault_fingerprint" - EnvVarVaultPrivateKeyPath = "vault_private_key_path" - EnvVarVaultRegion = "vault_region" + EnvVarOCIVaultTenancyOCID = "OCI_CLI_TENANCY" + EnvVarOCIVaultUserOCID = "OCI_CLI_USER" + EnvVarOCIVaultFingerprint = "OCI_CLI_FINGERPRINT" + EnvVarOCIVaultPrivateKeyPath = "OCI_CLI_KEY_FILE" + EnvVarOCIVaultRegion = "OCI_CLI_REGION" + EnvVarAzureVaultPasswordSecret = "AZ_VAULT_PASSWORD_SECRET" + EnvVarAzureVaultUsernameSecret = "AZ_VAULT_USERNAME_SECRET" + EnvVarAzureVaultID = "AZ_VAULT_ID" + EnvVarAzureTenantID = "AZURE_TENANT_ID" + EnvVarAzureClientID = "AZURE_CLIENT_ID" + EnvVarAzureClientSecret = "AZURE_CLIENT_SECRET" ) // Positive ConditionTypes @@ -165,10 +175,10 @@ const ( EventMessageFailedCRRetrieval = "Encountered error retrieving databaseObserver instance" EventReasonSpecError = "DeploymentSpecValidationFailed" - EventMessageSpecErrorDBPasswordSecretMissing = "Spec validation failed due to required dbPassword secret not found" + EventMessageSpecErrorConfigMapSpecifiedMissing = "Spec validation failed due to referenced configMap not found" EventMessageSpecErrorDBConnectionStringSecretMissing = "Spec validation failed due to required dbConnectionString secret not found" EventMessageSpecErrorDBPUserSecretMissing = "Spec validation failed due to dbUser secret not found" - EventMessageSpecErrorConfigmapMissing = "Spec validation failed due to custom config configmap not found" + EventMessageSpecErrorDBPwdSecretMissing = "Spec validation failed due to dbPassword secret not found" EventMessageSpecErrorDBWalletSecretMissing = "Spec validation failed due to provided dbWallet secret not found" EventReasonUpdateSucceeded = "ExporterDeploymentUpdated" diff --git a/commons/observability/utils.go b/commons/observability/utils.go index 6eccb261..693c9718 100644 --- a/commons/observability/utils.go +++ b/commons/observability/utils.go @@ -11,21 +11,19 @@ import ( func AddSidecarContainers(a *api.DatabaseObserver, listing *[]corev1.Container) { - if containers := a.Spec.ExporterSidecars; len(containers) > 0 { + if containers := a.Spec.Sidecar.Containers; len(containers) > 0 { for _, container := range containers { *listing = append(*listing, container) } - } } func AddSidecarVolumes(a *api.DatabaseObserver, listing *[]corev1.Volume) { - if volumes := a.Spec.SideCarVolumes; len(volumes) > 0 { + if volumes := a.Spec.Sidecar.Volumes; len(volumes) > 0 { for _, v := range volumes { *listing = append(*listing, v) } - } } @@ -67,7 +65,7 @@ func GetSelectorLabel(a *api.DatabaseObserver) map[string]string { func GetExporterVersion(a *api.DatabaseObserver) string { appVersion := "latest" whichImage := DefaultExporterImage - if img := a.Spec.Exporter.Deployment.ExporterImage; img != "" { + if img := a.Spec.Deployment.ExporterImage; img != "" { whichImage = img } @@ -80,7 +78,7 @@ func GetExporterVersion(a *api.DatabaseObserver) string { // GetExporterArgs retrieves args func GetExporterArgs(a *api.DatabaseObserver) []string { - if args := a.Spec.Exporter.Deployment.ExporterArgs; args != nil || len(args) > 0 { + if args := a.Spec.Deployment.ExporterArgs; args != nil || len(args) > 0 { return args } return nil @@ -88,7 +86,7 @@ func GetExporterArgs(a *api.DatabaseObserver) []string { // GetExporterDeploymentSecurityContext retrieves security context for container func GetExporterDeploymentSecurityContext(a *api.DatabaseObserver) *corev1.SecurityContext { - if sc := a.Spec.Exporter.Deployment.SecurityContext; sc != nil { + if sc := a.Spec.Deployment.SecurityContext; sc != nil { return sc } return &corev1.SecurityContext{} @@ -96,7 +94,7 @@ func GetExporterDeploymentSecurityContext(a *api.DatabaseObserver) *corev1.Secur // GetExporterPodSecurityContext retrieves security context for pods func GetExporterPodSecurityContext(a *api.DatabaseObserver) *corev1.PodSecurityContext { - if sc := a.Spec.Exporter.Deployment.DeploymentPodTemplate.SecurityContext; sc != nil { + if sc := a.Spec.Deployment.PodSecurityContext; sc != nil { return sc } return &corev1.PodSecurityContext{} @@ -104,7 +102,7 @@ func GetExporterPodSecurityContext(a *api.DatabaseObserver) *corev1.PodSecurityC // GetExporterCommands retrieves commands func GetExporterCommands(a *api.DatabaseObserver) []string { - if c := a.Spec.Exporter.Deployment.ExporterCommands; c != nil || len(c) > 0 { + if c := a.Spec.Deployment.ExporterCommands; c != nil || len(c) > 0 { return c } return nil @@ -116,7 +114,7 @@ func GetExporterServicePort(a *api.DatabaseObserver) []corev1.ServicePort { servicePorts := make([]corev1.ServicePort, 0) // get service ports - if ports := a.Spec.Exporter.Service.Ports; len(ports) > 0 { + if ports := a.Spec.Service.Ports; len(ports) > 0 { for _, port := range ports { servicePorts = append(servicePorts, port) } @@ -140,7 +138,7 @@ func GetEndpoints(a *api.DatabaseObserver) []monitorv1.Endpoint { endpoints := make([]monitorv1.Endpoint, 0) // get endpoints - if es := a.Spec.Prometheus.ServiceMonitor.Endpoints; len(es) > 0 { + if es := a.Spec.ServiceMonitor.Endpoints; len(es) > 0 { for _, e := range es { endpoints = append(endpoints, e) } @@ -157,8 +155,8 @@ func GetEndpoints(a *api.DatabaseObserver) []monitorv1.Endpoint { func AddNamespaceSelector(a *api.DatabaseObserver, spec *monitorv1.ServiceMonitorSpec) { - if ns := a.Spec.Prometheus.ServiceMonitor.NamespaceSelector; ns != nil { - a.Spec.Prometheus.ServiceMonitor.NamespaceSelector.DeepCopyInto(&spec.NamespaceSelector) + if ns := a.Spec.ServiceMonitor.NamespaceSelector; ns != nil { + a.Spec.ServiceMonitor.NamespaceSelector.DeepCopyInto(&spec.NamespaceSelector) } } @@ -168,25 +166,21 @@ func GetExporterDeploymentVolumeMounts(a *api.DatabaseObserver) []corev1.VolumeM volM := make([]corev1.VolumeMount, 0) - if cVolumeSourceName := a.Spec.ExporterConfig.Configmap.Name; cVolumeSourceName != "" { + if len(a.Spec.Metrics.Configmap) > 0 { volM = append(volM, corev1.VolumeMount{ Name: DefaultConfigVolumeString, MountPath: DefaultExporterConfigMountRootPath, }) } - // a.Spec.Database.DBWallet.SecretName optional + // a.Spec.Wallet.SecretName optional // if null, consider the database NON-ADB and connect as such - if secretName := a.Spec.Database.DBWallet.SecretName; secretName != "" { + if secretName := a.Spec.Wallet.SecretName; secretName != "" { - p := DefaultOracleTNSAdmin - - // Determine what the value of TNS_ADMIN - // if custom TNS_ADMIN environment variable is set and found, use that instead as the path - if rCustomEnvs := a.Spec.Exporter.Deployment.ExporterEnvs; rCustomEnvs != nil { - if v, f := rCustomEnvs[EnvVarTNSAdmin]; f { - p = v - } + // Determine where to mount + p := a.Spec.Wallet.MountPath + if p == "" { + p = DefaultOracleTNSAdmin } volM = append(volM, corev1.VolumeMount{ @@ -195,20 +189,62 @@ func GetExporterDeploymentVolumeMounts(a *api.DatabaseObserver) []corev1.VolumeM }) } - // a.Spec.OCIConfig.SecretName required if vault is used - if secretName := a.Spec.OCIConfig.SecretName; secretName != "" { + // a.Spec.Wallet.AdditionalWallets + if add := a.Spec.Wallet.AdditionalWallets; add != nil && len(add) > 0 { + for _, w := range add { + // Determine where to mount + volM = append(volM, corev1.VolumeMount{ + Name: w.Name, + MountPath: w.MountPath, + }) + } + + } + + // a.Spec.OCIConfig.ConfigMap + // a.Spec.OCIConfig.SecretName + if oci := a.Spec.OCIConfig; oci.ConfigMap.Name != "" { + + p := oci.MountPath + if p == "" { // overwrite + p = DefaultOCIConfigPath + } + volM = append(volM, corev1.VolumeMount{ - Name: DefaultOCIPrivateKeyVolumeString, - MountPath: DefaultVaultPrivateKeyRootPath, + Name: DefaultOCIConfigVolumeName, + MountPath: p, }) } - // a.Spec.Log.Path path to mount for a custom log path, a volume is required - if rLogPath := a.Spec.Log.Path; rLogPath != "" { - vName := GetLogName(a) + // a.Spec.Log.Destination path to mount for a custom log path, a volume is required + if disabled := a.Spec.Log.Disable; !disabled { + + vName := a.Spec.Log.Volume.Name + if vName == "" { + vName = DefaultLogVolumeString + } + + vDestination := a.Spec.Log.Destination + if vDestination == "" { + vDestination = DefaultLogDestination + } + volM = append(volM, corev1.VolumeMount{ Name: vName, - MountPath: rLogPath, + MountPath: vDestination, + }) + } + + // ObserverConfig VolumeMount + if volumeName := a.Spec.ExporterConfig.ConfigMap.Name; volumeName != "" { + mp := a.Spec.ExporterConfig.MountPath + if mp == "" { + mp = DefaultConfigMountPath + } + + volM = append(volM, corev1.VolumeMount{ + Name: DefaultConfigVolumeName, + MountPath: mp, }) } @@ -220,28 +256,48 @@ func GetExporterDeploymentVolumes(a *api.DatabaseObserver) []corev1.Volume { vol := make([]corev1.Volume, 0) - // config-volume Volume - // if null, the exporter uses the default built-in config - if cVolumeSourceName := a.Spec.ExporterConfig.Configmap.Name; cVolumeSourceName != "" { + // metrics-volume Volume + // if null, the exporter uses the default built-in metrics config + if len(a.Spec.Metrics.Configmap) > 1 { - cVolumeSourceKey := a.Spec.ExporterConfig.Configmap.Key - cMSource := &corev1.ConfigMapVolumeSource{ + vp := make([]corev1.VolumeProjection, 0) + for _, cm := range a.Spec.Metrics.Configmap { + cmSource := &corev1.ConfigMapProjection{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: cm.Name, + }, + } + + vp = append(vp, corev1.VolumeProjection{ConfigMap: cmSource}) + } + + vol = append(vol, corev1.Volume{ + Name: DefaultConfigVolumeString, + VolumeSource: corev1.VolumeSource{ + Projected: &corev1.ProjectedVolumeSource{ + Sources: vp, + }, + }}) + + } else if len(a.Spec.Metrics.Configmap) == 1 { + + cm := a.Spec.Metrics.Configmap[0] + cmSource := &corev1.ConfigMapVolumeSource{ LocalObjectReference: corev1.LocalObjectReference{ - Name: cVolumeSourceName, + Name: cm.Name, }, - Items: []corev1.KeyToPath{{ - Key: cVolumeSourceKey, - Path: DefaultExporterConfigmapFilename, - }}, } - vol = append(vol, corev1.Volume{Name: DefaultConfigVolumeString, VolumeSource: corev1.VolumeSource{ConfigMap: cMSource}}) + vol = append(vol, corev1.Volume{ + Name: DefaultConfigVolumeString, + VolumeSource: corev1.VolumeSource{ConfigMap: cmSource}, + }) } // creds Volume // a.Spec.Database.DBWallet.SecretName optional // if null, consider the database NON-ADB and connect as such - if secretName := a.Spec.Database.DBWallet.SecretName; secretName != "" { + if secretName := a.Spec.Wallet.SecretName; secretName != "" { vol = append(vol, corev1.Volume{ Name: DefaultWalletVolumeString, @@ -253,28 +309,57 @@ func GetExporterDeploymentVolumes(a *api.DatabaseObserver) []corev1.Volume { }) } - // ocikey Volume - // a.Spec.Database.DBWallet.SecretName optional - if secretName := a.Spec.OCIConfig.SecretName; secretName != "" { + // a.Spec.Wallet.AdditionalWallets + if add := a.Spec.Wallet.AdditionalWallets; add != nil && len(add) > 0 { + for _, w := range add { - OCIConfigSource := &corev1.SecretVolumeSource{ - SecretName: secretName, - Items: []corev1.KeyToPath{{ - Key: DefaultPrivateKeyFileKey, - Path: DefaultPrivateKeyFileName, - }}, + vol = append(vol, corev1.Volume{ + Name: w.Name, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: w.SecretName, + }, + }, + }) + } + + } + + // oci-config-volume Volume + // a.Spec.OCIConfig.PrivateKey.SecretName optional + if oci := a.Spec.OCIConfig; oci.ConfigMap.Name != "" && oci.PrivateKey.SecretName != "" { + + vp := make([]corev1.VolumeProjection, 0) + + cmSource := &corev1.ConfigMapProjection{ + LocalObjectReference: corev1.LocalObjectReference{Name: oci.ConfigMap.Name}, + } + secretSource := &corev1.SecretProjection{ + LocalObjectReference: corev1.LocalObjectReference{Name: oci.PrivateKey.SecretName}, } + vp = append(vp, corev1.VolumeProjection{ConfigMap: cmSource}) + vp = append(vp, corev1.VolumeProjection{Secret: secretSource}) + vol = append(vol, corev1.Volume{ - Name: DefaultOCIPrivateKeyVolumeString, - VolumeSource: corev1.VolumeSource{Secret: OCIConfigSource}, + Name: DefaultOCIConfigVolumeName, + VolumeSource: corev1.VolumeSource{ + Projected: &corev1.ProjectedVolumeSource{ + Sources: vp, + }, + }, }) + } // log-volume Volume - if rLogPath := a.Spec.Log.Path; rLogPath != "" { + if disabled := a.Spec.Log.Disable; !disabled { vs := GetLogVolumeSource(a) - vName := GetLogName(a) + + vName := a.Spec.Log.Volume.Name + if vName == "" { + vName = DefaultLogVolumeString + } vol = append(vol, corev1.Volume{ Name: vName, @@ -282,25 +367,35 @@ func GetExporterDeploymentVolumes(a *api.DatabaseObserver) []corev1.Volume { }) } - return vol -} - -// GetExporterConfig function retrieves config name for status -func GetExporterConfig(a *api.DatabaseObserver) string { + // ObserverConfig Volume + if volumeName := a.Spec.ExporterConfig.ConfigMap.Name; volumeName != "" { + cMSource := &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: volumeName, + }, + } - configName := DefaultValue - if cmName := a.Spec.ExporterConfig.Configmap.Name; cmName != "" { - configName = cmName + vol = append(vol, corev1.Volume{Name: DefaultConfigVolumeName, VolumeSource: corev1.VolumeSource{ConfigMap: cMSource}}) } - return configName + return vol } -func GetLogName(a *api.DatabaseObserver) string { - if name := a.Spec.Log.Volume.Name; name != "" { - return name +// GetMetricsConfig function retrieves config name for status +func GetMetricsConfig(a *api.DatabaseObserver) string { + + cms := a.Spec.Metrics.Configmap + if len(cms) > 1 { + metricsConfigList := make([]string, 0) + for _, cm := range cms { + metricsConfigList = append(metricsConfigList, cm.Name) + } + return strings.Join(metricsConfigList, ",") + + } else if len(cms) == 1 { + return cms[0].Name } - return DefaultLogVolumeString + return DefaultValue } // GetLogVolumeSource function retrieves the source to help GetExporterDeploymentVolumes @@ -328,149 +423,241 @@ func AddEnv(env []corev1.EnvVar, existing map[string]string, name string, v stri // Evaluate if env already exists if _, f := existing[name]; !f { env = append(env, corev1.EnvVar{Name: name, Value: v}) + existing[name] = "" } return env } -// AddEnvFrom is a helper method that appends an Env Var value source -func AddEnvFrom(env []corev1.EnvVar, existing map[string]string, name string, v *corev1.EnvVarSource) []corev1.EnvVar { - +func AddEnvFromConfigMap(env []corev1.EnvVar, existing map[string]string, environmentName string, key string, configMap string) []corev1.EnvVar { // Evaluate if env already exists - if _, f := existing[name]; !f { - env = append(env, corev1.EnvVar{Name: name, ValueFrom: v}) + if _, f := existing[environmentName]; !f { + + optional := true + cm := &corev1.EnvVarSource{ + ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ + Key: key, + LocalObjectReference: corev1.LocalObjectReference{Name: configMap}, + Optional: &optional, + }, + } + env = append(env, corev1.EnvVar{Name: environmentName, ValueFrom: cm}) } return env } -// GetExporterEnvs function retrieves env from a or provides default -func GetExporterEnvs(a *api.DatabaseObserver) []corev1.EnvVar { - - optional := true - rDBPasswordKey := a.Spec.Database.DBPassword.Key - rDBPasswordName := a.Spec.Database.DBPassword.SecretName - rDBConnectStrKey := a.Spec.Database.DBConnectionString.Key - rDBConnectStrName := a.Spec.Database.DBConnectionString.SecretName - rDBVaultSecretName := a.Spec.Database.DBPassword.VaultSecretName - rDBVaultOCID := a.Spec.Database.DBPassword.VaultOCID - rDBUserSKey := a.Spec.Database.DBUser.Key - rDBUserSName := a.Spec.Database.DBUser.SecretName - rOCIConfigCMName := a.Spec.OCIConfig.ConfigMapName - rLogPath := a.Spec.Log.Path - rLogFilename := a.Spec.Log.Filename - rCustomEnvs := a.Spec.Exporter.Deployment.ExporterEnvs +func AddEnvFromSecret(env []corev1.EnvVar, existing map[string]string, environmentName string, key string, secretName string) []corev1.EnvVar { + // Evaluate if env already exists + if _, f := existing[environmentName]; !f { - var env = make([]corev1.EnvVar, 0) + optional := true + e := &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + Key: key, + LocalObjectReference: corev1.LocalObjectReference{Name: secretName}, + Optional: &optional, + }} - // add CustomEnvs - if rCustomEnvs != nil { - for k, v := range rCustomEnvs { - env = append(env, corev1.EnvVar{Name: k, Value: v}) - } + env = append(env, corev1.EnvVar{Name: environmentName, ValueFrom: e}) } + return env +} + +func AddSingleDatabaseEnvs(a *api.DatabaseObserver, e map[string]string, source []corev1.EnvVar) []corev1.EnvVar { + + u := a.Spec.Database.DBUser + c := a.Spec.Database.DBConnectionString + p := a.Spec.Database.DBPassword + o := a.Spec.Database.OCIVault + z := a.Spec.Database.AzureVault // DB_USERNAME environment variable - if rDBUserSKey == "" { // overwrite - rDBUserSKey = DefaultDbUserKey + if IsUsingAzureVault(z, VaultUsernameInUse) { + source = AddEnv(source, e, EnvVarAzureVaultUsernameSecret, z.VaultUsernameSecret) + source = AddEnv(source, e, EnvVarAzureVaultID, z.VaultID) + + } else if u.SecretName != "" { + uKey := u.Key + if uKey == "" { // overwrite + uKey = DefaultDbUserKey + } + source = AddEnvFromSecret(source, e, EnvVarDataSourceUsername, uKey, u.SecretName) } - envUser := &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - Key: rDBUserSKey, - LocalObjectReference: corev1.LocalObjectReference{Name: rDBUserSName}, - Optional: &optional, - }} - env = AddEnvFrom(env, rCustomEnvs, EnvVarDataSourceUser, envUser) // DB_CONNECT_STRING environment variable - if rDBConnectStrKey == "" { - rDBConnectStrKey = DefaultDBConnectionStringKey + if c.SecretName != "" { + cKey := c.Key + if cKey == "" { + cKey = DefaultDBConnectionStringKey + } + source = AddEnvFromSecret(source, e, EnvVarDataSourceConnectString, cKey, c.SecretName) } - envConnectStr := &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - Key: rDBConnectStrKey, - LocalObjectReference: corev1.LocalObjectReference{Name: rDBConnectStrName}, - Optional: &optional, - }} - env = AddEnvFrom(env, rCustomEnvs, EnvVarDataSourceConnectString, envConnectStr) // DB_PASSWORD environment variable - // if useVault, add environment variables for Vault ID and Vault Secret Name - useVault := rDBVaultSecretName != "" && rDBVaultOCID != "" - if useVault { + if IsUsingOCIVault(o) { + source = AddEnv(source, e, EnvVarDataSourcePwdVaultSecretName, o.VaultPasswordSecret) + source = AddEnv(source, e, EnvVarDataSourcePwdVaultId, o.VaultID) + + } else if IsUsingAzureVault(z, VaultPasswordInUse) { + source = AddEnv(source, e, EnvVarAzureVaultPasswordSecret, z.VaultPasswordSecret) + source = AddEnv(source, e, EnvVarAzureVaultID, z.VaultID) + + } else if p.SecretName != "" { + pKey := p.Key + if pKey == "" { // overwrite + pKey = DefaultDBPasswordKey + } + env := p.EnvName + if env == "" { // overwrite + env = EnvVarDataSourcePassword + } + source = AddEnvFromSecret(source, e, env, pKey, p.SecretName) + } - env = AddEnv(env, rCustomEnvs, EnvVarDataSourcePwdVaultSecretName, rDBVaultSecretName) - env = AddEnv(env, rCustomEnvs, EnvVarDataSourcePwdVaultId, rDBVaultOCID) + // Add OCI Vault Required Values + if a.Spec.OCIConfig.ConfigMap.Name != "" { + ociConfig := a.Spec.OCIConfig.ConfigMap.Name + source = AddEnv(source, e, EnvVarOCIVaultPrivateKeyPath, DefaultVaultPrivateKeyAbsolutePath) + source = AddEnvFromConfigMap(source, e, EnvVarOCIVaultFingerprint, DefaultOCIConfigFingerprintKey, ociConfig) + source = AddEnvFromConfigMap(source, e, EnvVarOCIVaultUserOCID, DefaultOCIConfigUserKey, ociConfig) + source = AddEnvFromConfigMap(source, e, EnvVarOCIVaultTenancyOCID, DefaultOCIConfigTenancyKey, ociConfig) + source = AddEnvFromConfigMap(source, e, EnvVarOCIVaultRegion, DefaultOCIConfigRegionKey, ociConfig) + } - // Configuring the configProvider prefixed with vault_ - // https://github.com/oracle/oracle-db-appdev-monitoring/blob/main/vault/vault.go - configSourceFingerprintValue := &corev1.EnvVarSource{ - ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ - Key: DefaultOCIConfigFingerprintKey, - LocalObjectReference: corev1.LocalObjectReference{Name: rOCIConfigCMName}, - Optional: &optional, - }, - } - configSourceRegionValue := &corev1.EnvVarSource{ - ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ - Key: DefaultOCIConfigRegionKey, - LocalObjectReference: corev1.LocalObjectReference{Name: rOCIConfigCMName}, - Optional: &optional, - }, + // Add Azure Vault Required Values + if a.Spec.AzureConfig.ConfigMap.Name != "" { + azureConfig := a.Spec.AzureConfig.ConfigMap.Name + source = AddEnvFromConfigMap(source, e, EnvVarAzureTenantID, DefaultAzureConfigTenantId, azureConfig) + source = AddEnvFromConfigMap(source, e, EnvVarAzureClientID, DefaultAzureConfigClientId, azureConfig) + source = AddEnvFromConfigMap(source, e, EnvVarAzureClientSecret, DefaultAzureConfigClientSecret, azureConfig) + } + + return source +} + +func AddMultiDatabaseEnvs(a *api.DatabaseObserver, e map[string]string, source []corev1.EnvVar) []corev1.EnvVar { + + for key, db := range a.Spec.Databases { + u := db.DBUser + c := db.DBConnectionString + p := db.DBPassword + + // DB_USERNAME environment variable, if secret is defined + if u.SecretName != "" { + uKey := u.Key + if uKey == "" { // overwrite + uKey = DefaultDbUserKey + } + envUsername := u.EnvName + if envUsername == "" { + envUsername = key + DefaultEnvUserSuffix + } + source = AddEnvFromSecret(source, e, envUsername, uKey, u.SecretName) } - configSourceTenancyValue := &corev1.EnvVarSource{ - ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ - Key: DefaultOCIConfigTenancyKey, - LocalObjectReference: corev1.LocalObjectReference{Name: rOCIConfigCMName}, - Optional: &optional, - }, + + // DB_CONNECT_STRING environment variable, if secret is defined + if c.SecretName != "" { + cKey := c.Key + if cKey == "" { + cKey = key + DefaultEnvConnectionStringSuffix + } + envConnectionString := c.EnvName + if envConnectionString == "" { + envConnectionString = key + DefaultEnvConnectionStringSuffix + } + source = AddEnvFromSecret(source, e, envConnectionString, cKey, c.SecretName) } - configSourceUserValue := &corev1.EnvVarSource{ - ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ - Key: DefaultOCIConfigUserKey, - LocalObjectReference: corev1.LocalObjectReference{Name: rOCIConfigCMName}, - Optional: &optional, - }, + + // DB_PASSWORD environment variable, if secret is defined + if p.SecretName != "" { + pKey := p.Key + if pKey == "" { // overwrite + pKey = DefaultDBPasswordKey + } + env := p.EnvName + if env == "" { // overwrite + env = key + DefaultEnvPasswordSuffix + } + source = AddEnvFromSecret(source, e, env, pKey, p.SecretName) } - env = AddEnvFrom(env, rCustomEnvs, EnvVarVaultFingerprint, configSourceFingerprintValue) - env = AddEnvFrom(env, rCustomEnvs, EnvVarVaultUserOCID, configSourceUserValue) - env = AddEnvFrom(env, rCustomEnvs, EnvVarVaultTenancyOCID, configSourceTenancyValue) - env = AddEnvFrom(env, rCustomEnvs, EnvVarVaultRegion, configSourceRegionValue) - env = AddEnv(env, rCustomEnvs, EnvVarVaultPrivateKeyPath, DefaultVaultPrivateKeyAbsolutePath) - } else { + } + return source +} - if rDBPasswordKey == "" { // overwrite - rDBPasswordKey = DefaultDBPasswordKey - } - dbPassword := &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - Key: rDBPasswordKey, - LocalObjectReference: corev1.LocalObjectReference{Name: rDBPasswordName}, - Optional: &optional, - }} +func IsUsingOCIVault(f api.DBOCIVault) bool { + return f.VaultPasswordSecret != "" && f.VaultID != "" +} - env = AddEnvFrom(env, rCustomEnvs, EnvVarDataSourcePassword, dbPassword) +func IsUsingAzureVault(f api.DBAzureVault, v string) bool { + if v == VaultPasswordInUse { + return f.VaultID != "" && f.VaultPasswordSecret != "" + } + if v == VaultUsernameInUse { + return f.VaultID != "" && f.VaultUsernameSecret != "" + } + if v == VaultIDProvided { + return f.VaultID != "" } - // CUSTOM_METRICS environment variable - if customMetricsName := a.Spec.ExporterConfig.Configmap.Name; customMetricsName != "" { - customMetrics := DefaultExporterConfigmapAbsolutePath + return false + +} - env = AddEnv(env, rCustomEnvs, EnvVarCustomConfigmap, customMetrics) +func IsMultipleDatabasesDefined(a *api.DatabaseObserver) bool { + return a.Spec.Databases != nil && len(a.Spec.Databases) > 0 +} + +// GetExporterEnvs function retrieves env from a or provides default +func GetExporterEnvs(a *api.DatabaseObserver) []corev1.EnvVar { + + // create slices + var env = make([]corev1.EnvVar, 0) + + // First, add all environment variables provided + e := a.Spec.Deployment.ExporterEnvs + if e != nil { + for k, v := range e { + env = append(env, corev1.EnvVar{Name: k, Value: v}) + } + } else { + e = make(map[string]string) } - env = AddEnv(env, rCustomEnvs, EnvVarOracleHome, DefaultOracleHome) - env = AddEnv(env, rCustomEnvs, EnvVarTNSAdmin, DefaultOracleTNSAdmin) + // Add database environment variables based on single or multi DB configuration + if IsMultipleDatabasesDefined(a) { + env = AddMultiDatabaseEnvs(a, e, env) + } else { + env = AddSingleDatabaseEnvs(a, e, env) + } - // LOG_DESTINATION environment variable - if rLogPath != "" { - if rLogFilename == "" { - rLogFilename = DefaultLogFilename + // CUSTOM_METRICS environment variable + if cms := a.Spec.Metrics.Configmap; cms != nil && len(cms) > 0 { + metricsConfigList := make([]string, 0) + for _, cm := range cms { + metricsConfigList = append(metricsConfigList, DefaultExporterConfigMountRootPath+"/"+cm.Key) } - d := filepath.Join(rLogPath, rLogFilename) - env = AddEnv(env, rCustomEnvs, EnvVarDataSourceLogDestination, d) + customMetrics := strings.Join(metricsConfigList, ",") + env = AddEnv(env, e, EnvVarCustomConfigmap, customMetrics) } + env = AddEnv(env, e, EnvVarOracleHome, DefaultOracleHome) + env = AddEnv(env, e, EnvVarTNSAdmin, DefaultOracleTNSAdmin) + + // LOG_DESTINATION environment variable4 + if disabled := a.Spec.Log.Disable; !disabled { + d := a.Spec.Log.Destination + if d == "" { + d = DefaultLogDestination + } + + f := a.Spec.Log.Filename + if f == "" { + f = DefaultLogFilename + } + ld := filepath.Join(d, f) + env = AddEnv(env, e, EnvVarDataSourceLogDestination, ld) + } return env } @@ -484,10 +671,9 @@ func GetExporterReplicas(a *api.DatabaseObserver) int32 { // GetExporterImage function retrieves image from a or provides default func GetExporterImage(a *api.DatabaseObserver) string { - if img := a.Spec.Exporter.Deployment.ExporterImage; img != "" { + if img := a.Spec.Deployment.ExporterImage; img != "" { return img } - return DefaultExporterImage } diff --git a/commons/oci/containerdatabase.go b/commons/oci/containerdatabase.go index 9391d6f8..28b31353 100644 --- a/commons/oci/containerdatabase.go +++ b/commons/oci/containerdatabase.go @@ -56,7 +56,7 @@ func (d *DatabaseService) CreateAutonomousContainerDatabase(acd *dbv4.Autonomous CompartmentId: acd.Spec.CompartmentOCID, DisplayName: acd.Spec.DisplayName, CloudAutonomousVmClusterId: acd.Spec.AutonomousExadataVMClusterOCID, - PatchModel: database.CreateAutonomousContainerDatabaseDetailsPatchModelUpdates, + PatchModel: database.CreateAutonomousContainerDatabaseBasePatchModelUpdates, }, } diff --git a/commons/oci/database.go b/commons/oci/database.go index e43afb56..99240d1a 100644 --- a/commons/oci/database.go +++ b/commons/oci/database.go @@ -430,3 +430,27 @@ func (d *DatabaseService) CreateAutonomousDatabaseClone(adb *dbv4.AutonomousData return d.dbClient.CreateAutonomousDatabase(context.TODO(), request) } + +func (d *DatabaseService) SwitchoverAutonomousDatabase(adbOCID string) (database.SwitchoverAutonomousDatabaseResponse, error) { + retryPolicy := common.DefaultRetryPolicy() + + request := database.SwitchoverAutonomousDatabaseRequest{ + AutonomousDatabaseId: common.String(adbOCID), + RequestMetadata: common.RequestMetadata{ + RetryPolicy: &retryPolicy, + }, + } + return d.dbClient.SwitchoverAutonomousDatabase(context.TODO(), request) +} + +func (d *DatabaseService) FailoverAutonomousDatabase(adbOCID string) (database.FailOverAutonomousDatabaseResponse, error) { + retryPolicy := common.DefaultRetryPolicy() + + request := database.FailOverAutonomousDatabaseRequest{ + AutonomousDatabaseId: common.String(adbOCID), + RequestMetadata: common.RequestMetadata{ + RetryPolicy: &retryPolicy, + }, + } + return d.dbClient.FailOverAutonomousDatabase(context.TODO(), request) +} diff --git a/commons/oci/wallet.go b/commons/oci/wallet.go index 076460b1..0cc2b434 100644 --- a/commons/oci/wallet.go +++ b/commons/oci/wallet.go @@ -41,7 +41,7 @@ package oci import ( "archive/zip" "io" - "io/ioutil" + "os" "strings" ) @@ -62,7 +62,7 @@ func ExtractWallet(content io.ReadCloser) (map[string][]byte, error) { func saveWalletZip(content io.ReadCloser) (string, error) { // Create a temp file wallet*.zip const walletFileName = "wallet*.zip" - outZip, err := ioutil.TempFile("", walletFileName) + outZip, err := os.CreateTemp("", walletFileName) if err != nil { return "", err } @@ -91,7 +91,7 @@ func unzipWallet(path string) (map[string][]byte, error) { return files, err } - content, err := ioutil.ReadAll(reader) + content, err := io.ReadAll(reader) if err != nil { return files, err } diff --git a/commons/oraclerestart/exec.go b/commons/oraclerestart/exec.go new file mode 100644 index 00000000..b91d4226 --- /dev/null +++ b/commons/oraclerestart/exec.go @@ -0,0 +1,112 @@ +/* +** Copyright (c) 2022 Oracle and/or its affiliates. +** +** The Universal Permissive License (UPL), Version 1.0 +** +** Subject to the condition set forth below, permission is hereby granted to any +** person obtaining a copy of this software, associated documentation and/or data +** (collectively the "Software"), free of charge and under any and all copyright +** rights in the Software, and any and all patent rights owned or freely +** licensable by each licensor hereunder covering either (i) the unmodified +** Software as contributed to or provided by such licensor, or (ii) the Larger +** Works (as defined below), to deal in both +** +** (a) the Software, and +** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +** one is included with the Software (each a "Larger Work" to which the Software +** is contributed by such licensors), +** +** without restriction, including without limitation the rights to copy, create +** derivative works of, display, perform, and distribute the Software and make, +** use, sell, offer for sale, import, export, have made, and have sold the +** Software and the Larger Work(s), and to sublicense the foregoing rights on +** either these or other terms. +** +** This license is subject to the following condition: +** The above copyright notice and either this complete permission notice or at +** a minimum a reference to the UPL must be included in all copies or +** substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +** SOFTWARE. + */ + +package commons + +import ( + "bytes" + "net/http" + + oraclerestartdb "github.com/oracle/oracle-database-operator/apis/database/v4" + + "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/tools/remotecommand" +) + +// ExecCMDInContainer execute command in first container of a pod +func ExecCommand(podName string, cmd []string, kubeClient kubernetes.Interface, kubeConfig clientcmd.ClientConfig, instance *oraclerestartdb.OracleRestart, logger logr.Logger) (string, string, error) { + + var msg string + var ( + execOut bytes.Buffer + execErr bytes.Buffer + ) + + req := kubeClient.CoreV1().RESTClient(). + Post(). + Namespace(instance.Namespace). + Resource("pods"). + Name(podName). + SubResource("exec"). + VersionedParams(&corev1.PodExecOptions{ + Command: cmd, + Stdout: true, + Stderr: true, + TTY: true, + }, scheme.ParameterCodec) + + config, err := kubeConfig.ClientConfig() + if err != nil { + return "Error Occurred", "Error Occurred", err + } + + if config.NegotiatedSerializer == nil { + config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() + } + + // Connect to url (constructed from req) using SPDY (HTTP/2) protocol which allows bidirectional streams. + exec, err := remotecommand.NewSPDYExecutor(config, http.MethodPost, req.URL()) + if err != nil { + msg = "Error after executing remotecommand.NewSPDYExecutor" + LogMessages("Error", msg, err, instance, logger) + return "Error Occurred", "Error Occurred", err + } + + err = exec.Stream(remotecommand.StreamOptions{ + Stdout: &execOut, + Stderr: &execErr, + Tty: true, + }) + if err != nil { + msg = "Command execution failed inside the container!" + LogMessages("DEBUG", msg, err, instance, logger) + if len(execOut.String()) > 0 { + LogMessages("INFO", execOut.String(), nil, instance, logger) + } + if len(execErr.String()) > 0 { + LogMessages("INFO", execErr.String(), nil, instance, logger) + } + return execOut.String(), execErr.String(), err + } + + return execOut.String(), execErr.String(), nil +} diff --git a/commons/oraclerestart/oraclerestartcommon.go b/commons/oraclerestart/oraclerestartcommon.go new file mode 100644 index 00000000..434f1ce9 --- /dev/null +++ b/commons/oraclerestart/oraclerestartcommon.go @@ -0,0 +1,1397 @@ +/* +** Copyright (c) 2022 Oracle and/or its affiliates. +** +** The Universal Permissive License (UPL), Version 1.0 +** +** Subject to the condition set forth below, permission is hereby granted to any +** person obtaining a copy of this software, associated documentation and/or data +** (collectively the "Software"), free of charge and under any and all copyright +** rights in the Software, and any and all patent rights owned or freely +** licensable by each licensor hereunder covering either (i) the unmodified +** Software as contributed to or provided by such licensor, or (ii) the Larger +** Works (as defined below), to deal in both +** +** (a) the Software, and +** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +** one is included with the Software (each a "Larger Work" to which the Software +** is contributed by such licensors), +** +** without restriction, including without limitation the rights to copy, create +** derivative works of, display, perform, and distribute the Software and make, +** use, sell, offer for sale, import, export, have made, and have sold the +** Software and the Larger Work(s), and to sublicense the foregoing rights on +** either these or other terms. +** +** This license is subject to the following condition: +** The above copyright notice and either this complete permission notice or at +** a minimum a reference to the UPL must be included in all copies or +** substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +** SOFTWARE. + */ + +package commons + +import ( + "bufio" + "context" + "errors" + "fmt" + "path/filepath" + "slices" + "strconv" + + oraclerestart "github.com/oracle/oracle-database-operator/apis/database/v4" + utils "github.com/oracle/oracle-database-operator/commons/oraclerestart/utils" + + "strings" + + "github.com/go-logr/logr" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/apimachinery/pkg/util/rand" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// Function to build the env var specification +func buildEnvVarsSpec(envVariables []corev1.EnvVar) []corev1.EnvVar { + var result []corev1.EnvVar + + /** + if len(envVariables) > 0 { + result = append(result, corev1.EnvVar{Name: "container", Value: "true"}) + } else { + result = append(result, corev1.EnvVar{Name: "container", Value: "true"}) + } + **/ + + return result +} + +func checkAbsPath(location string) bool { + return filepath.IsAbs(location) +} + +// FUnction to build the svc definition for RAC +func buildContainerPortsDef(instance *oraclerestart.OracleRestart) []corev1.ContainerPort { + var result []corev1.ContainerPort + + /* + if len(OraRestartSpexPorts) > 0 { + for _, portMapping := range OraRestartSpexPorts { + name := generatePortMapping(portMapping) + if len(name) > 15 { + name = name[:15] + } + containerPort := + corev1.ContainerPort{ + Protocol: portMapping.Protocol, + ContainerPort: portMapping.Port, + Name: name, + } + result = append(result, containerPort) + } + } else { + */ + + result = append(result, corev1.ContainerPort{Protocol: corev1.ProtocolTCP, ContainerPort: utils.OraDBPort, Name: truncateName(fmt.Sprintf("%s-%d", "tcp", utils.OraDBPort))}) + result = append(result, corev1.ContainerPort{Protocol: corev1.ProtocolTCP, ContainerPort: utils.OraLsnrPort, Name: truncateName(fmt.Sprintf("%s-%d", "tcp", utils.OraLsnrPort))}) + result = append(result, corev1.ContainerPort{Protocol: corev1.ProtocolTCP, ContainerPort: utils.OraSSHPort, Name: truncateName(fmt.Sprintf("%s-%d", "tcp", utils.OraSSHPort))}) + result = append(result, corev1.ContainerPort{Protocol: corev1.ProtocolTCP, ContainerPort: utils.OraLocalOnsPort, Name: truncateName(fmt.Sprintf("%s-%d", "tcp", utils.OraLocalOnsPort))}) + result = append(result, corev1.ContainerPort{Protocol: corev1.ProtocolTCP, ContainerPort: utils.OraOemPort, Name: truncateName(fmt.Sprintf("%s-%d", "tcp", utils.OraOemPort))}) + + return result +} + +func truncateName(name string) string { + if len(name) > 15 { + return name[:15] + } + return name +} + +// FUnction to build the svc definition for RAC +func buildOracleRestartSvcPortsDef(npsvc oraclerestart.OracleRestartNodePortSvc) []corev1.ServicePort { + var result []corev1.ServicePort + + for _, portMapping := range npsvc.PortMappings { + servicePort := + corev1.ServicePort{ + Protocol: portMapping.Protocol, + Port: portMapping.Port, + Name: generatePortMapping(portMapping), + } + if portMapping.TargetPort > 0 { + servicePort.TargetPort = intstr.IntOrString{ + Type: intstr.Int, + IntVal: portMapping.TargetPort, + } + } + if portMapping.NodePort > 0 { + servicePort.NodePort = portMapping.NodePort + } + + result = append(result, servicePort) + } + + return result +} + +// Function to generate the Name +func generateName(base string) string { + maxNameLength := 50 + randomLength := 5 + maxGeneratedLength := maxNameLength - randomLength + if len(base) > maxGeneratedLength { + base = base[:maxGeneratedLength] + } + return fmt.Sprintf("%s%s", base, rand.String(randomLength)) +} + +// Function to generate the port mapping +func generatePortMapping(portMapping oraclerestart.OracleRestartPortMapping) string { + return generateName(fmt.Sprintf("%s-%d-%d-", "tcp", + portMapping.Port, portMapping.TargetPort)) +} + +func LogMessages(msgtype string, msg string, err error, instance *oraclerestart.OracleRestart, logger logr.Logger) { + // setting logrus formatter + //logrus.SetFormatter(&logrus.JSONFormatter{}) + //logrus.SetOutput(os.Stdout) + + if msgtype == "DEBUG" && utils.CheckStatusFlag(instance.Spec.IsDebug) { + if err != nil { + logger.Error(err, msg) + } else { + logger.Info(msg) + } + } else if msgtype == "INFO" { + logger.Info(msg) + } +} + +func GetRacPodName(racName string) string { + podName := racName + return podName +} + +func getlabelsForRac(instance *oraclerestart.OracleRestart) map[string]string { + return buildLabelsForOracleRestart(instance, "OracleRestart") +} + +func GetAsmPvcName(name string, diskPath string, instance *oraclerestart.OracleRestart) string { + + // pvcName := "asm-pvc-disk-" + strconv.Itoa(index) + "-" + name + "-" + dgType + "-" + "pvc" + dgType := CheckDiskInAsmDeviceList(instance, diskPath) + diskName := diskPath[strings.LastIndex(diskPath, "/")+1:] + pvcName := "asm-pvc-" + strings.ToLower(dgType) + "-" + diskName + "-" + name + "-" + instance.Spec.InstDetails.Name + "-0" + + return pvcName +} + +func GetAsmPvName(name string, diskPath string, instance *oraclerestart.OracleRestart) string { + + dgType := CheckDiskInAsmDeviceList(instance, diskPath) + diskName := diskPath[strings.LastIndex(diskPath, "/")+1:] + pvName := "asm-pv-" + strings.ToLower(dgType) + "-" + diskName + "-" + name + "-" + instance.Spec.InstDetails.Name + "-0" + return pvName +} + +func CheckSfset(sfsetName string, instance *oraclerestart.OracleRestart, kClient client.Client) (*appsv1.StatefulSet, error) { + sfSetFound := &appsv1.StatefulSet{} + err := kClient.Get(context.TODO(), types.NamespacedName{ + Name: sfsetName, + Namespace: instance.Namespace, + }, sfSetFound) + if err != nil { + return sfSetFound, err + } + return sfSetFound, nil +} + +func GetRacK8sClientConfig(kClient client.Client) (clientcmd.ClientConfig, kubernetes.Interface, error) { + var err1 error + var kubeConfig clientcmd.ClientConfig + var kubeClient kubernetes.Interface + + oraclerestart.KubeConfigOnce.Do(func() { + loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() + configOverrides := &clientcmd.ConfigOverrides{} + kubeConfig = clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides) + config, err := kubeConfig.ClientConfig() + if err != nil { + err1 = err + } + kubeClient, err = kubernetes.NewForConfig(config) + if err != nil { + err1 = err + } + + }) + return kubeConfig, kubeClient, err1 +} + +func oraclerestartValidationCmd() []string { + + oraScriptMount1 := getOraScriptMount() + var oraShardValidateCmd = []string{oraScriptMount1 + "/cmdExec", "/bin/python3", oraScriptMount1 + "/main.py ", "--checkliveness=true ", "--optype=primaryoraclerestart"} + return oraShardValidateCmd +} + +func OracleRestartNodeDelCmd() []string { + oraScriptMount1 := getOraScriptMount() + var oraOracleRestartNodeDelCmd = []string{oraScriptMount1 + "/cmdExec", "/bin/python3", oraScriptMount1 + "/main.py ", "--delOracleRestartNode=\"del_rachome=true;del_gridnode=true\""} + return oraOracleRestartNodeDelCmd +} + +func oraclerestartLsnrSetup() []string { + oraScriptMount1 := getOraScriptMount() + var oraOracleRestartNodeDelCmd = []string{oraScriptMount1 + "/cmdExec", "/bin/python3", oraScriptMount1 + "/main.py ", "--setupdblsnr=\"del_rachome=true;del_gridnode=true\""} + return oraOracleRestartNodeDelCmd +} + +func getAsmCmd() []string { + asmCmd := []string{"bash", "-c", "cat /etc/rac_env_vars/envfile | grep CRS_ASM_DEVICE_LIST"} + return asmCmd +} + +func getDbAsmCmd() []string { + asmCmd := []string{"bash", "-c", "cat /etc/rac_env_vars/envfile | grep DB_ASM_DEVICE_LIST"} + return asmCmd +} + +func getOraScriptMount() string { + + return utils.OraScriptMount + +} + +func getOraDbUser() string { + + return utils.OraDBUser + +} + +func getOraGiUser() string { + + return utils.OraGridUser + +} + +func getOraPythonCmd() string { + + return "/bin/python3" + +} + +func UpdateAsmCount(gihome string, podName string, instance *oraclerestart.OracleRestart, kubeClient kubernetes.Interface, kubeconfig clientcmd.ClientConfig, logger logr.Logger, +) error { + + _, _, err := ExecCommand(podName, getUpdateAsmCount(gihome), kubeClient, kubeconfig, instance, logger) + if err != nil { + return fmt.Errorf("error ocurred while updating TCP listener ports") + } + + return nil +} + +func ValidateDbSetup(podName string, instance *oraclerestart.OracleRestart, kubeClient kubernetes.Interface, kubeconfig clientcmd.ClientConfig, logger logr.Logger, +) error { + + _, _, err := ExecCommand(podName, oraclerestartValidationCmd(), kubeClient, kubeconfig, instance, logger) + if err != nil { + return fmt.Errorf("error ocurred while validating the DB Setup") + } + return nil +} + +func DelOracleRestartNode(podName string, instance *oraclerestart.OracleRestart, kubeClient kubernetes.Interface, kubeconfig clientcmd.ClientConfig, logger logr.Logger, +) error { + + _, _, err := ExecCommand(podName, OracleRestartNodeDelCmd(), kubeClient, kubeconfig, instance, logger) + if err != nil { + return fmt.Errorf("error ocurred while deleting the RAC node") + } + return nil +} + +func CheckAsmList(podName string, instance *oraclerestart.OracleRestart, kubeClient kubernetes.Interface, kubeconfig clientcmd.ClientConfig, logger logr.Logger) (string, error) { + output, _, err := ExecCommand(podName, getAsmCmd(), kubeClient, kubeconfig, instance, logger) + if err != nil { + return "", err + } + + parts := strings.SplitN(output, "=", 2) + if len(parts) < 2 { + return "", fmt.Errorf("unable to parse ASM device list from output: %s", output) + } + + // Trim the \r and \n characters from the end of the string + deviceList := strings.TrimSpace(parts[1]) + deviceList = strings.ReplaceAll(deviceList, "\r", "") + return deviceList, nil +} + +func CheckDbAsmList(podName string, instance *oraclerestart.OracleRestart, kubeClient kubernetes.Interface, kubeconfig clientcmd.ClientConfig, logger logr.Logger) (string, error) { + output, _, err := ExecCommand(podName, getDbAsmCmd(), kubeClient, kubeconfig, instance, logger) + if err != nil { + return "", err + } + + parts := strings.SplitN(output, "=", 2) + if len(parts) < 2 { + return "", fmt.Errorf("unable to parse ASM device list from output: %s", output) + } + + // Trim the \r and \n characters from the end of the string + deviceList := strings.TrimSpace(parts[1]) + deviceList = strings.ReplaceAll(deviceList, "\r", "") + return deviceList, nil +} + +func checkPvc(pvcName string, instance *oraclerestart.OracleRestart, kClient client.Client) (*corev1.PersistentVolumeClaim, error) { + pvcFound := &corev1.PersistentVolumeClaim{} + err := kClient.Get(context.TODO(), types.NamespacedName{ + Name: pvcName, + Namespace: instance.Namespace, + }, pvcFound) + if err != nil { + return pvcFound, err + } + return pvcFound, nil +} + +func checkPv(pvName string, instance *oraclerestart.OracleRestart, kClient client.Client) (*corev1.PersistentVolume, error) { + pvFound := &corev1.PersistentVolume{} + err := kClient.Get(context.TODO(), types.NamespacedName{ + Name: pvName, + Namespace: instance.Namespace, + }, pvFound) + if err != nil { + return pvFound, err + } + return pvFound, nil +} +func DelORestartPVC(instance *oraclerestart.OracleRestart, pindex int, cindex int, diskName string, disk *oraclerestart.AsmDiskDetails, kClient client.Client, logger logr.Logger) error { + pvcName := GetAsmPvcName(instance.Name, diskName, instance) + LogMessages("DEBUG", "Attempting to delete PVC: "+GetFmtStr(pvcName), nil, instance, logger) + + pvc, err := checkPvc(pvcName, instance, kClient) + if err != nil { + if apierrors.IsNotFound(err) { + LogMessages("DEBUG", "PVC not found, skipping deletion.", nil, instance, logger) + return nil + } + return err + } + + // Remove finalizers if any + if len(pvc.GetFinalizers()) > 0 { + LogMessages("DEBUG", "Removing PVC finalizers", nil, instance, logger) + pvc.SetFinalizers([]string{}) + if err := kClient.Update(context.Background(), pvc); err != nil { + return fmt.Errorf("failed to remove finalizers from PVC %s: %v", pvcName, err) + } + } + + if err := kClient.Delete(context.Background(), pvc); err != nil { + return fmt.Errorf("failed to delete PVC %s: %v", pvcName, err) + } + return nil +} + +func DelRestartSwPvc(instance *oraclerestart.OracleRestart, OraRestartSpex oraclerestart.OracleRestartInstDetailSpec, kClient client.Client, logger logr.Logger) error { + + pvcName := GetSwPvcName(instance.Name, instance) + LogMessages("DEBUG", "Inside the delPvc and received param: "+GetFmtStr(pvcName), nil, instance, logger) + pvcFound, err := checkPvc(pvcName, instance, kClient) + if err != nil { + LogMessages("DEBUG", "Error occurred in finding the pvc claim!", nil, instance, logger) + return nil + } + err = kClient.Delete(context.Background(), pvcFound) + if err != nil { + LogMessages("DEBUG", "Error occurred in deleting the pvc claim!", nil, instance, logger) + return err + } + return nil +} + +func DelORestartPv(instance *oraclerestart.OracleRestart, pindex int, cindex int, diskName string, disk *oraclerestart.AsmDiskDetails, kClient client.Client, logger logr.Logger) error { + + pvName := GetAsmPvName(instance.Name, diskName, instance) + LogMessages("DEBUG", "Inside the delPv and received param: "+GetFmtStr(pvName), nil, instance, logger) + pvFound, err := checkPv(pvName, instance, kClient) + if err != nil { + LogMessages("DEBUG", "Error occurred in finding the pv claim!", nil, instance, logger) + return nil + } + err = kClient.Delete(context.Background(), pvFound) + if err != nil { + LogMessages("DEBUG", "Error occurred in deleting the pv claim!", nil, instance, logger) + return err + } + return nil +} + +func CheckORestartSvc(instance *oraclerestart.OracleRestart, svcType string, OraRestartSpex oraclerestart.OracleRestartInstDetailSpec, svcName string, kClient client.Client) (*corev1.Service, error) { + svcFound := &corev1.Service{} + var name string + + name = getOracleRestartSvcName(instance, OraRestartSpex, svcType) + + err := kClient.Get(context.TODO(), types.NamespacedName{ + Name: name, + Namespace: instance.Namespace, + }, svcFound) + if err != nil { + return svcFound, err + } + return svcFound, nil +} + +func PodListValidation(podList *corev1.PodList, sfName string, instance *oraclerestart.OracleRestart, kClient client.Client) (bool, *corev1.Pod, *corev1.Pod) { + var notReadyPod *corev1.Pod + + for _, pod := range podList.Items { + if strings.Contains(pod.Name, sfName) { + // Check pod status + if pod.Status.Phase != corev1.PodRunning { + if notReadyPod == nil { + notReadyPod = &pod + } + continue + } + + // Check container readiness + allContainersReady := true + for _, containerStatus := range pod.Status.ContainerStatuses { + if !containerStatus.Ready { + allContainersReady = false + break + } + } + + if allContainersReady { + // Return the pod if it is ready + return true, &pod, nil + } else { + // Return the first not ready pod found + if notReadyPod == nil { + notReadyPod = &pod + } + } + } + } + + // Return false if no ready pod was found, and the first not ready pod (if any) + return false, nil, notReadyPod +} + +func GetPodList(sfsetName string, instance *oraclerestart.OracleRestart, kClient client.Client, OraRestartSpex oraclerestart.OracleRestartInstDetailSpec, +) (*corev1.PodList, error) { + podList := &corev1.PodList{} + //labelSelector := labels.SelectorFromSet(getlabelsForRAC(instance)) + //labelSelector := map[string]labels.Selector{} + var labelSelector labels.Selector = labels.SelectorFromSet(getSvcLabelsForOracleRestart(-1, OraRestartSpex)) + + listOps := &client.ListOptions{Namespace: instance.Namespace, LabelSelector: labelSelector} + + err := kClient.List(context.TODO(), podList, listOps) + if err != nil { + return nil, err + } + return podList, nil +} + +func checkPod(instance *oraclerestart.OracleRestart, pod *corev1.Pod, kClient client.Client, +) error { + err := kClient.Get(context.TODO(), types.NamespacedName{ + Name: pod.Name, + Namespace: instance.Namespace, + }, pod) + + if err != nil { + // Pod Doesn't exist + return err + } + + return nil +} + +func checkPodStatus(pod *corev1.Pod, kClient client.Client, +) error { + var msg string + for _, condition := range pod.Status.Conditions { + if pod.Status.Phase == corev1.PodRunning { + if condition.Type == corev1.PodReady { + // msg = "Pod Status is running" + // LogMessages("DEBUG", msg) + return nil + } + } else { + msg = "Pod is not scheduled or ready " + pod.Name + ".Describe the pod to check the detailed message" + return fmt.Errorf(msg) + } + } + return nil +} + +func checkContainerStatus(pod *corev1.Pod, kClient client.Client, +) error { + + var statuses []corev1.ContainerStatus + var msg string + // msg = "Inside the function checkContainerStatus" + // LogMessages("DEBUG", msg) + statuses = pod.Status.ContainerStatuses + var isRunning bool = false + for _, status := range statuses { + if status.State.Running == nil { + isRunning = false + } else { + isRunning = true + break + } + } + msg = "Container is not in running state" + pod.Name + ".Describe the pod to check the detailed message" + if isRunning { + return nil + } else { + return fmt.Errorf(msg) + } +} + +// NewNamespace creates a corev1.Namespace object using the provided name. +func NewNamespace(name string) *corev1.Namespace { + return &corev1.Namespace{ + TypeMeta: metav1.TypeMeta{ + Kind: "Namespace", + APIVersion: corev1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + } +} + +func getOwnerRef(instance *oraclerestart.OracleRestart, +) []metav1.OwnerReference { + + var ownerRef []metav1.OwnerReference + ownerRef = append(ownerRef, metav1.OwnerReference{Kind: instance.GroupVersionKind().Kind, APIVersion: instance.APIVersion, Name: instance.Name, UID: types.UID(instance.UID)}) + return ownerRef +} + +func getRacInitContainerCmd(resType string, name string, oraScriptMount string, +) string { + var initCmd string + if oraScriptMount != "NOLOC" { + initCmd = resType + ";chown -R 54321:54321 " + oraScriptMount + ";chmod 755 " + oraScriptMount + "/*" + } else { + initCmd = resType + } + + return initCmd +} + +func GetFmtStr(pstr string, +) string { + return "[" + pstr + "]" +} + +func getClusterState(podName string, instance *oraclerestart.OracleRestart, specidx int, kubeClient kubernetes.Interface, kubeConfig clientcmd.ClientConfig, logger logr.Logger, +) string { + stdoutput, _, err := ExecCommand(podName, getGiHealthCmd(), kubeClient, kubeConfig, instance, logger) + if err != nil { + msg := "Pending" + LogMessages("DEBUG", msg, err, instance, logger) + return msg + } + return strings.TrimSpace(stdoutput) +} + +func getDbInstState(podName string, instance *oraclerestart.OracleRestart, specidx int, kubeClient kubernetes.Interface, kubeConfig clientcmd.ClientConfig, logger logr.Logger, +) string { + stdoutput, _, err := ExecCommand(podName, getOracleRestartDbModeCmd(), kubeClient, kubeConfig, instance, logger) + if err != nil { + msg := "Pending" + LogMessages("DEBUG", msg, err, instance, logger) + return msg + } + return strings.TrimSpace(stdoutput) +} + +func getAsmInstState(podName string, instance *oraclerestart.OracleRestart, specidx int, kubeClient kubernetes.Interface, kubeConfig clientcmd.ClientConfig, logger logr.Logger, +) *oraclerestart.AsmInstanceStatus { + AsmStorageStatus := &oraclerestart.AsmInstanceStatus{} + diskGroup := getAsmDiskgroup(podName, instance, specidx, kubeClient, kubeConfig, logger) + if diskGroup == "Pending" { + return AsmStorageStatus + } + dglist := strings.Split(diskGroup, ",") + for _, dg := range dglist { + asmdg := oraclerestart.AsmDiskgroupStatus{} + disks := getAsmDisks(podName, string(dg), instance, specidx, kubeClient, kubeConfig, logger) + redundancy := getAsmDgRedundancy(podName, string(dg), instance, specidx, kubeClient, kubeConfig, logger) + + asmdg.Name = string(dg) + asmdg.Disks = disks + asmdg.Redundancy = redundancy + AsmStorageStatus.Diskgroup = append(AsmStorageStatus.Diskgroup, asmdg) + } + return AsmStorageStatus +} +func GetAsmInstState(podName string, instance *oraclerestart.OracleRestart, specidx int, kubeClient kubernetes.Interface, kubeConfig clientcmd.ClientConfig, logger logr.Logger, +) *oraclerestart.AsmInstanceStatus { + AsmStorageStatus := &oraclerestart.AsmInstanceStatus{} + diskGroup := getAsmDiskgroup(podName, instance, specidx, kubeClient, kubeConfig, logger) + if diskGroup == "Pending" { + return AsmStorageStatus + } + dglist := strings.Split(diskGroup, ",") + for _, dg := range dglist { + asmdg := oraclerestart.AsmDiskgroupStatus{} + disks := getAsmDisks(podName, string(dg), instance, specidx, kubeClient, kubeConfig, logger) + redundancy := getAsmDgRedundancy(podName, string(dg), instance, specidx, kubeClient, kubeConfig, logger) + + asmdg.Name = string(dg) + asmdg.Disks = disks + asmdg.Redundancy = redundancy + AsmStorageStatus.Diskgroup = append(AsmStorageStatus.Diskgroup, asmdg) + } + return AsmStorageStatus +} + +func getAsmDiskgroup(podName string, instance *oraclerestart.OracleRestart, specidx int, kubeClient kubernetes.Interface, kubeConfig clientcmd.ClientConfig, logger logr.Logger, +) string { + + stdoutput, _, err := ExecCommand(podName, getAsmDiskgroupCmd(), kubeClient, kubeConfig, instance, logger) + if err != nil { + msg := "Pending" + LogMessages("DEBUG", msg, err, instance, logger) + return msg + } + return strings.TrimSpace(stdoutput) +} + +func getAsmDisks(podName string, dg string, instance *oraclerestart.OracleRestart, specidx int, kubeClient kubernetes.Interface, kubeConfig clientcmd.ClientConfig, logger logr.Logger, +) []string { + + stdoutput, _, err := ExecCommand(podName, getAsmDisksCmd(dg), kubeClient, kubeConfig, instance, logger) + if err != nil { + msg := "Pending" + LogMessages("DEBUG", msg, err, instance, logger) + return strings.Split(msg, ",") + } + cleanOutput := strings.ReplaceAll(stdoutput, "\r", "") + return strings.Split(strings.TrimSpace(cleanOutput), "\n") +} + +func getAsmDgRedundancy(podName string, dg string, instance *oraclerestart.OracleRestart, specidx int, kubeClient kubernetes.Interface, kubeConfig clientcmd.ClientConfig, logger logr.Logger, +) string { + + stdoutput, _, err := ExecCommand(podName, getAsmDgRedundancyCmd(dg), kubeClient, kubeConfig, instance, logger) + if err != nil { + msg := "Pending" + LogMessages("DEBUG", msg, err, instance, logger) + return msg + } + return strings.TrimSpace(stdoutput) +} + +func getAsmInstName(podName string, instance *oraclerestart.OracleRestart, specidx int, kubeClient kubernetes.Interface, kubeConfig clientcmd.ClientConfig, logger logr.Logger, +) string { + + stdoutput, _, err := ExecCommand(podName, getAsmInstNameCmd(), kubeClient, kubeConfig, instance, logger) + if err != nil { + msg := "Pending" + LogMessages("DEBUG", msg, err, instance, logger) + return msg + } + return strings.TrimSpace(stdoutput) +} + +func getAsmInstStatus(podName string, instance *oraclerestart.OracleRestart, specidx int, kubeClient kubernetes.Interface, kubeConfig clientcmd.ClientConfig, logger logr.Logger, +) string { + + stdoutput, _, err := ExecCommand(podName, getAsmInstStatusCmd(), kubeClient, kubeConfig, instance, logger) + if err != nil { + msg := "Pending" + LogMessages("DEBUG", msg, err, instance, logger) + return msg + } + return strings.TrimSpace(stdoutput) +} + +func getOracleRestartInstStateFile(podName string, instance *oraclerestart.OracleRestart, specidx int, kubeClient kubernetes.Interface, kubeConfig clientcmd.ClientConfig, logger logr.Logger, +) string { + + stdoutput, _, err := ExecCommand(podName, getOracleRestartInstStateFileCmd(), kubeClient, kubeConfig, instance, logger) + if err != nil { + msg := "Pending" + LogMessages("DEBUG", msg, err, instance, logger) + return msg + } + //Possible values are "provisioning", "addnode", "failed", "completed" + if strings.ToLower(strings.TrimSpace(stdoutput)) == "provisioning" { + return string(oraclerestart.OracleRestartProvisionState) + } else if strings.ToLower(strings.TrimSpace(stdoutput)) == "completed" { + return string(oraclerestart.OracleRestartAvailableState) + } else if strings.ToLower(strings.TrimSpace(stdoutput)) == "addnode" { + return string(oraclerestart.OracleRestartAddInstState) + } else if strings.ToLower(strings.TrimSpace(stdoutput)) == "failed" { + return string(oraclerestart.OracleRestartFailedState) + } else { + return string(oraclerestart.OracleRestartPendingState) + } +} + +func getDBVersion(podName string, instance *oraclerestart.OracleRestart, specidx int, kubeClient kubernetes.Interface, kubeConfig clientcmd.ClientConfig, logger logr.Logger, +) string { + stdoutput, _, err := ExecCommand(podName, getOracleRestartDbVersionCmd(), kubeClient, kubeConfig, instance, logger) + if err != nil { + msg := "Pending" + LogMessages("DEBUG", msg, err, instance, logger) + return msg + } + if strings.Contains(stdoutput, "ERROR") { + return "NOTAVAILABLE" + } else { + return strings.TrimSpace(stdoutput) + } +} + +func getDbState(podName string, instance *oraclerestart.OracleRestart, specidx int, kubeClient kubernetes.Interface, kubeConfig clientcmd.ClientConfig, logger logr.Logger, +) string { + stdoutput, _, err := ExecCommand(podName, getOracleRestartHealthCmd(), kubeClient, kubeConfig, instance, logger) + if err != nil { + msg := "Pending" + LogMessages("DEBUG", msg, err, instance, logger) + return msg + } + return strings.TrimSpace(stdoutput) +} + +func getDbRole(podName string, instance *oraclerestart.OracleRestart, specidx int, kubeClient kubernetes.Interface, kubeConfig clientcmd.ClientConfig, logger logr.Logger, +) string { + stdoutput, _, err := ExecCommand(podName, getDbRoleCmd(), kubeClient, kubeConfig, instance, logger) + if err != nil { + msg := "Pending" + LogMessages("DEBUG", msg, err, instance, logger) + return msg + } + return strings.TrimSpace(stdoutput) +} + +func getConnStr(podName string, instance *oraclerestart.OracleRestart, specidx int, kubeClient kubernetes.Interface, kubeConfig clientcmd.ClientConfig, logger logr.Logger, +) string { + stdoutput, _, err := ExecCommand(podName, getConnStrCmd(), kubeClient, kubeConfig, instance, logger) + if err != nil { + msg := "Pending" + LogMessages("DEBUG", msg, err, instance, logger) + return msg + } + return strings.TrimSpace(stdoutput) +} + +func getExternalConnStr( + podName string, + instance *oraclerestart.OracleRestart, + specidx int, + kubeClient kubernetes.Interface, + kubeConfig clientcmd.ClientConfig, + logger logr.Logger, +) string { + + switch { + // Case 1: Neither service defined + case len(instance.Spec.NodePortSvc.PortMappings) == 0 && len(instance.Spec.LbService.PortMappings) == 0: + return "" + + // Case 2: LoadBalancer service defined, try to use it + case len(instance.Spec.LbService.PortMappings) != 0: + lbServiceName := instance.Spec.LbService.SvcName + "-0-lbsvc" + lbSvc, err := kubeClient.CoreV1().Services(instance.Namespace).Get(context.TODO(), lbServiceName, metav1.GetOptions{}) + if err == nil && lbSvc.Spec.Type == corev1.ServiceTypeLoadBalancer { + // Extract external IP or hostname + var lbExtIP string + for _, ingress := range lbSvc.Status.LoadBalancer.Ingress { + if ingress.IP != "" { + lbExtIP = ingress.IP + break + } else if ingress.Hostname != "" { + lbExtIP = ingress.Hostname + break + } + } + // Find port 1521 + var lbPort int32 + for _, port := range lbSvc.Spec.Ports { + if port.Port == 1521 { + lbPort = port.Port + break + } + } + if lbExtIP != "" && lbPort != 0 { + serviceName := instance.Spec.ServiceDetails.Name + return fmt.Sprintf("EXTERNAL: %s:%d/%s", lbExtIP, lbPort, serviceName) + } + } + return "" + + // Case 3: NodePort service defined, try to use it + case len(instance.Spec.NodePortSvc.PortMappings) != 0: + npServiceName := instance.Spec.NodePortSvc.SvcName + "-0-npsvc" + npSvc, err := kubeClient.CoreV1().Services(instance.Namespace).Get(context.TODO(), npServiceName, metav1.GetOptions{}) + if err != nil || npSvc.Spec.Type != corev1.ServiceTypeNodePort { + return "" + } + // Find port 1521 + var nodePort int32 + for _, port := range npSvc.Spec.Ports { + if port.Port == 1521 { + nodePort = port.NodePort + break + } + } + if nodePort == 0 { + return "" + } + // Get first node external/internal IP + nodeList, err := kubeClient.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{}) + if err != nil || len(nodeList.Items) == 0 { + return "" + } + var nodeIP string + for _, addr := range nodeList.Items[0].Status.Addresses { + if addr.Type == corev1.NodeExternalIP { + nodeIP = addr.Address + break + } else if addr.Type == corev1.NodeInternalIP && nodeIP == "" { + nodeIP = addr.Address + } + } + if nodeIP == "" { + return "" + } + serviceName := instance.Spec.ServiceDetails.Name + return fmt.Sprintf("%s:%d/%s", nodeIP, nodePort, serviceName) + } + + return "" +} + +func getClientEtcHost(podNames []string, instance *oraclerestart.OracleRestart, specidx int, kubeClient kubernetes.Interface, kubeConfig clientcmd.ClientConfig, logger logr.Logger, nodeDetails map[string]*corev1.Node) []string { + // Prepare to update ClientEtcHost + var clientEtcHost []string + + // Loop through all pod names + for _, podName := range podNames { + node := nodeDetails[podName] + if node == nil { + logger.Error(nil, "Node details not found for pod", "podName", podName) + continue + } + + // Get nodeIP from node details + nodeIP := getNodeIPFromNodeDetails(node) + + // Construct the line to be added to ClientEtcHost + line := fmt.Sprintf("%s %s.rac.svc.cluster.local %s-vip.rac.svc.cluster.local OracleRestartNode-scan.rac.svc.cluster.local", nodeIP, podName, podName) + clientEtcHost = append(clientEtcHost, line) + } + + // Assuming you are returning clientEtcHost as well for any further processing + return clientEtcHost +} + +func getNodeIPFromNodeDetails(node *corev1.Node) string { + var internalIP, externalIP string + + // Extract internal and external IPs from node details + for _, addr := range node.Status.Addresses { + if addr.Type == corev1.NodeInternalIP { + internalIP = addr.Address + } else if addr.Type == corev1.NodeExternalIP { + externalIP = addr.Address + } + } + + // Return external IP if it exists, otherwise return internal IP + if externalIP != "" { + return externalIP + } + return internalIP +} + +// readHostsFile reads the /etc/hosts file from the pod running in privileged mode +func readHostsFile(podName string, kubeClient kubernetes.Interface, kubeConfig clientcmd.ClientConfig, instance *oraclerestart.OracleRestart, logger logr.Logger) (string, error) { + + cmd := []string{"cat", "/etc/hosts"} // Assuming this matches the MountPath in buildContainerSpecForRac + + stdOutput, _, err := ExecCommand(podName, cmd, kubeClient, kubeConfig, instance, logger) + if err != nil { + msg := "Failed to read /etc/hosts from pod" + podName + LogMessages("DEBUG", msg, err, instance, logger) + return "", err + } + return strings.TrimSpace(stdOutput), nil +} + +// Helper function to parse /etc/hosts content +func parseHostsContent(content string) string { + var uncommentedLines string + + lines := strings.Split(content, "\n") + for _, line := range lines { + trimmedLine := strings.TrimSpace(line) + if trimmedLine != "" && !strings.HasPrefix(trimmedLine, "#") { + uncommentedLines = trimmedLine + } + } + + return uncommentedLines +} + +func getSvcState(podName string, instance *oraclerestart.OracleRestart, specidx int, kubeClient kubernetes.Interface, kubeConfig clientcmd.ClientConfig, logger logr.Logger, +) string { + + stdoutput, _, err := ExecCommand(podName, getDBServiceStatus(instance.Status.ConfigParams.DbHome, instance.Status.ConfigParams.DbName, instance.Status.ServiceDetails.Name), kubeClient, kubeConfig, instance, logger) + if err != nil { + msg := "Pending" + LogMessages("DEBUG", msg, err, instance, logger) + return msg + } + return strings.TrimSpace(stdoutput) +} + +func getPdbConnStr(podName string, instance *oraclerestart.OracleRestart, specidx int, kubeClient kubernetes.Interface, kubeConfig clientcmd.ClientConfig, logger logr.Logger, +) string { + stdoutput, _, err := ExecCommand(podName, getPdbConnStrCmd(), kubeClient, kubeConfig, instance, logger) + if err != nil { + msg := "Pending" + LogMessages("DEBUG", msg, err, instance, logger) + return msg + } + return strings.TrimSpace(stdoutput) +} + +// getGridHome retrieves the GRID_HOME environment variable from the specified pod +func getGridHome(podName string, instance *oraclerestart.OracleRestart, kubeClient kubernetes.Interface, kubeConfig clientcmd.ClientConfig, logger logr.Logger) (string, error) { + stdoutput, _, err := ExecCommand(podName, getGridHomeCmd(), kubeClient, kubeConfig, instance, logger) + if err != nil { + msg := "Error retrieving GRID_HOME" + LogMessages("DEBUG", msg, err, instance, logger) + return "", err + } + + return strings.TrimSpace(stdoutput), nil +} + +func GetAsmDevices(instance *oraclerestart.OracleRestart) string { + var asmDisk []string + if instance.Spec.AsmStorageDetails != nil { + // Loop through each DiskBySize and add disk names to asmDisk + for _, diskBySize := range instance.Spec.AsmStorageDetails.DisksBySize { + asmDisk = append(asmDisk, diskBySize.DiskNames...) + } + } + return strings.Join(asmDisk, ",") +} + +// func GetScanname(instance *oraclerestart.OracleRestart) string { +// return instance.Spec.ScanSvcName +// } + +func GetSSHkey(instance *oraclerestart.OracleRestart, name string, kClient client.Client) (bool, bool) { + + var privKeyFlag, pubKeyFlag bool + secretFound, err := CheckSecret(instance, name, kClient) + + if err != nil { + + } else { + for key := range secretFound.Data { + switch key { + case instance.Spec.SshKeySecret.PrivKeySecretName: + privKeyFlag = true + case instance.Spec.SshKeySecret.PubKeySecretName: + pubKeyFlag = true + } + } + } + + return privKeyFlag, pubKeyFlag + +} + +func GetDbSecret(instance *oraclerestart.OracleRestart, name string, kClient client.Client) (bool, bool, bool) { + + var commonospassflag, commonpwdfile, pwdkeyflag bool + secretFound, err := CheckSecret(instance, name, kClient) + + if err != nil { + return false, false, false + } else { + for key := range secretFound.Data { + switch key { + case instance.Spec.DbSecret.PwdFileName: + commonospassflag = true + case instance.Spec.DbSecret.KeyFileName: + pwdkeyflag = true + case "pwdfile": + commonpwdfile = true + } + } + } + + return commonospassflag, pwdkeyflag, commonpwdfile + +} + +func GetTdeWalletSecret(instance *oraclerestart.OracleRestart, name string, kClient client.Client) (bool, bool, bool) { + + var commonospassflag, commonpwdfile, pwdkeyflag bool + secretFound, err := CheckSecret(instance, name, kClient) + + if err != nil { + return false, false, false + } else { + for key := range secretFound.Data { + switch key { + case instance.Spec.TdeWalletSecret.PwdFileName: + commonospassflag = true + case instance.Spec.TdeWalletSecret.KeyFileName: + pwdkeyflag = true + case "pwdfile": + commonpwdfile = true + } + } + } + return commonospassflag, pwdkeyflag, commonpwdfile +} + +func CheckSecret(instance *oraclerestart.OracleRestart, secretName string, kClient client.Client) (*corev1.Secret, error) { + + secretFound := &corev1.Secret{} + + err := kClient.Get(context.TODO(), types.NamespacedName{ + Name: secretName, + Namespace: instance.Namespace, + }, secretFound) + + if err != nil { + return secretFound, err + } + + return secretFound, nil +} + +func GetGiResponseFile(instance *oraclerestart.OracleRestart, kClient client.Client) (bool, map[string]string) { + + var giFileFlag bool + cName := instance.Spec.ConfigParams.GridResponseFile.ConfigMapName + + configMapFound, err := CheckConfigMap(instance, cName, kClient) + if err != nil { + + } else { + for key := range configMapFound.Data { + if key == instance.Spec.ConfigParams.GridResponseFile.Name { + giFileFlag = true + } + } + } + return giFileFlag, configMapFound.Data +} + +func GetDbResponseFile(instance *oraclerestart.OracleRestart, kClient client.Client) (bool, map[string]string) { + + var dbFileFlag bool + cName := instance.Spec.ConfigParams.DbResponseFile.ConfigMapName + + configMapFound, err := CheckConfigMap(instance, cName, kClient) + if err != nil { + + } else { + for key := range configMapFound.Data { + if key == instance.Spec.ConfigParams.DbResponseFile.Name { + dbFileFlag = true + } + } + } + return dbFileFlag, configMapFound.Data +} + +func CheckConfigMap(instance *oraclerestart.OracleRestart, configMapName string, kClient client.Client) (*corev1.ConfigMap, error) { + + configMapFound := &corev1.ConfigMap{} + + err := kClient.Get(context.TODO(), types.NamespacedName{ + Name: configMapName, + Namespace: instance.Namespace, + }, configMapFound) + + if err != nil { + return configMapFound, err + } + + return configMapFound, nil + +} + +func GetConfigList(sfsetName string, instance *oraclerestart.OracleRestart, kClient client.Client, OraRestartSpex oraclerestart.OracleRestartInstDetailSpec, +) (*corev1.ConfigMapList, error) { + cmapList := &corev1.ConfigMapList{} + //labelSelector := labels.SelectorFromSet(getlabelsForRAC(instance)) + //labelSelector := map[string]labels.Selector{} + var labelSelector labels.Selector = labels.SelectorFromSet(getSvcLabelsForOracleRestart(-1, OraRestartSpex)) + + listOps := &client.ListOptions{Namespace: instance.Namespace, LabelSelector: labelSelector} + + err := kClient.List(context.TODO(), cmapList, listOps) + if err != nil { + return nil, err + } + return cmapList, nil +} + +func checkElem(list1 []string, element string) bool { + + if element != "" { + if len(list1) > 0 { + for _, v := range list1 { + if v == element { + return true + } + } + } + } + + return false +} + +func ValidateNetInterface(net string, instance *oraclerestart.OracleRestart, rspNetData string) error { + + var err error + + if net != "" { + if !strings.Contains(rspNetData, net) { + err = fmt.Errorf("error occurred during retreiving network card detail from grid responsefile: %s", "The key does not exist") + } + } + + return err +} + +func CheckRspData(instance *oraclerestart.OracleRestart, kClient client.Client, key string, cName string, fname string) (string, error) { + + var rspData string + configMapFound, _ := CheckConfigMap(instance, cName, kClient) + var status bool + var value []string + + data := configMapFound.Data[fname] + + scanner := bufio.NewScanner(strings.NewReader(data)) + for scanner.Scan() { + if strings.Contains(strings.ToLower(scanner.Text()), strings.ToLower(key)) { + if strings.ToLower(key) == "variables=" { + str1 := strings.Replace(scanner.Text(), "=", ";", 1) + value = strings.Split(str1, ";") + } else { + value = strings.Split(scanner.Text(), "=") + } + rspData = value[1] + fmt.Print("Key = " + value[0] + " value= " + value[1]) + status = true + break + } + } + + if !status { + return "", errors.New("the " + key + " key and value does not exist in grid responsefile. Invalid grid responsefile.") + } + + return rspData, nil +} + +func GetServiceParams(instance *oraclerestart.OracleRestart) string { + var sparams string + + sparams = "service:" + instance.Spec.ServiceDetails.Name + notficationFlag, _ := strconv.ParseBool(instance.Spec.ServiceDetails.Notification) + if notficationFlag { + sparams = sparams + ";notification:" + "True" + } + if instance.Spec.ServiceDetails.Cardinality != "" { + sparams = sparams + ";cardinality:" + instance.Spec.ServiceDetails.Cardinality + } + if len(instance.Spec.ServiceDetails.Preferred) > 0 { + sparams = sparams + ";preferred:" + strings.Join(instance.Spec.ServiceDetails.Preferred[:], ",") + } + if len(instance.Spec.ServiceDetails.Available) > 0 { + sparams = sparams + ";available:" + strings.Join(instance.Spec.ServiceDetails.Available[:], ",") + } + if instance.Spec.ServiceDetails.Pdb != "" { + sparams = sparams + ";pdb:" + instance.Spec.ServiceDetails.Pdb + } + + if instance.Spec.ServiceDetails.ClbGoal != "" { + sparams = sparams + ";clbgoal:" + instance.Spec.ServiceDetails.ClbGoal + } + + if instance.Spec.ServiceDetails.RlbGoal != "" { + sparams = sparams + ";rlbgoal:" + instance.Spec.ServiceDetails.RlbGoal + } + + if instance.Spec.ServiceDetails.FailOverRestore != "" { + sparams = sparams + ";failover_restore:" + instance.Spec.ServiceDetails.FailOverRestore + } + + if instance.Spec.ServiceDetails.FailBack != "" { + sparams = sparams + ";failback:" + instance.Spec.ServiceDetails.FailBack + } + + cmdFlag, _ := strconv.ParseBool(instance.Spec.ServiceDetails.CommitOutCome) + if cmdFlag { + sparams = sparams + ";commit_outcome:" + "True" + } + + cmtPathFlag, _ := strconv.ParseBool(instance.Spec.ServiceDetails.CommitOutComeFastPath) + if cmtPathFlag { + sparams = sparams + ";commit_outcome_fastpath:" + "True" + } + + if instance.Spec.ServiceDetails.FailBack != "" { + sparams = sparams + ";failback:" + instance.Spec.ServiceDetails.FailBack + } + + if instance.Spec.ServiceDetails.FailOverType != "" { + sparams = sparams + ";failovertype:" + instance.Spec.ServiceDetails.FailOverType + } + + if instance.Spec.ServiceDetails.FailOverDelay > 0 { + sparams = sparams + ";failoverdelay:" + strconv.FormatInt(int64(instance.Spec.ServiceDetails.FailOverDelay), 10) + } + + if instance.Spec.ServiceDetails.FailOverRetry > 0 { + sparams = sparams + ";failoverretry:" + strconv.FormatInt(int64(instance.Spec.ServiceDetails.FailOverRetry), 10) + } + + if instance.Spec.ServiceDetails.DrainTimeOut > 0 { + sparams = sparams + ";drain_timeout:" + strconv.FormatInt(int64(instance.Spec.ServiceDetails.DrainTimeOut), 10) + } + + if instance.Spec.ServiceDetails.Dtp != "" { + sparams = sparams + ";dtp:" + instance.Spec.ServiceDetails.Dtp + } + + if instance.Spec.ServiceDetails.Role != "" { + sparams = sparams + ";role:" + instance.Spec.ServiceDetails.Role + } + + if instance.Spec.ServiceDetails.Retention > 0 { + sparams = sparams + ";retention:" + strconv.FormatInt(int64(instance.Spec.ServiceDetails.Retention), 10) + } + + return sparams +} + +// . This function get the healthy node name from instance.status + +func GetHealthyNode(instance *oraclerestart.OracleRestart) (string, error) { + var i int32 + + if len(instance.Status.OracleRestartNodes) > 0 { + for i = 0; i < int32(len(instance.Status.OracleRestartNodes)); i++ { + if instance.Status.OracleRestartNodes[i].NodeDetails != nil { + if instance.Status.OracleRestartNodes[i].NodeDetails.ClusterState == "HEALTHY" { + return instance.Status.OracleRestartNodes[i].Name, nil + } + } + } + } + return "", fmt.Errorf("no healthy node exist") +} + +func GetHealthyNodeCounts(instance *oraclerestart.OracleRestart) (int, error) { + var i, totalNodes, healthyNodeCount int + + totalNodes = len(instance.Status.OracleRestartNodes) + + if totalNodes > 0 { + for i = 0; i < totalNodes; i++ { + if instance.Status.OracleRestartNodes[i].NodeDetails != nil { + if instance.Status.OracleRestartNodes[i].NodeDetails.ClusterState == "HEALTHY" { + healthyNodeCount++ + } + } + } + } + + if totalNodes == healthyNodeCount { + return healthyNodeCount, nil + } + return 0, fmt.Errorf("healthy cluster node counts are not matching with total cluster nodes") +} +func GetSwPvcName(name string, instance *oraclerestart.OracleRestart) string { + //// If you are making any change, please refer SwVolumeClaimTemplatesForOracleRestart function as we add instance.Spec.InstDetails.Name + "-0" + pvcName := "odb-sw-pvc-" + name + "-" + instance.Spec.InstDetails.Name + "-0" + return pvcName +} + +func CheckDiskInAsmDeviceList(instance *oraclerestart.OracleRestart, diskName string) string { + dgDisk := []string{"CRS", "DATA", "RECO", "REDO"} + + recoDisk := strings.Split(instance.Spec.ConfigParams.RecoAsmDeviceList, ",") + redoDisk := strings.Split(instance.Spec.ConfigParams.RedoAsmDeviceList, ",") + dataDisk := strings.Split(instance.Spec.ConfigParams.DbAsmDeviceList, ",") + crsDisk := strings.Split(instance.Spec.ConfigParams.CrsAsmDeviceList, ",") + + for _, value := range dgDisk { + switch value { + case "CRS": + if slices.Contains(crsDisk, diskName) { + return "CRSDG" + } + case "DATA": + if slices.Contains(dataDisk, diskName) { + return "DATADG" + } + case "RECO": + if slices.Contains(recoDisk, diskName) { + return "RECODG" + } + case "REDO": + if slices.Contains(redoDisk, diskName) { + return "REDODG" + } + default: + return "NODG" + } + + } + return "NODG" +} + +func CheckStorageClass(instance *oraclerestart.OracleRestart) string { + if len(instance.Spec.CrsDgStorageClass) == 0 && len(instance.Spec.DataDgStorageClass) == 0 && len(instance.Spec.RecoDgStorageClass) == 0 && len(instance.Spec.RedoDgStorageClass) == 0 { + return "NOSC" + } + return "SC" +} diff --git a/commons/oraclerestart/oraclerestartconstants.go b/commons/oraclerestart/oraclerestartconstants.go new file mode 100644 index 00000000..b91d9cbf --- /dev/null +++ b/commons/oraclerestart/oraclerestartconstants.go @@ -0,0 +1,159 @@ +/* +** Copyright (c) 2022 Oracle and/or its affiliates. +** +** The Universal Permissive License (UPL), Version 1.0 +** +** Subject to the condition set forth below, permission is hereby granted to any +** person obtaining a copy of this software, associated documentation and/or data +** (collectively the "Software"), free of charge and under any and all copyright +** rights in the Software, and any and all patent rights owned or freely +** licensable by each licensor hereunder covering either (i) the unmodified +** Software as contributed to or provided by such licensor, or (ii) the Larger +** Works (as defined below), to deal in both +** +** (a) the Software, and +** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +** one is included with the Software (each a "Larger Work" to which the Software +** is contributed by such licensors), +** +** without restriction, including without limitation the rights to copy, create +** derivative works of, display, perform, and distribute the Software and make, +** use, sell, offer for sale, import, export, have made, and have sold the +** Software and the Larger Work(s), and to sublicense the foregoing rights on +** either these or other terms. +** +** This license is subject to the following condition: +** The above copyright notice and either this complete permission notice or at +** a minimum a reference to the UPL must be included in all copies or +** substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +** SOFTWARE. + */ + +package commons + +func getOracleRestartDbModeCmd() []string { + oraScriptMount1 := getOraScriptMount() + oraPythonCmd := getOraPythonCmd() + var oraRacInstCmd = []string{oraScriptMount1 + "/cmdExec", oraPythonCmd, oraScriptMount1 + "/main.py ", "--checkracinst=true "} + return oraRacInstCmd +} + +func getGiHealthCmd() []string { + oraScriptMount1 := getOraScriptMount() + oraPythonCmd := getOraPythonCmd() + var oraGiCmd = []string{oraScriptMount1 + "/cmdExec", oraPythonCmd, oraScriptMount1 + "/main.py ", "--checkgilocal=true "} + return oraGiCmd +} + +func getOracleRestartHealthCmd() []string { + oraScriptMount1 := getOraScriptMount() + oraPythonCmd := getOraPythonCmd() + var oraRacCmd = []string{oraScriptMount1 + "/cmdExec", oraPythonCmd, oraScriptMount1 + "/main.py ", "--checkracdb=true "} + return oraRacCmd +} + +func getConnStrCmd() []string { + oraScriptMount1 := getOraScriptMount() + oraPythonCmd := getOraPythonCmd() + var oraRacConnCmd = []string{oraScriptMount1 + "/cmdExec", oraPythonCmd, oraScriptMount1 + "/main.py ", "--checkconnstr=true "} + return oraRacConnCmd +} +func getGridHomeCmd() []string { + // Command to source the envfile and echo GRID_HOME + gridHomeCmd := []string{"sh", "-c", "grep '^GRID_HOME=' /etc/rac_env_vars/envfile | cut -d'=' -f2"} + return gridHomeCmd +} + +func getPdbConnStrCmd() []string { + oraScriptMount1 := getOraScriptMount() + oraPythonCmd := getOraPythonCmd() + var oraRacConnCmd = []string{oraScriptMount1 + "/cmdExec", oraPythonCmd, oraScriptMount1 + "/main.py ", "--checkpdbconnstr=true "} + return oraRacConnCmd +} + +func getDbRoleCmd() []string { + oraScriptMount1 := getOraScriptMount() + oraPythonCmd := getOraPythonCmd() + var oraRacRoleCmd = []string{oraScriptMount1 + "/cmdExec", oraPythonCmd, oraScriptMount1 + "/main.py ", "--checkdbrole=true "} + return oraRacRoleCmd +} + +func getOracleRestartDbVersionCmd() []string { + oraScriptMount1 := getOraScriptMount() + oraPythonCmd := getOraPythonCmd() + var oraRacVersionCmd = []string{oraScriptMount1 + "/cmdExec", oraPythonCmd, oraScriptMount1 + "/main.py ", "--checkdbversion=true "} + return oraRacVersionCmd +} + +func getDBServiceStatus(dbhome string, dbname string, svcname string) []string { + oraScriptMount1 := getOraScriptMount() + oraPythonCmd := getOraPythonCmd() + svcStr := "\"" + "service=" + svcname + ";dbname=" + dbname + "\"" + var oraRacVersionCmd = []string{oraScriptMount1 + "/cmdExec", oraPythonCmd, oraScriptMount1 + "/main.py ", "--checkdbsvc=" + svcStr} + return oraRacVersionCmd +} + +// func modifyDBServiceStatus(dbhome string, dbname string, svcname string) []string { +// oraDBUser := getOraDbUser() + +// //oraGiUser := getOraGiUser() +// svcModifyCmd := "su " + oraDBUser + " -c \"" + oraDBUser + " ;srvctl modify service -s " + svcname + " -d " + dbname + " " + dbhome + "\"" +// var oraModifySvcCmd = []string{svcModifyCmd} +// return oraModifySvcCmd +// } + +func getAsmDiskgroupCmd() []string { + oraScriptMount1 := getOraScriptMount() + oraPythonCmd := getOraPythonCmd() + diskgroupscmd := []string{oraScriptMount1 + "/cmdExec", oraPythonCmd, oraScriptMount1 + "/main.py ", "--getasmdiskgroup=true"} + return diskgroupscmd +} + +func getAsmDisksCmd(diskgroup string) []string { + oraScriptMount1 := getOraScriptMount() + oraPythonCmd := getOraPythonCmd() + disks := []string{oraScriptMount1 + "/cmdExec", oraPythonCmd, oraScriptMount1 + "/main.py ", "--getasmdisks=" + diskgroup} + return disks +} + +func getAsmDgRedundancyCmd(diskgroup string) []string { + oraScriptMount1 := getOraScriptMount() + oraPythonCmd := getOraPythonCmd() + redundancy := []string{oraScriptMount1 + "/cmdExec", oraPythonCmd, oraScriptMount1 + "/main.py ", "--getdgredundancy=" + diskgroup} + return redundancy +} + +func getAsmInstNameCmd() []string { + oraScriptMount1 := getOraScriptMount() + oraPythonCmd := getOraPythonCmd() + name := []string{oraScriptMount1 + "/cmdExec", oraPythonCmd, oraScriptMount1 + "/main.py ", "--getasminstname=true"} + return name +} + +func getAsmInstStatusCmd() []string { + oraScriptMount1 := getOraScriptMount() + oraPythonCmd := getOraPythonCmd() + status := []string{oraScriptMount1 + "/cmdExec", oraPythonCmd, oraScriptMount1 + "/main.py ", "--getasminststatus=true"} + return status +} + +// This function generates cmmand for modifying asm cardinality for the nodes +func getUpdateAsmCount(gihome string) []string { + oraScriptMount1 := getOraScriptMount() + oraPythonCmd := getOraPythonCmd() + var oraUpdAsmCountCmd = []string{oraScriptMount1 + "/cmdExec", oraPythonCmd, oraScriptMount1 + "/main.py ", "--updateasmcount=2"} + return oraUpdAsmCountCmd +} + +// This function generates cmmand for modifying asm cardinality for the nodes +func getOracleRestartInstStateFileCmd() []string { + var oraStateCmd = []string{"/bin/bash", "-c", " cat /tmp/orod/.statefile"} + return oraStateCmd +} diff --git a/commons/oraclerestart/oraclerestartprov.go b/commons/oraclerestart/oraclerestartprov.go new file mode 100644 index 00000000..81e8fb48 --- /dev/null +++ b/commons/oraclerestart/oraclerestartprov.go @@ -0,0 +1,1154 @@ +/* +** Copyright (c) 2022 Oracle and/or its affiliates. +** +** The Universal Permissive License (UPL), Version 1.0 +** +** Subject to the condition set forth below, permission is hereby granted to any +** person obtaining a copy of this software, associated documentation and/or data +** (collectively the "Software"), free of charge and under any and all copyright +** rights in the Software, and any and all patent rights owned or freely +** licensable by each licensor hereunder covering either (i) the unmodified +** Software as contributed to or provided by such licensor, or (ii) the Larger +** Works (as defined below), to deal in both +** +** (a) the Software, and +** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +** one is included with the Software (each a "Larger Work" to which the Software +** is contributed by such licensors), +** +** without restriction, including without limitation the rights to copy, create +** derivative works of, display, perform, and distribute the Software and make, +** use, sell, offer for sale, import, export, have made, and have sold the +** Software and the Larger Work(s), and to sublicense the foregoing rights on +** either these or other terms. +** +** This license is subject to the following condition: +** The above copyright notice and either this complete permission notice or at +** a minimum a reference to the UPL must be included in all copies or +** substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +** SOFTWARE. + */ + +package commons + +import ( + "context" + "fmt" + "strconv" + "strings" + + "github.com/go-logr/logr" + oraclerestart "github.com/oracle/oracle-database-operator/apis/database/v4" + utils "github.com/oracle/oracle-database-operator/commons/oraclerestart/utils" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + storagev1 "k8s.io/api/storage/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// Constants for rac-stateful StatefulSet & Volumes +func buildLabelsForOracleRestart(instance *oraclerestart.OracleRestart, label string) map[string]string { + return map[string]string{ + "cluster": "oraclerestart", + } + + // "oralabel": getLabelForOracleRestart(instance), +} + +func buildLabelsForAsmPv(instance *oraclerestart.OracleRestart, diskName string) map[string]string { + return map[string]string{ + "asm_vol": "block-asm-pv-" + getLabelForOracleRestart(instance) + "-" + diskName[strings.LastIndex(diskName, "/")+1:], + } +} + +func getLabelForOracleRestart(instance *oraclerestart.OracleRestart) string { + + return instance.Name +} + +func BuildStatefulSetForOracleRestart(instance *oraclerestart.OracleRestart, OracleRestartSpex oraclerestart.OracleRestartInstDetailSpec, kClient client.Client) *appsv1.StatefulSet { + sfset := &appsv1.StatefulSet{ + TypeMeta: buildTypeMetaForOracleRestart(), + ObjectMeta: builObjectMetaForOracleRestart(instance, OracleRestartSpex), + Spec: *buildStatefulSpecForOracleRestart(instance, OracleRestartSpex, kClient), + } + return sfset +} + +// Function to build TypeMeta +func buildTypeMetaForOracleRestart() metav1.TypeMeta { + // building TypeMeta + typeMeta := metav1.TypeMeta{ + Kind: "StatefulSet", + APIVersion: "apps/v1", + } + return typeMeta +} + +// Function to build ObjectMeta +func builObjectMetaForOracleRestart(instance *oraclerestart.OracleRestart, OracleRestartSpex oraclerestart.OracleRestartInstDetailSpec) metav1.ObjectMeta { + // building objectMeta + objmeta := metav1.ObjectMeta{ + Name: OracleRestartSpex.Name, + Namespace: instance.Namespace, + Labels: buildLabelsForOracleRestart(instance, "OracleRestart"), + } + return objmeta +} + +// Function to build Stateful Specs +func buildStatefulSpecForOracleRestart( + instance *oraclerestart.OracleRestart, + OracleRestartSpex oraclerestart.OracleRestartInstDetailSpec, + kClient client.Client, +) *appsv1.StatefulSetSpec { + + // Build PodSpec first + podSpec := buildPodSpecForOracleRestart(instance, OracleRestartSpex) + + // Add service account name if specified + if instance.Spec.SrvAccountName != "" { + podSpec.ServiceAccountName = instance.Spec.SrvAccountName + } + + // Build StatefulSetSpec + sfsetspec := &appsv1.StatefulSetSpec{ + ServiceName: utils.OraSubDomain, + Selector: &metav1.LabelSelector{ + MatchLabels: buildLabelsForOracleRestart(instance, "OracleRestart"), + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Name: OracleRestartSpex.Name, + Labels: buildLabelsForOracleRestart(instance, "OracleRestart"), + }, + Spec: *podSpec, // dereference after modification + }, + } + // Add volume claim templates if a storage class is specified + if len(instance.Spec.DataDgStorageClass) != 0 && !asmPvcsExist(instance, kClient) { + sfsetspec.VolumeClaimTemplates = append(sfsetspec.VolumeClaimTemplates, ASMVolumeClaimTemplatesForDG(instance, OracleRestartSpex, &instance.Spec.DataDgStorageClass)...) + } + + if len(instance.Spec.CrsDgStorageClass) != 0 && !asmPvcsExist(instance, kClient) { + sfsetspec.VolumeClaimTemplates = append(sfsetspec.VolumeClaimTemplates, ASMVolumeClaimTemplatesForDG(instance, OracleRestartSpex, &instance.Spec.CrsDgStorageClass)...) + } + + if len(instance.Spec.RecoDgStorageClass) != 0 && !asmPvcsExist(instance, kClient) { + sfsetspec.VolumeClaimTemplates = append(sfsetspec.VolumeClaimTemplates, ASMVolumeClaimTemplatesForDG(instance, OracleRestartSpex, &instance.Spec.RecoDgStorageClass)...) + } + + if len(instance.Spec.RedoDgStorageClass) != 0 && !asmPvcsExist(instance, kClient) { + sfsetspec.VolumeClaimTemplates = append(sfsetspec.VolumeClaimTemplates, ASMVolumeClaimTemplatesForDG(instance, OracleRestartSpex, &instance.Spec.RedoDgStorageClass)...) + } + + if len(instance.Spec.SwStorageClass) != 0 && len(instance.Spec.InstDetails.HostSwLocation) == 0 { + sfsetspec.VolumeClaimTemplates = append(sfsetspec.VolumeClaimTemplates, SwVolumeClaimTemplatesForOracleRestart(instance, OracleRestartSpex)) + } + // Add annotations to the Pod template + // sfsetspec.Template.Annotations = generateNetworkDetails(instance, OracleRestartSpex, kClient) + + return sfsetspec +} + +func asmPvcsExist(instance *oraclerestart.OracleRestart, kClient client.Client) bool { + for _, diskBySize := range instance.Spec.AsmStorageDetails.DisksBySize { + for _, diskName := range diskBySize.DiskNames { + pvcName := GetAsmPvcName(instance.Name, diskName, instance) + var pvc corev1.PersistentVolumeClaim + err := kClient.Get(context.TODO(), types.NamespacedName{ + Name: pvcName, + Namespace: instance.Namespace, + }, &pvc) + + if err != nil { + if apierrors.IsNotFound(err) { + // If even one expected PVC is not found, treat as "not all exist" + return false + } + // If error is something else, assume PVCs exist to avoid accidental overwrite + return true + } + } + } + return true +} + +// Function to build PodSpec + +func buildPodSpecForOracleRestart(instance *oraclerestart.OracleRestart, OracleRestartSpex oraclerestart.OracleRestartInstDetailSpec) *corev1.PodSpec { + + spec := &corev1.PodSpec{ + Hostname: OracleRestartSpex.Name + "-0", + Subdomain: utils.OraSubDomain, + InitContainers: buildInitContainerSpecForOracleRestart(instance, OracleRestartSpex), + Containers: buildContainerSpecForOracleRestart(instance, OracleRestartSpex), + Volumes: buildVolumeSpecForOracleRestart(instance, OracleRestartSpex), + Affinity: getNodeAffinity(instance, OracleRestartSpex), + } + + if instance.Spec.SecurityContext != nil { + spec.SecurityContext = instance.Spec.SecurityContext + } + + if len(instance.Spec.ImagePullSecret) > 0 { + spec.ImagePullSecrets = []corev1.LocalObjectReference{ + { + Name: instance.Spec.ImagePullSecret, + }, + } + } + return spec +} + +// Function get the Node Affinity +func getNodeAffinity(instance *oraclerestart.OracleRestart, OracleRestartSpex oraclerestart.OracleRestartInstDetailSpec) *corev1.Affinity { + + nodeAffinity := &corev1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{ + NodeSelectorTerms: []corev1.NodeSelectorTerm{}, + }, + } + + racTerm := corev1.NodeSelectorTerm{ + MatchExpressions: []corev1.NodeSelectorRequirement{}, + } + + racMatch := corev1.NodeSelectorRequirement{ + Key: utils.OraNodeKey, + Operator: utils.OraOperatorKey, + Values: OracleRestartSpex.WorkerNode, + } + + racTerm.MatchExpressions = append(racTerm.MatchExpressions, racMatch) + nodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms = append(nodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms, + racTerm) + + affinity := &corev1.Affinity{NodeAffinity: nodeAffinity} + return affinity + +} + +// Function get the Node Affinity +func getAsmNodeAffinity(instance *oraclerestart.OracleRestart, disk *oraclerestart.AsmDiskDetails) *corev1.VolumeNodeAffinity { + + nodeAffinity := &corev1.VolumeNodeAffinity{ + Required: &corev1.NodeSelector{ + NodeSelectorTerms: []corev1.NodeSelectorTerm{}, + }, + } + + racTerm := corev1.NodeSelectorTerm{ + MatchExpressions: []corev1.NodeSelectorRequirement{}, + } + + racMatch := corev1.NodeSelectorRequirement{ + Key: utils.OraNodeKey, + Operator: utils.OraOperatorKey, + Values: instance.Spec.InstDetails.WorkerNode, + } + + racTerm.MatchExpressions = append(racTerm.MatchExpressions, racMatch) + nodeAffinity.Required.NodeSelectorTerms = append(nodeAffinity.Required.NodeSelectorTerms, + racTerm) + + return nodeAffinity + +} + +// Function to build Volume Spec +func buildVolumeSpecForOracleRestart(instance *oraclerestart.OracleRestart, OracleRestartSpex oraclerestart.OracleRestartInstDetailSpec) []corev1.Volume { + var result []corev1.Volume + result = []corev1.Volume{ + { + Name: OracleRestartSpex.Name + "-ssh-secretmap-vol", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: instance.Spec.SshKeySecret.Name, + }, + }, + }, + { + Name: OracleRestartSpex.Name + "-oradshm-vol", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{Medium: corev1.StorageMediumMemory}, + }, + }, + } + + if len(instance.Spec.ScriptsLocation) != 0 { + result = append(result, corev1.Volume{Name: OracleRestartSpex.Name + "-oradata-scripts-vol", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}}) + } + + if instance.Spec.DbSecret != nil { + if instance.Spec.DbSecret.Name != "" { + result = append(result, corev1.Volume{Name: OracleRestartSpex.Name + "-dbsecret-pwd-vol", VolumeSource: corev1.VolumeSource{Secret: &corev1.SecretVolumeSource{SecretName: instance.Spec.DbSecret.Name}}}) + } + + if instance.Spec.DbSecret.KeySecretName != "" { + result = append(result, corev1.Volume{Name: OracleRestartSpex.Name + "-dbsecret-key-vol", VolumeSource: corev1.VolumeSource{Secret: &corev1.SecretVolumeSource{SecretName: instance.Spec.DbSecret.KeySecretName}}}) + } + } + + if instance.Spec.TdeWalletSecret != nil { + if instance.Spec.TdeWalletSecret.Name != "" { + result = append(result, corev1.Volume{Name: OracleRestartSpex.Name + "-tdesecret-pwd-vol", VolumeSource: corev1.VolumeSource{Secret: &corev1.SecretVolumeSource{SecretName: instance.Spec.TdeWalletSecret.Name}}}) + } + + if instance.Spec.TdeWalletSecret.KeySecretName != "" { + result = append(result, corev1.Volume{Name: OracleRestartSpex.Name + "-tdesecret-key-vol", VolumeSource: corev1.VolumeSource{Secret: &corev1.SecretVolumeSource{SecretName: instance.Spec.TdeWalletSecret.KeySecretName}}}) + } + } + + if len(instance.Spec.ConfigParams.GridResponseFile.ConfigMapName) != 0 { + result = append(result, corev1.Volume{Name: OracleRestartSpex.Name + "-oradata-girsp", VolumeSource: corev1.VolumeSource{ConfigMap: &corev1.ConfigMapVolumeSource{LocalObjectReference: corev1.LocalObjectReference{Name: instance.Spec.ConfigParams.GridResponseFile.ConfigMapName}}}}) + } + + if len(instance.Spec.ConfigParams.DbResponseFile.ConfigMapName) != 0 { + result = append(result, corev1.Volume{Name: OracleRestartSpex.Name + "-oradata-dbrsp", VolumeSource: corev1.VolumeSource{ConfigMap: &corev1.ConfigMapVolumeSource{LocalObjectReference: corev1.LocalObjectReference{Name: instance.Spec.ConfigParams.DbResponseFile.ConfigMapName}}}}) + } + + if len(OracleRestartSpex.EnvFile) != 0 { + result = append(result, corev1.Volume{Name: OracleRestartSpex.Name + "-oradata-envfile", VolumeSource: corev1.VolumeSource{ConfigMap: &corev1.ConfigMapVolumeSource{LocalObjectReference: corev1.LocalObjectReference{Name: OracleRestartSpex.EnvFile}}}}) + } + + if len(OracleRestartSpex.HostSwLocation) != 0 { + result = append(result, corev1.Volume{Name: OracleRestartSpex.Name + "-oradata-sw-vol", VolumeSource: corev1.VolumeSource{HostPath: &corev1.HostPathVolumeSource{Path: OracleRestartSpex.HostSwLocation}}}) + } else { + if instance.Spec.SwStorageClass != "" { + result = append(result, corev1.Volume{Name: OracleRestartSpex.Name + "-oradata-sw-vol", VolumeSource: corev1.VolumeSource{PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ClaimName: GetSwPvcName(instance.Name, instance)}}}) + } + } + + // Following block checks for HostSwStageLocation, RUPatchLocation nd OpatchLocation + if instance.Spec.ConfigParams != nil && len(instance.Spec.ConfigParams.SwStagePvc) != 0 { + // FIrst Check + result = append(result, corev1.Volume{ + Name: OracleRestartSpex.Name + "-oradata-swstagepvc-vol", + VolumeSource: corev1.VolumeSource{PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ClaimName: instance.Spec.ConfigParams.SwStagePvc}}, + }) + } else { + if len(instance.Spec.ConfigParams.HostSwStageLocation) != 0 { + result = append(result, corev1.Volume{ + Name: OracleRestartSpex.Name + "-oradata-swstage-vol", + VolumeSource: corev1.VolumeSource{ + HostPath: &corev1.HostPathVolumeSource{ + Path: instance.Spec.ConfigParams.HostSwStageLocation, + }, + }, + }) + } + if instance.Spec.ConfigParams.RuPatchLocation != "" { + result = append(result, corev1.Volume{ + Name: OracleRestartSpex.Name + "-oradata-rupatch-vol", + VolumeSource: corev1.VolumeSource{ + HostPath: &corev1.HostPathVolumeSource{ + Path: instance.Spec.ConfigParams.RuPatchLocation, + }, + }, + }) + } + if instance.Spec.ConfigParams.OPatchLocation != "" { + result = append(result, corev1.Volume{ + Name: OracleRestartSpex.Name + "-oradata-opatch-vol", + VolumeSource: corev1.VolumeSource{ + HostPath: &corev1.HostPathVolumeSource{ + Path: instance.Spec.ConfigParams.OPatchLocation, + }, + }, + }) + } + if instance.Spec.ConfigParams.OneOffLocation != "" { + result = append(result, corev1.Volume{ + Name: OracleRestartSpex.Name + "-oradata-oneoff-vol", + VolumeSource: corev1.VolumeSource{ + HostPath: &corev1.HostPathVolumeSource{ + Path: instance.Spec.ConfigParams.OneOffLocation, + }, + }, + }) + } + } + + if len(OracleRestartSpex.PvcName) != 0 { + for source := range OracleRestartSpex.PvcName { + result = append(result, corev1.Volume{Name: OracleRestartSpex.Name + "-ora-vol-" + source, VolumeSource: corev1.VolumeSource{PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ClaimName: source}}}) + } + } + + if instance.Spec.AsmStorageDetails != nil { + // Iterate over the DisksBySize slice + for _, diskBySize := range instance.Spec.AsmStorageDetails.DisksBySize { + // For each DiskBySize, append PVCs for the disks in DiskNames + for _, diskName := range diskBySize.DiskNames { + // Construct PVC name based on index and instance name + pvcName := GetAsmPvcName(instance.Name, diskName, instance) + result = append(result, corev1.Volume{ + Name: pvcName, + VolumeSource: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: pvcName, + }, + }, + }) + } + } + } + + return result +} + +// Function to build the container Specification +func buildContainerSpecForOracleRestart(instance *oraclerestart.OracleRestart, OracleRestartSpex oraclerestart.OracleRestartInstDetailSpec) []corev1.Container { + // building Continer spec + var result []corev1.Container + privileged := false + failureThreshold := 1 + periodSeconds := 5 + initialDelaySeconds := 120 + oraLsnrPort := 1521 + + // Get the Idx + + containerSpec := corev1.Container{ + Name: OracleRestartSpex.Name, + Image: instance.Spec.Image, + SecurityContext: &corev1.SecurityContext{ + Privileged: &privileged, + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{corev1.Capability("NET_ADMIN"), corev1.Capability("SYS_NICE"), corev1.Capability("SYS_RESOURCE"), corev1.Capability("AUDIT_WRITE"), corev1.Capability("NET_RAW"), corev1.Capability("AUDIT_CONTROL"), corev1.Capability("SYS_CHROOT")}, + }, + }, + Command: []string{ + "/usr/sbin/init", + }, + VolumeDevices: getAsmVolumeDevices(instance, OracleRestartSpex), + Resources: corev1.ResourceRequirements{ + Requests: make(map[corev1.ResourceName]resource.Quantity), + }, + VolumeMounts: buildVolumeMountSpecForOracleRestart(instance, OracleRestartSpex), + ReadinessProbe: &corev1.Probe{ + // TODO: Investigate if it's ok to call status every 10 seconds + FailureThreshold: int32(failureThreshold), + PeriodSeconds: int32(periodSeconds), + InitialDelaySeconds: int32(initialDelaySeconds), + ProbeHandler: corev1.ProbeHandler{TCPSocket: &corev1.TCPSocketAction{Port: intstr.FromInt(int(oraLsnrPort))}}, + }, + } + if instance.Spec.Resources != nil { + containerSpec.Resources = *instance.Spec.Resources + } + + if len(OracleRestartSpex.EnvVars) > 0 { + containerSpec.Env = buildEnvVarsSpec(OracleRestartSpex.EnvVars) + } + + if instance.Spec.ReadinessProbe != nil { + containerSpec.ReadinessProbe = instance.Spec.ReadinessProbe + } + // building Complete Container Spec + containerSpec.Ports = buildContainerPortsDef(instance) + + result = []corev1.Container{ + containerSpec, + } + return result +} + +func getAsmVolumeDevices(instance *oraclerestart.OracleRestart, OracleRestartSpex oraclerestart.OracleRestartInstDetailSpec) []corev1.VolumeDevice { + var result []corev1.VolumeDevice + + if instance.Spec.AsmStorageDetails != nil { + // Iterate over the DisksBySize slice + for _, diskBySize := range instance.Spec.AsmStorageDetails.DisksBySize { + // For each disk in DiskNames, create a VolumeDevice + for _, diskName := range diskBySize.DiskNames { + // Create PVC name and append VolumeDevice to the result + pvcName := GetAsmPvcName(instance.Name, diskName, instance) + result = append(result, corev1.VolumeDevice{Name: pvcName, DevicePath: diskName}) + } + } + } + + return result +} + +// Function to build the init Container Spec +func buildInitContainerSpecForOracleRestart(instance *oraclerestart.OracleRestart, OracleRestartSpex oraclerestart.OracleRestartInstDetailSpec) []corev1.Container { + var result []corev1.Container + // building the init Container Spec + privFlag := true + var uid int64 = 0 + var scriptsCmd string + var scriptsLocation string + + if len(instance.Spec.ScriptsGetCmd) != 0 { + scriptsCmd = instance.Spec.ScriptsGetCmd + } else { + scriptsCmd = "/bin/true" + } + + if len(instance.Spec.ScriptsLocation) != 0 { + scriptsLocation = instance.Spec.ScriptsLocation + } else { + scriptsLocation = "NOLOC" + } + + init1spec := corev1.Container{ + Name: OracleRestartSpex.Name + "-init1", + Image: instance.Spec.Image, + SecurityContext: &corev1.SecurityContext{ + Privileged: &privFlag, + RunAsUser: &uid, + }, + Command: []string{ + "/bin/bash", + "-c", + getRacInitContainerCmd(scriptsCmd, instance.Name, scriptsLocation), + }, + VolumeMounts: buildVolumeMountSpecForOracleRestart(instance, OracleRestartSpex), + } + + // building Complete Init Container Spec + if instance.Spec.ImagePullPolicy != nil { + init1spec.ImagePullPolicy = *instance.Spec.ImagePullPolicy + } + result = []corev1.Container{ + init1spec, + } + return result +} + +func buildVolumeMountSpecForOracleRestart(instance *oraclerestart.OracleRestart, OracleRestartSpex oraclerestart.OracleRestartInstDetailSpec) []corev1.VolumeMount { + var result []corev1.VolumeMount + result = append(result, corev1.VolumeMount{Name: OracleRestartSpex.Name + "-ssh-secretmap-vol", MountPath: instance.Spec.SshKeySecret.KeyMountLocation, ReadOnly: true}) + if instance.Spec.DbSecret != nil { + if instance.Spec.DbSecret.KeySecretName != "" { + result = append(result, corev1.VolumeMount{Name: OracleRestartSpex.Name + "-dbsecret-key-vol", MountPath: instance.Spec.DbSecret.KeyFileMountLocation, ReadOnly: true}) + } + if instance.Spec.DbSecret.Name != "" { + result = append(result, corev1.VolumeMount{Name: OracleRestartSpex.Name + "-dbsecret-pwd-vol", MountPath: instance.Spec.DbSecret.PwdFileMountLocation, ReadOnly: true}) + } + } + + if instance.Spec.TdeWalletSecret != nil { + if instance.Spec.TdeWalletSecret.Name != "" { + result = append(result, corev1.VolumeMount{Name: OracleRestartSpex.Name + "-tdesecret-pwd-vol", MountPath: instance.Spec.TdeWalletSecret.PwdFileMountLocation, ReadOnly: true}) + } + + if instance.Spec.TdeWalletSecret.KeySecretName != "" { + result = append(result, corev1.VolumeMount{Name: OracleRestartSpex.Name + "-tdesecret-key-vol", MountPath: instance.Spec.TdeWalletSecret.KeyFileMountLocation, ReadOnly: true}) + } + } + //result = append(result, corev1.VolumeMount{Name: OracleRestartSpex.Name + "-oradata-boot-vol", MountPath: oraBootVol}) + result = append(result, corev1.VolumeMount{Name: OracleRestartSpex.Name + "-oradshm-vol", MountPath: utils.OraShm}) + + if len(instance.Spec.ConfigParams.GridResponseFile.ConfigMapName) != 0 { + result = append(result, corev1.VolumeMount{Name: OracleRestartSpex.Name + "-oradata-girsp", MountPath: utils.OraGiRsp}) + } + + if len(instance.Spec.ConfigParams.DbResponseFile.ConfigMapName) != 0 { + result = append(result, corev1.VolumeMount{Name: OracleRestartSpex.Name + "-oradata-dbrsp", MountPath: utils.OraDbRsp}) + } + + if len(OracleRestartSpex.EnvFile) != 0 { + result = append(result, corev1.VolumeMount{Name: OracleRestartSpex.Name + "-oradata-envfile", MountPath: utils.OraEnvFile}) + } + + if len(instance.Spec.ScriptsLocation) != 0 { + result = append(result, corev1.VolumeMount{Name: OracleRestartSpex.Name + "-oradata-scripts-vol", MountPath: instance.Spec.ScriptsLocation}) + } + if len(OracleRestartSpex.HostSwLocation) != 0 { + result = append(result, corev1.VolumeMount{Name: OracleRestartSpex.Name + "-oradata-sw-vol", MountPath: instance.Spec.ConfigParams.SwMountLocation}) + } else if len(instance.Spec.SwStorageClass) != 0 { + result = append(result, corev1.VolumeMount{Name: OracleRestartSpex.Name + "-oradata-sw-vol", MountPath: instance.Spec.ConfigParams.SwMountLocation}) + } else { + fmt.Println("No Location is passed for the software storage in" + OracleRestartSpex.Name) + } + + //var mountLoc string + + // Check if ConfigParams is not nil + if instance.Spec.ConfigParams != nil { + // Check if HostSwStageLocation is provided in ConfigParams + if len(instance.Spec.ConfigParams.SwStagePvc) != 0 { + result = append(result, corev1.VolumeMount{Name: OracleRestartSpex.Name + "-oradata-swstagepvc-vol", MountPath: instance.Spec.ConfigParams.SwStagePvcMountLocation}) + } else { + if instance.Spec.ConfigParams.HostSwStageLocation != "" { + result = append(result, corev1.VolumeMount{ + Name: OracleRestartSpex.Name + "-oradata-swstage-vol", + MountPath: instance.Spec.ConfigParams.HostSwStageLocation, + }) + } + + if instance.Spec.ConfigParams.RuPatchLocation != "" { + result = append(result, corev1.VolumeMount{ + Name: OracleRestartSpex.Name + "-oradata-rupatch-vol", + MountPath: instance.Spec.ConfigParams.RuPatchLocation, + }) + } + + if instance.Spec.ConfigParams.OPatchLocation != "" { + result = append(result, corev1.VolumeMount{ + Name: OracleRestartSpex.Name + "-oradata-opatch-vol", + MountPath: instance.Spec.ConfigParams.OPatchLocation, + }) + } + if instance.Spec.ConfigParams.OneOffLocation != "" { + result = append(result, corev1.VolumeMount{ + Name: OracleRestartSpex.Name + "-oradata-oneoff-vol", + MountPath: instance.Spec.ConfigParams.OneOffLocation, + }) + } + } + } + + if len(OracleRestartSpex.PvcName) != 0 { + for source, target := range OracleRestartSpex.PvcName { + result = append(result, corev1.VolumeMount{Name: OracleRestartSpex.Name + "-ora-vol-" + source, MountPath: target}) + } + } + + return result +} + +func VolumePVCForASM(instance *oraclerestart.OracleRestart, index int, diskName string, size int, asmStorage *oraclerestart.AsmDiskDetails, pvcName string, dgType string, k8sClient client.Client) *corev1.PersistentVolumeClaim { + // Set volume mode to block + volumeBlock := corev1.PersistentVolumeBlock + + // Create PersistentVolumeClaim + asmPvc := &corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: pvcName, // Use size to determine index + Namespace: instance.Namespace, + Labels: buildLabelsForOracleRestart(instance, "OracleRestart"), + }, + Spec: corev1.PersistentVolumeClaimSpec{ + AccessModes: []corev1.PersistentVolumeAccessMode{ + corev1.ReadWriteOnce, + }, + VolumeMode: &volumeBlock, + Resources: corev1.VolumeResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceStorage: resource.MustParse(strconv.FormatInt(int64(size), 10) + "Gi")}, + }, + }, + } + var scName *string + switch dgType { + case "RECO": + if len(instance.Spec.RecoDgStorageClass) != 0 { + scName = &instance.Spec.RecoDgStorageClass + } + case "REDO": + if len(instance.Spec.RedoDgStorageClass) != 0 { + scName = &instance.Spec.RedoDgStorageClass + } + case "CRS": + if len(instance.Spec.CrsDgStorageClass) != 0 { + scName = &instance.Spec.CrsDgStorageClass + } + case "DATA": + if len(instance.Spec.DataDgStorageClass) != 0 { + scName = &instance.Spec.DataDgStorageClass + } + } + + if scName == nil { + // Try to fetch the cluster's default StorageClass + if defaultSC, err := GetDefaultStorageClass(context.TODO(), k8sClient); err == nil && defaultSC != "" { + scName = &defaultSC + } else { + // No StorageClass, so use label selector and statically bound PVs + asmPvc.Spec.Selector = &metav1.LabelSelector{ + MatchLabels: buildLabelsForAsmPv(instance, string(diskName)), + } + scName = nil + } + } + asmPvc.Spec.StorageClassName = scName + + return asmPvc +} + +func GetDefaultStorageClass(ctx context.Context, k8sClient client.Client) (string, error) { + var scList storagev1.StorageClassList + if err := k8sClient.List(ctx, &scList); err != nil { + return "", err + } + for _, sc := range scList.Items { + if sc.Annotations["storageclass.kubernetes.io/is-default-class"] == "true" || + sc.Annotations["storageclass.beta.kubernetes.io/is-default-class"] == "true" { + return sc.Name, nil + } + } + return "", nil // No default StorageClass found +} + +func VolumePVForASM(instance *oraclerestart.OracleRestart, diskName string, size int, asmStorage *oraclerestart.AsmDiskDetails, pvName string, k8sClient client.Client) *corev1.PersistentVolume { + volumeBlock := corev1.PersistentVolumeBlock + + asmPvc := &corev1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: pvName, + Namespace: instance.Namespace, + Labels: buildLabelsForAsmPv(instance, diskName), + }, + Spec: corev1.PersistentVolumeSpec{ + AccessModes: []corev1.PersistentVolumeAccessMode{ + corev1.ReadWriteOnce, + }, + VolumeMode: &volumeBlock, + Capacity: corev1.ResourceList{ + corev1.ResourceStorage: resource.MustParse(strconv.FormatInt(int64(size), 10) + "Gi")}, + }, + } + + var scName *string + /* + if len(instance.Spec.StorageClass) != 0 { + scName = &instance.Spec.StorageClass + asmPvc.Spec.NodeAffinity = getAsmNodeAffinity(instance, index, asmStorage) + asmPvc.Spec.PersistentVolumeSource = corev1.PersistentVolumeSource{Local: &corev1.LocalVolumeSource{Path: diskName}} + } else { + */ + // Try to fetch the cluster's default StorageClass + if defaultSC, err := GetDefaultStorageClass(context.TODO(), k8sClient); err == nil && defaultSC != "" { + scName = &defaultSC + } else { + // No StorageClass, so use label selector and statically bound PVs + scName = nil + } + asmPvc.Spec.NodeAffinity = getAsmNodeAffinity(instance, asmStorage) + asmPvc.Spec.PersistentVolumeSource = corev1.PersistentVolumeSource{Local: &corev1.LocalVolumeSource{Path: diskName}} + + if scName != nil { + asmPvc.Spec.StorageClassName = *scName + } + + return asmPvc +} + +func BuildServiceDefForOracleRestart(instance *oraclerestart.OracleRestart, replicaCount int32, OracleRestartSpex oraclerestart.OracleRestartInstDetailSpec, svctype string) *corev1.Service { + //service := &corev1.Service{} + service := &corev1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service"}, + ObjectMeta: buildSvcObjectMetaForOracleRestart(instance, replicaCount, OracleRestartSpex, svctype), + Spec: corev1.ServiceSpec{}, + } + + // // Check if user want External Svc on each replica pod + if strings.ToUpper(svctype) == "VIP" || strings.ToUpper(svctype) == "LOCAL" { + service.Spec.ClusterIP = corev1.ClusterIPNone + service.Spec.Selector = getSvcLabelsForOracleRestart(replicaCount, OracleRestartSpex) + + } + + // if strings.ToUpper(svctype) == "SCAN" { + // service.Spec.ClusterIP = corev1.ClusterIPNone + // service.Spec.Selector = buildLabelsForOracleRestart(instance, "OracleRestart") + // } + + service.Spec.PublishNotReadyAddresses = true + + // build Service Ports Specs to be exposed. If the PortMappings is not set then default ports will be exposed. + + return service +} + +func BuildExternalServiceDefForOracleRestart(instance *oraclerestart.OracleRestart, index int32, OracleRestartSpex oraclerestart.OracleRestartInstDetailSpec, svctype string, opType string) *corev1.Service { + //service := &corev1.Service{} + + var npSvc oraclerestart.OracleRestartNodePortSvc + + service := &corev1.Service{ + ObjectMeta: buildSvcObjectMetaForOracleRestart(instance, index, OracleRestartSpex, opType), + Spec: corev1.ServiceSpec{}, + } + + // If user is setting Node Port Service + if opType == "nodeport" { + npSvc = instance.Spec.NodePortSvc + npSvc.PortMappings = instance.Spec.NodePortSvc.PortMappings + service.Spec.Type = corev1.ServiceTypeNodePort + if len(instance.Spec.NodePortSvc.SvcAnnotation) != 0 { + service.Annotations = instance.Spec.NodePortSvc.SvcAnnotation + } + } else if opType == "lbservice" { + npSvc = instance.Spec.LbService + npSvc.PortMappings = instance.Spec.LbService.PortMappings + service.Spec.Type = corev1.ServiceTypeLoadBalancer + if len(instance.Spec.LbService.SvcAnnotation) != 0 { + service.Annotations = instance.Spec.LbService.SvcAnnotation + } + } + + //service.Name = npSvc.SvcName + + service.Spec.Selector = getSvcLabelsForOracleRestart(0, OracleRestartSpex) + service.Spec.Ports = buildOracleRestartSvcPortsDef(npSvc) + + // build Service Ports Specs to be exposed. If the PortMappings is not set then default ports will be exposed. + + return service +} + +// Function to build Service ObjectMeta +func buildSvcObjectMetaForOracleRestart(instance *oraclerestart.OracleRestart, replicaCount int32, OracleRestartSpex oraclerestart.OracleRestartInstDetailSpec, svctype string) metav1.ObjectMeta { + // building objectMeta + //var svcName string + + var labelStr map[string]string + labelStr = getSvcLabelsForOracleRestart(replicaCount, OracleRestartSpex) + + objmeta := metav1.ObjectMeta{ + Name: getOracleRestartSvcName(instance, OracleRestartSpex, svctype), + Namespace: instance.Namespace, + Labels: labelStr, + } + + return objmeta +} + +func getOracleRestartSvcName(instance *oraclerestart.OracleRestart, OracleRestartSpex oraclerestart.OracleRestartInstDetailSpec, svcType string) string { + + switch svcType { + case "local": + return OracleRestartSpex.Name + "-0-local" + case "lbservice": + if instance.Spec.LbService.SvcName != "" { + return instance.Spec.LbService.SvcName + "-0-lbsvc" + } else { + return OracleRestartSpex.Name + "-0-lbsvc" + } + case "nodeport": + if instance.Spec.NodePortSvc.SvcName != "" { + return instance.Spec.NodePortSvc.SvcName + "-0-npsvc" + } else { + return OracleRestartSpex.Name + "-0-npsvc" + } + default: + return OracleRestartSpex.Name + } +} + +func getSvcLabelsForOracleRestart(replicaCount int32, OracleRestartSpex oraclerestart.OracleRestartInstDetailSpec) map[string]string { + + var labelStr map[string]string = make(map[string]string) + if replicaCount == -1 { + labelStr["statefulset.kubernetes.io/pod-name"] = OracleRestartSpex.Name + "-0" + } else { + labelStr["statefulset.kubernetes.io/pod-name"] = OracleRestartSpex.Name + "-0" + } + + // fmt.Println("Service Selector String Specification", labelStr) + return labelStr +} + +// This function cleanup the shard from GSM +func OraCleanupForOracleRestart(instance *oraclerestart.OracleRestart, + OracleRestartSpex oraclerestart.OracleRestartInstDetailSpec, + oldReplicaSize int32, + newReplicaSize int32, +) string { + var err1 string + if oldReplicaSize > newReplicaSize { + for replicaCount := (oldReplicaSize - 1); replicaCount > (newReplicaSize - 1); replicaCount-- { + fmt.Println("Deleting the RAC " + OracleRestartSpex.Name + "-" + strconv.FormatInt(int64(replicaCount), 10)) + } + } + + err1 = "Test" + return err1 +} + +func UpdateProvForOracleRestart(instance *oraclerestart.OracleRestart, + OracleRestartSpex oraclerestart.OracleRestartInstDetailSpec, kClient client.Client, sfSet *appsv1.StatefulSet, gsmPod *corev1.Pod, logger logr.Logger, +) (ctrl.Result, error) { + + var msg string + var size int32 = 1 + var isUpdate bool = false + var err error + //var i int + + msg = "Inside the updateProvForOracleRestart" + LogMessages("DEBUG", msg, nil, instance, logger) + + // Ensure deployment replicas match the desired state + + if sfSet.Spec.Replicas != nil { + if *sfSet.Spec.Replicas != size { + msg = "Current StatefulSet replicas do not match configured Shard Replicas. Gsm is configured with only 1 but current replicas is set with " + strconv.FormatInt(int64(*sfSet.Spec.Replicas), 10) + LogMessages("DEBUG", msg, nil, instance, logger) + isUpdate = true + } + } + + if isUpdate { + err = kClient.Update(context.Background(), BuildStatefulSetForOracleRestart(instance, OracleRestartSpex, kClient)) + if err != nil { + msg = "Failed to update Shard StatefulSet " + "StatefulSet.Name : " + sfSet.Name + LogMessages("Error", msg, err, instance, logger) + return ctrl.Result{}, err + } + + } + + return ctrl.Result{}, nil +} + +func ConfigMapSpecs(instance *oraclerestart.OracleRestart, cmData map[string]string, cmName string) *corev1.ConfigMap { + //cm := &corev1.ConfigMap{} + + return &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + Kind: "ConfigMap", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: cmName, + Namespace: instance.Namespace, + Labels: map[string]string{ + "name": instance.Name + cmName, + }, + }, + Data: cmData, + } + +} + +func BuildDiskCheckDaemonSet(OracleRestart *oraclerestart.OracleRestart) *appsv1.DaemonSet { + labels := buildLabelsForOracleRestart(OracleRestart, "disk-check") + + // Prepare the volume devices based on the PVCs + var volumeDevices []corev1.VolumeDevice + var volumes []corev1.Volume + disks := flattenDisksBySize(&OracleRestart.Spec) + + for _, diskPath := range disks { + pvcName := GetAsmPvcName(OracleRestart.Name, diskPath, OracleRestart) + volumeName := pvcName + + volumeDevices = append(volumeDevices, corev1.VolumeDevice{ + Name: volumeName, + DevicePath: diskPath, + }) + + volumes = append(volumes, corev1.Volume{ + Name: volumeName, + VolumeSource: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: pvcName, + }, + }, + }) + + } + + // Flatten the DisksBySize map to get a single slice of all disk names + diskNamesSlice := flattenDisksBySize(&OracleRestart.Spec) + + // Join the flattened list of disk names into a single space-separated string + diskNames := strings.Join(diskNamesSlice, " ") + + return &appsv1.DaemonSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "disk-check-daemonset", + Namespace: OracleRestart.Namespace, + Labels: labels, + }, + Spec: appsv1.DaemonSetSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: labels, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: labels, + }, + Spec: corev1.PodSpec{ + Affinity: &corev1.Affinity{ + NodeAffinity: &corev1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{ + NodeSelectorTerms: []corev1.NodeSelectorTerm{ + { + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "kubernetes.io/hostname", + Operator: corev1.NodeSelectorOpIn, + Values: OracleRestart.Spec.InstDetails.WorkerNode, + }, + }, + }, + }, + }, + }, + }, + Containers: []corev1.Container{ + { + Name: "disk-check", + Image: OracleRestart.Spec.Image, + Command: []string{"/bin/bash", "-c"}, + Args: []string{ + "for disk in " + diskNames + "; do " + + "if [ ! -e $disk ]; then " + + "echo Disk $disk is not a valid block device; " + + "exit 1; " + + "else " + + "echo Disk $disk is valid; " + + "fi; " + + "done; " + + "sleep 3600", + }, + + VolumeDevices: volumeDevices, + }, + }, + Volumes: volumes, + }, + }, + }, + } +} + +// Helper function to flatten DisksBySize into a single slice of disk names +func flattenDisksBySize(oraclerestartSpec *oraclerestart.OracleRestartSpec) []string { + disksBySize := oraclerestartSpec.AsmStorageDetails.DisksBySize + var allDisks []string + for _, diskBySize := range disksBySize { + allDisks = append(allDisks, diskBySize.DiskNames...) + } + return allDisks +} + +func CreateServiceAccountIfNotExists(instance *oraclerestart.OracleRestart, kClient client.Client) error { + if instance.Spec.SrvAccountName == "" { + return nil + } + + ServiceAccountName := instance.Spec.SrvAccountName + if ServiceAccountName == "" { + ServiceAccountName = "default" + return nil + } + sa := &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: ServiceAccountName, + Namespace: instance.Namespace, + }, + } + + existingSA := &corev1.ServiceAccount{} + err := kClient.Get(context.TODO(), types.NamespacedName{Name: sa.Name, Namespace: sa.Namespace}, existingSA) + if err != nil { + if apierrors.IsNotFound(err) { + err = kClient.Create(context.TODO(), sa) + if err != nil { + return err + } + } else { + return err + } + } + return nil +} + +func IsStaticProvisioning(k8sClient client.Client, instance *oraclerestart.OracleRestart) bool { + if CheckStorageClass(instance) == "NOSC" { + return false + } + + var scList storagev1.StorageClassList + if err := k8sClient.List(context.TODO(), &scList); err != nil { + return true // fallback to static if we can't query SCs + } + + // for _, sc := range scList.Items { + // if sc.Annotations["storageclass.kubernetes.io/is-default-class"] == "true" || + // sc.Annotations["storageclass.beta.kubernetes.io/is-default-class"] == "true" { + // return false // dynamic provisioning is available + // } + // } + + return true // no default SC → use static +} + +func SwVolumeClaimTemplatesForOracleRestart(instance *oraclerestart.OracleRestart, OracleRestartSpex oraclerestart.OracleRestartInstDetailSpec) corev1.PersistentVolumeClaim { + + // If user-provided PVC name exists, skip volume claim template creation + //pvcName := GetSwPvcName(OracleRestartSpex.Name) + // If you are making any change, please refer GetSwPvcName function as we add instance.Spec.InstDetails.Name + "-0" + pvcName := "odb-sw-pvc-" + instance.Name + return corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: pvcName, + Namespace: instance.Namespace, + Labels: buildLabelsForOracleRestart(instance, "OracleRestart"), + }, + Spec: corev1.PersistentVolumeClaimSpec{ + AccessModes: []corev1.PersistentVolumeAccessMode{ + corev1.ReadWriteOnce, + }, + StorageClassName: &instance.Spec.SwStorageClass, + Resources: corev1.VolumeResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceStorage: resource.MustParse(fmt.Sprintf("%dGi", OracleRestartSpex.SwLocStorageSizeInGb)), + }, + }, + }, + } +} + +func ASMVolumeClaimTemplatesForDG(instance *oraclerestart.OracleRestart, OracleRestartSpex oraclerestart.OracleRestartInstDetailSpec, StorageClass *string) []corev1.PersistentVolumeClaim { + var claims []corev1.PersistentVolumeClaim + mode := corev1.PersistentVolumeBlock + // If user-provided PVC name exists, skip volume claim template creation + if len(OracleRestartSpex.PvcName) != 0 { + return claims + } + + fmt.Printf("INFO", "working on asm storage class "+*StorageClass) + + for _, diskBySize := range instance.Spec.AsmStorageDetails.DisksBySize { + for _, diskName := range diskBySize.DiskNames { + // The folowing peice of code is generating ASM PVC name because by default VolumeCLaim Template add Instance name like -dbmc1-0 + dgType := CheckDiskInAsmDeviceList(instance, diskName) + disk := diskName[strings.LastIndex(diskName, "/")+1:] + pvcName := "asm-pvc-" + strings.ToLower(dgType) + "-" + disk + "-" + instance.Name + + claims = append(claims, corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: pvcName, + Namespace: instance.Namespace, + Labels: buildLabelsForOracleRestart(instance, "OracleRestart"), + }, + Spec: corev1.PersistentVolumeClaimSpec{ + AccessModes: []corev1.PersistentVolumeAccessMode{ + corev1.ReadWriteOnce, + }, + VolumeMode: &mode, + StorageClassName: StorageClass, + Resources: corev1.VolumeResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceStorage: resource.MustParse(fmt.Sprintf("%dGi", diskBySize.StorageSizeInGb)), + }, + }, + }, + }) + } + } + + return claims +} diff --git a/commons/oraclerestart/oraclerestartstatus.go b/commons/oraclerestart/oraclerestartstatus.go new file mode 100644 index 00000000..a9dbf94f --- /dev/null +++ b/commons/oraclerestart/oraclerestartstatus.go @@ -0,0 +1,340 @@ +/* +** Copyright (c) 2022 Oracle and/or its affiliates. +** +** The Universal Permissive License (UPL), Version 1.0 +** +** Subject to the condition set forth below, permission is hereby granted to any +** person obtaining a copy of this software, associated documentation and/or data +** (collectively the "Software"), free of charge and under any and all copyright +** rights in the Software, and any and all patent rights owned or freely +** licensable by each licensor hereunder covering either (i) the unmodified +** Software as contributed to or provided by such licensor, or (ii) the Larger +** Works (as defined below), to deal in both +** +** (a) the Software, and +** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +** one is included with the Software (each a "Larger Work" to which the Software +** is contributed by such licensors), +** +** without restriction, including without limitation the rights to copy, create +** derivative works of, display, perform, and distribute the Software and make, +** use, sell, offer for sale, import, export, have made, and have sold the +** Software and the Larger Work(s), and to sublicense the foregoing rights on +** either these or other terms. +** +** This license is subject to the following condition: +** The above copyright notice and either this complete permission notice or at +** a minimum a reference to the UPL must be included in all copies or +** substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +** SOFTWARE. + */ + +package commons + +import ( + "context" + "strings" + + "github.com/go-logr/logr" + oraclerestartdb "github.com/oracle/oracle-database-operator/apis/database/v4" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func UpdateOracleRestartInstStatusData( + OracleRestart *oraclerestartdb.OracleRestart, + ctx context.Context, + req ctrl.Request, + oraRestartSpex oraclerestartdb.OracleRestartInstDetailSpec, + state string, + kubeClient kubernetes.Interface, + kubeConfig clientcmd.ClientConfig, + logger logr.Logger, + kClient client.Client, +) { + // podName is constructed based on oraRestartSpex.Name + podName := oraRestartSpex.Name + "-0" + + oracleRestart := &oraclerestartdb.OracleRestartNodestatus{} + orestartNodeDetails := &oraclerestartdb.OracleRestartNodeDetailedStatus{} + strMap := make(map[string]string) + + if OracleRestart.Status.AsmDetails == nil { + OracleRestart.Status.AsmDetails = &oraclerestartdb.AsmInstanceStatus{} + } + + if OracleRestart.Status.ConfigParams == nil { + OracleRestart.Status.ConfigParams = &oraclerestartdb.InitParams{} + } + + if state == string(oraclerestartdb.OracleRestartUpdateState) { + OracleRestart.Status.State = state + } + if state == string(oraclerestartdb.OracleRestartProvisionState) { + OracleRestart.Status.State = state + } + if state == string(oraclerestartdb.OracleRestartFailedState) { + clusterState := getClusterState(podName, OracleRestart, 0, kubeClient, kubeConfig, logger) + instanceState := getDbInstState(podName, OracleRestart, 0, kubeClient, kubeConfig, logger) + if clusterState == "HEALTHY" && instanceState == "OPEN" { // cluster is healthy and database is also fine + OracleRestart.Status.State = string(oraclerestartdb.OracleRestartAvailableState) + state = string(oraclerestartdb.OracleRestartAvailableState) + } else { + OracleRestart.Status.State = state + } + } + if state == string(oraclerestartdb.OracleRestartManualState) { + OracleRestart.Status.State = state + } + if state == string(oraclerestartdb.OracleRestartAvailableState) { + OracleRestart.Status.State = state + orestartNodeDetails.ClusterState = getClusterState(podName, OracleRestart, 0, kubeClient, kubeConfig, logger) + orestartNodeDetails.PodState = state + orestartNodeDetails.State = "OPEN" + oracleRestart.Name = podName + + orestartNodeDetails.InstanceState = getDbInstState(podName, OracleRestart, 0, kubeClient, kubeConfig, logger) + orestartNodeDetails.MountedDevices = getMountedDevices(podName, OracleRestart.Namespace, oracleRestart, oraRestartSpex, kClient, kubeConfig, logger, kubeClient) + oracleRestart.NodeDetails = orestartNodeDetails + addOracleRestartNodestatus(OracleRestart, ctx, req, oracleRestart, oraRestartSpex, 0, kubeClient, kubeConfig, logger) + + if len(oraRestartSpex.PvcName) > 0 { + orestartNodeDetails.PvcName = getPvcDetails(OracleRestart, oracleRestart, oraRestartSpex, kClient) + } + } + + // Update status based on the state + if state == string(oraclerestartdb.OracleRestartPodAvailableState) { + OracleRestart.Status.State = state + orestartNodeDetails.ClusterState = getClusterState(podName, OracleRestart, 0, kubeClient, kubeConfig, logger) + orestartNodeDetails.PodState = state + orestartNodeDetails.State = "OPEN" + oracleRestart.Name = podName + orestartNodeDetails.InstanceState = getDbInstState(podName, OracleRestart, 0, kubeClient, kubeConfig, logger) + orestartNodeDetails.MountedDevices = getMountedDevices(podName, OracleRestart.Namespace, oracleRestart, oraRestartSpex, kClient, kubeConfig, logger, kubeClient) + oracleRestart.NodeDetails = orestartNodeDetails + OracleRestart.Status.ReleaseUpdate = "NOTAVAILABLE" + addOracleRestartNodestatus(OracleRestart, ctx, req, oracleRestart, oraRestartSpex, 0, kubeClient, kubeConfig, logger) + + if len(oraRestartSpex.PvcName) > 0 { + orestartNodeDetails.PvcName = getPvcDetails(OracleRestart, oracleRestart, oraRestartSpex, kClient) + } + if OracleRestart.Spec.ConfigParams.GridHome != "" { + OracleRestart.Status.ConfigParams.GridHome = OracleRestart.Spec.ConfigParams.GridHome + } + if OracleRestart.Spec.ConfigParams.DbHome != "" { + OracleRestart.Status.ConfigParams.DbHome = OracleRestart.Spec.ConfigParams.DbHome + } + // OracleRestart.Status.ConfigParams.CrsAsmDeviceList = OracleRestart.Spec.ConfigParams.CrsAsmDeviceList + OracleRestart.Status.ConfigParams.CrsAsmDeviceList = getcrsAsmDeviceList(OracleRestart, oracleRestart, oraRestartSpex, kClient, kubeConfig, logger, kubeClient) + + // OracleRestart.Status.ConfigParams.DbAsmDeviceList = OracleRestart.Spec.ConfigParams.DbAsmDeviceList + OracleRestart.Status.ConfigParams.DbAsmDeviceList = getdbAsmDeviceList(OracleRestart, oracleRestart, oraRestartSpex, kClient, kubeConfig, logger, kubeClient) + + } else if state == string(oraclerestartdb.OracleRestartStatefulSetNotFound) { + neworacleRestart := delOracleRestartNodestatus(OracleRestart, oraRestartSpex.Name+"-0") + OracleRestart.Status.OracleRestartNodes = neworacleRestart + OracleRestart.Status.ReleaseUpdate = "NOTAVAILABLE" + + } else if state == string(oraclerestartdb.PodNotFound) || state == string(oraclerestartdb.PodNotReadyState) || state == string(oraclerestartdb.PodFailureState) { + orestartNodeDetails.ClusterState = "NOTAVAILABLE" + orestartNodeDetails.PodState = state + // orestartNodeDetails.VipDetails = getVipDetails(OracleRestart, oracleRestart, oraRestartSpex, kClient) + orestartNodeDetails.InstanceState = "NOTAVAILABLE" + orestartNodeDetails.PvcName = strMap + OracleRestart.Status.ReleaseUpdate = "NOTAVAILABLE" + } +} + +func addOracleRestartNodestatus(instance *oraclerestartdb.OracleRestart, ctx context.Context, req ctrl.Request, oracleRestart *oraclerestartdb.OracleRestartNodestatus, oraRestartSpex oraclerestartdb.OracleRestartInstDetailSpec, idx int, kubeClient kubernetes.Interface, kubeConfig clientcmd.ClientConfig, logger logr.Logger) { + + var racState string + podName := oraRestartSpex.Name + "-0" + idx, status := contains(instance, oracleRestart) + if status { + // racstate need to be read before overwriting the instance.Status.OracleRestartNodes[idx] = oracleRestart + racState = instance.Status.OracleRestartNodes[idx].NodeDetails.State + instance.Status.OracleRestartNodes[idx] = oracleRestart + instState := instance.Status.OracleRestartNodes[idx].NodeDetails.InstanceState + if instState == "OPEN" { + instance.Status.OracleRestartNodes[idx].NodeDetails.State = "AVAILABLE" + } else { + if (racState == "PENDING") || (racState == "ADDNODE") || (racState == "PROVISIONING") || (racState == "FAILED") || (racState == "UPDATE") { + instance.Status.OracleRestartNodes[idx].NodeDetails.State = getOracleRestartInstStateFile(podName, instance, 0, kubeClient, kubeConfig, logger) + } else { + instance.Status.OracleRestartNodes[idx].NodeDetails.State = "PENDING" + } + + } + + } else { + instance.Status.OracleRestartNodes = append(instance.Status.OracleRestartNodes, oracleRestart) + instance.Status.OracleRestartNodes[idx].NodeDetails.State = "PENDING" + } + +} + +func UpdateOracleRestartInstState(instance *oraclerestartdb.OracleRestart, podName string, kubeClient kubernetes.Interface, kubeConfig clientcmd.ClientConfig, logger logr.Logger) { + + // if len(instance.Status.OracleRestartNodes) > 0 { + // for _, v := range instance.Status.OracleRestartNodes { + // if v.NodeDetails. + // } + //} + +} + +func UpdateoraclerestartdbTopologyState(instance *oraclerestartdb.OracleRestart, ctx context.Context, req ctrl.Request, podName string, kubeClient kubernetes.Interface, kubeConfig clientcmd.ClientConfig, logger logr.Logger) { + OracleRestart := &oraclerestartdb.OracleRestart{} + // fmt.Printf("I m inUpdateoraclerestartdbTopologyState") + if len(instance.Status.OracleRestartNodes) > 0 { + state := map[string]struct{}{} + for _, v := range instance.Status.OracleRestartNodes { + inst_state := strings.ToLower(strings.TrimSpace(v.NodeDetails.State)) + // fmt.Printf("inst_state", inst_state) + state[inst_state] = struct{}{} + } + if len(state) == 0 { + OracleRestart.Status.State = string(oraclerestartdb.OracleRestartPendingState) + } else if _, curr_state := state["failed"]; curr_state { + OracleRestart.Status.State = string(oraclerestartdb.OracleRestartFailedState) + } else if _, curr_state := state["pending"]; curr_state { + OracleRestart.Status.State = string(oraclerestartdb.OracleRestartPendingState) + } else if _, curr_state := state["provisioning"]; curr_state { + OracleRestart.Status.State = string(oraclerestartdb.OracleRestartProvisionState) + } else if _, curr_state := state["update"]; curr_state { + OracleRestart.Status.State = string(oraclerestartdb.OracleRestartUpdateState) + } else if _, curr_state := state["addnode"]; curr_state { + OracleRestart.Status.State = string(oraclerestartdb.OracleRestartAddInstState) + } else if _, curr_state := state["podavailable"]; curr_state { + OracleRestart.Status.State = string(oraclerestartdb.OracleRestartPodAvailableState) + } else { + OracleRestart.Status.State = string(oraclerestartdb.OracleRestartAvailableState) + } + instance.Status.State = OracleRestart.Status.State + // fmt.Printf("instance.Status.State", instance.Status.State) + } + +} + +func UpdateoraclerestartdbStatusData(OracleRestart *oraclerestartdb.OracleRestart, ctx context.Context, req ctrl.Request, podNames []string, kubeClient kubernetes.Interface, kubeConfig clientcmd.ClientConfig, logger logr.Logger, nodeDetails map[string]*corev1.Node, +) { + //mode := GetDbOpenMode(instance.Spec.Shard[0].Name+"-0", instance, kubeClient, kubeConfig, logger) + podName := podNames[len(podNames)-1] + OracleRestart.Status.DbState = getDbState(podName, OracleRestart, 0, kubeClient, kubeConfig, logger) + OracleRestart.Status.Role = getDbRole(podName, OracleRestart, 0, kubeClient, kubeConfig, logger) + OracleRestart.Status.ReleaseUpdate = getDBVersion(podName, OracleRestart, 0, kubeClient, kubeConfig, logger) + OracleRestart.Status.ConnectString = getConnStr(podName, OracleRestart, 0, kubeClient, kubeConfig, logger) + OracleRestart.Status.PdbConnectString = getPdbConnStr(podName, OracleRestart, 0, kubeClient, kubeConfig, logger) + OracleRestart.Status.ExternalConnectString = getExternalConnStr(podName, OracleRestart, 0, kubeClient, kubeConfig, logger) + OracleRestart.Status.DbSecret = OracleRestart.Spec.DbSecret + OracleRestart.Status.AsmDetails = getAsmInstState(podName, OracleRestart, 0, kubeClient, kubeConfig, logger) + + UpdateoraclerestartdbServiceStatus(OracleRestart, ctx, req, podName, kubeClient, kubeConfig, logger) + UpdateoraclerestartdbTopologyState(OracleRestart, ctx, req, podName, kubeClient, kubeConfig, logger) +} + +func UpdateoraclerestartdbServiceStatus(instance *oraclerestartdb.OracleRestart, ctx context.Context, req ctrl.Request, podName string, kubeClient kubernetes.Interface, kubeConfig clientcmd.ClientConfig, logger logr.Logger, +) { + //This function update the instance.Status.ServiceDetails states + if instance.Spec.ServiceDetails.Name != "" { + instance.Status.ServiceDetails.Name = instance.Spec.ServiceDetails.Name + instance.Status.ServiceDetails.SvcState = getSvcState(podName, instance, 0, kubeClient, kubeConfig, logger) + } +} + +func contains(instance *oraclerestartdb.OracleRestart, oracleRestart *oraclerestartdb.OracleRestartNodestatus) (int, bool) { + var index int + if len(instance.Status.OracleRestartNodes) > 0 { + for index, v := range instance.Status.OracleRestartNodes { + if v.Name == oracleRestart.Name { + return index, true + } + } + } + + return index, false +} + +func getcrsAsmDeviceList(instance *oraclerestartdb.OracleRestart, oracleRestart *oraclerestartdb.OracleRestartNodestatus, oraRestartSpex oraclerestartdb.OracleRestartInstDetailSpec, rclient client.Client, kubeConfig clientcmd.ClientConfig, logger logr.Logger, kubeClient kubernetes.Interface) string { + asmList := "" + var err error + if len(instance.Status.OracleRestartNodes) > 0 { + asmList, err = CheckAsmList(instance.Status.OracleRestartNodes[0].Name, instance, kubeClient, kubeConfig, logger) + if err != nil { + return "" + } + + } + + return asmList + +} +func getdbAsmDeviceList(instance *oraclerestartdb.OracleRestart, oracleRestart *oraclerestartdb.OracleRestartNodestatus, oraRestartSpex oraclerestartdb.OracleRestartInstDetailSpec, rclient client.Client, kubeConfig clientcmd.ClientConfig, logger logr.Logger, kubeClient kubernetes.Interface) string { + dbasmList := "" + var err error + if len(instance.Status.OracleRestartNodes) > 0 { + dbasmList, err = CheckDbAsmList(instance.Status.OracleRestartNodes[0].Name, instance, kubeClient, kubeConfig, logger) + if err != nil { + return "" + } + + } + + return dbasmList + +} + +func getMountedDevices(podName, namespace string, oracleRestart *oraclerestartdb.OracleRestartNodestatus, oraRestartSpex oraclerestartdb.OracleRestartInstDetailSpec, rclient client.Client, kubeConfig clientcmd.ClientConfig, logger logr.Logger, kubeClient kubernetes.Interface) []string { + var asmList []string + + // Get the pod associated with the RAC node + pod, err := kubeClient.CoreV1().Pods(namespace).Get(context.TODO(), podName, metav1.GetOptions{}) + if err != nil { + logger.Error(err, "Failed to get pod for RAC node", "PodName", podName, "Namespace", namespace) + return nil + } + + // Loop through the volume devices or mounted volumes in the pod spec + for _, container := range pod.Spec.Containers { + for _, volumeDevice := range container.VolumeDevices { + // Append the device path to the asmList + asmList = append(asmList, volumeDevice.DevicePath) + } + } + + return asmList +} + +func delOracleRestartNodestatus(instance *oraclerestartdb.OracleRestart, name string) []*oraclerestartdb.OracleRestartNodestatus { + neworacleRestart := []*oraclerestartdb.OracleRestartNodestatus{} + if len(instance.Status.OracleRestartNodes) > 0 { + for _, value := range instance.Status.OracleRestartNodes { + if ((value.Name) != (name)) && (value != nil) { + neworacleRestart = append(neworacleRestart, value) + } + } + } + return neworacleRestart +} + +func getPvcDetails(instance *oraclerestartdb.OracleRestart, oracleRestart *oraclerestartdb.OracleRestartNodestatus, oraRestartSpex oraclerestartdb.OracleRestartInstDetailSpec, rclient client.Client) map[string]string { + strMap := make(map[string]string) + if len(oraRestartSpex.PvcName) > 0 { + strMap = oraRestartSpex.PvcName + } + + return strMap + +} diff --git a/commons/oraclerestart/utils/utils.go b/commons/oraclerestart/utils/utils.go new file mode 100644 index 00000000..850f5110 --- /dev/null +++ b/commons/oraclerestart/utils/utils.go @@ -0,0 +1,189 @@ +/* +** Copyright (c) 2022 Oracle and/or its affiliates. +** +** The Universal Permissive License (UPL), Version 1.0 +** +** Subject to the condition set forth below, permission is hereby granted to any +** person obtaining a copy of this software, associated documentation and/or data +** (collectively the "Software"), free of charge and under any and all copyright +** rights in the Software, and any and all patent rights owned or freely +** licensable by each licensor hereunder covering either (i) the unmodified +** Software as contributed to or provided by such licensor, or (ii) the Larger +** Works (as defined below), to deal in both +** +** (a) the Software, and +** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +** one is included with the Software (each a "Larger Work" to which the Software +** is contributed by such licensors), +** +** without restriction, including without limitation the rights to copy, create +** derivative works of, display, perform, and distribute the Software and make, +** use, sell, offer for sale, import, export, have made, and have sold the +** Software and the Larger Work(s), and to sublicense the foregoing rights on +** either these or other terms. +** +** This license is subject to the following condition: +** The above copyright notice and either this complete permission notice or at +** a minimum a reference to the UPL must be included in all copies or +** substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +** SOFTWARE. + */ + +package commons + +import ( + "os" + "strconv" + "strings" + + corev1 "k8s.io/api/core/v1" +) + +// Constants for RAC StatefulSet & Volumes +const ( + OraImagePullPolicy = corev1.PullAlways + OrainitCmd1 = "set -ex;" + "touch /tmp/test_cmd1.txt" + OrainitCmd5 = "set -ex;" + "[[ `hostname` =~ -([0-9]+)$ ]] || exit 1 ;" + "ordinal=${BASH_REMATCH[1]};" + "cp /mnt/config-map/envfile /mnt/conf.d/; cat /mnt/conf.d/envfile | awk -v env_var=$ordinal -F '=' '{print \"export \" $1\"=\"$2 env_var }' > /tmp/test.env; mv /tmp/test.env /mnt/conf.d/envfile" + OraConfigMapMount = "/mnt/config-map" + OraEnvFileMount = "/mnt/conf.d" + OraSubDomain = "racnode" + OraEnvFile = "/etc/rac_env_vars" + OraRacSshSecretMount = "/mnt/.ssh" + OraGiRsp = "/mnt/gridrsp" + OraDbRsp = "/mnt/dbrsp" + OraEnvVars = "/etc/rac_env_vars" + OraNodeKey = "kubernetes.io/hostname" + OraOperatorKey = "In" + OraShm = "/dev/shm" + OraRacDbPwdFileSecretMount = "/mnt/.dbsecrets" + OraRacDbKeyFileSecretMount = "/mnt/.dbsecrets" + OraRacTdePwdFileSecretMount = "/mnt/.tdesecrets" + OraRacTdeKeyFileSecretMount = "/mnt/.tdesecrets" + OraStage = "/mnt/stage" + OraBootVol = "/boot" + OraSwLocation = "/u01" + OraScriptMount = "/opt/scripts/startup/scripts" + OraSSHPrivKey = "/mnt/.ssh/ssh-privkey" + OraSSHPubKey = "/mnt/.ssh/ssh-pubkey" + OraSwStageLocation = "/mnt/stage/software" + OraRuPatchStageLocation = "/mnt/stage/rupatch" + OraOPatchStageLocation = "/mnt/stage/opatch" + OraDBPort = 1521 + OraLsnrPort = 1522 + OraLocalOnsPort = 6200 + OraSSHPort = 22 + OraOemPort = 8080 + OraDBUser = "oracle" + OraGridUser = "grid" +) + +// Fixed Array Values + +var serviceCardinality = [...]string{"UNIFORM", "SINGLETON", "DUPLEX"} +var tafPolicy = [...]string{"NONE", "BASIC", "PRECONNECT"} +var serviceRole = [...]string{"PRIMARY", "PHYSICAL_STANDBY", "LOGICAL_STANDBY", "SNAPSHOT_STANDBY"} +var servicePolicy = [...]string{"AUTOMATIC", "MANUAL"} +var serviceResetState = [...]string{"NONE", "LEVEL1"} +var ServiceFailoverType = [...]string{"NONE", "SESSION", "SELECT", "TRANSACTION", "AUTO"} + +/// ====== Getter Function Begins here ======= /// + +func GetServiceCardinality() []string { + return serviceCardinality[:] +} + +func GetTafPolicy() []string { + return tafPolicy[:] +} + +func ServiceRole() []string { + return serviceRole[:] +} + +func GetServiceRole() []string { + return serviceRole[:] +} + +func GetServiceResetState() []string { + return serviceResetState[:] +} + +func GetServiceFailoverType() []string { + return ServiceFailoverType[:] +} + +/// ====== Getter Function Ends here ======= /// + +func CheckStringInList(str1 string, arr []string) bool { + + // iterate using the for loop + for i := 0; i < len(arr); i++ { + // check + if strings.ToLower(arr[i]) == strings.ToLower(str1) { + // return true + return true + } + } + return false +} + +func CheckStatusFlag(flagStr string) bool { + + if strings.ToLower(flagStr) == "delete" { + return true + } + + isTrueFlag, err := strconv.ParseBool(flagStr) + if err != nil { + return false + } + return isTrueFlag +} + +func GetWatchNamespaces() map[string]bool { + // Fetching the allowed namespaces from env variables + var watchNamespaceEnvVar = "WATCH_NAMESPACE" + ns, _ := os.LookupEnv(watchNamespaceEnvVar) + values := strings.Split(strings.TrimSpace(ns), ",") + namespaces := make(map[string]bool) + // put slice values into map + for _, s := range values { + namespaces[s] = true + } + return namespaces +} + +func GetValue(variable string, subkey string) string { + + str2 := "" + + str1 := strings.Split(variable, ",") + for _, item := range str1 { + str2 := strings.Split(item, "=") + if strings.ToLower(str2[0]) == strings.ToLower(subkey) { + return str2[1] + } + } + return str2 +} + +func GetDBUser() string { + return OraDBUser +} + +// Contains checks if a string is present in a slice. +func Contains(slice []string, str string) bool { + for _, item := range slice { + if item == str { + return true + } + } + return false +} diff --git a/commons/sharding/catalog.go b/commons/sharding/catalog.go index 646c89b8..db7ae50f 100644 --- a/commons/sharding/catalog.go +++ b/commons/sharding/catalog.go @@ -50,6 +50,8 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ctrl "sigs.k8s.io/controller-runtime" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -137,11 +139,14 @@ func buildPodSpecForCatalog(instance *databasev4.ShardingDatabase, OraCatalogSpe group := oraFsGroup spec := &corev1.PodSpec{ SecurityContext: &corev1.PodSecurityContext{ + RunAsNonRoot: BoolPointer(true), RunAsUser: &user, + RunAsGroup: &group, FSGroup: &group, }, Containers: buildContainerSpecForCatalog(instance, OraCatalogSpex), Volumes: buildVolumeSpecForCatalog(instance, OraCatalogSpex), + ServiceAccountName: instance.Spec.SrvAccountName, } if (instance.Spec.IsDownloadScripts) && (instance.Spec.ScriptsLocation != "") { @@ -185,6 +190,10 @@ func buildVolumeSpecForCatalog(instance *databasev4.ShardingDatabase, OraCatalog }, } + if OraCatalogSpex.CatalogConfigData != nil && len(OraCatalogSpex.CatalogConfigData.Name) != 0 { + result = append(result, corev1.Volume{Name: OraCatalogSpex.Name + "-oradata-configdata", VolumeSource: corev1.VolumeSource{ConfigMap: &corev1.ConfigMapVolumeSource{LocalObjectReference: corev1.LocalObjectReference{Name: OraCatalogSpex.CatalogConfigData.Name}}}}) + } + if len(OraCatalogSpex.PvcName) != 0 { result = append(result, corev1.Volume{Name: OraCatalogSpex.Name + "oradata-vol4", VolumeSource: corev1.VolumeSource{PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ClaimName: OraCatalogSpex.PvcName}}}) } @@ -210,12 +219,19 @@ func buildVolumeSpecForCatalog(instance *databasev4.ShardingDatabase, OraCatalog func buildContainerSpecForCatalog(instance *databasev4.ShardingDatabase, OraCatalogSpex databasev4.CatalogSpec) []corev1.Container { // building Continer spec var result []corev1.Container + user := oraRunAsUser + group := oraFsGroup containerSpec := corev1.Container{ Name: OraCatalogSpex.Name, Image: instance.Spec.DbImage, SecurityContext: &corev1.SecurityContext{ + RunAsNonRoot: BoolPointer(true), + RunAsUser: &user, + RunAsGroup: &group, + AllowPrivilegeEscalation: BoolPointer(false), Capabilities: &corev1.Capabilities{ Add: []corev1.Capability{corev1.Capability("NET_ADMIN"), corev1.Capability("SYS_NICE")}, + Drop: []corev1.Capability{"ALL",}, }, }, Resources: corev1.ResourceRequirements{ @@ -299,8 +315,13 @@ func buildInitContainerSpecForCatalog(instance *databasev4.ShardingDatabase, Ora Name: OraCatalogSpex.Name + "-init1", Image: instance.Spec.DbImage, SecurityContext: &corev1.SecurityContext{ + RunAsNonRoot: BoolPointer(true), + AllowPrivilegeEscalation: BoolPointer(false), Privileged: &privFlag, RunAsUser: &uid, + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL",}, + }, }, Command: []string{ "/bin/bash", @@ -329,6 +350,10 @@ func buildVolumeMountSpecForCatalog(instance *databasev4.ShardingDatabase, OraCa } result = append(result, corev1.VolumeMount{Name: OraCatalogSpex.Name + "oradshm-vol6", MountPath: oraShm}) + if OraCatalogSpex.CatalogConfigData != nil && len(OraCatalogSpex.CatalogConfigData.Name) != 0 { + result = append(result, corev1.VolumeMount{Name: OraCatalogSpex.Name + "-oradata-configdata", MountPath: OraCatalogSpex.CatalogConfigData.MountPath}) + } + if len(instance.Spec.StagePvcName) != 0 { result = append(result, corev1.VolumeMount{Name: OraCatalogSpex.Name + "orastage-vol7", MountPath: oraStage}) } @@ -526,3 +551,16 @@ func UpdateProvForCatalog(instance *databasev4.ShardingDatabase, return ctrl.Result{}, nil } + +func ExportTDEKey(podName string, sparams string, instance *databasev4.ShardingDatabase, kubeClient kubernetes.Interface, kubeconfig clientcmd.ClientConfig, logger logr.Logger,) error { + var msg string + + msg = "" + _, _, err := ExecCommand(podName, getExportTDEKeyCmd(sparams), kubeClient, kubeconfig, instance, logger) + if err != nil { + msg = "Error executing getExportTDEKeyCmd : podName=[" + podName + "]. errMsg=" + err.Error() + LogMessages("INFO", msg, nil, instance, logger) + return err + } + return nil +} diff --git a/commons/sharding/gsm.go b/commons/sharding/gsm.go index e6be8770..5e0d5c46 100644 --- a/commons/sharding/gsm.go +++ b/commons/sharding/gsm.go @@ -142,11 +142,14 @@ func buildPodSpecForGsm(instance *databasev4.ShardingDatabase, OraGsmSpex databa group := oraFsGroup spec := &corev1.PodSpec{ SecurityContext: &corev1.PodSecurityContext{ + RunAsNonRoot: BoolPointer(true), RunAsUser: &user, + RunAsGroup: &group, FSGroup: &group, }, Containers: buildContainerSpecForGsm(instance, OraGsmSpex), Volumes: buildVolumeSpecForGsm(instance, OraGsmSpex), + ServiceAccountName: instance.Spec.SrvAccountName, } if (instance.Spec.IsDownloadScripts) && (instance.Spec.ScriptsLocation != "") { @@ -189,6 +192,10 @@ func buildVolumeSpecForGsm(instance *databasev4.ShardingDatabase, OraGsmSpex dat }, } + if OraGsmSpex.GsmConfigData != nil && len(OraGsmSpex.GsmConfigData.Name) != 0 { + result = append(result, corev1.Volume{Name: OraGsmSpex.Name + "-oradata-configdata", VolumeSource: corev1.VolumeSource{ConfigMap: &corev1.ConfigMapVolumeSource{LocalObjectReference: corev1.LocalObjectReference{Name: OraGsmSpex.GsmConfigData.Name}}}}) + } + if len(OraGsmSpex.PvcName) != 0 { result = append(result, corev1.Volume{Name: OraGsmSpex.Name + "oradata-vol4", VolumeSource: corev1.VolumeSource{PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ClaimName: OraGsmSpex.PvcName}}}) } @@ -209,6 +216,8 @@ func buildContainerSpecForGsm(instance *databasev4.ShardingDatabase, OraGsmSpex var result []corev1.Container var masterGsmFlag = false var idx int + user := oraRunAsUser + group := oraFsGroup // Get the Idx if instance.Spec.Gsm[0].Name == OraGsmSpex.Name { masterGsmFlag = true @@ -223,8 +232,13 @@ func buildContainerSpecForGsm(instance *databasev4.ShardingDatabase, OraGsmSpex Name: OraGsmSpex.Name, Image: instance.Spec.GsmImage, SecurityContext: &corev1.SecurityContext{ + RunAsNonRoot: BoolPointer(true), + RunAsUser: &user, + RunAsGroup: &group, + AllowPrivilegeEscalation: BoolPointer(false), Capabilities: &corev1.Capabilities{ Add: []corev1.Capability{"NET_RAW"}, + Drop: []corev1.Capability{"ALL",}, }, }, Resources: corev1.ResourceRequirements{ @@ -288,8 +302,13 @@ func buildInitContainerSpecForGsm(instance *databasev4.ShardingDatabase, OraGsmS Name: OraGsmSpex.Name + "-init1", Image: instance.Spec.GsmImage, SecurityContext: &corev1.SecurityContext{ + RunAsNonRoot: BoolPointer(true), + AllowPrivilegeEscalation: BoolPointer(false), Privileged: &privFlag, RunAsUser: &uid, + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL",}, + }, }, Command: []string{ "/bin/bash", @@ -318,6 +337,10 @@ func buildVolumeMountSpecForGsm(instance *databasev4.ShardingDatabase, OraGsmSpe } result = append(result, corev1.VolumeMount{Name: OraGsmSpex.Name + "oradshm-vol6", MountPath: oraShm}) + if OraGsmSpex.GsmConfigData != nil && len(OraGsmSpex.GsmConfigData.Name) != 0 { + result = append(result, corev1.VolumeMount{Name: OraGsmSpex.Name + "-oradata-configdata", MountPath: OraGsmSpex.GsmConfigData.MountPath}) + } + if len(instance.Spec.StagePvcName) != 0 { result = append(result, corev1.VolumeMount{Name: OraGsmSpex.Name + "orastage-vol7", MountPath: oraStage}) } diff --git a/commons/sharding/scommon.go b/commons/sharding/scommon.go index 3b3f1b04..f284e5c6 100644 --- a/commons/sharding/scommon.go +++ b/commons/sharding/scommon.go @@ -238,6 +238,11 @@ func buildEnvVarsSpec(instance *databasev4.ShardingDatabase, variables []databas result = append(result, corev1.EnvVar{Name: "KEY_SECRET_VOLUME", Value: oraSecretMount}) } + if checkTdeWalletFlag(instance) { + result = append(result, corev1.EnvVar{Name: "TDE_PWD_KEY", Value: instance.Spec.DbSecret.TdeKeyFileName}) + result = append(result, corev1.EnvVar{Name: "TDE_PWD_FILE", Value: instance.Spec.DbSecret.TdePwdFileName}) + } + if restype == "GSM" { if !sDirectParam { //varinfo = "director_name=sharddirector" + sDirectorCounter + ";director_region=primary;director_port=1521" @@ -278,11 +283,14 @@ func buildEnvVarsSpec(instance *databasev4.ShardingDatabase, variables []databas if len(instance.Spec.GsmService) > 0 { svc = "" for i := 0; i < len(instance.Spec.GsmService); i++ { - svc = svc + "service_name=" + instance.Spec.GsmService[i].Name + ";" + svc = svc + "service_name=" + instance.Spec.GsmService[i].Name if len(instance.Spec.GsmService[i].Role) != 0 { - svc = svc + "service_role=" + instance.Spec.GsmService[i].Role + svc = svc + ";service_role=" + instance.Spec.GsmService[i].Role } else { - svc = svc + "service_role=primary" + svc = svc + ";service_role=primary" + } + if len(instance.Spec.GsmService[i].RuMode) != 0 { + svc = svc + ";service_mode=" + instance.Spec.GsmService[i].RuMode } result = append(result, corev1.EnvVar{Name: "SERVICE" + fmt.Sprint(i) + "_PARAMS", Value: svc}) svc = "" @@ -865,19 +873,26 @@ func buildDirectorParams(instance *databasev4.ShardingDatabase, oraGsmSpex datab var varinfo string var dnameFlag bool = false var dportFlag bool = false + var dname string + var dport string // Get the GSM Spec and build director params. idx feild is very important to build the unique director name and regiod. Idx is GSM array index. variables = oraGsmSpex.EnvVars for _, variable := range variables { if variable.Name == "DIRECTOR_NAME" { dnameFlag = true + dname = variable.Value } if variable.Name == "DIRECTOR_PORT" { dportFlag = true + dport = variable.Value } } if !dnameFlag { - varinfo = "director_name=sharddirector" + strconv.Itoa(idx) + ";" + varinfo = "director_name=sharddirector" + oraGsmSpex.Name + ";" + result = result + varinfo + } else { + varinfo = "director_name=" + dname + ";" result = result + varinfo } @@ -901,7 +916,11 @@ func buildDirectorParams(instance *databasev4.ShardingDatabase, oraGsmSpex datab if !dportFlag { varinfo = "director_port=1522" result = result + varinfo + } else { + varinfo = "director_port=" + dport + result = result + varinfo } + result = strings.TrimSuffix(result, ";") return result } @@ -1168,6 +1187,16 @@ func getGsmvalidateCmd() []string { return depCmd } +func getExportTDEKeyCmd(sparamStr string) []string { + var exportTDEKeyCmd []string = []string{oraDbScriptMount + "/cmdExec", "/bin/python", oraDbScriptMount + "/main.py ", "--exporttdekey=" + strconv.Quote(sparamStr)} + return exportTDEKeyCmd +} + +func getImportTDEKeyCmd(sparamStr string) []string { + var importTDEKeyCmd []string = []string{oraDbScriptMount + "/cmdExec", "/bin/python", oraDbScriptMount + "/main.py ", "--importtdekey=" + strconv.Quote(sparamStr)} + return importTDEKeyCmd +} + func getInitContainerCmd(resType string, name string, ) string { var initCmd string @@ -1530,6 +1559,15 @@ func checkTdeWalletFlag(instance *databasev4.ShardingDatabase) bool { return false } +func CheckIsTDEWalletFlag(instance *databasev4.ShardingDatabase, logger logr.Logger) bool { + LogMessages("INFO", "CheckIsTDEWalletFlag():isTdeWallet=["+instance.Spec.IsTdeWallet+"].", nil, instance, logger) + if strings.ToLower(instance.Spec.IsTdeWallet) == "enable" { + LogMessages("INFO", "CheckIsTDEWalletFlag():Returning true", nil, instance, logger) + return true + } + return false +} + func CheckIsDeleteFlag(delStr string, instance *databasev4.ShardingDatabase, logger logr.Logger) bool { if strings.ToLower(delStr) == "enable" { return true @@ -1546,3 +1584,11 @@ func getTdeWalletMountLoc(instance *databasev4.ShardingDatabase) string { } return "/tdewallet/" + instance.Name } + +func Int64Pointer(d int64) *int64 { + return &d +} + +func BoolPointer(d bool) *bool { + return &d +} diff --git a/commons/sharding/shard.go b/commons/sharding/shard.go index e48b56dd..244589ab 100644 --- a/commons/sharding/shard.go +++ b/commons/sharding/shard.go @@ -42,6 +42,7 @@ import ( "context" "reflect" "strconv" + "strings" databasev4 "github.com/oracle/oracle-database-operator/apis/database/v4" @@ -51,6 +52,8 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ctrl "sigs.k8s.io/controller-runtime" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -138,11 +141,14 @@ func buildPodSpecForShard(instance *databasev4.ShardingDatabase, OraShardSpex da group := oraFsGroup spec := &corev1.PodSpec{ SecurityContext: &corev1.PodSecurityContext{ + RunAsNonRoot: BoolPointer(true), RunAsUser: &user, + RunAsGroup: &group, FSGroup: &group, }, Containers: buildContainerSpecForShard(instance, OraShardSpex), Volumes: buildVolumeSpecForShard(instance, OraShardSpex), + ServiceAccountName: instance.Spec.SrvAccountName, } if (instance.Spec.IsDownloadScripts) && (instance.Spec.ScriptsLocation != "") { @@ -187,6 +193,10 @@ func buildVolumeSpecForShard(instance *databasev4.ShardingDatabase, OraShardSpex }, } + if OraShardSpex.ShardConfigData != nil && len(OraShardSpex.ShardConfigData.Name) != 0 { + result = append(result, corev1.Volume{Name: OraShardSpex.Name + "-oradata-configdata", VolumeSource: corev1.VolumeSource{ConfigMap: &corev1.ConfigMapVolumeSource{LocalObjectReference: corev1.LocalObjectReference{Name: OraShardSpex.ShardConfigData.Name}}}}) + } + if len(OraShardSpex.PvcName) != 0 { result = append(result, corev1.Volume{Name: OraShardSpex.Name + "oradata-vol4", VolumeSource: corev1.VolumeSource{PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ClaimName: OraShardSpex.PvcName}}}) } @@ -211,12 +221,19 @@ func buildVolumeSpecForShard(instance *databasev4.ShardingDatabase, OraShardSpex func buildContainerSpecForShard(instance *databasev4.ShardingDatabase, OraShardSpex databasev4.ShardSpec) []corev1.Container { // building Continer spec var result []corev1.Container + user := oraRunAsUser + group := oraFsGroup containerSpec := corev1.Container{ Name: OraShardSpex.Name, Image: instance.Spec.DbImage, SecurityContext: &corev1.SecurityContext{ + RunAsNonRoot: BoolPointer(true), + RunAsUser: &user, + RunAsGroup: &group, + AllowPrivilegeEscalation: BoolPointer(false), Capabilities: &corev1.Capabilities{ Add: []corev1.Capability{corev1.Capability("NET_ADMIN"), corev1.Capability("SYS_NICE")}, + Drop: []corev1.Capability{"ALL",}, }, }, Resources: corev1.ResourceRequirements{ @@ -304,8 +321,13 @@ func buildInitContainerSpecForShard(instance *databasev4.ShardingDatabase, OraSh Name: OraShardSpex.Name + "-init1", Image: instance.Spec.DbImage, SecurityContext: &corev1.SecurityContext{ + RunAsNonRoot: BoolPointer(true), + AllowPrivilegeEscalation: BoolPointer(false), Privileged: &privFlag, RunAsUser: &uid, + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL",}, + }, }, Command: []string{ "/bin/bash", @@ -334,6 +356,10 @@ func buildVolumeMountSpecForShard(instance *databasev4.ShardingDatabase, OraShar } result = append(result, corev1.VolumeMount{Name: OraShardSpex.Name + "oradshm-vol6", MountPath: oraShm}) + if OraShardSpex.ShardConfigData != nil && len(OraShardSpex.ShardConfigData.Name) != 0 { + result = append(result, corev1.VolumeMount{Name: OraShardSpex.Name + "-oradata-configdata", MountPath: OraShardSpex.ShardConfigData.MountPath}) + } + if len(instance.Spec.StagePvcName) != 0 { result = append(result, corev1.VolumeMount{Name: OraShardSpex.Name + "orastage-vol7", MountPath: oraStage}) } @@ -510,3 +536,21 @@ func UpdateProvForShard(instance *databasev4.ShardingDatabase, OraShardSpex data } return ctrl.Result{}, nil } + +func ImportTDEKey(podName string, sparams string, instance *databasev4.ShardingDatabase, kubeClient kubernetes.Interface, kubeconfig clientcmd.ClientConfig, logger logr.Logger,) error { + var msg string + + msg = "" + _, _, err := ExecCommand(podName, getImportTDEKeyCmd(sparams), kubeClient, kubeconfig, instance, logger) + if err != nil { + msg = "Error executing getImportTDEKeyCmd : podName=[" + podName + "]. errMsg=" + err.Error() + LogMessages("INFO", msg, nil, instance, logger) + return err + } + + importArr := getImportTDEKeyCmd(sparams) + importCmd := strings.Join(importArr, " ") + msg = "Executed getImportTDEKeyCmd[" + importCmd + "] on pod " + podName + LogMessages("INFO", msg, nil, instance, logger) + return nil +} diff --git a/config/certmanager/certificate-metrics.yaml b/config/certmanager/certificate-metrics.yaml new file mode 100644 index 00000000..c66f3fe6 --- /dev/null +++ b/config/certmanager/certificate-metrics.yaml @@ -0,0 +1,20 @@ +# The following manifests contain a self-signed issuer CR and a metrics certificate CR. +# More document can be found at https://docs.cert-manager.io +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + labels: + app.kubernetes.io/name: oracle-database-operator + app.kubernetes.io/managed-by: kustomize + name: metrics-certs # this name should match the one appeared in kustomizeconfig.yaml + namespace: system +spec: + dnsNames: + # SERVICE_NAME and SERVICE_NAMESPACE will be substituted by kustomize + # replacements in the config/default/kustomization.yaml file. + - SERVICE_NAME.SERVICE_NAMESPACE.svc + - SERVICE_NAME.SERVICE_NAMESPACE.svc.cluster.local + issuerRef: + kind: Issuer + name: selfsigned-issuer + secretName: metrics-server-cert diff --git a/config/certmanager/certificate-webhook.yaml b/config/certmanager/certificate-webhook.yaml new file mode 100644 index 00000000..1863f13b --- /dev/null +++ b/config/certmanager/certificate-webhook.yaml @@ -0,0 +1,20 @@ +# The following manifests contain a self-signed issuer CR and a certificate CR. +# More document can be found at https://docs.cert-manager.io +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + labels: + app.kubernetes.io/name: oracle-database-operator + app.kubernetes.io/managed-by: kustomize + name: serving-cert # this name should match the one appeared in kustomizeconfig.yaml + namespace: system +spec: + # SERVICE_NAME and SERVICE_NAMESPACE will be substituted by kustomize + # replacements in the config/default/kustomization.yaml file. + dnsNames: + - SERVICE_NAME.SERVICE_NAMESPACE.svc + - SERVICE_NAME.SERVICE_NAMESPACE.svc.cluster.local + issuerRef: + kind: Issuer + name: selfsigned-issuer + secretName: webhook-server-cert diff --git a/config/certmanager/certificate.yaml b/config/certmanager/certificate.yaml index 6b331894..320f647e 100644 --- a/config/certmanager/certificate.yaml +++ b/config/certmanager/certificate.yaml @@ -21,10 +21,10 @@ metadata: name: serving-cert # this name should match the one appeared in kustomizeconfig.yaml namespace: system spec: - # $(SERVICE_NAME) and $(SERVICE_NAMESPACE) will be substituted by kustomize + # SERVICE_NAME_PLACEHOLDER and SERVICE_NAMESPACE_PLACEHOLDER will be substituted by kustomize dnsNames: - - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc - - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc.cluster.local + - SERVICE_NAME_PLACEHOLDER.SERVICE_NAMESPACE_PLACEHOLDER.svc + - SERVICE_NAME_PLACEHOLDER.SERVICE_NAMESPACE_PLACEHOLDER.svc.cluster.local issuerRef: kind: Issuer name: selfsigned-issuer diff --git a/config/certmanager/issuer.yaml b/config/certmanager/issuer.yaml new file mode 100644 index 00000000..0518b674 --- /dev/null +++ b/config/certmanager/issuer.yaml @@ -0,0 +1,13 @@ +# The following manifest contains a self-signed issuer CR. +# More information can be found at https://docs.cert-manager.io +# WARNING: Targets CertManager v1.0. Check https://cert-manager.io/docs/installation/upgrading/ for breaking changes. +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + labels: + app.kubernetes.io/name: oracle-database-operator + app.kubernetes.io/managed-by: kustomize + name: selfsigned-issuer + namespace: system +spec: + selfSigned: {} diff --git a/config/crd/bases/database.oracle.com_autonomouscontainerdatabases.yaml b/config/crd/bases/database.oracle.com_autonomouscontainerdatabases.yaml index 1e078b63..78907768 100644 --- a/config/crd/bases/database.oracle.com_autonomouscontainerdatabases.yaml +++ b/config/crd/bases/database.oracle.com_autonomouscontainerdatabases.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.5 + controller-gen.kubebuilder.io/version: v0.17.3 name: autonomouscontainerdatabases.database.oracle.com spec: group: database.oracle.com diff --git a/config/crd/bases/database.oracle.com_autonomousdatabasebackups.yaml b/config/crd/bases/database.oracle.com_autonomousdatabasebackups.yaml index b0d6f8ed..c4c49b93 100644 --- a/config/crd/bases/database.oracle.com_autonomousdatabasebackups.yaml +++ b/config/crd/bases/database.oracle.com_autonomousdatabasebackups.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.5 + controller-gen.kubebuilder.io/version: v0.17.3 name: autonomousdatabasebackups.database.oracle.com spec: group: database.oracle.com @@ -62,14 +62,14 @@ spec: type: integer target: properties: - k8sADB: + k8sAdb: properties: name: type: string type: object - ociADB: + ociAdb: properties: - ocid: + id: type: string type: object type: object @@ -153,14 +153,14 @@ spec: type: integer target: properties: - k8sADB: + k8sAdb: properties: name: type: string type: object - ociADB: + ociAdb: properties: - ocid: + id: type: string type: object type: object diff --git a/config/crd/bases/database.oracle.com_autonomousdatabaserestores.yaml b/config/crd/bases/database.oracle.com_autonomousdatabaserestores.yaml index 3bfc5a4e..d59dfe2d 100644 --- a/config/crd/bases/database.oracle.com_autonomousdatabaserestores.yaml +++ b/config/crd/bases/database.oracle.com_autonomousdatabaserestores.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.5 + controller-gen.kubebuilder.io/version: v0.17.3 name: autonomousdatabaserestores.database.oracle.com spec: group: database.oracle.com @@ -48,7 +48,7 @@ spec: type: object source: properties: - k8sADBBackup: + k8sAdbBackup: properties: name: type: string @@ -61,14 +61,14 @@ spec: type: object target: properties: - k8sADB: + k8sAdb: properties: name: type: string type: object - ociADB: + ociAdb: properties: - ocid: + id: type: string type: object type: object @@ -134,7 +134,7 @@ spec: type: object source: properties: - k8sADBBackup: + k8sAdbBackup: properties: name: type: string @@ -147,14 +147,14 @@ spec: type: object target: properties: - k8sADB: + k8sAdb: properties: name: type: string type: object - ociADB: + ociAdb: properties: - ocid: + id: type: string type: object type: object diff --git a/config/crd/bases/database.oracle.com_autonomousdatabases.yaml b/config/crd/bases/database.oracle.com_autonomousdatabases.yaml index 1672ae81..d8fdd561 100644 --- a/config/crd/bases/database.oracle.com_autonomousdatabases.yaml +++ b/config/crd/bases/database.oracle.com_autonomousdatabases.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.5 + controller-gen.kubebuilder.io/version: v0.17.3 name: autonomousdatabases.database.oracle.com spec: group: database.oracle.com @@ -30,9 +30,9 @@ spec: - jsonPath: .spec.details.isDedicated name: Dedicated type: string - - jsonPath: .spec.details.cpuCoreCount - name: OCPUs - type: integer + - jsonPath: .spec.details.computeCount + name: Compute Count + type: number - jsonPath: .spec.details.dataStorageSizeInTBs name: Storage (TB) type: integer @@ -64,6 +64,8 @@ spec: - Start - Terminate - Clone + - Switchover + - Failover type: string clone: properties: @@ -363,9 +365,9 @@ spec: - jsonPath: .spec.details.isDedicated name: Dedicated type: string - - jsonPath: .spec.details.cpuCoreCount - name: OCPUs - type: integer + - jsonPath: .spec.details.computeCount + name: Compute Count + type: number - jsonPath: .spec.details.dataStorageSizeInTBs name: Storage (TB) type: integer @@ -397,6 +399,8 @@ spec: - Start - Terminate - Clone + - Switchover + - Failover type: string clone: properties: diff --git a/config/crd/bases/database.oracle.com_dataguardbrokers.yaml b/config/crd/bases/database.oracle.com_dataguardbrokers.yaml index 0e27126d..c71e94c0 100644 --- a/config/crd/bases/database.oracle.com_dataguardbrokers.yaml +++ b/config/crd/bases/database.oracle.com_dataguardbrokers.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.5 + controller-gen.kubebuilder.io/version: v0.17.3 name: dataguardbrokers.database.oracle.com spec: group: database.oracle.com @@ -11,6 +11,9 @@ spec: kind: DataguardBroker listKind: DataguardBrokerList plural: dataguardbrokers + shortNames: + - dgbroker + - dgbrokers singular: dataguardbroker scope: Namespaced versions: diff --git a/config/crd/bases/database.oracle.com_dbcssystems.yaml b/config/crd/bases/database.oracle.com_dbcssystems.yaml index 468d7612..351287e5 100644 --- a/config/crd/bases/database.oracle.com_dbcssystems.yaml +++ b/config/crd/bases/database.oracle.com_dbcssystems.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.5 + controller-gen.kubebuilder.io/version: v0.17.3 name: dbcssystems.database.oracle.com spec: group: database.oracle.com @@ -387,7 +387,23 @@ spec: storage: false subresources: status: {} - - name: v4 + - additionalPrinterColumns: + - jsonPath: .status.displayName + name: Display Name + type: string + - jsonPath: .status.dbInfo[0].dbName + name: DB Name + type: string + - jsonPath: .status.state + name: State + type: string + - jsonPath: .status.dbInfo[0].dbVersion + name: DB Version + type: string + - jsonPath: .status.dbInfo[0].connectionString + name: ConnString + type: string + name: v4 schema: openAPIV3Schema: properties: @@ -399,6 +415,45 @@ spec: type: object spec: properties: + dataGuard: + properties: + availabilityDomain: + type: string + dbAdminPasswordSecret: + type: string + dbName: + type: string + dbSystemFreeformTags: + additionalProperties: + type: string + type: object + displayName: + type: string + enabled: + type: boolean + hostName: + type: string + isDelete: + type: boolean + peerDbHomeId: + type: string + peerDbSystemId: + type: string + peerRole: + type: string + primaryDatabaseId: + type: string + protectionMode: + type: string + shape: + type: string + sidPrefix: + type: string + subnetId: + type: string + transportType: + type: string + type: object databaseId: type: string dbBackupId: @@ -407,10 +462,10 @@ spec: properties: dbAdminPasswordSecret: type: string - dbDbUniqueName: - type: string dbName: type: string + dbUniqueName: + type: string displayName: type: string domain: @@ -438,7 +493,6 @@ spec: tdeWalletPasswordSecret: type: string required: - - dbDbUniqueName - dbName - displayName - hostName @@ -448,6 +502,8 @@ spec: properties: availabilityDomain: type: string + backupDisplayName: + type: string backupSubnetId: type: string clusterName: @@ -473,10 +529,16 @@ spec: type: string dbEdition: type: string + dbHomeId: + type: string dbName: type: string + dbPatchOcid: + type: string dbUniqueName: type: string + dbUpgradeVersion: + type: string dbVersion: type: string dbWorkload: @@ -516,6 +578,16 @@ spec: type: string privateIp: type: string + restoreConfig: + properties: + latest: + type: boolean + scn: + type: string + timestamp: + format: date-time + type: string + type: object shape: type: string sshPublicKeys: @@ -534,18 +606,17 @@ spec: type: string timeZone: type: string - required: - - availabilityDomain - - compartmentId - - dbAdminPasswordSecret - - hostName - - shape - - subnetId type: object + enableBackup: + type: boolean hardLink: type: boolean id: type: string + isPatch: + type: boolean + isUpgrade: + type: boolean kmsConfig: properties: compartmentId: @@ -599,20 +670,72 @@ spec: properties: availabilityDomain: type: string + backups: + items: + properties: + backupId: + type: string + name: + type: string + timestamp: + type: string + required: + - backupId + - name + - timestamp + type: object + type: array cpuCoreCount: type: integer + dataGuardStatus: + properties: + dbAdminPasswordSecret: + type: string + dbName: + type: string + dbWorkload: + type: string + id: + type: string + isActiveDataGuardEnabled: + type: boolean + lifecycleDetails: + type: string + lifecycleState: + type: string + peerDataGuardAssociationId: + type: string + peerDatabaseId: + type: string + peerDbHomeId: + type: string + peerDbSystemId: + type: string + peerRole: + type: string + primaryDatabaseId: + type: string + protectionMode: + type: string + shape: + type: string + subnetId: + type: string + transportType: + type: string + type: object dataStoragePercentage: type: integer dataStorageSizeInGBs: type: integer dbCloneStatus: properties: - dbAdminPaswordSecret: - type: string - dbDbUniqueName: + dbAdminPasswordSecret: type: string dbName: type: string + dbUniqueName: + type: string displayName: type: string domain: @@ -629,15 +752,16 @@ spec: type: array subnetId: type: string - required: - - dbDbUniqueName - - hostName type: object dbEdition: type: string dbInfo: items: properties: + connectionString: + type: string + connectionStringLong: + type: string dbHomeId: type: string dbName: @@ -650,6 +774,8 @@ spec: type: string type: object type: array + dbVersion: + type: string displayName: type: string id: @@ -675,6 +801,8 @@ spec: type: object licenseModel: type: string + message: + type: string network: properties: clientSubnet: @@ -743,9 +871,6 @@ spec: type: string timeStarted: type: string - required: - - operationId - - operationType type: object type: array required: diff --git a/config/crd/bases/database.oracle.com_lrests.yaml b/config/crd/bases/database.oracle.com_lrests.yaml index c20356e7..4edf8f84 100644 --- a/config/crd/bases/database.oracle.com_lrests.yaml +++ b/config/crd/bases/database.oracle.com_lrests.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.5 + controller-gen.kubebuilder.io/version: v0.17.3 name: lrests.database.oracle.com spec: group: database.oracle.com @@ -55,6 +55,8 @@ spec: type: object spec: properties: + autodiscover: + type: boolean cdbAdminPwd: properties: secret: @@ -117,6 +119,21 @@ spec: required: - secret type: object + cdbTlsCat: + properties: + secret: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - secret + type: object cdbTlsCrt: properties: secret: @@ -147,6 +164,9 @@ spec: required: - secret type: object + clusterIp: + default: false + type: boolean dbPort: type: integer dbServer: @@ -155,6 +175,9 @@ spec: type: string deletePdbCascade: type: boolean + loadBalancer: + default: false + type: boolean lrestImage: type: string lrestImagePullPolicy: @@ -165,6 +188,7 @@ spec: lrestImagePullSecret: type: string lrestPort: + default: 8888 type: integer lrestPwd: properties: @@ -181,12 +205,16 @@ spec: required: - secret type: object + namespaceAutoDiscover: + type: string nodeSelector: additionalProperties: type: string type: object replicas: type: integer + serviceAccountName: + type: string serviceName: type: string sysAdminPwd: @@ -204,6 +232,9 @@ spec: required: - secret type: object + trace_level_client: + default: 0 + type: integer webServerPwd: properties: secret: diff --git a/config/crd/bases/database.oracle.com_lrpdbs.yaml b/config/crd/bases/database.oracle.com_lrpdbs.yaml index 14ad7f29..b58d9d89 100644 --- a/config/crd/bases/database.oracle.com_lrpdbs.yaml +++ b/config/crd/bases/database.oracle.com_lrpdbs.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.5 + controller-gen.kubebuilder.io/version: v0.17.3 name: lrpdbs.database.oracle.com spec: group: database.oracle.com @@ -31,18 +31,26 @@ spec: jsonPath: .status.totalSize name: PDB Size type: string - - description: Status of the LRPDB Resource - jsonPath: .status.phase - name: Status - type: string - description: Error message, if any jsonPath: .status.msg name: Message type: string + - description: open restricted + jsonPath: .status.restricted + name: Restricted + type: string - description: last sqlcode jsonPath: .status.sqlCode name: last sqlcode type: integer + - description: last plsql applied + jsonPath: .status.lastplsql + name: last PLSQL + type: string + - description: Bitmask status + jsonPath: .status.pdbBitMaskStr + name: BITMASK STATUS + type: string - description: The connect string to be used jsonPath: .status.connString name: Connect_String @@ -60,17 +68,6 @@ spec: spec: properties: action: - enum: - - Create - - Clone - - Plug - - Unplug - - Delete - - Modify - - Status - - Map - - Alter - - Noaction type: string adminName: properties: @@ -140,8 +137,6 @@ spec: type: string asClone: type: boolean - assertiveLrpdbDeletion: - type: boolean cdbName: type: string cdbNamespace: @@ -163,12 +158,16 @@ spec: type: object cdbResName: type: string + codeconfigmap: + type: string copyAction: enum: - COPY - NOCOPY - MOVE type: string + debug: + type: integer dropAction: enum: - INCLUDING @@ -177,6 +176,9 @@ spec: fileNameConversions: type: string getScript: + default: false + type: boolean + imperativeLrpdbDeletion: type: boolean lrpdbTlsCat: properties: @@ -231,6 +233,9 @@ spec: - READ WRITE - RESTRICTED type: string + modifyOption2: + default: NONE + type: string parameterScope: type: string pdbName: @@ -240,10 +245,21 @@ spec: - OPEN - CLOSE - ALTER + - DELETE + - UNPLUG + - PLUG + - CLONE + - RESET + - NONE type: string pdbconfigmap: type: string + plsqlexemode: + type: integer + reststate: + type: integer reuseTempFile: + default: true type: boolean sourceFileNameConversions: type: string @@ -292,6 +308,7 @@ spec: totalSize: type: string unlimitedStorage: + default: true type: boolean webServerPwd: properties: @@ -325,11 +342,6 @@ spec: type: object xmlFileName: type: string - required: - - action - - alterSystemParameter - - alterSystemValue - - webServerPwd type: object status: properties: @@ -343,14 +355,22 @@ spec: type: string connString: type: string + lastplsql: + type: string modifyOption: type: string msg: type: string openMode: type: string + pdbBitMask: + type: integer + pdbBitMaskStr: + type: string phase: type: string + restricted: + type: string sqlCode: type: integer status: diff --git a/config/crd/bases/database.oracle.com_oraclerestartnews.yaml b/config/crd/bases/database.oracle.com_oraclerestartnews.yaml new file mode 100644 index 00000000..3aece252 --- /dev/null +++ b/config/crd/bases/database.oracle.com_oraclerestartnews.yaml @@ -0,0 +1,1468 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.5 + name: oraclerestartnews.database.oracle.com +spec: + group: database.oracle.com + names: + kind: OracleRestartNew + listKind: OracleRestartNewList + plural: oraclerestartnews + singular: oraclerestartnew + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.configParams.dbName + name: DbName + type: string + - jsonPath: .status.dbState + name: DbState + type: string + - jsonPath: .status.role + name: Role + type: string + - jsonPath: .status.releaseUpdate + name: Version + type: string + - jsonPath: .status.pdbConnectString + name: Pdb Connect Str + type: string + - jsonPath: .status.state + name: State + type: string + name: v4 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + asmStorageDetails: + properties: + autoUpdate: + type: string + disksBySize: + items: + properties: + diskNames: + items: + type: string + type: array + storageSizeInGb: + type: integer + type: object + type: array + workerNodes: + items: + type: string + type: array + type: object + configParams: + properties: + cpuCount: + type: integer + crsAsmDeviceList: + type: string + crsAsmDiskDg: + type: string + crsAsmDiskDgRedundancy: + type: string + dbAsmDeviceList: + type: string + dbAsmDiskDgRedundancy: + type: string + dbBase: + type: string + dbCharSet: + type: string + dbConfigType: + type: string + dbDataFileDestDg: + type: string + dbHome: + type: string + dbName: + type: string + dbRecoveryFileDest: + type: string + dbRecoveryFileDestSize: + type: string + dbRedoFileSize: + type: string + dbResponseFile: + properties: + configMapName: + type: string + name: + type: string + type: object + dbStorageType: + type: string + dbSwZipFile: + type: string + dbType: + type: string + dbUniqueName: + type: string + enableArchiveLog: + type: string + gridBase: + type: string + gridHome: + type: string + gridResponseFile: + properties: + configMapName: + type: string + name: + type: string + type: object + gridSwZipFile: + type: string + hostSwStageLocation: + type: string + inventory: + type: string + opType: + type: string + pdbName: + type: string + pgaSize: + type: string + processes: + type: integer + recoAsmDeviceList: + type: string + recoAsmDiskDgRedudancy: + type: string + redoAsmDeviceList: + type: string + redoAsmDiskDgRedundancy: + type: string + sgaSize: + type: string + stagingSoftwareLocation: + type: string + swMountLocation: + type: string + type: object + dbSecret: + properties: + encryptionType: + type: string + keyFileMountLocation: + type: string + keyFileName: + type: string + keySecretName: + type: string + name: + type: string + pwdFileMountLocation: + type: string + pwdFileName: + type: string + type: object + externalSvcType: + type: string + image: + type: string + imagePullPolicy: + type: string + imagePullSecret: + type: string + instDetails: + items: + properties: + envFile: + type: string + envVars: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + hostSwLocation: + type: string + isDelete: + type: string + isForceDelete: + type: string + isKeepPVC: + type: string + label: + type: string + lsnrLocalPort: + format: int32 + type: integer + lsnrTargetPort: + format: int32 + type: integer + name: + type: string + nodePortSvc: + items: + properties: + name: + type: string + portMappings: + items: + properties: + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + type: string + targetPort: + format: int32 + type: integer + required: + - port + - protocol + - targetPort + type: object + type: array + svcType: + type: string + required: + - name + - svcType + type: object + type: array + onsLocalPort: + format: int32 + type: integer + onsTargetPort: + format: int32 + type: integer + portMappings: + items: + properties: + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + type: string + targetPort: + format: int32 + type: integer + required: + - port + - protocol + - targetPort + type: object + type: array + pvcName: + additionalProperties: + type: string + type: object + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + workerNode: + items: + type: string + type: array + required: + - name + type: object + type: array + isDebug: + type: string + isDeleteOraPvc: + type: string + isDeleteTopology: + type: string + isFailed: + type: boolean + isManual: + type: boolean + nfsStorageDetails: + properties: + path: + type: string + readOnly: + type: boolean + server: + type: string + required: + - path + - server + type: object + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + scriptsGetCmd: + type: string + scriptsLocation: + type: string + securityContext: + properties: + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + fsGroup: + format: int64 + type: integer + fsGroupChangePolicy: + type: string + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + supplementalGroups: + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + supplementalGroupsPolicy: + type: string + sysctls: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + serviceDetails: + properties: + available: + items: + type: string + type: array + cardinality: + type: string + clbGoal: + type: string + commitOutComeFastPath: + type: string + commitOutcome: + type: string + drainTimeOut: + type: integer + dtp: + type: string + edition: + type: string + failBack: + type: string + failOverDelay: + type: integer + failOverRestore: + type: string + failOverRetry: + type: integer + failOverType: + type: string + name: + type: string + notification: + type: string + pdb: + type: string + preferred: + items: + type: string + type: array + retenion: + type: integer + rlbGoal: + type: string + role: + type: string + sessionState: + type: string + stopOption: + type: string + svcState: + type: string + tafPolicy: + type: string + required: + - name + type: object + sshKeySecret: + properties: + keyMountLocation: + type: string + name: + type: string + privKeySecretName: + type: string + pubKeySecretName: + type: string + required: + - name + type: object + storageClass: + type: string + storageSizeInGB: + type: integer + tdeWalletSecret: + properties: + encryptionType: + type: string + keyFileMountLocation: + type: string + keyFileName: + type: string + keySecretName: + type: string + name: + type: string + pwdFileMountLocation: + type: string + pwdFileName: + type: string + type: object + useNfsforSwStorage: + type: string + required: + - instDetails + - securityContext + type: object + status: + properties: + DbName: + type: string + OracleRestartNodes: + items: + properties: + name: + type: string + nodeDetails: + properties: + InstanceState: + type: string + PodState: + type: string + clusterState: + type: string + isDelete: + type: string + mountedDevices: + items: + type: string + type: array + nodePortSvc: + items: + properties: + name: + type: string + portMappings: + items: + properties: + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + type: string + targetPort: + format: int32 + type: integer + required: + - port + - protocol + - targetPort + type: object + type: array + svcType: + type: string + required: + - name + - svcType + type: object + type: array + portMappings: + items: + properties: + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + type: string + targetPort: + format: int32 + type: integer + required: + - port + - protocol + - targetPort + type: object + type: array + pvcName: + additionalProperties: + type: string + type: object + state: + type: string + workerNode: + type: string + type: object + type: object + type: array + asmDetails: + properties: + diskgroup: + items: + properties: + disks: + items: + type: string + type: array + name: + type: string + redundancy: + type: string + type: object + type: array + type: object + clientEtcHost: + items: + type: string + type: array + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + maxLength: 32768 + type: string + observedGeneration: + format: int64 + minimum: 0 + type: integer + reason: + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + enum: + - "True" + - "False" + - Unknown + type: string + type: + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + configParams: + properties: + cpuCount: + type: integer + crsAsmDeviceList: + type: string + crsAsmDiskDg: + type: string + crsAsmDiskDgRedundancy: + type: string + dbAsmDeviceList: + type: string + dbAsmDiskDgRedundancy: + type: string + dbBase: + type: string + dbCharSet: + type: string + dbConfigType: + type: string + dbDataFileDestDg: + type: string + dbHome: + type: string + dbName: + type: string + dbRecoveryFileDest: + type: string + dbRecoveryFileDestSize: + type: string + dbRedoFileSize: + type: string + dbResponseFile: + properties: + configMapName: + type: string + name: + type: string + type: object + dbStorageType: + type: string + dbSwZipFile: + type: string + dbType: + type: string + dbUniqueName: + type: string + enableArchiveLog: + type: string + gridBase: + type: string + gridHome: + type: string + gridResponseFile: + properties: + configMapName: + type: string + name: + type: string + type: object + gridSwZipFile: + type: string + hostSwStageLocation: + type: string + inventory: + type: string + opType: + type: string + pdbName: + type: string + pgaSize: + type: string + processes: + type: integer + recoAsmDeviceList: + type: string + recoAsmDiskDgRedudancy: + type: string + redoAsmDeviceList: + type: string + redoAsmDiskDgRedundancy: + type: string + sgaSize: + type: string + stagingSoftwareLocation: + type: string + swMountLocation: + type: string + type: object + connectString: + type: string + dbSecret: + properties: + encryptionType: + type: string + keyFileMountLocation: + type: string + keyFileName: + type: string + keySecretName: + type: string + name: + type: string + pwdFileMountLocation: + type: string + pwdFileName: + type: string + type: object + dbState: + type: string + externalConnectString: + type: string + externalSvcType: + type: string + image: + type: string + imagePullPolicy: + type: string + imagePullSecret: + type: string + instDetails: + items: + properties: + envFile: + type: string + envVars: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + hostSwLocation: + type: string + isDelete: + type: string + isForceDelete: + type: string + isKeepPVC: + type: string + label: + type: string + lsnrLocalPort: + format: int32 + type: integer + lsnrTargetPort: + format: int32 + type: integer + name: + type: string + nodePortSvc: + items: + properties: + name: + type: string + portMappings: + items: + properties: + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + type: string + targetPort: + format: int32 + type: integer + required: + - port + - protocol + - targetPort + type: object + type: array + svcType: + type: string + required: + - name + - svcType + type: object + type: array + onsLocalPort: + format: int32 + type: integer + onsTargetPort: + format: int32 + type: integer + portMappings: + items: + properties: + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + type: string + targetPort: + format: int32 + type: integer + required: + - port + - protocol + - targetPort + type: object + type: array + pvcName: + additionalProperties: + type: string + type: object + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + workerNode: + items: + type: string + type: array + required: + - name + type: object + type: array + installNode: + type: string + isDebug: + type: string + isDeleteOraPvc: + type: string + isDeleteTopology: + type: string + nfsStorageDetails: + properties: + path: + type: string + readOnly: + type: boolean + server: + type: string + required: + - path + - server + type: object + oldSpec: + type: string + pdbConnectString: + type: string + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + releaseUpdate: + type: string + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + role: + type: string + scriptsGetCmd: + type: string + scriptsLocation: + type: string + securityContext: + properties: + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + fsGroup: + format: int64 + type: integer + fsGroupChangePolicy: + type: string + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + supplementalGroups: + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + supplementalGroupsPolicy: + type: string + sysctls: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + serviceDetails: + properties: + available: + items: + type: string + type: array + cardinality: + type: string + clbGoal: + type: string + commitOutComeFastPath: + type: string + commitOutcome: + type: string + drainTimeOut: + type: integer + dtp: + type: string + edition: + type: string + failBack: + type: string + failOverDelay: + type: integer + failOverRestore: + type: string + failOverRetry: + type: integer + failOverType: + type: string + name: + type: string + notification: + type: string + pdb: + type: string + preferred: + items: + type: string + type: array + retenion: + type: integer + rlbGoal: + type: string + role: + type: string + sessionState: + type: string + stopOption: + type: string + svcState: + type: string + tafPolicy: + type: string + required: + - name + type: object + sshKeySecret: + properties: + keyMountLocation: + type: string + name: + type: string + privKeySecretName: + type: string + pubKeySecretName: + type: string + required: + - name + type: object + state: + type: string + storageClass: + type: string + storageSizeInGB: + type: integer + tdeWalletSecret: + properties: + encryptionType: + type: string + keyFileMountLocation: + type: string + keyFileName: + type: string + keySecretName: + type: string + name: + type: string + pwdFileMountLocation: + type: string + pwdFileName: + type: string + type: object + useNfsforSwStorage: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/database.oracle.com_oraclerestarts.yaml b/config/crd/bases/database.oracle.com_oraclerestarts.yaml new file mode 100644 index 00000000..5db2113c --- /dev/null +++ b/config/crd/bases/database.oracle.com_oraclerestarts.yaml @@ -0,0 +1,1431 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.3 + name: oraclerestarts.database.oracle.com +spec: + group: database.oracle.com + names: + kind: OracleRestart + listKind: OracleRestartList + plural: oraclerestarts + singular: oraclerestart + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.configParams.dbName + name: DbName + type: string + - jsonPath: .status.dbState + name: DbState + type: string + - jsonPath: .status.role + name: Role + type: string + - jsonPath: .status.releaseUpdate + name: Version + type: string + - jsonPath: .status.pdbConnectString + name: Pdb Connect Str + type: string + - jsonPath: .status.state + name: State + type: string + name: v4 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + asmStorageDetails: + properties: + autoUpdate: + type: string + disksBySize: + items: + properties: + diskNames: + items: + type: string + type: array + storageSizeInGb: + type: integer + type: object + type: array + type: object + configParams: + properties: + cpuCount: + type: integer + crsAsmDeviceList: + type: string + crsAsmDiskDg: + type: string + crsAsmDiskDgRedundancy: + type: string + dbAsmDeviceList: + type: string + dbAsmDiskDgRedundancy: + type: string + dbBase: + type: string + dbCharSet: + type: string + dbConfigType: + type: string + dbDataFileDestDg: + type: string + dbHome: + type: string + dbName: + type: string + dbOneOffIds: + type: string + dbRecoveryFileDest: + type: string + dbRecoveryFileDestSize: + type: string + dbRedoFileSize: + type: string + dbResponseFile: + properties: + configMapName: + type: string + name: + type: string + type: object + dbStorageType: + type: string + dbSwZipFile: + type: string + dbType: + type: string + dbUniqueName: + type: string + enableArchiveLog: + type: string + gridBase: + type: string + gridHome: + type: string + gridOneOffIds: + type: string + gridResponseFile: + properties: + configMapName: + type: string + name: + type: string + type: object + gridSwZipFile: + type: string + hostSwStageLocation: + type: string + inventory: + type: string + oPatchLocation: + type: string + oPatchSwZipFile: + type: string + oneOffLocation: + type: string + opType: + type: string + pdbName: + type: string + pgaSize: + type: string + processes: + type: integer + recoAsmDeviceList: + type: string + recoAsmDiskDgRedundancy: + type: string + redoAsmDeviceList: + type: string + redoAsmDiskDg: + type: string + redoAsmDiskDgRedundancy: + type: string + ruFolderName: + type: string + ruPatchLocation: + type: string + sgaSize: + type: string + stagingSoftwareLocation: + type: string + swMountLocation: + type: string + swStagePvc: + type: string + swStagePvcMountLocation: + type: string + type: object + crsDgStorageClass: + type: string + dataDgStorageClass: + type: string + dbSecret: + properties: + encryptionType: + type: string + keyFileMountLocation: + type: string + keyFileName: + type: string + keySecretName: + type: string + name: + type: string + pwdFileMountLocation: + type: string + pwdFileName: + type: string + type: object + enableOns: + default: enable + enum: + - enable + - disable + type: string + image: + type: string + imagePullPolicy: + enum: + - Always + - IfNotPresent + - Never + type: string + imagePullSecret: + type: string + instDetails: + properties: + envFile: + type: string + envVars: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + fileKeyRef: + properties: + key: + type: string + optional: + default: false + type: boolean + path: + type: string + volumeName: + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + hostSwLocation: + type: string + isDelete: + type: string + isForceDelete: + type: string + isKeepPVC: + type: string + label: + type: string + name: + type: string + pvcName: + additionalProperties: + type: string + type: object + swLocStorageSizeInGb: + type: integer + workerNode: + items: + type: string + type: array + type: object + isDebug: + type: string + isDeleteTopology: + type: string + isFailed: + type: boolean + isManual: + type: boolean + lbService: + properties: + name: + type: string + onsLocalPort: + format: int32 + type: integer + onsTargetPort: + format: int32 + type: integer + portMappings: + items: + properties: + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + default: TCP + enum: + - TCP + - UDP + - SCTP + type: string + targetPort: + format: int32 + type: integer + type: object + type: array + svcAnnotation: + additionalProperties: + type: string + type: object + svcLBIP: + type: string + svcType: + type: string + type: object + nodePortSvc: + properties: + name: + type: string + onsLocalPort: + format: int32 + type: integer + onsTargetPort: + format: int32 + type: integer + portMappings: + items: + properties: + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + default: TCP + enum: + - TCP + - UDP + - SCTP + type: string + targetPort: + format: int32 + type: integer + type: object + type: array + svcAnnotation: + additionalProperties: + type: string + type: object + svcLBIP: + type: string + svcType: + type: string + type: object + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + recoDgStorageClass: + type: string + redoDgStorageClass: + type: string + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + scriptsGetCmd: + type: string + scriptsLocation: + type: string + securityContext: + properties: + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + fsGroup: + format: int64 + type: integer + fsGroupChangePolicy: + type: string + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxChangePolicy: + type: string + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + supplementalGroups: + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + supplementalGroupsPolicy: + type: string + sysctls: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + serviceAccountName: + type: string + serviceDetails: + properties: + available: + items: + type: string + type: array + cardinality: + type: string + clbGoal: + type: string + commitOutComeFastPath: + type: string + commitOutcome: + type: string + drainTimeOut: + type: integer + dtp: + type: string + edition: + type: string + failBack: + type: string + failOverDelay: + type: integer + failOverRestore: + type: string + failOverRetry: + type: integer + failOverType: + type: string + name: + type: string + notification: + type: string + pdb: + type: string + preferred: + items: + type: string + type: array + retenion: + type: integer + rlbGoal: + type: string + role: + type: string + sessionState: + type: string + stopOption: + type: string + svcState: + type: string + tafPolicy: + type: string + required: + - name + type: object + sshKeySecret: + properties: + keyMountLocation: + type: string + name: + type: string + privKeySecretName: + type: string + pubKeySecretName: + type: string + required: + - name + type: object + swDgStorageClass: + type: string + tdeWalletSecret: + properties: + encryptionType: + type: string + keyFileMountLocation: + type: string + keyFileName: + type: string + keySecretName: + type: string + name: + type: string + pwdFileMountLocation: + type: string + pwdFileName: + type: string + type: object + required: + - instDetails + - securityContext + type: object + status: + properties: + DbName: + type: string + OracleRestartNodes: + items: + properties: + name: + type: string + nodeDetails: + properties: + InstanceState: + type: string + PodState: + type: string + clusterState: + type: string + isDelete: + type: string + mountedDevices: + items: + type: string + type: array + nodePortSvc: + items: + properties: + name: + type: string + onsLocalPort: + format: int32 + type: integer + onsTargetPort: + format: int32 + type: integer + portMappings: + items: + properties: + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + default: TCP + enum: + - TCP + - UDP + - SCTP + type: string + targetPort: + format: int32 + type: integer + type: object + type: array + svcAnnotation: + additionalProperties: + type: string + type: object + svcLBIP: + type: string + svcType: + type: string + type: object + type: array + portMappings: + items: + properties: + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + default: TCP + enum: + - TCP + - UDP + - SCTP + type: string + targetPort: + format: int32 + type: integer + type: object + type: array + pvcName: + additionalProperties: + type: string + type: object + state: + type: string + workerNode: + type: string + type: object + type: object + type: array + asmDetails: + properties: + diskgroup: + items: + properties: + disks: + items: + type: string + type: array + name: + type: string + redundancy: + type: string + type: object + type: array + type: object + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + maxLength: 32768 + type: string + observedGeneration: + format: int64 + minimum: 0 + type: integer + reason: + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + enum: + - "True" + - "False" + - Unknown + type: string + type: + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + configParams: + properties: + cpuCount: + type: integer + crsAsmDeviceList: + type: string + crsAsmDiskDg: + type: string + crsAsmDiskDgRedundancy: + type: string + dbAsmDeviceList: + type: string + dbAsmDiskDgRedundancy: + type: string + dbBase: + type: string + dbCharSet: + type: string + dbConfigType: + type: string + dbDataFileDestDg: + type: string + dbHome: + type: string + dbName: + type: string + dbOneOffIds: + type: string + dbRecoveryFileDest: + type: string + dbRecoveryFileDestSize: + type: string + dbRedoFileSize: + type: string + dbResponseFile: + properties: + configMapName: + type: string + name: + type: string + type: object + dbStorageType: + type: string + dbSwZipFile: + type: string + dbType: + type: string + dbUniqueName: + type: string + enableArchiveLog: + type: string + gridBase: + type: string + gridHome: + type: string + gridOneOffIds: + type: string + gridResponseFile: + properties: + configMapName: + type: string + name: + type: string + type: object + gridSwZipFile: + type: string + hostSwStageLocation: + type: string + inventory: + type: string + oPatchLocation: + type: string + oPatchSwZipFile: + type: string + oneOffLocation: + type: string + opType: + type: string + pdbName: + type: string + pgaSize: + type: string + processes: + type: integer + recoAsmDeviceList: + type: string + recoAsmDiskDgRedundancy: + type: string + redoAsmDeviceList: + type: string + redoAsmDiskDg: + type: string + redoAsmDiskDgRedundancy: + type: string + ruFolderName: + type: string + ruPatchLocation: + type: string + sgaSize: + type: string + stagingSoftwareLocation: + type: string + swMountLocation: + type: string + swStagePvc: + type: string + swStagePvcMountLocation: + type: string + type: object + connectString: + type: string + dbSecret: + properties: + encryptionType: + type: string + keyFileMountLocation: + type: string + keyFileName: + type: string + keySecretName: + type: string + name: + type: string + pwdFileMountLocation: + type: string + pwdFileName: + type: string + type: object + dbState: + type: string + externalConnectString: + type: string + externalSvcType: + type: string + image: + type: string + imagePullPolicy: + type: string + imagePullSecret: + type: string + instDetails: + properties: + envFile: + type: string + envVars: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + fileKeyRef: + properties: + key: + type: string + optional: + default: false + type: boolean + path: + type: string + volumeName: + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + hostSwLocation: + type: string + isDelete: + type: string + isForceDelete: + type: string + isKeepPVC: + type: string + label: + type: string + name: + type: string + pvcName: + additionalProperties: + type: string + type: object + swLocStorageSizeInGb: + type: integer + workerNode: + items: + type: string + type: array + type: object + installNode: + type: string + isDebug: + type: string + isDeleteOraPvc: + type: string + isDeleteTopology: + type: string + nfsStorageDetails: + properties: + path: + type: string + readOnly: + type: boolean + server: + type: string + required: + - path + - server + type: object + oldSpec: + type: string + pdbConnectString: + type: string + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + releaseUpdate: + type: string + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + role: + type: string + scriptsGetCmd: + type: string + scriptsLocation: + type: string + securityContext: + properties: + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + fsGroup: + format: int64 + type: integer + fsGroupChangePolicy: + type: string + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxChangePolicy: + type: string + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + supplementalGroups: + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + supplementalGroupsPolicy: + type: string + sysctls: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + serviceDetails: + properties: + available: + items: + type: string + type: array + cardinality: + type: string + clbGoal: + type: string + commitOutComeFastPath: + type: string + commitOutcome: + type: string + drainTimeOut: + type: integer + dtp: + type: string + edition: + type: string + failBack: + type: string + failOverDelay: + type: integer + failOverRestore: + type: string + failOverRetry: + type: integer + failOverType: + type: string + name: + type: string + notification: + type: string + pdb: + type: string + preferred: + items: + type: string + type: array + retenion: + type: integer + rlbGoal: + type: string + role: + type: string + sessionState: + type: string + stopOption: + type: string + svcState: + type: string + tafPolicy: + type: string + required: + - name + type: object + sshKeySecret: + properties: + keyMountLocation: + type: string + name: + type: string + privKeySecretName: + type: string + pubKeySecretName: + type: string + required: + - name + type: object + state: + type: string + storageClass: + type: string + storageSizeInGB: + type: integer + tdeWalletSecret: + properties: + encryptionType: + type: string + keyFileMountLocation: + type: string + keyFileName: + type: string + keySecretName: + type: string + name: + type: string + pwdFileMountLocation: + type: string + pwdFileName: + type: string + type: object + useNfsforSwStorage: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/database.oracle.com_oraclerestartservices.yaml b/config/crd/bases/database.oracle.com_oraclerestartservices.yaml new file mode 100644 index 00000000..3f798336 --- /dev/null +++ b/config/crd/bases/database.oracle.com_oraclerestartservices.yaml @@ -0,0 +1,1468 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.5 + name: oraclerestartservices.database.oracle.com +spec: + group: database.oracle.com + names: + kind: OracleRestartService + listKind: OracleRestartServiceList + plural: oraclerestartservices + singular: oraclerestartservice + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.configParams.dbName + name: DbName + type: string + - jsonPath: .status.dbState + name: DbState + type: string + - jsonPath: .status.role + name: Role + type: string + - jsonPath: .status.releaseUpdate + name: Version + type: string + - jsonPath: .status.pdbConnectString + name: Pdb Connect Str + type: string + - jsonPath: .status.state + name: State + type: string + name: v4 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + asmStorageDetails: + properties: + autoUpdate: + type: string + disksBySize: + items: + properties: + diskNames: + items: + type: string + type: array + storageSizeInGb: + type: integer + type: object + type: array + workerNodes: + items: + type: string + type: array + type: object + configParams: + properties: + cpuCount: + type: integer + crsAsmDeviceList: + type: string + crsAsmDiskDg: + type: string + crsAsmDiskDgRedundancy: + type: string + dbAsmDeviceList: + type: string + dbAsmDiskDgRedundancy: + type: string + dbBase: + type: string + dbCharSet: + type: string + dbConfigType: + type: string + dbDataFileDestDg: + type: string + dbHome: + type: string + dbName: + type: string + dbRecoveryFileDest: + type: string + dbRecoveryFileDestSize: + type: string + dbRedoFileSize: + type: string + dbResponseFile: + properties: + configMapName: + type: string + name: + type: string + type: object + dbStorageType: + type: string + dbSwZipFile: + type: string + dbType: + type: string + dbUniqueName: + type: string + enableArchiveLog: + type: string + gridBase: + type: string + gridHome: + type: string + gridResponseFile: + properties: + configMapName: + type: string + name: + type: string + type: object + gridSwZipFile: + type: string + hostSwStageLocation: + type: string + inventory: + type: string + opType: + type: string + pdbName: + type: string + pgaSize: + type: string + processes: + type: integer + recoAsmDeviceList: + type: string + recoAsmDiskDgRedudancy: + type: string + redoAsmDeviceList: + type: string + redoAsmDiskDgRedundancy: + type: string + sgaSize: + type: string + stagingSoftwareLocation: + type: string + swMountLocation: + type: string + type: object + dbSecret: + properties: + encryptionType: + type: string + keyFileMountLocation: + type: string + keyFileName: + type: string + keySecretName: + type: string + name: + type: string + pwdFileMountLocation: + type: string + pwdFileName: + type: string + type: object + externalSvcType: + type: string + image: + type: string + imagePullPolicy: + type: string + imagePullSecret: + type: string + instDetails: + items: + properties: + envFile: + type: string + envVars: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + hostSwLocation: + type: string + isDelete: + type: string + isForceDelete: + type: string + isKeepPVC: + type: string + label: + type: string + lsnrLocalPort: + format: int32 + type: integer + lsnrTargetPort: + format: int32 + type: integer + name: + type: string + nodePortSvc: + items: + properties: + name: + type: string + portMappings: + items: + properties: + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + type: string + targetPort: + format: int32 + type: integer + required: + - port + - protocol + - targetPort + type: object + type: array + svcType: + type: string + required: + - name + - svcType + type: object + type: array + onsLocalPort: + format: int32 + type: integer + onsTargetPort: + format: int32 + type: integer + portMappings: + items: + properties: + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + type: string + targetPort: + format: int32 + type: integer + required: + - port + - protocol + - targetPort + type: object + type: array + pvcName: + additionalProperties: + type: string + type: object + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + workerNode: + items: + type: string + type: array + required: + - name + type: object + type: array + isDebug: + type: string + isDeleteOraPvc: + type: string + isDeleteTopology: + type: string + isFailed: + type: boolean + isManual: + type: boolean + nfsStorageDetails: + properties: + path: + type: string + readOnly: + type: boolean + server: + type: string + required: + - path + - server + type: object + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + scriptsGetCmd: + type: string + scriptsLocation: + type: string + securityContext: + properties: + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + fsGroup: + format: int64 + type: integer + fsGroupChangePolicy: + type: string + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + supplementalGroups: + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + supplementalGroupsPolicy: + type: string + sysctls: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + serviceDetails: + properties: + available: + items: + type: string + type: array + cardinality: + type: string + clbGoal: + type: string + commitOutComeFastPath: + type: string + commitOutcome: + type: string + drainTimeOut: + type: integer + dtp: + type: string + edition: + type: string + failBack: + type: string + failOverDelay: + type: integer + failOverRestore: + type: string + failOverRetry: + type: integer + failOverType: + type: string + name: + type: string + notification: + type: string + pdb: + type: string + preferred: + items: + type: string + type: array + retenion: + type: integer + rlbGoal: + type: string + role: + type: string + sessionState: + type: string + stopOption: + type: string + svcState: + type: string + tafPolicy: + type: string + required: + - name + type: object + sshKeySecret: + properties: + keyMountLocation: + type: string + name: + type: string + privKeySecretName: + type: string + pubKeySecretName: + type: string + required: + - name + type: object + storageClass: + type: string + storageSizeInGB: + type: integer + tdeWalletSecret: + properties: + encryptionType: + type: string + keyFileMountLocation: + type: string + keyFileName: + type: string + keySecretName: + type: string + name: + type: string + pwdFileMountLocation: + type: string + pwdFileName: + type: string + type: object + useNfsforSwStorage: + type: string + required: + - instDetails + - securityContext + type: object + status: + properties: + DbName: + type: string + OracleRestartNodes: + items: + properties: + name: + type: string + nodeDetails: + properties: + InstanceState: + type: string + PodState: + type: string + clusterState: + type: string + isDelete: + type: string + mountedDevices: + items: + type: string + type: array + nodePortSvc: + items: + properties: + name: + type: string + portMappings: + items: + properties: + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + type: string + targetPort: + format: int32 + type: integer + required: + - port + - protocol + - targetPort + type: object + type: array + svcType: + type: string + required: + - name + - svcType + type: object + type: array + portMappings: + items: + properties: + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + type: string + targetPort: + format: int32 + type: integer + required: + - port + - protocol + - targetPort + type: object + type: array + pvcName: + additionalProperties: + type: string + type: object + state: + type: string + workerNode: + type: string + type: object + type: object + type: array + asmDetails: + properties: + diskgroup: + items: + properties: + disks: + items: + type: string + type: array + name: + type: string + redundancy: + type: string + type: object + type: array + type: object + clientEtcHost: + items: + type: string + type: array + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + maxLength: 32768 + type: string + observedGeneration: + format: int64 + minimum: 0 + type: integer + reason: + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + enum: + - "True" + - "False" + - Unknown + type: string + type: + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + configParams: + properties: + cpuCount: + type: integer + crsAsmDeviceList: + type: string + crsAsmDiskDg: + type: string + crsAsmDiskDgRedundancy: + type: string + dbAsmDeviceList: + type: string + dbAsmDiskDgRedundancy: + type: string + dbBase: + type: string + dbCharSet: + type: string + dbConfigType: + type: string + dbDataFileDestDg: + type: string + dbHome: + type: string + dbName: + type: string + dbRecoveryFileDest: + type: string + dbRecoveryFileDestSize: + type: string + dbRedoFileSize: + type: string + dbResponseFile: + properties: + configMapName: + type: string + name: + type: string + type: object + dbStorageType: + type: string + dbSwZipFile: + type: string + dbType: + type: string + dbUniqueName: + type: string + enableArchiveLog: + type: string + gridBase: + type: string + gridHome: + type: string + gridResponseFile: + properties: + configMapName: + type: string + name: + type: string + type: object + gridSwZipFile: + type: string + hostSwStageLocation: + type: string + inventory: + type: string + opType: + type: string + pdbName: + type: string + pgaSize: + type: string + processes: + type: integer + recoAsmDeviceList: + type: string + recoAsmDiskDgRedudancy: + type: string + redoAsmDeviceList: + type: string + redoAsmDiskDgRedundancy: + type: string + sgaSize: + type: string + stagingSoftwareLocation: + type: string + swMountLocation: + type: string + type: object + connectString: + type: string + dbSecret: + properties: + encryptionType: + type: string + keyFileMountLocation: + type: string + keyFileName: + type: string + keySecretName: + type: string + name: + type: string + pwdFileMountLocation: + type: string + pwdFileName: + type: string + type: object + dbState: + type: string + externalConnectString: + type: string + externalSvcType: + type: string + image: + type: string + imagePullPolicy: + type: string + imagePullSecret: + type: string + instDetails: + items: + properties: + envFile: + type: string + envVars: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + hostSwLocation: + type: string + isDelete: + type: string + isForceDelete: + type: string + isKeepPVC: + type: string + label: + type: string + lsnrLocalPort: + format: int32 + type: integer + lsnrTargetPort: + format: int32 + type: integer + name: + type: string + nodePortSvc: + items: + properties: + name: + type: string + portMappings: + items: + properties: + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + type: string + targetPort: + format: int32 + type: integer + required: + - port + - protocol + - targetPort + type: object + type: array + svcType: + type: string + required: + - name + - svcType + type: object + type: array + onsLocalPort: + format: int32 + type: integer + onsTargetPort: + format: int32 + type: integer + portMappings: + items: + properties: + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + type: string + targetPort: + format: int32 + type: integer + required: + - port + - protocol + - targetPort + type: object + type: array + pvcName: + additionalProperties: + type: string + type: object + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + workerNode: + items: + type: string + type: array + required: + - name + type: object + type: array + installNode: + type: string + isDebug: + type: string + isDeleteOraPvc: + type: string + isDeleteTopology: + type: string + nfsStorageDetails: + properties: + path: + type: string + readOnly: + type: boolean + server: + type: string + required: + - path + - server + type: object + oldSpec: + type: string + pdbConnectString: + type: string + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + releaseUpdate: + type: string + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + role: + type: string + scriptsGetCmd: + type: string + scriptsLocation: + type: string + securityContext: + properties: + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + fsGroup: + format: int64 + type: integer + fsGroupChangePolicy: + type: string + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + supplementalGroups: + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + supplementalGroupsPolicy: + type: string + sysctls: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + serviceDetails: + properties: + available: + items: + type: string + type: array + cardinality: + type: string + clbGoal: + type: string + commitOutComeFastPath: + type: string + commitOutcome: + type: string + drainTimeOut: + type: integer + dtp: + type: string + edition: + type: string + failBack: + type: string + failOverDelay: + type: integer + failOverRestore: + type: string + failOverRetry: + type: integer + failOverType: + type: string + name: + type: string + notification: + type: string + pdb: + type: string + preferred: + items: + type: string + type: array + retenion: + type: integer + rlbGoal: + type: string + role: + type: string + sessionState: + type: string + stopOption: + type: string + svcState: + type: string + tafPolicy: + type: string + required: + - name + type: object + sshKeySecret: + properties: + keyMountLocation: + type: string + name: + type: string + privKeySecretName: + type: string + pubKeySecretName: + type: string + required: + - name + type: object + state: + type: string + storageClass: + type: string + storageSizeInGB: + type: integer + tdeWalletSecret: + properties: + encryptionType: + type: string + keyFileMountLocation: + type: string + keyFileName: + type: string + keySecretName: + type: string + name: + type: string + pwdFileMountLocation: + type: string + pwdFileName: + type: string + type: object + useNfsforSwStorage: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/database.oracle.com_oraclerestdataservices.yaml b/config/crd/bases/database.oracle.com_oraclerestdataservices.yaml index fe93a531..82479941 100644 --- a/config/crd/bases/database.oracle.com_oraclerestdataservices.yaml +++ b/config/crd/bases/database.oracle.com_oraclerestdataservices.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.5 + controller-gen.kubebuilder.io/version: v0.17.3 name: oraclerestdataservices.database.oracle.com spec: group: database.oracle.com @@ -11,6 +11,8 @@ spec: kind: OracleRestDataService listKind: OracleRestDataServiceList plural: oraclerestdataservices + shortNames: + - ords singular: oraclerestdataservice scope: Namespaced versions: diff --git a/config/crd/bases/database.oracle.com_ordssrvs.yaml b/config/crd/bases/database.oracle.com_ordssrvs.yaml index 9c4ab88f..bb2208d6 100644 --- a/config/crd/bases/database.oracle.com_ordssrvs.yaml +++ b/config/crd/bases/database.oracle.com_ordssrvs.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.5 + controller-gen.kubebuilder.io/version: v0.17.3 name: ordssrvs.database.oracle.com spec: group: database.oracle.com @@ -68,22 +68,40 @@ spec: type: boolean globalSettings: properties: + apex.download: + default: false + type: boolean + apex.download.url: + default: https://download.oracle.com/otn_software/apex/apex-latest.zip + type: string + apex.installation.persistence: + properties: + accessMode: + default: ReadWriteOnce + enum: + - ReadWriteOnce + - ReadWriteMany + type: string + size: + default: 1Gi + type: string + storageClass: + type: string + volumeName: + type: string + type: object cache.metadata.enabled: type: boolean cache.metadata.graphql.expireAfterAccess: - format: int64 - type: integer + type: string cache.metadata.graphql.expireAfterWrite: - format: int64 - type: integer + type: string cache.metadata.jwks.enabled: type: boolean cache.metadata.jwks.expireAfterAccess: - format: int64 - type: integer + type: string cache.metadata.jwks.expireAfterWrite: - format: int64 - type: integer + type: string cache.metadata.jwks.initialCapacity: format: int32 type: integer @@ -91,8 +109,7 @@ spec: format: int32 type: integer cache.metadata.timeout: - format: int64 - type: integer + type: string certSecret: properties: cert: @@ -111,8 +128,7 @@ spec: database.api.management.services.disabled: type: boolean db.invalidPoolTimeout: - format: int64 - type: integer + type: string debug.printDebugToScreen: type: boolean enable.mongo.access.log: @@ -139,11 +155,9 @@ spec: mongo.enabled: type: boolean mongo.idle.timeout: - format: int64 - type: integer + type: string mongo.op.timeout: - format: int64 - type: integer + type: string mongo.port: default: 27017 format: int32 @@ -154,8 +168,7 @@ spec: format: int32 type: integer security.credentials.lock.time: - format: int64 - type: integer + type: string security.disableDefaultExclusionList: type: boolean security.exclusionList: @@ -187,8 +200,7 @@ spec: format: int32 type: integer standalone.stop.timeout: - format: int64 - type: integer + type: string type: object image: type: string @@ -254,8 +266,7 @@ spec: db.hostname: type: string db.poolDestroyTimeout: - format: int64 - type: integer + type: string db.port: format: int32 type: integer @@ -313,8 +324,7 @@ spec: format: int32 type: integer jdbc.MaxConnectionReuseTime: - format: int32 - type: integer + type: string jdbc.MaxLimit: format: int32 type: integer @@ -360,23 +370,18 @@ spec: restEnabledSql.active: type: boolean security.jwks.connection.timeout: - format: int64 - type: integer + type: string security.jwks.read.timeout: - format: int64 - type: integer + type: string security.jwks.refresh.interval: - format: int64 - type: integer + type: string security.jwks.size: format: int32 type: integer security.jwt.allowed.age: - format: int64 - type: integer + type: string security.jwt.allowed.skew: - format: int64 - type: integer + type: string security.jwt.profile.enabled: type: boolean security.requestAuthenticationFunction: @@ -410,6 +415,8 @@ spec: format: int32 minimum: 1 type: integer + serviceAccountName: + type: string workloadType: default: Deployment enum: diff --git a/config/crd/bases/database.oracle.com_shardingdatabases.yaml b/config/crd/bases/database.oracle.com_shardingdatabases.yaml index 90c6dd53..df7ab8ed 100644 --- a/config/crd/bases/database.oracle.com_shardingdatabases.yaml +++ b/config/crd/bases/database.oracle.com_shardingdatabases.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.5 + controller-gen.kubebuilder.io/version: v0.17.3 name: shardingdatabases.database.oracle.com spec: group: database.oracle.com @@ -588,6 +588,13 @@ spec: catalog: items: properties: + catalogConfigData: + properties: + mountPath: + type: string + name: + type: string + type: object envVars: items: properties: @@ -688,9 +695,14 @@ spec: type: string pwdFileName: type: string + tdeKeyFileName: + type: string + tdePwdFileName: + type: string required: - name - pwdFileName + - tdePwdFileName type: object fssStorageClass: type: string @@ -711,6 +723,13 @@ spec: - value type: object type: array + gsmConfigData: + properties: + mountPath: + type: string + name: + type: string + type: object imagePullPolicy: type: string isDelete: @@ -832,6 +851,8 @@ spec: type: string role: type: string + ruMode: + type: string sessionState: type: string sqlTransactionProfile: @@ -915,6 +936,8 @@ spec: type: string scriptsLocation: type: string + serviceAccountName: + type: string shard: items: properties: @@ -992,6 +1015,13 @@ spec: x-kubernetes-int-or-string: true type: object type: object + shardConfigData: + properties: + mountPath: + type: string + name: + type: string + type: object shardGroup: type: string shardRegion: @@ -1009,6 +1039,47 @@ spec: type: string shardConfigName: type: string + shardInfo: + items: + properties: + replicas: + format: int32 + type: integer + shape: + type: string + shardGroupDetails: + properties: + ShardSpace: + type: string + deployAs: + type: string + isDelete: + type: string + region: + type: string + repFactor: + type: integer + shardGroupName: + type: string + type: object + shardPreFixName: + type: string + shardSpaceDetails: + properties: + Chnuks: + type: integer + protectMode: + type: string + shardSpaceName: + type: string + type: object + storageSizeInGb: + format: int32 + type: integer + required: + - shardPreFixName + type: object + type: array shardRegion: items: type: string @@ -1030,7 +1101,6 @@ spec: - dbImage - gsm - gsmImage - - shard type: object status: properties: diff --git a/config/crd/bases/database.oracle.com_singleinstancedatabases.yaml b/config/crd/bases/database.oracle.com_singleinstancedatabases.yaml index 8357f2c5..247a5308 100644 --- a/config/crd/bases/database.oracle.com_singleinstancedatabases.yaml +++ b/config/crd/bases/database.oracle.com_singleinstancedatabases.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.5 + controller-gen.kubebuilder.io/version: v0.17.3 name: singleinstancedatabases.database.oracle.com spec: group: database.oracle.com @@ -11,6 +11,9 @@ spec: kind: SingleInstanceDatabase listKind: SingleInstanceDatabaseList plural: singleinstancedatabases + shortNames: + - sidb + - sidbs singular: singleinstancedatabase scope: Namespaced versions: diff --git a/config/crd/bases/observability.oracle.com_databaseobservers.yaml b/config/crd/bases/observability.oracle.com_databaseobservers.yaml index 298f9d4e..200902bc 100644 --- a/config/crd/bases/observability.oracle.com_databaseobservers.yaml +++ b/config/crd/bases/observability.oracle.com_databaseobservers.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.5 + controller-gen.kubebuilder.io/version: v0.17.3 name: databaseobservers.observability.oracle.com spec: group: observability.oracle.com @@ -18,8 +18,8 @@ spec: scope: Namespaced versions: - additionalPrinterColumns: - - jsonPath: .status.exporterConfig - name: ExporterConfig + - jsonPath: .status.metricsConfig + name: MetricsConfig type: string - jsonPath: .status.status name: Status @@ -39,7 +39,7 @@ spec: type: object spec: properties: - configuration: + azureConfig: properties: configMap: properties: @@ -51,8 +51,19 @@ spec: type: object database: properties: + azure: + properties: + vaultID: + type: string + vaultPasswordSecret: + type: string + vaultUsernameSecret: + type: string + type: object dbConnectionString: properties: + envName: + type: string key: type: string secret: @@ -60,241 +71,252 @@ spec: type: object dbPassword: properties: + envName: + type: string key: type: string secret: type: string - vaultOCID: - type: string - vaultSecretName: - type: string type: object dbUser: properties: + envName: + type: string key: type: string secret: type: string type: object - dbWallet: + oci: properties: - key: + vaultID: type: string - secret: + vaultPasswordSecret: type: string type: object type: object - exporter: - properties: - deployment: - properties: - args: - items: + databases: + additionalProperties: + properties: + dbConnectionString: + properties: + envName: type: string - type: array - commands: - items: + key: type: string - type: array - env: - additionalProperties: + secret: + type: string + type: object + dbPassword: + properties: + envName: type: string + key: + type: string + secret: + type: string + type: object + dbUser: + properties: + envName: + type: string + key: + type: string + secret: + type: string + type: object + type: object + type: object + deployment: + properties: + args: + items: + type: string + type: array + commands: + items: + type: string + type: array + env: + additionalProperties: + type: string + type: object + image: + type: string + labels: + additionalProperties: + type: string + type: object + podSecurityContext: + properties: + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type type: object - image: + fsGroup: + format: int64 + type: integer + fsGroupChangePolicy: type: string - labels: - additionalProperties: - type: string + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxChangePolicy: + type: string + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string type: object - podTemplate: + seccompProfile: properties: - labels: - additionalProperties: - type: string - type: object - securityContext: - properties: - appArmorProfile: - properties: - localhostProfile: - type: string - type: - type: string - required: - - type - type: object - fsGroup: - format: int64 - type: integer - fsGroupChangePolicy: - type: string - runAsGroup: - format: int64 - type: integer - runAsNonRoot: - type: boolean - runAsUser: - format: int64 - type: integer - seLinuxOptions: - properties: - level: - type: string - role: - type: string - type: - type: string - user: - type: string - type: object - seccompProfile: - properties: - localhostProfile: - type: string - type: - type: string - required: - - type - type: object - supplementalGroups: - items: - format: int64 - type: integer - type: array - x-kubernetes-list-type: atomic - supplementalGroupsPolicy: - type: string - sysctls: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - x-kubernetes-list-type: atomic - windowsOptions: - properties: - gmsaCredentialSpec: - type: string - gmsaCredentialSpecName: - type: string - hostProcess: - type: boolean - runAsUserName: - type: string - type: object - type: object + localhostProfile: + type: string + type: + type: string + required: + - type type: object - securityContext: + supplementalGroups: + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + supplementalGroupsPolicy: + type: string + sysctls: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + windowsOptions: properties: - allowPrivilegeEscalation: - type: boolean - appArmorProfile: - properties: - localhostProfile: - type: string - type: - type: string - required: - - type - type: object - capabilities: - properties: - add: - items: - type: string - type: array - x-kubernetes-list-type: atomic - drop: - items: - type: string - type: array - x-kubernetes-list-type: atomic - type: object - privileged: - type: boolean - procMount: + gmsaCredentialSpec: type: string - readOnlyRootFilesystem: - type: boolean - runAsGroup: - format: int64 - type: integer - runAsNonRoot: + gmsaCredentialSpecName: + type: string + hostProcess: type: boolean - runAsUser: - format: int64 - type: integer - seLinuxOptions: - properties: - level: - type: string - role: - type: string - type: - type: string - user: - type: string - type: object - seccompProfile: - properties: - localhostProfile: - type: string - type: - type: string - required: - - type - type: object - windowsOptions: - properties: - gmsaCredentialSpec: - type: string - gmsaCredentialSpecName: - type: string - hostProcess: - type: boolean - runAsUserName: - type: string - type: object + runAsUserName: + type: string type: object type: object - service: + podTemplate: properties: labels: additionalProperties: type: string type: object - ports: - items: - properties: - appProtocol: - type: string - name: + type: object + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + capabilities: + properties: + add: + items: type: string - nodePort: - format: int32 - type: integer - port: - format: int32 - type: integer - protocol: - default: TCP + type: array + x-kubernetes-list-type: atomic + drop: + items: type: string - targetPort: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - required: - - port - type: object - type: array + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + type: object + exporterConfig: + properties: + configMap: + properties: + key: + type: string + name: + type: string type: object + mountPath: + type: string type: object inheritLabels: items: @@ -302,9 +324,11 @@ spec: type: array log: properties: - filename: + destination: type: string - path: + disable: + type: boolean + filename: type: string volume: properties: @@ -317,41 +341,204 @@ spec: type: object type: object type: object + metrics: + properties: + configMap: + items: + properties: + key: + type: string + name: + type: string + type: object + type: array + type: object ociConfig: properties: - configMapName: - type: string - secretName: + configMap: + properties: + key: + type: string + name: + type: string + type: object + mountPath: type: string + privateKey: + properties: + secret: + type: string + type: object type: object - prometheus: + replicas: + format: int32 + type: integer + service: properties: - serviceMonitor: - properties: - endpoints: - items: - properties: - authorization: - properties: - credentials: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: - type: string - type: object - basicAuth: - properties: - password: + labels: + additionalProperties: + type: string + type: object + ports: + items: + properties: + appProtocol: + type: string + name: + type: string + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + default: TCP + type: string + targetPort: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + type: object + serviceMonitor: + properties: + endpoints: + items: + properties: + authorization: + properties: + credentials: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: + type: string + type: object + basicAuth: + properties: + password: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + username: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + bearerTokenFile: + type: string + bearerTokenSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + enableHttp2: + type: boolean + filterRunning: + type: boolean + followRedirects: + type: boolean + honorLabels: + type: boolean + honorTimestamps: + type: boolean + interval: + pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ + type: string + metricRelabelings: + items: + properties: + action: + default: replace + enum: + - replace + - Replace + - keep + - Keep + - drop + - Drop + - hashmod + - HashMod + - labelmap + - LabelMap + - labeldrop + - LabelDrop + - labelkeep + - LabelKeep + - lowercase + - Lowercase + - uppercase + - Uppercase + - keepequal + - KeepEqual + - dropequal + - DropEqual + type: string + modulus: + format: int64 + type: integer + regex: + type: string + replacement: + type: string + separator: + type: string + sourceLabels: + items: + pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ + type: string + type: array + targetLabel: + type: string + type: object + type: array + noProxy: + type: string + oauth2: + properties: + clientId: + properties: + configMap: properties: key: type: string @@ -364,7 +551,7 @@ spec: - key type: object x-kubernetes-map-type: atomic - username: + secret: properties: key: type: string @@ -378,9 +565,7 @@ spec: type: object x-kubernetes-map-type: atomic type: object - bearerTokenFile: - type: string - bearerTokenSecret: + clientSecret: properties: key: type: string @@ -393,98 +578,15 @@ spec: - key type: object x-kubernetes-map-type: atomic - enableHttp2: - type: boolean - filterRunning: - type: boolean - followRedirects: - type: boolean - honorLabels: - type: boolean - honorTimestamps: - type: boolean - interval: - pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ + endpointParams: + additionalProperties: + type: string + type: object + noProxy: type: string - metricRelabelings: - items: - properties: - action: - default: replace - enum: - - replace - - Replace - - keep - - Keep - - drop - - Drop - - hashmod - - HashMod - - labelmap - - LabelMap - - labeldrop - - LabelDrop - - labelkeep - - LabelKeep - - lowercase - - Lowercase - - uppercase - - Uppercase - - keepequal - - KeepEqual - - dropequal - - DropEqual - type: string - modulus: - format: int64 - type: integer - regex: - type: string - replacement: - type: string - separator: - type: string - sourceLabels: - items: - pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ - type: string - type: array - targetLabel: - type: string - type: object - type: array - oauth2: - properties: - clientId: - properties: - configMap: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - clientSecret: + proxyConnectHeader: + additionalProperties: + items: properties: key: type: string @@ -497,211 +599,18 @@ spec: - key type: object x-kubernetes-map-type: atomic - endpointParams: - additionalProperties: - type: string - type: object - noProxy: - type: string - proxyConnectHeader: - additionalProperties: - items: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: array - type: object - x-kubernetes-map-type: atomic - proxyFromEnvironment: - type: boolean - proxyUrl: - pattern: ^http(s)?://.+$ - type: string - scopes: - items: - type: string - type: array - tlsConfig: - properties: - ca: - properties: - configMap: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - cert: - properties: - configMap: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - insecureSkipVerify: - type: boolean - keySecret: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - maxVersion: - enum: - - TLS10 - - TLS11 - - TLS12 - - TLS13 - type: string - minVersion: - enum: - - TLS10 - - TLS11 - - TLS12 - - TLS13 - type: string - serverName: - type: string - type: object - tokenUrl: - minLength: 1 - type: string - required: - - clientId - - clientSecret - - tokenUrl - type: object - params: - additionalProperties: - items: - type: string type: array type: object - path: - type: string - port: - type: string + x-kubernetes-map-type: atomic + proxyFromEnvironment: + type: boolean proxyUrl: + pattern: ^(http|https|socks5)://.+$ type: string - relabelings: + scopes: items: - properties: - action: - default: replace - enum: - - replace - - Replace - - keep - - Keep - - drop - - Drop - - hashmod - - HashMod - - labelmap - - LabelMap - - labeldrop - - LabelDrop - - labelkeep - - LabelKeep - - lowercase - - Lowercase - - uppercase - - Uppercase - - keepequal - - KeepEqual - - dropequal - - DropEqual - type: string - modulus: - format: int64 - type: integer - regex: - type: string - replacement: - type: string - separator: - type: string - sourceLabels: - items: - pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ - type: string - type: array - targetLabel: - type: string - type: object + type: string type: array - scheme: - enum: - - http - - https - type: string - scrapeTimeout: - pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ - type: string - targetPort: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true tlsConfig: properties: ca: @@ -733,8 +642,6 @@ spec: type: object x-kubernetes-map-type: atomic type: object - caFile: - type: string cert: properties: configMap: @@ -764,12 +671,8 @@ spec: type: object x-kubernetes-map-type: atomic type: object - certFile: - type: string insecureSkipVerify: type: boolean - keyFile: - type: string keySecret: properties: key: @@ -800,573 +703,329 @@ spec: serverName: type: string type: object - trackTimestampsStaleness: - type: boolean + tokenUrl: + minLength: 1 + type: string + required: + - clientId + - clientSecret + - tokenUrl type: object - type: array - labels: - additionalProperties: - type: string - type: object - namespaceSelector: - properties: - any: - type: boolean - matchNames: + params: + additionalProperties: items: type: string type: array - type: object - type: object - type: object - replicas: - format: int32 - type: integer - sidecarVolumes: - items: - properties: - awsElasticBlockStore: - properties: - fsType: - type: string - partition: - format: int32 - type: integer - readOnly: - type: boolean - volumeID: - type: string - required: - - volumeID - type: object - azureDisk: - properties: - cachingMode: - type: string - diskName: - type: string - diskURI: - type: string - fsType: - default: ext4 - type: string - kind: - type: string - readOnly: - default: false - type: boolean - required: - - diskName - - diskURI - type: object - azureFile: - properties: - readOnly: - type: boolean - secretName: - type: string - shareName: - type: string - required: - - secretName - - shareName - type: object - cephfs: - properties: - monitors: - items: - type: string - type: array - x-kubernetes-list-type: atomic + type: object path: type: string - readOnly: - type: boolean - secretFile: + port: type: string - secretRef: - properties: - name: - default: "" - type: string + proxyConnectHeader: + additionalProperties: + items: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: array type: object x-kubernetes-map-type: atomic - user: - type: string - required: - - monitors - type: object - cinder: - properties: - fsType: - type: string - readOnly: + proxyFromEnvironment: type: boolean - secretRef: - properties: - name: - default: "" - type: string - type: object - x-kubernetes-map-type: atomic - volumeID: + proxyUrl: + pattern: ^(http|https|socks5)://.+$ type: string - required: - - volumeID - type: object - configMap: - properties: - defaultMode: - format: int32 - type: integer - items: + relabelings: items: properties: - key: + action: + default: replace + enum: + - replace + - Replace + - keep + - Keep + - drop + - Drop + - hashmod + - HashMod + - labelmap + - LabelMap + - labeldrop + - LabelDrop + - labelkeep + - LabelKeep + - lowercase + - Lowercase + - uppercase + - Uppercase + - keepequal + - KeepEqual + - dropequal + - DropEqual type: string - mode: - format: int32 + modulus: + format: int64 type: integer - path: + regex: type: string - required: - - key - - path - type: object - type: array - x-kubernetes-list-type: atomic - name: - default: "" - type: string - optional: - type: boolean - type: object - x-kubernetes-map-type: atomic - csi: - properties: - driver: - type: string - fsType: - type: string - nodePublishSecretRef: - properties: - name: - default: "" - type: string - type: object - x-kubernetes-map-type: atomic - readOnly: - type: boolean - volumeAttributes: - additionalProperties: - type: string - type: object - required: - - driver - type: object - downwardAPI: - properties: - defaultMode: - format: int32 - type: integer - items: - items: - properties: - fieldRef: - properties: - apiVersion: - type: string - fieldPath: - type: string - required: - - fieldPath - type: object - x-kubernetes-map-type: atomic - mode: - format: int32 - type: integer - path: + replacement: + type: string + separator: + type: string + sourceLabels: + items: + pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ + type: string + type: array + targetLabel: type: string - resourceFieldRef: - properties: - containerName: - type: string - divisor: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - resource: - type: string - required: - - resource - type: object - x-kubernetes-map-type: atomic - required: - - path type: object type: array - x-kubernetes-list-type: atomic - type: object - emptyDir: - properties: - medium: + scheme: + enum: + - http + - https type: string - sizeLimit: + scrapeTimeout: + pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ + type: string + targetPort: anyOf: - type: integer - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - type: object - ephemeral: - properties: - volumeClaimTemplate: + tlsConfig: properties: - metadata: - type: object - spec: + ca: properties: - accessModes: - items: - type: string - type: array - x-kubernetes-list-type: atomic - dataSource: + configMap: properties: - apiGroup: - type: string - kind: + key: type: string name: + default: "" type: string + optional: + type: boolean required: - - kind - - name + - key type: object x-kubernetes-map-type: atomic - dataSourceRef: + secret: properties: - apiGroup: - type: string - kind: + key: type: string name: + default: "" type: string - namespace: - type: string + optional: + type: boolean required: - - kind - - name + - key type: object - resources: + x-kubernetes-map-type: atomic + type: object + caFile: + type: string + cert: + properties: + configMap: properties: - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key type: object - selector: + x-kubernetes-map-type: atomic + secret: properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key type: object x-kubernetes-map-type: atomic - storageClassName: - type: string - volumeAttributesClassName: - type: string - volumeMode: + type: object + certFile: + type: string + insecureSkipVerify: + type: boolean + keyFile: + type: string + keySecret: + properties: + key: type: string - volumeName: + name: + default: "" type: string + optional: + type: boolean + required: + - key type: object - required: - - spec + x-kubernetes-map-type: atomic + maxVersion: + enum: + - TLS10 + - TLS11 + - TLS12 + - TLS13 + type: string + minVersion: + enum: + - TLS10 + - TLS11 + - TLS12 + - TLS13 + type: string + serverName: + type: string type: object + trackTimestampsStaleness: + type: boolean type: object - fc: - properties: - fsType: + type: array + labels: + additionalProperties: + type: string + type: object + namespaceSelector: + properties: + any: + type: boolean + matchNames: + items: type: string - lun: - format: int32 - type: integer - readOnly: - type: boolean - targetWWNs: + type: array + type: object + type: object + sidecar: + properties: + containers: + items: + properties: + args: items: type: string type: array x-kubernetes-list-type: atomic - wwids: + command: items: type: string type: array x-kubernetes-list-type: atomic - type: object - flexVolume: - properties: - driver: - type: string - fsType: - type: string - options: - additionalProperties: - type: string - type: object - readOnly: - type: boolean - secretRef: - properties: - name: - default: "" - type: string - type: object - x-kubernetes-map-type: atomic - required: - - driver - type: object - flocker: - properties: - datasetName: - type: string - datasetUUID: - type: string - type: object - gcePersistentDisk: - properties: - fsType: - type: string - partition: - format: int32 - type: integer - pdName: - type: string - readOnly: - type: boolean - required: - - pdName - type: object - gitRepo: - properties: - directory: - type: string - repository: - type: string - revision: - type: string - required: - - repository - type: object - glusterfs: - properties: - endpoints: - type: string - path: - type: string - readOnly: - type: boolean - required: - - endpoints - - path - type: object - hostPath: - properties: - path: - type: string - type: - type: string - required: - - path - type: object - image: - properties: - pullPolicy: - type: string - reference: - type: string - type: object - iscsi: - properties: - chapAuthDiscovery: - type: boolean - chapAuthSession: - type: boolean - fsType: - type: string - initiatorName: - type: string - iqn: - type: string - iscsiInterface: - default: default - type: string - lun: - format: int32 - type: integer - portals: - items: - type: string - type: array - x-kubernetes-list-type: atomic - readOnly: - type: boolean - secretRef: - properties: - name: - default: "" - type: string - type: object - x-kubernetes-map-type: atomic - targetPortal: - type: string - required: - - iqn - - lun - - targetPortal - type: object - name: - type: string - nfs: - properties: - path: - type: string - readOnly: - type: boolean - server: - type: string - required: - - path - - server - type: object - persistentVolumeClaim: - properties: - claimName: - type: string - readOnly: - type: boolean - required: - - claimName - type: object - photonPersistentDisk: - properties: - fsType: - type: string - pdID: - type: string - required: - - pdID - type: object - portworxVolume: - properties: - fsType: - type: string - readOnly: - type: boolean - volumeID: - type: string - required: - - volumeID - type: object - projected: - properties: - defaultMode: - format: int32 - type: integer - sources: + env: items: properties: - clusterTrustBundle: + name: + type: string + value: + type: string + valueFrom: properties: - labelSelector: + configMapKeyRef: properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + fileKeyRef: + properties: + key: + type: string + optional: + default: false + type: boolean + path: + type: string + volumeName: + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key type: object x-kubernetes-map-type: atomic - name: - type: string - optional: - type: boolean - path: - type: string - signerName: - type: string - required: - - path type: object - configMap: + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + envFrom: + items: + properties: + configMapRef: properties: - items: - items: - properties: - key: - type: string - mode: - format: int32 - type: integer - path: - type: string - required: - - key - - path - type: object - type: array - x-kubernetes-list-type: atomic name: default: "" type: string @@ -1374,66 +1033,10 @@ spec: type: boolean type: object x-kubernetes-map-type: atomic - downwardAPI: - properties: - items: - items: - properties: - fieldRef: - properties: - apiVersion: - type: string - fieldPath: - type: string - required: - - fieldPath - type: object - x-kubernetes-map-type: atomic - mode: - format: int32 - type: integer - path: - type: string - resourceFieldRef: - properties: - containerName: - type: string - divisor: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - resource: - type: string - required: - - resource - type: object - x-kubernetes-map-type: atomic - required: - - path - type: object - type: array - x-kubernetes-list-type: atomic - type: object - secret: + prefix: + type: string + secretRef: properties: - items: - items: - properties: - key: - type: string - mode: - format: int32 - type: integer - path: - type: string - required: - - key - - path - type: object - type: array - x-kubernetes-list-type: atomic name: default: "" type: string @@ -1441,283 +1044,139 @@ spec: type: boolean type: object x-kubernetes-map-type: atomic - serviceAccountToken: - properties: - audience: - type: string - expirationSeconds: - format: int64 - type: integer - path: - type: string - required: - - path - type: object type: object type: array x-kubernetes-list-type: atomic - type: object - quobyte: - properties: - group: - type: string - readOnly: - type: boolean - registry: - type: string - tenant: - type: string - user: - type: string - volume: - type: string - required: - - registry - - volume - type: object - rbd: - properties: - fsType: - type: string image: type: string - keyring: - default: /etc/ceph/keyring - type: string - monitors: - items: - type: string - type: array - x-kubernetes-list-type: atomic - pool: - default: rbd - type: string - readOnly: - type: boolean - secretRef: - properties: - name: - default: "" - type: string - type: object - x-kubernetes-map-type: atomic - user: - default: admin - type: string - required: - - image - - monitors - type: object - scaleIO: - properties: - fsType: - default: xfs - type: string - gateway: - type: string - protectionDomain: + imagePullPolicy: type: string - readOnly: - type: boolean - secretRef: + lifecycle: properties: - name: - default: "" - type: string - type: object - x-kubernetes-map-type: atomic - sslEnabled: - type: boolean - storageMode: - default: ThinProvisioned - type: string - storagePool: - type: string - system: - type: string - volumeName: - type: string - required: - - gateway - - secretRef - - system - type: object - secret: - properties: - defaultMode: - format: int32 - type: integer - items: - items: - properties: - key: - type: string - mode: - format: int32 - type: integer - path: - type: string - required: - - key - - path - type: object - type: array - x-kubernetes-list-type: atomic - optional: - type: boolean - secretName: - type: string - type: object - storageos: - properties: - fsType: - type: string - readOnly: - type: boolean - secretRef: - properties: - name: - default: "" + postStart: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + stopSignal: type: string type: object - x-kubernetes-map-type: atomic - volumeName: - type: string - volumeNamespace: - type: string - type: object - vsphereVolume: - properties: - fsType: - type: string - storagePolicyID: - type: string - storagePolicyName: - type: string - volumePath: - type: string - required: - - volumePath - type: object - required: - - name - type: object - type: array - sidecars: - items: - properties: - args: - items: - type: string - type: array - x-kubernetes-list-type: atomic - command: - items: - type: string - type: array - x-kubernetes-list-type: atomic - env: - items: - properties: - name: - type: string - value: - type: string - valueFrom: - properties: - configMapKeyRef: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - fieldRef: - properties: - apiVersion: - type: string - fieldPath: - type: string - required: - - fieldPath - type: object - x-kubernetes-map-type: atomic - resourceFieldRef: - properties: - containerName: - type: string - divisor: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - resource: - type: string - required: - - resource - type: object - x-kubernetes-map-type: atomic - secretKeyRef: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - envFrom: - items: - properties: - configMapRef: - properties: - name: - default: "" - type: string - optional: - type: boolean - type: object - x-kubernetes-map-type: atomic - prefix: - type: string - secretRef: - properties: - name: - default: "" - type: string - optional: - type: boolean - type: object - x-kubernetes-map-type: atomic - type: object - type: array - x-kubernetes-list-type: atomic - image: - type: string - imagePullPolicy: - type: string - lifecycle: - properties: - postStart: + livenessProbe: properties: exec: properties: @@ -1727,6 +1186,20 @@ spec: type: array x-kubernetes-list-type: atomic type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object httpGet: properties: host: @@ -1756,14 +1229,15 @@ spec: required: - port type: object - sleep: - properties: - seconds: - format: int64 - type: integer - required: - - seconds - type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer tcpSocket: properties: host: @@ -1776,8 +1250,40 @@ spec: required: - port type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer type: object - preStop: + name: + type: string + ports: + items: + properties: + containerPort: + format: int32 + type: integer + hostIP: + type: string + hostPort: + format: int32 + type: integer + name: + type: string + protocol: + default: TCP + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: properties: exec: properties: @@ -1787,6 +1293,20 @@ spec: type: array x-kubernetes-list-type: atomic type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object httpGet: properties: host: @@ -1816,14 +1336,15 @@ spec: required: - port type: object - sleep: - properties: - seconds: - format: int64 - type: integer - required: - - seconds - type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer tcpSocket: properties: host: @@ -1836,513 +1357,1172 @@ spec: required: - port type: object - type: object - type: object - livenessProbe: - properties: - exec: - properties: - command: - items: - type: string - type: array - x-kubernetes-list-type: atomic - type: object - failureThreshold: - format: int32 - type: integer - grpc: - properties: - port: + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: format: int32 type: integer - service: - default: "" - type: string - required: - - port type: object - httpGet: + resizePolicy: + items: + properties: + resourceName: + type: string + restartPolicy: + type: string + required: + - resourceName + - restartPolicy + type: object + type: array + x-kubernetes-list-type: atomic + resources: properties: - host: - type: string - httpHeaders: + claims: items: properties: name: type: string - value: + request: type: string required: - name - - value type: object type: array - x-kubernetes-list-type: atomic - path: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - scheme: - type: string - required: - - port + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object type: object - initialDelaySeconds: - format: int32 - type: integer - periodSeconds: - format: int32 - type: integer - successThreshold: - format: int32 - type: integer - tcpSocket: - properties: - host: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - required: - - port - type: object - terminationGracePeriodSeconds: - format: int64 - type: integer - timeoutSeconds: - format: int32 - type: integer - type: object - name: - type: string - ports: - items: - properties: - containerPort: - format: int32 - type: integer - hostIP: - type: string - hostPort: - format: int32 - type: integer - name: - type: string - protocol: - default: TCP - type: string - required: - - containerPort - type: object - type: array - x-kubernetes-list-map-keys: - - containerPort - - protocol - x-kubernetes-list-type: map - readinessProbe: - properties: - exec: - properties: - command: - items: + restartPolicy: + type: string + restartPolicyRules: + items: + properties: + action: type: string - type: array - x-kubernetes-list-type: atomic - type: object - failureThreshold: - format: int32 - type: integer - grpc: - properties: - port: - format: int32 - type: integer - service: - default: "" - type: string - required: - - port - type: object - httpGet: - properties: - host: - type: string - httpHeaders: - items: + exitCodes: properties: - name: - type: string - value: + operator: type: string + values: + items: + format: int32 + type: integer + type: array + x-kubernetes-list-type: set required: - - name - - value + - operator type: object - type: array - x-kubernetes-list-type: atomic - path: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - scheme: + required: + - action + type: object + type: array + x-kubernetes-list-type: atomic + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + capabilities: + properties: + add: + items: + type: string + type: array + x-kubernetes-list-type: atomic + drop: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + type: boolean + procMount: type: string - required: - - port + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object type: object - initialDelaySeconds: - format: int32 - type: integer - periodSeconds: - format: int32 - type: integer - successThreshold: - format: int32 - type: integer - tcpSocket: + startupProbe: properties: - host: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - required: - - port + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer type: object - terminationGracePeriodSeconds: - format: int64 - type: integer - timeoutSeconds: - format: int32 - type: integer - type: object - resizePolicy: - items: - properties: - resourceName: - type: string - restartPolicy: - type: string - required: - - resourceName - - restartPolicy - type: object - type: array - x-kubernetes-list-type: atomic - resources: - properties: - claims: + stdin: + type: boolean + stdinOnce: + type: boolean + terminationMessagePath: + type: string + terminationMessagePolicy: + type: string + tty: + type: boolean + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + required: + - devicePath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - devicePath + x-kubernetes-list-type: map + volumeMounts: items: properties: + mountPath: + type: string + mountPropagation: + type: string name: type: string - request: + readOnly: + type: boolean + recursiveReadOnly: + type: string + subPath: + type: string + subPathExpr: type: string required: + - mountPath - name type: object type: array x-kubernetes-list-map-keys: - - name + - mountPath x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object + workingDir: + type: string + required: + - name type: object - restartPolicy: - type: string - securityContext: + type: array + volumes: + items: properties: - allowPrivilegeEscalation: - type: boolean - appArmorProfile: + awsElasticBlockStore: properties: - localhostProfile: + fsType: type: string - type: + partition: + format: int32 + type: integer + readOnly: + type: boolean + volumeID: type: string required: - - type - type: object - capabilities: - properties: - add: - items: - type: string - type: array - x-kubernetes-list-type: atomic - drop: - items: - type: string - type: array - x-kubernetes-list-type: atomic + - volumeID type: object - privileged: - type: boolean - procMount: - type: string - readOnlyRootFilesystem: - type: boolean - runAsGroup: - format: int64 - type: integer - runAsNonRoot: - type: boolean - runAsUser: - format: int64 - type: integer - seLinuxOptions: + azureDisk: properties: - level: - type: string - role: + cachingMode: type: string - type: + diskName: type: string - user: + diskURI: type: string - type: object - seccompProfile: - properties: - localhostProfile: + fsType: + default: ext4 type: string - type: + kind: type: string + readOnly: + default: false + type: boolean required: - - type + - diskName + - diskURI type: object - windowsOptions: + azureFile: properties: - gmsaCredentialSpec: - type: string - gmsaCredentialSpecName: - type: string - hostProcess: + readOnly: type: boolean - runAsUserName: + secretName: type: string + shareName: + type: string + required: + - secretName + - shareName type: object - type: object - startupProbe: - properties: - exec: + cephfs: properties: - command: + monitors: items: type: string type: array x-kubernetes-list-type: atomic + path: + type: string + readOnly: + type: boolean + secretFile: + type: string + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + user: + type: string + required: + - monitors type: object - failureThreshold: - format: int32 - type: integer - grpc: + cinder: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + volumeID: + type: string + required: + - volumeID + type: object + configMap: properties: - port: + defaultMode: format: int32 type: integer - service: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: default: "" type: string - required: - - port + optional: + type: boolean type: object - httpGet: + x-kubernetes-map-type: atomic + csi: properties: - host: + driver: + type: string + fsType: type: string - httpHeaders: + nodePublishSecretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: + type: boolean + volumeAttributes: + additionalProperties: + type: string + type: object + required: + - driver + type: object + downwardAPI: + properties: + defaultMode: + format: int32 + type: integer + items: items: properties: - name: - type: string - value: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + format: int32 + type: integer + path: type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic required: - - name - - value + - path type: object type: array x-kubernetes-list-type: atomic - path: + type: object + emptyDir: + properties: + medium: type: string - port: + sizeLimit: anyOf: - type: integer - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - scheme: + type: object + ephemeral: + properties: + volumeClaimTemplate: + properties: + metadata: + type: object + spec: + properties: + accessModes: + items: + type: string + type: array + x-kubernetes-list-type: atomic + dataSource: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + dataSourceRef: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + required: + - kind + - name + type: object + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + storageClassName: + type: string + volumeAttributesClassName: + type: string + volumeMode: + type: string + volumeName: + type: string + type: object + required: + - spec + type: object + type: object + fc: + properties: + fsType: + type: string + lun: + format: int32 + type: integer + readOnly: + type: boolean + targetWWNs: + items: + type: string + type: array + x-kubernetes-list-type: atomic + wwids: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + flexVolume: + properties: + driver: + type: string + fsType: type: string + options: + additionalProperties: + type: string + type: object + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic required: - - port + - driver type: object - initialDelaySeconds: - format: int32 - type: integer - periodSeconds: - format: int32 - type: integer - successThreshold: - format: int32 - type: integer - tcpSocket: + flocker: properties: - host: + datasetName: type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true + datasetUUID: + type: string + type: object + gcePersistentDisk: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + pdName: + type: string + readOnly: + type: boolean required: - - port + - pdName type: object - terminationGracePeriodSeconds: - format: int64 - type: integer - timeoutSeconds: - format: int32 - type: integer - type: object - stdin: - type: boolean - stdinOnce: - type: boolean - terminationMessagePath: - type: string - terminationMessagePolicy: - type: string - tty: - type: boolean - volumeDevices: - items: - properties: - devicePath: - type: string - name: - type: string - required: - - devicePath - - name - type: object - type: array - x-kubernetes-list-map-keys: - - devicePath - x-kubernetes-list-type: map - volumeMounts: - items: - properties: - mountPath: - type: string - mountPropagation: - type: string - name: - type: string - readOnly: - type: boolean - recursiveReadOnly: - type: string - subPath: - type: string - subPathExpr: - type: string - required: - - mountPath - - name - type: object - type: array - x-kubernetes-list-map-keys: - - mountPath - x-kubernetes-list-type: map - workingDir: - type: string - required: - - name - type: object - type: array - type: object - status: - properties: - conditions: - items: - properties: - lastTransitionTime: - format: date-time - type: string - message: - maxLength: 32768 - type: string - observedGeneration: - format: int64 - minimum: 0 - type: integer - reason: - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - enum: - - "True" - - "False" - - Unknown - type: string - type: - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - exporterConfig: - type: string - replicas: - type: integer - status: - type: string - version: - type: string - required: - - conditions - - exporterConfig - - version - type: object - type: object + gitRepo: + properties: + directory: + type: string + repository: + type: string + revision: + type: string + required: + - repository + type: object + glusterfs: + properties: + endpoints: + type: string + path: + type: string + readOnly: + type: boolean + required: + - endpoints + - path + type: object + hostPath: + properties: + path: + type: string + type: + type: string + required: + - path + type: object + image: + properties: + pullPolicy: + type: string + reference: + type: string + type: object + iscsi: + properties: + chapAuthDiscovery: + type: boolean + chapAuthSession: + type: boolean + fsType: + type: string + initiatorName: + type: string + iqn: + type: string + iscsiInterface: + default: default + type: string + lun: + format: int32 + type: integer + portals: + items: + type: string + type: array + x-kubernetes-list-type: atomic + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + targetPortal: + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + type: string + nfs: + properties: + path: + type: string + readOnly: + type: boolean + server: + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + properties: + claimName: + type: string + readOnly: + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + properties: + fsType: + type: string + pdID: + type: string + required: + - pdID + type: object + portworxVolume: + properties: + fsType: + type: string + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + projected: + properties: + defaultMode: + format: int32 + type: integer + sources: + items: + properties: + clusterTrustBundle: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + name: + type: string + optional: + type: boolean + path: + type: string + signerName: + type: string + required: + - path + type: object + configMap: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + downwardAPI: + properties: + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podCertificate: + properties: + certificateChainPath: + type: string + credentialBundlePath: + type: string + keyPath: + type: string + keyType: + type: string + maxExpirationSeconds: + format: int32 + type: integer + signerName: + type: string + required: + - keyType + - signerName + type: object + secret: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + serviceAccountToken: + properties: + audience: + type: string + expirationSeconds: + format: int64 + type: integer + path: + type: string + required: + - path + type: object + type: object + type: array + x-kubernetes-list-type: atomic + type: object + quobyte: + properties: + group: + type: string + readOnly: + type: boolean + registry: + type: string + tenant: + type: string + user: + type: string + volume: + type: string + required: + - registry + - volume + type: object + rbd: + properties: + fsType: + type: string + image: + type: string + keyring: + default: /etc/ceph/keyring + type: string + monitors: + items: + type: string + type: array + x-kubernetes-list-type: atomic + pool: + default: rbd + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + user: + default: admin + type: string + required: + - image + - monitors + type: object + scaleIO: + properties: + fsType: + default: xfs + type: string + gateway: + type: string + protectionDomain: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + sslEnabled: + type: boolean + storageMode: + default: ThinProvisioned + type: string + storagePool: + type: string + system: + type: string + volumeName: + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + optional: + type: boolean + secretName: + type: string + type: object + storageos: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + volumeName: + type: string + volumeNamespace: + type: string + type: object + vsphereVolume: + properties: + fsType: + type: string + storagePolicyID: + type: string + storagePolicyName: + type: string + volumePath: + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + type: object + wallet: + properties: + additional: + items: + properties: + mountPath: + type: string + name: + type: string + secret: + type: string + type: object + type: array + mountPath: + type: string + secret: + type: string + type: object + type: object + status: + properties: + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + maxLength: 32768 + type: string + observedGeneration: + format: int64 + minimum: 0 + type: integer + reason: + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + enum: + - "True" + - "False" + - Unknown + type: string + type: + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + metricsConfig: + type: string + replicas: + type: integer + status: + type: string + version: + type: string + required: + - conditions + - metricsConfig + - version + type: object + type: object served: true storage: false subresources: status: {} - additionalPrinterColumns: - - jsonPath: .status.exporterConfig - name: ExporterConfig + - jsonPath: .status.metricsConfig + name: MetricsConfig type: string - jsonPath: .status.status name: Status @@ -2362,7 +2542,7 @@ spec: type: object spec: properties: - configuration: + azureConfig: properties: configMap: properties: @@ -2374,8 +2554,19 @@ spec: type: object database: properties: + azure: + properties: + vaultID: + type: string + vaultPasswordSecret: + type: string + vaultUsernameSecret: + type: string + type: object dbConnectionString: properties: + envName: + type: string key: type: string secret: @@ -2383,241 +2574,252 @@ spec: type: object dbPassword: properties: + envName: + type: string key: type: string secret: type: string - vaultOCID: - type: string - vaultSecretName: - type: string type: object dbUser: properties: + envName: + type: string key: type: string secret: type: string type: object - dbWallet: + oci: properties: - key: + vaultID: type: string - secret: + vaultPasswordSecret: type: string type: object type: object - exporter: - properties: - deployment: - properties: - args: - items: + databases: + additionalProperties: + properties: + dbConnectionString: + properties: + envName: type: string - type: array - commands: - items: + key: type: string - type: array - env: - additionalProperties: + secret: type: string - type: object - image: - type: string - labels: - additionalProperties: + type: object + dbPassword: + properties: + envName: type: string + key: + type: string + secret: + type: string + type: object + dbUser: + properties: + envName: + type: string + key: + type: string + secret: + type: string + type: object + type: object + type: object + deployment: + properties: + args: + items: + type: string + type: array + commands: + items: + type: string + type: array + env: + additionalProperties: + type: string + type: object + image: + type: string + labels: + additionalProperties: + type: string + type: object + podSecurityContext: + properties: + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type type: object - podTemplate: - properties: - labels: - additionalProperties: - type: string - type: object - securityContext: - properties: - appArmorProfile: - properties: - localhostProfile: - type: string - type: - type: string - required: - - type - type: object - fsGroup: - format: int64 - type: integer - fsGroupChangePolicy: - type: string - runAsGroup: - format: int64 - type: integer - runAsNonRoot: - type: boolean - runAsUser: - format: int64 - type: integer - seLinuxOptions: - properties: - level: - type: string - role: - type: string - type: - type: string - user: - type: string - type: object - seccompProfile: - properties: - localhostProfile: - type: string - type: - type: string - required: - - type - type: object - supplementalGroups: - items: - format: int64 - type: integer - type: array - x-kubernetes-list-type: atomic - supplementalGroupsPolicy: - type: string - sysctls: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - x-kubernetes-list-type: atomic - windowsOptions: - properties: - gmsaCredentialSpec: - type: string - gmsaCredentialSpecName: - type: string - hostProcess: - type: boolean - runAsUserName: - type: string - type: object - type: object - type: object - securityContext: + fsGroup: + format: int64 + type: integer + fsGroupChangePolicy: + type: string + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxChangePolicy: + type: string + seLinuxOptions: properties: - allowPrivilegeEscalation: - type: boolean - appArmorProfile: - properties: - localhostProfile: - type: string - type: - type: string - required: - - type - type: object - capabilities: - properties: - add: - items: - type: string - type: array - x-kubernetes-list-type: atomic - drop: - items: - type: string - type: array - x-kubernetes-list-type: atomic - type: object - privileged: - type: boolean - procMount: + level: + type: string + role: + type: string + type: + type: string + user: type: string - readOnlyRootFilesystem: - type: boolean - runAsGroup: - format: int64 - type: integer - runAsNonRoot: - type: boolean - runAsUser: - format: int64 - type: integer - seLinuxOptions: - properties: - level: - type: string - role: - type: string - type: - type: string - user: - type: string - type: object - seccompProfile: - properties: - localhostProfile: - type: string - type: - type: string - required: - - type - type: object - windowsOptions: - properties: - gmsaCredentialSpec: - type: string - gmsaCredentialSpecName: - type: string - hostProcess: - type: boolean - runAsUserName: - type: string - type: object type: object - type: object - service: - properties: - labels: - additionalProperties: - type: string + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type type: object - ports: + supplementalGroups: + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + supplementalGroupsPolicy: + type: string + sysctls: items: properties: - appProtocol: - type: string name: type: string - nodePort: - format: int32 - type: integer - port: - format: int32 - type: integer - protocol: - default: TCP + value: type: string - targetPort: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true required: - - port + - name + - value type: object type: array + x-kubernetes-list-type: atomic + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + podTemplate: + properties: + labels: + additionalProperties: + type: string + type: object + type: object + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + capabilities: + properties: + add: + items: + type: string + type: array + x-kubernetes-list-type: atomic + drop: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + type: object + exporterConfig: + properties: + configMap: + properties: + key: + type: string + name: + type: string type: object + mountPath: + type: string type: object inheritLabels: items: @@ -2625,9 +2827,11 @@ spec: type: array log: properties: - filename: + destination: type: string - path: + disable: + type: boolean + filename: type: string volume: properties: @@ -2640,41 +2844,204 @@ spec: type: object type: object type: object + metrics: + properties: + configMap: + items: + properties: + key: + type: string + name: + type: string + type: object + type: array + type: object ociConfig: properties: - configMapName: - type: string - secretName: + configMap: + properties: + key: + type: string + name: + type: string + type: object + mountPath: type: string + privateKey: + properties: + secret: + type: string + type: object type: object - prometheus: + replicas: + format: int32 + type: integer + service: properties: - serviceMonitor: - properties: - endpoints: - items: + labels: + additionalProperties: + type: string + type: object + ports: + items: + properties: + appProtocol: + type: string + name: + type: string + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + default: TCP + type: string + targetPort: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + type: object + serviceMonitor: + properties: + endpoints: + items: + properties: + authorization: properties: - authorization: + credentials: properties: - credentials: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: + type: string + type: object + basicAuth: + properties: + password: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + username: + properties: + key: type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key type: object - basicAuth: + x-kubernetes-map-type: atomic + type: object + bearerTokenFile: + type: string + bearerTokenSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + enableHttp2: + type: boolean + filterRunning: + type: boolean + followRedirects: + type: boolean + honorLabels: + type: boolean + honorTimestamps: + type: boolean + interval: + pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ + type: string + metricRelabelings: + items: + properties: + action: + default: replace + enum: + - replace + - Replace + - keep + - Keep + - drop + - Drop + - hashmod + - HashMod + - labelmap + - LabelMap + - labeldrop + - LabelDrop + - labelkeep + - LabelKeep + - lowercase + - Lowercase + - uppercase + - Uppercase + - keepequal + - KeepEqual + - dropequal + - DropEqual + type: string + modulus: + format: int64 + type: integer + regex: + type: string + replacement: + type: string + separator: + type: string + sourceLabels: + items: + pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ + type: string + type: array + targetLabel: + type: string + type: object + type: array + noProxy: + type: string + oauth2: + properties: + clientId: properties: - password: + configMap: properties: key: type: string @@ -2687,7 +3054,7 @@ spec: - key type: object x-kubernetes-map-type: atomic - username: + secret: properties: key: type: string @@ -2701,9 +3068,7 @@ spec: type: object x-kubernetes-map-type: atomic type: object - bearerTokenFile: - type: string - bearerTokenSecret: + clientSecret: properties: key: type: string @@ -2716,98 +3081,15 @@ spec: - key type: object x-kubernetes-map-type: atomic - enableHttp2: - type: boolean - filterRunning: - type: boolean - followRedirects: - type: boolean - honorLabels: - type: boolean - honorTimestamps: - type: boolean - interval: - pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ + endpointParams: + additionalProperties: + type: string + type: object + noProxy: type: string - metricRelabelings: - items: - properties: - action: - default: replace - enum: - - replace - - Replace - - keep - - Keep - - drop - - Drop - - hashmod - - HashMod - - labelmap - - LabelMap - - labeldrop - - LabelDrop - - labelkeep - - LabelKeep - - lowercase - - Lowercase - - uppercase - - Uppercase - - keepequal - - KeepEqual - - dropequal - - DropEqual - type: string - modulus: - format: int64 - type: integer - regex: - type: string - replacement: - type: string - separator: - type: string - sourceLabels: - items: - pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ - type: string - type: array - targetLabel: - type: string - type: object - type: array - oauth2: - properties: - clientId: - properties: - configMap: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - clientSecret: + proxyConnectHeader: + additionalProperties: + items: properties: key: type: string @@ -2820,211 +3102,18 @@ spec: - key type: object x-kubernetes-map-type: atomic - endpointParams: - additionalProperties: - type: string - type: object - noProxy: - type: string - proxyConnectHeader: - additionalProperties: - items: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: array - type: object - x-kubernetes-map-type: atomic - proxyFromEnvironment: - type: boolean - proxyUrl: - pattern: ^http(s)?://.+$ - type: string - scopes: - items: - type: string - type: array - tlsConfig: - properties: - ca: - properties: - configMap: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - cert: - properties: - configMap: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - insecureSkipVerify: - type: boolean - keySecret: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - maxVersion: - enum: - - TLS10 - - TLS11 - - TLS12 - - TLS13 - type: string - minVersion: - enum: - - TLS10 - - TLS11 - - TLS12 - - TLS13 - type: string - serverName: - type: string - type: object - tokenUrl: - minLength: 1 - type: string - required: - - clientId - - clientSecret - - tokenUrl - type: object - params: - additionalProperties: - items: - type: string type: array type: object - path: - type: string - port: - type: string + x-kubernetes-map-type: atomic + proxyFromEnvironment: + type: boolean proxyUrl: + pattern: ^(http|https|socks5)://.+$ type: string - relabelings: + scopes: items: - properties: - action: - default: replace - enum: - - replace - - Replace - - keep - - Keep - - drop - - Drop - - hashmod - - HashMod - - labelmap - - LabelMap - - labeldrop - - LabelDrop - - labelkeep - - LabelKeep - - lowercase - - Lowercase - - uppercase - - Uppercase - - keepequal - - KeepEqual - - dropequal - - DropEqual - type: string - modulus: - format: int64 - type: integer - regex: - type: string - replacement: - type: string - separator: - type: string - sourceLabels: - items: - pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ - type: string - type: array - targetLabel: - type: string - type: object + type: string type: array - scheme: - enum: - - http - - https - type: string - scrapeTimeout: - pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ - type: string - targetPort: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true tlsConfig: properties: ca: @@ -3056,8 +3145,6 @@ spec: type: object x-kubernetes-map-type: atomic type: object - caFile: - type: string cert: properties: configMap: @@ -3087,12 +3174,8 @@ spec: type: object x-kubernetes-map-type: atomic type: object - certFile: - type: string insecureSkipVerify: type: boolean - keyFile: - type: string keySecret: properties: key: @@ -3123,573 +3206,329 @@ spec: serverName: type: string type: object - trackTimestampsStaleness: - type: boolean + tokenUrl: + minLength: 1 + type: string + required: + - clientId + - clientSecret + - tokenUrl type: object - type: array - labels: - additionalProperties: - type: string - type: object - namespaceSelector: - properties: - any: - type: boolean - matchNames: + params: + additionalProperties: items: type: string type: array - type: object - type: object - type: object - replicas: - format: int32 - type: integer - sidecarVolumes: - items: - properties: - awsElasticBlockStore: - properties: - fsType: - type: string - partition: - format: int32 - type: integer - readOnly: - type: boolean - volumeID: - type: string - required: - - volumeID - type: object - azureDisk: - properties: - cachingMode: - type: string - diskName: - type: string - diskURI: - type: string - fsType: - default: ext4 - type: string - kind: - type: string - readOnly: - default: false - type: boolean - required: - - diskName - - diskURI - type: object - azureFile: - properties: - readOnly: - type: boolean - secretName: - type: string - shareName: - type: string - required: - - secretName - - shareName - type: object - cephfs: - properties: - monitors: - items: - type: string - type: array - x-kubernetes-list-type: atomic - path: - type: string - readOnly: - type: boolean - secretFile: - type: string - secretRef: - properties: - name: - default: "" - type: string - type: object - x-kubernetes-map-type: atomic - user: - type: string - required: - - monitors - type: object - cinder: - properties: - fsType: - type: string - readOnly: - type: boolean - secretRef: - properties: - name: - default: "" - type: string type: object - x-kubernetes-map-type: atomic - volumeID: - type: string - required: - - volumeID - type: object - configMap: - properties: - defaultMode: - format: int32 - type: integer - items: - items: - properties: - key: - type: string - mode: - format: int32 - type: integer - path: - type: string - required: - - key - - path - type: object - type: array - x-kubernetes-list-type: atomic - name: - default: "" - type: string - optional: - type: boolean - type: object - x-kubernetes-map-type: atomic - csi: - properties: - driver: + path: type: string - fsType: + port: type: string - nodePublishSecretRef: - properties: - name: - default: "" - type: string + proxyConnectHeader: + additionalProperties: + items: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: array type: object x-kubernetes-map-type: atomic - readOnly: + proxyFromEnvironment: type: boolean - volumeAttributes: - additionalProperties: - type: string - type: object - required: - - driver - type: object - downwardAPI: - properties: - defaultMode: - format: int32 - type: integer - items: + proxyUrl: + pattern: ^(http|https|socks5)://.+$ + type: string + relabelings: items: properties: - fieldRef: - properties: - apiVersion: - type: string - fieldPath: - type: string - required: - - fieldPath - type: object - x-kubernetes-map-type: atomic - mode: - format: int32 + action: + default: replace + enum: + - replace + - Replace + - keep + - Keep + - drop + - Drop + - hashmod + - HashMod + - labelmap + - LabelMap + - labeldrop + - LabelDrop + - labelkeep + - LabelKeep + - lowercase + - Lowercase + - uppercase + - Uppercase + - keepequal + - KeepEqual + - dropequal + - DropEqual + type: string + modulus: + format: int64 type: integer - path: + regex: + type: string + replacement: + type: string + separator: + type: string + sourceLabels: + items: + pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ + type: string + type: array + targetLabel: type: string - resourceFieldRef: - properties: - containerName: - type: string - divisor: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - resource: - type: string - required: - - resource - type: object - x-kubernetes-map-type: atomic - required: - - path type: object type: array - x-kubernetes-list-type: atomic - type: object - emptyDir: - properties: - medium: + scheme: + enum: + - http + - https type: string - sizeLimit: + scrapeTimeout: + pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ + type: string + targetPort: anyOf: - type: integer - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - type: object - ephemeral: - properties: - volumeClaimTemplate: + tlsConfig: properties: - metadata: - type: object - spec: + ca: properties: - accessModes: - items: - type: string - type: array - x-kubernetes-list-type: atomic - dataSource: + configMap: properties: - apiGroup: - type: string - kind: + key: type: string name: + default: "" type: string + optional: + type: boolean required: - - kind - - name + - key type: object x-kubernetes-map-type: atomic - dataSourceRef: + secret: properties: - apiGroup: - type: string - kind: + key: type: string name: + default: "" type: string - namespace: - type: string + optional: + type: boolean required: - - kind - - name + - key type: object - resources: + x-kubernetes-map-type: atomic + type: object + caFile: + type: string + cert: + properties: + configMap: properties: - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key type: object - selector: + x-kubernetes-map-type: atomic + secret: properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key type: object x-kubernetes-map-type: atomic - storageClassName: - type: string - volumeAttributesClassName: - type: string - volumeMode: + type: object + certFile: + type: string + insecureSkipVerify: + type: boolean + keyFile: + type: string + keySecret: + properties: + key: type: string - volumeName: + name: + default: "" type: string + optional: + type: boolean + required: + - key type: object - required: - - spec + x-kubernetes-map-type: atomic + maxVersion: + enum: + - TLS10 + - TLS11 + - TLS12 + - TLS13 + type: string + minVersion: + enum: + - TLS10 + - TLS11 + - TLS12 + - TLS13 + type: string + serverName: + type: string type: object + trackTimestampsStaleness: + type: boolean type: object - fc: - properties: - fsType: + type: array + labels: + additionalProperties: + type: string + type: object + namespaceSelector: + properties: + any: + type: boolean + matchNames: + items: type: string - lun: - format: int32 - type: integer - readOnly: - type: boolean - targetWWNs: - items: - type: string - type: array - x-kubernetes-list-type: atomic - wwids: + type: array + type: object + type: object + sidecar: + properties: + containers: + items: + properties: + args: items: type: string type: array x-kubernetes-list-type: atomic - type: object - flexVolume: - properties: - driver: - type: string - fsType: - type: string - options: - additionalProperties: - type: string - type: object - readOnly: - type: boolean - secretRef: - properties: - name: - default: "" - type: string - type: object - x-kubernetes-map-type: atomic - required: - - driver - type: object - flocker: - properties: - datasetName: - type: string - datasetUUID: - type: string - type: object - gcePersistentDisk: - properties: - fsType: - type: string - partition: - format: int32 - type: integer - pdName: - type: string - readOnly: - type: boolean - required: - - pdName - type: object - gitRepo: - properties: - directory: - type: string - repository: - type: string - revision: - type: string - required: - - repository - type: object - glusterfs: - properties: - endpoints: - type: string - path: - type: string - readOnly: - type: boolean - required: - - endpoints - - path - type: object - hostPath: - properties: - path: - type: string - type: - type: string - required: - - path - type: object - image: - properties: - pullPolicy: - type: string - reference: - type: string - type: object - iscsi: - properties: - chapAuthDiscovery: - type: boolean - chapAuthSession: - type: boolean - fsType: - type: string - initiatorName: - type: string - iqn: - type: string - iscsiInterface: - default: default - type: string - lun: - format: int32 - type: integer - portals: + command: items: type: string type: array x-kubernetes-list-type: atomic - readOnly: - type: boolean - secretRef: - properties: - name: - default: "" - type: string - type: object - x-kubernetes-map-type: atomic - targetPortal: - type: string - required: - - iqn - - lun - - targetPortal - type: object - name: - type: string - nfs: - properties: - path: - type: string - readOnly: - type: boolean - server: - type: string - required: - - path - - server - type: object - persistentVolumeClaim: - properties: - claimName: - type: string - readOnly: - type: boolean - required: - - claimName - type: object - photonPersistentDisk: - properties: - fsType: - type: string - pdID: - type: string - required: - - pdID - type: object - portworxVolume: - properties: - fsType: - type: string - readOnly: - type: boolean - volumeID: - type: string - required: - - volumeID - type: object - projected: - properties: - defaultMode: - format: int32 - type: integer - sources: + env: items: properties: - clusterTrustBundle: + name: + type: string + value: + type: string + valueFrom: properties: - labelSelector: + configMapKeyRef: properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + fileKeyRef: + properties: + key: + type: string + optional: + default: false + type: boolean + path: + type: string + volumeName: + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key type: object x-kubernetes-map-type: atomic - name: - type: string - optional: - type: boolean - path: - type: string - signerName: - type: string - required: - - path type: object - configMap: + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + envFrom: + items: + properties: + configMapRef: properties: - items: - items: - properties: - key: - type: string - mode: - format: int32 - type: integer - path: - type: string - required: - - key - - path - type: object - type: array - x-kubernetes-list-type: atomic name: default: "" type: string @@ -3697,66 +3536,10 @@ spec: type: boolean type: object x-kubernetes-map-type: atomic - downwardAPI: - properties: - items: - items: - properties: - fieldRef: - properties: - apiVersion: - type: string - fieldPath: - type: string - required: - - fieldPath - type: object - x-kubernetes-map-type: atomic - mode: - format: int32 - type: integer - path: - type: string - resourceFieldRef: - properties: - containerName: - type: string - divisor: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - resource: - type: string - required: - - resource - type: object - x-kubernetes-map-type: atomic - required: - - path - type: object - type: array - x-kubernetes-list-type: atomic - type: object - secret: + prefix: + type: string + secretRef: properties: - items: - items: - properties: - key: - type: string - mode: - format: int32 - type: integer - path: - type: string - required: - - key - - path - type: object - type: array - x-kubernetes-list-type: atomic name: default: "" type: string @@ -3764,283 +3547,470 @@ spec: type: boolean type: object x-kubernetes-map-type: atomic - serviceAccountToken: - properties: - audience: - type: string - expirationSeconds: - format: int64 - type: integer - path: - type: string - required: - - path - type: object type: object type: array x-kubernetes-list-type: atomic - type: object - quobyte: - properties: - group: - type: string - readOnly: - type: boolean - registry: - type: string - tenant: - type: string - user: - type: string - volume: - type: string - required: - - registry - - volume - type: object - rbd: - properties: - fsType: - type: string image: type: string - keyring: - default: /etc/ceph/keyring - type: string - monitors: - items: - type: string - type: array - x-kubernetes-list-type: atomic - pool: - default: rbd + imagePullPolicy: type: string - readOnly: - type: boolean - secretRef: + lifecycle: properties: - name: - default: "" + postStart: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + stopSignal: type: string type: object - x-kubernetes-map-type: atomic - user: - default: admin - type: string - required: - - image - - monitors - type: object - scaleIO: - properties: - fsType: - default: xfs - type: string - gateway: - type: string - protectionDomain: - type: string - readOnly: - type: boolean - secretRef: + livenessProbe: properties: - name: - default: "" - type: string + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer type: object - x-kubernetes-map-type: atomic - sslEnabled: - type: boolean - storageMode: - default: ThinProvisioned - type: string - storagePool: - type: string - system: - type: string - volumeName: + name: type: string - required: - - gateway - - secretRef - - system - type: object - secret: - properties: - defaultMode: - format: int32 - type: integer - items: + ports: items: properties: - key: + containerPort: + format: int32 + type: integer + hostIP: type: string - mode: + hostPort: format: int32 type: integer - path: + name: + type: string + protocol: + default: TCP type: string required: - - key - - path + - containerPort type: object type: array - x-kubernetes-list-type: atomic - optional: - type: boolean - secretName: - type: string - type: object - storageos: - properties: - fsType: - type: string - readOnly: - type: boolean - secretRef: + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: properties: - name: - default: "" - type: string + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer type: object - x-kubernetes-map-type: atomic - volumeName: - type: string - volumeNamespace: - type: string - type: object - vsphereVolume: - properties: - fsType: - type: string - storagePolicyID: - type: string - storagePolicyName: - type: string - volumePath: - type: string - required: - - volumePath - type: object - required: - - name - type: object - type: array - sidecars: - items: - properties: - args: - items: - type: string - type: array - x-kubernetes-list-type: atomic - command: - items: - type: string - type: array - x-kubernetes-list-type: atomic - env: - items: - properties: - name: - type: string - value: - type: string - valueFrom: + resizePolicy: + items: properties: - configMapKeyRef: + resourceName: + type: string + restartPolicy: + type: string + required: + - resourceName + - restartPolicy + type: object + type: array + x-kubernetes-list-type: atomic + resources: + properties: + claims: + items: properties: - key: - type: string name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - fieldRef: - properties: - apiVersion: type: string - fieldPath: + request: type: string required: - - fieldPath + - name type: object - x-kubernetes-map-type: atomic - resourceFieldRef: + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + restartPolicy: + type: string + restartPolicyRules: + items: + properties: + action: + type: string + exitCodes: properties: - containerName: - type: string - divisor: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - resource: + operator: type: string + values: + items: + format: int32 + type: integer + type: array + x-kubernetes-list-type: set required: - - resource + - operator type: object - x-kubernetes-map-type: atomic - secretKeyRef: - properties: - key: + required: + - action + type: object + type: array + x-kubernetes-list-type: atomic + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + capabilities: + properties: + add: + items: type: string - name: - default: "" + type: array + x-kubernetes-list-type: atomic + drop: + items: type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - envFrom: - items: - properties: - configMapRef: - properties: - name: - default: "" - type: string - optional: - type: boolean - type: object - x-kubernetes-map-type: atomic - prefix: - type: string - secretRef: - properties: - name: - default: "" - type: string - optional: - type: boolean - type: object - x-kubernetes-map-type: atomic - type: object - type: array - x-kubernetes-list-type: atomic - image: - type: string - imagePullPolicy: - type: string - lifecycle: - properties: - postStart: + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + startupProbe: properties: exec: properties: @@ -4050,6 +4020,20 @@ spec: type: array x-kubernetes-list-type: atomic type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object httpGet: properties: host: @@ -4079,14 +4063,15 @@ spec: required: - port type: object - sleep: - properties: - seconds: - format: int64 - type: integer - required: - - seconds - type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer tcpSocket: properties: host: @@ -4099,513 +4084,888 @@ spec: required: - port type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + stdin: + type: boolean + stdinOnce: + type: boolean + terminationMessagePath: + type: string + terminationMessagePolicy: + type: string + tty: + type: boolean + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + required: + - devicePath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - devicePath + x-kubernetes-list-type: map + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + recursiveReadOnly: + type: string + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - mountPath + x-kubernetes-list-type: map + workingDir: + type: string + required: + - name + type: object + type: array + volumes: + items: + properties: + awsElasticBlockStore: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID type: object - preStop: + azureDisk: properties: - exec: + cachingMode: + type: string + diskName: + type: string + diskURI: + type: string + fsType: + default: ext4 + type: string + kind: + type: string + readOnly: + default: false + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + properties: + readOnly: + type: boolean + secretName: + type: string + shareName: + type: string + required: + - secretName + - shareName + type: object + cephfs: + properties: + monitors: + items: + type: string + type: array + x-kubernetes-list-type: atomic + path: + type: string + readOnly: + type: boolean + secretFile: + type: string + secretRef: properties: - command: - items: - type: string - type: array - x-kubernetes-list-type: atomic + name: + default: "" + type: string type: object - httpGet: + x-kubernetes-map-type: atomic + user: + type: string + required: + - monitors + type: object + cinder: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: properties: - host: + name: + default: "" type: string - httpHeaders: - items: + type: object + x-kubernetes-map-type: atomic + volumeID: + type: string + required: + - volumeID + type: object + configMap: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + csi: + properties: + driver: + type: string + fsType: + type: string + nodePublishSecretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: + type: boolean + volumeAttributes: + additionalProperties: + type: string + type: object + required: + - driver + type: object + downwardAPI: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + fieldRef: properties: - name: + apiVersion: type: string - value: + fieldPath: type: string required: - - name - - value + - fieldPath type: object - type: array - x-kubernetes-list-type: atomic - path: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - scheme: - type: string - required: - - port - type: object - sleep: + x-kubernetes-map-type: atomic + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + emptyDir: + properties: + medium: + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + properties: + volumeClaimTemplate: properties: - seconds: - format: int64 - type: integer + metadata: + type: object + spec: + properties: + accessModes: + items: + type: string + type: array + x-kubernetes-list-type: atomic + dataSource: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + dataSourceRef: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + required: + - kind + - name + type: object + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + storageClassName: + type: string + volumeAttributesClassName: + type: string + volumeMode: + type: string + volumeName: + type: string + type: object required: - - seconds + - spec + type: object + type: object + fc: + properties: + fsType: + type: string + lun: + format: int32 + type: integer + readOnly: + type: boolean + targetWWNs: + items: + type: string + type: array + x-kubernetes-list-type: atomic + wwids: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + flexVolume: + properties: + driver: + type: string + fsType: + type: string + options: + additionalProperties: + type: string type: object - tcpSocket: + readOnly: + type: boolean + secretRef: properties: - host: + name: + default: "" type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - required: - - port type: object + x-kubernetes-map-type: atomic + required: + - driver type: object - type: object - livenessProbe: - properties: - exec: + flocker: properties: - command: - items: - type: string - type: array - x-kubernetes-list-type: atomic + datasetName: + type: string + datasetUUID: + type: string type: object - failureThreshold: - format: int32 - type: integer - grpc: + gcePersistentDisk: properties: - port: + fsType: + type: string + partition: format: int32 type: integer - service: - default: "" + pdName: type: string + readOnly: + type: boolean required: - - port + - pdName type: object - httpGet: + gitRepo: properties: - host: + directory: type: string - httpHeaders: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - x-kubernetes-list-type: atomic - path: + repository: type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - scheme: + revision: type: string required: - - port + - repository type: object - initialDelaySeconds: - format: int32 - type: integer - periodSeconds: - format: int32 - type: integer - successThreshold: - format: int32 - type: integer - tcpSocket: + glusterfs: properties: - host: + endpoints: type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true + path: + type: string + readOnly: + type: boolean required: - - port + - endpoints + - path type: object - terminationGracePeriodSeconds: - format: int64 - type: integer - timeoutSeconds: - format: int32 - type: integer - type: object - name: - type: string - ports: - items: - properties: - containerPort: - format: int32 - type: integer - hostIP: - type: string - hostPort: - format: int32 - type: integer - name: - type: string - protocol: - default: TCP - type: string - required: - - containerPort - type: object - type: array - x-kubernetes-list-map-keys: - - containerPort - - protocol - x-kubernetes-list-type: map - readinessProbe: - properties: - exec: + hostPath: properties: - command: - items: - type: string - type: array - x-kubernetes-list-type: atomic + path: + type: string + type: + type: string + required: + - path type: object - failureThreshold: - format: int32 - type: integer - grpc: + image: properties: - port: - format: int32 - type: integer - service: - default: "" + pullPolicy: + type: string + reference: type: string - required: - - port type: object - httpGet: + iscsi: properties: - host: + chapAuthDiscovery: + type: boolean + chapAuthSession: + type: boolean + fsType: + type: string + initiatorName: + type: string + iqn: + type: string + iscsiInterface: + default: default type: string - httpHeaders: + lun: + format: int32 + type: integer + portals: items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object + type: string type: array x-kubernetes-list-type: atomic + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + targetPortal: + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + type: string + nfs: + properties: path: type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - scheme: + readOnly: + type: boolean + server: type: string required: - - port + - path + - server type: object - initialDelaySeconds: - format: int32 - type: integer - periodSeconds: - format: int32 - type: integer - successThreshold: - format: int32 - type: integer - tcpSocket: + persistentVolumeClaim: properties: - host: + claimName: type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true + readOnly: + type: boolean required: - - port - type: object - terminationGracePeriodSeconds: - format: int64 - type: integer - timeoutSeconds: - format: int32 - type: integer - type: object - resizePolicy: - items: - properties: - resourceName: - type: string - restartPolicy: - type: string - required: - - resourceName - - restartPolicy - type: object - type: array - x-kubernetes-list-type: atomic - resources: - properties: - claims: - items: - properties: - name: - type: string - request: - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true + - claimName type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true + photonPersistentDisk: + properties: + fsType: + type: string + pdID: + type: string + required: + - pdID type: object - type: object - restartPolicy: - type: string - securityContext: - properties: - allowPrivilegeEscalation: - type: boolean - appArmorProfile: + portworxVolume: properties: - localhostProfile: + fsType: type: string - type: + readOnly: + type: boolean + volumeID: type: string required: - - type + - volumeID type: object - capabilities: + projected: properties: - add: - items: - type: string - type: array - x-kubernetes-list-type: atomic - drop: + defaultMode: + format: int32 + type: integer + sources: items: - type: string + properties: + clusterTrustBundle: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + name: + type: string + optional: + type: boolean + path: + type: string + signerName: + type: string + required: + - path + type: object + configMap: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + downwardAPI: + properties: + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podCertificate: + properties: + certificateChainPath: + type: string + credentialBundlePath: + type: string + keyPath: + type: string + keyType: + type: string + maxExpirationSeconds: + format: int32 + type: integer + signerName: + type: string + required: + - keyType + - signerName + type: object + secret: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + serviceAccountToken: + properties: + audience: + type: string + expirationSeconds: + format: int64 + type: integer + path: + type: string + required: + - path + type: object + type: object type: array x-kubernetes-list-type: atomic type: object - privileged: - type: boolean - procMount: - type: string - readOnlyRootFilesystem: - type: boolean - runAsGroup: - format: int64 - type: integer - runAsNonRoot: - type: boolean - runAsUser: - format: int64 - type: integer - seLinuxOptions: + quobyte: properties: - level: + group: type: string - role: + readOnly: + type: boolean + registry: type: string - type: + tenant: type: string user: type: string - type: object - seccompProfile: - properties: - localhostProfile: - type: string - type: + volume: type: string required: - - type + - registry + - volume type: object - windowsOptions: + rbd: properties: - gmsaCredentialSpec: + fsType: type: string - gmsaCredentialSpecName: + image: type: string - hostProcess: - type: boolean - runAsUserName: + keyring: + default: /etc/ceph/keyring type: string - type: object - type: object - startupProbe: - properties: - exec: - properties: - command: + monitors: items: type: string type: array x-kubernetes-list-type: atomic + pool: + default: rbd + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + user: + default: admin + type: string + required: + - image + - monitors type: object - failureThreshold: - format: int32 - type: integer - grpc: + scaleIO: properties: - port: - format: int32 - type: integer - service: - default: "" + fsType: + default: xfs + type: string + gateway: + type: string + protectionDomain: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + sslEnabled: + type: boolean + storageMode: + default: ThinProvisioned + type: string + storagePool: + type: string + system: + type: string + volumeName: type: string required: - - port + - gateway + - secretRef + - system type: object - httpGet: + secret: properties: - host: - type: string - httpHeaders: + defaultMode: + format: int32 + type: integer + items: items: properties: - name: + key: type: string - value: + mode: + format: int32 + type: integer + path: type: string required: - - name - - value + - key + - path type: object type: array x-kubernetes-list-type: atomic - path: + optional: + type: boolean + secretName: type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - scheme: + type: object + storageos: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + volumeName: + type: string + volumeNamespace: type: string - required: - - port type: object - initialDelaySeconds: - format: int32 - type: integer - periodSeconds: - format: int32 - type: integer - successThreshold: - format: int32 - type: integer - tcpSocket: + vsphereVolume: properties: - host: + fsType: + type: string + storagePolicyID: + type: string + storagePolicyName: + type: string + volumePath: type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true required: - - port + - volumePath type: object - terminationGracePeriodSeconds: - format: int64 - type: integer - timeoutSeconds: - format: int32 - type: integer + required: + - name type: object - stdin: - type: boolean - stdinOnce: - type: boolean - terminationMessagePath: - type: string - terminationMessagePolicy: - type: string - tty: - type: boolean - volumeDevices: - items: - properties: - devicePath: - type: string - name: - type: string - required: - - devicePath - - name - type: object - type: array - x-kubernetes-list-map-keys: - - devicePath - x-kubernetes-list-type: map - volumeMounts: - items: - properties: - mountPath: - type: string - mountPropagation: - type: string - name: - type: string - readOnly: - type: boolean - recursiveReadOnly: - type: string - subPath: - type: string - subPathExpr: - type: string - required: - - mountPath - - name - type: object - type: array - x-kubernetes-list-map-keys: - - mountPath - x-kubernetes-list-type: map - workingDir: - type: string - required: - - name - type: object - type: array + type: array + type: object + wallet: + properties: + additional: + items: + properties: + mountPath: + type: string + name: + type: string + secret: + type: string + type: object + type: array + mountPath: + type: string + secret: + type: string + type: object type: object status: properties: @@ -4645,7 +5005,7 @@ spec: - type type: object type: array - exporterConfig: + metricsConfig: type: string replicas: type: integer @@ -4655,7 +5015,7 @@ spec: type: string required: - conditions - - exporterConfig + - metricsConfig - version type: object type: object @@ -4664,8 +5024,8 @@ spec: subresources: status: {} - additionalPrinterColumns: - - jsonPath: .status.exporterConfig - name: ExporterConfig + - jsonPath: .status.metricsConfig + name: MetricsConfig type: string - jsonPath: .status.status name: Status @@ -4685,7 +5045,7 @@ spec: type: object spec: properties: - configuration: + azureConfig: properties: configMap: properties: @@ -4697,8 +5057,19 @@ spec: type: object database: properties: + azure: + properties: + vaultID: + type: string + vaultPasswordSecret: + type: string + vaultUsernameSecret: + type: string + type: object dbConnectionString: properties: + envName: + type: string key: type: string secret: @@ -4706,241 +5077,252 @@ spec: type: object dbPassword: properties: + envName: + type: string key: type: string secret: type: string - vaultOCID: - type: string - vaultSecretName: - type: string type: object dbUser: properties: + envName: + type: string key: type: string secret: type: string type: object - dbWallet: + oci: properties: - key: + vaultID: type: string - secret: + vaultPasswordSecret: type: string type: object type: object - exporter: - properties: - deployment: - properties: - args: - items: + databases: + additionalProperties: + properties: + dbConnectionString: + properties: + envName: type: string - type: array - commands: - items: + key: type: string - type: array - env: - additionalProperties: + secret: + type: string + type: object + dbPassword: + properties: + envName: + type: string + key: + type: string + secret: + type: string + type: object + dbUser: + properties: + envName: + type: string + key: type: string + secret: + type: string + type: object + type: object + type: object + deployment: + properties: + args: + items: + type: string + type: array + commands: + items: + type: string + type: array + env: + additionalProperties: + type: string + type: object + image: + type: string + labels: + additionalProperties: + type: string + type: object + podSecurityContext: + properties: + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type type: object - image: + fsGroup: + format: int64 + type: integer + fsGroupChangePolicy: type: string - labels: - additionalProperties: - type: string + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxChangePolicy: + type: string + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string type: object - podTemplate: + seccompProfile: properties: - labels: - additionalProperties: - type: string - type: object - securityContext: - properties: - appArmorProfile: - properties: - localhostProfile: - type: string - type: - type: string - required: - - type - type: object - fsGroup: - format: int64 - type: integer - fsGroupChangePolicy: - type: string - runAsGroup: - format: int64 - type: integer - runAsNonRoot: - type: boolean - runAsUser: - format: int64 - type: integer - seLinuxOptions: - properties: - level: - type: string - role: - type: string - type: - type: string - user: - type: string - type: object - seccompProfile: - properties: - localhostProfile: - type: string - type: - type: string - required: - - type - type: object - supplementalGroups: - items: - format: int64 - type: integer - type: array - x-kubernetes-list-type: atomic - supplementalGroupsPolicy: - type: string - sysctls: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - x-kubernetes-list-type: atomic - windowsOptions: - properties: - gmsaCredentialSpec: - type: string - gmsaCredentialSpecName: - type: string - hostProcess: - type: boolean - runAsUserName: - type: string - type: object - type: object + localhostProfile: + type: string + type: + type: string + required: + - type type: object - securityContext: + supplementalGroups: + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + supplementalGroupsPolicy: + type: string + sysctls: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + windowsOptions: properties: - allowPrivilegeEscalation: - type: boolean - appArmorProfile: - properties: - localhostProfile: - type: string - type: - type: string - required: - - type - type: object - capabilities: - properties: - add: - items: - type: string - type: array - x-kubernetes-list-type: atomic - drop: - items: - type: string - type: array - x-kubernetes-list-type: atomic - type: object - privileged: - type: boolean - procMount: + gmsaCredentialSpec: type: string - readOnlyRootFilesystem: - type: boolean - runAsGroup: - format: int64 - type: integer - runAsNonRoot: + gmsaCredentialSpecName: + type: string + hostProcess: type: boolean - runAsUser: - format: int64 - type: integer - seLinuxOptions: - properties: - level: - type: string - role: - type: string - type: - type: string - user: - type: string - type: object - seccompProfile: - properties: - localhostProfile: - type: string - type: - type: string - required: - - type - type: object - windowsOptions: - properties: - gmsaCredentialSpec: - type: string - gmsaCredentialSpecName: - type: string - hostProcess: - type: boolean - runAsUserName: - type: string - type: object + runAsUserName: + type: string type: object type: object - service: + podTemplate: properties: labels: additionalProperties: type: string type: object - ports: - items: - properties: - appProtocol: - type: string - name: + type: object + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + capabilities: + properties: + add: + items: type: string - nodePort: - format: int32 - type: integer - port: - format: int32 - type: integer - protocol: - default: TCP + type: array + x-kubernetes-list-type: atomic + drop: + items: type: string - targetPort: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - required: - - port - type: object - type: array + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + type: object + exporterConfig: + properties: + configMap: + properties: + key: + type: string + name: + type: string type: object + mountPath: + type: string type: object inheritLabels: items: @@ -4948,9 +5330,11 @@ spec: type: array log: properties: - filename: + destination: type: string - path: + disable: + type: boolean + filename: type: string volume: properties: @@ -4963,41 +5347,204 @@ spec: type: object type: object type: object + metrics: + properties: + configMap: + items: + properties: + key: + type: string + name: + type: string + type: object + type: array + type: object ociConfig: properties: - configMapName: - type: string - secretName: + configMap: + properties: + key: + type: string + name: + type: string + type: object + mountPath: type: string + privateKey: + properties: + secret: + type: string + type: object type: object - prometheus: + replicas: + format: int32 + type: integer + service: properties: - serviceMonitor: - properties: - endpoints: - items: + labels: + additionalProperties: + type: string + type: object + ports: + items: + properties: + appProtocol: + type: string + name: + type: string + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + default: TCP + type: string + targetPort: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + type: object + serviceMonitor: + properties: + endpoints: + items: + properties: + authorization: properties: - authorization: + credentials: properties: - credentials: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: + type: string + type: object + basicAuth: + properties: + password: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + username: + properties: + key: + type: string + name: + default: "" type: string + optional: + type: boolean + required: + - key type: object - basicAuth: + x-kubernetes-map-type: atomic + type: object + bearerTokenFile: + type: string + bearerTokenSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + enableHttp2: + type: boolean + filterRunning: + type: boolean + followRedirects: + type: boolean + honorLabels: + type: boolean + honorTimestamps: + type: boolean + interval: + pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ + type: string + metricRelabelings: + items: + properties: + action: + default: replace + enum: + - replace + - Replace + - keep + - Keep + - drop + - Drop + - hashmod + - HashMod + - labelmap + - LabelMap + - labeldrop + - LabelDrop + - labelkeep + - LabelKeep + - lowercase + - Lowercase + - uppercase + - Uppercase + - keepequal + - KeepEqual + - dropequal + - DropEqual + type: string + modulus: + format: int64 + type: integer + regex: + type: string + replacement: + type: string + separator: + type: string + sourceLabels: + items: + pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ + type: string + type: array + targetLabel: + type: string + type: object + type: array + noProxy: + type: string + oauth2: + properties: + clientId: properties: - password: + configMap: properties: key: type: string @@ -5010,7 +5557,7 @@ spec: - key type: object x-kubernetes-map-type: atomic - username: + secret: properties: key: type: string @@ -5024,9 +5571,7 @@ spec: type: object x-kubernetes-map-type: atomic type: object - bearerTokenFile: - type: string - bearerTokenSecret: + clientSecret: properties: key: type: string @@ -5039,98 +5584,15 @@ spec: - key type: object x-kubernetes-map-type: atomic - enableHttp2: - type: boolean - filterRunning: - type: boolean - followRedirects: - type: boolean - honorLabels: - type: boolean - honorTimestamps: - type: boolean - interval: - pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ - type: string - metricRelabelings: - items: - properties: - action: - default: replace - enum: - - replace - - Replace - - keep - - Keep - - drop - - Drop - - hashmod - - HashMod - - labelmap - - LabelMap - - labeldrop - - LabelDrop - - labelkeep - - LabelKeep - - lowercase - - Lowercase - - uppercase - - Uppercase - - keepequal - - KeepEqual - - dropequal - - DropEqual - type: string - modulus: - format: int64 - type: integer - regex: - type: string - replacement: - type: string - separator: - type: string - sourceLabels: - items: - pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ - type: string - type: array - targetLabel: - type: string - type: object - type: array - oauth2: - properties: - clientId: - properties: - configMap: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - clientSecret: + endpointParams: + additionalProperties: + type: string + type: object + noProxy: + type: string + proxyConnectHeader: + additionalProperties: + items: properties: key: type: string @@ -5143,211 +5605,18 @@ spec: - key type: object x-kubernetes-map-type: atomic - endpointParams: - additionalProperties: - type: string - type: object - noProxy: - type: string - proxyConnectHeader: - additionalProperties: - items: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: array - type: object - x-kubernetes-map-type: atomic - proxyFromEnvironment: - type: boolean - proxyUrl: - pattern: ^http(s)?://.+$ - type: string - scopes: - items: - type: string - type: array - tlsConfig: - properties: - ca: - properties: - configMap: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - cert: - properties: - configMap: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - insecureSkipVerify: - type: boolean - keySecret: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - maxVersion: - enum: - - TLS10 - - TLS11 - - TLS12 - - TLS13 - type: string - minVersion: - enum: - - TLS10 - - TLS11 - - TLS12 - - TLS13 - type: string - serverName: - type: string - type: object - tokenUrl: - minLength: 1 - type: string - required: - - clientId - - clientSecret - - tokenUrl - type: object - params: - additionalProperties: - items: - type: string type: array type: object - path: - type: string - port: - type: string + x-kubernetes-map-type: atomic + proxyFromEnvironment: + type: boolean proxyUrl: + pattern: ^(http|https|socks5)://.+$ type: string - relabelings: + scopes: items: - properties: - action: - default: replace - enum: - - replace - - Replace - - keep - - Keep - - drop - - Drop - - hashmod - - HashMod - - labelmap - - LabelMap - - labeldrop - - LabelDrop - - labelkeep - - LabelKeep - - lowercase - - Lowercase - - uppercase - - Uppercase - - keepequal - - KeepEqual - - dropequal - - DropEqual - type: string - modulus: - format: int64 - type: integer - regex: - type: string - replacement: - type: string - separator: - type: string - sourceLabels: - items: - pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ - type: string - type: array - targetLabel: - type: string - type: object + type: string type: array - scheme: - enum: - - http - - https - type: string - scrapeTimeout: - pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ - type: string - targetPort: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true tlsConfig: properties: ca: @@ -5379,8 +5648,6 @@ spec: type: object x-kubernetes-map-type: atomic type: object - caFile: - type: string cert: properties: configMap: @@ -5410,12 +5677,8 @@ spec: type: object x-kubernetes-map-type: atomic type: object - certFile: - type: string insecureSkipVerify: type: boolean - keyFile: - type: string keySecret: properties: key: @@ -5446,573 +5709,329 @@ spec: serverName: type: string type: object - trackTimestampsStaleness: - type: boolean + tokenUrl: + minLength: 1 + type: string + required: + - clientId + - clientSecret + - tokenUrl type: object - type: array - labels: - additionalProperties: - type: string - type: object - namespaceSelector: - properties: - any: - type: boolean - matchNames: + params: + additionalProperties: items: type: string type: array - type: object - type: object - type: object - replicas: - format: int32 - type: integer - sidecarVolumes: - items: - properties: - awsElasticBlockStore: - properties: - fsType: - type: string - partition: - format: int32 - type: integer - readOnly: - type: boolean - volumeID: - type: string - required: - - volumeID - type: object - azureDisk: - properties: - cachingMode: - type: string - diskName: - type: string - diskURI: - type: string - fsType: - default: ext4 - type: string - kind: - type: string - readOnly: - default: false - type: boolean - required: - - diskName - - diskURI - type: object - azureFile: - properties: - readOnly: - type: boolean - secretName: - type: string - shareName: - type: string - required: - - secretName - - shareName - type: object - cephfs: - properties: - monitors: - items: - type: string - type: array - x-kubernetes-list-type: atomic + type: object path: type: string - readOnly: - type: boolean - secretFile: + port: type: string - secretRef: - properties: - name: - default: "" - type: string + proxyConnectHeader: + additionalProperties: + items: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: array type: object x-kubernetes-map-type: atomic - user: - type: string - required: - - monitors - type: object - cinder: - properties: - fsType: - type: string - readOnly: + proxyFromEnvironment: type: boolean - secretRef: - properties: - name: - default: "" - type: string - type: object - x-kubernetes-map-type: atomic - volumeID: + proxyUrl: + pattern: ^(http|https|socks5)://.+$ type: string - required: - - volumeID - type: object - configMap: - properties: - defaultMode: - format: int32 - type: integer - items: + relabelings: items: properties: - key: + action: + default: replace + enum: + - replace + - Replace + - keep + - Keep + - drop + - Drop + - hashmod + - HashMod + - labelmap + - LabelMap + - labeldrop + - LabelDrop + - labelkeep + - LabelKeep + - lowercase + - Lowercase + - uppercase + - Uppercase + - keepequal + - KeepEqual + - dropequal + - DropEqual type: string - mode: - format: int32 + modulus: + format: int64 type: integer - path: + regex: type: string - required: - - key - - path - type: object - type: array - x-kubernetes-list-type: atomic - name: - default: "" - type: string - optional: - type: boolean - type: object - x-kubernetes-map-type: atomic - csi: - properties: - driver: - type: string - fsType: - type: string - nodePublishSecretRef: - properties: - name: - default: "" - type: string - type: object - x-kubernetes-map-type: atomic - readOnly: - type: boolean - volumeAttributes: - additionalProperties: - type: string - type: object - required: - - driver - type: object - downwardAPI: - properties: - defaultMode: - format: int32 - type: integer - items: - items: - properties: - fieldRef: - properties: - apiVersion: - type: string - fieldPath: - type: string - required: - - fieldPath - type: object - x-kubernetes-map-type: atomic - mode: - format: int32 - type: integer - path: + replacement: + type: string + separator: + type: string + sourceLabels: + items: + pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ + type: string + type: array + targetLabel: type: string - resourceFieldRef: - properties: - containerName: - type: string - divisor: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - resource: - type: string - required: - - resource - type: object - x-kubernetes-map-type: atomic - required: - - path type: object type: array - x-kubernetes-list-type: atomic - type: object - emptyDir: - properties: - medium: + scheme: + enum: + - http + - https type: string - sizeLimit: + scrapeTimeout: + pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ + type: string + targetPort: anyOf: - type: integer - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - ephemeral: - properties: - volumeClaimTemplate: + x-kubernetes-int-or-string: true + tlsConfig: properties: - metadata: - type: object - spec: + ca: properties: - accessModes: - items: - type: string - type: array - x-kubernetes-list-type: atomic - dataSource: + configMap: properties: - apiGroup: - type: string - kind: + key: type: string name: + default: "" type: string + optional: + type: boolean required: - - kind - - name + - key type: object x-kubernetes-map-type: atomic - dataSourceRef: + secret: properties: - apiGroup: - type: string - kind: + key: type: string name: + default: "" type: string - namespace: - type: string + optional: + type: boolean required: - - kind - - name + - key type: object - resources: + x-kubernetes-map-type: atomic + type: object + caFile: + type: string + cert: + properties: + configMap: properties: - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key type: object - selector: + x-kubernetes-map-type: atomic + secret: properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key type: object x-kubernetes-map-type: atomic - storageClassName: - type: string - volumeAttributesClassName: - type: string - volumeMode: + type: object + certFile: + type: string + insecureSkipVerify: + type: boolean + keyFile: + type: string + keySecret: + properties: + key: type: string - volumeName: + name: + default: "" type: string + optional: + type: boolean + required: + - key type: object - required: - - spec + x-kubernetes-map-type: atomic + maxVersion: + enum: + - TLS10 + - TLS11 + - TLS12 + - TLS13 + type: string + minVersion: + enum: + - TLS10 + - TLS11 + - TLS12 + - TLS13 + type: string + serverName: + type: string type: object + trackTimestampsStaleness: + type: boolean type: object - fc: - properties: - fsType: + type: array + labels: + additionalProperties: + type: string + type: object + namespaceSelector: + properties: + any: + type: boolean + matchNames: + items: type: string - lun: - format: int32 - type: integer - readOnly: - type: boolean - targetWWNs: - items: - type: string - type: array - x-kubernetes-list-type: atomic - wwids: + type: array + type: object + type: object + sidecar: + properties: + containers: + items: + properties: + args: items: type: string type: array x-kubernetes-list-type: atomic - type: object - flexVolume: - properties: - driver: - type: string - fsType: - type: string - options: - additionalProperties: - type: string - type: object - readOnly: - type: boolean - secretRef: - properties: - name: - default: "" - type: string - type: object - x-kubernetes-map-type: atomic - required: - - driver - type: object - flocker: - properties: - datasetName: - type: string - datasetUUID: - type: string - type: object - gcePersistentDisk: - properties: - fsType: - type: string - partition: - format: int32 - type: integer - pdName: - type: string - readOnly: - type: boolean - required: - - pdName - type: object - gitRepo: - properties: - directory: - type: string - repository: - type: string - revision: - type: string - required: - - repository - type: object - glusterfs: - properties: - endpoints: - type: string - path: - type: string - readOnly: - type: boolean - required: - - endpoints - - path - type: object - hostPath: - properties: - path: - type: string - type: - type: string - required: - - path - type: object - image: - properties: - pullPolicy: - type: string - reference: - type: string - type: object - iscsi: - properties: - chapAuthDiscovery: - type: boolean - chapAuthSession: - type: boolean - fsType: - type: string - initiatorName: - type: string - iqn: - type: string - iscsiInterface: - default: default - type: string - lun: - format: int32 - type: integer - portals: + command: items: type: string type: array x-kubernetes-list-type: atomic - readOnly: - type: boolean - secretRef: - properties: - name: - default: "" - type: string - type: object - x-kubernetes-map-type: atomic - targetPortal: - type: string - required: - - iqn - - lun - - targetPortal - type: object - name: - type: string - nfs: - properties: - path: - type: string - readOnly: - type: boolean - server: - type: string - required: - - path - - server - type: object - persistentVolumeClaim: - properties: - claimName: - type: string - readOnly: - type: boolean - required: - - claimName - type: object - photonPersistentDisk: - properties: - fsType: - type: string - pdID: - type: string - required: - - pdID - type: object - portworxVolume: - properties: - fsType: - type: string - readOnly: - type: boolean - volumeID: - type: string - required: - - volumeID - type: object - projected: - properties: - defaultMode: - format: int32 - type: integer - sources: + env: items: properties: - clusterTrustBundle: + name: + type: string + value: + type: string + valueFrom: properties: - labelSelector: + configMapKeyRef: properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + fileKeyRef: + properties: + key: + type: string + optional: + default: false + type: boolean + path: + type: string + volumeName: + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key type: object x-kubernetes-map-type: atomic - name: - type: string - optional: - type: boolean - path: - type: string - signerName: - type: string - required: - - path type: object - configMap: + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + envFrom: + items: + properties: + configMapRef: properties: - items: - items: - properties: - key: - type: string - mode: - format: int32 - type: integer - path: - type: string - required: - - key - - path - type: object - type: array - x-kubernetes-list-type: atomic name: default: "" type: string @@ -6020,66 +6039,10 @@ spec: type: boolean type: object x-kubernetes-map-type: atomic - downwardAPI: - properties: - items: - items: - properties: - fieldRef: - properties: - apiVersion: - type: string - fieldPath: - type: string - required: - - fieldPath - type: object - x-kubernetes-map-type: atomic - mode: - format: int32 - type: integer - path: - type: string - resourceFieldRef: - properties: - containerName: - type: string - divisor: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - resource: - type: string - required: - - resource - type: object - x-kubernetes-map-type: atomic - required: - - path - type: object - type: array - x-kubernetes-list-type: atomic - type: object - secret: + prefix: + type: string + secretRef: properties: - items: - items: - properties: - key: - type: string - mode: - format: int32 - type: integer - path: - type: string - required: - - key - - path - type: object - type: array - x-kubernetes-list-type: atomic name: default: "" type: string @@ -6087,283 +6050,246 @@ spec: type: boolean type: object x-kubernetes-map-type: atomic - serviceAccountToken: - properties: - audience: - type: string - expirationSeconds: - format: int64 - type: integer - path: - type: string - required: - - path - type: object type: object type: array x-kubernetes-list-type: atomic - type: object - quobyte: - properties: - group: - type: string - readOnly: - type: boolean - registry: - type: string - tenant: - type: string - user: - type: string - volume: - type: string - required: - - registry - - volume - type: object - rbd: - properties: - fsType: - type: string image: type: string - keyring: - default: /etc/ceph/keyring - type: string - monitors: - items: - type: string - type: array - x-kubernetes-list-type: atomic - pool: - default: rbd - type: string - readOnly: - type: boolean - secretRef: - properties: - name: - default: "" - type: string - type: object - x-kubernetes-map-type: atomic - user: - default: admin - type: string - required: - - image - - monitors - type: object - scaleIO: - properties: - fsType: - default: xfs - type: string - gateway: - type: string - protectionDomain: + imagePullPolicy: type: string - readOnly: - type: boolean - secretRef: + lifecycle: properties: - name: - default: "" + postStart: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + stopSignal: type: string type: object - x-kubernetes-map-type: atomic - sslEnabled: - type: boolean - storageMode: - default: ThinProvisioned - type: string - storagePool: - type: string - system: - type: string - volumeName: - type: string - required: - - gateway - - secretRef - - system - type: object - secret: - properties: - defaultMode: - format: int32 - type: integer - items: - items: - properties: - key: - type: string - mode: - format: int32 - type: integer - path: - type: string - required: - - key - - path - type: object - type: array - x-kubernetes-list-type: atomic - optional: - type: boolean - secretName: - type: string - type: object - storageos: - properties: - fsType: - type: string - readOnly: - type: boolean - secretRef: + livenessProbe: properties: - name: - default: "" - type: string + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer type: object - x-kubernetes-map-type: atomic - volumeName: - type: string - volumeNamespace: - type: string - type: object - vsphereVolume: - properties: - fsType: - type: string - storagePolicyID: - type: string - storagePolicyName: - type: string - volumePath: + name: type: string - required: - - volumePath - type: object - required: - - name - type: object - type: array - sidecars: - items: - properties: - args: - items: - type: string - type: array - x-kubernetes-list-type: atomic - command: - items: - type: string - type: array - x-kubernetes-list-type: atomic - env: - items: - properties: - name: - type: string - value: - type: string - valueFrom: - properties: - configMapKeyRef: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - fieldRef: - properties: - apiVersion: - type: string - fieldPath: - type: string - required: - - fieldPath - type: object - x-kubernetes-map-type: atomic - resourceFieldRef: - properties: - containerName: - type: string - divisor: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - resource: - type: string - required: - - resource - type: object - x-kubernetes-map-type: atomic - secretKeyRef: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - envFrom: - items: - properties: - configMapRef: + ports: + items: properties: - name: - default: "" + containerPort: + format: int32 + type: integer + hostIP: type: string - optional: - type: boolean - type: object - x-kubernetes-map-type: atomic - prefix: - type: string - secretRef: - properties: + hostPort: + format: int32 + type: integer name: - default: "" type: string - optional: - type: boolean + protocol: + default: TCP + type: string + required: + - containerPort type: object - x-kubernetes-map-type: atomic - type: object - type: array - x-kubernetes-list-type: atomic - image: - type: string - imagePullPolicy: - type: string - lifecycle: - properties: - postStart: + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: properties: exec: properties: @@ -6373,6 +6299,20 @@ spec: type: array x-kubernetes-list-type: atomic type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object httpGet: properties: host: @@ -6402,14 +6342,15 @@ spec: required: - port type: object - sleep: - properties: - seconds: - format: int64 - type: integer - required: - - seconds - type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer tcpSocket: properties: host: @@ -6422,8 +6363,157 @@ spec: required: - port type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + resizePolicy: + items: + properties: + resourceName: + type: string + restartPolicy: + type: string + required: + - resourceName + - restartPolicy + type: object + type: array + x-kubernetes-list-type: atomic + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + restartPolicy: + type: string + restartPolicyRules: + items: + properties: + action: + type: string + exitCodes: + properties: + operator: + type: string + values: + items: + format: int32 + type: integer + type: array + x-kubernetes-list-type: set + required: + - operator + type: object + required: + - action + type: object + type: array + x-kubernetes-list-type: atomic + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + capabilities: + properties: + add: + items: + type: string + type: array + x-kubernetes-list-type: atomic + drop: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object type: object - preStop: + startupProbe: properties: exec: properties: @@ -6433,6 +6523,20 @@ spec: type: array x-kubernetes-list-type: atomic type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object httpGet: properties: host: @@ -6462,14 +6566,15 @@ spec: required: - port type: object - sleep: - properties: - seconds: - format: int64 - type: integer - required: - - seconds - type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer tcpSocket: properties: host: @@ -6482,453 +6587,888 @@ spec: required: - port type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + stdin: + type: boolean + stdinOnce: + type: boolean + terminationMessagePath: + type: string + terminationMessagePolicy: + type: string + tty: + type: boolean + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + required: + - devicePath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - devicePath + x-kubernetes-list-type: map + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + recursiveReadOnly: + type: string + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - mountPath + x-kubernetes-list-type: map + workingDir: + type: string + required: + - name + type: object + type: array + volumes: + items: + properties: + awsElasticBlockStore: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + azureDisk: + properties: + cachingMode: + type: string + diskName: + type: string + diskURI: + type: string + fsType: + default: ext4 + type: string + kind: + type: string + readOnly: + default: false + type: boolean + required: + - diskName + - diskURI type: object - type: object - livenessProbe: - properties: - exec: + azureFile: + properties: + readOnly: + type: boolean + secretName: + type: string + shareName: + type: string + required: + - secretName + - shareName + type: object + cephfs: properties: - command: + monitors: items: type: string type: array x-kubernetes-list-type: atomic + path: + type: string + readOnly: + type: boolean + secretFile: + type: string + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + user: + type: string + required: + - monitors type: object - failureThreshold: - format: int32 - type: integer - grpc: + cinder: properties: - port: - format: int32 - type: integer - service: - default: "" + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + volumeID: type: string required: - - port + - volumeID type: object - httpGet: + configMap: properties: - host: - type: string - httpHeaders: + defaultMode: + format: int32 + type: integer + items: items: properties: - name: + key: type: string - value: + mode: + format: int32 + type: integer + path: type: string required: - - name - - value + - key + - path type: object type: array x-kubernetes-list-type: atomic - path: + name: + default: "" type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - scheme: + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + csi: + properties: + driver: type: string + fsType: + type: string + nodePublishSecretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: + type: boolean + volumeAttributes: + additionalProperties: + type: string + type: object required: - - port + - driver type: object - initialDelaySeconds: - format: int32 - type: integer - periodSeconds: - format: int32 - type: integer - successThreshold: - format: int32 - type: integer - tcpSocket: + downwardAPI: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + emptyDir: properties: - host: + medium: type: string - port: + sizeLimit: anyOf: - type: integer - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - required: - - port type: object - terminationGracePeriodSeconds: - format: int64 - type: integer - timeoutSeconds: - format: int32 - type: integer - type: object - name: - type: string - ports: - items: - properties: - containerPort: - format: int32 - type: integer - hostIP: - type: string - hostPort: - format: int32 - type: integer - name: - type: string - protocol: - default: TCP - type: string - required: - - containerPort - type: object - type: array - x-kubernetes-list-map-keys: - - containerPort - - protocol - x-kubernetes-list-type: map - readinessProbe: - properties: - exec: + ephemeral: + properties: + volumeClaimTemplate: + properties: + metadata: + type: object + spec: + properties: + accessModes: + items: + type: string + type: array + x-kubernetes-list-type: atomic + dataSource: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + dataSourceRef: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + required: + - kind + - name + type: object + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + storageClassName: + type: string + volumeAttributesClassName: + type: string + volumeMode: + type: string + volumeName: + type: string + type: object + required: + - spec + type: object + type: object + fc: properties: - command: + fsType: + type: string + lun: + format: int32 + type: integer + readOnly: + type: boolean + targetWWNs: + items: + type: string + type: array + x-kubernetes-list-type: atomic + wwids: items: type: string type: array x-kubernetes-list-type: atomic type: object - failureThreshold: - format: int32 - type: integer - grpc: + flexVolume: properties: - port: - format: int32 - type: integer - service: - default: "" + driver: + type: string + fsType: type: string + options: + additionalProperties: + type: string + type: object + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic required: - - port + - driver type: object - httpGet: + flocker: properties: - host: + datasetName: type: string - httpHeaders: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - x-kubernetes-list-type: atomic - path: + datasetUUID: type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - scheme: + type: object + gcePersistentDisk: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + pdName: type: string + readOnly: + type: boolean required: - - port + - pdName type: object - initialDelaySeconds: - format: int32 - type: integer - periodSeconds: - format: int32 - type: integer - successThreshold: - format: int32 - type: integer - tcpSocket: + gitRepo: properties: - host: + directory: + type: string + repository: + type: string + revision: type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true required: - - port - type: object - terminationGracePeriodSeconds: - format: int64 - type: integer - timeoutSeconds: - format: int32 - type: integer - type: object - resizePolicy: - items: - properties: - resourceName: - type: string - restartPolicy: - type: string - required: - - resourceName - - restartPolicy - type: object - type: array - x-kubernetes-list-type: atomic - resources: - properties: - claims: - items: - properties: - name: - type: string - request: - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true + - repository type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true + glusterfs: + properties: + endpoints: + type: string + path: + type: string + readOnly: + type: boolean + required: + - endpoints + - path type: object - type: object - restartPolicy: - type: string - securityContext: - properties: - allowPrivilegeEscalation: - type: boolean - appArmorProfile: + hostPath: properties: - localhostProfile: + path: type: string type: type: string required: - - type + - path type: object - capabilities: + image: properties: - add: - items: - type: string - type: array - x-kubernetes-list-type: atomic - drop: + pullPolicy: + type: string + reference: + type: string + type: object + iscsi: + properties: + chapAuthDiscovery: + type: boolean + chapAuthSession: + type: boolean + fsType: + type: string + initiatorName: + type: string + iqn: + type: string + iscsiInterface: + default: default + type: string + lun: + format: int32 + type: integer + portals: items: type: string type: array x-kubernetes-list-type: atomic + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + targetPortal: + type: string + required: + - iqn + - lun + - targetPortal type: object - privileged: - type: boolean - procMount: + name: type: string - readOnlyRootFilesystem: - type: boolean - runAsGroup: - format: int64 - type: integer - runAsNonRoot: - type: boolean - runAsUser: - format: int64 - type: integer - seLinuxOptions: + nfs: properties: - level: - type: string - role: + path: type: string - type: + readOnly: + type: boolean + server: type: string - user: + required: + - path + - server + type: object + persistentVolumeClaim: + properties: + claimName: type: string + readOnly: + type: boolean + required: + - claimName type: object - seccompProfile: + photonPersistentDisk: properties: - localhostProfile: + fsType: type: string - type: + pdID: type: string required: - - type + - pdID type: object - windowsOptions: + portworxVolume: properties: - gmsaCredentialSpec: + fsType: + type: string + readOnly: + type: boolean + volumeID: type: string - gmsaCredentialSpecName: + required: + - volumeID + type: object + projected: + properties: + defaultMode: + format: int32 + type: integer + sources: + items: + properties: + clusterTrustBundle: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + name: + type: string + optional: + type: boolean + path: + type: string + signerName: + type: string + required: + - path + type: object + configMap: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + downwardAPI: + properties: + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podCertificate: + properties: + certificateChainPath: + type: string + credentialBundlePath: + type: string + keyPath: + type: string + keyType: + type: string + maxExpirationSeconds: + format: int32 + type: integer + signerName: + type: string + required: + - keyType + - signerName + type: object + secret: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + serviceAccountToken: + properties: + audience: + type: string + expirationSeconds: + format: int64 + type: integer + path: + type: string + required: + - path + type: object + type: object + type: array + x-kubernetes-list-type: atomic + type: object + quobyte: + properties: + group: type: string - hostProcess: + readOnly: type: boolean - runAsUserName: + registry: + type: string + tenant: type: string + user: + type: string + volume: + type: string + required: + - registry + - volume type: object - type: object - startupProbe: - properties: - exec: + rbd: properties: - command: + fsType: + type: string + image: + type: string + keyring: + default: /etc/ceph/keyring + type: string + monitors: items: type: string type: array x-kubernetes-list-type: atomic + pool: + default: rbd + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + user: + default: admin + type: string + required: + - image + - monitors type: object - failureThreshold: - format: int32 - type: integer - grpc: + scaleIO: properties: - port: - format: int32 - type: integer - service: - default: "" + fsType: + default: xfs + type: string + gateway: + type: string + protectionDomain: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + sslEnabled: + type: boolean + storageMode: + default: ThinProvisioned + type: string + storagePool: + type: string + system: + type: string + volumeName: type: string required: - - port + - gateway + - secretRef + - system type: object - httpGet: + secret: properties: - host: - type: string - httpHeaders: + defaultMode: + format: int32 + type: integer + items: items: properties: - name: + key: type: string - value: + mode: + format: int32 + type: integer + path: type: string required: - - name - - value + - key + - path type: object type: array x-kubernetes-list-type: atomic - path: + optional: + type: boolean + secretName: type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - scheme: + type: object + storageos: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + volumeName: + type: string + volumeNamespace: type: string - required: - - port type: object - initialDelaySeconds: - format: int32 - type: integer - periodSeconds: - format: int32 - type: integer - successThreshold: - format: int32 - type: integer - tcpSocket: + vsphereVolume: properties: - host: + fsType: + type: string + storagePolicyID: + type: string + storagePolicyName: + type: string + volumePath: type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true required: - - port + - volumePath type: object - terminationGracePeriodSeconds: - format: int64 - type: integer - timeoutSeconds: - format: int32 - type: integer + required: + - name type: object - stdin: - type: boolean - stdinOnce: - type: boolean - terminationMessagePath: - type: string - terminationMessagePolicy: - type: string - tty: - type: boolean - volumeDevices: - items: - properties: - devicePath: - type: string - name: - type: string - required: - - devicePath - - name - type: object - type: array - x-kubernetes-list-map-keys: - - devicePath - x-kubernetes-list-type: map - volumeMounts: - items: - properties: - mountPath: - type: string - mountPropagation: - type: string - name: - type: string - readOnly: - type: boolean - recursiveReadOnly: - type: string - subPath: - type: string - subPathExpr: - type: string - required: - - mountPath - - name - type: object - type: array - x-kubernetes-list-map-keys: - - mountPath - x-kubernetes-list-type: map - workingDir: - type: string - required: - - name - type: object - type: array + type: array + type: object + wallet: + properties: + additional: + items: + properties: + mountPath: + type: string + name: + type: string + secret: + type: string + type: object + type: array + mountPath: + type: string + secret: + type: string + type: object type: object status: properties: @@ -6968,7 +7508,7 @@ spec: - type type: object type: array - exporterConfig: + metricsConfig: type: string replicas: type: integer @@ -6978,7 +7518,7 @@ spec: type: string required: - conditions - - exporterConfig + - metricsConfig - version type: object type: object diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 726521b0..59f9af93 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -11,8 +11,6 @@ resources: - bases/database.oracle.com_autonomousdatabaserestores.yaml - bases/database.oracle.com_singleinstancedatabases.yaml - bases/database.oracle.com_shardingdatabases.yaml -- bases/database.oracle.com_pdbs.yaml -- bases/database.oracle.com_cdbs.yaml - bases/database.oracle.com_oraclerestdataservices.yaml - bases/database.oracle.com_autonomouscontainerdatabases.yaml - bases/database.oracle.com_dbcssystems.yaml @@ -21,51 +19,62 @@ resources: - bases/database.oracle.com_lrests.yaml - bases/database.oracle.com_lrpdbs.yaml - bases/database.oracle.com_ordssrvs.yaml +- bases/database.oracle.com_oraclerestarts.yaml # +kubebuilder:scaffold:crdkustomizeresource -patchesStrategicMerge: +patches: + # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. # patches here are for enabling the conversion webhook for each CRD #- patches/webhook_in_provshards.yaml #- patches/webhook_in_singleinstancedatabases.yaml #- patches/webhook_in_shardingdatabases.yaml -#- patches/webhook_in_pdbs.yaml -#- patches/webhook_in_cdbs.yaml #- patches/webhook_in_oraclerestdataservices.yaml #- patches/webhook_in_dbcssystems.yaml #- patches/webhook_in_dataguardbrokers.yaml #- patches/webhook_in_databaseobservers.yaml -- patches/webhook_in_autonomousdatabases.yaml -- patches/webhook_in_autonomousdatabasebackups.yaml -- patches/webhook_in_autonomousdatabaserestores.yaml -- patches/webhook_in_autonomouscontainerdatabases.yaml +- path: patches/webhook_in_autonomousdatabases.yaml +- path: patches/webhook_in_autonomousdatabasebackups.yaml +- path: patches/webhook_in_autonomousdatabaserestores.yaml +- path: patches/webhook_in_autonomouscontainerdatabases.yaml #- patches/webhook_in_lrests.yaml #- patches/webhook_in_lrpdbs.yaml #- patches/webhook_in_ordssrvs.yaml # +kubebuilder:scaffold:crdkustomizewebhookpatch -# [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix. +# [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. # patches here are for enabling the CA injection for each CRD #- patches/cainjection_in_provshards.yaml -- patches/cainjection_in_singleinstancedatabases.yaml -- patches/cainjection_in_shardingdatabases.yaml -- patches/cainjection_in_pdbs.yaml -- patches/cainjection_in_cdbs.yaml #- patches/cainjection_in_oraclerestdataservices.yaml -#- patches/cainjection_in_autonomouscontainerdatabases.yaml -- patches/cainjection_in_dbcssystems.yaml #- patches/cainjection_in_dataguardbrokers.yaml #- patches/cainjection_in_databaseobservers.yaml -- patches/cainjection_in_autonomousdatabases.yaml -- patches/cainjection_in_autonomousdatabasebackups.yaml -- patches/cainjection_in_autonomousdatabaserestores.yaml -- patches/cainjection_in_autonomouscontainerdatabases.yaml #- patches/cainjection_in_lrests.yaml #- patches/cainjection_in_lrpdbs.yaml #- patches/cainjection_in_ordssrvs.yaml -#- patches/cainjection_in_singleinstancedatabases.yaml +- path: patches/cainjection_in_singleinstancedatabases.yaml +- path: patches/cainjection_in_shardingdatabases.yaml +- path: patches/cainjection_in_dbcssystems.yaml +- path: patches/cainjection_in_autonomousdatabases.yaml +- path: patches/cainjection_in_autonomousdatabasebackups.yaml +- path: patches/cainjection_in_autonomousdatabaserestores.yaml +- path: patches/cainjection_in_autonomouscontainerdatabases.yaml + # +kubebuilder:scaffold:crdkustomizecainjectionpatch # the following config is for teaching kustomize how to do kustomization for CRDs. configurations: - kustomizeconfig.yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +patches: +- path: patches/webhook_in_autonomousdatabases.yaml +- path: patches/webhook_in_autonomousdatabasebackups.yaml +- path: patches/webhook_in_autonomousdatabaserestores.yaml +- path: patches/webhook_in_autonomouscontainerdatabases.yaml +- path: patches/cainjection_in_singleinstancedatabases.yaml +- path: patches/cainjection_in_shardingdatabases.yaml +- path: patches/cainjection_in_dbcssystems.yaml +- path: patches/cainjection_in_autonomousdatabases.yaml +- path: patches/cainjection_in_autonomousdatabasebackups.yaml +- path: patches/cainjection_in_autonomousdatabaserestores.yaml +- path: patches/cainjection_in_autonomouscontainerdatabases.yaml diff --git a/config/crd/patches/cainjection_in_autonomouscontainerdatabases.yaml b/config/crd/patches/cainjection_in_autonomouscontainerdatabases.yaml index 734407bc..45edfbf0 100644 --- a/config/crd/patches/cainjection_in_autonomouscontainerdatabases.yaml +++ b/config/crd/patches/cainjection_in_autonomouscontainerdatabases.yaml @@ -4,5 +4,5 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE_PLACEHOLDER/CERTIFICATE_NAME_PLACEHOLDER name: autonomouscontainerdatabases.database.oracle.com diff --git a/config/crd/patches/cainjection_in_autonomousdatabasebackups.yaml b/config/crd/patches/cainjection_in_autonomousdatabasebackups.yaml index 9468569d..65af0df2 100644 --- a/config/crd/patches/cainjection_in_autonomousdatabasebackups.yaml +++ b/config/crd/patches/cainjection_in_autonomousdatabasebackups.yaml @@ -4,5 +4,5 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE_PLACEHOLDER/CERTIFICATE_NAME_PLACEHOLDER name: autonomousdatabasebackups.database.oracle.com diff --git a/config/crd/patches/cainjection_in_autonomousdatabaserestores.yaml b/config/crd/patches/cainjection_in_autonomousdatabaserestores.yaml index cfc941f8..bf846773 100644 --- a/config/crd/patches/cainjection_in_autonomousdatabaserestores.yaml +++ b/config/crd/patches/cainjection_in_autonomousdatabaserestores.yaml @@ -4,5 +4,5 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE_PLACEHOLDER/CERTIFICATE_NAME_PLACEHOLDER name: autonomousdatabaserestores.database.oracle.com diff --git a/config/crd/patches/cainjection_in_autonomousdatabases.yaml b/config/crd/patches/cainjection_in_autonomousdatabases.yaml index 05842d0b..45c3325d 100644 --- a/config/crd/patches/cainjection_in_autonomousdatabases.yaml +++ b/config/crd/patches/cainjection_in_autonomousdatabases.yaml @@ -8,5 +8,5 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE_PLACEHOLDER/CERTIFICATE_NAME_PLACEHOLDER name: autonomousdatabases.database.oracle.com diff --git a/config/crd/patches/cainjection_in_cdbs.yaml b/config/crd/patches/cainjection_in_cdbs.yaml index 8cb50343..97b3a780 100644 --- a/config/crd/patches/cainjection_in_cdbs.yaml +++ b/config/crd/patches/cainjection_in_cdbs.yaml @@ -4,5 +4,5 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE_PLACEHOLDER/CERTIFICATE_NAME_PLACEHOLDER name: cdbs.database.oracle.com diff --git a/config/crd/patches/cainjection_in_dbcssystems.yaml b/config/crd/patches/cainjection_in_dbcssystems.yaml index 1c14e1fd..31bdd9e1 100644 --- a/config/crd/patches/cainjection_in_dbcssystems.yaml +++ b/config/crd/patches/cainjection_in_dbcssystems.yaml @@ -4,5 +4,5 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE_PLACEHOLDER/CERTIFICATE_NAME_PLACEHOLDER name: dbcssystems.database.oracle.com diff --git a/config/crd/patches/cainjection_in_pdbs.yaml b/config/crd/patches/cainjection_in_pdbs.yaml index 8c41010a..90ee25bb 100644 --- a/config/crd/patches/cainjection_in_pdbs.yaml +++ b/config/crd/patches/cainjection_in_pdbs.yaml @@ -4,5 +4,5 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE_PLACEHOLDER/CERTIFICATE_NAME_PLACEHOLDER name: pdbs.database.oracle.com diff --git a/config/crd/patches/cainjection_in_shardingdatabases.yaml b/config/crd/patches/cainjection_in_shardingdatabases.yaml index 45d35376..f7220bc6 100644 --- a/config/crd/patches/cainjection_in_shardingdatabases.yaml +++ b/config/crd/patches/cainjection_in_shardingdatabases.yaml @@ -8,5 +8,5 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE_PLACEHOLDER/CERTIFICATE_NAME_PLACEHOLDER name: shardingdatabases.database.oracle.com diff --git a/config/crd/patches/cainjection_in_singleinstancedatabases.yaml b/config/crd/patches/cainjection_in_singleinstancedatabases.yaml index 11114339..65d1336e 100644 --- a/config/crd/patches/cainjection_in_singleinstancedatabases.yaml +++ b/config/crd/patches/cainjection_in_singleinstancedatabases.yaml @@ -8,5 +8,5 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE_PLACEHOLDER/CERTIFICATE_NAME_PLACEHOLDER name: singleinstancedatabases.database.oracle.com diff --git a/config/database.oracle.com_autonomousdatabases.yaml b/config/database.oracle.com_autonomousdatabases.yaml deleted file mode 100644 index f77407f3..00000000 --- a/config/database.oracle.com_autonomousdatabases.yaml +++ /dev/null @@ -1,324 +0,0 @@ - ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.6.1 - creationTimestamp: null - name: autonomousdatabases.database.oracle.com -spec: - group: database.oracle.com - names: - kind: AutonomousDatabase - listKind: AutonomousDatabaseList - plural: autonomousdatabases - shortNames: - - adb - - adbs - singular: autonomousdatabase - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.details.displayName - name: Display Name - type: string - - jsonPath: .spec.details.dbName - name: Db Name - type: string - - jsonPath: .status.lifecycleState - name: State - type: string - - jsonPath: .spec.details.isDedicated - name: Dedicated - type: string - - jsonPath: .spec.details.cpuCoreCount - name: OCPUs - type: integer - - jsonPath: .spec.details.dataStorageSizeInTBs - name: Storage (TB) - type: integer - - jsonPath: .spec.details.dbWorkload - name: Workload Type - type: string - - jsonPath: .status.timeCreated - name: Created - type: string - name: v1alpha1 - schema: - openAPIV3Schema: - description: AutonomousDatabase is the Schema for the autonomousdatabases - API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: 'AutonomousDatabaseSpec defines the desired state of AutonomousDatabase - Important: Run "make" to regenerate code after modifying this file' - properties: - details: - description: AutonomousDatabaseDetails defines the detail information - of AutonomousDatabase, corresponding to oci-go-sdk/database/AutonomousDatabase - properties: - adminPassword: - properties: - k8sSecret: - description: "*********************** *\tSecret specs ***********************" - properties: - name: - type: string - type: object - ociSecret: - properties: - ocid: - type: string - type: object - type: object - autonomousContainerDatabase: - description: ACDSpec defines the spec of the target for backup/restore - runs. The name could be the name of an AutonomousDatabase or - an AutonomousDatabaseBackup - properties: - k8sACD: - description: "*********************** *\tACD specs ***********************" - properties: - name: - type: string - type: object - ociACD: - properties: - ocid: - type: string - type: object - type: object - autonomousDatabaseOCID: - type: string - compartmentOCID: - type: string - cpuCoreCount: - type: integer - dataStorageSizeInTBs: - type: integer - dbName: - type: string - dbVersion: - type: string - dbWorkload: - description: 'AutonomousDatabaseDbWorkloadEnum Enum with underlying - type: string' - enum: - - OLTP - - DW - - AJD - - APEX - type: string - displayName: - type: string - freeformTags: - additionalProperties: - type: string - type: object - isAutoScalingEnabled: - type: boolean - isDedicated: - type: boolean - licenseModel: - description: 'AutonomousDatabaseLicenseModelEnum Enum with underlying - type: string' - enum: - - LICENSE_INCLUDED - - BRING_YOUR_OWN_LICENSE - type: string - lifecycleState: - description: 'AutonomousDatabaseLifecycleStateEnum Enum with underlying - type: string' - type: string - networkAccess: - properties: - accessControlList: - items: - type: string - type: array - accessType: - enum: - - "" - - PUBLIC - - RESTRICTED - - PRIVATE - type: string - isAccessControlEnabled: - type: boolean - isMTLSConnectionRequired: - type: boolean - privateEndpoint: - properties: - hostnamePrefix: - type: string - nsgOCIDs: - items: - type: string - type: array - subnetOCID: - type: string - type: object - type: object - wallet: - properties: - name: - type: string - password: - properties: - k8sSecret: - description: "*********************** *\tSecret specs - ***********************" - properties: - name: - type: string - type: object - ociSecret: - properties: - ocid: - type: string - type: object - type: object - type: object - type: object - hardLink: - default: false - type: boolean - ociConfig: - description: "*********************** *\tOCI config ***********************" - properties: - configMapName: - type: string - secretName: - type: string - type: object - required: - - details - type: object - status: - description: AutonomousDatabaseStatus defines the observed state of AutonomousDatabase - properties: - allConnectionStrings: - items: - properties: - connectionStrings: - items: - properties: - connectionString: - type: string - tnsName: - type: string - type: object - type: array - tlsAuthentication: - type: string - required: - - connectionStrings - type: object - type: array - conditions: - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n \ttype FooStatus struct{ \t // Represents the observations - of a foo's current state. \t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\" \t // - +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map - \t // +listMapKey=type \t Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields - \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - lifecycleState: - description: 'INSERT ADDITIONAL STATUS FIELD - define observed state - of cluster Important: Run "make" to regenerate code after modifying - this file' - type: string - timeCreated: - type: string - walletExpiringDate: - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/config/database.oracle.com_dataguardbrokers.yaml b/config/database.oracle.com_dataguardbrokers.yaml deleted file mode 100644 index f19a3e22..00000000 --- a/config/database.oracle.com_dataguardbrokers.yaml +++ /dev/null @@ -1,134 +0,0 @@ - ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.6.1 - creationTimestamp: null - name: dataguardbrokers.database.oracle.com -spec: - group: database.oracle.com - names: - kind: DataguardBroker - listKind: DataguardBrokerList - plural: dataguardbrokers - singular: dataguardbroker - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .status.primaryDatabase - name: Primary - type: string - - jsonPath: .status.standbyDatabases - name: Standbys - type: string - - jsonPath: .spec.protectionMode - name: Protection Mode - type: string - - jsonPath: .status.clusterConnectString - name: Cluster Connect Str - priority: 1 - type: string - - jsonPath: .status.externalConnectString - name: Connect Str - type: string - - jsonPath: .spec.primaryDatabaseRef - name: Primary Database - priority: 1 - type: string - - jsonPath: .status.status - name: Status - type: string - name: v1alpha1 - schema: - openAPIV3Schema: - description: DataguardBroker is the Schema for the dataguardbrokers API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: DataguardBrokerSpec defines the desired state of DataguardBroker - properties: - fastStartFailOver: - properties: - enable: - type: boolean - strategy: - items: - description: FSFO strategy - properties: - sourceDatabaseRef: - type: string - targetDatabaseRefs: - type: string - type: object - type: array - type: object - loadBalancer: - type: boolean - nodeSelector: - additionalProperties: - type: string - type: object - primaryDatabaseRef: - type: string - protectionMode: - enum: - - MaxPerformance - - MaxAvailability - type: string - serviceAnnotations: - additionalProperties: - type: string - type: object - setAsPrimaryDatabase: - type: string - standbyDatabaseRefs: - items: - type: string - type: array - required: - - primaryDatabaseRef - - protectionMode - - standbyDatabaseRefs - type: object - status: - description: DataguardBrokerStatus defines the observed state of DataguardBroker - properties: - clusterConnectString: - type: string - externalConnectString: - type: string - primaryDatabase: - type: string - primaryDatabaseRef: - type: string - protectionMode: - type: string - standbyDatabases: - type: string - status: - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/config/database.oracle.com_shardingdatabases.yaml b/config/database.oracle.com_shardingdatabases.yaml deleted file mode 100644 index bb9bbd38..00000000 --- a/config/database.oracle.com_shardingdatabases.yaml +++ /dev/null @@ -1,688 +0,0 @@ - ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.16.5 - creationTimestamp: null - name: shardingdatabases.database.oracle.com -spec: - group: database.oracle.com - names: - kind: ShardingDatabase - listKind: ShardingDatabaseList - plural: shardingdatabases - singular: shardingdatabase - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .status.gsm.state - name: Gsm State - type: string - - jsonPath: .status.gsm.services - name: Services - type: string - - jsonPath: .status.gsm.shards - name: shards - priority: 1 - type: string - name: v4 - schema: - openAPIV3Schema: - description: ShardingDatabase is the Schema for the shardingdatabases API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: ShardingDatabaseSpec defines the desired state of ShardingDatabase - properties: - InvitedNodeSubnet: - type: string - catalog: - items: - description: CatalogSpec defines the desired state of CatalogSpec - properties: - envVars: - items: - description: EnvironmentVariable represents a named variable - accessible for containers. - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - imagePullPolicy: - description: PullPolicy describes a policy for if/when to pull - a container image - type: string - isDelete: - type: string - label: - type: string - name: - type: string - nodeSelector: - additionalProperties: - type: string - type: object - pvAnnotations: - additionalProperties: - type: string - type: object - pvMatchLabels: - additionalProperties: - type: string - type: object - pvcName: - type: string - resources: - description: ResourceRequirements describes the compute resource - requirements. - properties: - claims: - description: "Claims lists the names of resources, defined - in spec.resourceClaims, that are used by this container. - \n This is an alpha field and requires enabling the DynamicResourceAllocation - feature gate. \n This field is immutable. It can only - be set for containers." - items: - description: ResourceClaim references one entry in PodSpec.ResourceClaims. - properties: - name: - description: Name must match the name of one entry - in pod.spec.resourceClaims of the Pod where this - field is used. It makes that resource available - inside a container. - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests - cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - type: object - storageSizeInGb: - format: int32 - type: integer - required: - - name - type: object - type: array - dbEdition: - type: string - dbImage: - type: string - dbImagePullSecret: - type: string - dbSecret: - description: Secret Details - properties: - encryptionType: - type: string - keyFileMountLocation: - type: string - keyFileName: - type: string - keySecretName: - type: string - name: - type: string - nsConfigMap: - type: string - nsSecret: - type: string - pwdFileMountLocation: - type: string - pwdFileName: - type: string - required: - - name - - pwdFileName - type: object - fssStorageClass: - type: string - gsm: - items: - description: GsmSpec defines the desired state of GsmSpec - properties: - directorName: - type: string - envVars: - description: Replicas int32 `json:"replicas,omitempty"` // - Gsm Replicas. If you set OraGsmPvcName then it is set default - to 1. - items: - description: EnvironmentVariable represents a named variable - accessible for containers. - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - imagePullPolicy: - description: PullPolicy describes a policy for if/when to pull - a container image - type: string - isDelete: - type: string - label: - type: string - name: - type: string - nodeSelector: - additionalProperties: - type: string - type: object - pvMatchLabels: - additionalProperties: - type: string - type: object - pvcName: - type: string - region: - type: string - resources: - description: ResourceRequirements describes the compute resource - requirements. - properties: - claims: - description: "Claims lists the names of resources, defined - in spec.resourceClaims, that are used by this container. - \n This is an alpha field and requires enabling the DynamicResourceAllocation - feature gate. \n This field is immutable. It can only - be set for containers." - items: - description: ResourceClaim references one entry in PodSpec.ResourceClaims. - properties: - name: - description: Name must match the name of one entry - in pod.spec.resourceClaims of the Pod where this - field is used. It makes that resource available - inside a container. - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests - cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - type: object - storageSizeInGb: - format: int32 - type: integer - required: - - name - type: object - type: array - gsmDevMode: - type: string - gsmImage: - type: string - gsmImagePullSecret: - type: string - gsmService: - items: - description: Service Definition - properties: - available: - type: string - clbGoal: - type: string - commitOutcome: - type: string - drainTimeout: - type: string - dtp: - type: string - edition: - type: string - failoverDelay: - type: string - failoverMethod: - type: string - failoverPrimary: - type: string - failoverRestore: - type: string - failoverRetry: - type: string - failoverType: - type: string - gdsPool: - type: string - lag: - type: integer - locality: - type: string - name: - type: string - notification: - type: string - pdbName: - type: string - policy: - type: string - preferred: - type: string - prferredAll: - type: string - regionFailover: - type: string - retention: - type: string - role: - type: string - sessionState: - type: string - sqlTransactionProfile: - type: string - stopOption: - type: string - tableFamily: - type: string - tfaPolicy: - type: string - required: - - name - type: object - type: array - gsmShardGroup: - items: - properties: - deployAs: - type: string - name: - type: string - region: - type: string - required: - - name - type: object - type: array - gsmShardSpace: - items: - description: ShardSpace Specs - properties: - chunks: - type: integer - name: - type: string - protectionMode: - type: string - shardGroup: - type: string - required: - - name - type: object - type: array - invitedNodeSubnetFlag: - type: string - isClone: - type: boolean - isDataGuard: - type: boolean - isDebug: - type: boolean - isDeleteOraPvc: - type: boolean - isDownloadScripts: - type: boolean - isExternalSvc: - type: boolean - isTdeWallet: - type: string - liveinessCheckPeriod: - type: integer - namespace: - type: string - portMappings: - items: - description: PortMapping is a specification of port mapping for - an application deployment. - properties: - port: - format: int32 - type: integer - protocol: - default: TCP - type: string - targetPort: - format: int32 - type: integer - required: - - port - - protocol - - targetPort - type: object - type: array - readinessCheckPeriod: - type: integer - replicationType: - type: string - scriptsLocation: - type: string - shard: - description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster - Important: Run "make" to regenerate code after modifying this file' - items: - description: ShardSpec is a specification of Shards for an application - deployment. - properties: - deployAs: - type: string - envVars: - items: - description: EnvironmentVariable represents a named variable - accessible for containers. - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - imagePullPolicy: - description: PullPolicy describes a policy for if/when to pull - a container image - type: string - isDelete: - enum: - - enable - - disable - - failed - - force - type: string - label: - type: string - name: - type: string - nodeSelector: - additionalProperties: - type: string - type: object - pvAnnotations: - additionalProperties: - type: string - type: object - pvMatchLabels: - additionalProperties: - type: string - type: object - pvcName: - type: string - resources: - description: ResourceRequirements describes the compute resource - requirements. - properties: - claims: - description: "Claims lists the names of resources, defined - in spec.resourceClaims, that are used by this container. - \n This is an alpha field and requires enabling the DynamicResourceAllocation - feature gate. \n This field is immutable. It can only - be set for containers." - items: - description: ResourceClaim references one entry in PodSpec.ResourceClaims. - properties: - name: - description: Name must match the name of one entry - in pod.spec.resourceClaims of the Pod where this - field is used. It makes that resource available - inside a container. - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: 'Requests describes the minimum amount of compute - resources required. If Requests is omitted for a container, - it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests - cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' - type: object - type: object - shardGroup: - type: string - shardRegion: - type: string - shardSpace: - type: string - storageSizeInGb: - format: int32 - type: integer - required: - - name - type: object - type: array - shardBuddyRegion: - type: string - shardConfigName: - type: string - shardRegion: - items: - type: string - type: array - shardingType: - type: string - stagePvcName: - type: string - storageClass: - type: string - tdeWalletPvc: - type: string - tdeWalletPvcMountLocation: - type: string - required: - - catalog - - dbImage - - gsm - - gsmImage - - shard - type: object - status: - description: To understand Metav1.Condition, please refer the link https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1 - ShardingDatabaseStatus defines the observed state of ShardingDatabase - properties: - catalogs: - additionalProperties: - type: string - type: object - conditions: - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n \ttype FooStatus struct{ \t // Represents the observations - of a foo's current state. \t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\" \t // - +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map - \t // +listMapKey=type \t Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields - \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - gsm: - properties: - details: - additionalProperties: - type: string - type: object - externalConnectStr: - type: string - internalConnectStr: - type: string - services: - type: string - shards: - additionalProperties: - type: string - type: object - state: - type: string - type: object - shards: - additionalProperties: - type: string - type: object - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/config/database.oracle.com_singleinstancedatabases.yaml b/config/database.oracle.com_singleinstancedatabases.yaml deleted file mode 100644 index 1c011e17..00000000 --- a/config/database.oracle.com_singleinstancedatabases.yaml +++ /dev/null @@ -1,421 +0,0 @@ - ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.6.1 - creationTimestamp: null - name: singleinstancedatabases.database.oracle.com -spec: - group: database.oracle.com - names: - kind: SingleInstanceDatabase - listKind: SingleInstanceDatabaseList - plural: singleinstancedatabases - singular: singleinstancedatabase - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .status.edition - name: Edition - type: string - - jsonPath: .status.sid - name: Sid - priority: 1 - type: string - - jsonPath: .status.status - name: Status - type: string - - jsonPath: .status.role - name: Role - type: string - - jsonPath: .status.releaseUpdate - name: Version - type: string - - jsonPath: .status.connectString - name: Connect Str - type: string - - jsonPath: .status.pdbConnectString - name: Pdb Connect Str - priority: 1 - type: string - - jsonPath: .status.tcpsConnectString - name: TCPS Connect Str - type: string - - jsonPath: .status.tcpsPdbConnectString - name: TCPS Pdb Connect Str - priority: 1 - type: string - - jsonPath: .status.oemExpressUrl - name: Oem Express Url - type: string - name: v1alpha1 - schema: - openAPIV3Schema: - description: SingleInstanceDatabase is the Schema for the singleinstancedatabases - API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: SingleInstanceDatabaseSpec defines the desired state of SingleInstanceDatabase - properties: - adminPassword: - description: SingleInsatnceAdminPassword defines the secret containing - Admin Password mapped to secretKey for Database - properties: - keepSecret: - type: boolean - secretKey: - default: oracle_pwd - type: string - secretName: - type: string - required: - - secretName - type: object - archiveLog: - type: boolean - charset: - type: string - createAs: - enum: - - primary - - standby - - clone - type: string - dgBrokerConfigured: - type: boolean - edition: - enum: - - standard - - enterprise - - express - - free - type: string - enableTCPS: - type: boolean - flashBack: - type: boolean - forceLog: - type: boolean - image: - description: SingleInstanceDatabaseImage defines the Image source - and pullSecrets for POD - properties: - prebuiltDB: - type: boolean - pullFrom: - type: string - pullSecrets: - type: string - version: - type: string - required: - - pullFrom - type: object - initParams: - description: SingleInstanceDatabaseInitParams defines the Init Parameters - properties: - cpuCount: - type: integer - pgaAggregateTarget: - type: integer - processes: - type: integer - sgaTarget: - type: integer - type: object - listenerPort: - type: integer - loadBalancer: - type: boolean - nodeSelector: - additionalProperties: - type: string - type: object - pdbName: - type: string - persistence: - description: SingleInstanceDatabasePersistence defines the storage - size and class for PVC - properties: - accessMode: - enum: - - ReadWriteOnce - - ReadWriteMany - type: string - datafilesVolumeName: - type: string - scriptsVolumeName: - type: string - setWritePermissions: - type: boolean - size: - type: string - storageClass: - type: string - volumeClaimAnnotation: - type: string - type: object - primaryDatabaseRef: - type: string - readinessCheckPeriod: - type: integer - replicas: - type: integer - resources: - properties: - limits: - properties: - cpu: - type: string - memory: - type: string - type: object - requests: - properties: - cpu: - type: string - memory: - type: string - type: object - type: object - serviceAccountName: - type: string - serviceAnnotations: - additionalProperties: - type: string - type: object - sid: - description: SID must be alphanumeric (no special characters, only - a-z, A-Z, 0-9), and no longer than 12 characters. - maxLength: 12 - pattern: ^[a-zA-Z0-9]+$ - type: string - tcpsCertRenewInterval: - type: string - tcpsListenerPort: - type: integer - tcpsTlsSecret: - type: string - required: - - image - type: object - status: - description: SingleInstanceDatabaseStatus defines the observed state of - SingleInstanceDatabase - properties: - apexInstalled: - type: boolean - archiveLog: - type: string - certCreationTimestamp: - type: string - certRenewInterval: - type: string - charset: - type: string - clientWalletLoc: - type: string - clusterConnectString: - type: string - conditions: - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n \ttype FooStatus struct{ \t // Represents the observations - of a foo's current state. \t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\" \t // - +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map - \t // +listMapKey=type \t Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields - \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - connectString: - type: string - createdAs: - type: string - datafilesCreated: - default: "false" - type: string - datafilesPatched: - default: "false" - type: string - dgBrokerConfigured: - type: boolean - edition: - type: string - flashBack: - type: string - forceLog: - type: string - initParams: - description: SingleInstanceDatabaseInitParams defines the Init Parameters - properties: - cpuCount: - type: integer - pgaAggregateTarget: - type: integer - processes: - type: integer - sgaTarget: - type: integer - type: object - initPgaSize: - type: integer - initSgaSize: - type: integer - isTcpsEnabled: - default: false - type: boolean - nodes: - items: - type: string - type: array - oemExpressUrl: - type: string - ordsReference: - type: string - pdbConnectString: - type: string - pdbName: - type: string - persistence: - description: SingleInstanceDatabasePersistence defines the storage - size and class for PVC - properties: - accessMode: - enum: - - ReadWriteOnce - - ReadWriteMany - type: string - datafilesVolumeName: - type: string - scriptsVolumeName: - type: string - setWritePermissions: - type: boolean - size: - type: string - storageClass: - type: string - volumeClaimAnnotation: - type: string - type: object - prebuiltDB: - type: boolean - primaryDatabase: - type: string - releaseUpdate: - type: string - replicas: - type: integer - role: - type: string - sid: - type: string - standbyDatabases: - additionalProperties: - type: string - type: object - status: - type: string - tcpsConnectString: - type: string - tcpsPdbConnectString: - type: string - tcpsTlsSecret: - default: "" - type: string - required: - - isTcpsEnabled - - persistence - - tcpsTlsSecret - type: object - type: object - served: true - storage: true - subresources: - scale: - specReplicasPath: .spec.replicas - statusReplicasPath: .status.replicas - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml index d41001b0..f2bd4689 100644 --- a/config/default/kustomization.yaml +++ b/config/default/kustomization.yaml @@ -17,59 +17,125 @@ namePrefix: oracle-database-operator- #commonLabels: # someName: someValue -bases: +resources: - ../crd - ../rbac - ../manager -# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in -# crd/kustomization.yaml - ../webhook -# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. - ../certmanager # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. #- ../prometheus -patchesStrategicMerge: # Protect the /metrics endpoint by putting it behind auth. # If you want your controller-manager to expose the /metrics # endpoint w/o any authn/z, please comment the following line. #- manager_auth_proxy_patch.yaml +patches: # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in # crd/kustomization.yaml -- manager_webhook_patch.yaml - +- path: manager_webhook_patch.yaml # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. # 'CERTMANAGER' needs to be enabled to use ca injection -- webhookcainjection_patch.yaml +- path: webhookcainjection_patch.yaml -# the following config is for teaching kustomize how to do var substitution -vars: # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. -- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR - objref: - kind: Certificate - group: cert-manager.io - version: v1 - name: serving-cert # this name should match the one in certificate.yaml - fieldref: - fieldpath: metadata.namespace -- name: CERTIFICATE_NAME - objref: - kind: Certificate - group: cert-manager.io - version: v1 - name: serving-cert # this name should match the one in certificate.yaml -- name: SERVICE_NAMESPACE # namespace of the service - objref: - kind: Service - version: v1 - name: webhook-service - fieldref: - fieldpath: metadata.namespace -- name: SERVICE_NAME - objref: - kind: Service - version: v1 - name: webhook-service +# Uncomment the following replacements to add the cert-manager CA injection annotations +replacements: + - source: # Add cert-manager annotation to ValidatingWebhookConfiguration, MutatingWebhookConfiguration and CRDs + kind: Certificate + group: cert-manager.io + version: v1 + name: serving-cert # this name should match the one in certificate.yaml + fieldPath: .metadata.namespace # namespace of the certificate CR + targets: + - select: + kind: ValidatingWebhookConfiguration + fieldPaths: + - .metadata.annotations.[cert-manager.io/inject-ca-from] + options: + delimiter: '/' + index: 0 + create: true + - select: + kind: MutatingWebhookConfiguration + fieldPaths: + - .metadata.annotations.[cert-manager.io/inject-ca-from] + options: + delimiter: '/' + index: 0 + create: true + - select: + kind: CustomResourceDefinition + fieldPaths: + - .metadata.annotations.[cert-manager.io/inject-ca-from] + options: + delimiter: '/' + index: 0 + create: true + - source: + kind: Certificate + group: cert-manager.io + version: v1 + name: serving-cert # this name should match the one in certificate.yaml + fieldPath: .metadata.name + targets: + - select: + kind: ValidatingWebhookConfiguration + fieldPaths: + - .metadata.annotations.[cert-manager.io/inject-ca-from] + options: + delimiter: '/' + index: 1 + create: true + - select: + kind: MutatingWebhookConfiguration + fieldPaths: + - .metadata.annotations.[cert-manager.io/inject-ca-from] + options: + delimiter: '/' + index: 1 + create: true + - select: + kind: CustomResourceDefinition + fieldPaths: + - .metadata.annotations.[cert-manager.io/inject-ca-from] + options: + delimiter: '/' + index: 1 + create: true + - source: # Add cert-manager annotation to the webhook Service + kind: Service + version: v1 + name: webhook-service + fieldPath: .metadata.name # namespace of the service + targets: + - select: + kind: Certificate + group: cert-manager.io + version: v1 + fieldPaths: + - .spec.dnsNames.0 + - .spec.dnsNames.1 + options: + delimiter: '.' + index: 0 + create: true + - source: + kind: Service + version: v1 + name: webhook-service + fieldPath: .metadata.namespace # namespace of the service + targets: + - select: + kind: Certificate + group: cert-manager.io + version: v1 + fieldPaths: + - .spec.dnsNames.0 + - .spec.dnsNames.1 + options: + delimiter: '.' + index: 1 + create: true diff --git a/config/default/webhookcainjection_patch.yaml b/config/default/webhookcainjection_patch.yaml index c6b7ea34..63ef5ff6 100644 --- a/config/default/webhookcainjection_patch.yaml +++ b/config/default/webhookcainjection_patch.yaml @@ -4,17 +4,17 @@ # # This patch add annotation to admission webhook config and -# the variables $(CERTIFICATE_NAMESPACE) and $(CERTIFICATE_NAME) will be substituted by kustomize. +# the variables CERTIFICATE_NAMESPACE_PLACEHOLDER and CERTIFICATE_NAME_PLACEHOLDER will be substituted by kustomize. apiVersion: admissionregistration.k8s.io/v1 kind: MutatingWebhookConfiguration metadata: name: mutating-webhook-configuration annotations: - cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE_PLACEHOLDER/CERTIFICATE_NAME_PLACEHOLDER --- apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration metadata: name: validating-webhook-configuration annotations: - cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE_PLACEHOLDER/CERTIFICATE_NAME_PLACEHOLDER diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 7a52fb17..6173a20a 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -8,5 +8,5 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization images: - name: controller - newName: container-registry.oracle.com/database/operator - newTag: 1.2.0 + newName: phx.ocir.io/intsanjaysingh/db-repo/oracle/database + newTag: orestart-operator-sa diff --git a/config/manifests/bases/oracle-database-operator.clusterserviceversion.yaml b/config/manifests/bases/oracle-database-operator.clusterserviceversion.yaml index 933a2bfa..79cb8430 100644 --- a/config/manifests/bases/oracle-database-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/oracle-database-operator.clusterserviceversion.yaml @@ -4,47 +4,82 @@ metadata: annotations: alm-examples: '[]' capabilities: Seamless Upgrades - operators.operatorframework.io/builder: operator-sdk-v1.2.0 - operators.operatorframework.io/project_layout: go.kubebuilder.io/v2 - name: oracle-database-operator.v1.1.0 + categories: Database + containerImage: container-registry.oracle.com/database/operator:2.0 + operators.operatorframework.io/builder: operator-sdk-v1.39.2 + operators.operatorframework.io/project_layout: go.kubebuilder.io/v4 + name: oracle-database-operator.v2.0 namespace: oracle-database-operator-system spec: apiservicedefinitions: {} customresourcedefinitions: owned: - - description: DbcsSystem is the Schema for the dbcssystems API - displayName: Dbcs System - kind: DbcsSystem - name: DbcsSystem.database.oracle.com - version: v4 - description: AutonomousContainerDatabase is the Schema for the autonomouscontainerdatabases API displayName: Autonomous Container Database kind: AutonomousContainerDatabase name: autonomouscontainerdatabases.database.oracle.com version: v1alpha1 + - description: AutonomousContainerDatabase is the Schema for the autonomouscontainerdatabases + API + displayName: Autonomous Container Database + kind: AutonomousContainerDatabase + name: autonomouscontainerdatabases.database.oracle.com + version: v4 - description: AutonomousDatabaseBackup is the Schema for the autonomousdatabasebackups API displayName: Autonomous Database Backup kind: AutonomousDatabaseBackup name: autonomousdatabasebackups.database.oracle.com version: v1alpha1 + - description: AutonomousDatabaseBackup is the Schema for the autonomousdatabasebackups + API + displayName: Autonomous Database Backup + kind: AutonomousDatabaseBackup + name: autonomousdatabasebackups.database.oracle.com + version: v4 - description: AutonomousDatabaseRestore is the Schema for the autonomousdatabaserestores API displayName: Autonomous Database Restore kind: AutonomousDatabaseRestore name: autonomousdatabaserestores.database.oracle.com version: v1alpha1 + - description: AutonomousDatabaseRestore is the Schema for the autonomousdatabaserestores + API + displayName: Autonomous Database Restore + kind: AutonomousDatabaseRestore + name: autonomousdatabaserestores.database.oracle.com + version: v4 - description: AutonomousDatabase is the Schema for the autonomousdatabases API displayName: Autonomous Database kind: AutonomousDatabase name: autonomousdatabases.database.oracle.com version: v1alpha1 + - description: AutonomousDatabase is the Schema for the autonomousdatabases API + displayName: Autonomous Database + kind: AutonomousDatabase + name: autonomousdatabases.database.oracle.com + version: v4 - description: CDB is the Schema for the cdbs API displayName: CDB kind: CDB name: cdbs.database.oracle.com version: v1alpha1 + - description: CDB is the Schema for the cdbs API + displayName: CDB + kind: CDB + name: cdbs.database.oracle.com + version: v4 + - description: DatabaseObserver is the Schema for the databaseobservers API + displayName: Database Observer + kind: DatabaseObserver + name: databaseobservers.observability.oracle.com + version: v4 + - description: DatabaseObserver is the Schema for the databaseobservers API + displayName: Database Observer + kind: DatabaseObserver + name: databaseobservers.observability.oracle.com + version: v1 - description: DatabaseObserver is the Schema for the databaseobservers API displayName: Database Observer kind: DatabaseObserver @@ -54,22 +89,85 @@ spec: displayName: Dataguard Broker kind: DataguardBroker name: dataguardbrokers.database.oracle.com + version: v4 + - description: DataguardBroker is the Schema for the dataguardbrokers API + displayName: Dataguard Broker + kind: DataguardBroker + name: dataguardbrokers.database.oracle.com + version: v1alpha1 + - description: DbcsSystem is the Schema for the dbcssystems API + displayName: Dbcs System + kind: DbcsSystem + name: dbcssystems.database.oracle.com + version: v4 + - description: DbcsSystem is the Schema for the dbcssystems API + displayName: Dbcs System + kind: DbcsSystem + name: dbcssystems.database.oracle.com version: v1alpha1 + - description: LREST is the Schema for the lrests API + displayName: LREST + kind: LREST + name: lrests.database.oracle.com + version: v4 + - description: LRPDB is the Schema for the pdbs API + displayName: LRPDB + kind: LRPDB + name: lrpdbs.database.oracle.com + version: v4 + - description: OracleRestDataService is the Schema for the oraclerestdataservices + API + displayName: Oracle Rest Data Service + kind: OracleRestDataService + name: oraclerestdataservices.database.oracle.com + version: v4 - description: OracleRestDataService is the Schema for the oraclerestdataservices API displayName: Oracle Rest Data Service kind: OracleRestDataService name: oraclerestdataservices.database.oracle.com version: v1alpha1 + - description: OrdsSrvs is the Schema for the ordssrvs API + displayName: Ords Srvs + kind: OrdsSrvs + name: ordssrvs.database.oracle.com + statusDescriptors: + - displayName: Conditions + path: conditions + version: v1alpha1 + - description: OrdsSrvs is the Schema for the ordssrvs API + displayName: Ords Srvs + kind: OrdsSrvs + name: ordssrvs.database.oracle.com + statusDescriptors: + - displayName: Conditions + path: conditions + version: v4 - description: PDB is the Schema for the pdbs API displayName: PDB kind: PDB name: pdbs.database.oracle.com version: v1alpha1 + - description: PDB is the Schema for the pdbs API + displayName: PDB + kind: PDB + name: pdbs.database.oracle.com + version: v4 + - description: ShardingDatabase is the Schema for the shardingdatabases API + displayName: Sharding Database + kind: ShardingDatabase + name: shardingdatabases.database.oracle.com + version: v4 - description: ShardingDatabase is the Schema for the shardingdatabases API displayName: Sharding Database kind: ShardingDatabase name: shardingdatabases.database.oracle.com + version: v1alpha1 + - description: SingleInstanceDatabase is the Schema for the singleinstancedatabases + API + displayName: Single Instance Database + kind: SingleInstanceDatabase + name: singleinstancedatabases.database.oracle.com version: v4 - description: SingleInstanceDatabase is the Schema for the singleinstancedatabases API @@ -79,22 +177,28 @@ spec: version: v1alpha1 description: | As part of Oracle's resolution to make Oracle Database Kubernetes native (that is, observable and operable by Kubernetes), Oracle released Oracle Database Operator for Kubernetes (OraOperator or the operator). OraOperator extends the Kubernetes API with custom resources and controllers for automating Oracle Database lifecycle management. - In this v1.1.0 production release, OraOperator supports the following database configurations and infrastructure: - ## Oracle Autonomous Database: - * Oracle Autonomous Database shared Oracle Cloud Infrastructure (OCI) (ADB-S) - * Oracle Autonomous Database on dedicated Cloud infrastructure (ADB-D) - * Oracle Autonomous Container Database (ACD) (infrastructure) is the infrastructure for provisioning Autonomous Databases. - * Containerized Single Instance databases (SIDB) deployed in the Oracle Kubernetes Engine (OKE) and any k8s where OraOperator is deployed - * Containerized Sharded databases (SHARDED) deployed in OKE and any k8s where OraOperator is deployed - * Oracle Multitenant Databases (CDB/PDBs) - * Oracle Base Database Cloud Service (BDBCS) - * Oracle Data Guard (Preview status) - * Oracle Database Observability (Preview status) - * Oracle will continue to extend OraOperator to support additional Oracle Database configurations. + In this v1.2.0 production release, OraOperator supports the following database configurations and infrastructure: + ##Supported Database Configurations: + * Oracle Cloud Infrastructure (OCI) Databases + * Oracle Autonomous Database Serverless (ADB-S) + * Oracle Autonomous Database on Dedicated on (ADB-D) + * Oracle Base Database Cloud Service (Base DB) + * Containerized Database deployments on Kubernetes + * Oracle Kubernetes Engine (OKE) and Oracle Cloud Native Environments (OCNE) + * Single Instance databases (SIDB) + * Globally Distributed Databases (Shared) + * Oracle Data Guard + * True Cache (Preview release) + * Oracle Database Observability + * On-Premises Databases + * Oracle Multitenant Databases (CDB/PDBs management) + * ORDS Services + + Refer to full feature list and capabilities: [https://github.com/oracle/oracle-database-operator/blob/main/README.md](https://github.com/oracle/oracle-database-operator/blob/main/README.md) displayName: Oracle Database Operator icon: - base64data: iVBORw0KGgoAAAANSUhEUgAAALQAAAC0CAYAAAA9zQYyAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QAAAAAAAD5Q7t/AAAJjUlEQVR42u3cfcwcRQHH8S9PH0BokZfCVBgpOgjyFjRoQIQQkLeA0PLWqgQMFDVgja9AChIKKCEKSgQEQVsQJGKxtNCAvAi2vJiCqAQMUpQRMKM4vFiCQEUo/jH7kOt19m7vbveK8fdJLukzMzuzczc7OzszWxAREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREZH/X2tVSRStmwi8B5gErN1nWS8DAVhmgl9ZsdwpVc+xoteKc/iTCf7VujKN1o0A+xef5cDPTfCP1XjeY+VsAWwFTATGDZjdPSb4F6J1U9sjTPA31n3uXeq1MfBe4F30376ADo0lWjcBOAa4EHhHzXW4FzgDuNsE/2aHc3gJmFBz2WMuBc4ywT87SCbRunWAB4APtEV92gR/zaAnGa2bBJwInFVz/SeY4F+O1q32/Zvg6+xEyuq1PnA08F1gg7ryHSkpbDrwEnAZ9TdmgD2BxcDj0bptG8i/is8DMVq394D5zGb1xgxwdbRum34zjdaNi9adAjxD/Y15PxP8yzXn2UvdDiPdsX9IjY0ZOHOVKzFaNw64Apgx5DoelrvNNdxDt9rDBP/rXg+K1u0EPNIhyWPADp3uQiX5bggsIX+hDOp6E/z0lrKG1kMXQ7OLgJkNZL8SWO+tHjpatxbwM4bfmAEWRuuOWAPljrmvGGJVVlz8d3ZJth1wXI/5jgeW0UxjhnRnGrqifV1FM40ZYDcT/GujLQGnAUd2OGAu6Qd8oY/CxgGTgWOB3UvSzI/W7WiCf7RLXgcNUOnJwOUlcScCF/SQ10mAqZBubrTuVhP837sljNZB6lQmdUh2AbCUdMvu1Ssm+Of6OK4OXyb9/mV+DNxOf+0LE/yDUDwURuu2A/5YknYWcLEJ/pU6ahWt2wq4FtgjE/08YMZmQUqGHHua4O8boPx1gNuAvTPRI1WGB0UdnsxEPQzsnAm/HTjQBN8t36nAwpLoTwDzTfBv9Fv3kjIbH3JE6xzwREn0bOA7dY3px4Ycl5bEH2CC/1ZdjRnABP8UsBfwg0z0RODgusoqKf810uxNzsbdjm/pRXOOIP1A7Q4ADu2S7yjljXl7E/y8uhvzEF1YEj7FBH9OnQ+oI9G6zYF9MnEzTfB3NFG7ogf+ApDrsr7XRJlt/lYSvmGFY6cDu2XCZ5ngnwC+XXLcTcXDXpmyodheTcxpD0u0bjNgSibqZBP8orrLGyH1HjlXNFnRorc5KhPlioWcJo2WhL/e6aBo3abke+eVFL2QCX4F+eEMpDnXMsdnwu4ywd/T8HfRtLLv4pImChsFDsmEn2aCf73XzPrwUEn4+4Gep9F68LGS8G4PTN8vCf9oMZQBwAS/JFp3PTCtLd2MaN3ckmeAXIP+ZoPfwbCUDSFXFMO3Om03QlrkaLd4GDUtHsByPd7WTZUZrdsLuDUTdX+n5fBo3b6k4Ua7K0zw92fCy6an7o3WVV2seqRiurez3QfPopI5Jvhlo+SniPqaOunT05mwjmPZaN2WwKPAmy0fOvw99u/NOmR7aofyxgO/7OU4E/yz0brjSHOv7c4oPt3U9jC+Bk0aPItKvgZpDJ27zVZ5OKrLFpmwlzodYIL/K2k+egPgncX5bghsRJqp2KT4TAQ2LT6dGvONJvi7O8R/oyR8qgn+xQ7HXUP+wffrxSpjN+v195W+rQxj3vuQsd9hhDRJ326P3vLrTzGGOjoT9Zdux5rg7yWtxA3qsZJzGDvHXYCvZKIWAzd1OceVlE/X3VmsNnayfQ31W9MeaDj/W0zwN4/9MQosYvUv/YJo3UVVt3kOYAfyO/4qTVOZ4JcVu9EeAjbvo/xLSNNH/85FRuvWBsp67guBKRUfbM4jrcSucvqk1caxp/3rgE+2pfkqaWfi/7JfkO8w1m5i4mGUtGrWbhzwKdKKXiOKtf0rM1HPAbFqPib4GK17H2lRYv8Kh7xIWkj6kQned0n7JWB8JnwmaSfiFgzm4mjdomKxaQ6rN+jDo3UfMsH/dsBy1qSy/S4zaGBqeMQE/zTwu0zcT6J1u/WaYRVFr3YOsGsmema3JeJ2xUrmQcDFHZKdU9R3IxP86d0ac7Rua+D8XBRpP0iVi6eKecXFXXYneDBaN7mmsoau2MOyJBN1eTHjVKuxpe/PlMQvjdadUGGsV1mxcjSP8qf8Bf3ka4J/wwT/RdImmJwzSbv6uj5oFQ1sYUn0fkVZjwJn1/CV7ApM77Ik/1S0rq4LaE0o2+G3JFp3UjG0q8Vb49do3fnAyR3SngvcQ3rFqFfjAEtaaJjWId2uJvjftJxTX5uTonWHAzeURQMf7LT7LVp3LHB1Jup8E/ypLenWBVb08X3kbEbanLWU/J0L0uzP2aQ76qBTeq+a4B/ObU4CPlJTnVodTOpUypxH6smXD1JIa4MeBX5FfqFlGGaY4FcZUw+y264YLi3tkGQXE/zvM8dNIr0lkjO+faNWtG5n0sXai1syYfNN8EdF6zYhNeymHWmCv6GkQdftKtIo4GbgwCYLan9jZV1gPvDxIVSy1fEm+KvaAwfdPlqMg//cIcnhJviFbccsIr8dYD8TfLcN/ZVE6y4HPpeJOsAEf0dxUf2BNH/ehMXAPiZ4htSgNzbBLy+27l5Lfg9PLVZ5p7CYvjqU4b3V8B/gw7nGXIdi99umlO/FXRCtmzU29RatO4h8Y15QV2MuzCoJvz1aN8EE/w9gS3p74aAXx/T64D2AKSb45fDW1t1plD+zDWrf1V6SNcG/aYK/jDSmm917npU8Tdo7vH7TU1Im+OeBnShfBDkP+Gm0bgPyQwFIb7PUeU7/pHwx59wizQoT/CmAI793vF+fNcGHlr+P7zun7m4jrXO01h0T/BzSKu7pNZZ1nQn+rqr/L8dkYFvSvGu/T6T/Ap4CHjfBV9orEq07IRN8pwn+yV4LL17Q7PTj3UV+F95zTfw/FcVMSvb9zeIHz53/1sA2pEWZfmeermxfMIvW7Uh6EB3pL8tSN5vgn+mWqNibsw3wbvpvXwuqtisREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREWnxX2ox1/vZSvwPAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDI0LTA4LTEzVDE5OjUyOjMxKzAwOjAwsDIMcAAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyNC0wOC0xM1QxOTo1MjozMSswMDowMMFvtMwAAABVdEVYdHN2Zzpjb21tZW50ACBVcGxvYWRlZCB0bzogU1ZHIFJlcG8sIHd3dy5zdmdyZXBvLmNvbSwgR2VuZXJhdG9yOiBTVkcgUmVwbyBNaXhlciBUb29scyBFB1wTAAAAAElFTkSuQmCC - mediatype: png + mediatype: image/png install: spec: deployments: null @@ -118,4 +222,5 @@ spec: maturity: alpha provider: name: Oracle + replaces: oracle-database-operator.v1.1.0 version: 1.2.0 diff --git a/config/network-policy/allow-webhook-traffic.yaml b/config/network-policy/allow-webhook-traffic.yaml new file mode 100644 index 00000000..7acb3a39 --- /dev/null +++ b/config/network-policy/allow-webhook-traffic.yaml @@ -0,0 +1,27 @@ +# This NetworkPolicy allows ingress traffic to your webhook server running +# as part of the controller-manager from specific namespaces and pods. CR(s) which uses webhooks +# will only work when applied in namespaces labeled with 'webhook: enabled' +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + labels: + app.kubernetes.io/name: oracle-database-operator + app.kubernetes.io/managed-by: kustomize + name: allow-webhook-traffic + namespace: system +spec: + podSelector: + matchLabels: + control-plane: controller-manager + app.kubernetes.io/name: oracle-database-operator + policyTypes: + - Ingress + ingress: + # This allows ingress traffic from any namespace with the label webhook: enabled + - from: + - namespaceSelector: + matchLabels: + webhook: enabled # Only from namespaces with this label + ports: + - port: 443 + protocol: TCP diff --git a/config/rbac/cdb_editor_role.yaml b/config/rbac/cdb_editor_role.yaml deleted file mode 100644 index 244ddff2..00000000 --- a/config/rbac/cdb_editor_role.yaml +++ /dev/null @@ -1,24 +0,0 @@ -# permissions for end users to edit cdbs. -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: cdb-editor-role -rules: -- apiGroups: - - database.oracle.com - resources: - - cdbs - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - database.oracle.com - resources: - - cdbs/status - verbs: - - get diff --git a/config/rbac/cdb_viewer_role.yaml b/config/rbac/cdb_viewer_role.yaml deleted file mode 100644 index 78a84283..00000000 --- a/config/rbac/cdb_viewer_role.yaml +++ /dev/null @@ -1,20 +0,0 @@ -# permissions for end users to view cdbs. -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: cdb-viewer-role -rules: -- apiGroups: - - database.oracle.com - resources: - - cdbs - verbs: - - get - - list - - watch -- apiGroups: - - database.oracle.com - resources: - - cdbs/status - verbs: - - get diff --git a/config/rbac/database_oraclerestart_admin_role.yaml b/config/rbac/database_oraclerestart_admin_role.yaml new file mode 100644 index 00000000..a07ef7e9 --- /dev/null +++ b/config/rbac/database_oraclerestart_admin_role.yaml @@ -0,0 +1,27 @@ +# This rule is not used by the project oracle-database-operator itself. +# It is provided to allow the cluster admin to help manage permissions for users. +# +# Grants full permissions ('*') over database.oracle.com. +# This role is intended for users authorized to modify roles and bindings within the cluster, +# enabling them to delegate specific permissions to other users or groups as needed. + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: oracle-database-operator + app.kubernetes.io/managed-by: kustomize + name: database-oraclerestart-admin-role +rules: +- apiGroups: + - database.oracle.com + resources: + - oraclerestarts + verbs: + - '*' +- apiGroups: + - database.oracle.com + resources: + - oraclerestarts/status + verbs: + - get diff --git a/config/rbac/database_oraclerestart_editor_role.yaml b/config/rbac/database_oraclerestart_editor_role.yaml new file mode 100644 index 00000000..ac68b530 --- /dev/null +++ b/config/rbac/database_oraclerestart_editor_role.yaml @@ -0,0 +1,33 @@ +# This rule is not used by the project oracle-database-operator itself. +# It is provided to allow the cluster admin to help manage permissions for users. +# +# Grants permissions to create, update, and delete resources within the database.oracle.com. +# This role is intended for users who need to manage these resources +# but should not control RBAC or manage permissions for others. + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: oracle-database-operator + app.kubernetes.io/managed-by: kustomize + name: database-oraclerestart-editor-role +rules: +- apiGroups: + - database.oracle.com + resources: + - oraclerestarts + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - database.oracle.com + resources: + - oraclerestarts/status + verbs: + - get diff --git a/config/rbac/database_oraclerestart_viewer_role.yaml b/config/rbac/database_oraclerestart_viewer_role.yaml new file mode 100644 index 00000000..fba2f80b --- /dev/null +++ b/config/rbac/database_oraclerestart_viewer_role.yaml @@ -0,0 +1,29 @@ +# This rule is not used by the project oracle-database-operator itself. +# It is provided to allow the cluster admin to help manage permissions for users. +# +# Grants read-only access to database.oracle.com resources. +# This role is intended for users who need visibility into these resources +# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing. + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: oracle-database-operator + app.kubernetes.io/managed-by: kustomize + name: database-oraclerestart-viewer-role +rules: +- apiGroups: + - database.oracle.com + resources: + - oraclerestarts + verbs: + - get + - list + - watch +- apiGroups: + - database.oracle.com + resources: + - oraclerestarts/status + verbs: + - get diff --git a/config/rbac/kustomization.yaml b/config/rbac/kustomization.yaml index 7a20231c..e9c0caa0 100644 --- a/config/rbac/kustomization.yaml +++ b/config/rbac/kustomization.yaml @@ -14,3 +14,9 @@ resources: - auth_proxy_role.yaml - auth_proxy_role_binding.yaml - auth_proxy_client_clusterrole.yaml +# For each CRD, "Admin", "Editor" and "Viewer" roles are scaffolded by +# default, aiding admins in cluster management. Those roles are +# not used by the {{ .ProjectName }} itself. You can comment the following lines +# if you do not want those helpers be installed with your Project. + + diff --git a/config/rbac/pdb_editor_role.yaml b/config/rbac/pdb_editor_role.yaml deleted file mode 100644 index 7d668e4a..00000000 --- a/config/rbac/pdb_editor_role.yaml +++ /dev/null @@ -1,24 +0,0 @@ -# permissions for end users to edit pdbs. -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: pdb-editor-role -rules: -- apiGroups: - - database.oracle.com - resources: - - pdbs - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - database.oracle.com - resources: - - pdbs/status - verbs: - - get diff --git a/config/rbac/pdb_viewer_role.yaml b/config/rbac/pdb_viewer_role.yaml deleted file mode 100644 index 5fcf68c9..00000000 --- a/config/rbac/pdb_viewer_role.yaml +++ /dev/null @@ -1,20 +0,0 @@ -# permissions for end users to view pdbs. -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: pdb-viewer-role -rules: -- apiGroups: - - database.oracle.com - resources: - - pdbs - verbs: - - get - - list - - watch -- apiGroups: - - database.oracle.com - resources: - - pdbs/status - verbs: - - get diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 3a12386c..e6086c1d 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -10,9 +10,12 @@ rules: - configmaps - containers - deployments + - endpoints - events - namespaces + - persistentvolumeclaim - persistentvolumeclaims + - persistentvolumes - pods - pods/exec - pods/log @@ -33,20 +36,13 @@ rules: - configmaps/status - daemonsets/status - deployments/status + - persistentvolumeclaim/status - services/status - statefulsets/status verbs: - get - patch - update -- apiGroups: - - "" - resources: - - persistentvolumes - verbs: - - get - - list - - watch - apiGroups: - "" resources: @@ -103,15 +99,14 @@ rules: resources: - autonomouscontainerdatabases - autonomousdatabases - - cdbs - dataguardbrokers - dbcssystems - events - lrests - lrpdbs + - oraclerestarts - oraclerestdataservices - ordssrvs - - pdbs - shardingdatabases - singleinstancedatabases verbs: @@ -128,14 +123,13 @@ rules: - autonomouscontainerdatabases/status - autonomousdatabasebackups/status - autonomousdatabaserestores/status - - cdbs/status - dataguardbrokers/status - dbcssystems/status - lrests/status - lrpdbs/status + - oraclerestarts/status - oraclerestdataservices/status - ordssrvs/status - - pdbs/status - shardingdatabases/status - singleinstancedatabases/status verbs: @@ -164,7 +158,6 @@ rules: - apiGroups: - database.oracle.com resources: - - cdbs/finalizers - dataguardbrokers/finalizers - lrests/finalizers - oraclerestdataservices/finalizers @@ -176,8 +169,9 @@ rules: - database.oracle.com resources: - dbcssystems/finalizers + - lrpdbs/configmaps - lrpdbs/finalizers - - pdbs/finalizers + - oraclerestarts/finalizers - shardingdatabases/finalizers verbs: - create diff --git a/config/samples/acd/autonomouscontainerdatabase_bind.yaml b/config/samples/acd/autonomouscontainerdatabase_bind.yaml index 3d28ba4d..12912ac9 100644 --- a/config/samples/acd/autonomouscontainerdatabase_bind.yaml +++ b/config/samples/acd/autonomouscontainerdatabase_bind.yaml @@ -2,7 +2,7 @@ # Copyright (c) 2022, Oracle and/or its affiliates. # Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. # -apiVersion: database.oracle.com/v1alpha1 +apiVersion: database.oracle.com/v4 kind: AutonomousContainerDatabase metadata: name: autonomouscontainerdatabase-sample diff --git a/config/samples/acd/autonomouscontainerdatabase_change_displayname.yaml b/config/samples/acd/autonomouscontainerdatabase_change_displayname.yaml index dd75250d..99568e52 100644 --- a/config/samples/acd/autonomouscontainerdatabase_change_displayname.yaml +++ b/config/samples/acd/autonomouscontainerdatabase_change_displayname.yaml @@ -2,7 +2,7 @@ # Copyright (c) 2022, Oracle and/or its affiliates. # Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. # -apiVersion: database.oracle.com/v1alpha1 +apiVersion: database.oracle.com/v4 kind: AutonomousContainerDatabase metadata: name: autonomouscontainerdatabase-sample diff --git a/config/samples/acd/autonomouscontainerdatabase_create.yaml b/config/samples/acd/autonomouscontainerdatabase_create.yaml index 5f42a136..1a1daeb6 100644 --- a/config/samples/acd/autonomouscontainerdatabase_create.yaml +++ b/config/samples/acd/autonomouscontainerdatabase_create.yaml @@ -2,7 +2,7 @@ # Copyright (c) 2022, Oracle and/or its affiliates. # Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. # -apiVersion: database.oracle.com/v1alpha1 +apiVersion: database.oracle.com/v4 kind: AutonomousContainerDatabase metadata: name: autonomouscontainerdatabase-sample diff --git a/config/samples/acd/autonomouscontainerdatabase_delete_resource.yaml b/config/samples/acd/autonomouscontainerdatabase_delete_resource.yaml index 5be06b5a..b2bd4c33 100644 --- a/config/samples/acd/autonomouscontainerdatabase_delete_resource.yaml +++ b/config/samples/acd/autonomouscontainerdatabase_delete_resource.yaml @@ -2,7 +2,7 @@ # Copyright (c) 2022, Oracle and/or its affiliates. # Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. # -apiVersion: database.oracle.com/v1alpha1 +apiVersion: database.oracle.com/v4 kind: AutonomousContainerDatabase metadata: name: autonomouscontainerdatabase-sample diff --git a/config/samples/acd/autonomouscontainerdatabase_restart_terminate.yaml b/config/samples/acd/autonomouscontainerdatabase_restart_terminate.yaml index 0e884f6e..96644fed 100644 --- a/config/samples/acd/autonomouscontainerdatabase_restart_terminate.yaml +++ b/config/samples/acd/autonomouscontainerdatabase_restart_terminate.yaml @@ -2,7 +2,7 @@ # Copyright (c) 2022, Oracle and/or its affiliates. # Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. # -apiVersion: database.oracle.com/v1alpha1 +apiVersion: database.oracle.com/v4 kind: AutonomousContainerDatabase metadata: name: autonomouscontainerdatabase-sample diff --git a/config/samples/adb/autonomousdatabase_backup.yaml b/config/samples/adb/autonomousdatabase_backup.yaml index 0099a347..448481ee 100644 --- a/config/samples/adb/autonomousdatabase_backup.yaml +++ b/config/samples/adb/autonomousdatabase_backup.yaml @@ -2,7 +2,7 @@ # Copyright (c) 2022, Oracle and/or its affiliates. # Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. # -apiVersion: database.oracle.com/v1alpha1 +apiVersion: database.oracle.com/v4 kind: AutonomousDatabaseBackup metadata: name: autonomousdatabasebackup-sample @@ -10,11 +10,11 @@ spec: # Before you can create on-demand backups, you must have an Object Storage bucket and your database must be configured to connect to it. This is a one-time operation. # See https://docs.oracle.com/en-us/iaas/Content/Database/Tasks/adbbackingup.htm#creatingbucket target: - k8sADB: + k8sAdb: name: autonomousdatabase-sample # # Uncomment the below block if you use ADB OCID as the input of the target ADB - # ociADB: - # ocid: ocid1.autonomousdatabase... + # ociAdb: + # id: ocid1.autonomousdatabase... displayName: autonomousdatabasebackup-sample isLongTermBackup: true retentionPeriodInDays: 90 # minimum retention period is 90 days diff --git a/config/samples/adb/autonomousdatabase_bind.yaml b/config/samples/adb/autonomousdatabase_bind.yaml index 702b8f03..29105860 100644 --- a/config/samples/adb/autonomousdatabase_bind.yaml +++ b/config/samples/adb/autonomousdatabase_bind.yaml @@ -2,7 +2,7 @@ # Copyright (c) 2022, Oracle and/or its affiliates. # Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. # -apiVersion: database.oracle.com/v1alpha1 +apiVersion: database.oracle.com/v4 kind: AutonomousDatabase metadata: name: autonomousdatabase-sample diff --git a/config/samples/adb/autonomousdatabase_clone.yaml b/config/samples/adb/autonomousdatabase_clone.yaml index 559d7185..3a01d606 100644 --- a/config/samples/adb/autonomousdatabase_clone.yaml +++ b/config/samples/adb/autonomousdatabase_clone.yaml @@ -2,7 +2,7 @@ # Copyright (c) 2022, 2024, Oracle and/or its affiliates. # Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. # -apiVersion: database.oracle.com/v1alpha1 +apiVersion: database.oracle.com/v4 kind: AutonomousDatabase metadata: name: autonomousdatabase-sample @@ -16,7 +16,8 @@ spec: # The dbName must begin with an alphabetic character and can contain a maximum of 14 alphanumeric characters. Special characters are not permitted. The database name must be unique in the tenancy. dbName: ClonedADB displayName: ClonedADB - cpuCoreCount: 1 + computeModel: ECPU + computeCount: 1 adminPassword: # Comment out k8sSecret and uncomment ociSecret if you pass the admin password using OCI Secret. k8sSecret: diff --git a/config/samples/adb/autonomousdatabase_create.yaml b/config/samples/adb/autonomousdatabase_create.yaml index d633cb84..682a271d 100644 --- a/config/samples/adb/autonomousdatabase_create.yaml +++ b/config/samples/adb/autonomousdatabase_create.yaml @@ -2,7 +2,7 @@ # Copyright (c) 2022, 2024, Oracle and/or its affiliates. # Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. # -apiVersion: database.oracle.com/v1alpha1 +apiVersion: database.oracle.com/v4 kind: AutonomousDatabase metadata: name: autonomousdatabase-sample @@ -14,7 +14,8 @@ spec: # The dbName must begin with an alphabetic character and can contain a maximum of 14 alphanumeric characters. Special characters are not permitted. The database name must be unique in the tenancy. dbName: NewADB displayName: NewADB - cpuCoreCount: 1 + computeModel: ECPU + computeCount: 1 adminPassword: # Comment out k8sSecret and uncomment ociSecret if you pass the admin password using OCI Secret. k8sSecret: @@ -25,39 +26,30 @@ spec: # ocid: ocid1.vaultsecret... dataStorageSizeInTBs: 1 - # networkAccess: - # # Uncomment this block to configure the network access type with the PUBLIC option, which allows secure access from everywhere. - # accessType: PUBLIC + # # Uncomment this block to configure the network access type with the RESTRICTED option. + # # This option lets you restrict access by defining access control rules in an Access Control List (ACL). + # # By specifying an ACL, the database will be accessible from a whitelisted set of IP addresses, CIDR (Classless Inter-Domain Routing) blocks, or VCNs. + # # Use a semicolon (;) as a deliminator between the VCN-specific subnets or IPs. + # whitelistedIps: + # - 1.1.1.1 + # - 1.1.0.0/16 + # - ocid1.vcn... + # - ocid1.vcn...;1.1.1.1 + # - ocid1.vcn...;1.1.0.0/16 + # isMtlsConnectionRequired: true - # # Uncomment this block to configure the network access type with the RESTRICTED option. - # # This option lets you restrict access by defining access control rules in an Access Control List (ACL). - # # By specifying an ACL, the database will be accessible from a whitelisted set of IP addresses, CIDR (Classless Inter-Domain Routing) blocks, or VCNs. - # # Use a semicolon (;) as a deliminator between the VCN-specific subnets or IPs. - # accessType: RESTRICTED - # accessControlList: - # - 1.1.1.1 - # - 1.1.0.0/16 - # - ocid1.vcn... - # - ocid1.vcn...;1.1.1.1 - # - ocid1.vcn...;1.1.0.0/16 - # isMTLSConnectionRequired: true + # # Uncomment this block to configure the network access type with the PRIVATE option. + # # This option assigns a private endpoint, private IP, and hostname to your database. + # # Specifying this option allows traffic only from the VCN you specify. + # # This allows you to define security rules, ingress/egress, at the Network Security Group (NSG) level and to control traffic to your Autonomous Database. + # subnetId: ocid1.subnet... + # isMtlsConnectionRequired: true - # # Uncomment this block to configure the network access type with the PRIVATE option. - # # This option assigns a private endpoint, private IP, and hostname to your database. - # # Specifying this option allows traffic only from the VCN you specify. - # # This allows you to define security rules, ingress/egress, at the Network Security Group (NSG) level and to control traffic to your Autonomous Database. - # accessType: PRIVATE - # privateEndpoint: - # subnetOCID: ocid1.subnet... - # nsgOCIDs: - # - ocid1.networksecuritygroup... - # isMTLSConnectionRequired: true - - # # Uncomment this block to configure the network access of an dedicated Autonomous Database (ADB-D) with an access control list. - # isAccessControlEnabled: true - # accessControlList: - # - 1.1.1.1 - # - 1.1.0.0/16 + # # Uncomment this block to configure the network access of an dedicated Autonomous Database (ADB-D) with an access control list. + # isAccessControlEnabled: true + # whitelistedIps: + # - 1.1.1.1 + # - 1.1.0.0/16 # Authorize the operator with API signing key pair. Comment out the ociConfig fields if your nodes are already authorized with instance principal. ociConfig: diff --git a/config/samples/adb/autonomousdatabase_delete_resource.yaml b/config/samples/adb/autonomousdatabase_delete_resource.yaml index bae1f605..0d5504a7 100644 --- a/config/samples/adb/autonomousdatabase_delete_resource.yaml +++ b/config/samples/adb/autonomousdatabase_delete_resource.yaml @@ -2,7 +2,7 @@ # Copyright (c) 2022, Oracle and/or its affiliates. # Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. # -apiVersion: database.oracle.com/v1alpha1 +apiVersion: database.oracle.com/v4 kind: AutonomousDatabase metadata: name: autonomousdatabase-sample diff --git a/config/samples/adb/autonomousdatabase_wallet.yaml b/config/samples/adb/autonomousdatabase_download_wallet.yaml similarity index 96% rename from config/samples/adb/autonomousdatabase_wallet.yaml rename to config/samples/adb/autonomousdatabase_download_wallet.yaml index 84136647..c717c4f3 100644 --- a/config/samples/adb/autonomousdatabase_wallet.yaml +++ b/config/samples/adb/autonomousdatabase_download_wallet.yaml @@ -2,7 +2,7 @@ # Copyright (c) 2022, Oracle and/or its affiliates. # Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. # -apiVersion: database.oracle.com/v1alpha1 +apiVersion: database.oracle.com/v4 kind: AutonomousDatabase metadata: name: autonomousdatabase-sample diff --git a/config/samples/adb/autonomousdatabase_manual_failover.yaml b/config/samples/adb/autonomousdatabase_manual_failover.yaml new file mode 100644 index 00000000..3182d607 --- /dev/null +++ b/config/samples/adb/autonomousdatabase_manual_failover.yaml @@ -0,0 +1,17 @@ +# +# Copyright (c) 2022, 2025, Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. +# +apiVersion: database.oracle.com/v4 +kind: AutonomousDatabase +metadata: + name: autonomousdatabase-sample +spec: + action: Failover + details: + id: ocid1.autonomousdatabase... + # Authorize the operator with API signing key pair. Comment out the ociConfig fields if your nodes are already authorized with instance principal. + ociConfig: + configMapName: oci-cred + # Comment out secretName if using OKE workload identity + secretName: oci-privatekey \ No newline at end of file diff --git a/config/samples/adb/autonomousdatabase_rename.yaml b/config/samples/adb/autonomousdatabase_rename.yaml index 22dbcc0f..1d5f3d0f 100644 --- a/config/samples/adb/autonomousdatabase_rename.yaml +++ b/config/samples/adb/autonomousdatabase_rename.yaml @@ -2,7 +2,7 @@ # Copyright (c) 2022, Oracle and/or its affiliates. # Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. # -apiVersion: database.oracle.com/v1alpha1 +apiVersion: database.oracle.com/v4 kind: AutonomousDatabase metadata: name: autonomousdatabase-sample diff --git a/config/samples/adb/autonomousdatabase_restore.yaml b/config/samples/adb/autonomousdatabase_restore.yaml index 3db8a1b6..98d9a827 100644 --- a/config/samples/adb/autonomousdatabase_restore.yaml +++ b/config/samples/adb/autonomousdatabase_restore.yaml @@ -2,7 +2,7 @@ # Copyright (c) 2022, Oracle and/or its affiliates. # Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. # -apiVersion: database.oracle.com/v1alpha1 +apiVersion: database.oracle.com/v4 kind: AutonomousDatabaseRestore metadata: name: autonomousdatabaserestore-sample @@ -10,13 +10,13 @@ spec: # Restore the database either from a backup or using point-in-time restore # The name of your AutonomousDatabaseBackup resource target: - k8sADB: + k8sAdb: name: autonomousdatabase-sample # # Uncomment the below block if you use ADB OCID as the input of the target ADB - # ociADB: - # ocid: ocid1.autonomousdatabase... + # ociAdb: + # id: ocid1.autonomousdatabase... source: - k8sADBBackup: + k8sAdbBackup: name: autonomousdatabasebackup-sample # # Uncomment the following field to perform point-in-time restore # pointInTime: diff --git a/config/samples/adb/autonomousdatabase_scale.yaml b/config/samples/adb/autonomousdatabase_scale.yaml index ea53e94d..3698bda0 100644 --- a/config/samples/adb/autonomousdatabase_scale.yaml +++ b/config/samples/adb/autonomousdatabase_scale.yaml @@ -2,7 +2,7 @@ # Copyright (c) 2022, Oracle and/or its affiliates. # Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. # -apiVersion: database.oracle.com/v1alpha1 +apiVersion: database.oracle.com/v4 kind: AutonomousDatabase metadata: name: autonomousdatabase-sample @@ -10,8 +10,8 @@ spec: action: Update details: id: ocid1.autonomousdatabase... - # Your database's OPCU core count - cpuCoreCount: 2 + # Your database's compute count + computeCount: 2 # Your database's storage size in TB dataStorageSizeInTBs: 2 # Enable/Disable auto scaling for your database diff --git a/config/samples/adb/autonomousdatabase_stop_start_terminate.yaml b/config/samples/adb/autonomousdatabase_stop_start_terminate.yaml index 4a191dd6..f6daf426 100644 --- a/config/samples/adb/autonomousdatabase_stop_start_terminate.yaml +++ b/config/samples/adb/autonomousdatabase_stop_start_terminate.yaml @@ -2,13 +2,13 @@ # Copyright (c) 2022, Oracle and/or its affiliates. # Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. # -apiVersion: database.oracle.com/v1alpha1 +apiVersion: database.oracle.com/v4 kind: AutonomousDatabase metadata: name: autonomousdatabase-sample spec: - action: Stop # Use the value "Start" to start the database + action: Stop # Use the value "Start" to start the database, or "Terminate" to terminate the database details: id: ocid1.autonomousdatabase... # Authorize the operator with API signing key pair. Comment out the ociConfig fields if your nodes are already authorized with instance principal. diff --git a/config/samples/adb/autonomousdatabase_switchover.yaml b/config/samples/adb/autonomousdatabase_switchover.yaml new file mode 100644 index 00000000..8cd1fce1 --- /dev/null +++ b/config/samples/adb/autonomousdatabase_switchover.yaml @@ -0,0 +1,17 @@ +# +# Copyright (c) 2022, 2025, Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. +# +apiVersion: database.oracle.com/v4 +kind: AutonomousDatabase +metadata: + name: autonomousdatabase-sample +spec: + action: Switchover + details: + id: ocid1.autonomousdatabase... + # Authorize the operator with API signing key pair. Comment out the ociConfig fields if your nodes are already authorized with instance principal. + ociConfig: + configMapName: oci-cred + # Comment out secretName if using OKE workload identity + secretName: oci-privatekey \ No newline at end of file diff --git a/config/samples/adb/autonomousdatabase_update_admin_password.yaml b/config/samples/adb/autonomousdatabase_update_admin_password.yaml index be7aca69..14a43639 100644 --- a/config/samples/adb/autonomousdatabase_update_admin_password.yaml +++ b/config/samples/adb/autonomousdatabase_update_admin_password.yaml @@ -2,7 +2,7 @@ # Copyright (c) 2022, Oracle and/or its affiliates. # Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. # -apiVersion: database.oracle.com/v1alpha1 +apiVersion: database.oracle.com/v4 kind: AutonomousDatabase metadata: name: autonomousdatabase-sample diff --git a/config/samples/adb/autonomousdatabase_update_mtls.yaml b/config/samples/adb/autonomousdatabase_update_mtls.yaml index 25eda529..87c98bbc 100644 --- a/config/samples/adb/autonomousdatabase_update_mtls.yaml +++ b/config/samples/adb/autonomousdatabase_update_mtls.yaml @@ -2,7 +2,7 @@ # Copyright (c) 2022, Oracle and/or its affiliates. # Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. # -apiVersion: database.oracle.com/v1alpha1 +apiVersion: database.oracle.com/v4 kind: AutonomousDatabase metadata: name: autonomousdatabase-sample @@ -11,7 +11,7 @@ spec: details: id: ocid1.autonomousdatabase... # Set the patameter to false to allow both TLS and mutual TLS (mTLS) authentication, or true to require mTLS connections and disallow TLS connections. - isMTLSConnectionRequired: true + isMtlsConnectionRequired: true # Authorize the operator with API signing key pair. Comment out the ociConfig fields if your nodes are already authorized with instance principal. ociConfig: diff --git a/config/samples/adb/autonomousdatabase_update_network_access.yaml b/config/samples/adb/autonomousdatabase_update_network_access.yaml index 7dd3fa0c..2c704d53 100644 --- a/config/samples/adb/autonomousdatabase_update_network_access.yaml +++ b/config/samples/adb/autonomousdatabase_update_network_access.yaml @@ -2,7 +2,7 @@ # Copyright (c) 2022, Oracle and/or its affiliates. # Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. # -apiVersion: database.oracle.com/v1alpha1 +apiVersion: database.oracle.com/v4 kind: AutonomousDatabase metadata: name: autonomousdatabase-sample @@ -11,15 +11,13 @@ spec: details: id: ocid1.autonomousdatabase... # # Allow secure access from everywhere. Uncomment one of the following field depends on your network access configuration. - # accessControlList: - # - - # privateEndpoint: "" + # privateEndpointLabel: "" # # Uncomment this block to configure the network access type with the RESTRICTED option. # # This option lets you restrict access by defining access control rules in an Access Control List (ACL). - # # By specifying an ACL, the database will be accessible from a whitelisted set of IP addresses, CIDR (Classless Inter-Domain Routing) blocks, or VCNs. + # # By specifying an ACL, the database will be accessible from a whitelisted set of IP addresses, CIDR (Classless Inter-Domain Routing) blocks, or VCNs. # # Use a semicolon (;) as a deliminator between the VCN-specific subnets or IPs. - # accessControlList: + # whitelistedIps: # - 1.1.1.1 # - 1.1.0.0/16 # - ocid1.vcn... @@ -27,16 +25,16 @@ spec: # - ocid1.vcn...;1.1.0.0/16 # # Uncomment this block to configure the network access type with the PRIVATE option. - # # This option assigns a private endpoint, private IP, and hostname to your database. + # # This option assigns a private endpoint, private IP, and hostname to your database. # # Specifying this option allows traffic only from the VCN you specify. # # This allows you to define security rules, ingress/egress, at the Network Security Group (NSG) level and to control traffic to your Autonomous Database. - # privateEndpoint: - # subnetOCID: ocid1.subnet... - # nsgOCIDs: # Optional - # - ocid1.networksecuritygroup... + # subnetId: ocid1.subnet... + # nsgIds: + # - ocid1.networksecuritygroup... # # Uncomment this block to configure the network access of an dedicated Autonomous Database (ADB-D) with an access control list. - # accessControlList: + # isAccessControlEnabled: true + # whitelistedIps: # - 1.1.1.1 # - 1.1.0.0/16 diff --git a/config/samples/database_v4_oraclerestart.yaml b/config/samples/database_v4_oraclerestart.yaml new file mode 100644 index 00000000..de72184a --- /dev/null +++ b/config/samples/database_v4_oraclerestart.yaml @@ -0,0 +1,9 @@ +apiVersion: database.oracle.com/v4 +kind: OracleRestart +metadata: + labels: + app.kubernetes.io/name: oracle-database-operator + app.kubernetes.io/managed-by: kustomize + name: oraclerestart-sample +spec: + # TODO(user): Add fields here diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index 1a032832..da2a839d 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -5,47 +5,15 @@ ## Append samples you want in your CSV to this file as resources ## resources: - - multitenant/pdb_plug.yaml - - multitenant/cdb_secret.yaml - - multitenant/pdb_secret.yaml - - multitenant/pdb_clone.yaml - - multitenant/cdb.yaml - - sidb/singleinstancedatabase_patch.yaml - - sidb/oraclerestdataservice_apex.yaml - - sidb/singleinstancedatabase_express.yaml - - sidb/singleinstancedatabase_secrets.yaml - - sidb/singleinstancedatabase_clone.yaml - - sidb/singleinstancedatabase_prebuiltdb.yaml - - sidb/dataguardbroker.yaml - - sidb/oraclerestdataservice_secrets.yaml - - sidb/singleinstancedatabase_free.yaml - - sidb/singleinstancedatabase_standby.yaml - - sidb/openshift_rbac.yaml - - sharding/sharding_v1alpha1_provshard_clonespec1.yaml - - sharding/shardingdatabase.yaml - - sharding/sharding_v1alpha1_provshard_clonespec.yaml - - observability/databaseobserver_vault.yaml - - observability/databaseobserver_minimal.yaml - - adb/autonomousdatabase_bind.yaml - - adb/autonomousdatabase_backup.yaml - - adb/autonomousdatabase_restore.yaml - - acd/autonomouscontainerdatabase_create.yaml - - sidb/singleinstancedatabase.yaml - - sharding/shardingdatabase.yaml - - sharding/sharding_v1alpha1_provshard.yaml - - dbcs/database_v1alpha1_dbcssystem.yaml - - database_v1alpha1_dataguardbroker.yaml - - database_v1alpha1_shardingdatabase.yaml - - observability/v1alpha1/databaseobserver.yaml - - observability/v1/databaseobserver.yaml - - observability/v4/databaseobserver.yaml - - acd/autonomouscontainerdatabase_restart_terminate.yaml - - database_v4_shardingdatabase.yaml - - database_v4_dbcssystem.yaml -- database_v4_lrest.yaml -- database_v4_lrpdb.yaml -- database_v4_ordssrvs.yaml -- database_v4_singleinstancedatabase.yaml -- database_v4_dataguardbroker.yaml -- database_v4_oraclerestdataservice.yaml - # +kubebuilder:scaffold:manifestskustomizesamples +- acd/autonomouscontainerdatabase_create.yaml +- adb/autonomousdatabase_backup.yaml +- adb/autonomousdatabase_create.yaml +- dbcs/database_v1alpha1_dbcssystem.yaml +- multitenant/cdb.yaml +- multitenant/pdb_create.yaml +- observability/databaseobserver.yaml +- sharding/sharding_v1alpha1_provshard.yaml +- sidb/singleinstancedatabase_create.yaml +- omlai_v4_privateai.yaml +- database_v4_oraclerestart.yaml +# +kubebuilder:scaffold:manifestskustomizesamples diff --git a/config/samples/multitenant/cdb.yaml b/config/samples/multitenant/cdb.yaml deleted file mode 100644 index e3513d12..00000000 --- a/config/samples/multitenant/cdb.yaml +++ /dev/null @@ -1,43 +0,0 @@ -# -# Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. -# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. -# -apiVersion: database.oracle.com/v1alpha1 -kind: CDB -metadata: - name: cdb-dev - namespace: oracle-database-operator-system -spec: - cdbName: "devcdb" - dbServer: "172.17.0.4" - dbPort: 1521 - replicas: 1 - ordsImage: "" - ordsImagePullPolicy: "Always" - # Uncomment Below Secret Format for accessing ords image from private docker registry - # ordsImagePullSecret: "" - serviceName: "devdb.example.com" - sysAdminPwd: - secret: - secretName: "cdb1-secret" - key: "sysadmin_pwd" - ordsPwd: - secret: - secretName: "cdb1-secret" - key: "ords_pwd" - cdbAdminUser: - secret: - secretName: "cdb1-secret" - key: "cdbadmin_user" - cdbAdminPwd: - secret: - secretName: "cdb1-secret" - key: "cdbadmin_pwd" - webServerUser: - secret: - secretName: "cdb1-secret" - key: "webserver_user" - webServerPwd: - secret: - secretName: "cdb1-secret" - key: "webserver_pwd" diff --git a/config/samples/multitenant/cdb_secret.yaml b/config/samples/multitenant/cdb_secret.yaml deleted file mode 100644 index e270100d..00000000 --- a/config/samples/multitenant/cdb_secret.yaml +++ /dev/null @@ -1,17 +0,0 @@ -# -# Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. -# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. -# -apiVersion: v1 -kind: Secret -metadata: - name: cdb1-secret - namespace: oracle-database-operator-system -type: Opaque -data: - ords_pwd: "[base64 encode value]" - sysadmin_pwd: "[base64 encode value]" - cdbadmin_user: "[base64 encode value]" - cdbadmin_pwd: "[base64 encode value]" - webserver_user: "[base64 encode values]" - webserver_pwd: "[base64 encode values]" diff --git a/config/samples/multitenant/pdb_clone.yaml b/config/samples/multitenant/pdb_clone.yaml deleted file mode 100644 index f36e904d..00000000 --- a/config/samples/multitenant/pdb_clone.yaml +++ /dev/null @@ -1,27 +0,0 @@ -# -# Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. -# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. -# -apiVersion: database.oracle.com/v1alpha1 -kind: PDB -metadata: - name: pdb1-clone - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbName: "devcdb" - pdbName: "pdbdevclone" - adminName: - secret: - secretName: "pdb1-secret" - key: "sysadmin_user" - adminPwd: - secret: - secretName: "pdb1-secret" - key: "sysadmin_pwd" - fileNameConversions: "NONE" - totalSize: "1G" - tempSize: "100M" - action: "Clone" diff --git a/config/samples/multitenant/pdb_create.yaml b/config/samples/multitenant/pdb_create.yaml deleted file mode 100644 index 2be31acf..00000000 --- a/config/samples/multitenant/pdb_create.yaml +++ /dev/null @@ -1,27 +0,0 @@ -# -# Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. -# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. -# -apiVersion: database.oracle.com/v1alpha1 -kind: PDB -metadata: - name: pdb1 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbName: "devcdb" - pdbName: "pdbdev" - adminName: - secret: - secretName: "pdb1-secret" - key: "sysadmin_user" - adminPwd: - secret: - secretName: "pdb1-secret" - key: "sysadmin_pwd" - fileNameConversions: "NONE" - totalSize: "1G" - tempSize: "100M" - action: "Create" diff --git a/config/samples/multitenant/pdb_delete.yaml b/config/samples/multitenant/pdb_delete.yaml deleted file mode 100644 index 6c5299c0..00000000 --- a/config/samples/multitenant/pdb_delete.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# -# Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. -# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. -# -apiVersion: database.oracle.com/v1alpha1 -kind: PDB -metadata: - name: pdb1 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - pdbName: "pdbdev" - action: "Delete" - dropAction: "INCLUDING" \ No newline at end of file diff --git a/config/samples/multitenant/pdb_modify.yaml b/config/samples/multitenant/pdb_modify.yaml deleted file mode 100644 index feac2dbf..00000000 --- a/config/samples/multitenant/pdb_modify.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# -# Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. -# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. -# -apiVersion: database.oracle.com/v1alpha1 -kind: PDB -metadata: - name: pdb1 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbName: "democdb" - pdbName: "demotest" - action: "Modify" - pdbState: "CLOSE" - modifyOption: "IMMEDIATE" - - # To Open an existing PDB, uncomment the below lines and comment the two lines above - #pdbState: "OPEN" - #modifyOption: "READ WRITE" \ No newline at end of file diff --git a/config/samples/multitenant/pdb_plug.yaml b/config/samples/multitenant/pdb_plug.yaml deleted file mode 100644 index b48c4ffc..00000000 --- a/config/samples/multitenant/pdb_plug.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# -# Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. -# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. -# -apiVersion: database.oracle.com/v1alpha1 -kind: PDB -metadata: - name: pdb1 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - pdbName: "pdbdev" - xmlFileName: "/opt/oracle/oradata/pdbdev.xml" - sourceFileNameConversions: "NONE" - fileNameConversions: "NONE" - copyAction: "NOCOPY" - action: "Plug" \ No newline at end of file diff --git a/config/samples/multitenant/pdb_secret.yaml b/config/samples/multitenant/pdb_secret.yaml deleted file mode 100644 index 8a3202d9..00000000 --- a/config/samples/multitenant/pdb_secret.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# -# Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. -# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. -# -apiVersion: v1 -kind: Secret -metadata: - name: pdb1-secret - namespace: oracle-database-operator-system -type: Opaque -data: - sysadmin_user: "[ base64 encode value]" - sysadmin_pwd: "[ base64 encode value]" diff --git a/config/samples/multitenant/pdb_unplug.yaml b/config/samples/multitenant/pdb_unplug.yaml deleted file mode 100644 index 21d7b187..00000000 --- a/config/samples/multitenant/pdb_unplug.yaml +++ /dev/null @@ -1,27 +0,0 @@ -# -# Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. -# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. -# -apiVersion: database.oracle.com/v1alpha1 -kind: PDB -metadata: - name: pdb1 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - pdbName: "pdbdev" - xmlFileName: "/opt/oracle/oradata/demotest_pdb.xml" - action: "Unplug" - tdeExport: true - tdeSecret: - secret: - secretName: "pdb1-secret" - key: "tde_secret" - tdeKeystorePath: "/opt/oracle/test" - tdePassword: - secret: - secretName: "pdb1-secret" - key: "tde_pwd" - getScript: true \ No newline at end of file diff --git a/config/samples/observability/databaseobserver.yaml b/config/samples/observability/databaseobserver.yaml index b3140549..8b5245cc 100644 --- a/config/samples/observability/databaseobserver.yaml +++ b/config/samples/observability/databaseobserver.yaml @@ -1,5 +1,5 @@ # example -apiVersion: observability.oracle.com/v1alpha1 +apiVersion: observability.oracle.com/v4 kind: DatabaseObserver metadata: name: obs-sample @@ -17,28 +17,9 @@ spec: key: "connection" secret: db-secret - dbWallet: - secret: instance-wallet - - exporter: - image: "container-registry.oracle.com/database/observability-exporter:latest" - configuration: - configmap: - key: "config.toml" - configmapName: "devcm-oradevdb-config" - - service: - port: 9161 - - prometheus: - port: metrics - labels: - app: app-sample-label - - replicas: 1 - - ociConfig: - configMapName: oci-cred - secretName: oci-privatekey - + wallet: + secret: instance-wallet + serviceMonitor: + labels: + release: prometheus diff --git a/config/samples/observability/databaseobserver_custom_config.yaml b/config/samples/observability/databaseobserver_custom_config.yaml deleted file mode 100644 index 1e9fff47..00000000 --- a/config/samples/observability/databaseobserver_custom_config.yaml +++ /dev/null @@ -1,28 +0,0 @@ -# example -apiVersion: observability.oracle.com/v1alpha1 -kind: DatabaseObserver -metadata: - name: obs-sample - namespace: observer -spec: - database: - dbUser: - key: "username" - secret: db-secret - - dbPassword: - key: "password" - secret: db-secret - - dbConnectionString: - key: "connection" - secret: db-secret - - dbWallet: - secret: instance-wallet - - exporter: - configuration: - configmap: - key: "config.toml" - configmapName: "devcm-oradevdb-config" \ No newline at end of file diff --git a/config/samples/observability/databaseobserver_minimal.yaml b/config/samples/observability/databaseobserver_minimal.yaml deleted file mode 100644 index 2eeaf3ab..00000000 --- a/config/samples/observability/databaseobserver_minimal.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# example -apiVersion: observability.oracle.com/v1alpha1 -kind: DatabaseObserver -metadata: - name: obs-sample - namespace: observer -spec: - database: - dbUser: - key: "username" - secret: db-secret - - dbPassword: - key: "password" - secret: db-secret - - dbConnectionString: - key: "connection" - secret: db-secret - - dbWallet: - secret: instance-wallets \ No newline at end of file diff --git a/config/samples/observability/databaseobserver_vault.yaml b/config/samples/observability/databaseobserver_vault.yaml deleted file mode 100644 index fa2e09d4..00000000 --- a/config/samples/observability/databaseobserver_vault.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# example -apiVersion: observability.oracle.com/v1alpha1 -kind: DatabaseObserver -metadata: - name: obs-sample -spec: - database: - dbUser: - key: "username" - secret: db-secret - - dbPassword: - vaultSecretName: sample_secret - vaultOCID: ocid1.vault.oc1.. - - dbConnectionString: - key: "connection" - secret: db-secret - - dbWallet: - secret: instance-wallet - - ociConfig: - configMapName: oci-cred - secretName: oci-privatekey \ No newline at end of file diff --git a/config/samples/observability/v1/databaseobserver.yaml b/config/samples/observability/v1/databaseobserver.yaml index 82a7e89e..26ffe3ff 100644 --- a/config/samples/observability/v1/databaseobserver.yaml +++ b/config/samples/observability/v1/databaseobserver.yaml @@ -6,7 +6,7 @@ metadata: labels: app.kubernetes.io/name: observability-exporter app.kubernetes.io/instance: obs-sample - app.kubernetes.io/version: 1.5.1 + app.kubernetes.io/version: 2.0.2 spec: database: dbUser: @@ -21,61 +21,56 @@ spec: key: "connection" secret: db-secret - dbWallet: - secret: instance-wallet - - inheritLabels: - - app.kubernetes.io/name - - app.kubernetes.io/instance - - app.kubernetes.io/version - - sidecars: [ ] - sidecarVolumes: [ ] - - exporter: - deployment: - env: - TNS_ADMIN: /some/custom/path - ORACLE_HOME: /some/custom/path - DB_ROLE: SYSDBA - image: "container-registry.oracle.com/database/observability-exporter:1.5.1" - args: [ "--log.level=info" ] - commands: [ "/oracledb_exporter" ] - - labels: - environment: dev - podTemplate: - labels: - environment: dev - - service: + wallet: + secret: instance-wallet + + sidecar: + containers: [ ] + volumes: [ ] + + deployment: + env: + TNS_ADMIN: /some/custom/path + ORACLE_HOME: /some/custom/path + DB_ROLE: SYSDBA + image: "container-registry.oracle.com/database/observability-exporter:2.0.2" + args: [ "--log.level=info" ] + commands: [ "/oracledb_exporter" ] + + labels: + environment: dev + podTemplate: labels: environment: dev - configuration: - configMap: - key: "config.toml" - name: "devcm-oradevdb-config" + service: + labels: + environment: dev prometheus: serviceMonitor: labels: release: prometheus + metrics: + configMap: + - name: "devcm-oradevdb-config" + key: "config.toml" log: filename: "alert.log" - path: "/log" - + destination: "/log" volume: - name: volume persistentVolumeClaim: claimName: "my-pvc" - replicas: 1 - ociConfig: - configMapName: oci-cred - secretName: oci-privatekey + configMap: + key: config + name: oci-cred + privateKey: + secret: oci-privatekey + mountPath: "/.oci" + replicas: 1 diff --git a/config/samples/observability/v1/databaseobserver_customization_fields.yaml b/config/samples/observability/v1/databaseobserver_customization_fields.yaml index d88caec4..5cc5d319 100644 --- a/config/samples/observability/v1/databaseobserver_customization_fields.yaml +++ b/config/samples/observability/v1/databaseobserver_customization_fields.yaml @@ -17,38 +17,40 @@ spec: key: "connection" secret: db-secret - dbWallet: - secret: instance-wallets - - exporter: - deployment: - image: "container-registry.oracle.com/database/observability-exporter:1.5.1" - args: - - "--log.level=info" - commands: - - "/oracledb_exporter" - env: - TNS_ADMIN: /some/custom/path + wallet: + secret: instance-wallet + + + + deployment: + image: "container-registry.oracle.com/database/observability-exporter:2.0.2" + args: + - "--log.level=info" + commands: + - "/oracledb_exporter" + env: + TNS_ADMIN: /some/custom/path + labels: + environment: dev + podTemplate: labels: environment: dev - podTemplate: - labels: - environment: dev - service: - ports: + + service: + ports: - name: "metrics" port: 9161 targetPort: 9161 - labels: - environment: dev + labels: + environment: dev - prometheus: - serviceMonitor: - endpoints: - - bearerTokenSecret: - key: '' - interval: 15s - port: metrics - labels: - release: prometheus + + serviceMonitor: + endpoints: + - bearerTokenSecret: + key: '' + interval: 15s + port: metrics + labels: + release: prometheus diff --git a/config/samples/observability/v1/databaseobserver_logs_promtail.yaml b/config/samples/observability/v1/databaseobserver_logs_promtail.yaml index 8130f487..31a56573 100644 --- a/config/samples/observability/v1/databaseobserver_logs_promtail.yaml +++ b/config/samples/observability/v1/databaseobserver_logs_promtail.yaml @@ -6,7 +6,7 @@ metadata: labels: app.kubernetes.io/name: observability-exporter app.kubernetes.io/instance: obs-sample - app.kubernetes.io/version: 1.5.1 + app.kubernetes.io/version: 2.0.2 spec: database: dbUser: @@ -21,54 +21,48 @@ spec: key: "connection" secret: db-secret - dbWallet: - secret: instance-wallet + wallet: + secret: instance-wallet + inheritLabels: - app.kubernetes.io/name - app.kubernetes.io/instance - app.kubernetes.io/version - sidecars: - - name: promtail - image: grafana/promtail - args: - - -config.file=/etc/promtail/promtail.yaml - volumeMounts: - - name: config - mountPath: /etc/promtail - - name: log-volume - mountPath: /log + sidecar: + containers: + - name: promtail + image: grafana/promtail + args: + - -config.file=/etc/promtail/promtail.yaml + volumeMounts: + - name: config + mountPath: /etc/promtail + - name: log-volume + mountPath: /log + volumes: + - name: config + configMap: + name: promtail-sidecar-config - sidecarVolumes: - - name: config - configMap: - name: promtail-sidecar-config - exporter: - deployment: - image: "container-registry.oracle.com/database/observability-exporter:1.5.1" - args: [ "--log.level=info" ] - commands: [ "/oracledb_exporter" ] + deployment: + image: "container-registry.oracle.com/database/observability-exporter:2.0.2" + args: [ "--log.level=info" ] + commands: [ "/oracledb_exporter" ] - configuration: + metrics: configMap: - key: "config.toml" - name: "devcm-oradevdb-config" + - name: "devcm-oradevdb-config" + key: "config.toml" + - prometheus: - serviceMonitor: - labels: - release: prometheus + serviceMonitor: + labels: + release: prometheus log: + destination: "/log" filename: "alert.log" - path: "/log" - - volume: - name: log-volume replicas: 1 - - ociConfig: - configMapName: oci-cred - secretName: oci-privatekey diff --git a/config/samples/observability/v1alpha1/databaseobserver.yaml b/config/samples/observability/v1alpha1/databaseobserver.yaml index 24672d8b..077d4941 100644 --- a/config/samples/observability/v1alpha1/databaseobserver.yaml +++ b/config/samples/observability/v1alpha1/databaseobserver.yaml @@ -6,7 +6,7 @@ metadata: labels: app.kubernetes.io/name: observability-exporter app.kubernetes.io/instance: obs-sample - app.kubernetes.io/version: 1.5.1 + app.kubernetes.io/version: 2.0.2 spec: database: dbUser: @@ -21,8 +21,9 @@ spec: key: "connection" secret: db-secret - dbWallet: - secret: instance-wallet + wallet: + secret: instance-wallet + inheritLabels: @@ -30,51 +31,57 @@ spec: - app.kubernetes.io/instance - app.kubernetes.io/version - sidecars: [ ] - sidecarVolumes: [ ] - - exporter: - deployment: - image: "container-registry.oracle.com/database/observability-exporter:1.5.1" - args: [ "--log.level=info" ] - commands: [ "/oracledb_exporter" ] - env: - TNS_ADMIN: /some/custom/path - ORACLE_HOME: /some/custom/path - labels: - environment: dev - podTemplate: - labels: - environment: dev + sidecar: + containers: [ ] + volumes: [ ] + + + deployment: + env: + TNS_ADMIN: /some/custom/path + ORACLE_HOME: /some/custom/path + DB_ROLE: SYSDBA + image: "container-registry.oracle.com/database/observability-exporter:2.0.2" + args: [ "--log.level=info" ] + commands: [ "/oracledb_exporter" ] - service: + labels: + environment: dev + podTemplate: labels: environment: dev - configuration: + service: + labels: + environment: dev + + metrics: configMap: - key: "config.toml" - name: "devcm-oradevdb-config" + - name: "devcm-oradevdb-config" + key: "config.toml" - prometheus: - serviceMonitor: - labels: - release: prometheus + serviceMonitor: + labels: + release: prometheus log: filename: "alert.log" - path: "/log" + destination: "/log" volume: - name: volume persistentVolumeClaim: claimName: "my-pvc" + ociConfig: + configMap: + key: config + name: oci-cred + privateKey: + secret: oci-privatekey + mountPath: "/.oci" + replicas: 1 - ociConfig: - configMapName: oci-cred - secretName: oci-privatekey diff --git a/config/samples/observability/v1alpha1/databaseobserver_custom_config.yaml b/config/samples/observability/v1alpha1/databaseobserver_custom_config.yaml index 8e0d0623..e0ad4e9c 100644 --- a/config/samples/observability/v1alpha1/databaseobserver_custom_config.yaml +++ b/config/samples/observability/v1alpha1/databaseobserver_custom_config.yaml @@ -3,10 +3,6 @@ apiVersion: observability.oracle.com/v1alpha1 kind: DatabaseObserver metadata: name: obs-sample - labels: - app.kubernetes.io/name: observability-exporter - app.kubernetes.io/instance: obs-sample - app.kubernetes.io/version: 1.5.1 spec: database: dbUser: @@ -21,26 +17,21 @@ spec: key: "connection" secret: db-secret - dbWallet: - secret: instance-wallet + wallet: + secret: instance-wallet - inherit_labels: - - app.kubernetes.io/name - - app.kubernetes.io/instance - - app.kubernetes.io/version - exporter: - deployment: - image: "container-registry.oracle.com/database/observability-exporter:1.5.1" - args: [ "--log.level=info" ] - commands: [ "/oracledb_exporter" ] + deployment: + image: "container-registry.oracle.com/database/observability-exporter:2.0.2" + args: [ "--log.level=info" ] + commands: [ "/oracledb_exporter" ] - prometheus: - serviceMonitor: - labels: - release: prometheus - configuration: + serviceMonitor: + labels: + release: prometheus + + metrics: configMap: - key: "config.toml" - name: "devcm-oradevdb-config" \ No newline at end of file + - name: "devcm-oradevdb-config" + key: "config.toml" diff --git a/config/samples/observability/v1alpha1/databaseobserver_logs_promtail.yaml b/config/samples/observability/v1alpha1/databaseobserver_logs_promtail.yaml index 28592cb0..f2345bee 100644 --- a/config/samples/observability/v1alpha1/databaseobserver_logs_promtail.yaml +++ b/config/samples/observability/v1alpha1/databaseobserver_logs_promtail.yaml @@ -3,10 +3,6 @@ apiVersion: observability.oracle.com/v1alpha1 kind: DatabaseObserver metadata: name: obs-sample - labels: - app.kubernetes.io/name: observability-exporter - app.kubernetes.io/instance: obs-sample - app.kubernetes.io/version: 1.5.1 spec: database: dbUser: @@ -21,54 +17,41 @@ spec: key: "connection" secret: db-secret - dbWallet: - secret: instance-wallet - - inheritLabels: - - app.kubernetes.io/name - - app.kubernetes.io/instance - - app.kubernetes.io/version - - sidecars: - - name: promtail - image: grafana/promtail - args: - - -config.file=/etc/promtail/promtail.yaml - volumeMounts: - - name: config - mountPath: /etc/promtail - - name: log-volume - mountPath: /log - - sidecarVolumes: - - name: config - configMap: - name: promtail-sidecar-config - exporter: - deployment: - image: "container-registry.oracle.com/database/observability-exporter:1.5.1" - args: [ "--log.level=info" ] - commands: [ "/oracledb_exporter" ] - - configuration: + wallet: + secret: instance-wallet + + sidecar: + containers: + - name: promtail + image: grafana/promtail + args: + - -config.file=/etc/promtail/promtail.yaml + volumeMounts: + - name: config + mountPath: /etc/promtail + - name: log-volume + mountPath: /log + volumes: + - name: config + configMap: + name: promtail-sidecar-config + + deployment: + image: "container-registry.oracle.com/database/observability-exporter:1.5.1" + args: [ "--log.level=info" ] + commands: [ "/oracledb_exporter" ] + + serviceMonitor: + labels: + release: prometheus + metrics: configMap: - key: "config.toml" - name: "devcm-oradevdb-config" - - prometheus: - serviceMonitor: - labels: - release: prometheus + - name: "devcm-oradevdb-config" + key: "config.toml" log: + destination: "/log" filename: "alert.log" - path: "/log" - - volume: - name: log-volume replicas: 1 - ociConfig: - configMapName: oci-cred - secretName: oci-privatekey diff --git a/config/samples/observability/v1alpha1/databaseobserver_minimal.yaml b/config/samples/observability/v1alpha1/databaseobserver_minimal.yaml index 74620ac7..9c2044bf 100644 --- a/config/samples/observability/v1alpha1/databaseobserver_minimal.yaml +++ b/config/samples/observability/v1alpha1/databaseobserver_minimal.yaml @@ -6,21 +6,18 @@ metadata: spec: database: dbUser: - key: "username" secret: db-secret dbPassword: - key: "password" secret: db-secret dbConnectionString: - key: "connection" secret: db-secret - dbWallet: - secret: instance-wallets + wallet: + secret: instance-wallet - prometheus: - serviceMonitor: - labels: - release: prometheus \ No newline at end of file + + serviceMonitor: + labels: + release: prometheus \ No newline at end of file diff --git a/config/samples/observability/v1alpha1/databaseobserver_vault.yaml b/config/samples/observability/v1alpha1/databaseobserver_vault.yaml deleted file mode 100644 index 2fc3c9f0..00000000 --- a/config/samples/observability/v1alpha1/databaseobserver_vault.yaml +++ /dev/null @@ -1,30 +0,0 @@ -# example -apiVersion: observability.oracle.com/v1alpha1 -kind: DatabaseObserver -metadata: - name: obs-sample -spec: - database: - dbUser: - key: "username" - secret: db-secret - - dbPassword: - vaultSecretName: sample_secret - vaultOCID: ocid1.vault.oc1.. - - dbConnectionString: - key: "connection" - secret: db-secret - - dbWallet: - secret: instance-wallet - - prometheus: - serviceMonitor: - labels: - release: prometheus - - ociConfig: - configMapName: oci-cred - secretName: oci-privatekey \ No newline at end of file diff --git a/config/samples/observability/v1alpha1/databaseobserver_vault_oci.yaml b/config/samples/observability/v1alpha1/databaseobserver_vault_oci.yaml new file mode 100644 index 00000000..5efb9bff --- /dev/null +++ b/config/samples/observability/v1alpha1/databaseobserver_vault_oci.yaml @@ -0,0 +1,29 @@ +# example +apiVersion: observability.oracle.com/v1alpha1 +kind: DatabaseObserver +metadata: + name: obs-sample +spec: + database: + dbUser: + secret: db-secret + dbConnectionString: + secret: db-secret + oci: + vaultID: ocid1.vault.oc1.. + vaultPasswordSecret: sample_secret + + wallet: + secret: instance-wallet + + serviceMonitor: + labels: + release: prometheus + + ociConfig: + configMap: + key: config + name: oci-cred + privateKey: + secret: oci-privatekey + mountPath: "/.oci" \ No newline at end of file diff --git a/config/samples/observability/v4/databaseobserver.yaml b/config/samples/observability/v4/databaseobserver.yaml index f7b310f7..1abcc721 100644 --- a/config/samples/observability/v4/databaseobserver.yaml +++ b/config/samples/observability/v4/databaseobserver.yaml @@ -21,59 +21,60 @@ spec: key: "connection" secret: db-secret - dbWallet: - secret: instance-wallet + wallet: + secret: instance-wallet inheritLabels: - app.kubernetes.io/name - app.kubernetes.io/instance - app.kubernetes.io/version - sidecars: [ ] - sidecarVolumes: [ ] - - exporter: - deployment: - image: "container-registry.oracle.com/database/observability-exporter:1.5.1" - args: [ "--log.level=info" ] - commands: [ "/oracledb_exporter" ] - env: - TNS_ADMIN: /some/custom/path - ORACLE_HOME: /some/custom/path + sidecar: + containers: [ ] + volumes: [ ] + + deployment: + env: + TNS_ADMIN: /some/custom/path + ORACLE_HOME: /some/custom/path + DB_ROLE: SYSDBA + image: "container-registry.oracle.com/database/observability-exporter:2.0.2" + args: [ "--log.level=info" ] + commands: [ "/oracledb_exporter" ] + + labels: + environment: dev + podTemplate: labels: environment: dev - podTemplate: - labels: - environment: dev - service: - labels: - environment: dev + service: + labels: + environment: dev + serviceMonitor: + labels: + release: prometheus - configuration: + metrics: configMap: - key: "config.toml" - name: "devcm-oradevdb-config" - - prometheus: - serviceMonitor: - labels: - release: prometheus + - name: "devcm-oradevdb-config" + key: "config.toml" log: filename: "alert.log" - path: "/log" + destination: "/log" volume: - name: volume persistentVolumeClaim: claimName: "my-pvc" - replicas: 1 - ociConfig: - configMapName: oci-cred - secretName: oci-privatekey - + configMap: + key: config + name: oci-cred + privateKey: + secret: oci-privatekey + mountPath: "/.oci" + replicas: 1 diff --git a/config/samples/observability/v4/databaseobserver_custom_config.yaml b/config/samples/observability/v4/databaseobserver_custom_config.yaml index dd2e3da5..e82f7478 100644 --- a/config/samples/observability/v4/databaseobserver_custom_config.yaml +++ b/config/samples/observability/v4/databaseobserver_custom_config.yaml @@ -6,7 +6,7 @@ metadata: labels: app.kubernetes.io/name: observability-exporter app.kubernetes.io/instance: obs-sample - app.kubernetes.io/version: latest + app.kubernetes.io/version: 2.0.2 spec: database: dbUser: @@ -21,26 +21,26 @@ spec: key: "connection" secret: db-secret - dbWallet: - secret: instance-wallet + wallet: + secret: instance-wallet inherit_labels: - app.kubernetes.io/name - app.kubernetes.io/instance - app.kubernetes.io/version - exporter: - deployment: - image: "container-registry.oracle.com/database/observability-exporter:1.5.1" - args: [ "--log.level=info" ] - commands: [ "/oracledb_exporter" ] - configuration: + deployment: + image: "container-registry.oracle.com/database/observability-exporter:1.5.1" + args: [ "--log.level=info" ] + commands: [ "/oracledb_exporter" ] + + metrics: configMap: - key: "config.toml" - name: "devcm-oradevdb-config" + - name: "devcm-oradevdb-config" + key: "config.toml" + - prometheus: - serviceMonitor: - labels: - release: prometheus \ No newline at end of file + serviceMonitor: + labels: + release: prometheus \ No newline at end of file diff --git a/config/samples/observability/v4/databaseobserver_exporter_config_file.yaml b/config/samples/observability/v4/databaseobserver_exporter_config_file.yaml new file mode 100644 index 00000000..fac3359e --- /dev/null +++ b/config/samples/observability/v4/databaseobserver_exporter_config_file.yaml @@ -0,0 +1,42 @@ +# example +apiVersion: observability.oracle.com/v4 +kind: DatabaseObserver +metadata: + name: obs-sample +spec: + + deployment: + args: + - "--config.file=/config/config.yaml" + + # alternatively, the CONFIG_FILE environment variable can be set + #env: + # CONFIG_FILE: "/config/config.yaml" + + exporterConfig: + configMap: + key: config.yaml + name: exporter-config-file + + databases: + db1: + dbUser: + secret: db1-secret + dbPassword: + secret: db1-secret + dbConnectionString: + secret: db1-secret + db2: + dbUser: + secret: db2-secret + dbPassword: + secret: db2-secret + dbConnectionString: + secret: db2-secret + wallet: + secret: instance-wallet + + + serviceMonitor: + labels: + release: prometheus diff --git a/config/samples/observability/v4/databaseobserver_logs_promtail.yaml b/config/samples/observability/v4/databaseobserver_logs_promtail.yaml index 26a747a3..ae639308 100644 --- a/config/samples/observability/v4/databaseobserver_logs_promtail.yaml +++ b/config/samples/observability/v4/databaseobserver_logs_promtail.yaml @@ -6,7 +6,7 @@ metadata: labels: app.kubernetes.io/name: observability-exporter app.kubernetes.io/instance: obs-sample - app.kubernetes.io/version: latest + app.kubernetes.io/version: 2.0.2 spec: database: dbUser: @@ -21,56 +21,54 @@ spec: key: "connection" secret: db-secret - dbWallet: - secret: instance-wallet + wallet: + secret: instance-wallet inheritLabels: - app.kubernetes.io/name - app.kubernetes.io/instance - app.kubernetes.io/version - sidecars: - - name: promtail - image: grafana/promtail - args: - - -config.file=/etc/promtail/promtail.yaml - volumeMounts: - - name: config - mountPath: /etc/promtail - - name: log-volume - mountPath: /log + sidecar: + containers: + - name: promtail + image: grafana/promtail + args: + - -config.file=/etc/promtail/promtail.yaml + volumeMounts: + - name: config + mountPath: /etc/promtail + - name: log-volume + mountPath: /log + volumes: + - name: config + configMap: + name: promtail-sidecar-config + + + deployment: + image: "container-registry.oracle.com/database/observability-exporter:2.0.2" + args: [ "--log.level=info" ] + commands: [ "/oracledb_exporter" ] - sidecarVolumes: - - name: config - configMap: - name: promtail-sidecar-config - exporter: - deployment: - image: "container-registry.oracle.com/database/observability-exporter:1.5.1" - args: [ "--log.level=info" ] - commands: [ "/oracledb_exporter" ] - configuration: - configMap: - key: "config.toml" - name: "devcm-oradevdb-config" - prometheus: - serviceMonitor: - labels: - release: prometheus + serviceMonitor: + labels: + release: prometheus + + metrics: + configMap: + - name: "devcm-oradevdb-config" + key: "config.toml" log: + destination: "/log" filename: "alert.log" - path: "/log" - volume: - name: log-volume - - replicas: 1 ociConfig: configMapName: oci-cred secretName: oci-privatekey - + replicas: 1 diff --git a/config/samples/observability/v4/databaseobserver_minimal.yaml b/config/samples/observability/v4/databaseobserver_minimal.yaml index cc14fbea..9f913058 100644 --- a/config/samples/observability/v4/databaseobserver_minimal.yaml +++ b/config/samples/observability/v4/databaseobserver_minimal.yaml @@ -6,21 +6,18 @@ metadata: spec: database: dbUser: - key: "username" secret: db-secret dbPassword: - key: "password" secret: db-secret dbConnectionString: - key: "connection" secret: db-secret - dbWallet: - secret: instance-wallets + wallet: + secret: instance-wallet - prometheus: - serviceMonitor: - labels: - release: prometheus + + serviceMonitor: + labels: + release: prometheus diff --git a/config/samples/observability/v4/databaseobserver_vault.yaml b/config/samples/observability/v4/databaseobserver_vault.yaml deleted file mode 100644 index 4f5845f6..00000000 --- a/config/samples/observability/v4/databaseobserver_vault.yaml +++ /dev/null @@ -1,39 +0,0 @@ -# example -apiVersion: observability.oracle.com/v4 -kind: DatabaseObserver -metadata: - name: obs-sample - labels: - app.kubernetes.io/name: observability-exporter - app.kubernetes.io/instance: obs-sample - app.kubernetes.io/version: latest -spec: - database: - dbUser: - key: "username" - secret: db-secret - - dbPassword: - vaultSecretName: sample_secret - vaultOCID: ocid1.vault.oc1.. - - dbConnectionString: - key: "connection" - secret: db-secret - - dbWallet: - secret: instance-wallet - - inherit_labels: - - app.kubernetes.io/name - - app.kubernetes.io/instance - - app.kubernetes.io/version - - prometheus: - serviceMonitor: - labels: - release: prometheus - - ociConfig: - configMapName: oci-cred - secretName: oci-privatekey \ No newline at end of file diff --git a/config/samples/observability/v4/databaseobserver_vault_azure.yaml b/config/samples/observability/v4/databaseobserver_vault_azure.yaml new file mode 100644 index 00000000..2c83ccc8 --- /dev/null +++ b/config/samples/observability/v4/databaseobserver_vault_azure.yaml @@ -0,0 +1,24 @@ +# example +apiVersion: observability.oracle.com/v4 +kind: DatabaseObserver +metadata: + name: obs-sample +spec: + database: + dbConnectionString: + secret: db-secret + azure: + vaultID: "..." + vaultUsernameSecret: sample_un_secret + vaultPasswordSecret: sample_pwd_secret + + wallet: + secret: instance-wallet + + serviceMonitor: + labels: + release: prometheus + + azureConfig: + configMap: + name: azure-cred \ No newline at end of file diff --git a/config/samples/observability/v4/databaseobserver_vault_oci.yaml b/config/samples/observability/v4/databaseobserver_vault_oci.yaml new file mode 100644 index 00000000..9e60c16f --- /dev/null +++ b/config/samples/observability/v4/databaseobserver_vault_oci.yaml @@ -0,0 +1,29 @@ +# example +apiVersion: observability.oracle.com/v4 +kind: DatabaseObserver +metadata: + name: obs-sample +spec: + database: + dbUser: + secret: db-secret + dbConnectionString: + secret: db-secret + oci: + vaultID: ocid1.vault.oc1.. + vaultPasswordSecret: sample_secret + + wallet: + secret: instance-wallet + + serviceMonitor: + labels: + release: prometheus + + ociConfig: + configMap: + key: config + name: oci-cred + privateKey: + secret: oci-privatekey + mountPath: "/.oci" \ No newline at end of file diff --git a/config/samples/sidb/singleinstancedatabase.yaml b/config/samples/sidb/singleinstancedatabase.yaml index 368762f5..a99a569c 100644 --- a/config/samples/sidb/singleinstancedatabase.yaml +++ b/config/samples/sidb/singleinstancedatabase.yaml @@ -118,6 +118,9 @@ spec: ## memory is measured in bytes and can be expressed in plain integer or as a fixed-point number ## using one of these quantity suffixes: E, P, T, G, M, k. ## You can also use the power-of-two equivalents: Ei, Pi, Ti, Gi, Mi, Ki. + ## For Enterprise Edition, the recommended values are: + ## cpu="2" + ## memory="16Gi" resources: ## requests denotes minimum node resources required/to be utilized by the database pod requests: diff --git a/config/scorecard/kustomization.yaml b/config/scorecard/kustomization.yaml index bf4c1e7c..576c02a4 100644 --- a/config/scorecard/kustomization.yaml +++ b/config/scorecard/kustomization.yaml @@ -4,17 +4,18 @@ # resources: - bases/config.yaml -patchesJson6902: +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +patches: - path: patches/basic.config.yaml target: group: scorecard.operatorframework.io - version: v1alpha3 kind: Configuration name: config + version: v1alpha3 - path: patches/olm.config.yaml target: group: scorecard.operatorframework.io - version: v1alpha3 kind: Configuration name: config -# +kubebuilder:scaffold:patchesJson6902 + version: v1alpha3 diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml index b186a5b0..43539626 100644 --- a/config/webhook/manifests.yaml +++ b/config/webhook/manifests.yaml @@ -4,47 +4,6 @@ kind: MutatingWebhookConfiguration metadata: name: mutating-webhook-configuration webhooks: -- admissionReviewVersions: - - v1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /mutate-database-oracle-com-v4-autonomousdatabasebackup - failurePolicy: Fail - name: mautonomousdatabasebackupv4.kb.io - rules: - - apiGroups: - - database.oracle.com - apiVersions: - - v4 - operations: - - CREATE - - UPDATE - resources: - - autonomousdatabasebackups - sideEffects: None -- admissionReviewVersions: - - v1 - - v1beta1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /mutate-database-oracle-com-v4-cdb - failurePolicy: Fail - name: mcdb.kb.io - rules: - - apiGroups: - - database.oracle.com - apiVersions: - - v4 - operations: - - CREATE - - UPDATE - resources: - - cdbs - sideEffects: None - admissionReviewVersions: - v1 clientConfig: @@ -109,14 +68,13 @@ webhooks: sideEffects: None - admissionReviewVersions: - v1 - - v1beta1 clientConfig: service: name: webhook-service namespace: system - path: /mutate-database-oracle-com-v4-pdb + path: /mutate-database-oracle-com-v4-oraclerestart failurePolicy: Fail - name: mpdb.kb.io + name: moraclerestart.kb.io rules: - apiGroups: - database.oracle.com @@ -126,7 +84,7 @@ webhooks: - CREATE - UPDATE resources: - - pdbs + - oraclerestarts sideEffects: None - admissionReviewVersions: - v1 @@ -148,47 +106,6 @@ webhooks: resources: - shardingdatabases sideEffects: None -- admissionReviewVersions: - - v1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /mutate-database-oracle-com-v1alpha1-autonomousdatabasebackup - failurePolicy: Fail - name: mautonomousdatabasebackupv1alpha1.kb.io - rules: - - apiGroups: - - database.oracle.com - apiVersions: - - v1alpha1 - operations: - - CREATE - - UPDATE - resources: - - autonomousdatabasebackups - sideEffects: None -- admissionReviewVersions: - - v1 - - v1beta1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /mutate-database-oracle-com-v4-cdb - failurePolicy: Fail - name: mcdb.kb.io - rules: - - apiGroups: - - database.oracle.com - apiVersions: - - v4 - operations: - - CREATE - - UPDATE - resources: - - cdbs - sideEffects: None - admissionReviewVersions: - v1 - v1beta1 @@ -251,47 +168,6 @@ webhooks: resources: - oraclerestdataservices sideEffects: None -- admissionReviewVersions: - - v1 - - v1beta1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /mutate-database-oracle-com-v4-pdb - failurePolicy: Fail - name: mpdb.kb.io - rules: - - apiGroups: - - database.oracle.com - apiVersions: - - v4 - operations: - - CREATE - - UPDATE - resources: - - pdbs - sideEffects: None -- admissionReviewVersions: - - v1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /mutate-database-oracle-com-v1alpha1-shardingdatabase - failurePolicy: Fail - name: mshardingdatabasev1alpha1.kb.io - rules: - - apiGroups: - - database.oracle.com - apiVersions: - - v1alpha1 - operations: - - CREATE - - UPDATE - resources: - - shardingdatabases - sideEffects: None - admissionReviewVersions: - v1 - v1beta1 @@ -441,14 +317,13 @@ webhooks: sideEffects: None - admissionReviewVersions: - v1 - - v1beta1 clientConfig: service: name: webhook-service namespace: system - path: /validate-database-oracle-com-v4-cdb + path: /validate-database-oracle-com-v4-autonomousdatabase failurePolicy: Fail - name: vcdb.kb.io + name: vautonomousdatabasev4.kb.io rules: - apiGroups: - database.oracle.com @@ -458,7 +333,28 @@ webhooks: - CREATE - UPDATE resources: - - cdbs + - autonomousdatabases + sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /validate-database-oracle-com-v4-dbcssystem + failurePolicy: Fail + name: vdbcssystemv4.kb.io + rules: + - apiGroups: + - database.oracle.com + apiVersions: + - v4 + operations: + - CREATE + - UPDATE + - DELETE + resources: + - dbcssystems sideEffects: None - admissionReviewVersions: - v4 @@ -504,14 +400,13 @@ webhooks: sideEffects: None - admissionReviewVersions: - v1 - - v1beta1 clientConfig: service: name: webhook-service namespace: system - path: /validate-database-oracle-com-v4-pdb + path: /validate-database-oracle-com-v4-oraclerestart failurePolicy: Fail - name: vpdb.kb.io + name: voraclerestart.kb.io rules: - apiGroups: - database.oracle.com @@ -520,8 +415,9 @@ webhooks: operations: - CREATE - UPDATE + - DELETE resources: - - pdbs + - oraclerestarts sideEffects: None - admissionReviewVersions: - v1 @@ -624,27 +520,6 @@ webhooks: resources: - autonomousdatabases sideEffects: None -- admissionReviewVersions: - - v1 - - v1beta1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /validate-database-oracle-com-v4-cdb - failurePolicy: Fail - name: vcdb.kb.io - rules: - - apiGroups: - - database.oracle.com - apiVersions: - - v4 - operations: - - CREATE - - UPDATE - resources: - - cdbs - sideEffects: None - admissionReviewVersions: - v1 - v1beta1 @@ -687,48 +562,6 @@ webhooks: resources: - oraclerestdataservices sideEffects: None -- admissionReviewVersions: - - v1 - - v1beta1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /validate-database-oracle-com-v4-pdb - failurePolicy: Fail - name: vpdb.kb.io - rules: - - apiGroups: - - database.oracle.com - apiVersions: - - v4 - operations: - - CREATE - - UPDATE - resources: - - pdbs - sideEffects: None -- admissionReviewVersions: - - v1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /validate-database-oracle-com-v1alpha1-shardingdatabase - failurePolicy: Fail - name: vshardingdatabasev1alpha1.kb.io - rules: - - apiGroups: - - database.oracle.com - apiVersions: - - v1alpha1 - operations: - - CREATE - - UPDATE - - DELETE - resources: - - shardingdatabases - sideEffects: None - admissionReviewVersions: - v1 - v1beta1 diff --git a/controllers/database/autonomousdatabase_controller.go b/controllers/database/autonomousdatabase_controller.go index 37ae1b14..989e7b5d 100644 --- a/controllers/database/autonomousdatabase_controller.go +++ b/controllers/database/autonomousdatabase_controller.go @@ -39,9 +39,11 @@ package controllers import ( + "bytes" "context" "errors" "fmt" + "io" "reflect" "regexp" "strings" @@ -276,6 +278,7 @@ func (r *AutonomousDatabaseReconciler) Reconcile(ctx context.Context, req ctrl.R if err := k8s.RemoveFinalizerAndPatch(r.KubeClient, desiredAdb, ADB_FINALIZER); err != nil { return emptyResult, fmt.Errorf("Failed to remove finalizer to Autonomous Database "+desiredAdb.Name+": %w", err) } + return emptyResult, nil } else { // Remove the Autonomous Database in OCI. // Change the action to Terminate and proceed with the rest of the reconcile logic @@ -327,7 +330,7 @@ func (r *AutonomousDatabaseReconciler) Reconcile(ctx context.Context, req ctrl.R /****************************************************************** * Update the Autonomous Database at the end of every reconcile. ******************************************************************/ - if specChanged { + if specChanged && desiredAdb.Status.LifecycleState != database.AutonomousDatabaseLifecycleStateTerminating { if err := r.KubeClient.Update(context.TODO(), desiredAdb); err != nil { return r.manageError( logger.WithName("updateSpec"), @@ -568,6 +571,30 @@ func (r *AutonomousDatabaseReconciler) performOperation( } return true, nil + case "Switchover": + l.Info("Sending SwitchoverAutonomousDatabase request to OCI") + + resp, err := r.dbService.SwitchoverAutonomousDatabase(*adb.Spec.Details.Id) + if err != nil { + return false, err + } + + adb.Spec.Action = "" + adb.Status.LifecycleState = resp.LifecycleState + return true, nil + + case "Failover": + l.Info("Sending FailOverAutonomousDatabase request to OCI") + + resp, err := r.dbService.FailoverAutonomousDatabase(*adb.Spec.Details.Id) + if err != nil { + return false, err + } + + adb.Spec.Action = "" + adb.Status.LifecycleState = resp.LifecycleState + return true, nil + case "": // No-op return false, nil @@ -697,11 +724,18 @@ func (r *AutonomousDatabaseReconciler) validateWallet(logger logr.Logger, adb *d return err } - data, err := oci.ExtractWallet(resp.Content) + walletBytes, err := io.ReadAll(resp.Content) + + data, err := oci.ExtractWallet(io.NopCloser(bytes.NewReader(walletBytes))) if err != nil { return err } + // Include the unextracted zip file + // https://github.com/oracle/oracle-database-operator/issues/97 + zipFileName := fmt.Sprintf("Wallet_%s.zip", *adb.Spec.Details.DbName) + data[zipFileName] = walletBytes + adb.Status.WalletExpiringDate = oci.WalletExpiringDate(data) label := map[string]string{"app": adb.GetName()} diff --git a/controllers/database/autonomousdatabasebackup_controller.go b/controllers/database/autonomousdatabasebackup_controller.go index 9744f3fb..b647ac4c 100644 --- a/controllers/database/autonomousdatabasebackup_controller.go +++ b/controllers/database/autonomousdatabasebackup_controller.go @@ -227,8 +227,8 @@ func (r *AutonomousDatabaseBackupReconciler) verifyTargetAdb(backup *dbv4.Autono } } - if backup.Spec.Target.OciAdb.OCID != nil { - return *backup.Spec.Target.OciAdb.OCID, nil + if backup.Spec.Target.OciAdb.Id != nil { + return *backup.Spec.Target.OciAdb.Id, nil } if ownerAdb != nil && ownerAdb.Spec.Details.Id != nil { return *ownerAdb.Spec.Details.Id, nil diff --git a/controllers/database/autonomousdatabaserestore_controller.go b/controllers/database/autonomousdatabaserestore_controller.go index 61b84c5d..93f1c57e 100644 --- a/controllers/database/autonomousdatabaserestore_controller.go +++ b/controllers/database/autonomousdatabaserestore_controller.go @@ -236,8 +236,8 @@ func (r *AutonomousDatabaseRestoreReconciler) verifyTargetAdb(restore *dbv4.Auto } } - if restore.Spec.Target.OciAdb.OCID != nil { - return *restore.Spec.Target.OciAdb.OCID, nil + if restore.Spec.Target.OciAdb.Id != nil { + return *restore.Spec.Target.OciAdb.Id, nil } if ownerAdb != nil && ownerAdb.Spec.Details.Id != nil { return *ownerAdb.Spec.Details.Id, nil diff --git a/controllers/database/cdb_controller.go b/controllers/database/cdb_controller.go deleted file mode 100644 index 6c5fc747..00000000 --- a/controllers/database/cdb_controller.go +++ /dev/null @@ -1,1093 +0,0 @@ -/* -** Copyright (c) 2022 Oracle and/or its affiliates. -** -** The Universal Permissive License (UPL), Version 1.0 -** -** Subject to the condition set forth below, permission is hereby granted to any -** person obtaining a copy of this software, associated documentation and/or data -** (collectively the "Software"), free of charge and under any and all copyright -** rights in the Software, and any and all patent rights owned or freely -** licensable by each licensor hereunder covering either (i) the unmodified -** Software as contributed to or provided by such licensor, or (ii) the Larger -** Works (as defined below), to deal in both -** -** (a) the Software, and -** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if -** one is included with the Software (each a "Larger Work" to which the Software -** is contributed by such licensors), -** -** without restriction, including without limitation the rights to copy, create -** derivative works of, display, perform, and distribute the Software and make, -** use, sell, offer for sale, import, export, have made, and have sold the -** Software and the Larger Work(s), and to sublicense the foregoing rights on -** either these or other terms. -** -** This license is subject to the following condition: -** The above copyright notice and either this complete permission notice or at -** a minimum a reference to the UPL must be included in all copies or -** substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -** SOFTWARE. - */ - -package controllers - -import ( - "context" - "encoding/json" - "errors" - "fmt" - - //"fmt" - "strconv" - "strings" - "time" - - "github.com/go-logr/logr" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/record" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "sigs.k8s.io/controller-runtime/pkg/event" - "sigs.k8s.io/controller-runtime/pkg/predicate" - - dbapi "github.com/oracle/oracle-database-operator/apis/database/v4" - dbcommons "github.com/oracle/oracle-database-operator/commons/database" -) - -// CDBReconciler reconciles a CDB object -type CDBReconciler struct { - client.Client - Scheme *runtime.Scheme - Config *rest.Config - Log logr.Logger - Interval time.Duration - Recorder record.EventRecorder -} - -var ( - cdbPhaseInit = "Initializing" - cdbPhasePod = "CreatingPod" - cdbPhaseValPod = "ValidatingPods" - cdbPhaseService = "CreatingService" - cdbPhaseSecrets = "DeletingSecrets" - cdbPhaseReady = "Ready" - cdbPhaseDelete = "Deleting" - cdbPhaseFail = "Failed" -) - -const CDBFinalizer = "database.oracle.com/CDBfinalizer" - -//+kubebuilder:rbac:groups=database.oracle.com,resources=cdbs,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=database.oracle.com,resources=cdbs/status,verbs=get;update;patch -//+kubebuilder:rbac:groups=database.oracle.com,resources=cdbs/finalizers,verbs=update -//+kubebuilder:rbac:groups="",resources=pods;pods/log;pods/exec;services;configmaps;events;replicasets,verbs=create;delete;get;list;patch;update;watch -//+kubebuilder:rbac:groups=core,resources=pods;secrets;services;configmaps;namespaces,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=apps,resources=replicasets,verbs=get;list;watch;create;update;patch;delete - -// Reconcile is part of the main kubernetes reconciliation loop which aims to -// move the current state of the cluster closer to the desired state. -// TODO(user): Modify the Reconcile function to compare the state specified by -// the CDB object against the actual cluster state, and then -// perform operations to make the cluster state reflect the state specified by -// the user. -// -// For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.9.2/pkg/reconcile -func (r *CDBReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - - log := r.Log.WithValues("multitenantoperator", req.NamespacedName) - log.Info("Reconcile requested") - - reconcilePeriod := r.Interval * time.Second - requeueY := ctrl.Result{Requeue: true, RequeueAfter: reconcilePeriod} - requeueN := ctrl.Result{} - - var err error - cdb := &dbapi.CDB{} - - // Execute for every reconcile - defer func() { - log.Info("DEFER", "Name", cdb.Name, "Phase", cdb.Status.Phase, "Status", strconv.FormatBool(cdb.Status.Status)) - if !cdb.Status.Status { - if err := r.Status().Update(ctx, cdb); err != nil { - log.Error(err, "Failed to update status for :"+cdb.Name, "err", err.Error()) - } - } - }() - - err = r.Client.Get(context.TODO(), req.NamespacedName, cdb) - if err != nil { - if apierrors.IsNotFound(err) { - log.Info("CDB Resource Not found", "Name", cdb.Name) - // Request object not found, could have been deleted after reconcile req. - // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. - // Return and don't requeue - cdb.Status.Status = true - return requeueN, nil - } - // Error reading the object - requeue the req. - return requeueY, err - } - - log.Info("Res Status:", "Name", cdb.Name, "Phase", cdb.Status.Phase, "Status", strconv.FormatBool(cdb.Status.Status)) - - // Finalizer section - err = r.manageCDBDeletion(ctx, req, cdb) - if err != nil { - log.Info("Reconcile queued") - return requeueY, nil - } - - // If post-creation, CDB spec is changed, check and take appropriate action - if (cdb.Status.Phase == cdbPhaseReady) && cdb.Status.Status { - r.evaluateSpecChange(ctx, req, cdb) - } - - if !cdb.Status.Status { - phase := cdb.Status.Phase - log.Info("Current Phase:"+phase, "Name", cdb.Name) - - switch phase { - case cdbPhaseInit: - err = r.verifySecrets(ctx, req, cdb) - if err != nil { - cdb.Status.Phase = cdbPhaseFail - return requeueN, nil - } - cdb.Status.Phase = cdbPhasePod - case cdbPhasePod: - // Create ORDS PODs - err = r.createORDSInstances(ctx, req, cdb) - if err != nil { - log.Info("Reconcile queued") - return requeueY, nil - } - cdb.Status.Phase = cdbPhaseValPod - case cdbPhaseValPod: - // Validate ORDS PODs - err = r.validateORDSPods(ctx, req, cdb) - if err != nil { - if cdb.Status.Phase == cdbPhaseFail { - return requeueN, nil - } - log.Info("Reconcile queued") - return requeueY, nil - } - cdb.Status.Phase = cdbPhaseService - case cdbPhaseService: - // Create ORDS Service - err = r.createORDSSVC(ctx, req, cdb) - if err != nil { - log.Info("Reconcile queued") - return requeueY, nil - } - //cdb.Status.Phase = cdbPhaseSecrets - cdb.Status.Phase = cdbPhaseReady - case cdbPhaseSecrets: - // Delete CDB Secrets - //r.deleteSecrets(ctx, req, cdb) - cdb.Status.Phase = cdbPhaseReady - cdb.Status.Msg = "Success" - case cdbPhaseReady: - cdb.Status.Status = true - r.Status().Update(ctx, cdb) - return requeueN, nil - default: - cdb.Status.Phase = cdbPhaseInit - log.Info("DEFAULT:", "Name", cdb.Name, "Phase", phase, "Status", strconv.FormatBool(cdb.Status.Status)) - } - - if err := r.Status().Update(ctx, cdb); err != nil { - log.Error(err, "Failed to update status for :"+cdb.Name, "err", err.Error()) - } - return requeueY, nil - } - - log.Info("Reconcile completed") - return requeueN, nil -} - -/* -********************************************************* - - Create a ReplicaSet for pods based on the ORDS container - /******************************************************* -*/ -func (r *CDBReconciler) createORDSInstances(ctx context.Context, req ctrl.Request, cdb *dbapi.CDB) error { - - log := r.Log.WithValues("createORDSInstances", req.NamespacedName) - - replicaSet := r.createReplicaSetSpec(cdb) - - foundRS := &appsv1.ReplicaSet{} - err := r.Get(context.TODO(), types.NamespacedName{Name: replicaSet.Name, Namespace: cdb.Namespace}, foundRS) - if err != nil && apierrors.IsNotFound(err) { - log.Info("Creating ORDS Replicaset: " + replicaSet.Name) - err = r.Create(ctx, replicaSet) - if err != nil { - log.Error(err, "Failed to create ReplicaSet for :"+cdb.Name, "Namespace", replicaSet.Namespace, "Name", replicaSet.Name) - return err - } - } else if err != nil { - log.Error(err, "Replicaset : "+replicaSet.Name+" already exists.") - return err - } - - // Set CDB instance as the owner and controller - ctrl.SetControllerReference(cdb, replicaSet, r.Scheme) - - log.Info("Created ORDS ReplicaSet successfully") - r.Recorder.Eventf(cdb, corev1.EventTypeNormal, "CreatedORDSReplicaSet", "Created ORDS Replicaset (Replicas - %s) for %s", strconv.Itoa(cdb.Spec.Replicas), cdb.Name) - return nil -} - -/* -************************************************ - - Validate ORDS Pod. Check if there are any errors - /*********************************************** -*/ -func (r *CDBReconciler) validateORDSPods(ctx context.Context, req ctrl.Request, cdb *dbapi.CDB) error { - - log := r.Log.WithValues("validateORDSPod", req.NamespacedName) - - log.Info("Validating Pod creation for :" + cdb.Name) - - podName := cdb.Name + "-ords" - podList := &corev1.PodList{} - listOpts := []client.ListOption{client.InNamespace(req.Namespace), client.MatchingLabels{"name": podName}} - - // List retrieves list of objects for a given namespace and list options. - err := r.List(ctx, podList, listOpts...) - if err != nil { - log.Info("Failed to list pods of: "+podName, "Namespace", req.Namespace) - return err - } - - if len(podList.Items) == 0 { - log.Info("No pods found for: "+podName, "Namespace", req.Namespace) - cdb.Status.Msg = "Waiting for ORDS Pod(s) to start" - return errors.New("Waiting for ORDS pods to start") - } - - /* /opt/oracle/ords/secrets/$TLSKEY /opt/oracle/ords/secrets/$TLSCRT */ - getORDSStatus := " curl --cert /opt/oracle/ords/secrets/tls.crt --key /opt/oracle/ords/secrets/tls.key -sSkv -k -X GET https://localhost:" + strconv.Itoa(cdb.Spec.ORDSPort) + "/ords/_/db-api/stable/metadata-catalog/ || curl --cert /opt/oracle/ords/secrets/tls.crt --key /opt/oracle/ords/secrets/tls.key -sSkv -X GET http://localhost:" + strconv.Itoa(cdb.Spec.ORDSPort) + "/ords/_/db-api/stable/metadata-catalog/ " - readyPods := 0 - for _, pod := range podList.Items { - if pod.Status.Phase == corev1.PodRunning { - // Get ORDS Status - out, err := dbcommons.ExecCommand(r, r.Config, pod.Name, pod.Namespace, "", ctx, req, false, "bash", "-c", getORDSStatus) - if strings.Contains(out, "HTTP/1.1 200 OK") || strings.Contains(strings.ToUpper(err.Error()), "HTTP/1.1 200 OK") || - strings.Contains(out, "HTTP/2") || strings.Contains(strings.ToUpper(err.Error()), " HTTP/2") { - readyPods++ - } else if strings.Contains(out, "HTTP/1.1 404 Not Found") || strings.Contains(strings.ToUpper(err.Error()), "HTTP/1.1 404 NOT FOUND") || strings.Contains(strings.ToUpper(err.Error()), "HTTP/2 404") || strings.Contains(strings.ToUpper(err.Error()), "Failed to connect to localhost") { - // Check if DB connection parameters are correct - getORDSInstallStatus := " grep -q 'Failed to' /tmp/ords_install.log; echo $?;" - out, _ := dbcommons.ExecCommand(r, r.Config, pod.Name, pod.Namespace, "", ctx, req, false, "bash", "-c", getORDSInstallStatus) - if strings.TrimSpace(out) == "0" { - cdb.Status.Msg = "Check DB connection parameters" - cdb.Status.Phase = cdbPhaseFail - // Delete existing ReplicaSet - r.deleteReplicaSet(ctx, req, cdb) - return errors.New("Check DB connection parameters") - } - } - } - } - - if readyPods != cdb.Spec.Replicas { - log.Info("Replicas: "+strconv.Itoa(cdb.Spec.Replicas), "Ready Pods: ", readyPods) - cdb.Status.Msg = "Waiting for ORDS Pod(s) to be ready" - return errors.New("Waiting for ORDS pods to be ready") - } - - cdb.Status.Msg = "" - return nil -} - -/* -*********************** - - Create Pod spec - -/*********************** -*/ -func (r *CDBReconciler) createPodSpec(cdb *dbapi.CDB) corev1.PodSpec { - - podSpec := corev1.PodSpec{ - Volumes: []corev1.Volume{{ - Name: "secrets", - VolumeSource: corev1.VolumeSource{ - Projected: &corev1.ProjectedVolumeSource{ - DefaultMode: func() *int32 { i := int32(0666); return &i }(), - Sources: []corev1.VolumeProjection{ - { - Secret: &corev1.SecretProjection{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: cdb.Spec.SysAdminPwd.Secret.SecretName, - }, - Items: []corev1.KeyToPath{ - { - Key: cdb.Spec.SysAdminPwd.Secret.Key, - Path: cdb.Spec.SysAdminPwd.Secret.Key, - }, - }, - }, - }, - { - Secret: &corev1.SecretProjection{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: cdb.Spec.CDBAdminUser.Secret.SecretName, - }, - Items: []corev1.KeyToPath{ - { - Key: cdb.Spec.CDBAdminUser.Secret.Key, - Path: cdb.Spec.CDBAdminUser.Secret.Key, - }, - }, - }, - }, - /***/ - { - Secret: &corev1.SecretProjection{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: cdb.Spec.CDBTlsKey.Secret.SecretName, - }, - Items: []corev1.KeyToPath{ - { - Key: cdb.Spec.CDBTlsKey.Secret.Key, - Path: cdb.Spec.CDBTlsKey.Secret.Key, - }, - }, - }, - }, - - { - Secret: &corev1.SecretProjection{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: cdb.Spec.CDBTlsCrt.Secret.SecretName, - }, - Items: []corev1.KeyToPath{ - { - Key: cdb.Spec.CDBTlsCrt.Secret.Key, - Path: cdb.Spec.CDBTlsCrt.Secret.Key, - }, - }, - }, - }, - - /***/ - { - Secret: &corev1.SecretProjection{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: cdb.Spec.CDBAdminPwd.Secret.SecretName, - }, - Items: []corev1.KeyToPath{ - { - Key: cdb.Spec.CDBAdminPwd.Secret.Key, - Path: cdb.Spec.CDBAdminPwd.Secret.Key, - }, - }, - }, - }, - { - Secret: &corev1.SecretProjection{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: cdb.Spec.ORDSPwd.Secret.SecretName, - }, - Items: []corev1.KeyToPath{ - { - Key: cdb.Spec.ORDSPwd.Secret.Key, - Path: cdb.Spec.ORDSPwd.Secret.Key, - }, - }, - }, - }, - { - Secret: &corev1.SecretProjection{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: cdb.Spec.WebServerUser.Secret.SecretName, - }, - Items: []corev1.KeyToPath{ - { - Key: cdb.Spec.WebServerUser.Secret.Key, - Path: cdb.Spec.WebServerUser.Secret.Key, - }, - }, - }, - }, - { - Secret: &corev1.SecretProjection{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: cdb.Spec.WebServerPwd.Secret.SecretName, - }, - Items: []corev1.KeyToPath{ - { - Key: cdb.Spec.WebServerPwd.Secret.Key, - Path: cdb.Spec.WebServerPwd.Secret.Key, - }, - }, - }, - }, - }, - }, - }, - }}, - Containers: []corev1.Container{{ - Name: cdb.Name + "-ords", - Image: cdb.Spec.ORDSImage, - VolumeMounts: []corev1.VolumeMount{{ - MountPath: "/opt/oracle/ords/secrets", - Name: "secrets", - ReadOnly: true, - }}, - Env: func() []corev1.EnvVar { - return []corev1.EnvVar{ - { - Name: "ORACLE_HOST", - Value: cdb.Spec.DBServer, - }, - { - Name: "DBTNSURL", - Value: cdb.Spec.DBTnsurl, - }, - { - Name: "TLSCRT", - Value: cdb.Spec.CDBTlsCrt.Secret.Key, - }, - { - Name: "TLSKEY", - Value: cdb.Spec.CDBTlsKey.Secret.Key, - }, - { - Name: "ORACLE_PORT", - Value: strconv.Itoa(cdb.Spec.DBPort), - }, - { - Name: "ORDS_PORT", - Value: strconv.Itoa(cdb.Spec.ORDSPort), - }, - { - Name: "ORACLE_SERVICE", - Value: cdb.Spec.ServiceName, - }, - { - Name: "ORACLE_PWD_KEY", - Value: cdb.Spec.SysAdminPwd.Secret.Key, - }, - { - Name: "CDBADMIN_USER_KEY", - Value: cdb.Spec.CDBAdminUser.Secret.Key, - }, - { - Name: "CDBADMIN_PWD_KEY", - Value: cdb.Spec.CDBAdminPwd.Secret.Key, - }, - { - Name: "ORDS_PWD_KEY", - Value: cdb.Spec.ORDSPwd.Secret.Key, - }, - { - Name: "WEBSERVER_USER_KEY", - Value: cdb.Spec.WebServerUser.Secret.Key, - }, - { - Name: "WEBSERVER_PASSWORD_KEY", - Value: cdb.Spec.WebServerPwd.Secret.Key, - }, - { - Name: "R1", - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: cdb.Spec.CDBPriKey.Secret.SecretName, - }, - Key: cdb.Spec.CDBPriKey.Secret.Key, - }, - }, - }, - } - }(), - }}, - - NodeSelector: func() map[string]string { - ns := make(map[string]string) - if len(cdb.Spec.NodeSelector) != 0 { - for key, value := range cdb.Spec.NodeSelector { - ns[key] = value - } - } - return ns - }(), - } - - if len(cdb.Spec.ORDSImagePullSecret) > 0 { - podSpec.ImagePullSecrets = []corev1.LocalObjectReference{ - { - Name: cdb.Spec.ORDSImagePullSecret, - }, - } - } - - podSpec.Containers[0].ImagePullPolicy = corev1.PullAlways - - if len(cdb.Spec.ORDSImagePullPolicy) > 0 { - if strings.ToUpper(cdb.Spec.ORDSImagePullPolicy) == "NEVER" { - podSpec.Containers[0].ImagePullPolicy = corev1.PullNever - } - } - - return podSpec -} - -/* -*********************** - - Create ReplicaSet spec - -/*********************** -*/ -func (r *CDBReconciler) createReplicaSetSpec(cdb *dbapi.CDB) *appsv1.ReplicaSet { - - replicas := int32(cdb.Spec.Replicas) - podSpec := r.createPodSpec(cdb) - - replicaSet := &appsv1.ReplicaSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: cdb.Name + "-ords-rs", - Namespace: cdb.Namespace, - Labels: map[string]string{ - "name": cdb.Name + "-ords-rs", - }, - }, - Spec: appsv1.ReplicaSetSpec{ - Replicas: &replicas, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Name: cdb.Name + "-ords", - Namespace: cdb.Namespace, - Labels: map[string]string{ - "name": cdb.Name + "-ords", - }, - }, - Spec: podSpec, - }, - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "name": cdb.Name + "-ords", - }, - }, - }, - } - - return replicaSet -} - -/* -********************************************************* - - Evaluate change in Spec post creation and instantiation - /******************************************************* -*/ -func (r *CDBReconciler) deleteReplicaSet(ctx context.Context, req ctrl.Request, cdb *dbapi.CDB) error { - log := r.Log.WithValues("deleteReplicaSet", req.NamespacedName) - - k_client, err := kubernetes.NewForConfig(r.Config) - if err != nil { - log.Error(err, "Kubernetes Config Error") - return err - } - - replicaSetName := cdb.Name + "-ords-rs" - err = k_client.AppsV1().ReplicaSets(cdb.Namespace).Delete(context.TODO(), replicaSetName, metav1.DeleteOptions{}) - if err != nil { - log.Info("Could not delete ReplicaSet", "RS Name", replicaSetName, "err", err.Error()) - if !strings.Contains(strings.ToUpper(err.Error()), "NOT FOUND") { - return err - } - } else { - log.Info("Successfully deleted ORDS ReplicaSet", "RS Name", replicaSetName) - } - - return nil -} - -/* -********************************************************* - - Evaluate change in Spec post creation and instantiation - /******************************************************* -*/ -func (r *CDBReconciler) evaluateSpecChange(ctx context.Context, req ctrl.Request, cdb *dbapi.CDB) error { - log := r.Log.WithValues("evaluateSpecChange", req.NamespacedName) - - // List the Pods matching the PodTemplate Labels - podName := cdb.Name + "-ords" - podList := &corev1.PodList{} - listOpts := []client.ListOption{client.InNamespace(req.Namespace), client.MatchingLabels{"name": podName}} - - // List retrieves list of objects for a given namespace and list options. - err := r.List(ctx, podList, listOpts...) - if err != nil { - log.Info("Failed to list pods of: "+podName, "Namespace", req.Namespace) - return err - } - - var foundPod corev1.Pod - for _, pod := range podList.Items { - foundPod = pod - break - } - - ordsSpecChange := false - for _, envVar := range foundPod.Spec.Containers[0].Env { - if envVar.Name == "ORACLE_HOST" && envVar.Value != cdb.Spec.DBServer { - ordsSpecChange = true - } else if envVar.Name == "ORACLE_PORT" && envVar.Value != strconv.Itoa(cdb.Spec.DBPort) { - ordsSpecChange = true - } else if envVar.Name == "ORDS_PORT" && envVar.Value != strconv.Itoa(cdb.Spec.ORDSPort) { - ordsSpecChange = true - } else if envVar.Name == "ORACLE_SERVICE" && envVar.Value != cdb.Spec.ServiceName { - ordsSpecChange = true - } - } - - if ordsSpecChange { - // Delete existing ReplicaSet - err = r.deleteReplicaSet(ctx, req, cdb) - if err != nil { - return err - } - - cdb.Status.Phase = cdbPhaseInit - cdb.Status.Status = false - r.Status().Update(ctx, cdb) - } else { - // Update the RS if the value of "replicas" is changed - replicaSetName := cdb.Name + "-ords-rs" - - foundRS := &appsv1.ReplicaSet{} - err := r.Get(context.TODO(), types.NamespacedName{Name: replicaSetName, Namespace: cdb.Namespace}, foundRS) - if err != nil { - log.Error(err, "Unable to get ORDS Replicaset: "+replicaSetName) - return err - } - - // Check if number of replicas have changed - replicas := int32(cdb.Spec.Replicas) - if cdb.Spec.Replicas != int(*(foundRS.Spec.Replicas)) { - log.Info("Existing Replicas: " + strconv.Itoa(int(*(foundRS.Spec.Replicas))) + ", New Replicas: " + strconv.Itoa(cdb.Spec.Replicas)) - foundRS.Spec.Replicas = &replicas - err = r.Update(ctx, foundRS) - if err != nil { - log.Error(err, "Failed to update ReplicaSet for :"+cdb.Name, "Namespace", cdb.Namespace, "Name", replicaSetName) - return err - } - cdb.Status.Phase = cdbPhaseValPod - cdb.Status.Status = false - r.Status().Update(ctx, cdb) - } - } - - return nil -} - -/* -************************************************ - - Create a Cluster Service for ORDS CDB Pod - /*********************************************** -*/ -func (r *CDBReconciler) createORDSSVC(ctx context.Context, req ctrl.Request, cdb *dbapi.CDB) error { - - log := r.Log.WithValues("createORDSSVC", req.NamespacedName) - - foundSvc := &corev1.Service{} - err := r.Get(context.TODO(), types.NamespacedName{Name: cdb.Name + "-ords", Namespace: cdb.Namespace}, foundSvc) - if err != nil && apierrors.IsNotFound(err) { - svc := r.createSvcSpec(cdb) - - log.Info("Creating a new Cluster Service for: "+cdb.Name, "Svc.Namespace", svc.Namespace, "Service.Name", svc.Name) - err := r.Create(ctx, svc) - if err != nil { - log.Error(err, "Failed to create new Cluster Service for: "+cdb.Name, "Svc.Namespace", svc.Namespace, "Service.Name", svc.Name) - return err - } - - log.Info("Created ORDS Cluster Service successfully") - r.Recorder.Eventf(cdb, corev1.EventTypeNormal, "CreatedORDSService", "Created ORDS Service for %s", cdb.Name) - } else { - log.Info("ORDS Cluster Service already exists") - } - - return nil -} - -/* -*********************** - - Create Service spec - /*********************** -*/ -func (r *CDBReconciler) createSvcSpec(cdb *dbapi.CDB) *corev1.Service { - - svc := &corev1.Service{ - TypeMeta: metav1.TypeMeta{ - Kind: "Service", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: cdb.Name + "-ords", - Namespace: cdb.Namespace, - }, - Spec: corev1.ServiceSpec{ - Selector: map[string]string{ - "name": cdb.Name + "-ords", - }, - ClusterIP: corev1.ClusterIPNone, - }, - } - // Set CDB instance as the owner and controller - ctrl.SetControllerReference(cdb, svc, r.Scheme) - return svc -} - -/* -************************************************ - - Check CDB deletion - -/*********************************************** -*/ -func (r *CDBReconciler) manageCDBDeletion(ctx context.Context, req ctrl.Request, cdb *dbapi.CDB) error { - log := r.Log.WithValues("manageCDBDeletion", req.NamespacedName) - - /* REGISTER FINALIZER */ - if cdb.ObjectMeta.DeletionTimestamp.IsZero() { - if !controllerutil.ContainsFinalizer(cdb, CDBFinalizer) { - controllerutil.AddFinalizer(cdb, CDBFinalizer) - if err := r.Update(ctx, cdb); err != nil { - return err - } - } - - } else { - log.Info("cdb set to be deleted") - cdb.Status.Phase = cdbPhaseDelete - cdb.Status.Status = true - r.Status().Update(ctx, cdb) - - if controllerutil.ContainsFinalizer(cdb, CDBFinalizer) { - - if err := r.DeletePDBS(ctx, req, cdb); err != nil { - log.Info("Cannot delete pdbs") - return err - } - - controllerutil.RemoveFinalizer(cdb, CDBFinalizer) - if err := r.Update(ctx, cdb); err != nil { - return err - } - } - - err := r.deleteCDBInstance(ctx, req, cdb) - if err != nil { - log.Info("Could not delete CDB Resource", "CDB Name", cdb.Spec.CDBName, "err", err.Error()) - return err - } - - } - return nil -} - -/* -************************************************ - - Delete CDB Resource - -/*********************************************** -*/ -func (r *CDBReconciler) deleteCDBInstance(ctx context.Context, req ctrl.Request, cdb *dbapi.CDB) error { - - log := r.Log.WithValues("deleteCDBInstance", req.NamespacedName) - - k_client, err := kubernetes.NewForConfig(r.Config) - if err != nil { - log.Error(err, "Kubernetes Config Error") - } - - replicaSetName := cdb.Name + "-ords-rs" - - err = k_client.AppsV1().ReplicaSets(cdb.Namespace).Delete(context.TODO(), replicaSetName, metav1.DeleteOptions{}) - if err != nil { - log.Info("Could not delete ReplicaSet", "RS Name", replicaSetName, "err", err.Error()) - if !strings.Contains(strings.ToUpper(err.Error()), "NOT FOUND") { - return err - } - } else { - log.Info("Successfully deleted ORDS ReplicaSet", "RS Name", replicaSetName) - } - - r.Recorder.Eventf(cdb, corev1.EventTypeNormal, "DeletedORDSReplicaSet", "Deleted ORDS ReplicaSet for %s", cdb.Name) - - svcName := cdb.Name + "-ords" - - err = k_client.CoreV1().Services(cdb.Namespace).Delete(context.TODO(), svcName, metav1.DeleteOptions{}) - if err != nil { - log.Info("Could not delete Service", "Service Name", svcName, "err", err.Error()) - if !strings.Contains(strings.ToUpper(err.Error()), "NOT FOUND") { - return err - } - } else { - r.Recorder.Eventf(cdb, corev1.EventTypeNormal, "DeletedORDSService", "Deleted ORDS Service for %s", cdb.Name) - log.Info("Successfully deleted ORDS Service", "Service Name", svcName) - } - - log.Info("Successfully deleted CDB resource", "CDB Name", cdb.Spec.CDBName) - return nil -} - -/* -************************************************ - - Get Secret Key for a Secret Name - -/*********************************************** -*/ -func (r *CDBReconciler) verifySecrets(ctx context.Context, req ctrl.Request, cdb *dbapi.CDB) error { - - log := r.Log.WithValues("verifySecrets", req.NamespacedName) - - if err := r.checkSecret(ctx, req, cdb, cdb.Spec.SysAdminPwd.Secret.SecretName); err != nil { - return err - } - if err := r.checkSecret(ctx, req, cdb, cdb.Spec.CDBAdminUser.Secret.SecretName); err != nil { - return err - } - if err := r.checkSecret(ctx, req, cdb, cdb.Spec.CDBAdminPwd.Secret.SecretName); err != nil { - return err - } - if err := r.checkSecret(ctx, req, cdb, cdb.Spec.ORDSPwd.Secret.SecretName); err != nil { - return err - } - if err := r.checkSecret(ctx, req, cdb, cdb.Spec.WebServerUser.Secret.SecretName); err != nil { - return err - } - if err := r.checkSecret(ctx, req, cdb, cdb.Spec.WebServerPwd.Secret.SecretName); err != nil { - return err - } - if err := r.checkSecret(ctx, req, cdb, cdb.Spec.CDBPriKey.Secret.SecretName); err != nil { - return err - } - - cdb.Status.Msg = "" - log.Info("Verified secrets successfully") - return nil -} - -/* -************************************************ - - Get Secret Key for a Secret Name - -/*********************************************** -*/ -func (r *CDBReconciler) checkSecret(ctx context.Context, req ctrl.Request, cdb *dbapi.CDB, secretName string) error { - - log := r.Log.WithValues("checkSecret", req.NamespacedName) - - secret := &corev1.Secret{} - err := r.Get(ctx, types.NamespacedName{Name: secretName, Namespace: cdb.Namespace}, secret) - if err != nil { - if apierrors.IsNotFound(err) { - log.Info("Secret not found:" + secretName) - cdb.Status.Msg = "Secret not found:" + secretName - return err - } - log.Error(err, "Unable to get the secret.") - return err - } - - return nil -} - -/* -************************************************ - - Delete Secrets - -/*********************************************** -*/ -func (r *CDBReconciler) deleteSecrets(ctx context.Context, req ctrl.Request, cdb *dbapi.CDB) { - - log := r.Log.WithValues("deleteSecrets", req.NamespacedName) - - log.Info("Deleting CDB secrets") - secret := &corev1.Secret{} - err := r.Get(ctx, types.NamespacedName{Name: cdb.Spec.SysAdminPwd.Secret.SecretName, Namespace: cdb.Namespace}, secret) - if err == nil { - err := r.Delete(ctx, secret) - if err == nil { - log.Info("Deleted the secret : " + cdb.Spec.SysAdminPwd.Secret.SecretName) - } - } - - err = r.Get(ctx, types.NamespacedName{Name: cdb.Spec.CDBAdminUser.Secret.SecretName, Namespace: cdb.Namespace}, secret) - if err == nil { - err := r.Delete(ctx, secret) - if err == nil { - log.Info("Deleted the secret : " + cdb.Spec.CDBAdminUser.Secret.SecretName) - } - } - - err = r.Get(ctx, types.NamespacedName{Name: cdb.Spec.CDBAdminPwd.Secret.SecretName, Namespace: cdb.Namespace}, secret) - if err == nil { - err := r.Delete(ctx, secret) - if err == nil { - log.Info("Deleted the secret : " + cdb.Spec.CDBAdminPwd.Secret.SecretName) - } - } - - err = r.Get(ctx, types.NamespacedName{Name: cdb.Spec.ORDSPwd.Secret.SecretName, Namespace: cdb.Namespace}, secret) - if err == nil { - err := r.Delete(ctx, secret) - if err == nil { - log.Info("Deleted the secret : " + cdb.Spec.ORDSPwd.Secret.SecretName) - } - } - - err = r.Get(ctx, types.NamespacedName{Name: cdb.Spec.WebServerUser.Secret.SecretName, Namespace: cdb.Namespace}, secret) - if err == nil { - err := r.Delete(ctx, secret) - if err == nil { - log.Info("Deleted the secret : " + cdb.Spec.WebServerUser.Secret.SecretName) - } - } - - err = r.Get(ctx, types.NamespacedName{Name: cdb.Spec.WebServerPwd.Secret.SecretName, Namespace: cdb.Namespace}, secret) - if err == nil { - err := r.Delete(ctx, secret) - if err == nil { - log.Info("Deleted the secret : " + cdb.Spec.WebServerPwd.Secret.SecretName) - } - } -} - -/* Delete cascade option */ - -/* -************************************************************* - - SetupWithManager sets up the controller with the Manager. -/************************************************************ -*/ - -func (r *CDBReconciler) DeletePDBS(ctx context.Context, req ctrl.Request, cdb *dbapi.CDB) error { - log := r.Log.WithValues("DeletePDBS", req.NamespacedName) - - /* =================== DELETE CASCADE ================ */ - if cdb.Spec.DeletePDBCascade == true { - log.Info("DELETE PDB CASCADE OPTION") - pdbList := &dbapi.PDBList{} - listOpts := []client.ListOption{} - err := r.List(ctx, pdbList, listOpts...) - if err != nil { - log.Info("Failed to get the list of pdbs") - } - - var url string - if err == nil { - for _, pdbitem := range pdbList.Items { - log.Info("pdbitem.Spec.CDBName : " + pdbitem.Spec.CDBName) - log.Info("pdbitem.Spec.CDBNamespace: " + pdbitem.Spec.CDBNamespace) - log.Info("cdb.Spec.CDBName : " + cdb.Spec.CDBName) - log.Info("cdb.Namespace : " + cdb.Namespace) - if pdbitem.Spec.CDBName == cdb.Spec.CDBName && pdbitem.Spec.CDBNamespace == cdb.Namespace { - fmt.Printf("DeletePDBS Call Delete function for %s %s\n", pdbitem.Name, pdbitem.Spec.PDBName) - - var objmap map[string]interface{} /* Used for the return payload */ - values := map[string]string{ - "state": "CLOSE", - "modifyOption": "IMMEDIATE", - "getScript": "FALSE", - } - - //url := "https://" + pdbitem.Spec.CDBResName + "-cdb." + pdbitem.Spec.CDBNamespace + ":" + strconv.Itoa(cdb.Spec.ORDSPort) + "/database/pdbs/" + pdbitem.Spec.PDBName - url = "https://" + pdbitem.Spec.CDBResName + "-ords." + pdbitem.Spec.CDBNamespace + ":" + strconv.Itoa(cdb.Spec.ORDSPort) + "/ords/_/db-api/latest/database/pdbs/" + pdbitem.Spec.PDBName + "/status" - - log.Info("callAPI(URL):" + url) - log.Info("pdbitem.Status.OpenMode" + pdbitem.Status.OpenMode) - - if pdbitem.Status.OpenMode != "MOUNTED" { - - log.Info("Force pdb closure") - respData, errapi := NewCallApi(r, ctx, req, &pdbitem, url, values, "POST") - - fmt.Printf("Debug NEWCALL:%s\n", respData) - if err := json.Unmarshal([]byte(respData), &objmap); err != nil { - log.Error(err, "failed to get respData from callAPI", "err", err.Error()) - return err - } - - if errapi != nil { - log.Error(err, "callAPI cannot close pdb "+pdbitem.Spec.PDBName, "err", err.Error()) - return err - } - - r.Recorder.Eventf(cdb, corev1.EventTypeNormal, "close pdb", "pdbname=%s", pdbitem.Spec.PDBName) - } - - /* start dropping pdb */ - log.Info("Drop pluggable database") - values = map[string]string{ - "action": "INCLUDING", - "getScript": "FALSE", - } - url = "https://" + pdbitem.Spec.CDBResName + "-ords." + pdbitem.Spec.CDBNamespace + ":" + strconv.Itoa(cdb.Spec.ORDSPort) + "/ords/_/db-api/latest/database/pdbs/" + pdbitem.Spec.PDBName + "/" - respData, errapi := NewCallApi(r, ctx, req, &pdbitem, url, values, "DELETE") - - if err := json.Unmarshal([]byte(respData), &objmap); err != nil { - log.Error(err, "failed to get respData from callAPI", "err", err.Error()) - return err - } - - if errapi != nil { - log.Error(err, "callAPI cannot drop pdb "+pdbitem.Spec.PDBName, "err", err.Error()) - return err - } - r.Recorder.Eventf(cdb, corev1.EventTypeNormal, "drop pdb", "pdbname=%s", pdbitem.Spec.PDBName) - - err = r.Delete(context.Background(), &pdbitem, client.GracePeriodSeconds(0)) - if err != nil { - log.Info("Could not delete PDB resource", "err", err.Error()) - return err - } - - } /* check pdb name */ - } /* end of loop */ - } - - } - /* ================================================ */ - return nil -} - -func (r *CDBReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&dbapi.CDB{}). - Owns(&appsv1.ReplicaSet{}). //Watch for deleted RS owned by this controller - WithEventFilter(predicate.Funcs{ - UpdateFunc: func(e event.UpdateEvent) bool { - // Ignore updates to CR status in which case metadata.Generation does not change - return e.ObjectOld.GetGeneration() != e.ObjectNew.GetGeneration() - }, - DeleteFunc: func(e event.DeleteEvent) bool { - // Evaluates to false if the object has been confirmed deleted. - //return !e.DeleteStateUnknown - return false - }, - }). - WithOptions(controller.Options{MaxConcurrentReconciles: 100}). - Complete(r) -} diff --git a/controllers/database/dbcssystem_controller.go b/controllers/database/dbcssystem_controller.go index 1fd94dde..6b4a135a 100644 --- a/controllers/database/dbcssystem_controller.go +++ b/controllers/database/dbcssystem_controller.go @@ -39,14 +39,16 @@ package controllers import ( + "bytes" "context" + "encoding/json" "fmt" "reflect" "strings" "time" databasev4 "github.com/oracle/oracle-database-operator/apis/database/v4" - dbcsv1 "github.com/oracle/oracle-database-operator/commons/dbcssystem" + dbcsv4 "github.com/oracle/oracle-database-operator/commons/dbcssystem" "github.com/oracle/oracle-database-operator/commons/finalizer" "github.com/oracle/oracle-database-operator/commons/oci" @@ -103,15 +105,16 @@ func (r *DbcsSystemReconciler) Reconcile(ctx context.Context, req ctrl.Request) var err error resultNq := ctrl.Result{Requeue: false} resultQ := ctrl.Result{Requeue: true, RequeueAfter: 60 * time.Second} - // Get the dbcs instance from the cluster dbcsInst := &databasev4.DbcsSystem{} r.Logger.Info("Reconciling DbSystemDetails", "name", req.NamespacedName) - - if err := r.KubeClient.Get(context.TODO(), req.NamespacedName, dbcsInst); err != nil { - if !errors.IsNotFound(err) { - return ctrl.Result{}, err + if err := r.KubeClient.Get(ctx, req.NamespacedName, dbcsInst); err != nil { + if errors.IsNotFound(err) { + // CR was deleted → stop reconciling + r.Logger.Info("DbcsSystem resource not found.", "name", req.NamespacedName) + return ctrl.Result{}, nil } + return ctrl.Result{}, err } // Create oci-go-sdk client @@ -144,10 +147,23 @@ func (r *DbcsSystemReconciler) Reconcile(ctx context.Context, req ctrl.Request) result := resultNq return result, err } + + var compartmentId string + if dbcsInst.Spec.DbSystem != nil && dbcsInst.Spec.DbSystem.CompartmentId != "" { + compartmentId = dbcsInst.Spec.DbSystem.CompartmentId + } else if dbcsInst.Spec.Id != nil && *dbcsInst.Spec.Id != "" { + var err error + compartmentId, err = r.getCompartmentIDByDbSystemID(ctx, *dbcsInst.Spec.Id) + if err != nil { + fmt.Printf("Failed to get compartment ID: %v\n", err) + dbcsInst.Status.Message = err.Error() + return ctrl.Result{}, err + } + } r.Logger.Info("OCI provider configured succesfully") /* - Using Finalizer for object deletion + Using Finalizer for object deletion */ if dbcsInst.ObjectMeta.DeletionTimestamp.IsZero() { @@ -162,10 +178,11 @@ func (r *DbcsSystemReconciler) Reconcile(ctx context.Context, req ctrl.Request) } else { // The object is being deleted r.Logger.Info("Terminate DbcsSystem Database: " + dbcsInst.Spec.DbSystem.DisplayName) - if err := dbcsv1.DeleteDbcsSystemSystem(r.dbClient, *dbcsInst.Spec.Id); err != nil { + if err := dbcsv4.DeleteDbcsSystemSystem(r.dbClient, *dbcsInst.Spec.Id); err != nil { r.Logger.Error(err, "Fail to terminate DbcsSystem Instance") + dbcsInst.Status.Message = err.Error() // Change the status to Failed - if statusErr := dbcsv1.SetLifecycleState(r.KubeClient, r.dbClient, dbcsInst, databasev4.Terminate, r.nwClient, r.wrClient); statusErr != nil { + if statusErr := dbcsv4.SetLifecycleState(compartmentId, r.KubeClient, r.dbClient, dbcsInst, databasev4.Terminate, r.nwClient, r.wrClient); statusErr != nil { result := resultNq return result, err } @@ -183,6 +200,7 @@ func (r *DbcsSystemReconciler) Reconcile(ctx context.Context, req ctrl.Request) // Call deletePluggableDatabase function dbSystemId := *dbcsInst.Spec.Id if err := r.deletePluggableDatabase(ctx, pdbConfig, dbSystemId); err != nil { + dbcsInst.Status.Message = err.Error() result := resultNq return result, err } @@ -200,7 +218,7 @@ func (r *DbcsSystemReconciler) Reconcile(ctx context.Context, req ctrl.Request) } /* - Determine whether it's a provision or bind operation + Determine whether it's a provision or bind operation */ lastSuccessfullSpec, err := dbcsInst.GetLastSuccessfulSpec() if err != nil { @@ -217,7 +235,7 @@ func (r *DbcsSystemReconciler) Reconcile(ctx context.Context, req ctrl.Request) if lastSuccessfullKMSConfig == nil && lastSuccessfullKMSStatus == nil { - if dbcsInst.Spec.KMSConfig.KeyName != "" { + if dbcsInst.Spec.KMSConfig != nil && dbcsInst.Spec.KMSConfig.KeyName != "" { kmsVaultClient, err := keymanagement.NewKmsVaultClientWithConfigurationProvider(provider) @@ -236,6 +254,7 @@ func (r *DbcsSystemReconciler) Reconcile(ctx context.Context, req ctrl.Request) listResp, err := kmsVaultClient.ListVaults(ctx, getVaultReq) if err != nil { + dbcsInst.Status.Message = err.Error() return ctrl.Result{}, fmt.Errorf("error listing vaults: %v", err) } @@ -260,8 +279,9 @@ func (r *DbcsSystemReconciler) Reconcile(ctx context.Context, req ctrl.Request) if existingVaultId == nil { // Create the KMS vault - createResp, err := r.createKMSVault(ctx, &dbcsInst.Spec.KMSConfig, kmsClient, &dbcsInst.Status.KMSDetailsStatus) + createResp, err := r.createKMSVault(ctx, dbcsInst.Spec.KMSConfig, kmsClient, &dbcsInst.Status.KMSDetailsStatus) if err != nil { + dbcsInst.Status.Message = err.Error() return ctrl.Result{}, fmt.Errorf("error creating vault: %v", err) } existingVaultId = createResp.Id @@ -287,6 +307,7 @@ func (r *DbcsSystemReconciler) Reconcile(ctx context.Context, req ctrl.Request) listKeysResp, err := kmsClient.ListKeys(ctx, listKeysReq) if err != nil { r.Logger.Error(err, "Error listing keys in existing vault") + dbcsInst.Status.Message = err.Error() return ctrl.Result{}, err } @@ -305,7 +326,7 @@ func (r *DbcsSystemReconciler) Reconcile(ctx context.Context, req ctrl.Request) r.Logger.Info("Master key not found in existing vault, creating new key") // Create the KMS key in the existing vault - keyResponse, err := r.createKMSKey(ctx, &dbcsInst.Spec.KMSConfig, kmsClient, &dbcsInst.Status.KMSDetailsStatus) + keyResponse, err := r.createKMSKey(ctx, dbcsInst.Spec.KMSConfig, kmsClient, &dbcsInst.Status.KMSDetailsStatus) if err != nil { return ctrl.Result{}, err } @@ -324,15 +345,17 @@ func (r *DbcsSystemReconciler) Reconcile(ctx context.Context, req ctrl.Request) r.Logger.Info("Creating new vault") // Create the new vault - vaultResponse, err := r.createKMSVault(ctx, &dbcsInst.Spec.KMSConfig, kmsClient, &dbcsInst.Status.KMSDetailsStatus) + vaultResponse, err := r.createKMSVault(ctx, dbcsInst.Spec.KMSConfig, kmsClient, &dbcsInst.Status.KMSDetailsStatus) if err != nil { + dbcsInst.Status.Message = err.Error() return ctrl.Result{}, err } dbcsInst.Status.KMSDetailsStatus.VaultId = *vaultResponse.Id dbcsInst.Status.KMSDetailsStatus.ManagementEndpoint = *vaultResponse.ManagementEndpoint // Create the KMS key in the newly created vault - keyResponse, err := r.createKMSKey(ctx, &dbcsInst.Spec.KMSConfig, kmsClient, &dbcsInst.Status.KMSDetailsStatus) + keyResponse, err := r.createKMSKey(ctx, dbcsInst.Spec.KMSConfig, kmsClient, &dbcsInst.Status.KMSDetailsStatus) if err != nil { + dbcsInst.Status.Message = err.Error() return ctrl.Result{}, err } @@ -343,11 +366,207 @@ func (r *DbcsSystemReconciler) Reconcile(ctx context.Context, req ctrl.Request) } } } - //debugging - // lastSuccessfullSpec = nil - // r.ensureDBSystemSpec(&dbcsInst.Spec.DbSystem) - // Check if cloning is needed, debugging - // *dbcsInst.Status.DbCloneStatus.Id = "" + // Backup Creation + if dbcsInst.Spec.EnableBackup { + var compartmentId string + if dbcsInst.Spec.DbSystem.CompartmentId != "" { + compartmentId = dbcsInst.Spec.DbSystem.CompartmentId + } else if dbcsInst.Spec.Id != nil && *dbcsInst.Spec.Id != "" { + var err error + compartmentId, err = r.getCompartmentIDByDbSystemID(ctx, *dbcsInst.Spec.Id) + if err != nil { + fmt.Printf("Failed to get compartment ID: %v\n", err) + dbcsInst.Status.Message = err.Error() + return ctrl.Result{}, err + } + } else { + err := fmt.Errorf("compartment ID or DB system ID must be set") + dbcsInst.Status.Message = err.Error() + return ctrl.Result{}, err + } + backupId, err := dbcsv4.CreateDbcsBackup(compartmentId, r.Logger, r.dbClient, dbcsInst, r.KubeClient, r.nwClient, r.wrClient) + if err != nil { + r.Logger.Error(err, "Backup creation failed") + dbcsInst.Status.Message = err.Error() + return ctrl.Result{}, nil + } else { + + dbHomeId, err := r.getDbHomeIdByDbSystemID(ctx, compartmentId, *dbcsInst.Spec.Id) + if err != nil { + fmt.Printf("Failed to get DB Home ID: %v\n", err) + dbcsInst.Status.Message = err.Error() + return ctrl.Result{}, err + } + + databaseIds, err := r.getDatabaseIDByDbSystemID(ctx, *dbcsInst.Spec.Id, compartmentId, dbHomeId) + if err != nil { + fmt.Printf("Failed to get database IDs: %v\n", err) + dbcsInst.Status.Message = err.Error() + return ctrl.Result{}, err + } + + // Assume the first database is the one to back up (customize as needed) + databaseId := databaseIds[0] + // After successful creation and backup becomes ACTIVE + listBackupsReq := database.ListBackupsRequest{ + DatabaseId: &databaseId, + } + + listBackupsResp, err := r.dbClient.ListBackups(ctx, listBackupsReq) + if err != nil { + r.Logger.Error(err, "Failed to list backups") + dbcsInst.Status.Message = err.Error() + return ctrl.Result{}, nil + } + + // Reset and populate status.Backups with up-to-date backup list + dbcsInst.Status.Backups = []databasev4.BackupInfo{} + for _, b := range listBackupsResp.Items { + if b.Id != nil && b.DisplayName != nil && b.TimeStarted != nil { + dbcsInst.Status.Backups = append(dbcsInst.Status.Backups, databasev4.BackupInfo{ + Name: *b.DisplayName, + BackupID: *b.Id, + Timestamp: b.TimeStarted.Format(time.RFC3339), + }) + } + } + r.Logger.Info("Backup Completed successfully", "BackupID", backupId) + } + } + restoreCount := 0 + var restoreInfo *databasev4.RestoreConfig + if dbcsInst.Spec.DbSystem != nil { + restoreInfo = dbcsInst.Spec.DbSystem.RestoreConfig + } + if restoreInfo != nil { + if restoreInfo.Latest { + restoreCount++ + } + if restoreInfo.Timestamp != nil { + restoreCount++ + } + if restoreInfo.SCN != nil { + restoreCount++ + } + + if restoreCount > 1 { + return ctrl.Result{}, fmt.Errorf("exactly one of Timestamp, SCN, or Latest must be specified for restore") + } + + aj, _ := json.Marshal(dbcsInst.Spec) + bj, _ := json.Marshal(lastSuccessfullSpec) + + if bytes.Equal(aj, bj) { + r.Logger.Info("Restore already applied — skipping", "DbcsSystem", dbcsInst.Name) + } else if restoreCount == 1 { // do restore when exactly one restore is new spec and defined correctly + switch { + case restoreInfo.Latest: + err = dbcsv4.RestoreDbcsToPoint(compartmentId, r.Logger, r.dbClient, dbcsInst, + databasev4.RestoreConfig{Latest: true}, + r.KubeClient, r.nwClient, r.wrClient) + + case restoreInfo.Timestamp != nil: + err = dbcsv4.RestoreDbcsToPoint(compartmentId, r.Logger, r.dbClient, dbcsInst, + databasev4.RestoreConfig{Timestamp: restoreInfo.Timestamp}, + r.KubeClient, r.nwClient, r.wrClient) + + case restoreInfo.SCN != nil: + err = dbcsv4.RestoreDbcsToPoint(compartmentId, r.Logger, r.dbClient, dbcsInst, + databasev4.RestoreConfig{SCN: restoreInfo.SCN}, + r.KubeClient, r.nwClient, r.wrClient) + } + + if err != nil { + r.Logger.Error(err, "Restore failed") + return ctrl.Result{}, err + } + + // STEP 4: Mark spec as successfully applied + if err := dbcsInst.UpdateLastSuccessfulSpec(r.KubeClient); err != nil { + dbcsInst.Status.Message = err.Error() + return ctrl.Result{}, err + } + + r.Logger.Info("Restore Operation Completed and lastSuccessfulSpec updated", "DbcsSystem", dbcsInst.Name) + return ctrl.Result{}, nil + } + } + + switch { + case dbcsInst.Spec.IsPatch && dbcsInst.Spec.IsUpgrade: + errMsg := "Both IsPatch and IsUpgrade are set. Only one operation can be performed at a time." + r.Logger.Error(nil, errMsg) + dbcsInst.Status.Message = errMsg + return ctrl.Result{}, nil + + case dbcsInst.Spec.IsPatch: + if dbcsInst.Spec.Id == nil || dbcsInst.Spec.DbSystem.PatchOCID == "" { + errMsg := "Patching is requested but Patch Version or DB System ID is missing." + r.Logger.Error(nil, errMsg) + dbcsInst.Status.Message = errMsg + return ctrl.Result{}, nil + } + compartmentId, err := r.getCompartmentIDByDbSystemID(ctx, *dbcsInst.Spec.Id) + if err != nil { + fmt.Printf("Failed to get compartment ID: %v\n", err) + dbcsInst.Status.Message = err.Error() + return ctrl.Result{}, err + } + + err = dbcsv4.PatchDBSystem(ctx, compartmentId, r.Logger, r.KubeClient, r.dbClient, dbcsInst, r.nwClient, r.wrClient, dbcsInst.Spec.DbSystem.DbHomeId, dbcsInst.Spec.DbSystem.PatchOCID) + if err != nil { + r.Logger.Error(err, "Fail to patch db system") + if statusErr := dbcsv4.SetLifecycleState(compartmentId, r.KubeClient, r.dbClient, dbcsInst, databasev4.Failed, r.nwClient, r.wrClient); statusErr != nil { + dbcsInst.Status.Message = err.Error() + return ctrl.Result{}, statusErr + } + } + + case dbcsInst.Spec.IsUpgrade: + if dbcsInst.Spec.Id == nil || dbcsInst.Spec.DbSystem.UpgradeVersion == "" { + errMsg := "Upgrade is requested but Upgrade Version or DB System ID is missing." + r.Logger.Error(nil, errMsg) + dbcsInst.Status.Message = errMsg + return ctrl.Result{}, nil + } + compartmentId, err := r.getCompartmentIDByDbSystemID(ctx, *dbcsInst.Spec.Id) + if err != nil { + fmt.Printf("Failed to get compartment ID: %v\n", err) + dbcsInst.Status.Message = err.Error() + return ctrl.Result{}, err + } + + err = dbcsv4.UpgradeDatabaseVersion(ctx, compartmentId, r.Logger, r.KubeClient, r.dbClient, dbcsInst, r.nwClient, r.wrClient, *dbcsInst.Spec.DatabaseId, dbcsInst.Spec.DbSystem.UpgradeVersion) + if err != nil { + r.Logger.Error(err, "Failed to upgrade DB Home.") + dbcsInst.Status.Message = fmt.Sprintf("Upgrade failed: %v", err) + return ctrl.Result{}, err + } + + } + + isDeleteDataguard := false + setupDataguard := false + + // Check if DataGuard is marked for deletion + if dbcsInst.Spec.DataGuard.IsDelete { + isDeleteDataguard = true + // If marked for delete, skip DataGuard setup + setupDataguard = false + } else if dbcsInst.Spec.DataGuard.Enabled { + // Proceed to check required fields for DataGuard setup + if dbcsInst.Spec.DataGuard.PrimaryDatabaseId == nil || + dbcsInst.Spec.DataGuard.DbAdminPasswordSecret == nil || + dbcsInst.Spec.DataGuard.DisplayName == nil { + r.Logger.Error(err, "setupDataguard is defined but other necessary details are not present. Refer README.md file for instructions.") + return ctrl.Result{}, nil + } + // All required fields are present, enable setup + setupDataguard = true + } + + //-----------------Clone Setup---------------------------------// + setupCloning := false // Check if SetupDBCloning is true and ensure one of the required fields is provided if dbcsInst.Spec.SetupDBCloning { @@ -355,6 +574,7 @@ func (r *DbcsSystemReconciler) Reconcile(ctx context.Context, req ctrl.Request) if dbcsInst.Spec.Id == nil && dbcsInst.Spec.DbBackupId == nil && dbcsInst.Spec.DatabaseId == nil { // If none of the required fields are set, log an error and exit the function r.Logger.Error(err, "SetupDBCloning is defined but other necessary details (Id, DbBackupId, DatabaseId) are not present. Refer README.md file for instructions.") + dbcsInst.Status.Message = "SetupDBCloning is defined but other necessary details (Id, DbBackupId, DatabaseId) are not present. Refer README.md file for instructions." return ctrl.Result{}, nil } // If the condition is met, proceed with cloning setup @@ -363,17 +583,19 @@ func (r *DbcsSystemReconciler) Reconcile(ctx context.Context, req ctrl.Request) // If SetupDBCloning is false, continue as usual without cloning setupCloning = false } - var dbSystemId string // Executing DB Cloning Process, if defined. Do not repeat cloning again when Status has Id present. if setupCloning && dbcsInst.Status.DbCloneStatus.Id == nil { + // if setupCloning { + switch { case dbcsInst.Spec.SetupDBCloning && dbcsInst.Spec.DbBackupId != nil: - dbSystemId, err = dbcsv1.CloneFromBackupAndGetDbcsId(r.Logger, r.KubeClient, r.dbClient, dbcsInst, r.nwClient, r.wrClient) + dbSystemId, err = dbcsv4.CloneFromBackupAndGetDbcsId(compartmentId, r.Logger, r.KubeClient, r.dbClient, dbcsInst, r.nwClient, r.wrClient) if err != nil { r.Logger.Error(err, "Fail to clone db system from backup and get DbcsSystem System ID") - if statusErr := dbcsv1.SetLifecycleState(r.KubeClient, r.dbClient, dbcsInst, databasev4.Failed, r.nwClient, r.wrClient); statusErr != nil { + if statusErr := dbcsv4.SetLifecycleState(compartmentId, r.KubeClient, r.dbClient, dbcsInst, databasev4.Failed, r.nwClient, r.wrClient); statusErr != nil { + dbcsInst.Status.Message = err.Error() return ctrl.Result{}, statusErr } @@ -382,10 +604,12 @@ func (r *DbcsSystemReconciler) Reconcile(ctx context.Context, req ctrl.Request) r.Logger.Info("DB Cloning completed successfully from provided backup DB system") case dbcsInst.Spec.SetupDBCloning && dbcsInst.Spec.DatabaseId != nil: - dbSystemId, err = dbcsv1.CloneFromDatabaseAndGetDbcsId(r.Logger, r.KubeClient, r.dbClient, dbcsInst, r.nwClient, r.wrClient) + dbSystemId, err = dbcsv4.CloneFromDatabaseAndGetDbcsId(compartmentId, r.Logger, r.KubeClient, r.dbClient, dbcsInst, r.nwClient, r.wrClient) if err != nil { r.Logger.Error(err, "Fail to clone db system from DatabaseID provided") - if statusErr := dbcsv1.SetLifecycleState(r.KubeClient, r.dbClient, dbcsInst, databasev4.Failed, r.nwClient, r.wrClient); statusErr != nil { + dbcsInst.Status.Message = err.Error() + if statusErr := dbcsv4.SetLifecycleState(compartmentId, r.KubeClient, r.dbClient, dbcsInst, databasev4.Failed, r.nwClient, r.wrClient); statusErr != nil { + return ctrl.Result{}, statusErr } @@ -394,31 +618,38 @@ func (r *DbcsSystemReconciler) Reconcile(ctx context.Context, req ctrl.Request) r.Logger.Info("DB Cloning completed successfully from provided databaseId") case dbcsInst.Spec.SetupDBCloning && dbcsInst.Spec.DbBackupId == nil && dbcsInst.Spec.DatabaseId == nil: - dbSystemId, err = dbcsv1.CloneAndGetDbcsId(r.Logger, r.KubeClient, r.dbClient, dbcsInst, r.nwClient, r.wrClient) + dbSystemId, err = dbcsv4.CloneAndGetDbcsId(compartmentId, r.Logger, r.KubeClient, r.dbClient, dbcsInst, r.nwClient, r.wrClient) if err != nil { r.Logger.Error(err, "Fail to clone db system and get DbcsSystem System ID") - if statusErr := dbcsv1.SetLifecycleState(r.KubeClient, r.dbClient, dbcsInst, databasev4.Failed, r.nwClient, r.wrClient); statusErr != nil { + if statusErr := dbcsv4.SetLifecycleState(compartmentId, r.KubeClient, r.dbClient, dbcsInst, databasev4.Failed, r.nwClient, r.wrClient); statusErr != nil { + dbcsInst.Status.Message = err.Error() return ctrl.Result{}, statusErr } return ctrl.Result{}, nil } r.Logger.Info("DB Cloning completed successfully from provided db system") } - } else if !setupCloning { + } else if !setupCloning && !setupDataguard && !isDeleteDataguard { if dbcsInst.Spec.Id == nil && lastSuccessfullSpec == nil { // If no DbcsSystem ID specified, create a new DB System // ======================== Validate Specs ============== - err = dbcsv1.ValidateSpex(r.Logger, r.KubeClient, r.dbClient, dbcsInst, r.nwClient, r.Recorder) + if dbcsInst == nil { + // Safety guard + return ctrl.Result{}, nil + } + err = dbcsv4.ValidateSpex(r.Logger, r.KubeClient, r.dbClient, dbcsInst, r.nwClient, r.Recorder) if err != nil { + dbcsInst.Status.Message = err.Error() return ctrl.Result{}, err } r.Logger.Info("DbcsSystem DBSystem provisioning") - dbcsID, err := dbcsv1.CreateAndGetDbcsId(r.Logger, r.KubeClient, r.dbClient, dbcsInst, r.nwClient, r.wrClient, &dbcsInst.Status.KMSDetailsStatus) + dbcsID, err := dbcsv4.CreateAndGetDbcsId(compartmentId, r.Logger, r.KubeClient, r.dbClient, dbcsInst, r.nwClient, r.wrClient, &dbcsInst.Status.KMSDetailsStatus) if err != nil { + dbcsInst.Status.Message = err.Error() r.Logger.Error(err, "Fail to provision and get DbcsSystem System ID") // Change the status to Failed - if statusErr := dbcsv1.SetLifecycleState(r.KubeClient, r.dbClient, dbcsInst, databasev4.Failed, r.nwClient, r.wrClient); statusErr != nil { + if statusErr := dbcsv4.SetLifecycleState(compartmentId, r.KubeClient, r.dbClient, dbcsInst, databasev4.Failed, r.nwClient, r.wrClient); statusErr != nil { return ctrl.Result{}, statusErr } // The reconciler should not requeue since the error returned from OCI during update will not be solved by requeue @@ -428,16 +659,17 @@ func (r *DbcsSystemReconciler) Reconcile(ctx context.Context, req ctrl.Request) assignDBCSID(dbcsInst, dbcsID) // Check if KMSConfig is specified kmsConfig := dbcsInst.Spec.KMSConfig - if kmsConfig != (databasev4.KMSConfig{}) { + if kmsConfig != nil { // Check if KMSDetailsStatus is uninitialized (zero value) - if dbcsInst.Spec.DbSystem.KMSConfig != dbcsInst.Spec.KMSConfig { + if dbcsInst.Spec.DbSystem.KMSConfig != nil && dbcsInst.Spec.KMSConfig != nil && + *dbcsInst.Spec.DbSystem.KMSConfig != *dbcsInst.Spec.KMSConfig { dbcsInst.Spec.DbSystem.KMSConfig = dbcsInst.Spec.KMSConfig } } - if err := dbcsv1.UpdateDbcsSystemId(r.KubeClient, dbcsInst); err != nil { + if err := dbcsv4.UpdateDbcsSystemId(r.KubeClient, dbcsInst); err != nil { // Change the status to Failed assignDBCSID(dbcsInst, dbcsID) - if statusErr := dbcsv1.SetLifecycleState(r.KubeClient, r.dbClient, dbcsInst, databasev4.Failed, r.nwClient, r.wrClient); statusErr != nil { + if statusErr := dbcsv4.SetLifecycleState(compartmentId, r.KubeClient, r.dbClient, dbcsInst, databasev4.Failed, r.nwClient, r.wrClient); statusErr != nil { return ctrl.Result{}, statusErr } return ctrl.Result{}, err @@ -446,28 +678,30 @@ func (r *DbcsSystemReconciler) Reconcile(ctx context.Context, req ctrl.Request) r.Logger.Info("DbcsSystem system provisioned succesfully") assignDBCSID(dbcsInst, dbcsID) if err := dbcsInst.UpdateLastSuccessfulSpec(r.KubeClient); err != nil { + dbcsInst.Status.Message = err.Error() return ctrl.Result{}, err } assignDBCSID(dbcsInst, dbcsID) } else { - if lastSuccessfullSpec == nil { // first time after creation of DB - if err := dbcsv1.GetDbSystemId(r.Logger, r.dbClient, dbcsInst); err != nil { + if lastSuccessfullSpec == nil { // first time update after creation of DB + if err := dbcsv4.GetDbSystemId(r.Logger, r.dbClient, dbcsInst); err != nil { // Change the status to Failed - if statusErr := dbcsv1.SetLifecycleState(r.KubeClient, r.dbClient, dbcsInst, databasev4.Failed, r.nwClient, r.wrClient); statusErr != nil { + if statusErr := dbcsv4.SetLifecycleState(compartmentId, r.KubeClient, r.dbClient, dbcsInst, databasev4.Failed, r.nwClient, r.wrClient); statusErr != nil { return ctrl.Result{}, statusErr } return ctrl.Result{}, err } - if err := dbcsv1.SetDBCSDatabaseLifecycleState(r.Logger, r.KubeClient, r.dbClient, dbcsInst, r.nwClient, r.wrClient); err != nil { + if err := dbcsv4.SetDBCSDatabaseLifecycleState(compartmentId, r.Logger, r.KubeClient, r.dbClient, dbcsInst, r.nwClient, r.wrClient); err != nil { // Change the status to required state + dbcsInst.Status.Message = err.Error() return ctrl.Result{}, err } dbSystemId := *dbcsInst.Spec.Id - if err := dbcsv1.UpdateDbcsSystemId(r.KubeClient, dbcsInst); err != nil { + if err := dbcsv4.UpdateDbcsSystemId(r.KubeClient, dbcsInst); err != nil { // Change the status to Failed assignDBCSID(dbcsInst, dbSystemId) - if statusErr := dbcsv1.SetLifecycleState(r.KubeClient, r.dbClient, dbcsInst, databasev4.Failed, r.nwClient, r.wrClient); statusErr != nil { + if statusErr := dbcsv4.SetLifecycleState(compartmentId, r.KubeClient, r.dbClient, dbcsInst, databasev4.Failed, r.nwClient, r.wrClient); statusErr != nil { return ctrl.Result{}, statusErr } return ctrl.Result{}, err @@ -477,6 +711,7 @@ func (r *DbcsSystemReconciler) Reconcile(ctx context.Context, req ctrl.Request) dbSystemId = *dbcsInst.Spec.Id if err := dbcsInst.UpdateLastSuccessfulSpec(r.KubeClient); err != nil { + dbcsInst.Status.Message = err.Error() return ctrl.Result{}, err } assignDBCSID(dbcsInst, dbSystemId) @@ -489,46 +724,75 @@ func (r *DbcsSystemReconciler) Reconcile(ctx context.Context, req ctrl.Request) dbSystemId = *dbcsInst.Spec.Id } //debugging - // *dbcsInst.Spec.Id = "ocid1.dbsystem.oc1.iad.anuwcljsabf7htya55wz5vfil7ul3pkzpubnymp6zrp3fhgomv3fcdr2vtiq" + compartmentId, err := r.getCompartmentIDByDbSystemID(ctx, *dbcsInst.Spec.Id) if err != nil { fmt.Printf("Failed to get compartment ID: %v\n", err) + dbcsInst.Status.Message = err.Error() return ctrl.Result{}, err } dbHomeId, err := r.getDbHomeIdByDbSystemID(ctx, compartmentId, *dbcsInst.Spec.Id) if err != nil { fmt.Printf("Failed to get DB Home ID: %v\n", err) + dbcsInst.Status.Message = err.Error() return ctrl.Result{}, err } databaseIds, err := r.getDatabaseIDByDbSystemID(ctx, *dbcsInst.Spec.Id, compartmentId, dbHomeId) if err != nil { fmt.Printf("Failed to get database IDs: %v\n", err) + dbcsInst.Status.Message = err.Error() return ctrl.Result{}, err } err = r.getPluggableDatabaseDetails(ctx, dbcsInst, *dbcsInst.Spec.Id, databaseIds) if err != nil { fmt.Printf("Failed to get pluggable database details: %v\n", err) + dbcsInst.Status.Message = err.Error() return ctrl.Result{}, err } + // Example: assume you have dataGuardRetryCount as an int - if err := dbcsv1.UpdateDbcsSystemIdInst(r.Logger, r.dbClient, dbcsInst, r.KubeClient, r.nwClient, r.wrClient, databaseIds[0]); err != nil { + if len(databaseIds) > 0 && dbcsInst.Spec.Id != nil { + dataGuardRetryCount := 0 + if dataGuardRetryCount < 5 { + err = r.getDataGuardStatusAndUpdate(ctx, dbcsInst, databaseIds[0], *dbcsInst.Spec.Id) + if err != nil { + dataGuardRetryCount++ + // persist the updated retry count + + fmt.Printf("Failed to get dataguard details (attempt %d/5): %v\n", + dataGuardRetryCount, err) + return ctrl.Result{Requeue: true}, err + } + // reset retry count on success + dataGuardRetryCount = 0 + } else { + fmt.Println("Max retries (5) reached. Failing DataGuard status update.") + return ctrl.Result{}, fmt.Errorf("max retries reached for DataGuard status update") + } + } else { + fmt.Println("Skipping DataGuard status update as DatabaseIds or DbcsSystem ID is nil") + } + + if err := dbcsv4.UpdateDbcsSystemIdInst(compartmentId, r.Logger, r.dbClient, dbcsInst, r.KubeClient, r.nwClient, r.wrClient, databaseIds[0]); err != nil { r.Logger.Error(err, "Fail to update DbcsSystem Id") + dbcsInst.Status.Message = err.Error() // Change the status to Failed - if statusErr := dbcsv1.SetLifecycleState(r.KubeClient, r.dbClient, dbcsInst, databasev4.Failed, r.nwClient, r.wrClient); statusErr != nil { + if statusErr := dbcsv4.SetLifecycleState(compartmentId, r.KubeClient, r.dbClient, dbcsInst, databasev4.Failed, r.nwClient, r.wrClient); statusErr != nil { return ctrl.Result{}, statusErr } // The reconciler should not requeue since the error returned from OCI during update will not be solved by requeue return ctrl.Result{}, nil } - if err := dbcsv1.SetDBCSDatabaseLifecycleState(r.Logger, r.KubeClient, r.dbClient, dbcsInst, r.nwClient, r.wrClient); err != nil { + if err := dbcsv4.SetDBCSDatabaseLifecycleState(compartmentId, r.Logger, r.KubeClient, r.dbClient, dbcsInst, r.nwClient, r.wrClient); err != nil { // Change the status to required state return ctrl.Result{}, err } // Update Spec and Status result, err := r.updateSpecsAndStatus(ctx, dbcsInst, dbSystemId) if err != nil { + dbcsInst.Status.Message = err.Error() return result, err } } @@ -537,29 +801,85 @@ func (r *DbcsSystemReconciler) Reconcile(ctx context.Context, req ctrl.Request) // Update the Wallet Secret when the secret name is given //r.updateWalletSecret(dbcs) + // Dataguard enablement + switch { + case setupDataguard: + // Data Guard Creation Flow + compartmentId, err := r.getCompartmentIDByDbSystemID(ctx, *dbcsInst.Spec.Id) + if err != nil { + fmt.Printf("Failed to get compartment ID: %v\n", err) + dbcsInst.Status.Message = err.Error() + return ctrl.Result{}, err + } + if err := r.EnableDataGuard(ctx, compartmentId, r.Logger, r.dbClient, dbcsInst, r.KubeClient, r.nwClient, r.wrClient, *dbcsInst.Spec.DataGuard.PrimaryDatabaseId, &dbcsInst.Spec.DataGuard); err != nil { + r.Logger.Error(err, "Failed to enable Data Guard and update DbcsSystem ID") + + // Update status to Failed + if statusErr := dbcsv4.SetLifecycleState(compartmentId, r.KubeClient, r.dbClient, dbcsInst, databasev4.Failed, r.nwClient, r.wrClient); statusErr != nil { + return ctrl.Result{}, statusErr + } + return ctrl.Result{}, nil + } + + if err := r.KubeClient.Status().Update(ctx, dbcsInst); err != nil { + r.Logger.Error(err, "Failed to update DB status") + return reconcile.Result{}, err + } + + if dbcsInst.Status.DataGuardStatus != nil && dbcsInst.Status.DataGuardStatus.PeerDbSystemId != nil { + dbSystemId = *dbcsInst.Status.DataGuardStatus.PeerDbSystemId + assignDBCSID(dbcsInst, dbSystemId) + } + if err := dbcsInst.UpdateLastSuccessfulSpec(r.KubeClient); err != nil { + return ctrl.Result{}, err + } + + case isDeleteDataguard: + // Data Guard Deletion Flow + compartmentId, err := r.getCompartmentIDByDbSystemID(ctx, *dbcsInst.Spec.Id) + if err != nil { + fmt.Printf("Failed to get compartment ID: %v\n", err) + dbcsInst.Status.Message = err.Error() + return ctrl.Result{}, err + } + if err := r.DeleteDataGuard(ctx, compartmentId, r.Logger, r.dbClient, dbcsInst); err != nil { + r.Logger.Error(err, "Failed to delete Data Guard") + + // Update status to Failed + if statusErr := dbcsv4.SetLifecycleState(compartmentId, r.KubeClient, r.dbClient, dbcsInst, databasev4.Failed, r.nwClient, r.wrClient); statusErr != nil { + return ctrl.Result{}, statusErr + } + return ctrl.Result{}, nil + } + + } // Update the last succesful spec if dbcsInst.Spec.Id != nil { dbSystemId = *dbcsInst.Spec.Id if err := dbcsInst.UpdateLastSuccessfulSpec(r.KubeClient); err != nil { + dbcsInst.Status.Message = err.Error() return ctrl.Result{}, err } } else if dbcsInst.Status.DbCloneStatus.Id != nil { dbSystemId = *dbcsInst.Status.DbCloneStatus.Id + } else if setupDataguard { + dbSystemId = *dbcsInst.Status.DataGuardStatus.PeerDbSystemId } - //assignDBCSID(dbcsInst,dbcsI) + // Change the phase to "Available" assignDBCSID(dbcsInst, dbSystemId) - if statusErr := dbcsv1.SetLifecycleState(r.KubeClient, r.dbClient, dbcsInst, databasev4.Available, r.nwClient, r.wrClient); statusErr != nil { + if statusErr := dbcsv4.SetLifecycleState(compartmentId, r.KubeClient, r.dbClient, dbcsInst, databasev4.Available, r.nwClient, r.wrClient); statusErr != nil { return ctrl.Result{}, statusErr } - r.Logger.Info("DBInst after assignment", "dbcsInst:->", dbcsInst) + // r.Logger.Info("DBInst after assignment", "dbcsInst:->", dbcsInst) // Check if specified PDB exists or needs to be created exists, err := r.validatePDBExistence(dbcsInst) if err != nil { + dbcsInst.Status.Message = err.Error() fmt.Printf("Failed to get PDB Details: %v\n", err) return ctrl.Result{}, err } @@ -572,17 +892,20 @@ func (r *DbcsSystemReconciler) Reconcile(ctx context.Context, req ctrl.Request) // Get Compartment ID by DB System ID compartmentId, err := r.getCompartmentIDByDbSystemID(ctx, dbSystemId) if err != nil { + dbcsInst.Status.Message = err.Error() fmt.Printf("Failed to get compartment ID: %v\n", err) return ctrl.Result{}, err } dbHomeId, err := r.getDbHomeIdByDbSystemID(ctx, compartmentId, dbSystemId) if err != nil { fmt.Printf("Failed to get DB Home ID: %v\n", err) + dbcsInst.Status.Message = err.Error() return ctrl.Result{}, err } databaseIds, err := r.getDatabaseIDByDbSystemID(ctx, dbSystemId, compartmentId, dbHomeId) if err != nil { fmt.Printf("Failed to get database IDs: %v\n", err) + dbcsInst.Status.Message = err.Error() return ctrl.Result{}, err } @@ -593,6 +916,7 @@ func (r *DbcsSystemReconciler) Reconcile(ctx context.Context, req ctrl.Request) if pdbConfig.IsDelete != nil && *pdbConfig.IsDelete { // Call deletePluggableDatabase function if err := r.deletePluggableDatabase(ctx, pdbConfig, dbSystemId); err != nil { + dbcsInst.Status.Message = err.Error() return ctrl.Result{}, err } // Continue to the next pdbConfig @@ -603,6 +927,7 @@ func (r *DbcsSystemReconciler) Reconcile(ctx context.Context, req ctrl.Request) pdbId, err := r.createPluggableDatabase(ctx, dbcsInst, pdbConfig, databaseIds[0], compartmentId, dbSystemId) if err != nil { // Handle error if required + dbcsInst.Status.Message = err.Error() return ctrl.Result{}, err } @@ -638,10 +963,9 @@ func (r *DbcsSystemReconciler) Reconcile(ctx context.Context, req ctrl.Request) // Assign the updated slice to dbcsInst.Status.PdbDetailsStatus dbcsInst.Status.PdbDetailsStatus = updatedPdbDetailsStatus - // Update the status in Kubernetes - // Update the status subresource err = r.KubeClient.Status().Update(ctx, dbcsInst) if err != nil { + dbcsInst.Status.Message = err.Error() r.Logger.Error(err, "Failed to update DB status") return reconcile.Result{}, err } @@ -653,10 +977,6 @@ func (r *DbcsSystemReconciler) Reconcile(ctx context.Context, req ctrl.Request) r.Logger.Info("No change in PDB configurations or, already existed PDB Status.") } } - // } else { - // r.Logger.Info("No PDB configurations given.") - // } - // r.Logger.Info("DBInst after assignment", "dbcsInst:->", dbcsInst) // // Check if PDBConfig is defined and needs to be created or deleted pdbConfigs := dbcsInst.Spec.PdbConfigs if pdbConfigs != nil { @@ -667,16 +987,19 @@ func (r *DbcsSystemReconciler) Reconcile(ctx context.Context, req ctrl.Request) // Get Compartment ID by DB System ID compartmentId, err := r.getCompartmentIDByDbSystemID(ctx, dbSystemId) if err != nil { + dbcsInst.Status.Message = err.Error() fmt.Printf("Failed to get compartment ID: %v\n", err) return ctrl.Result{}, err } dbHomeId, err := r.getDbHomeIdByDbSystemID(ctx, compartmentId, dbSystemId) if err != nil { + dbcsInst.Status.Message = err.Error() fmt.Printf("Failed to get DB Home ID: %v\n", err) return ctrl.Result{}, err } databaseIds, err := r.getDatabaseIDByDbSystemID(ctx, dbSystemId, compartmentId, dbHomeId) if err != nil { + dbcsInst.Status.Message = err.Error() fmt.Printf("Failed to get database IDs: %v\n", err) return ctrl.Result{}, err } @@ -688,6 +1011,7 @@ func (r *DbcsSystemReconciler) Reconcile(ctx context.Context, req ctrl.Request) if pdbConfig.IsDelete != nil && *pdbConfig.IsDelete { // Call deletePluggableDatabase function if err := r.deletePluggableDatabase(ctx, pdbConfig, dbSystemId); err != nil { + dbcsInst.Status.Message = err.Error() return ctrl.Result{}, err } // Continue to the next pdbConfig @@ -698,6 +1022,7 @@ func (r *DbcsSystemReconciler) Reconcile(ctx context.Context, req ctrl.Request) _, err := r.createPluggableDatabase(ctx, dbcsInst, pdbConfig, databaseIds[0], compartmentId, dbSystemId) if err != nil { // Handle error if required + dbcsInst.Status.Message = err.Error() return ctrl.Result{}, err } } @@ -708,6 +1033,386 @@ func (r *DbcsSystemReconciler) Reconcile(ctx context.Context, req ctrl.Request) return resultQ, nil } + +func (r *DbcsSystemReconciler) DeleteDataGuard( + ctx context.Context, + compartmentId string, + log logr.Logger, + dbClient database.DatabaseClient, + dbcsInst *databasev4.DbcsSystem, +) error { + dataGuardStatus := dbcsInst.Status.DataGuardStatus + var peerDbSystemId *string + + // Prefer runtime status, fallback to spec if available + if dataGuardStatus != nil && dataGuardStatus.PeerDbSystemId != nil { + peerDbSystemId = dataGuardStatus.PeerDbSystemId + } + if dbcsInst.Spec.DataGuard.PeerDbSystemId != nil { + peerDbSystemId = dbcsInst.Spec.DataGuard.PeerDbSystemId + } + + if peerDbSystemId == nil { + msg := "Skipping Data Guard deletion — peer DB System ID is nil" + log.Info(msg) + dbcsInst.Status.Message = msg + dbcsInst.Status.DataGuardStatus = nil + _ = r.KubeClient.Status().Update(ctx, dbcsInst) + return nil + } + if dbcsInst.Status.DataGuardStatus == nil { + dbcsInst.Status.DataGuardStatus = &databasev4.DataGuardStatus{} + } + status := dbcsInst.Status.DataGuardStatus + + // Use Spec to fill necessary details + spec := dbcsInst.Spec.DataGuard + + // Populate Status from Spec (if present) + status.DbAdminPasswordSecret = spec.DbAdminPasswordSecret + // status.PeerDbSystemId = spec.PeerDbSystemId + status.PrimaryDatabaseId = dbcsInst.Spec.Id + status.ProtectionMode = spec.ProtectionMode + status.TransportType = spec.TransportType + status.PeerRole = spec.PeerRole + status.Shape = spec.Shape + status.SubnetId = spec.SubnetId + + getDbSystemResp, err := dbClient.GetDbSystem(ctx, database.GetDbSystemRequest{ + DbSystemId: peerDbSystemId, + }) + if err != nil { + if svcErr, ok := err.(common.ServiceError); ok && svcErr.GetHTTPStatusCode() == 404 { + msg := fmt.Sprintf("Peer DB System %s already terminated or not found, cleaning up Data Guardstatus", + func(s *string) string { + if s == nil { + return "" + } + return *s + }(peerDbSystemId), + ) + log.Info(msg) + dbcsInst.Status.Message = msg + dbcsInst.Status.DataGuardStatus = nil + _ = r.KubeClient.Status().Update(ctx, dbcsInst) + return nil + } + + log.Error(err, "Failed to fetch peer DB system status") + dbcsInst.Status.Message = "Failed to fetch peer DB system status" + _ = r.KubeClient.Status().Update(ctx, dbcsInst) + return err + } + + var primaryDatabaseId *string + + // List DB Homes + dbHomesResp, err := dbClient.ListDbHomes(ctx, database.ListDbHomesRequest{ + CompartmentId: &compartmentId, + DbSystemId: dbcsInst.Spec.Id, + }) + if err != nil { + return fmt.Errorf("failed to list DB Homes for system %s: %w", *dbcsInst.Spec.Id, err) + } + + // Iterate DB Homes + for _, home := range dbHomesResp.Items { + dbsResp, err := dbClient.ListDatabases(ctx, database.ListDatabasesRequest{ + CompartmentId: &compartmentId, + DbHomeId: home.Id, + }) + if err != nil { + return fmt.Errorf("failed to list databases for DB Home %s: %w", *home.Id, err) + } + + for _, db := range dbsResp.Items { + // List DG associations for this DB + dgResp, err := dbClient.ListDataGuardAssociations(ctx, database.ListDataGuardAssociationsRequest{ + DatabaseId: db.Id, + }) + if err != nil { + return fmt.Errorf("failed to list Data Guard associations for DB %s: %w", *db.Id, err) + } + + for _, assoc := range dgResp.Items { + if assoc.Role == database.DataGuardAssociationSummaryRolePrimary { + primaryDatabaseId = db.Id + if dbcsInst.Status.DataGuardStatus == nil { + dbcsInst.Status.DataGuardStatus = &databasev4.DataGuardStatus{} + } + dbcsInst.Status.DataGuardStatus.PrimaryDatabaseId = primaryDatabaseId + dbcsInst.Status.DataGuardStatus.PeerDbSystemId = assoc.PeerDbSystemId + break + } + } + + if primaryDatabaseId != nil { + break + } + } + + if primaryDatabaseId != nil { + break + } + } + + if primaryDatabaseId == nil { + msg := fmt.Sprintf( + "Skipping Data Guard deletion — peer DB %s is not associated with primary DB %s", + strVal(peerDbSystemId), + strVal(primaryDatabaseId), + ) + log.Info(msg) + + dbcsInst.Status.Message = msg + + dbcsInst.Status.Message = msg + _ = r.KubeClient.Status().Update(ctx, dbcsInst) + return nil + } + + // Verify Data Guard association exists + listAssocResp, err := dbClient.ListDataGuardAssociations(ctx, database.ListDataGuardAssociationsRequest{ + DatabaseId: primaryDatabaseId, + }) + if err != nil { + log.Error(err, "Failed to list Data Guard associations for primary DB") + dbcsInst.Status.Message = "Failed to list Data Guard associations" + _ = r.KubeClient.Status().Update(ctx, dbcsInst) + return err + } + + var association *database.DataGuardAssociationSummary + for _, assoc := range listAssocResp.Items { + if assoc.PeerDbSystemId != nil && *assoc.PeerDbSystemId == *peerDbSystemId { + association = &assoc + break + } + } + + if association == nil { + msg := fmt.Sprintf( + "Skipping Data Guard deletion — peer DB %s is not associated with primary DB %s", + *peerDbSystemId, primaryDatabaseId, + ) + log.Info(msg) + dbcsInst.Status.Message = msg + if dbcsInst.Status.DataGuardStatus == nil { + dbcsInst.Status.DataGuardStatus = &databasev4.DataGuardStatus{} + } + dbcsInst.Status.DataGuardStatus.LifecycleDetails = &msg + + _ = r.KubeClient.Status().Update(ctx, dbcsInst) + + return fmt.Errorf(msg) + } + + // At this point, we know the primary and peer DB are actually in Data Guard + log.Info("Confirmed Data Guard association, proceeding with deletion", + "primary", primaryDatabaseId, + "peer", *peerDbSystemId) + + switch getDbSystemResp.DbSystem.LifecycleState { + + case database.DbSystemLifecycleStateTerminated: + log.Info("Peer DB system is already terminated.", "peerDbSystemId", *peerDbSystemId) + + terminated := string(database.DbSystemLifecycleStateTerminated) + dbcsInst.Status.DataGuardStatus.LifecycleState = &terminated + details := "Peer DB system already terminated" + dbcsInst.Status.DataGuardStatus.LifecycleDetails = &details + + dbcsInst.Status.Message = "Data Guard peer already terminated" + + if statusErr := dbcsv4.SetLifecycleState( + compartmentId, r.KubeClient, r.dbClient, dbcsInst, + databasev4.Available, r.nwClient, r.wrClient, + ); statusErr != nil { + return statusErr + } + + return r.KubeClient.Status().Update(ctx, dbcsInst) + + case database.DbSystemLifecycleStateTerminating: + // Wait until it becomes TERMINATED (with timeout) + log.Info("Peer DB system is in TERMINATING state, waiting for it to reach TERMINATED...", + "peerDbSystemId", *peerDbSystemId) + status := dbcsInst.Status.DataGuardStatus + + // Use Spec to fill necessary details + spec := dbcsInst.Spec.DataGuard + + // Populate Status from Spec (if present) + status.DbAdminPasswordSecret = spec.DbAdminPasswordSecret + // status.PeerDbSystemId = spec.PeerDbSystemId + status.PrimaryDatabaseId = dbcsInst.Spec.Id + status.ProtectionMode = spec.ProtectionMode + status.TransportType = spec.TransportType + status.PeerRole = spec.PeerRole + status.Shape = spec.Shape + status.SubnetId = spec.SubnetId + terminatingState := string(database.DbSystemLifecycleStateTerminating) + status.LifecycleState = &terminatingState + status.LifecycleDetails = common.String("Dataguard Peer DB system is terminating...") + dbcsInst.Status.State = databasev4.Update + dbcsInst.Status.Message = "Peer DB system is terminating..." + _ = r.KubeClient.Status().Update(ctx, dbcsInst) + + pollInterval := 30 * time.Second + maxWait := 120 * time.Minute + timeout := time.After(maxWait) + + for { + select { + case <-ctx.Done(): + return ctx.Err() + case <-timeout: + dbcsInst.Status.Message = "Timeout while waiting for peer DB system termination" + _ = r.KubeClient.Status().Update(ctx, dbcsInst) + return fmt.Errorf("timed out waiting for peer DB system %s to terminate", *peerDbSystemId) + + case <-time.After(pollInterval): + latest, err := dbClient.GetDbSystem(ctx, database.GetDbSystemRequest{ + DbSystemId: peerDbSystemId, + }) + if err != nil { + log.Error(err, "Failed to poll DB system lifecycle state") + return err + } + + switch latest.DbSystem.LifecycleState { + case database.DbSystemLifecycleStateTerminated: + log.Info("Peer DB system successfully terminated", "peerDbSystemId", *peerDbSystemId) + goto Cleanup + case database.DbSystemLifecycleStateFailed: + dbcsInst.Status.Message = "Peer DB system termination failed" + dbcsInst.Status.State = databasev4.Available + _ = r.KubeClient.Status().Update(ctx, dbcsInst) + return fmt.Errorf("DB system termination failed for peer %s", *peerDbSystemId) + default: + log.Info("Peer DB system still terminating...", + "peerDbSystemId", *peerDbSystemId, + "lifecycleState", latest.DbSystem.LifecycleState) + } + } + } + + default: + // Active or other state → initiate termination + if statusErr := dbcsv4.SetLifecycleState(compartmentId, r.KubeClient, r.dbClient, + dbcsInst, databasev4.Update, r.nwClient, r.wrClient); statusErr != nil { + return statusErr + } + + log.Info("Terminating peer DB system", "peerDbSystemId", *peerDbSystemId) + status := dbcsInst.Status.DataGuardStatus + + // Use Spec to fill necessary details + spec := dbcsInst.Spec.DataGuard + + // Populate Status from Spec (if present) + status.DbAdminPasswordSecret = spec.DbAdminPasswordSecret + // status.PeerDbSystemId = spec.PeerDbSystemId + status.PrimaryDatabaseId = dbcsInst.Spec.Id + status.ProtectionMode = spec.ProtectionMode + status.TransportType = spec.TransportType + status.PeerRole = spec.PeerRole + status.Shape = spec.Shape + status.SubnetId = spec.SubnetId + terminatingState := string(database.DbSystemLifecycleStateTerminating) + status.LifecycleState = &terminatingState + status.LifecycleDetails = common.String("Dataguard Peer DB system is terminating...") + dbcsInst.Status.State = databasev4.Update + dbcsInst.Status.Message = "Peer DB system is terminating..." + + dbcsInst.Status.Message = "Initiating peer DB system termination" + _ = r.KubeClient.Status().Update(ctx, dbcsInst) + + termSysResp, err := dbClient.TerminateDbSystem(ctx, database.TerminateDbSystemRequest{ + DbSystemId: peerDbSystemId, + }) + if err != nil { + log.Error(err, "Failed to initiate termination of peer DB System") + dbcsInst.Status.Message = "Failed to initiate peer DB system termination" + _ = r.KubeClient.Status().Update(ctx, dbcsInst) + return err + } + + if err := r.waitForWorkRequest(ctx, log, termSysResp.OpcWorkRequestId); err != nil { + log.Error(err, "Peer DB system termination failed or timed out") + dbcsInst.Status.Message = "Peer DB system termination failed or timed out" + _ = r.KubeClient.Status().Update(ctx, dbcsInst) + return err + } + if statusErr := dbcsv4.SetLifecycleState(compartmentId, r.KubeClient, r.dbClient, + dbcsInst, databasev4.Available, r.nwClient, r.wrClient); statusErr != nil { + return statusErr + } + } + +Cleanup: + // Final cleanup — clear DataGuardStatus + dbcsInst.Status.Message = "Data Guard peer deleted successfully" + if statusErr := dbcsv4.SetLifecycleState(compartmentId, r.KubeClient, r.dbClient, + dbcsInst, databasev4.Available, r.nwClient, r.wrClient); statusErr != nil { + return statusErr + } + if err := r.KubeClient.Status().Update(ctx, dbcsInst); err != nil { + log.Error(err, "Failed to update DB status after deleting Data Guard") + return err + } + log.Info("Successfully deleted Data Guard association") + return nil +} +func strVal(s *string) string { + if s == nil { + return "" + } + return *s +} + +func (r *DbcsSystemReconciler) waitForWorkRequest( + ctx context.Context, + log logr.Logger, + workRequestID *string, +) error { + if workRequestID == nil { + return fmt.Errorf("missing WorkRequest ID") + } + + log.Info("Waiting for work request to complete", "workRequestID", *workRequestID) + timeout := time.After(120 * time.Minute) + pollInterval := 30 * time.Second + + for { + select { + case <-timeout: + return fmt.Errorf("timed out waiting for WorkRequest %s", *workRequestID) + default: + resp, err := r.wrClient.GetWorkRequest(ctx, workrequests.GetWorkRequestRequest{ + WorkRequestId: workRequestID, + }) + if err != nil { + log.Error(err, "Failed to get WorkRequest status", "workRequestID", *workRequestID) + return err + } + + status := resp.WorkRequest.Status + log.Info("Polling WorkRequest status", "status", status) + + switch status { + case workrequests.WorkRequestStatusSucceeded: + log.Info("WorkRequest succeeded", "workRequestID", *workRequestID) + return nil + case workrequests.WorkRequestStatusFailed: + return fmt.Errorf("work request %s failed", *workRequestID) + } + + time.Sleep(pollInterval) + } + } +} + func (r *DbcsSystemReconciler) updateSpecsAndStatus(ctx context.Context, dbcsInst *databasev4.DbcsSystem, dbSystemId string) (reconcile.Result, error) { // Retry mechanism for handling resource version conflicts @@ -913,7 +1618,7 @@ func (r *DbcsSystemReconciler) createPluggableDatabase(ctx context.Context, dbcs return "", err } // Change the status to Provisioning - if statusErr := dbcsv1.SetLifecycleState(r.KubeClient, r.dbClient, dbcs, databasev4.Provision, r.nwClient, r.wrClient); statusErr != nil { + if statusErr := dbcsv4.SetLifecycleState(compartmentId, r.KubeClient, r.dbClient, dbcs, databasev4.Provision, r.nwClient, r.wrClient); statusErr != nil { r.Logger.Error(err, "Failed to set DBCS LifeCycle State to Provisioning") return "", statusErr } @@ -961,7 +1666,7 @@ func (r *DbcsSystemReconciler) createPluggableDatabase(ctx context.Context, dbcs if pdbStatus == database.PluggableDatabaseLifecycleStateAvailable { r.Logger.Info("Pluggable database successfully created", "PDBName", pdbConfig.PdbName, "PDBID", *pdbConfig.PluggableDatabaseId) // Change the status to Available - if statusErr := dbcsv1.SetLifecycleState(r.KubeClient, r.dbClient, dbcs, databasev4.Available, r.nwClient, r.wrClient); statusErr != nil { + if statusErr := dbcsv4.SetLifecycleState(compartmentId, r.KubeClient, r.dbClient, dbcs, databasev4.Available, r.nwClient, r.wrClient); statusErr != nil { return "", statusErr } return *response.PluggableDatabase.Id, nil @@ -970,7 +1675,7 @@ func (r *DbcsSystemReconciler) createPluggableDatabase(ctx context.Context, dbcs if pdbStatus == database.PluggableDatabaseLifecycleStateFailed { r.Logger.Error(fmt.Errorf("pluggable database creation failed"), "PDBName", pdbConfig.PdbName, "PDBID", *pdbConfig.PluggableDatabaseId) // Change the status to Failed - if statusErr := dbcsv1.SetLifecycleState(r.KubeClient, r.dbClient, dbcs, databasev4.Failed, r.nwClient, r.wrClient); statusErr != nil { + if statusErr := dbcsv4.SetLifecycleState(compartmentId, r.KubeClient, r.dbClient, dbcs, databasev4.Failed, r.nwClient, r.wrClient); statusErr != nil { return "", statusErr } return "", fmt.Errorf("pluggable database creation failed") @@ -1079,6 +1784,59 @@ func (r *DbcsSystemReconciler) getPluggableDatabaseID(ctx context.Context, pdbCo return pdbID, nil } +func (r *DbcsSystemReconciler) getDataGuardStatusAndUpdate( + ctx context.Context, + dbcsInst *databasev4.DbcsSystem, + primaryDatabaseId string, + dbSystemId string, +) error { + log := r.Logger.WithValues("func", "getDataGuardStatusAndUpdate") + + // compartmentId, err := r.getCompartmentIDByDbSystemID(ctx, dbSystemId) + // if err != nil { + // log.Error(err, "Failed to get compartment ID for DB System") + // return err + // } + + listRequest := database.ListDataGuardAssociationsRequest{ + DatabaseId: common.String(primaryDatabaseId), + } + + listResp, err := r.dbClient.ListDataGuardAssociations(ctx, listRequest) + if err != nil { + log.Error(err, "Failed to list Data Guard associations") + return err + } + + if len(listResp.Items) == 0 { + // log.Info("No Data Guard associations found") + dbcsInst.Status.DataGuardStatus = &databasev4.DataGuardStatus{} // reset to empty + return nil + } + + // Assuming one-to-one association + dg := listResp.Items[0] + + status := databasev4.DataGuardStatus{ + Id: dg.Id, + PeerDatabaseId: dg.PeerDatabaseId, + PeerDbSystemId: dg.PeerDbSystemId, + PeerDbHomeId: dg.PeerDbHomeId, + PeerRole: (*string)(&dg.Role), // Cast enum to string pointer + PrimaryDatabaseId: dg.DatabaseId, + TransportType: (*string)(&dg.TransportType), + ProtectionMode: (*string)(&dg.ProtectionMode), + LifecycleState: (*string)(&dg.LifecycleState), + LifecycleDetails: dg.LifecycleDetails, + PeerDataGuardAssociationId: dg.Id, + } + + dbcsInst.Status.DataGuardStatus = &status + + log.Info("Updated DataGuardStatus in CR", "DataGuardAssociationId", *dg.Id) + return nil +} + func (r *DbcsSystemReconciler) getPluggableDatabaseDetails(ctx context.Context, dbcsInst *databasev4.DbcsSystem, dbSystemId string, databaseIds []string) error { compartmentId, err := r.getCompartmentIDByDbSystemID(ctx, dbSystemId) if err != nil { @@ -1097,12 +1855,6 @@ func (r *DbcsSystemReconciler) getPluggableDatabaseDetails(ctx context.Context, // Create a map to track existing PDBDetailsStatus by PdbName pdbDetailsMap := make(map[string]databasev4.PDBConfigStatus) - // Populate the map with existing PDBDetailsStatus from dbcsInst.Status.PdbDetailsStatus - // for _, existingPdbDetails := range dbcsInst.Status.PdbDetailsStatus { - // for _, existingPdbConfig := range existingPdbDetails.PDBConfigStatus { - // pdbDetailsMap[*existingPdbConfig.PdbName] = existingPdbConfig - // } - // } // Convert databaseIds array to a set for quick lookup databaseIdsSet := make(map[string]struct{}) for _, id := range databaseIds { @@ -1186,6 +1938,283 @@ func (r *DbcsSystemReconciler) doesPluggableDatabaseExist(ctx context.Context, c return false, nil, nil } +// Enable Dataguard +func (r *DbcsSystemReconciler) EnableDataGuard( + ctx context.Context, + compartmentId string, + log logr.Logger, + dbClient database.DatabaseClient, + dbcsSystem *databasev4.DbcsSystem, + kubeClient client.Client, + nwClient core.VirtualNetworkClient, + wrClient workrequests.WorkRequestClient, + databaseID string, + dataGuardConfig *databasev4.DataGuardConfig, +) error { + + // Check if the `Id` field is set + if databaseID == "" { + return fmt.Errorf("DbcsSystem.Spec.DataGuard.peerDBSytemID is not set") + } + + // Extract last successful spec for comparison (optional) + oldSpec, err := dbcsSystem.GetLastSuccessfulSpecWithLog(log) + if err != nil { + log.Error(err, "Failed to get last successful spec for DbcsSystem") + return err + } + + // Compare DataGuard configurations to determine if an update is required + updateFlag := false + + if oldSpec == nil || !reflect.DeepEqual(oldSpec.DataGuard, dataGuardConfig) { + updateFlag = true + } + + databaseAdminPassword, err := r.getSecret(ctx, dbcsSystem.Namespace, *dataGuardConfig.DbAdminPasswordSecret) + if err != nil { + return err + } + // Trim newline character from the password + databaseAdminPassword = strings.TrimSpace(databaseAdminPassword) + if updateFlag { + + exists, err := r.checkExistingDataGuardAssociation(ctx, dbClient, dbcsSystem, databaseID, r.KubeClient, r.nwClient, r.wrClient) + if err != nil { + return err + } + + if exists { + log.Info("Skipping Data Guard creation as it already exists for the database", "DatabaseID", databaseID) + return nil + } + // Change the phase to "Provisioning" + if statusErr := dbcsv4.SetLifecycleState(compartmentId, kubeClient, dbClient, dbcsSystem, databasev4.Update, nwClient, wrClient); statusErr != nil { + return statusErr + } + + request := database.CreateDataGuardAssociationRequest{ + DatabaseId: common.String(databaseID), + CreateDataGuardAssociationDetails: database.CreateDataGuardAssociationWithNewDbSystemDetails{ + // CreationType: common.String("NewDbSystem"), + DatabaseAdminPassword: common.String(databaseAdminPassword), + ProtectionMode: database.CreateDataGuardAssociationDetailsProtectionModeEnum(*dataGuardConfig.ProtectionMode), + TransportType: database.CreateDataGuardAssociationDetailsTransportTypeEnum(*dataGuardConfig.TransportType), + AvailabilityDomain: dataGuardConfig.AvailabilityDomain, + DisplayName: dataGuardConfig.DisplayName, + Hostname: dataGuardConfig.HostName, + Shape: dataGuardConfig.Shape, + SubnetId: dataGuardConfig.SubnetId, + }, + } + + response, err := dbClient.CreateDataGuardAssociation(ctx, request) + // fmt.Printf("Response:\n%+v\n", response) + if err != nil { + r.Logger.Error(err, "DbcsSystem did not reach desired state after DataGuard update") + return err + } + + r.Logger.Info("Data Guard association creation started") + if dbcsSystem.Status.DataGuardStatus == nil { + dbcsSystem.Status.DataGuardStatus = &databasev4.DataGuardStatus{} + } + status := dbcsSystem.Status.DataGuardStatus + + // Use Spec to fill necessary details + spec := dbcsSystem.Spec.DataGuard + + // Populate Status from Spec (if present) + if spec.DbAdminPasswordSecret != nil { + status.DbAdminPasswordSecret = spec.DbAdminPasswordSecret + } + + if spec.PeerDbSystemId != nil { + status.PeerDbSystemId = spec.PeerDbSystemId + } + if dbcsSystem.Spec.Id != nil { + status.PrimaryDatabaseId = dbcsSystem.Spec.Id + } + if spec.ProtectionMode != nil { + status.ProtectionMode = spec.ProtectionMode + } + + if spec.TransportType != nil { + status.TransportType = spec.TransportType + } + + if spec.PeerRole != nil { + status.PeerRole = spec.PeerRole + } + + if spec.Shape != nil { + status.Shape = spec.Shape + } + + if spec.SubnetId != nil { + status.SubnetId = spec.SubnetId + } + + // Mark lifecycle as provisioning + provisioningState := string(database.DbSystemLifecycleStateProvisioning) + status.LifecycleState = &provisioningState + status.LifecycleDetails = common.String("Dataguard Peer DB system is Provisioning...") + + dbcsSystem.Status.State = databasev4.Update + dbcsSystem.Status.Message = "Peer DB system is provisioning..." + + dbcsSystem.Status.Message = "Initiating peer DB system provisioning for Data Guard association" + _ = r.KubeClient.Status().Update(ctx, dbcsSystem) + + // Extract the DataGuardAssociation ID from the response + associationId := *response.DataGuardAssociation.Id + + // Wait for the update to be applied and resource state to become "AVAILABLE" + // _, err = dbcsv4.CheckResourceState(log, dbClient, *dbcsSystem.Spec.Id, "UPDATING", "AVAILABLE") + _, err = dbcsv4.CheckDataGuardAssociationState(log, dbClient, associationId, "UPDATING", "AVAILABLE", databaseID) + if err != nil { + r.Logger.Error(err, "Error checking Data Guard Association state") + } + + r.Logger.Info("Data Guard Association is now in the 'AVAILABLE' state.") + + r.Logger.Info("DataGuard update successful", "dbSystemId", *dbcsSystem.Spec.Id) + } else { + r.Logger.Info("No DataGuard update required; configurations match") + } + + _, err = r.checkExistingDataGuardAssociation(ctx, dbClient, dbcsSystem, databaseID, r.KubeClient, r.nwClient, r.wrClient) + if err != nil { + return err + } + + return nil +} + +// Get Dataguard Details +func (r *DbcsSystemReconciler) checkExistingDataGuardAssociation( + ctx context.Context, + dbClient database.DatabaseClient, + dbcsSystem *databasev4.DbcsSystem, + databaseID string, + kubeClient client.Client, + nwClient core.VirtualNetworkClient, + wrClient workrequests.WorkRequestClient, +) (bool, error) { + + request := database.ListDataGuardAssociationsRequest{ + DatabaseId: common.String(databaseID), + } + + response, err := dbClient.ListDataGuardAssociations(ctx, request) + if err != nil { + return false, fmt.Errorf("failed to list Data Guard associations: %w", err) + } + + if len(response.Items) > 0 { + item := response.Items[0] + + // r.Logger.Info("Data Guard association found for the database", "DatabaseID", databaseID) + + // Ensure DataGuardStatus struct exists + if dbcsSystem.Status.DataGuardStatus == nil { + dbcsSystem.Status.DataGuardStatus = &databasev4.DataGuardStatus{} + } + status := dbcsSystem.Status.DataGuardStatus + + if item.PeerDbSystemId != nil { + status.PeerDbSystemId = item.PeerDbSystemId + } + + if item.DatabaseId != nil { + status.PrimaryDatabaseId = item.DatabaseId + } + + status.DbAdminPasswordSecret = dbcsSystem.Spec.DataGuard.DbAdminPasswordSecret + + if item.IsActiveDataGuardEnabled != nil { + status.IsActiveDataGuardEnabled = *item.IsActiveDataGuardEnabled + } + + if item.PeerRole != "" { + s := string(item.PeerRole) + status.PeerRole = &s + } + + if item.ProtectionMode != "" { + s := string(item.ProtectionMode) + status.ProtectionMode = &s + } + + if item.TransportType != "" { + s := string(item.TransportType) + status.TransportType = &s + } + + if item.LifecycleState != "" { + s := string(item.LifecycleState) + status.LifecycleState = &s + } + + if item.PeerDataGuardAssociationId != nil { + status.PeerDataGuardAssociationId = item.PeerDataGuardAssociationId + } + + if item.LifecycleDetails != nil { + status.LifecycleDetails = item.LifecycleDetails + } else { + status.LifecycleDetails = common.String("Dataguard association enabled for the database") + } + + if item.Id != nil { + status.Id = item.Id + } + + if item.PeerDatabaseId != nil { + status.PeerDatabaseId = item.PeerDatabaseId + } + + if item.LifecycleState == database.DataGuardAssociationSummaryLifecycleStateAvailable { + status.LifecycleDetails = common.String("Data Guard association is available") + + r.Logger.Info("Data Guard association is available", "DatabaseID", databaseID) + + if err := r.KubeClient.Status().Update(ctx, dbcsSystem); err != nil { + return false, fmt.Errorf("failed to update DbcsSystem status: %w", err) + } + + return true, nil + } + + if item.LifecycleState == database.DataGuardAssociationSummaryLifecycleStateFailed { + if item.LifecycleDetails != nil { + status.LifecycleDetails = item.LifecycleDetails + } else { + status.LifecycleDetails = common.String("Data Guard association is failed") + } + + _ = r.KubeClient.Status().Update(ctx, dbcsSystem) + + return false, fmt.Errorf("data guard association failed: %s", *status.LifecycleDetails) + } + + if item.LifecycleState == database.DataGuardAssociationSummaryLifecycleStateProvisioning { + status.LifecycleDetails = common.String("Data Guard association is getting provisioned") + + if err := r.KubeClient.Status().Update(ctx, dbcsSystem); err != nil { + return false, fmt.Errorf("failed to update DbcsSystem status during provisioning: %w", err) + } + + r.Logger.Info("Data Guard association is provisioning", "DatabaseID", databaseID) + return true, nil + } + + } else { + return false, nil + } + return false, nil +} + // Function to create KMS vault func (r *DbcsSystemReconciler) createKMSVault(ctx context.Context, kmsConfig *databasev4.KMSConfig, kmsClient keymanagement.KmsManagementClient, kmsInst *databasev4.KMSDetailsStatus) (*keymanagement.CreateVaultResponse, error) { // Dereference the ConfigurationProvider pointer @@ -1230,7 +2259,7 @@ func (r *DbcsSystemReconciler) createKMSVault(ctx context.Context, kmsConfig *da return nil, err } // Wait until vault becomes active or timeout - timeout := time.After(5 * time.Minute) // Example timeout: 5 minutes + timeout := time.After(15 * time.Minute) // Example timeout: 5 minutes ticker := time.NewTicker(10 * time.Second) defer ticker.Stop() @@ -1327,100 +2356,6 @@ func (r *DbcsSystemReconciler) getSecret(ctx context.Context, namespace, secretN return "", fmt.Errorf("secret %s is empty", secretName) } -// func (r *DbcsSystemReconciler) cloneDbSystem(ctx context.Context, dbcsInst *databasev4.DbcsSystem, provider common.ConfigurationProvider) error { - -// // Initialize OCI clients -// dbClient, err := database.NewDatabaseClientWithConfigurationProvider(provider) -// if err != nil { -// return fmt.Errorf("failed to create OCI database client: %v", err) -// } - -// // Get DB System details -// compartmentId, err := r.getCompartmentIDByDbSystemID(ctx, *dbcsInst.Status.Id) -// if err != nil { -// fmt.Printf("Failed to get compartment ID: %v\n", err) -// return err -// } - -// dbHomeId, err := r.getDbHomeIdByDbSystemID(ctx, compartmentId, *dbcsInst.Status.Id) -// if err != nil { -// fmt.Printf("Failed to get DB Home ID: %v\n", err) -// return err -// } - -// databaseIds, err := r.getDatabaseIDByDbSystemID(ctx, *dbcsInst.Status.Id, compartmentId, dbHomeId) -// if err != nil { -// fmt.Printf("Failed to get database IDs: %v\n", err) -// return err -// } - -// // Use the first database ID for cloning -// if len(databaseIds) == 0 { -// return fmt.Errorf("no databases found in the DB system") -// } - -// // Retrieve details of the database to clone -// sourceDatabaseId := databaseIds[0] -// _, err = dbClient.GetDatabase(ctx, database.GetDatabaseRequest{ -// DatabaseId: common.String(sourceDatabaseId), -// }) -// if err != nil { -// return fmt.Errorf("failed to get source database details: %v", err) -// } - -// // adminPassword, err := dbcsv1.GetAdminPassword(kubeClient, dbcsInstance) -// // if err != nil { -// // log.Fatalf("Error getting admin password: %v", err) -// // } - -// // tdePassword, err := GetTdePassword(kubeClient, dbcsInstance) -// // if err != nil { -// // log.Fatalf("Error getting TDE password: %v", err) -// // } - -// // Define the details for creating the database from the existing DB system -// // createDatabaseDetails := CreateDatabaseBaseWrapper{ -// // CreateDatabaseFromDbSystemDetails: database.CreateDatabaseFromDbSystemDetails{ -// // AdminPassword: common.String(adminPassword), // Replace with actual admin password -// // DbName: common.String(dbcsInst.Spec.DbSystem.DbName), // Use the dbName from DbcsSystemSpec -// // DbDomain: common.String(dbcsInst.Spec.DbSystem.DbDomain), // Use the dbDomain from DbcsSystemSpec -// // DbUniqueName: common.String(dbcsInst.Spec.DbSystem.DbUniqueName), // Use the dbUniqueName from DbcsSystemSpec -// // DbBackupConfig: &database.DbBackupConfig{ -// // AutoBackupEnabled: dbcsInst.Spec.DbSystem.DbBackupConfig.AutoBackupEnabled, -// // RecoveryWindowInDays: dbcsInst.Spec.DbSystem.DbBackupConfig.RecoveryWindowsInDays, -// // }, -// // FreeformTags: dbcsInst.Spec.DbSystem.Tags, -// // DefinedTags: map[string]map[string]interface{}{ -// // "Namespace": { -// // "TagKey": "TagValue", // Replace with actual defined tags if needed -// // }, -// // }, -// // }, -// // } -// // createDatabaseRequest := database.CreateDatabaseRequest{ -// // CreateNewDatabaseDetails: &createDatabaseDetails, -// // } - -// // createDatabaseResponse, err := dbClient.CreateDatabase(ctx, createDatabaseRequest) -// // if err != nil { -// // return fmt.Errorf("failed to create database from DB system: %v", err) -// // } - -// // // Update instance status with the new database ID -// // dbcsInst.Status.DbInfo = append(dbcsInst.Status.DbInfo, databasev4.DbStatus{ -// // Id: createDatabaseResponse.Database.Id, -// // DbName: dbcsInst.Spec.DbSystem.DbName, -// // DbUniqueName: dbcsInst.Spec.DbSystem.DbUniqueName, -// // }) - -// // err = r.KubeClient.Status().Update(ctx, dbcsInst) -// // if err != nil { -// // return fmt.Errorf("failed to update instance status with database ID: %v", err) -// // } - -// return nil -// } - // Convert DbBackupConfigAutoBackupWindowEnum to *string func autoBackupWindowEnumToStringPtr(enum *database.DbBackupConfigAutoBackupWindowEnum) *string { if enum == nil { diff --git a/controllers/database/lrest_controller.go b/controllers/database/lrest_controller.go index 91c883e1..a7b07816 100644 --- a/controllers/database/lrest_controller.go +++ b/controllers/database/lrest_controller.go @@ -39,10 +39,17 @@ package controllers import ( + "bytes" "context" + "crypto/tls" + "crypto/x509" "encoding/json" "errors" "fmt" + "io/ioutil" + "net" + "net/http" + "slices" //"fmt" "strconv" @@ -52,10 +59,15 @@ import ( "github.com/go-logr/logr" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/tools/record" @@ -68,6 +80,9 @@ import ( dbapi "github.com/oracle/oracle-database-operator/apis/database/v4" dbcommons "github.com/oracle/oracle-database-operator/commons/database" + "github.com/oracle/oracle-database-operator/commons/k8s" + . "github.com/oracle/oracle-database-operator/commons/multitenant/lrest" + lrcommons "github.com/oracle/oracle-database-operator/commons/multitenant/lrest" //lrcommons "github.com/oracle/oracle-database-operator/commons/multitenant/lrest" ) @@ -90,6 +105,8 @@ var ( lrestPhaseReady = "Ready" lrestPhaseDelete = "Deleting" lrestPhaseFail = "Failed" + lrestHealthy = "Healthy" + lrestUnHealthy = "Unhealthy" ) const LRESTFinalizer = "database.oracle.com/LRESTfinalizer" @@ -97,7 +114,7 @@ const LRESTFinalizer = "database.oracle.com/LRESTfinalizer" //+kubebuilder:rbac:groups=database.oracle.com,resources=lrests,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=database.oracle.com,resources=lrests/status,verbs=get;update;patch //+kubebuilder:rbac:groups=database.oracle.com,resources=lrests/finalizers,verbs=update -//+kubebuilder:rbac:groups="",resources=pods;pods/log;pods/exec;services;configmaps;events;replicasets,verbs=create;delete;get;list;patch;update;watch +//+kubebuilder:rbac:groups="",resources=pods;pods/log;services;configmaps;events;replicasets,verbs=create;delete;get;list;patch;update;watch //+kubebuilder:rbac:groups=core,resources=pods;secrets;services;configmaps;namespaces,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=apps,resources=replicasets,verbs=get;list;watch;create;update;patch;delete @@ -158,6 +175,13 @@ func (r *LRESTReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl // If post-creation, LREST spec is changed, check and take appropriate action if (lrest.Status.Phase == lrestPhaseReady) && lrest.Status.Status { r.evaluateSpecChange(ctx, req, lrest) + r.lrestHealthCheck(ctx, req, lrest) + } + + // Auto discover functionality looks for pdb with no crd + if lrest.Spec.PdbAutoDiscover == true && lrest.Status.Status == true { + log.Info("PDB auto discover turned on") + r.PdbAutoDiscover(ctx, req, lrest) } if !lrest.Status.Status { @@ -179,10 +203,10 @@ func (r *LRESTReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl log.Info("Reconcile queued") return requeueY, nil } - lrest.Status.Phase = lrestPhaseValPod + lrest.Status.Phase = lrestPhaseService case lrestPhaseValPod: // Validate LREST PODs - err = r.validateLRESTPods(ctx, req, lrest) + err = r.validateLRESTPods2(ctx, req, lrest) if err != nil { if lrest.Status.Phase == lrestPhaseFail { return requeueN, nil @@ -190,7 +214,7 @@ func (r *LRESTReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl log.Info("Reconcile queued") return requeueY, nil } - lrest.Status.Phase = lrestPhaseService + lrest.Status.Phase = lrestPhaseReady case lrestPhaseService: // Create LREST Service err = r.createLRESTSVC(ctx, req, lrest) @@ -199,7 +223,7 @@ func (r *LRESTReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl return requeueY, nil } //lrest.Status.Phase = lrestPhaseSecrets - lrest.Status.Phase = lrestPhaseReady + lrest.Status.Phase = lrestPhaseValPod case lrestPhaseSecrets: // Delete LREST Secrets //r.deleteSecrets(ctx, req, lrest) @@ -208,7 +232,7 @@ func (r *LRESTReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl case lrestPhaseReady: lrest.Status.Status = true r.Status().Update(ctx, lrest) - return requeueN, nil + return requeueY, nil default: lrest.Status.Phase = lrestPhaseInit log.Info("DEFAULT:", "Name", lrest.Name, "Phase", phase, "Status", strconv.FormatBool(lrest.Status.Status)) @@ -217,11 +241,12 @@ func (r *LRESTReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl if err := r.Status().Update(ctx, lrest); err != nil { log.Error(err, "Failed to update status for :"+lrest.Name, "err", err.Error()) } + return requeueY, nil } log.Info("Reconcile completed") - return requeueN, nil + return requeueY, nil } /* @@ -262,6 +287,40 @@ func (r *LRESTReconciler) createLRESTInstances(ctx context.Context, req ctrl.Req - Validate LREST Pod. Check if there are any errors /*********************************************** */ +func (r *LRESTReconciler) validateLRESTPods2(ctx context.Context, req ctrl.Request, lrest *dbapi.LREST) error { + log := r.Log.WithValues("validateLRESTPod2", req.NamespacedName) + log.Info("Validating Pod creation for :" + lrest.Name) + + /* + _, err := r.SelectFromVpdbs(ctx, req, lrest) + if err != nil { + log.Info("LREST is not ready ", "Namespace", req.Namespace) + lrest.Status.Msg = "Waiting for LREST Pod(s) to be read" + return errors.New("Waiting for LREST pods to be ready") + } + */ + + /* Using a smarter and ligther method to validate the pod + No need to read the whole v$pdbs*/ + RestPort := lrest.Spec.LRESTPort + RestName := lrest.Name + "-lrest" + RestNmsp := lrest.Namespace + Ip := RestName + "." + RestNmsp + ":" + strconv.Itoa(RestPort) + + url := "https://" + Ip + "/database/pdbs/PDB$SEED/status/" + _, err := NewCallAPIAllPdbs(r, ctx, req, lrest, url, nil, "GET") + if err != nil { + log.Info("LREST is not ready ", "Namespace", req.Namespace) + lrest.Status.Msg = "Waiting for LREST Pod(s) to be read" + return errors.New("Waiting for LREST pods to be ready") + } + + lrest.Status.Msg = "" + return nil + +} + +/* Un-used function func (r *LRESTReconciler) validateLRESTPods(ctx context.Context, req ctrl.Request, lrest *dbapi.LREST) error { log := r.Log.WithValues("validateLRESTPod", req.NamespacedName) @@ -318,6 +377,7 @@ func (r *LRESTReconciler) validateLRESTPods(ctx context.Context, req ctrl.Reques lrest.Status.Msg = "" return nil } +*/ /* *********************** @@ -328,97 +388,16 @@ func (r *LRESTReconciler) validateLRESTPods(ctx context.Context, req ctrl.Reques func (r *LRESTReconciler) createPodSpec(lrest *dbapi.LREST) corev1.PodSpec { podSpec := corev1.PodSpec{ - Volumes: []corev1.Volume{{ - Name: "secrets", - VolumeSource: corev1.VolumeSource{ - Projected: &corev1.ProjectedVolumeSource{ - DefaultMode: func() *int32 { i := int32(0666); return &i }(), - Sources: []corev1.VolumeProjection{ - { - Secret: &corev1.SecretProjection{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: lrest.Spec.LRESTPubKey.Secret.SecretName, - }, - Items: []corev1.KeyToPath{ - { - Key: lrest.Spec.LRESTPubKey.Secret.Key, - Path: lrest.Spec.LRESTPubKey.Secret.Key, - }, - }, - }, - }, - { - Secret: &corev1.SecretProjection{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: lrest.Spec.LRESTPriKey.Secret.SecretName, - }, - Items: []corev1.KeyToPath{ - { - Key: lrest.Spec.LRESTPriKey.Secret.Key, - Path: lrest.Spec.LRESTPriKey.Secret.Key, - }, - }, - }, - }, - - /***/ - { - Secret: &corev1.SecretProjection{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: lrest.Spec.LRESTTlsKey.Secret.SecretName, - }, - Items: []corev1.KeyToPath{ - { - Key: lrest.Spec.LRESTTlsKey.Secret.Key, - Path: lrest.Spec.LRESTTlsKey.Secret.Key, - }, - }, - }, - }, - { - Secret: &corev1.SecretProjection{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: lrest.Spec.LRESTTlsCrt.Secret.SecretName, - }, - Items: []corev1.KeyToPath{ - { - Key: lrest.Spec.LRESTTlsCrt.Secret.Key, - Path: lrest.Spec.LRESTTlsCrt.Secret.Key, - }, - }, - }, - }, - }, - }, - }, - }}, + Volumes: PodVolumes(lrest), /* Volumes */ SecurityContext: &corev1.PodSecurityContext{ - RunAsNonRoot: &[]bool{true}[0], - FSGroup: &[]int64{54321}[0], - SeccompProfile: &corev1.SeccompProfile{ - Type: corev1.SeccompProfileTypeRuntimeDefault, - }, + RunAsNonRoot: k8s.BoolPointer(true), + RunAsUser: k8s.Int64Pointer(dbcommons.ORACLE_UID), + RunAsGroup: k8s.Int64Pointer(dbcommons.ORACLE_GUID), + FSGroup: k8s.Int64Pointer(dbcommons.ORACLE_GUID), + //SeccompProfile: &corev1.SeccompProfile{ + // Type: corev1.SeccompProfileTypeRuntimeDefault, + //}, }, - /*InitContainers: []corev1.Container{{ - Image: lrest.Spec.LRESTImage, - Name: lrest.Name + "-init", - ImagePullPolicy: corev1.PullIfNotPresent, - SecurityContext: securityContextDefineLrest(), - Command: []string{"echo test > /opt/oracle/lrest/certificates/tests"}, - Env: func() []corev1.EnvVar { - return []corev1.EnvVar{ - { - Name: "ORACLE_HOST", - Value: lrest.Spec.DBTnsurl, - }} - }(), - VolumeMounts: []corev1.VolumeMount{ - { - MountPath: "/opt/oracle/lrest/certificates", - Name: "secrets", - ReadOnly: false, - }}, - }},*/ Containers: []corev1.Container{{ Image: lrest.Spec.LRESTImage, Name: lrest.Name + "-lrest", @@ -431,90 +410,7 @@ func (r *LRESTReconciler) createPodSpec(lrest *dbapi.LREST) corev1.PodSpec { ReadOnly: true, }, }, - Env: func() []corev1.EnvVar { - return []corev1.EnvVar{ - { - Name: "ORACLE_HOST", - Value: lrest.Spec.DBServer, - }, - { - Name: "DBTNSURL", - Value: lrest.Spec.DBTnsurl, - }, - { - Name: "TLSCRT", - Value: lrest.Spec.LRESTTlsCrt.Secret.Key, - }, - { - Name: "TLSKEY", - Value: lrest.Spec.LRESTTlsKey.Secret.Key, - }, - { - Name: "PUBKEY", - Value: lrest.Spec.LRESTPubKey.Secret.Key, - }, - { - Name: "PRVKEY", - Value: lrest.Spec.LRESTPriKey.Secret.Key, - }, - { - Name: "ORACLE_PORT", - Value: strconv.Itoa(lrest.Spec.DBPort), - }, - { - Name: "LREST_PORT", - Value: strconv.Itoa(lrest.Spec.LRESTPort), - }, - { - Name: "ORACLE_SERVICE", - Value: lrest.Spec.ServiceName, - }, - { - Name: "R1", - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: lrest.Spec.LRESTAdminUser.Secret.SecretName, - }, - Key: lrest.Spec.LRESTAdminUser.Secret.Key, - }, - }, - }, - { - Name: "R2", - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: lrest.Spec.LRESTAdminPwd.Secret.SecretName, - }, - Key: lrest.Spec.LRESTAdminPwd.Secret.Key, - }, - }, - }, - { - Name: "R3", - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: lrest.Spec.WebLrestServerUser.Secret.SecretName, - }, - Key: lrest.Spec.WebLrestServerUser.Secret.Key, - }, - }, - }, - { - Name: "R4", - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: lrest.Spec.WebLrestServerPwd.Secret.SecretName, - }, - Key: lrest.Spec.WebLrestServerPwd.Secret.Key, - }, - }, - }, - } - }(), + Env: ContainerEnv(lrest), /* Environment Variables */ }}, NodeSelector: func() map[string]string { @@ -526,6 +422,8 @@ func (r *LRESTReconciler) createPodSpec(lrest *dbapi.LREST) corev1.PodSpec { } return ns }(), + + ServiceAccountName: lrest.Spec.SrvAccountName, } if len(lrest.Spec.LRESTImagePullSecret) > 0 { @@ -594,7 +492,7 @@ func (r *LRESTReconciler) createReplicaSetSpec(lrest *dbapi.LREST) *appsv1.Repli - Evaluate change in Spec post creation and instantiation /******************************************************* */ -func (r *LRESTReconciler) deleteReplicaSet(ctx context.Context, req ctrl.Request, lrest *dbapi.LREST) error { +func (r *LRESTReconciler) deleteReplicaSet(req ctrl.Request, lrest *dbapi.LREST) error { log := r.Log.WithValues("deleteReplicaSet", req.NamespacedName) k_client, err := kubernetes.NewForConfig(r.Config) @@ -658,7 +556,7 @@ func (r *LRESTReconciler) evaluateSpecChange(ctx context.Context, req ctrl.Reque if lrestSpecChange { // Delete existing ReplicaSet - err = r.deleteReplicaSet(ctx, req, lrest) + err = r.deleteReplicaSet(req, lrest) if err != nil { return err } @@ -708,7 +606,7 @@ func (r *LRESTReconciler) createLRESTSVC(ctx context.Context, req ctrl.Request, foundSvc := &corev1.Service{} err := r.Get(context.TODO(), types.NamespacedName{Name: lrest.Name + "-lrest", Namespace: lrest.Namespace}, foundSvc) if err != nil && apierrors.IsNotFound(err) { - svc := r.createSvcSpec(lrest) + svc := r.createCoreService(lrest) log.Info("Creating a new Cluster Service for: "+lrest.Name, "Svc.Namespace", svc.Namespace, "Service.Name", svc.Name) err := r.Create(ctx, svc) @@ -731,7 +629,34 @@ func (r *LRESTReconciler) createLRESTSVC(ctx context.Context, req ctrl.Request, - Create Service spec /*********************** */ -func (r *LRESTReconciler) createSvcSpec(lrest *dbapi.LREST) *corev1.Service { + +func (r *LRESTReconciler) createCoreService(lrest *dbapi.LREST) *corev1.Service { + var portLrest int32 + fmt.Sscan(fmt.Sprintf("%d", lrest.Spec.LRESTPort), &portLrest) // 64->32 + svcspecIp := corev1.ServiceSpec{} + svcspecIp.Selector = map[string]string{"name": lrest.Name + "-lrest"} + + if lrest.Spec.ClusterIP == false { + svcspecIp.ClusterIP = corev1.ClusterIPNone + } else { + svcspecIp.Ports = []v1.ServicePort{ + { + Protocol: v1.ProtocolTCP, + Port: 443, + TargetPort: intstr.FromInt(443), + Name: "https", + }, + { + Protocol: v1.ProtocolTCP, + Port: portLrest, + TargetPort: intstr.FromInt(lrest.Spec.LRESTPort), + Name: "lrest-port", + }, + } + if lrest.Spec.LoadBalancer == true { + svcspecIp.Type = v1.ServiceTypeLoadBalancer + } + } svc := &corev1.Service{ TypeMeta: metav1.TypeMeta{ @@ -741,12 +666,7 @@ func (r *LRESTReconciler) createSvcSpec(lrest *dbapi.LREST) *corev1.Service { Name: lrest.Name + "-lrest", Namespace: lrest.Namespace, }, - Spec: corev1.ServiceSpec{ - Selector: map[string]string{ - "name": lrest.Name + "-lrest", - }, - ClusterIP: corev1.ClusterIPNone, - }, + Spec: svcspecIp, } // Set LREST instance as the owner and controller ctrl.SetControllerReference(lrest, svc, r.Scheme) @@ -772,7 +692,7 @@ func (r *LRESTReconciler) manageLRESTDeletion(ctx context.Context, req ctrl.Requ } } else { - log.Info("lrest set to be deleted") + log.Info("lrest mark to be delited") lrest.Status.Phase = lrestPhaseDelete lrest.Status.Status = true r.Status().Update(ctx, lrest) @@ -790,7 +710,7 @@ func (r *LRESTReconciler) manageLRESTDeletion(ctx context.Context, req ctrl.Requ } } - err := r.deleteLRESTInstance(ctx, req, lrest) + err := r.deleteLRESTInstance(req, lrest) if err != nil { log.Info("Could not delete LREST Resource", "LREST Name", lrest.Spec.LRESTName, "err", err.Error()) return err @@ -806,7 +726,7 @@ func (r *LRESTReconciler) manageLRESTDeletion(ctx context.Context, req ctrl.Requ /*********************************************** */ -func (r *LRESTReconciler) deleteLRESTInstance(ctx context.Context, req ctrl.Request, lrest *dbapi.LREST) error { +func (r *LRESTReconciler) deleteLRESTInstance(req ctrl.Request, lrest *dbapi.LREST) error { log := r.Log.WithValues("deleteLRESTInstance", req.NamespacedName) @@ -907,23 +827,15 @@ func (r *LRESTReconciler) checkSecret(ctx context.Context, req ctrl.Request, lre /* ************************************************ - Delete Secrets - /*********************************************** -*/ + - No longer used +************************************************/ +/* func (r *LRESTReconciler) deleteSecrets(ctx context.Context, req ctrl.Request, lrest *dbapi.LREST) { log := r.Log.WithValues("deleteSecrets", req.NamespacedName) log.Info("Deleting LREST secrets") secret := &corev1.Secret{} - /* - err := r.Get(ctx, types.NamespacedName{Name: lrest.Spec.SysAdminPwd.Secret.SecretName, Namespace: lrest.Namespace}, secret) - if err == nil { - err := r.Delete(ctx, secret) - if err == nil { - log.Info("Deleted the secret : " + lrest.Spec.SysAdminPwd.Secret.SecretName) - } - } - */ err := r.Get(ctx, types.NamespacedName{Name: lrest.Spec.LRESTAdminUser.Secret.SecretName, Namespace: lrest.Namespace}, secret) if err == nil { @@ -940,15 +852,6 @@ func (r *LRESTReconciler) deleteSecrets(ctx context.Context, req ctrl.Request, l log.Info("Deleted the secret : " + lrest.Spec.LRESTAdminPwd.Secret.SecretName) } } - /* - err = r.Get(ctx, types.NamespacedName{Name: lrest.Spec.LRESTPwd.Secret.SecretName, Namespace: lrest.Namespace}, secret) - if err == nil { - err := r.Delete(ctx, secret) - if err == nil { - log.Info("Deleted the secret : " + lrest.Spec.LRESTPwd.Secret.SecretName) - } - } - */ err = r.Get(ctx, types.NamespacedName{Name: lrest.Spec.WebLrestServerUser.Secret.SecretName, Namespace: lrest.Namespace}, secret) if err == nil { @@ -966,6 +869,7 @@ func (r *LRESTReconciler) deleteSecrets(ctx context.Context, req ctrl.Request, l } } } +*/ /* ************************************************************* @@ -993,8 +897,9 @@ func (r *LRESTReconciler) SetupWithManager(mgr ctrl.Manager) error { func securityContextDefineLrest() *corev1.SecurityContext { return &corev1.SecurityContext{ - RunAsNonRoot: &[]bool{true}[0], - RunAsUser: &[]int64{54321}[0], + RunAsNonRoot: k8s.BoolPointer(true), + RunAsUser: k8s.Int64Pointer(dbcommons.ORACLE_UID), + RunAsGroup: k8s.Int64Pointer(dbcommons.ORACLE_GUID), AllowPrivilegeEscalation: &[]bool{false}[0], Capabilities: &corev1.Capabilities{ Drop: []corev1.Capability{ @@ -1004,6 +909,166 @@ func securityContextDefineLrest() *corev1.SecurityContext { } } +func ContainerEnv(lrest *dbapi.LREST) []corev1.EnvVar { + EnvVar := []corev1.EnvVar{ + { + Name: "ORACLE_HOST", + Value: lrest.Spec.DBServer, + }, + { + Name: "DBTNSURL", + Value: lrest.Spec.DBTnsurl, + }, + { + Name: "TLSCRT", + Value: lrest.Spec.LRESTTlsCrt.Secret.Key, + }, + { + Name: "TLSKEY", + Value: lrest.Spec.LRESTTlsKey.Secret.Key, + }, + { + Name: "PUBKEY", + Value: lrest.Spec.LRESTPubKey.Secret.Key, + }, + { + Name: "PRVKEY", + Value: lrest.Spec.LRESTPriKey.Secret.Key, + }, + { + Name: "ORACLE_PORT", + Value: strconv.Itoa(lrest.Spec.DBPort), + }, + { + Name: "LREST_PORT", + Value: strconv.Itoa(lrest.Spec.LRESTPort), + }, + { + Name: "ORACLE_SERVICE", + Value: lrest.Spec.ServiceName, + }, + { + Name: "TRACE_LEVEL_CLIENT", + Value: strconv.Itoa(lrest.Spec.SqlNetTrace), + }, + { + Name: "R1", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: lrest.Spec.LRESTAdminUser.Secret.SecretName, + }, + Key: lrest.Spec.LRESTAdminUser.Secret.Key, + }, + }, + }, + { + Name: "R2", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: lrest.Spec.LRESTAdminPwd.Secret.SecretName, + }, + Key: lrest.Spec.LRESTAdminPwd.Secret.Key, + }, + }, + }, + { + Name: "R3", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: lrest.Spec.WebLrestServerUser.Secret.SecretName, + }, + Key: lrest.Spec.WebLrestServerUser.Secret.Key, + }, + }, + }, + { + Name: "R4", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: lrest.Spec.WebLrestServerPwd.Secret.SecretName, + }, + Key: lrest.Spec.WebLrestServerPwd.Secret.Key, + }, + }, + }, + } + + return EnvVar +} + +func PodVolumes(lrest *dbapi.LREST) []corev1.Volume { + + Volumes := []corev1.Volume{{ + Name: "secrets", + VolumeSource: corev1.VolumeSource{ + Projected: &corev1.ProjectedVolumeSource{ + DefaultMode: func() *int32 { i := int32(0666); return &i }(), + Sources: []corev1.VolumeProjection{ + { + Secret: &corev1.SecretProjection{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: lrest.Spec.LRESTPubKey.Secret.SecretName, + }, + Items: []corev1.KeyToPath{ + { + Key: lrest.Spec.LRESTPubKey.Secret.Key, + Path: lrest.Spec.LRESTPubKey.Secret.Key, + }, + }, + }, + }, + { + Secret: &corev1.SecretProjection{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: lrest.Spec.LRESTPriKey.Secret.SecretName, + }, + Items: []corev1.KeyToPath{ + { + Key: lrest.Spec.LRESTPriKey.Secret.Key, + Path: lrest.Spec.LRESTPriKey.Secret.Key, + }, + }, + }, + }, + + { + Secret: &corev1.SecretProjection{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: lrest.Spec.LRESTTlsKey.Secret.SecretName, + }, + Items: []corev1.KeyToPath{ + { + Key: lrest.Spec.LRESTTlsKey.Secret.Key, + Path: lrest.Spec.LRESTTlsKey.Secret.Key, + }, + }, + }, + }, + { + Secret: &corev1.SecretProjection{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: lrest.Spec.LRESTTlsCrt.Secret.SecretName, + }, + Items: []corev1.KeyToPath{ + { + Key: lrest.Spec.LRESTTlsCrt.Secret.Key, + Path: lrest.Spec.LRESTTlsCrt.Secret.Key, + }, + }, + }, + }, + }, + }, + }, + }} + + return Volumes +} + func (r *LRESTReconciler) DeletePDBS(ctx context.Context, req ctrl.Request, lrest *dbapi.LREST) error { log := r.Log.WithValues("DeletePDBS", req.NamespacedName) @@ -1046,7 +1111,7 @@ func (r *LRESTReconciler) DeletePDBS(ctx context.Context, req ctrl.Request, lres } pdbitem.Status.SqlCode = int(objmap["sqlcode"].(float64)) - log.Info("pdb closure.......:", "sqlcode", pdbitem.Status.SqlCode) + log.Info("pdb closuer.......:", "sqlcode", pdbitem.Status.SqlCode) if errapi != nil { log.Error(err, "callAPI cannot close pdb "+pdbitem.Spec.LRPDBName, "err", err.Error()) @@ -1061,7 +1126,7 @@ func (r *LRESTReconciler) DeletePDBS(ctx context.Context, req ctrl.Request, lres values = map[string]string{ "action": "INCLUDING", } - respData, errapi := NewCallLAPI(r, ctx, req, &pdbitem, url, values, "DELETE") + respData, errapi := NewCallAPISQL(r, ctx, req, &pdbitem, url, values, "DELETE") if err := json.Unmarshal([]byte(respData), &objmap); err != nil { log.Error(err, "failed to get respData from callAPI", "err", err.Error()) @@ -1076,7 +1141,6 @@ func (r *LRESTReconciler) DeletePDBS(ctx context.Context, req ctrl.Request, lres return err } r.Recorder.Eventf(lrest, corev1.EventTypeNormal, "drop pdb", "pdbname=%s", pdbitem.Spec.LRPDBName) - /* remove finalizer */ if controllerutil.ContainsFinalizer(&pdbitem, LRPDBFinalizer) { @@ -1100,6 +1164,524 @@ func (r *LRESTReconciler) DeletePDBS(ctx context.Context, req ctrl.Request, lres } } - /* ================================================ */ return nil } + +func SearchElementInDbList(element string, TheList []string) bool { + var inthelist bool + inthelist = false + for idx := range TheList { + if strings.ToLower(element) == strings.ToLower(TheList[idx]) { + inthelist = true + return inthelist + } + } + return inthelist +} + +func (r *LRESTReconciler) SelectFromVpdbs(ctx context.Context, req ctrl.Request, lrest *dbapi.LREST) ([]interface{}, error) { + log := r.Log.WithValues("SelectFromVpdbs", req.NamespacedName) + url := "https://" + lrest.Name + "-lrest." + lrest.Namespace + ":" + strconv.Itoa(lrest.Spec.LRESTPort) + "/database/pdbs/" + + output, err := NewCallAPIAllPdbs(r, ctx, req, lrest, url, nil, "GET") + if err != nil { + log.Info("NewCallAPIAllPdbs Error") + } + + data := []byte(` {"PDBS":` + output + `}`) + var idata interface{} + err = json.Unmarshal(data, &idata) + if err != nil { + log.Info("error json.Unmarshal") + return nil, err + } + + mdata := idata.(map[string]interface{}) + ndata := mdata["PDBS"].([]interface{}) + + return ndata, nil + +} + +func (r *LRESTReconciler) LrpdbCreation(ctx context.Context, req ctrl.Request, lrest *dbapi.LREST, dbinfo []interface{}, idx int) error { + log := r.Log.WithValues("LrpdbCreation", req.NamespacedName) + log.Info("Creating LRPDB for :" + dbinfo[idx].(map[string]interface{})["name"].(string)) + + cln, err := dynamic.NewForConfig(r.Config) + if err != nil { + log.Error(err, "Kubernetes Config Error") + return err + } + + TlsCrtecobj := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "secret": map[string]interface{}{ + "key": lrest.Spec.LRESTTlsCrt.Secret.Key, + "secretName": lrest.Spec.LRESTTlsCrt.Secret.SecretName, + }, + }, + } + + TlsCatecobj := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "secret": map[string]interface{}{ + "key": lrest.Spec.LRESTTlsCat.Secret.Key, + "secretName": lrest.Spec.LRESTTlsCat.Secret.SecretName, + }, + }, + } + + TlsKeyecobj := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "secret": map[string]interface{}{ + "key": lrest.Spec.LRESTTlsKey.Secret.Key, + "secretName": lrest.Spec.LRESTTlsKey.Secret.SecretName, + }, + }, + } + + WebUseObj := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "secret": map[string]interface{}{ + "key": lrest.Spec.WebLrestServerUser.Secret.Key, + "secretName": lrest.Spec.WebLrestServerUser.Secret.SecretName, + }, + }, + } + + WebPasObj := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "secret": map[string]interface{}{ + "key": lrest.Spec.WebLrestServerPwd.Secret.Key, + "secretName": lrest.Spec.WebLrestServerPwd.Secret.SecretName, + }, + }, + } + + CdbPrvKeyObj := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "secret": map[string]interface{}{ + "key": lrest.Spec.LRESTPriKey.Secret.Key, + "secretName": lrest.Spec.LRESTPriKey.Secret.SecretName, + }, + }, + } + + TotSzStr := fmt.Sprintf("%f", dbinfo[idx].(map[string]interface{})["total_size"].(float64)) + + log.Info("secretName:" + lrest.Spec.WebLrestServerUser.Secret.SecretName) + log.Info("secretName:" + lrest.Spec.WebLrestServerPwd.Secret.SecretName) + log.Info("DEBUGSIZE::" + TotSzStr) + + var NamesSpaceAutoDiscover string + if lrest.Spec.NamesSpaceAutoDiscover != "" { + NamesSpaceAutoDiscover = lrest.Spec.NamesSpaceAutoDiscover + } else { + NamesSpaceAutoDiscover = lrest.Namespace + } + log.Info("NamesSpaceAutoDiscover := " + NamesSpaceAutoDiscover) + + Resname := "atd-" + strings.ToLower(dbinfo[idx].(map[string]interface{})["name"].(string)) + Resname = strings.ReplaceAll(Resname, "_", "-") + + obj := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "database.oracle.com/v4", + "kind": "LRPDB", + "metadata": map[string]interface{}{ + "name": Resname, + "namespace": NamesSpaceAutoDiscover, + }, + "spec": map[string]interface{}{ + "pdbName": dbinfo[idx].(map[string]interface{})["name"].(string), + "cdbNamespace": lrest.Namespace, + "cdbResName": lrest.Name, + "cdbName": lrest.Spec.LRESTName, + "totalSize": TotSzStr, + "lrpdbTlsCrt": TlsCrtecobj, + "lrpdbTlsCat": TlsCatecobj, + "lrpdbTlsKey": TlsKeyecobj, + "cdbPrvKey": CdbPrvKeyObj, + "webServerUser": WebUseObj, + "webServerPwd": WebPasObj, + "adminName": WebUseObj, /* Place holder */ + "adminPwd": WebUseObj, /* Place holder */ + "adminpdbUser": WebUseObj, /* Place holder */ + "adminpdbPass": WebUseObj, /* Place holder */ + "fileNameConversions": "NONE", + "imperativeLrpdbDeletion": true, + "reststate": PDBAUT, + "pdbState": "RESET", + }, + }, + } + + obj.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "database.oracle.com", + Version: "v4", + Kind: "LRPDB", + }) + result, err := cln.Resource(schema.GroupVersionResource{ + Group: "database.oracle.com", + Version: "v4", + Resource: "lrpdbs", + }).Namespace(NamesSpaceAutoDiscover).Create(context.TODO(), obj, metav1.CreateOptions{}) + + if err != nil { + log.Error(err, "Error creating custom resource: ") + } else { + log.Info("Custom resource created successfully ") + fmt.Printf("obj:%s\n", result) + r.Recorder.Eventf(lrest, corev1.EventTypeNormal, "LrpdbCreation", "created lrpdb:%s", Resname) + + } + + var lrpdb dbapi.LRPDB + + err = r.Get(context.Background(), client.ObjectKey{ + Namespace: NamesSpaceAutoDiscover, + Name: Resname, + }, &lrpdb) + + lrpdb.Status.PDBBitMask = Bis(lrpdb.Status.PDBBitMask, PDBAUT) + lrpdb.Status.PDBBitMaskStr = Bitmaskprint(lrpdb.Status.PDBBitMask) + + err = r.Status().Update(context.Background(), &lrpdb) + + return nil +} + +func (r *LRESTReconciler) PdbAutoDiscover(ctx context.Context, req ctrl.Request, lrest *dbapi.LREST) error { + log := r.Log.WithValues("PdbAutoDiscover", req.NamespacedName) + + // SELECT * FROM V$PDBS + ndata, err := r.SelectFromVpdbs(ctx, req, lrest) + + // LIST OF ALL LRPDB + log.Info("Get list of lrpdb resources\n") + var pdbNameList []string /* the list of pdb name */ + lrpdbList := &dbapi.LRPDBList{} + listOpts := []client.ListOption{} + err = r.List(ctx, lrpdbList, listOpts...) + if err != nil { + log.Info("Failed to get the list of pdbs") + return err + } + + for _, pdbitem := range lrpdbList.Items { + if (pdbitem.Spec.CDBName == lrest.Spec.LRESTName) && Bit(pdbitem.Status.PDBBitMask, PDBCRT) == true { + log.Info("CRD(lrpdb): " + pdbitem.Name + ":" + pdbitem.Spec.LRPDBName) + pdbNameList = slices.Insert(pdbNameList, len(pdbNameList), pdbitem.Spec.LRPDBName) + } + } + + lrpdbList01 := &dbapi.LRPDBList{} + for idx := range ndata { + name := ndata[idx].(map[string]interface{})["name"].(string) + log.Info("PDB:" + name) + if name != "PDB$SEED" { + InTheList := SearchElementInDbList(name, pdbNameList) + if InTheList == false { + log.Info("Orphan PDB:[" + name + "]") + /*** Final check ***/ + listOpts01 := []client.ListOption{client.MatchingFields{"spec.pdbName": strings.ToLower(name)}} + err = r.List(ctx, lrpdbList01, listOpts01...) + if err != nil { + log.Info("Failed to get the list02 of pdbs") + + return err + } + if len(lrpdbList01.Items) != 0 { + log.Info("Db gets crd in the meantime.....") + return nil + } + + err := r.LrpdbCreation(ctx, req, lrest, ndata, idx) + if err != nil { + log.Error(err, "error calling r.LrpdbCreation") + } + } + } + } + + return nil + +} + +func NewCallAPIAllPdbs(intr interface{}, ctx context.Context, req ctrl.Request, lrest *dbapi.LREST, url string, payload map[string]string, action string) (string, error) { + var c client.Client + var r logr.Logger + var e record.EventRecorder + var err error + + recpdb, ok1 := intr.(*LRPDBReconciler) + if ok1 { + fmt.Printf("func NewCallLApi ((*PDBReconciler),......)\n") + c = recpdb.Client + e = recpdb.Recorder + r = recpdb.Log + } + + reccdb, ok2 := intr.(*LRESTReconciler) + if ok2 { + fmt.Printf("func NewCallLApi ((*CDBReconciler),......)\n") + c = reccdb.Client + e = reccdb.Recorder + r = reccdb.Log + } + + log := r.WithValues("NewCallAPIAllPdbs", req.NamespacedName) + + secret := &corev1.Secret{} + + err = c.Get(ctx, types.NamespacedName{Name: lrest.Spec.LRESTTlsKey.Secret.SecretName, Namespace: lrest.Namespace}, secret) + + if err != nil { + if apierrors.IsNotFound(err) { + log.Info("Secret not found:" + lrest.Spec.LRESTTlsKey.Secret.SecretName) + return "", err + } + log.Error(err, "Unable to get the secret.") + return "", err + } + rsaKeyPEM := secret.Data[lrest.Spec.LRESTTlsKey.Secret.Key] + + err = c.Get(ctx, types.NamespacedName{Name: lrest.Spec.LRESTTlsCrt.Secret.SecretName, Namespace: lrest.Namespace}, secret) + + if err != nil { + if apierrors.IsNotFound(err) { + log.Info("Secret not found:" + lrest.Spec.LRESTTlsCrt.Secret.SecretName) + return "", err + } + log.Error(err, "Unable to get the secret.") + return "", err + } + + rsaCertPEM := secret.Data[lrest.Spec.LRESTTlsCrt.Secret.Key] + + err = c.Get(ctx, types.NamespacedName{Name: lrest.Spec.LRESTTlsCat.Secret.SecretName, Namespace: lrest.Namespace}, secret) + + if err != nil { + if apierrors.IsNotFound(err) { + log.Info("Secret not found:" + lrest.Spec.LRESTTlsCat.Secret.SecretName) + return "", err + } + log.Error(err, "Unable to get the secret.") + return "", err + } + + caCert := secret.Data[lrest.Spec.LRESTTlsCat.Secret.Key] + + certificate, err := tls.X509KeyPair([]byte(rsaCertPEM), []byte(rsaKeyPEM)) + if err != nil { + lrest.Status.Msg = "Error tls.X509KeyPair" + return "", err + } + + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(caCert) + /* + tlsConf := &tls.Config{Certificates: []tls.Certificate{certificate}, + RootCAs: caCertPool} + */ + tlsConf := &tls.Config{Certificates: []tls.Certificate{certificate}, + RootCAs: caCertPool, + //MinVersion: tls.VersionTLS12, + CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256}, + PreferServerCipherSuites: true, + CipherSuites: []uint16{ + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + tls.TLS_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_RSA_WITH_AES_256_CBC_SHA, + }, + } + + tr := &http.Transport{TLSClientConfig: tlsConf} + + httpclient := &http.Client{Transport: tr} + + log.Info("Issuing REST call", "URL", url, "Action", action) + + // Get Web Server User + //secret := &corev1.Secret{} + err = c.Get(ctx, types.NamespacedName{Name: lrest.Spec.WebLrestServerUser.Secret.SecretName, Namespace: lrest.Namespace}, secret) + if err != nil { + if apierrors.IsNotFound(err) { + log.Info("Secret not found:" + lrest.Spec.WebLrestServerUser.Secret.SecretName) + return "", err + } + log.Error(err, "Unable to get the secret.") + return "", err + } + webUserEnc := string(secret.Data[lrest.Spec.WebLrestServerUser.Secret.Key]) + webUserEnc = strings.TrimSpace(webUserEnc) + + err = c.Get(ctx, types.NamespacedName{Name: lrest.Spec.LRESTPriKey.Secret.SecretName, Namespace: lrest.Namespace}, secret) + if err != nil { + if apierrors.IsNotFound(err) { + log.Info("Secret not found:" + lrest.Spec.LRESTPriKey.Secret.SecretName) + return "", err + } + log.Error(err, "Unable to get the secret.") + return "", err + } + privKey := string(secret.Data[lrest.Spec.LRESTPriKey.Secret.Key]) + webUser, err := lrcommons.CommonDecryptWithPrivKey2(privKey, webUserEnc, req) + + // Get Web Server User Password + secret = &corev1.Secret{} + err = c.Get(ctx, types.NamespacedName{Name: lrest.Spec.WebLrestServerPwd.Secret.SecretName, Namespace: lrest.Namespace}, secret) + if err != nil { + if apierrors.IsNotFound(err) { + log.Info("Secret not found:" + lrest.Spec.WebLrestServerPwd.Secret.SecretName) + return "", err + } + log.Error(err, "Unable to get the secret.") + return "", err + } + webUserPwdEnc := string(secret.Data[lrest.Spec.WebLrestServerPwd.Secret.Key]) + webUserPwdEnc = strings.TrimSpace(webUserPwdEnc) + webUserPwd, err := lrcommons.CommonDecryptWithPrivKey2(privKey, webUserPwdEnc, req) + + var httpreq *http.Request + if action == "GET" { + httpreq, err = http.NewRequest(action, url, nil) + } else { + jsonValue, _ := json.Marshal(payload) + httpreq, err = http.NewRequest(action, url, bytes.NewBuffer(jsonValue)) + } + + if err != nil { + log.Info("Unable to create HTTP Request for LRPDB : "+lrest.Name, "err", err.Error()) + return "", err + } + + httpreq.Header.Add("Accept", "application/json") + httpreq.Header.Add("Content-Type", "application/json") + httpreq.SetBasicAuth(webUser, webUserPwd) + + resp, err := httpclient.Do(httpreq) + if err != nil { + log.Info("Rest server temporary unavailable") + errmsg := err.Error() + log.Error(err, "Failed - Could not connect to LREST Pod", "err", err.Error()) + lrest.Status.Msg = "Error: Could not connect to LREST Pod" + e.Eventf(lrest, corev1.EventTypeWarning, "LRESTError", errmsg) + return "", err + } + + e.Eventf(lrest, corev1.EventTypeWarning, "Done", lrest.Spec.LRESTName) + if resp.StatusCode != http.StatusOK { + bb, _ := ioutil.ReadAll(resp.Body) + + if resp.StatusCode == 404 { + log.Info("error 404") + + } else { + if flood_control == false { + lrest.Status.Msg = "LREST Error - HTTP Status Code:" + strconv.Itoa(resp.StatusCode) + } + } + + if flood_control == false { + log.Info("LREST Error - HTTP Status Code :"+strconv.Itoa(resp.StatusCode), "Err", string(bb)) + } + + var apiErr LRESTError + json.Unmarshal([]byte(bb), &apiErr) + if flood_control == false { + e.Eventf(lrest, corev1.EventTypeWarning, "LRESTError", "Failed: %s", apiErr.Message) + } + fmt.Printf("\n================== APIERR ======================\n") + fmt.Printf("%+v \n", apiErr) + fmt.Printf("URL=%s\n", url) + fmt.Printf("resp.StatusCode=%s\n", strconv.Itoa(resp.StatusCode)) + fmt.Printf("\n================== APIERR ======================\n") + flood_control = true + return "", errors.New("LREST Error") + } + flood_control = false + + defer resp.Body.Close() + + bodyBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + fmt.Print(err.Error()) + } + respData := string(bodyBytes) + fmt.Print("CALL API return msg.....:") + fmt.Println(string(bodyBytes)) + + var apiResponse restSQLCollection + json.Unmarshal([]byte(bodyBytes), &apiResponse) + fmt.Printf("===> %#v\n", apiResponse) + fmt.Printf("===> %+v\n", apiResponse) + + errFound := false + for _, sqlItem := range apiResponse.Items { + if sqlItem.ErrorDetails != "" { + log.Info("LREST Error - Oracle Error Code :" + strconv.Itoa(sqlItem.ErrorCode)) + if !errFound { + lrest.Status.Msg = sqlItem.ErrorDetails + } + e.Eventf(lrest, corev1.EventTypeWarning, "OraError", "%s", sqlItem.ErrorDetails) + errFound = true + } + } + + if errFound { + return "", errors.New("Oracle Error") + } + + return respData, nil +} + +func (r *LRESTReconciler) lrestHealthCheck(ctx context.Context, req ctrl.Request, lrest *dbapi.LREST) { + log := r.Log.WithValues("lrestHealthCheck", req.NamespacedName) + + //* Check port status *// + + // lrestHealthy = "Healthy" + // lrestUnHealthy = "Unhealthy" + + log.Info("starting lrest health check") + lrest.Status.Msg = lrestHealthy + RestPort := lrest.Spec.LRESTPort + RestName := lrest.Name + "-lrest" + RestNmsp := lrest.Namespace + Ip := RestName + "." + RestNmsp + ":" + strconv.Itoa(RestPort) + _, err := net.DialTimeout("tcp", Ip, time.Duration(300)*time.Millisecond) + + if err != nil { + log.Error(err, "net.DialTimeout", "err", err.Error()) + if lrest.Status.Msg == lrestHealthy { + // Sent event only if we go from Healthy to unHealthy + r.Recorder.Eventf(lrest, corev1.EventTypeWarning, "net.DialTimeout ", "lrest=%s", lrest.Name+"."+lrest.Namespace) + } + + lrest.Status.Msg = lrestUnHealthy + + } + + //* Check rdbms availability *// + // We can check the pdb$seed status to verify that cdb is aliave + // in the future we can expose a rest call for OCIPing + + url := "https://" + Ip + "/database/pdbs/PDB$SEED/status/" + _, err = NewCallAPIAllPdbs(r, ctx, req, lrest, url, nil, "GET") + if err != nil { + log.Info("NewCallAPIAllPdbs Error") + if lrest.Status.Msg == lrestHealthy { + // Sent event only if we go from Healthy to unHealthy + r.Recorder.Eventf(lrest, corev1.EventTypeWarning, "RDBMS issue ", "lrest=%s", lrest.Name+"."+lrest.Namespace) + } + + lrest.Status.Msg = lrestUnHealthy + } + + if err := r.Status().Update(ctx, lrest); err != nil { + log.Error(err, "Failed to update status for :"+lrest.Name, "err", err.Error()) + } + +} diff --git a/controllers/database/lrpdb_controller.go b/controllers/database/lrpdb_controller.go index 1aadf65b..5b12fa66 100644 --- a/controllers/database/lrpdb_controller.go +++ b/controllers/database/lrpdb_controller.go @@ -41,12 +41,16 @@ package controllers import ( "bytes" "context" + "crypto/rand" "crypto/rsa" + "crypto/sha256" "crypto/tls" "crypto/x509" "encoding/base64" "encoding/json" "encoding/pem" + "reflect" + "sort" //"encoding/pem" "errors" @@ -58,9 +62,10 @@ import ( "strings" "time" + databasev4 "github.com/oracle/oracle-database-operator/apis/database/v4" dbapi "github.com/oracle/oracle-database-operator/apis/database/v4" "github.com/oracle/oracle-database-operator/commons/k8s" - lrcommons "github.com/oracle/oracle-database-operator/commons/multitenant/lrest" + . "github.com/oracle/oracle-database-operator/commons/multitenant/lrest" "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" @@ -80,14 +85,50 @@ import ( "sigs.k8s.io/controller-runtime/pkg/predicate" ) +/* + + BITMASK STATUS RECAP. + ~~~~~~~~~~~~~~~~~~~~ + PDBCRT = 0x00000001 -- Create pdb + PDBOPN = 0x00000002 -- Open pdb read write + PDBCLS = 0x00000004 -- Close pdb + PDBDIC = 0x00000008 -- Drop pdb include datafiles + OCIHDL = 0x00000010 -- OCI handle allocation + OCICON = 0x00000020 -- Rdbms connection + FNALAZ = 0x00000040 -- Finalizer configured + PDBUPL = 0x00000080 -- Unplug pdb + PDBPLG = 0x00000100 -- plug pdb + -- Error section -- + PDBCRE = 0x00001000 -- PDB creation error + PDBOPE = 0x00002000 -- PDB open error + PDBCLE = 0x00004000 -- PDB close error + OCIHDE = 0x00008000 -- Allocation Handle Error + OCICOE = 0x00010000 -- CDD connection Error + FNALAE = 0x00020000 -- Finalizer error + PDBUPE = 0x00040000 -- Unplug Error + PDBPLE = 0x00080000 -- Plug Error + PDBPLW = 0x00100000 -- Plug Warining + -- Autodiscover + PDBAUT = 0x01000000 -- Autodisover + + + BITMASK CONFIGMAP PARAMETER RECAP. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + MPAPPL = 0x00000001 -- The map config has been applyed + MPSYNC = 0x00000002 -- The map config is in sync with v$parameters where is default=flase + MPEMPT = 0x00000004 -- The map is empty - not specify + MPWARN = 0x00000008 -- Map applied with warnings + MPINIT = 0x00000010 -- Config map init + SPARE3 = 0x00000020 -- + + +*/ + // Bitmask functions + const ( - MPAPPL = 0x00000001 /* The map config has been applyed */ - MPSYNC = 0x00000002 /* The map config is in sync with v$parameters where is default=flase */ - MPEMPT = 0x00000004 /* The map is empty - not specify */ - MPWARN = 0x00000008 /* Map applied with warnings */ - MPINIT = 0x00000010 /* Config map init */ - SPARE3 = 0x00000020 + DBGAPI = 0x00000001 /* Debug NewcallApi */ + DBGCRT = 0x00000002 /* Debug pdb creation */ ) func bis(bitmask int, bitval int) int { @@ -95,6 +136,7 @@ func bis(bitmask int, bitval int) int { return bitmask } +/* func bit(bitmask int, bitval int) bool { if bitmask&bitval != 0 { return true @@ -102,35 +144,14 @@ func bit(bitmask int, bitval int) bool { return false } } +*/ +/* func bid(bitmask int, bitval int) int { bitmask ^= ((bitval) & (bitmask)) return bitmask } - -func bitmaskprint(bitmask int) string { - BitRead := "|" - if bit(bitmask, MPAPPL) { - BitRead = strings.Join([]string{BitRead, "MPAPPL|"}, "") - } - if bit(bitmask, MPSYNC) { - BitRead = strings.Join([]string{BitRead, "MPSYNC|"}, "") - } - if bit(bitmask, MPEMPT) { - BitRead = strings.Join([]string{BitRead, "MPEMPT|"}, "") - } - if bit(bitmask, MPWARN) { - BitRead = strings.Join([]string{BitRead, "MPWARN|"}, "") - } - if bit(bitmask, MPINIT) { - BitRead = strings.Join([]string{BitRead, "MPINIT|"}, "") - } - if bit(bitmask, SPARE3) { - BitRead = strings.Join([]string{BitRead, "SPARE3|"}, "") - } - - return BitRead -} +*/ // LRPDBReconciler reconciles a LRPDB object type LRPDBReconciler struct { @@ -165,28 +186,17 @@ type LRESTError struct { Instance string `json:"instance,omitempty"` } -var ( - lrpdbPhaseCreate = "Creating" - lrpdbPhasePlug = "Plugging" - lrpdbPhaseUnplug = "Unplugging" - lrpdbPhaseClone = "Cloning" - lrpdbPhaseFinish = "Finishing" - lrpdbPhaseReady = "Ready" - lrpdbPhaseDelete = "Deleting" - lrpdbPhaseModify = "Modifying" - lrpdbPhaseMap = "Mapping" - lrpdbPhaseStatus = "CheckingState" - lrpdbPhaseFail = "Failed" - lrpdbPhaseAlterPlug = "AlterPlugDb" - lrpdbPhaseSpare = "NoAction" -) +type PLSQLPayLoad struct { + Values map[string]string + Sqltokens []string +} const LRPDBFinalizer = "database.oracle.com/LRPDBfinalizer" var tde_Password string var tde_Secret string var flood_control bool = false -var assertiveLpdbDeletion bool = false /* Global variable for assertive pdb deletion */ +var imperativeLpdbDeletion bool = false /* Global variable for imperative pdb deletion */ /* We need to record the config map name after pdb creation in order to use it during open and clone op if config map @@ -200,6 +210,7 @@ var globalsqlcode int //+kubebuilder:rbac:groups=database.oracle.com,resources=events,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=database.oracle.com,resources=lrpdbs/status,verbs=get;update;patch //+kubebuilder:rbac:groups=database.oracle.com,resources=lrpdbs/finalizers,verbs=get;create;update;patch;delete +//+kubebuilder:rbac:groups=database.oracle.com,resources=lrpdbs/configmaps,verbs=get;create;update;patch;delete // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. @@ -210,786 +221,635 @@ var globalsqlcode int // // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.9.2/pkg/reconcile + +/**** RECONCILIATION LOOP ****/ func (r *LRPDBReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { log := r.Log.WithValues("multitenantoperator", req.NamespacedName) log.Info("Reconcile requested") reconcilePeriod := r.Interval * time.Second requeueY := ctrl.Result{Requeue: true, RequeueAfter: reconcilePeriod} - requeueN := ctrl.Result{} + //requeueN := ctrl.Result{} var err error lrpdb := &dbapi.LRPDB{} - // Execute for every reconcile - defer func() { - //log.Info("DEFER LRPDB", "Name", lrpdb.Name, "Phase", lrpdb.Status.Phase, "Status", strconv.FormatBool(lrpdb.Status.Status)) - if !lrpdb.Status.Status { - if lrpdb.Status.Phase == lrpdbPhaseReady { - lrpdb.Status.Status = true - } - if err := r.Status().Update(ctx, lrpdb); err != nil { - log.Error(err, "Failed to update status for :"+lrpdb.Name, "err", err.Error()) - } - } - }() - + /**** GET CLIENT ****/ err = r.Client.Get(context.TODO(), req.NamespacedName, lrpdb) if err != nil { if apierrors.IsNotFound(err) { - log.Info("LRPDB Resource Not found", "Name", lrpdb.Name) - // Request object not found, could have been deleted after reconcile req. - // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. - // Return and don't requeue - lrpdb.Status.Status = true + log.Info("PDB resource not found", "Pdb", lrpdb.Spec.LRPDBName) return requeueN, nil } - // Error reading the object - requeue the req. - return requeueY, err + log.Info("Client.Get Error") + return requeueN, err } - // Finalizer section - err = r.manageLRPDBDeletion2(ctx, req, lrpdb) - if err != nil { - log.Info("Reconcile queued") - return requeueY, nil - } - - // Check for Duplicate LRPDB - if !lrpdb.Status.Status { - err = r.checkDuplicateLRPDB(ctx, req, lrpdb) + /**** CREATE ****/ + if Bit(lrpdb.Status.PDBBitMask, PDBCRT) == false && Bit(lrpdb.Status.PDBBitMask, PDBCRE) == false && lrpdb.Spec.SrcLRPDBName == "" && lrpdb.Spec.XMLFileName == "" { + log.Info("REC. LOOP: create pdb") + err = r.CreateLRPDB(ctx, req, lrpdb) if err != nil { - return requeueN, nil + log.Error(err, err.Error()) + return requeueN, err } + } - action := strings.ToUpper(lrpdb.Spec.Action) - /* - Bug 36714702 - LREST OPERATOR - POST ALTER PDB OPTION LRPDB STATUS INTERMITTENTLY - SHOWS "WAITING FOR LRPDB PARAMETER TO BE MODIFIED" - introducing additional check to avoid alter system repetition during - reconciliation loop - */ - if lrpdb.Status.Phase == lrpdbPhaseReady { - if (lrpdb.Status.Action != "" || action != "NOACTION") && (action == "ALTER" || action == "MODIFY" || action == "STATUS" || lrpdb.Status.Action != action) { - lrpdb.Status.Status = false - } else { - err = r.getLRPDBState(ctx, req, lrpdb) - if err != nil { - lrpdb.Status.Phase = lrpdbPhaseFail - } else { - lrpdb.Status.Phase = lrpdbPhaseReady - lrpdb.Status.Msg = "Success" + /*** INIT CONFIG MAP ***/ + if Bit(lrpdb.Status.PDBBitMask, PDBCRT) == true && Bit(lrpdb.Status.CmBitstat, MPINIT) == false { + log.Info("REC. LOOP: init config map") + r.InitConfigMap(ctx, req, lrpdb) + } + + /*** FINALYZER ***/ + if Bit(lrpdb.Status.PDBBitMask, FNALAZ) == false && Bit(lrpdb.Status.PDBBitMask, PDBCRT) == true { + if lrpdb.ObjectMeta.DeletionTimestamp.IsZero() { + if !controllerutil.ContainsFinalizer(lrpdb, LRPDBFinalizer) { + log.Info("add finalizer:" + lrpdb.Spec.LRPDBName) + controllerutil.AddFinalizer(lrpdb, LRPDBFinalizer) + if err := r.Update(ctx, lrpdb); err != nil { + log.Info("Cannot add finalizer") + return requeueN, err + + } + lrpdb.Status.PDBBitMask = Bis(lrpdb.Status.PDBBitMask, FNALAZ) + lrpdb.Status.PDBBitMaskStr = Bitmaskprint(lrpdb.Status.PDBBitMask) + r.UpdateStatus(ctx, req, lrpdb) } - r.Status().Update(ctx, lrpdb) } } - if !lrpdb.Status.Status { - r.validatePhase(ctx, req, lrpdb) - phase := lrpdb.Status.Phase - log.Info("LRPDB:", "Name", lrpdb.Name, "Phase", phase, "Status", strconv.FormatBool(lrpdb.Status.Status)) - - switch phase { - case lrpdbPhaseCreate: - err = r.createLRPDB(ctx, req, lrpdb) - case lrpdbPhaseClone: - err = r.cloneLRPDB(ctx, req, lrpdb) - case lrpdbPhasePlug: - err = r.plugLRPDB(ctx, req, lrpdb) - case lrpdbPhaseUnplug: - err = r.unplugLRPDB(ctx, req, lrpdb) - case lrpdbPhaseModify: - err = r.modifyLRPDB(ctx, req, lrpdb) - case lrpdbPhaseDelete: - err = r.deleteLRPDB(ctx, req, lrpdb) - case lrpdbPhaseStatus: - err = r.getLRPDBState(ctx, req, lrpdb) - case lrpdbPhaseMap: - err = r.mapLRPDB(ctx, req, lrpdb) - case lrpdbPhaseFail: - err = r.mapLRPDB(ctx, req, lrpdb) - case lrpdbPhaseAlterPlug: - err = r.alterSystemLRPDB(ctx, req, lrpdb) - default: - log.Info("DEFAULT:", "Name", lrpdb.Name, "Phase", phase, "Status", strconv.FormatBool(lrpdb.Status.Status)) - return requeueN, nil - } - lrpdb.Status.Action = strings.ToUpper(lrpdb.Spec.Action) + /**** OPEN ****/ + if lrpdb.Spec.LRPDBState == "OPEN" && Bit(lrpdb.Status.PDBBitMask, PDBOPN) == false && Bit(lrpdb.Status.PDBBitMask, PDBOPE) == false { + log.Info("REC. LOOP: open pdb") + err = r.OpenLRPDB(ctx, req, lrpdb) if err != nil { - lrpdb.Status.Phase = lrpdbPhaseFail - lrpdb.Status.SqlCode = globalsqlcode - } else { - lrpdb.Status.Phase = lrpdbPhaseReady - lrpdb.Status.Msg = "Success" + log.Error(err, err.Error()) + return requeueN, err } } - r.ManageConfigMapForCloningAndPlugin(ctx, req, lrpdb) - lrpdb.Status.BitStatStr = bitmaskprint(lrpdb.Status.Bitstat) + /**** CLOSE ****/ + if lrpdb.Spec.LRPDBState == "CLOSE" && Bit(lrpdb.Status.PDBBitMask, PDBOPN) == true { + log.Info("REC. LOOP: open pdb") + err = r.CloseLRPDB(ctx, req, lrpdb) + if err != nil { + log.Error(err, err.Error()) + return requeueN, err + } + } - log.Info("Reconcile completed") - return requeueY, nil -} + /**** DELETE (imperative approach) ****/ + if !lrpdb.ObjectMeta.DeletionTimestamp.IsZero() && Bit(lrpdb.Status.PDBBitMask, PDBCRT) == true && Bit(lrpdb.Status.PDBBitMask, FNALAZ) == true && Bit(lrpdb.Status.PDBBitMask, PDBDIC) == false { + log.Info("REC. LOOP: delete pdb - imperative approach") + log.Info(" ObjectMeta.DeletionTimestamp.IsZero is not null") + err = r.DeleteLRPDB(ctx, req, lrpdb) + if err != nil { + log.Error(err, err.Error()) + return requeueN, err + } -/* -************************************************ - - Validate the LRPDB Spec - /*********************************************** -*/ -func (r *LRPDBReconciler) validatePhase(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) { - - log := r.Log.WithValues("validatePhase", req.NamespacedName) - - action := strings.ToUpper(lrpdb.Spec.Action) - - log.Info("Validating LRPDB phase for: "+lrpdb.Name, "Action", action) - - switch action { - case "CREATE": - lrpdb.Status.Phase = lrpdbPhaseCreate - case "CLONE": - lrpdb.Status.Phase = lrpdbPhaseClone - case "PLUG": - lrpdb.Status.Phase = lrpdbPhasePlug - case "UNPLUG": - lrpdb.Status.Phase = lrpdbPhaseUnplug - case "MODIFY": - lrpdb.Status.Phase = lrpdbPhaseModify - case "DELETE": - lrpdb.Status.Phase = lrpdbPhaseDelete - case "STATUS": - lrpdb.Status.Phase = lrpdbPhaseStatus - case "MAP": - lrpdb.Status.Phase = lrpdbPhaseMap - case "ALTER": - lrpdb.Status.Phase = lrpdbPhaseAlterPlug - case "NOACTION": - lrpdb.Status.Phase = lrpdbPhaseStatus - - } - - log.Info("Validation complete") -} + } -/* - This function scans the list of crd - pdb to verify the existence of the - pdb (crd) that we want to clone. - Bug 36752925 - LREST OPERATOR - CLONE NON-EXISTENT - PDB CREATES A LRPDB WITH STATUS FAILED + /**** DELETE (declarative approach) ****/ + if lrpdb.Spec.LRPDBState == "DELETE" && Bit(lrpdb.Status.PDBBitMask, PDBCRT) == true && Bit(lrpdb.Status.PDBBitMask, FNALAZ) == true && Bit(lrpdb.Status.PDBBitMask, PDBDIC) == false { + log.Info("REC. LOOP: delete pdb - imperative approach") + err = r.DeleteLRPDBDeclarative(ctx, req, lrpdb) + if err != nil { + log.Error(err, err.Error()) + return requeueN, err + } - return 1 - CRD found - return 0 - CRD not found / Stop clone process + } - Bug 36753107 - LREST OPERATOR - CLONE - CLOSED PDB SUCCESSFULLY CLONES + /**** CLONE *****/ + if lrpdb.Spec.SrcLRPDBName != "" && Bit(lrpdb.Status.PDBBitMask, PDBCRT|FNALAZ|PDBCRE) == false { + log.Info("REC. LOOP: clone pdb ") + err = r.CloneLRPDB(ctx, req, lrpdb) + if err != nil { + log.Error(err, err.Error()) + return requeueN, err + } -*/ + } -func (r *LRPDBReconciler) checkPDBforCloninig(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB, targetPdbName string) (int, error) { - log := r.Log.WithValues("checkDuplicateLRPDB", req.NamespacedName) - var pdbCounter int - pdbCounter = 0 + /**** UNPLUG AND PLUG SECTION ****/ + if lrpdb.Spec.LRPDBState == "UNPLUG" && lrpdb.Spec.XMLFileName != "" && Bit(lrpdb.Status.PDBBitMask, PDBCRT) == true && Bit(lrpdb.Status.PDBBitMask, FNALAZ) == true && Bit(lrpdb.Status.PDBBitMask, PDBUPE) == false { + log.Info("REC. LOOP: unplug pdb ") + err = r.UnplugLRPDB(ctx, req, lrpdb) + if err != nil { + log.Error(err, err.Error()) + return requeueN, err + } - lrpdbList := &dbapi.LRPDBList{} - listOpts := []client.ListOption{client.InNamespace(req.Namespace), client.MatchingFields{"spec.pdbName": targetPdbName}} - err := r.List(ctx, lrpdbList, listOpts...) - if err != nil { - log.Info("Failed to list lrpdbs", "Namespace", req.Namespace, "Error", err) - return 0, err - } - if len(lrpdbList.Items) == 0 { - log.Info("No pdbs available") - return pdbCounter, err } - for _, p := range lrpdbList.Items { - fmt.Printf("DEBUGCLONE %s %s %i\n", p.Spec.LRPDBName, targetPdbName, pdbCounter) - if p.Spec.LRPDBName == targetPdbName { - log.Info("Found " + targetPdbName + " in the crd list") - if p.Status.OpenMode == "MOUNTED" { - log.Info("Cannot clone a mounted pdb") - return pdbCounter, err - } - pdbCounter++ - fmt.Printf("DEBUGCLONE %s %s %i\n", p.Spec.LRPDBName, targetPdbName, pdbCounter) - return pdbCounter, err + if lrpdb.Spec.LRPDBState == "PLUG" && lrpdb.Spec.XMLFileName != "" && Bit(lrpdb.Status.PDBBitMask, PDBCRT) == false && Bit(lrpdb.Status.PDBBitMask, PDBPLE) == false { + log.Info("REC. LOOP: plug pdb ") + err = r.PlugLRPDB(ctx, req, lrpdb) + if err != nil { + log.Error(err, err.Error()) + return requeueN, err } } - return pdbCounter, err -} -/* -*************************************************************** - - Check for Duplicate LRPDB. Same LRPDB name on the same LREST resource. + /**** APPLY CONFIG MAP PARAMETER ****/ + if lrpdb.Spec.PDBConfigMap != "" && Bit(lrpdb.Status.PDBBitMask, PDBOPN) == true && Bit(lrpdb.Status.PDBBitMask, PDBCRT) == true && Bit(lrpdb.Status.CmBitstat, MPAPPL) == false && lrpdb.Spec.LRPDBState != "UNPLUG" { + log.Info("REC. LOOP: plug pdb ") + log.Info("Apply configmap:" + lrpdb.Spec.PDBConfigMap) + Cardinality, err := r.ApplyConfigMap(ctx, req, lrpdb) + if err != nil { + log.Error(err, err.Error()) + return requeueN, err + } + log.Info("Config. Map Cardinality:" + strconv.FormatInt(int64(Cardinality), 10)) -/************************************************************** -*/ -func (r *LRPDBReconciler) checkDuplicateLRPDB(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) error { + } - log := r.Log.WithValues("checkDuplicateLRPDB", req.NamespacedName) + /**** APPLY PLSQL/SQL SCRIPT *****/ + if lrpdb.Spec.PLSQLBlock != "" && Bit(lrpdb.Status.PDBBitMask, PDBOPN) == true && Bit(lrpdb.Status.PDBBitMask, PDBCRT) == true && lrpdb.Spec.LRPDBState != "UNPLUG" && Bit(lrpdb.Status.CmBitstat, MPINIT) == true && Bit(lrpdb.Status.PDBBitMask, FNALAZ) == true { + log.Info("REC. LOOP: apply plsql/sql") + err = r.execPLSQL(ctx, req, lrpdb) + if err != nil { + log.Error(err, err.Error()) + return requeueN, err + } - // Name of the LREST CR that holds the LREST container - lrestResName := lrpdb.Spec.CDBResName - //lrestame := lrpdb.Spec.LRESTName + } - // Name of the LRPDB resource - lrpdbResName := lrpdb.Spec.LRPDBName + /**** ALTER SYSTEM ****/ + if lrpdb.Spec.AlterSystemValue != "" && lrpdb.Spec.AlterSystemParameter != "" && Bit(lrpdb.Status.PDBBitMask, PDBOPN) == true && Bit(lrpdb.Status.PDBBitMask, PDBCRT) == true && lrpdb.Spec.LRPDBState != "UNPLUG" && Bit(lrpdb.Status.CmBitstat, MPINIT) == true && Bit(lrpdb.Status.PDBBitMask, FNALAZ) == true && lrpdb.Spec.PLSQLBlock == "" { + log.Info("REC. LOOP: Alter system ") + err = r.alterSystemLRPDB(ctx, req, lrpdb) + if err != nil { + log.Error(err, err.Error()) + return requeueN, err + } - lrpdbList := &dbapi.LRPDBList{} + } - listOpts := []client.ListOption{client.InNamespace(req.Namespace), client.MatchingFields{"spec.pdbName": lrpdbResName}} + /**** MONITOR PDB *****/ + if Bit(lrpdb.Status.PDBBitMask, PDBCRT) == true && Bit(lrpdb.Status.PDBBitMask, FNALAZ) == true && lrpdb.Spec.PLSQLBlock == "" && lrpdb.Spec.AlterSystemValue == "" && lrpdb.Spec.XMLFileName == "" && Bit(lrpdb.Status.CmBitstat, MPINIT) == true { + log.Info("REC. LOOP: Monitor PDB") + err = r.MonitorLRPDB(ctx, req, lrpdb) + if err != nil { + log.Error(err, err.Error()) + return requeueN, err + } - // List retrieves list of objects for a given namespace and list options. - err := r.List(ctx, lrpdbList, listOpts...) - if err != nil { - log.Info("Failed to list lrpdbs", "Namespace", req.Namespace, "Error", err) - return err } - if len(lrpdbList.Items) == 0 { - log.Info("No lrpdbs found for LRPDBName: "+lrpdbResName, "CDBResName", lrestResName) - return nil - } + /* REST STAT */ + if lrpdb.Spec.PDBBitMask != 0 && lrpdb.Spec.LRPDBState == "RESET" { + log.Info("REC. LOOP: reset state") + lrpdb.Status.PDBBitMask = lrpdb.Spec.PDBBitMask + log.Info("lrpdb.Status.PDBBitMask:" + strconv.Itoa(lrpdb.Status.PDBBitMask)) + log.Info("lrpdb.Spec.PDBBitMask:" + strconv.Itoa(lrpdb.Spec.PDBBitMask)) + if Bit(lrpdb.Spec.PDBBitMask, PDBAUT) == true { + log.Info("reset state PDBAUT") + if controllerutil.ContainsFinalizer(lrpdb, LRPDBFinalizer) { + lrpdb.Status.PDBBitMask = Bis(lrpdb.Status.PDBBitMask, FNALAZ) + } - for _, p := range lrpdbList.Items { - log.Info("Found LRPDB: " + p.Name) - if (p.Name != lrpdb.Name) && (p.Spec.CDBResName == lrestResName) { - log.Info("Duplicate LRPDB found") - lrpdb.Status.Msg = "LRPDB Resource already exists" - lrpdb.Status.Status = false - lrpdb.Status.Phase = lrpdbPhaseFail - return errors.New("Duplicate LRPDB found") + lrpdb.Status.PDBBitMask = Bis(lrpdb.Status.PDBBitMask, PDBCRT) + } + lrpdb.Status.PDBBitMaskStr = Bitmaskprint(lrpdb.Status.PDBBitMask) + r.UpdateStatus(ctx, req, lrpdb) + + lrpdb.Spec.PDBBitMask = 0 + lrpdb.Spec.LRPDBState = "NONE" + + err = r.Update(ctx, lrpdb) + if err != nil { + log.Error(err, err.Error()) + return requeueN, err } } - return nil + + return requeueY, nil } /* -*************************************************************** - - Get the Custom Resource for the LREST mentioned in the LRPDB Spec - /************************************************************** -*/ -func (r *LRPDBReconciler) getLRESTResource(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) (dbapi.LREST, error) { - - log := r.Log.WithValues("getLRESTResource", req.NamespacedName) +********************************************************************* + - MONITOR PDB - var lrest dbapi.LREST // LREST CR corresponding to the LREST name specified in the LRPDB spec +********************************************************************* +*/ - // Name of the LREST CR that holds the LREST container - lrestResName := lrpdb.Spec.CDBResName - lrestNamespace := lrpdb.Spec.CDBNamespace +func (r *LRPDBReconciler) MonitorLRPDB(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) error { + log := r.Log.WithValues("MonitorLRPDB ", req.NamespacedName) + r.getLRPDBState(ctx, req, lrpdb) - log.Info("lrestResName...........:" + lrestResName) - log.Info("lrestNamespace.........:" + lrestNamespace) + /* Check open mode consistency */ + if Bit(lrpdb.Status.PDBBitMask, PDBCLS) == true && lrpdb.Status.OpenMode == "READ WRITE" { + log.Info("Open mode inconsistency.......:target:close - status read write") + log.Info("Fix inconsistency.............:call(r.CloseLRPDB(ctx, req, lrpdb) )") + r.Recorder.Eventf(lrpdb, corev1.EventTypeWarning, "open mode inconsistency", "Target:[PDBCLS] Status:['%s']", lrpdb.Status.OpenMode) + err := r.CloseLRPDB(ctx, req, lrpdb) + if err != nil { + log.Error(err, err.Error()) + return err + } - // Get LREST CR corresponding to the LREST name specified in the LRPDB spec - err := r.Get(context.Background(), client.ObjectKey{ - Namespace: lrestNamespace, - Name: lrestResName, - }, &lrest) + return nil + } - if err != nil { - log.Info("Failed to get CRD for LREST", "Name", lrestResName, "Namespace", lrestNamespace, "Error", err.Error()) - lrpdb.Status.Msg = "Unable to get CRD for LREST : " + lrestResName - r.Status().Update(ctx, lrpdb) - return lrest, err + if Bit(lrpdb.Status.PDBBitMask, PDBOPN) == true && lrpdb.Status.OpenMode == "MOUNTED" { + log.Info("Open mode inconsistency.......:target:read write - status mounted") + log.Info("Fix inconsistency.............:call(r.OpenLRPDB(ctx, req, lrpdb) )") + r.Recorder.Eventf(lrpdb, corev1.EventTypeWarning, "open mode inconsistency", "Target:[PDBOPN] Status:['%s']", lrpdb.Status.OpenMode) + err := r.OpenLRPDB(ctx, req, lrpdb) + if err != nil { + log.Error(err, err.Error()) + return err + } + return nil } - log.Info("Found CR for LREST", "Name", lrestResName, "CR Name", lrest.Name) - return lrest, nil + return nil } /* -*************************************************************** - - Get the LREST Pod for the LREST mentioned in the LRPDB Spec - /************************************************************** -*/ -func (r *LRPDBReconciler) getLRESTPod(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) (corev1.Pod, error) { +********************************************************************* + - PLUG PDB - log := r.Log.WithValues("getLRESTPod", req.NamespacedName) +********************************************************************* +*/ - var lrestPod corev1.Pod // LREST Pod container with connection to the concerned LREST +func (r *LRPDBReconciler) PlugLRPDB(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) error { - // Name of the LREST CR that holds the LREST container - lrestResName := lrpdb.Spec.CDBResName + log := r.Log.WithValues("PlugLRPDB", req.NamespacedName) + globalsqlcode = 0 - // Get LREST Pod associated with the LREST Name specified in the LRPDB Spec - err := r.Get(context.Background(), client.ObjectKey{ - Namespace: req.Namespace, - Name: lrestResName + "-lrest", - }, &lrestPod) + var err error + // var tde_Password string + // var tde_Secret string + lrest, err := r.getLRESTResource(ctx, req, lrpdb) if err != nil { - log.Info("Failed to get Pod for LREST", "Name", lrestResName, "Namespace", req.Namespace, "Error", err.Error()) - lrpdb.Status.Msg = "Unable to get LREST Pod for LREST : " + lrestResName - return lrestPod, err + return err } - log.Info("Found LREST Pod for LREST", "Name", lrestResName, "Pod Name", lrestPod.Name, "LREST Container hostname", lrestPod.Spec.Hostname) - return lrestPod, nil -} + values := map[string]string{ + "method": "PLUG", + "xmlFileName": lrpdb.Spec.XMLFileName, + "pdb_name": lrpdb.Spec.LRPDBName, + "sourceFileNameConversions": lrpdb.Spec.SourceFileNameConversions, + "copyAction": lrpdb.Spec.CopyAction, + "fileNameConversions": lrpdb.Spec.FileNameConversions, + "unlimitedStorage": strconv.FormatBool(*(lrpdb.Spec.UnlimitedStorage)), + "reuseTempFile": strconv.FormatBool(*(lrpdb.Spec.ReuseTempFile)), + "totalSize": lrpdb.Spec.TotalSize, + "tempSize": lrpdb.Spec.TempSize, + "getScript": strconv.FormatBool(*(lrpdb.Spec.GetScript))} -/* -************************************************ - - Get Secret Key for a Secret Name - /*********************************************** -*/ -func (r *LRPDBReconciler) getSecret(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB, secretName string, keyName string) (string, error) { + /* + if *(lrpdb.Spec.LTDEImport) { + tde_Password, err = r.getSecret(ctx, req, lrpdb, lrpdb.Spec.LTDEPassword.Secret.SecretName, lrpdb.Spec.LTDEPassword.Secret.Key) + if err != nil { + return err + } + tde_Secret, err = r.getSecret(ctx, req, lrpdb, lrpdb.Spec.LTDESecret.Secret.SecretName, lrpdb.Spec.LTDESecret.Secret.Key) + if err != nil { + return err + } - log := r.Log.WithValues("getSecret", req.NamespacedName) + tde_Secret = tde_Secret[:len(tde_Secret)-1] + tde_Password = tde_Secret[:len(tde_Password)-1] + values["tde_Password"] = tde_Password + values["tdeKeystorePath"] = lrpdb.Spec.LTDEKeystorePath + values["tde_Secret"] = tde_Secret + values["tdeImport"] = strconv.FormatBool(*(lrpdb.Spec.LTDEImport)) + } - secret := &corev1.Secret{} - err := r.Get(ctx, types.NamespacedName{Name: secretName, Namespace: lrpdb.Namespace}, secret) - if err != nil { - if apierrors.IsNotFound(err) { - log.Info("Secret not found:" + secretName) - lrpdb.Status.Msg = "Secret not found:" + secretName - return "", err + if *(lrpdb.Spec.AsClone) { + values["asClone"] = strconv.FormatBool(*(lrpdb.Spec.AsClone)) } - log.Error(err, "Unable to get the secret.") - return "", err - } + */ - return string(secret.Data[keyName]), nil -} + lrpdb.Status.Msg = "plug:[op. in progress]" + lrpdb.Status.PDBBitMask = Bis(lrpdb.Status.PDBBitMask, PDBPLG) + lrpdb.Status.PDBBitMaskStr = Bitmaskprint(lrpdb.Status.PDBBitMask) + r.UpdateStatus(ctx, req, lrpdb) -/* -************************************************ - - Issue a REST API Call to the LREST container - /*********************************************** -*/ -func (r *LRPDBReconciler) callAPI(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB, url string, payload map[string]string, action string) (string, error) { - log := r.Log.WithValues("callAPI", req.NamespacedName) + url := r.BaseUrl(ctx, req, lrpdb, lrest) - var err error + respData, err := NewCallAPISQL(r, ctx, req, lrpdb, url, values, "POST") + if err != nil { + log.Error(err, "Failure NewCallAPISQL( "+url+")", "err", err.Error()) + return err + } - secret := &corev1.Secret{} + r.GetSqlCode(respData, &(lrpdb.Status.SqlCode)) + globalsqlcode = lrpdb.Status.SqlCode - err = r.Get(ctx, types.NamespacedName{Name: lrpdb.Spec.LRPDBTlsKey.Secret.SecretName, Namespace: lrpdb.Namespace}, secret) + if lrpdb.Status.SqlCode != 0 { + globalsqlcode = lrpdb.Status.SqlCode + lrpdb.Status.PDBBitMask = Bis(lrpdb.Status.PDBBitMask, PDBPLE) /* Upplug error */ + lrpdb.Status.PDBBitMask = Bid(lrpdb.Status.PDBBitMask, PDBPLG) /* Remove unplug flag */ + lrpdb.Status.PDBBitMaskStr = Bitmaskprint(lrpdb.Status.PDBBitMask) + oer := fmt.Sprintf("ORA-%d", lrpdb.Status.SqlCode) /* Print the oracle error */ + lrpdb.Status.Msg = "close:[" + oer + "]" + r.UpdateStatus(ctx, req, lrpdb) + return errors.New(oer) - if err != nil { - if apierrors.IsNotFound(err) { - log.Info("Secret not found:" + lrpdb.Spec.LRPDBTlsKey.Secret.SecretName) - return "", err - } - log.Error(err, "Unable to get the secret.") - return "", err - } - rsaKeyPEM := secret.Data[lrpdb.Spec.LRPDBTlsKey.Secret.Key] - - err = r.Get(ctx, types.NamespacedName{Name: lrpdb.Spec.LRPDBTlsCrt.Secret.SecretName, Namespace: lrpdb.Namespace}, secret) - - if err != nil { - if apierrors.IsNotFound(err) { - log.Info("Secret not found:" + lrpdb.Spec.LRPDBTlsCrt.Secret.SecretName) - return "", err - } - log.Error(err, "Unable to get the secret.") - return "", err - } - - rsaCertPEM := secret.Data[lrpdb.Spec.LRPDBTlsCrt.Secret.Key] - - err = r.Get(ctx, types.NamespacedName{Name: lrpdb.Spec.LRPDBTlsCat.Secret.SecretName, Namespace: lrpdb.Namespace}, secret) - - if err != nil { - if apierrors.IsNotFound(err) { - log.Info("Secret not found:" + lrpdb.Spec.LRPDBTlsCat.Secret.SecretName) - return "", err - } - log.Error(err, "Unable to get the secret.") - return "", err } - caCert := secret.Data[lrpdb.Spec.LRPDBTlsCat.Secret.Key] - /* - r.Recorder.Eventf(lrpdb, corev1.EventTypeWarning, "LRESTINFO", string(rsaKeyPEM)) - r.Recorder.Eventf(lrpdb, corev1.EventTypeWarning, "LRESTINFO", string(rsaCertPEM)) - r.Recorder.Eventf(lrpdb, corev1.EventTypeWarning, "LRESTINFO", string(caCert)) - */ + r.Recorder.Eventf(lrpdb, corev1.EventTypeNormal, "Created", "LRPDB '%s' plugged successfully", lrpdb.Spec.LRPDBName) - certificate, err := tls.X509KeyPair([]byte(rsaCertPEM), []byte(rsaKeyPEM)) - if err != nil { - lrpdb.Status.Msg = "Error tls.X509KeyPair" - return "", err + if lrest.Spec.DBServer != "" { + lrpdb.Status.ConnString = lrest.Spec.DBServer + ":" + strconv.Itoa(lrest.Spec.DBPort) + "/" + lrpdb.Spec.LRPDBName + } else { + log.Info("Parsing connectstring") + lrpdb.Status.ConnString = lrest.Spec.DBTnsurl + parseTnsAlias(&(lrpdb.Status.ConnString), &(lrpdb.Spec.LRPDBName)) } - caCertPool := x509.NewCertPool() - caCertPool.AppendCertsFromPEM(caCert) - /* - tlsConf := &tls.Config{Certificates: []tls.Certificate{certificate}, - RootCAs: caCertPool} - */ - tlsConf := &tls.Config{Certificates: []tls.Certificate{certificate}, - RootCAs: caCertPool, - //MinVersion: tls.VersionTLS12, - CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256}, - PreferServerCipherSuites: true, - CipherSuites: []uint16{ - tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, - tls.TLS_RSA_WITH_AES_256_GCM_SHA384, - tls.TLS_RSA_WITH_AES_256_CBC_SHA, - }, + imperativeLpdbDeletion = lrpdb.Spec.ImperativeLrpdbDeletion + if lrpdb.Spec.ImperativeLrpdbDeletion == true { + r.Recorder.Eventf(lrpdb, corev1.EventTypeNormal, "Plug", "PDB '%s' imperative pdb deletion turned on", lrpdb.Spec.LRPDBName) } - tr := &http.Transport{TLSClientConfig: tlsConf} + r.getLRPDBState(ctx, req, lrpdb) - httpclient := &http.Client{Transport: tr} + lrpdb.Status.Msg = "plug:[op. completed]" + lrpdb.Status.PDBBitMask = Bis(lrpdb.Status.PDBBitMask, PDBCRT) /* Set the creation flag */ + lrpdb.Status.PDBBitMask = Bis(lrpdb.Status.PDBBitMask, PDBOPN) /* Set the creation flag */ + lrpdb.Status.PDBBitMaskStr = Bitmaskprint(lrpdb.Status.PDBBitMask) + r.UpdateStatus(ctx, req, lrpdb) - log.Info("Issuing REST call", "URL", url, "Action", action) + log.Info("Successfully plugged LRPDB", "LRPDB Name", lrpdb.Spec.LRPDBName) + return nil +} - webUser, err := r.getEncriptedSecret(ctx, req, lrpdb, lrpdb.Spec.WebLrpdbServerUser.Secret.SecretName, lrpdb.Spec.WebLrpdbServerUser.Secret.Key, lrpdb.Spec.LRPDBPriKey.Secret.SecretName, lrpdb.Spec.LRPDBPriKey.Secret.Key) - if err != nil { - log.Error(err, "Unable to get webuser account name ") - return "", err - } +/* +********************************************************************* + - UNPLUG PDB - webUserPwd, err := r.getEncriptedSecret(ctx, req, lrpdb, lrpdb.Spec.WebLrpdbServerPwd.Secret.SecretName, lrpdb.Spec.WebLrpdbServerPwd.Secret.Key, lrpdb.Spec.LRPDBPriKey.Secret.SecretName, lrpdb.Spec.LRPDBPriKey.Secret.Key) - if err != nil { - log.Error(err, "Unable to get webuser account password ") - return "", err - } +********************************************************************* +*/ - var httpreq *http.Request - if action == "GET" { - httpreq, err = http.NewRequest(action, url, nil) - } else { - jsonValue, _ := json.Marshal(payload) - httpreq, err = http.NewRequest(action, url, bytes.NewBuffer(jsonValue)) - } +func (r *LRPDBReconciler) UnplugLRPDB(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) error { - if err != nil { - log.Info("Unable to create HTTP Request for LRPDB : "+lrpdb.Name, "err", err.Error()) - return "", err - } + log := r.Log.WithValues("unplugLRPDB", req.NamespacedName) + globalsqlcode = 0 - httpreq.Header.Add("Accept", "application/json") - httpreq.Header.Add("Content-Type", "application/json") - httpreq.SetBasicAuth(webUser, webUserPwd) + var err error + //var tde_Password string + //var tde_Secret string - resp, err := httpclient.Do(httpreq) + lrest, err := r.getLRESTResource(ctx, req, lrpdb) if err != nil { - errmsg := err.Error() - log.Error(err, "Failed - Could not connect to LREST Pod", "err", err.Error()) - lrpdb.Status.Msg = "Error: Could not connect to LREST Pod" - r.Recorder.Eventf(lrpdb, corev1.EventTypeWarning, "LRESTError", errmsg) - return "", err + return err } - r.Recorder.Eventf(lrpdb, corev1.EventTypeWarning, "Done", lrpdb.Spec.CDBResName) - if resp.StatusCode != http.StatusOK { - bb, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode == 404 { - lrpdb.Status.ConnString = "" - lrpdb.Status.Msg = lrpdb.Spec.LRPDBName + " not found" + values := map[string]string{ + "method": "UNPLUG", + "xmlFileName": lrpdb.Spec.XMLFileName, + "getScript": strconv.FormatBool(*(lrpdb.Spec.GetScript))} - } else { - if flood_control == false { - lrpdb.Status.Msg = "LREST Error - HTTP Status Code:" + strconv.Itoa(resp.StatusCode) + /* + if *(lrpdb.Spec.LTDEExport) { + tde_Password, err = r.getSecret(ctx, req, lrpdb, lrpdb.Spec.LTDEPassword.Secret.SecretName, lrpdb.Spec.LTDEPassword.Secret.Key) + if err != nil { + return err + } + tde_Secret, err = r.getSecret(ctx, req, lrpdb, lrpdb.Spec.LTDESecret.Secret.SecretName, lrpdb.Spec.LTDESecret.Secret.Key) + if err != nil { + return err } - } - if flood_control == false { - log.Info("LREST Error - HTTP Status Code :"+strconv.Itoa(resp.StatusCode), "Err", string(bb)) + tde_Secret = tde_Secret[:len(tde_Secret)-1] + tde_Password = tde_Secret[:len(tde_Password)-1] + values["tde_Password"] = tde_Password + values["tdeKeystorePath"] = lrpdb.Spec.LTDEKeystorePath + values["tde_Secret"] = tde_Secret + values["tdeExport"] = strconv.FormatBool(*(lrpdb.Spec.LTDEExport)) } + */ - var apiErr LRESTError - json.Unmarshal([]byte(bb), &apiErr) - if flood_control == false { - r.Recorder.Eventf(lrpdb, corev1.EventTypeWarning, "LRESTError", "Failed: %s", apiErr.Message) - } - fmt.Printf("\n================== APIERR ======================\n") - fmt.Printf("%+v \n", apiErr) - fmt.Printf(string(bb)) - fmt.Printf("URL=%s\n", url) - fmt.Printf("resp.StatusCode=%s\n", strconv.Itoa(resp.StatusCode)) - fmt.Printf("\n================== APIERR ======================\n") - flood_control = true - return "", errors.New("LREST Error") + lrpdb.Status.Msg = "unplug:[op. in progress]" + lrpdb.Status.PDBBitMask = Bis(lrpdb.Status.PDBBitMask, PDBUPL) + lrpdb.Status.PDBBitMaskStr = Bitmaskprint(lrpdb.Status.PDBBitMask) + + if Bit(lrpdb.Status.PDBBitMask, PDBPLG) { /*database already plugged in the past */ + lrpdb.Status.PDBBitMask = Bid(lrpdb.Status.PDBBitMask, PDBPLG) + lrpdb.Status.PDBBitMaskStr = Bitmaskprint(lrpdb.Status.PDBBitMask) } - flood_control = false - defer resp.Body.Close() + r.UpdateStatus(ctx, req, lrpdb) + url := r.BaseUrl(ctx, req, lrpdb, lrest) + lrpdb.Spec.LRPDBName - bodyBytes, err := ioutil.ReadAll(resp.Body) + log.Info("CallAPI(url)", "url", url) + respData, err := NewCallAPISQL(r, ctx, req, lrpdb, url, values, "POST") if err != nil { - fmt.Print(err.Error()) + log.Error(err, "Failure NewCallAPISQL( "+url+")", "err", err.Error()) + return err } - respData := string(bodyBytes) - fmt.Print("CALL API return msg.....:") - fmt.Println(string(bodyBytes)) - var apiResponse restSQLCollection - json.Unmarshal([]byte(bodyBytes), &apiResponse) - fmt.Printf("===> %#v\n", apiResponse) - fmt.Printf("===> %+v\n", apiResponse) + r.GetSqlCode(respData, &(lrpdb.Status.SqlCode)) + r.UpdateStatus(ctx, req, lrpdb) + + if lrpdb.Status.SqlCode != 0 { + globalsqlcode = lrpdb.Status.SqlCode + lrpdb.Status.PDBBitMask = Bis(lrpdb.Status.PDBBitMask, PDBUPE) /* Upplug error */ + lrpdb.Status.PDBBitMask = Bid(lrpdb.Status.PDBBitMask, PDBUPL) /* Remove unplug flag */ + lrpdb.Status.PDBBitMaskStr = Bitmaskprint(lrpdb.Status.PDBBitMask) + oer := fmt.Sprintf("ORA-%d", lrpdb.Status.SqlCode) /* Print the oracle error */ + lrpdb.Status.Msg = "close:[" + oer + "]" + r.UpdateStatus(ctx, req, lrpdb) + return errors.New(oer) - errFound := false - for _, sqlItem := range apiResponse.Items { - if sqlItem.ErrorDetails != "" { - log.Info("LREST Error - Oracle Error Code :" + strconv.Itoa(sqlItem.ErrorCode)) - if !errFound { - lrpdb.Status.Msg = sqlItem.ErrorDetails - } - r.Recorder.Eventf(lrpdb, corev1.EventTypeWarning, "OraError", "%s", sqlItem.ErrorDetails) - errFound = true - } } - if errFound { - return "", errors.New("Oracle Error") + /*... CRD is going to be delete... loging message in the logfile */ + lrpdb.Status.Msg = "unplug:[op. completed]" + r.UpdateStatus(ctx, req, lrpdb) + log.Info("unplug:[op. completed]") + + if controllerutil.ContainsFinalizer(lrpdb, LRPDBFinalizer) { + log.Info("Removing finalizer") + controllerutil.RemoveFinalizer(lrpdb, LRPDBFinalizer) + err = r.Update(ctx, lrpdb) + if err != nil { + log.Info("Could not remove finalizer", "err", err.Error()) + return err + } + lrpdb.Status.Status = true + err = r.Delete(context.Background(), lrpdb, client.GracePeriodSeconds(1)) + if err != nil { + log.Info("Could not delete LRPDB resource", "err", err.Error()) + return err + } } - return respData, nil + r.Recorder.Eventf(lrpdb, corev1.EventTypeNormal, "Unplugged", "LRPDB '%s' unplugged successfully", lrpdb.Spec.LRPDBName) + globalsqlcode = 0 + log.Info("Successfully unplugged LRPDB resource") + return nil } /* -************************************************ - - Create a LRPDB +********************************************************************* + - OPEN PDB -*********************************************** +********************************************************************* */ -func (r *LRPDBReconciler) createLRPDB(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) error { - - log := r.Log.WithValues("createLRPDB", req.NamespacedName) +func (r *LRPDBReconciler) OpenLRPDB(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) error { - var err error - var tde_Password string - var tde_Secret string + log := r.Log.WithValues("OpenLRPDB", req.NamespacedName) + r.Recorder.Eventf(lrpdb, corev1.EventTypeNormal, "Modify", "Info:'%s %s %s' ", lrpdb.Spec.LRPDBName, lrpdb.Spec.LRPDBState, lrpdb.Status.ModifyOption) - log.Info("call getLRESTResource \n") lrest, err := r.getLRESTResource(ctx, req, lrpdb) if err != nil { + log.Info("Failure: Cannot get lrest info") return err } - lrpdbAdminName, err := r.getEncriptedSecret(ctx, req, lrpdb, lrpdb.Spec.AdminpdbUser.Secret.SecretName, lrpdb.Spec.AdminpdbUser.Secret.Key, lrpdb.Spec.LRPDBPriKey.Secret.SecretName, lrpdb.Spec.LRPDBPriKey.Secret.Key) - if err != nil { - log.Error(err, "Unable to find pdb admin user ") - return err - } - - lrpdbAdminPwd, err := r.getEncriptedSecret(ctx, req, lrpdb, lrpdb.Spec.AdminpdbPass.Secret.SecretName, lrpdb.Spec.AdminpdbPass.Secret.Key, lrpdb.Spec.LRPDBPriKey.Secret.SecretName, lrpdb.Spec.LRPDBPriKey.Secret.Key) + values := map[string]string{} + values = map[string]string{ + "state": lrpdb.Spec.LRPDBState, + "modifyOption": lrpdb.Spec.ModifyOption, + "modifyOption2": lrpdb.Spec.ModifyOption2, + "getScript": strconv.FormatBool(*(lrpdb.Spec.GetScript))} - if err != nil { - log.Error(err, "Unable to find pdb admin password ") - return err + if lrpdb.Spec.LRPDBState == "OPEN" || lrpdb.Spec.LRPDBState == "CLOSE" { + log.Info("MODIFY LRPDB", "lrpdb.Spec.LRPDBState=", lrpdb.Spec.LRPDBState, "lrpdb.Spec.ModifyOption=", lrpdb.Spec.ModifyOption) + log.Info("LRPDB STATUS OPENMODE", "lrpdb.Status.OpenMode=", lrpdb.Status.OpenMode) } - err = r.getLRPDBState(ctx, req, lrpdb) - if err != nil { - if apierrors.IsNotFound(err) { - log.Info("Check LRPDB not existence completed", "LRPDB Name", lrpdb.Spec.LRPDBName) - } + lrpdbName := lrpdb.Spec.LRPDBName + url := r.BaseUrl(ctx, req, lrpdb, lrest) + lrpdbName - } else { + lrpdb.Status.Msg = "open:[op in progress]" + r.UpdateStatus(ctx, req, lrpdb) - lrpdb.Status.Phase = lrpdbPhaseFail - lrpdb.Status.Msg = "PDB " + lrpdb.Spec.LRPDBName + " already exists " - if err := r.Status().Update(ctx, lrpdb); err != nil { - log.Error(err, "Failed to update status for :"+lrpdb.Name, "err", err.Error()) - } - log.Info("Database already exists ", "LRPDB Name", lrpdb.Spec.LRPDBName) - err := fmt.Errorf("%v", 65012) + respData, err := NewCallAPISQL(r, ctx, req, lrpdb, url, values, "POST") + if err != nil { + log.Error(err, "Failure NewCallAPISQL( "+url+")", "err", err.Error()) return err } - values := map[string]string{ - "method": "CREATE", - "pdb_name": lrpdb.Spec.LRPDBName, - "adminName": lrpdbAdminName, - "adminPwd": lrpdbAdminPwd, - "fileNameConversions": lrpdb.Spec.FileNameConversions, - "reuseTempFile": strconv.FormatBool(*(lrpdb.Spec.ReuseTempFile)), - "unlimitedStorage": strconv.FormatBool(*(lrpdb.Spec.UnlimitedStorage)), - "totalSize": lrpdb.Spec.TotalSize, - "tempSize": lrpdb.Spec.TempSize, - "getScript": strconv.FormatBool(*(lrpdb.Spec.GetScript))} - - fmt.Printf("===== PAYLOAD ===\n") - fmt.Print(" method ", values["method"], "\n") - fmt.Print(" pdb_name ", values["pdb_name"], "\n") - fmt.Print(" adminName ", values["adminName"], "\n") - fmt.Print(" adminPwd --------------\n") - fmt.Print(" fileNameConversions ", values["fileNameConversions"], "\n") - fmt.Print(" unlimitedStorage ", values["unlimitedStorage"], "\n") - fmt.Print(" reuseTempFile ", values["reuseTempFile"], "\n") - fmt.Print(" tempSize ", values["tempSize"], "\n") - fmt.Print(" totalSize ", values["totalSize"], "\n") - fmt.Print(" getScript ", values["getScript"], "\n") - - if *(lrpdb.Spec.LTDEImport) { - tde_Password, err = r.getSecret(ctx, req, lrpdb, lrpdb.Spec.LTDEPassword.Secret.SecretName, lrpdb.Spec.LTDEPassword.Secret.Key) - if err != nil { - return err - } - tde_Secret, err = r.getSecret(ctx, req, lrpdb, lrpdb.Spec.LTDESecret.Secret.SecretName, lrpdb.Spec.LTDESecret.Secret.Key) - if err != nil { - return err - } - - tde_Secret = tde_Secret[:len(tde_Secret)-1] - tde_Password = tde_Secret[:len(tde_Password)-1] - values["tde_Password"] = tde_Password - values["tdeKeystorePath"] = lrpdb.Spec.LTDEKeystorePath - values["tde_Secret"] = tde_Secret + r.GetSqlCode(respData, &(lrpdb.Status.SqlCode)) + /* if sqlcode is zero then unset the closebit */ + if lrpdb.Status.SqlCode == 0 { + lrpdb.Status.PDBBitMask = Bid(lrpdb.Status.PDBBitMask, PDBCLS) + lrpdb.Status.PDBBitMaskStr = Bitmaskprint(lrpdb.Status.PDBBitMask) } - //url := "https://" + lrpdb.Spec.CDBResName + "-lrest:" + strconv.Itoa(lrest.Spec.LRESTPort) + "/database/pdbs/" - url := r.BaseUrl(ctx, req, lrpdb, lrest) - fmt.Print("============================================================\n") - fmt.Print(url) - fmt.Print("\n============================================================\n") - lrpdb.Status.TotalSize = lrpdb.Spec.TotalSize - lrpdb.Status.Phase = lrpdbPhaseCreate - lrpdb.Status.Msg = "Waiting for LRPDB to be created" - - if err := r.Status().Update(ctx, lrpdb); err != nil { - log.Error(err, "Failed to update status for :"+lrpdb.Name, "err", err.Error()) - } + r.UpdateStatus(ctx, req, lrpdb) - respData, err := NewCallLAPI(r, ctx, req, lrpdb, url, values, "POST") - if err != nil { - log.Error(err, "Failure NewCallLAPI( "+url+")", "err", err.Error()) - return err + /* Return Error if sqlcode != */ + if lrpdb.Status.SqlCode != 0 { + lrpdb.Status.PDBBitMask = Bis(lrpdb.Status.PDBBitMask, PDBOPE) + lrpdb.Status.PDBBitMaskStr = Bitmaskprint(lrpdb.Status.PDBBitMask) + oer := fmt.Sprintf("ORA-%d", lrpdb.Status.SqlCode) + lrpdb.Status.Msg = "open:[" + oer + "]" + r.UpdateStatus(ctx, req, lrpdb) + return errors.New(oer) } - r.GetSqlCode(respData, &(lrpdb.Status.SqlCode)) globalsqlcode = lrpdb.Status.SqlCode - if lrpdb.Status.SqlCode != 0 { - err := fmt.Errorf("%v", lrpdb.Status.SqlCode) - return err - } + r.getLRPDBState(ctx, req, lrpdb) - r.Recorder.Eventf(lrpdb, corev1.EventTypeNormal, - "Created", "LRPDB '%s' created successfully", lrpdb.Spec.LRPDBName) + if lrpdb.Spec.LRPDBState == "OPEN" || lrpdb.Spec.LRPDBState == "CLOSE" { + r.Recorder.Eventf(lrpdb, corev1.EventTypeNormal, "Modified", " '%s' modified successfully '%s'", lrpdb.Spec.LRPDBName, lrpdb.Spec.LRPDBState) + } if lrest.Spec.DBServer != "" { - lrpdb.Status.ConnString = - lrest.Spec.DBServer + ":" + strconv.Itoa(lrest.Spec.DBPort) + "/" + lrpdb.Spec.LRPDBName + lrpdb.Status.ConnString = lrest.Spec.DBServer + ":" + strconv.Itoa(lrest.Spec.DBPort) + "/" + lrpdb.Spec.LRPDBName } else { - log.Info("Parsing connectstring") lrpdb.Status.ConnString = lrest.Spec.DBTnsurl parseTnsAlias(&(lrpdb.Status.ConnString), &(lrpdb.Spec.LRPDBName)) - } - assertiveLpdbDeletion = lrpdb.Spec.AssertiveLrpdbDeletion - if lrpdb.Spec.AssertiveLrpdbDeletion == true { - r.Recorder.Eventf(lrpdb, corev1.EventTypeNormal, "Created", "PDB '%s' assertive pdb deletion turned on", lrpdb.Spec.LRPDBName) } - r.getLRPDBState(ctx, req, lrpdb) - log.Info("Created LRPDB Resource", "LRPDB Name", lrpdb.Spec.LRPDBName) + log.Info("Successfully modified LRPDB state", "LRPDB Name", lrpdb.Spec.LRPDBName) - if bit(lrpdb.Status.Bitstat, MPINIT) == false { - r.InitConfigMap(ctx, req, lrpdb) - Cardinality, _ := r.ApplyConfigMap(ctx, req, lrpdb) - log.Info("Config Map Cardinality " + strconv.Itoa(int(Cardinality))) - } + /* After database openining we reapply the config map if warning is present */ + if lrpdb.Spec.LRPDBState == "OPEN" { + if Bit(lrpdb.Status.CmBitstat, MPWARN|MPINIT) { + log.Info("re-apply config map") + r.ApplyConfigMap(ctx, req, lrpdb) - if err := r.Status().Update(ctx, lrpdb); err != nil { - log.Error(err, "Failed to update status for :"+lrpdb.Name, "err", err.Error()) + } } + lrpdb.Status.Msg = "open:[op. completed]" + lrpdb.Status.PDBBitMask = Bis(lrpdb.Status.PDBBitMask, PDBOPN) + lrpdb.Status.PDBBitMaskStr = Bitmaskprint(lrpdb.Status.PDBBitMask) + r.UpdateStatus(ctx, req, lrpdb) return nil } /* -************************************************ - - Clone a LRPDB - /*********************************************** -*/ -func (r *LRPDBReconciler) cloneLRPDB(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) error { - - if lrpdb.Spec.LRPDBName == lrpdb.Spec.SrcLRPDBName { - return nil - } +********************************************************************* + - CLOSE PDB - log := r.Log.WithValues("cloneLRPDB", req.NamespacedName) +********************************************************************* +*/ +func (r *LRPDBReconciler) CloseLRPDB(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) error { - globalsqlcode = 0 - var err error + log := r.Log.WithValues("CloseLRPDB", req.NamespacedName) + r.Recorder.Eventf(lrpdb, corev1.EventTypeNormal, "Close", "Info:'%s %s %s' ", lrpdb.Spec.LRPDBName, lrpdb.Spec.LRPDBState, lrpdb.Status.ModifyOption) lrest, err := r.getLRESTResource(ctx, req, lrpdb) if err != nil { + log.Info("Failure: Cannot get lrest info") return err } - /* Prevent cloning an existing lrpdb */ - err = r.getLRPDBState(ctx, req, lrpdb) - if err != nil { - if apierrors.IsNotFound(err) { - log.Info("Check LRPDB not existence completed", "LRPDB Name", lrpdb.Spec.LRPDBName) - } - - } else { - log.Info("Database already exists ", "LRPDB Name", lrpdb.Spec.LRPDBName) - return nil - } - - values := map[string]string{ - "method": "CLONE", - "pdb_name": lrpdb.Spec.LRPDBName, - "srcPdbName": lrpdb.Spec.SrcLRPDBName, - "reuseTempFile": strconv.FormatBool(*(lrpdb.Spec.ReuseTempFile)), - "unlimitedStorage": strconv.FormatBool(*(lrpdb.Spec.UnlimitedStorage)), - "getScript": strconv.FormatBool(*(lrpdb.Spec.GetScript))} + values := map[string]string{} + values = map[string]string{ + "state": lrpdb.Spec.LRPDBState, + "modifyOption": lrpdb.Spec.ModifyOption, + "modifyOption2": lrpdb.Spec.ModifyOption2, + "getScript": strconv.FormatBool(*(lrpdb.Spec.GetScript))} - //* check the existence of lrpdb.Spec.SrcLRPDBName // - var allErrs field.ErrorList - pdbCounter, _ := r.checkPDBforCloninig(ctx, req, lrpdb, lrpdb.Spec.SrcLRPDBName) - if pdbCounter == 0 { - log.Info("target pdb " + lrpdb.Spec.SrcLRPDBName + " does not exists or is not open") - allErrs = append(allErrs, field.NotFound(field.NewPath("Spec").Child("LRPDBName"), " "+lrpdb.Spec.LRPDBName+" does not exist : failure")) - r.Delete(context.Background(), lrpdb, client.GracePeriodSeconds(1)) - return nil + if lrpdb.Spec.LRPDBState == "OPEN" || lrpdb.Spec.LRPDBState == "CLOSE" { + log.Info("MODIFY LRPDB", "lrpdb.Spec.LRPDBState=", lrpdb.Spec.LRPDBState, "lrpdb.Spec.ModifyOption=", lrpdb.Spec.ModifyOption) + log.Info("LRPDB STATUS OPENMODE", "lrpdb.Status.OpenMode=", lrpdb.Status.OpenMode) } - if lrpdb.Spec.SparseClonePath != "" { - values["sparseClonePath"] = lrpdb.Spec.SparseClonePath - } - if lrpdb.Spec.FileNameConversions != "" { - values["fileNameConversions"] = lrpdb.Spec.FileNameConversions - } - if lrpdb.Spec.TotalSize != "" { - values["totalSize"] = lrpdb.Spec.TotalSize - } - if lrpdb.Spec.TempSize != "" { - values["tempSize"] = lrpdb.Spec.TempSize - } + lrpdbName := lrpdb.Spec.LRPDBName + url := r.BaseUrl(ctx, req, lrpdb, lrest) + lrpdbName - url := r.BaseUrl(ctx, req, lrpdb, lrest) + lrpdb.Spec.LRPDBName + "/" + lrpdb.Status.Msg = "close:[op. in progress]" + r.UpdateStatus(ctx, req, lrpdb) - lrpdb.Status.Phase = lrpdbPhaseClone - lrpdb.Status.Msg = "Waiting for LRPDB to be cloned" - if err := r.Status().Update(ctx, lrpdb); err != nil { - log.Error(err, "Failed to update status for :"+lrpdb.Name, "err", err.Error()) - } - respData, err := NewCallLAPI(r, ctx, req, lrpdb, url, values, "POST") + respData, err := NewCallAPISQL(r, ctx, req, lrpdb, url, values, "POST") if err != nil { - log.Error(err, "Failure NewCallLAPI( "+url+")", "err", err.Error()) + log.Error(err, "Failure NewCallAPISQL( "+url+")", "err", err.Error()) return err } r.GetSqlCode(respData, &(lrpdb.Status.SqlCode)) - globalsqlcode = lrpdb.Status.SqlCode + /* if sqlcode is zero then unset the openbit */ + if lrpdb.Status.SqlCode == 0 { + lrpdb.Status.PDBBitMask = Bid(lrpdb.Status.PDBBitMask, PDBOPN) + lrpdb.Status.PDBBitMaskStr = Bitmaskprint(lrpdb.Status.PDBBitMask) + } + r.UpdateStatus(ctx, req, lrpdb) + + /* Return Error if sqlcode != */ if lrpdb.Status.SqlCode != 0 { - errclone := errors.New("Cannot clone database: ora-" + strconv.Itoa(lrpdb.Status.SqlCode)) - log.Info("Cannot clone database ora-" + strconv.Itoa(lrpdb.Status.SqlCode)) - lrpdb.Status.Msg = lrpdb.Spec.SrcLRPDBName + " is open in mount cannot clone " - if err := r.Status().Update(ctx, lrpdb); err != nil { - log.Error(err, "Failed to update status for :"+lrpdb.Name, "err", err.Error()) - } - return errclone + lrpdb.Status.PDBBitMask = Bis(lrpdb.Status.PDBBitMask, PDBCLE) + lrpdb.Status.PDBBitMaskStr = Bitmaskprint(lrpdb.Status.PDBBitMask) + oer := fmt.Sprintf("ORA-%d", lrpdb.Status.SqlCode) + lrpdb.Status.Msg = "close:[" + oer + "]" + r.UpdateStatus(ctx, req, lrpdb) + return errors.New(oer) } - r.Recorder.Eventf(lrpdb, corev1.EventTypeNormal, "Created", "LRPDB '%s' cloned successfully", lrpdb.Spec.LRPDBName) + globalsqlcode = lrpdb.Status.SqlCode + r.getLRPDBState(ctx, req, lrpdb) + + if lrpdb.Spec.LRPDBState == "OPEN" || lrpdb.Spec.LRPDBState == "CLOSE" { + r.Recorder.Eventf(lrpdb, corev1.EventTypeNormal, "Modified", " '%s' modified successfully '%s'", lrpdb.Spec.LRPDBName, lrpdb.Spec.LRPDBState) + } if lrest.Spec.DBServer != "" { lrpdb.Status.ConnString = lrest.Spec.DBServer + ":" + strconv.Itoa(lrest.Spec.DBPort) + "/" + lrpdb.Spec.LRPDBName @@ -998,1133 +858,2031 @@ func (r *LRPDBReconciler) cloneLRPDB(ctx context.Context, req ctrl.Request, lrpd parseTnsAlias(&(lrpdb.Status.ConnString), &(lrpdb.Spec.LRPDBName)) } - assertiveLpdbDeletion = lrpdb.Spec.AssertiveLrpdbDeletion - if lrpdb.Spec.AssertiveLrpdbDeletion == true { - r.Recorder.Eventf(lrpdb, corev1.EventTypeNormal, "Clone", "PDB '%s' assertive pdb deletion turned on", lrpdb.Spec.LRPDBName) - } - log.Info("Cloned LRPDB successfully", "Source LRPDB Name", lrpdb.Spec.SrcLRPDBName, "Clone LRPDB Name", lrpdb.Spec.LRPDBName) - r.getLRPDBState(ctx, req, lrpdb) + lrpdb.Status.Msg = "close:[op. completed]" + log.Info("Successfully modified LRPDB state", "LRPDB Name", lrpdb.Spec.LRPDBName) + lrpdb.Status.PDBBitMask = Bis(lrpdb.Status.PDBBitMask, PDBCLS) + lrpdb.Status.PDBBitMaskStr = Bitmaskprint(lrpdb.Status.PDBBitMask) + r.UpdateStatus(ctx, req, lrpdb) + return nil } /* -************************************************ - - Plug a LRPDB +********************************************************************* + - DELETE PDB - IMPERATIVE APPROAC -*********************************************** +********************************************************************* */ -func (r *LRPDBReconciler) plugLRPDB(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) error { - - log := r.Log.WithValues("plugLRPDB", req.NamespacedName) - globalsqlcode = 0 +func (r *LRPDBReconciler) DeleteLRPDB(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) error { + log := r.Log.WithValues("deleteLRPDB", req.NamespacedName) var err error - var tde_Password string - var tde_Secret string lrest, err := r.getLRESTResource(ctx, req, lrpdb) if err != nil { + log.Info("Failure: Cannot get lrest info") return err } - values := map[string]string{ - "method": "PLUG", - "xmlFileName": lrpdb.Spec.XMLFileName, - "pdb_name": lrpdb.Spec.LRPDBName, - "sourceFileNameConversions": lrpdb.Spec.SourceFileNameConversions, - "copyAction": lrpdb.Spec.CopyAction, - "fileNameConversions": lrpdb.Spec.FileNameConversions, - "unlimitedStorage": strconv.FormatBool(*(lrpdb.Spec.UnlimitedStorage)), - "reuseTempFile": strconv.FormatBool(*(lrpdb.Spec.ReuseTempFile)), - "totalSize": lrpdb.Spec.TotalSize, - "tempSize": lrpdb.Spec.TempSize, - "getScript": strconv.FormatBool(*(lrpdb.Spec.GetScript))} + if lrpdb.Spec.ImperativeLrpdbDeletion == true { + /* Close the pdb if it's open */ + if Bit(lrpdb.Status.PDBBitMask, PDBOPN) == true { + valuesclose := map[string]string{ + "state": "CLOSE", + "modifyOption": "IMMEDIATE", + "getScript": "FALSE"} + lrpdbName := lrpdb.Spec.LRPDBName + url := r.BaseUrl(ctx, req, lrpdb, lrest) + lrpdbName + respData, err := NewCallAPISQL(r, ctx, req, lrpdb, url, valuesclose, "POST") + r.GetSqlCode(respData, &(lrpdb.Status.SqlCode)) + if lrpdb.Status.SqlCode != 0 { + oer := fmt.Sprintf("ORA-%d", lrpdb.Status.SqlCode) + lrpdb.Status.Msg = "close:[" + oer + "]" + r.UpdateStatus(ctx, req, lrpdb) + } + if err != nil { + log.Info("Warning error closing lrpdb continue anyway") + + } + lrpdb.Status.PDBBitMask = Bid(lrpdb.Status.PDBBitMask, PDBOPN) + lrpdb.Status.PDBBitMask = Bis(lrpdb.Status.PDBBitMask, PDBCLS) + lrpdb.Status.PDBBitMaskStr = Bitmaskprint(lrpdb.Status.PDBBitMask) + r.UpdateStatus(ctx, req, lrpdb) - if *(lrpdb.Spec.LTDEImport) { - tde_Password, err = r.getSecret(ctx, req, lrpdb, lrpdb.Spec.LTDEPassword.Secret.SecretName, lrpdb.Spec.LTDEPassword.Secret.Key) - if err != nil { - return err - } - tde_Secret, err = r.getSecret(ctx, req, lrpdb, lrpdb.Spec.LTDESecret.Secret.SecretName, lrpdb.Spec.LTDESecret.Secret.Key) - if err != nil { - return err } - tde_Secret = tde_Secret[:len(tde_Secret)-1] - tde_Password = tde_Secret[:len(tde_Password)-1] - values["tde_Password"] = tde_Password - values["tdeKeystorePath"] = lrpdb.Spec.LTDEKeystorePath - values["tde_Secret"] = tde_Secret - values["tdeImport"] = strconv.FormatBool(*(lrpdb.Spec.LTDEImport)) - } - if *(lrpdb.Spec.AsClone) { - values["asClone"] = strconv.FormatBool(*(lrpdb.Spec.AsClone)) - } + values := map[string]string{ + "action": "INCLUDING", + "getScript": strconv.FormatBool(*(lrpdb.Spec.GetScript))} - url := r.BaseUrl(ctx, req, lrpdb, lrest) + if lrpdb.Spec.DropAction != "" { + values["action"] = lrpdb.Spec.DropAction + } - lrpdb.Status.TotalSize = lrpdb.Spec.TotalSize - lrpdb.Status.Phase = lrpdbPhasePlug - lrpdb.Status.Msg = "Waiting for LRPDB to be plugged" - if err := r.Status().Update(ctx, lrpdb); err != nil { - log.Error(err, "Failed to update status for :"+lrpdb.Name, "err", err.Error()) - } + lrpdbName := lrpdb.Spec.LRPDBName + url := r.BaseUrl(ctx, req, lrpdb, lrest) + lrpdbName - respData, err := NewCallLAPI(r, ctx, req, lrpdb, url, values, "POST") - if err != nil { - log.Error(err, "Failure NewCallLAPI( "+url+")", "err", err.Error()) - return err - } + respData, err := NewCallAPISQL(r, ctx, req, lrpdb, url, values, "DELETE") + if err != nil { + log.Error(err, "Failure NewCallAPISQL( "+url+")", "err", err.Error()) + return err + } - r.GetSqlCode(respData, &(lrpdb.Status.SqlCode)) - globalsqlcode = lrpdb.Status.SqlCode + r.GetSqlCode(respData, &(lrpdb.Status.SqlCode)) + globalsqlcode = lrpdb.Status.SqlCode + if lrpdb.Status.SqlCode != 0 { + lrpdb.Status.PDBBitMask = Bis(lrpdb.Status.PDBBitMask, FNALAE) + lrpdb.Status.PDBBitMaskStr = Bitmaskprint(lrpdb.Status.PDBBitMask) + oer := fmt.Sprintf("ORA-%d", lrpdb.Status.SqlCode) + lrpdb.Status.Msg = "delete:[" + oer + "]" + r.UpdateStatus(ctx, req, lrpdb) + return err + } else { + lrpdb.Status.PDBBitMask = Bis(lrpdb.Status.PDBBitMask, PDBDIC) + lrpdb.Status.PDBBitMaskStr = Bitmaskprint(lrpdb.Status.PDBBitMask) + r.UpdateStatus(ctx, req, lrpdb) + } - if lrpdb.Status.SqlCode != 0 { - log.Info("Plug database failure........:" + strconv.Itoa(lrpdb.Status.SqlCode)) - err = fmt.Errorf("%v", lrpdb.Status.SqlCode) - return err } - r.Recorder.Eventf(lrpdb, corev1.EventTypeNormal, "Created", "LRPDB '%s' plugged successfully", lrpdb.Spec.LRPDBName) - - if lrest.Spec.DBServer != "" { - lrpdb.Status.ConnString = lrest.Spec.DBServer + ":" + strconv.Itoa(lrest.Spec.DBPort) + "/" + lrpdb.Spec.LRPDBName - } else { - log.Info("Parsing connectstring") - lrpdb.Status.ConnString = lrest.Spec.DBTnsurl - parseTnsAlias(&(lrpdb.Status.ConnString), &(lrpdb.Spec.LRPDBName)) - } + log.Info("Successfully dropped LRPDB", "LRPDB Name", lrpdb.Spec.LRPDBName) - assertiveLpdbDeletion = lrpdb.Spec.AssertiveLrpdbDeletion - if lrpdb.Spec.AssertiveLrpdbDeletion == true { - r.Recorder.Eventf(lrpdb, corev1.EventTypeNormal, "Plug", "PDB '%s' assertive pdb deletion turned on", lrpdb.Spec.LRPDBName) + controllerutil.RemoveFinalizer(lrpdb, LRPDBFinalizer) + if err := r.Update(ctx, lrpdb); err != nil { + log.Info("Cannot remove finalizer") + return err } - log.Info("Successfully plugged LRPDB", "LRPDB Name", lrpdb.Spec.LRPDBName) - r.getLRPDBState(ctx, req, lrpdb) return nil } /* -************************************************ - - Unplug a LRPDB +********************************************************************* + - DELETE PDB - DECLARATIVE APPROACH -*********************************************** +********************************************************************* */ -func (r *LRPDBReconciler) unplugLRPDB(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) error { - log := r.Log.WithValues("unplugLRPDB", req.NamespacedName) - globalsqlcode = 0 +func (r *LRPDBReconciler) DeleteLRPDBDeclarative(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) error { + log := r.Log.WithValues("deleteLRPDBDeclaratve", req.NamespacedName) var err error - var tde_Password string - var tde_Secret string lrest, err := r.getLRESTResource(ctx, req, lrpdb) if err != nil { + log.Info("Failure: Cannot get lrest info") return err } - values := map[string]string{ - "method": "UNPLUG", - "xmlFileName": lrpdb.Spec.XMLFileName, - "getScript": strconv.FormatBool(*(lrpdb.Spec.GetScript))} + if lrpdb.Spec.ImperativeLrpdbDeletion == true { + /* Close the pdb if it's open */ + if Bit(lrpdb.Status.PDBBitMask, PDBOPN) == true { + valuesclose := map[string]string{ + "state": "CLOSE", + "modifyOption": "IMMEDIATE", + "getScript": "FALSE"} + lrpdbName := lrpdb.Spec.LRPDBName + url := r.BaseUrl(ctx, req, lrpdb, lrest) + lrpdbName + respData, err := NewCallAPISQL(r, ctx, req, lrpdb, url, valuesclose, "POST") + r.GetSqlCode(respData, &(lrpdb.Status.SqlCode)) + if lrpdb.Status.SqlCode != 0 { + oer := fmt.Sprintf("ORA-%d", lrpdb.Status.SqlCode) + lrpdb.Status.Msg = "close:[" + oer + "]" + r.UpdateStatus(ctx, req, lrpdb) + } + if err != nil { + log.Info("Warning error closing lrpdb continue anyway") + + } + lrpdb.Status.PDBBitMask = Bid(lrpdb.Status.PDBBitMask, PDBOPN) + lrpdb.Status.PDBBitMask = Bis(lrpdb.Status.PDBBitMask, PDBCLS) + lrpdb.Status.PDBBitMaskStr = Bitmaskprint(lrpdb.Status.PDBBitMask) + r.UpdateStatus(ctx, req, lrpdb) - if *(lrpdb.Spec.LTDEExport) { - // Get the TDE Password - tde_Password, err = r.getSecret(ctx, req, lrpdb, lrpdb.Spec.LTDEPassword.Secret.SecretName, lrpdb.Spec.LTDEPassword.Secret.Key) - if err != nil { - return err - } - tde_Secret, err = r.getSecret(ctx, req, lrpdb, lrpdb.Spec.LTDESecret.Secret.SecretName, lrpdb.Spec.LTDESecret.Secret.Key) - if err != nil { - return err } - tde_Secret = tde_Secret[:len(tde_Secret)-1] - tde_Password = tde_Secret[:len(tde_Password)-1] - values["tde_Password"] = tde_Password - values["tdeKeystorePath"] = lrpdb.Spec.LTDEKeystorePath - values["tde_Secret"] = tde_Secret - values["tdeExport"] = strconv.FormatBool(*(lrpdb.Spec.LTDEExport)) - } + values := map[string]string{ + "action": "INCLUDING", + "getScript": strconv.FormatBool(*(lrpdb.Spec.GetScript))} - url := r.BaseUrl(ctx, req, lrpdb, lrest) + lrpdb.Spec.LRPDBName + "/" + if lrpdb.Spec.DropAction != "" { + values["action"] = lrpdb.Spec.DropAction + } - log.Info("CallAPI(url)", "url", url) - lrpdb.Status.Phase = lrpdbPhaseUnplug - lrpdb.Status.Msg = "Waiting for LRPDB to be unplugged" - if err := r.Status().Update(ctx, lrpdb); err != nil { - log.Error(err, "Failed to update status for :"+lrpdb.Name, "err", err.Error()) - } - respData, err := NewCallLAPI(r, ctx, req, lrpdb, url, values, "POST") - if err != nil { - log.Error(err, "Failure NewCallLAPI( "+url+")", "err", err.Error()) - return err - } + lrpdbName := lrpdb.Spec.LRPDBName + url := r.BaseUrl(ctx, req, lrpdb, lrest) + lrpdbName - r.GetSqlCode(respData, &(lrpdb.Status.SqlCode)) + respData, err := NewCallAPISQL(r, ctx, req, lrpdb, url, values, "DELETE") + if err != nil { + log.Error(err, "Failure NewCallAPISQL( "+url+")", "err", err.Error()) + return err + } - if lrpdb.Status.SqlCode != 0 { + r.GetSqlCode(respData, &(lrpdb.Status.SqlCode)) globalsqlcode = lrpdb.Status.SqlCode - - lrpdb.Status.Msg = lrpdb.Spec.LRPDBName + " database cannot be unplugged " - log.Info(lrpdb.Spec.LRPDBName + " database cannot be unplugged ") - if lrpdb.Status.SqlCode == 65170 { - log.Info(lrpdb.Spec.XMLFileName + " xml file already exists ") + if lrpdb.Status.SqlCode != 0 { + lrpdb.Status.PDBBitMask = Bis(lrpdb.Status.PDBBitMask, FNALAE) + lrpdb.Status.PDBBitMaskStr = Bitmaskprint(lrpdb.Status.PDBBitMask) + oer := fmt.Sprintf("ORA-%d", lrpdb.Status.SqlCode) + lrpdb.Status.Msg = "delete:[" + oer + "]" + r.UpdateStatus(ctx, req, lrpdb) + return err + } else { + lrpdb.Status.PDBBitMask = Bis(lrpdb.Status.PDBBitMask, PDBDIC) + lrpdb.Status.PDBBitMaskStr = Bitmaskprint(lrpdb.Status.PDBBitMask) + r.UpdateStatus(ctx, req, lrpdb) } - - /* - err := r.Update(ctx, lrpdb) - if err != nil { - log.Info("Fail to update crd", "err", err.Error()) - return err - } - - if err := r.Status().Update(ctx, lrpdb); err != nil { - log.Error(err, "Failed to update status"+lrpdb.Name, "err", err.Error()) - return err - } - */ - - r.Recorder.Eventf(lrpdb, corev1.EventTypeNormal, "Unplugged", " ORA-%s ", strconv.Itoa(lrpdb.Status.SqlCode)) - err = fmt.Errorf("%v", lrpdb.Status.SqlCode) - return err } + log.Info("Successfully dropped LRPDB", "LRPDB Name", lrpdb.Spec.LRPDBName) if controllerutil.ContainsFinalizer(lrpdb, LRPDBFinalizer) { log.Info("Removing finalizer") controllerutil.RemoveFinalizer(lrpdb, LRPDBFinalizer) - err = r.Update(ctx, lrpdb) + err := r.Update(ctx, lrpdb) if err != nil { log.Info("Could not remove finalizer", "err", err.Error()) return err } - lrpdb.Status.Status = true - err = r.Delete(context.Background(), lrpdb, client.GracePeriodSeconds(1)) - if err != nil { - log.Info("Could not delete LRPDB resource", "err", err.Error()) - return err - } } - r.Recorder.Eventf(lrpdb, corev1.EventTypeNormal, "Unplugged", "LRPDB '%s' unplugged successfully", lrpdb.Spec.LRPDBName) - globalsqlcode = 0 - log.Info("Successfully unplugged LRPDB resource") + err = r.Delete(context.Background(), lrpdb, client.GracePeriodSeconds(1)) + if err != nil { + log.Info("Could not delete LRPDB resource", "err", err.Error()) + return err + } + return nil } -/************************************************** -Alter system LRPDB -**************************************************/ - -/**just push the trasnsaction **/ -func (r *LRPDBReconciler) alterSystemLRPDB(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) error { +/* +********************************************************************* + - CHECK BEFORE CLONING - log := r.Log.WithValues("alterSystemLRPDB", req.NamespacedName) - globalsqlcode = 0 +********************************************************************* +*/ +func (r *LRPDBReconciler) checkPDBforCloninig(ctx context.Context, req ctrl.Request, targetPdbName string) (int, error) { + log := r.Log.WithValues("checkPDBforCloninig", req.NamespacedName) + var pdbCounter int + pdbCounter = 0 - var err error - err = r.getLRPDBState(ctx, req, lrpdb) + lrpdbList := &dbapi.LRPDBList{} + listOpts := []client.ListOption{client.InNamespace(req.Namespace), client.MatchingFields{"spec.pdbName": targetPdbName}} + err := r.List(ctx, lrpdbList, listOpts...) if err != nil { - if apierrors.IsNotFound(err) { - log.Info("Warning LRPDB does not exist", "LRPDB Name", lrpdb.Spec.LRPDBName) - return nil - } - return err + log.Info("Failed to list lrpdbs", "Namespace", req.Namespace, "Error", err) + return 0, err } - - lrest, err := r.getLRESTResource(ctx, req, lrpdb) - if err != nil { - log.Info("Cannot find LREST server") - return err + if len(lrpdbList.Items) == 0 { + log.Info("No pdbs available") + return pdbCounter, err } - /* alter system payload */ + for _, p := range lrpdbList.Items { + fmt.Printf("DEBUGCLONE %s %s %d\n", p.Spec.LRPDBName, targetPdbName, pdbCounter) + if p.Spec.LRPDBName == targetPdbName { + log.Info("Found " + targetPdbName + " in the crd list") + if p.Status.OpenMode == "MOUNTED" { + log.Info("Cannot clone a mounted pdb") + return pdbCounter, err + } + pdbCounter++ + fmt.Printf("DEBUGCLONE %s %s %d\n", p.Spec.LRPDBName, targetPdbName, pdbCounter) + return pdbCounter, err + } - values := map[string]string{ - "state": "ALTER", - "alterSystemParameter": lrpdb.Spec.AlterSystemParameter, - "alterSystemValue": lrpdb.Spec.AlterSystemValue, - "parameterScope": lrpdb.Spec.ParameterScope, } + return pdbCounter, err +} - lrpdbName := lrpdb.Spec.LRPDBName - url := r.BaseUrl(ctx, req, lrpdb, lrest) + lrpdbName - log.Info("alter system payload...:", "lrpdb.Spec.AlterSystemValue=", lrpdb.Spec.AlterSystemValue) - log.Info("alter system payload...:", "lrpdb.Spec.AlterSystemParameter=", lrpdb.Spec.AlterSystemParameter) - log.Info("alter system payload...:", "lrpdb.Spec.ParameterScope=", lrpdb.Spec.ParameterScope) - log.Info("alter system path.......:", "url=", url) - - lrpdb.Status.Phase = lrpdbPhaseAlterPlug - lrpdb.Status.ModifyOption = lrpdb.Spec.AlterSystem + " " + lrpdb.Spec.ParameterScope - lrpdb.Status.Msg = "Waiting for LRPDB parameter to be modified" - if err := r.Status().Update(ctx, lrpdb); err != nil { - log.Error(err, "Failed to update lrpdb parameter :"+lrpdb.Name, "err", err.Error()) - return err - } - - respData, err := NewCallLAPI(r, ctx, req, lrpdb, url, values, "POST") - if err != nil { - log.Error(err, "Failure NewCallLAPI( "+url+")", "err", err.Error()) - return err - } - - r.GetSqlCode(respData, &(lrpdb.Status.SqlCode)) - globalsqlcode = lrpdb.Status.SqlCode - - if lrpdb.Status.SqlCode == 0 { - r.Recorder.Eventf(lrpdb, corev1.EventTypeNormal, "Altered", "LRPDB(name,cmd,sqlcode) '%s %s %d' ", lrpdb.Spec.LRPDBName, lrpdb.Spec.AlterSystem, lrpdb.Status.SqlCode) - lrpdb.Status.Phase = lrpdbPhaseReady - lrpdb.Spec.Action = "Noaction" - lrpdb.Status.Action = "Noaction" - lrpdb.Status.Status = true +/* +********************************************************************* + - CLONE PDB - if err := r.Update(ctx, lrpdb); err != nil { - log.Error(err, "Cannot rest lrpdb Spec :"+lrpdb.Name, "err", err.Error()) - return err - } +********************************************************************* +*/ +func (r *LRPDBReconciler) CloneLRPDB(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) error { - if err := r.Status().Update(ctx, lrpdb); err != nil { - log.Error(err, "Failed to update lrpdb parameter :"+lrpdb.Name, "err", err.Error()) - return err - } + log := r.Log.WithValues("CloneLRPDB", req.NamespacedName) + if lrpdb.Spec.LRPDBName == lrpdb.Spec.SrcLRPDBName { + log.Info("Invalid Name") return nil - - } - - if lrpdb.Status.SqlCode != 0 { - r.Recorder.Eventf(lrpdb, corev1.EventTypeWarning, "alter system failure", "LRPDB(name,cmd,sqlcode) '%s %s %d' ", lrpdb.Spec.LRPDBName, lrpdb.Spec.AlterSystem, lrpdb.Status.SqlCode) - erralter := errors.New("Error: cannot modify parameter") - - lrpdb.Status.ModifyOption = lrpdb.Spec.AlterSystem + " " + lrpdb.Spec.ParameterScope - lrpdb.Status.Msg = "Failed: cannot modify system parameter" - lrpdb.Status.Phase = lrpdbPhaseStatus - lrpdb.Spec.AlterSystem = "" - lrpdb.Spec.ParameterScope = "" - lrpdb.Spec.Action = "Noaction" - if err := r.Update(ctx, lrpdb); err != nil { - log.Error(err, "Cannot rest lrpdb Spec :"+lrpdb.Name, "err", err.Error()) - return err - } - - if err := r.Status().Update(ctx, lrpdb); err != nil { - log.Error(err, "Failed to update lrpdb parameter :"+lrpdb.Name, "err", err.Error()) - return err - } - return erralter } - lrpdb.Status.Status = false + globalsqlcode = 0 + var err error - if err := r.Status().Update(ctx, lrpdb); err != nil { - log.Error(err, "Failed to update lrpdb parameter :"+lrpdb.Name, "err", err.Error()) + lrest, err := r.getLRESTResource(ctx, req, lrpdb) + if err != nil { return err } - return nil -} - -/************************************************* - * Modify a LRPDB state - ***********************************************/ -func (r *LRPDBReconciler) modifyLRPDB(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) error { - - log := r.Log.WithValues("modifyLRPDB", req.NamespacedName) - r.Recorder.Eventf(lrpdb, corev1.EventTypeNormal, "Modify", "Info:'%s %s %s' ", lrpdb.Spec.LRPDBName, lrpdb.Spec.LRPDBState, lrpdb.Status.ModifyOption) - var err error + /* Prevent cloning an existing lrpdb */ err = r.getLRPDBState(ctx, req, lrpdb) if err != nil { - if lrpdb.Status.SqlCode == 1403 { - // BUG 36752465 - // We have to handle to verify a non existings results using both - log.Info("Database does not exists ") - r.Delete(context.Background(), lrpdb, client.GracePeriodSeconds(1)) - return nil - } if apierrors.IsNotFound(err) { - log.Info("Warning LRPDB does not exist", "LRPDB Name", lrpdb.Spec.LRPDBName) - r.Delete(context.Background(), lrpdb, client.GracePeriodSeconds(1)) - return nil + log.Info("Check LRPDB not existence completed", "LRPDB Name", lrpdb.Spec.LRPDBName) } - return err - } - /* This scenario is managed by webhook acceptance test ... leave it here anyway */ - if lrpdb.Status.OpenMode == "READ WRITE" && lrpdb.Spec.LRPDBState == "OPEN" && lrpdb.Spec.ModifyOption == "READ WRITE" { - /* Database is already open no action required */ + } else { + log.Info("Database already exists ", "LRPDB Name", lrpdb.Spec.LRPDBName) return nil } - if lrpdb.Status.OpenMode == "MOUNTED" && lrpdb.Spec.LRPDBState == "CLOSE" && lrpdb.Spec.ModifyOption == "IMMEDIATE" { - /* Database is already close no action required */ + values := map[string]string{ + "method": "CLONE", + "pdb_name": lrpdb.Spec.LRPDBName, + "srcPdbName": lrpdb.Spec.SrcLRPDBName, + "reuseTempFile": strconv.FormatBool(*(lrpdb.Spec.ReuseTempFile)), + "unlimitedStorage": strconv.FormatBool(*(lrpdb.Spec.UnlimitedStorage)), + "getScript": strconv.FormatBool(*(lrpdb.Spec.GetScript))} + + //* check the existence of lrpdb.Spec.SrcLRPDBName // + var allErrs field.ErrorList + pdbCounter, _ := r.checkPDBforCloninig(ctx, req, lrpdb.Spec.SrcLRPDBName) + if pdbCounter == 0 { + log.Info("target pdb " + lrpdb.Spec.SrcLRPDBName + " does not exists or is not open") + allErrs = append(allErrs, field.NotFound(field.NewPath("Spec").Child("LRPDBName"), " "+lrpdb.Spec.LRPDBName+" does not exist : failure")) + r.Delete(context.Background(), lrpdb, client.GracePeriodSeconds(1)) return nil } - lrest, err := r.getLRESTResource(ctx, req, lrpdb) - if err != nil { - return err + if lrpdb.Spec.SparseClonePath != "" { + values["sparseClonePath"] = lrpdb.Spec.SparseClonePath } - - values := map[string]string{} - if lrpdb.Spec.LRPDBState == "OPEN" || lrpdb.Spec.LRPDBState == "CLOSE" { - values = map[string]string{ - "state": lrpdb.Spec.LRPDBState, - "modifyOption": lrpdb.Spec.ModifyOption, - "getScript": strconv.FormatBool(*(lrpdb.Spec.GetScript))} - if lrpdb.Spec.LRPDBState == "OPEN" || lrpdb.Spec.LRPDBState == "CLOSE" { - log.Info("MODIFY LRPDB", "lrpdb.Spec.LRPDBState=", lrpdb.Spec.LRPDBState, "lrpdb.Spec.ModifyOption=", lrpdb.Spec.ModifyOption) - log.Info("LRPDB STATUS OPENMODE", "lrpdb.Status.OpenMode=", lrpdb.Status.OpenMode) - } + if lrpdb.Spec.FileNameConversions != "" { + values["fileNameConversions"] = lrpdb.Spec.FileNameConversions } - - lrpdbName := lrpdb.Spec.LRPDBName - url := r.BaseUrl(ctx, req, lrpdb, lrest) + lrpdbName + "/status/" - - lrpdb.Status.Phase = lrpdbPhaseModify - if lrpdb.Spec.LRPDBState == "OPEN" || lrpdb.Spec.LRPDBState == "CLOSE" { - lrpdb.Status.ModifyOption = lrpdb.Spec.LRPDBState + "-" + lrpdb.Spec.ModifyOption + if lrpdb.Spec.TotalSize != "" { + values["totalSize"] = lrpdb.Spec.TotalSize } - - lrpdb.Status.Msg = "Waiting for LRPDB to be modified" - if err := r.Status().Update(ctx, lrpdb); err != nil { - log.Error(err, "Failed to update status for :"+lrpdb.Name, "err", err.Error()) + if lrpdb.Spec.TempSize != "" { + values["tempSize"] = lrpdb.Spec.TempSize } - respData, err := NewCallLAPI(r, ctx, req, lrpdb, url, values, "POST") + url := r.BaseUrl(ctx, req, lrpdb, lrest) + lrpdb.Spec.LRPDBName + "/" + + lrpdb.Status.Msg = "clone:[op. in progress]" + r.UpdateStatus(ctx, req, lrpdb) + + respData, err := NewCallAPISQL(r, ctx, req, lrpdb, url, values, "POST") if err != nil { - log.Error(err, "Failure NewCallLAPI( "+url+")", "err", err.Error()) + log.Error(err, "Failure NewCallAPISQL( "+url+")", "err", err.Error()) return err } r.GetSqlCode(respData, &(lrpdb.Status.SqlCode)) globalsqlcode = lrpdb.Status.SqlCode + r.UpdateStatus(ctx, req, lrpdb) + + if lrpdb.Status.SqlCode != 0 { + lrpdb.Status.PDBBitMask = Bis(lrpdb.Status.PDBBitMask, PDBCRE) + lrpdb.Status.PDBBitMaskStr = Bitmaskprint(lrpdb.Status.PDBBitMask) + oer := fmt.Sprintf("ORA-%d", lrpdb.Status.SqlCode) + lrpdb.Status.Msg = "open:[" + oer + "]" + r.UpdateStatus(ctx, req, lrpdb) + return errors.New(oer) - if lrpdb.Spec.LRPDBState == "OPEN" || lrpdb.Spec.LRPDBState == "CLOSE" { - r.Recorder.Eventf(lrpdb, corev1.EventTypeNormal, "Modified", " '%s' modified successfully '%s'", lrpdb.Spec.LRPDBName, lrpdb.Spec.LRPDBState) } + r.Recorder.Eventf(lrpdb, corev1.EventTypeNormal, "Created", "LRPDB '%s' cloned successfully", lrpdb.Spec.LRPDBName) + lrpdb.Status.TotalSize = r.GetPdbSize(ctx, req, lrpdb, lrpdb.Spec.SrcLRPDBName) + if lrest.Spec.DBServer != "" { lrpdb.Status.ConnString = lrest.Spec.DBServer + ":" + strconv.Itoa(lrest.Spec.DBPort) + "/" + lrpdb.Spec.LRPDBName } else { - lrpdb.Status.ConnString = lrest.Spec.DBTnsurl + lrpdb.Status.ConnString = strings.TrimSpace(lrest.Spec.DBTnsurl) parseTnsAlias(&(lrpdb.Status.ConnString), &(lrpdb.Spec.LRPDBName)) } + imperativeLpdbDeletion = lrpdb.Spec.ImperativeLrpdbDeletion + if lrpdb.Spec.ImperativeLrpdbDeletion == true { + r.Recorder.Eventf(lrpdb, corev1.EventTypeNormal, "Clone", "PDB '%s' imperative pdb deletion turned on", lrpdb.Spec.LRPDBName) + } - lrpdb.Status.Msg = "alter lrpdb completed" - lrpdb.Status.Status = false - lrpdb.Status.Phase = lrpdbPhaseReady - - log.Info("Successfully modified LRPDB state", "LRPDB Name", lrpdb.Spec.LRPDBName) + log.Info("Cloned LRPDB successfully", "Source LRPDB Name", lrpdb.Spec.SrcLRPDBName, "Clone LRPDB Name", lrpdb.Spec.LRPDBName) + r.getLRPDBState(ctx, req, lrpdb) - /* After database openining we reapply the config map if warning is present */ - if lrpdb.Spec.LRPDBState == "OPEN" { - if bit(lrpdb.Status.Bitstat, MPWARN|MPINIT) { - log.Info("re-apply config map") - r.ApplyConfigMap(ctx, req, lrpdb) + lrpdb.Status.PDBBitMask = Bis(lrpdb.Status.PDBBitMask, PDBCRT) + lrpdb.Status.PDBBitMaskStr = Bitmaskprint(lrpdb.Status.PDBBitMask) + lrpdb.Status.Msg = "clone:[op. completed]" + r.UpdateStatus(ctx, req, lrpdb) - } - } - if err := r.Status().Update(ctx, lrpdb); err != nil { - log.Error(err, "Failed to update status for :"+lrpdb.Name, "err", err.Error()) + /* If we clone we don't have to re-exec sql/plsql */ + lrpdb.Spec.PLSQLBlock = "" + if err := r.Update(ctx, lrpdb); err != nil { + log.Error(err, "Failred to update lrpdb Spec :"+lrpdb.Name, "err", err.Error()) + return err } + log.Info("plsql block reset :[" + lrpdb.Spec.PLSQLBlock + "]") - //r.getLRPDBState(ctx, req, lrpdb) return nil } /* -************************************************ - - Get LRPDB State - /*********************************************** +************************************************************** + - Check for Duplicate LRPDB. Same LRPDB name on the same LREST resource. + - Decomissioned function +************************************************************** */ -func (r *LRPDBReconciler) getLRPDBState(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) error { +/* +func (r *LRPDBReconciler) checkDuplicateLRPDB(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) error { - log := r.Log.WithValues("getLRPDBState", req.NamespacedName) + log := r.Log.WithValues("checkDuplicateLRPDB", req.NamespacedName) - var err error + // Name of the LREST CR that holds the LREST container + lrestResName := lrpdb.Spec.CDBResName + //lrestame := lrpdb.Spec.LRESTName - lrest, err := r.getLRESTResource(ctx, req, lrpdb) - if err != nil { - return err - } + // Name of the LRPDB resource + lrpdbResName := lrpdb.Spec.LRPDBName - lrpdbName := lrpdb.Spec.LRPDBName - url := r.BaseUrl(ctx, req, lrpdb, lrest) + lrpdbName + "/status/" + lrpdbList := &dbapi.LRPDBList{} - lrpdb.Status.Msg = "Getting LRPDB state" - fmt.Print("============================\n") - fmt.Println(lrpdb.Status) - fmt.Print("============================\n") - if err := r.Status().Update(ctx, lrpdb); err != nil { - log.Error(err, "Failed to update status for :"+lrpdb.Name, "err", err.Error()) - } + listOpts := []client.ListOption{client.InNamespace(req.Namespace), client.MatchingFields{"spec.pdbName": lrpdbResName}} - respData, err := NewCallLAPI(r, ctx, req, lrpdb, url, nil, "GET") + // List retrieves list of objects for a given namespace and list options. + err := r.List(ctx, lrpdbList, listOpts...) if err != nil { - log.Info("Begin respData") - log.Info(respData) - log.Info("End respData") - lrpdb.Status.Msg = "getLRPDBState failure : check lrpdb status" - lrpdb.Status.Status = false - log.Error(err, "Failure NewCallLAPI( "+url+")", "err", err.Error()) + log.Info("Failed to list lrpdbs", "Namespace", req.Namespace, "Error", err) return err } - r.GetSqlCode(respData, &(lrpdb.Status.SqlCode)) - globalsqlcode = lrpdb.Status.SqlCode - - if lrpdb.Status.SqlCode == 1403 { - lrpdb.Status.OpenMode = "unknown" - lrpdb.Status.Msg = "check lrpdb status" - lrpdb.Status.Status = false - return errors.New("NO_DATA_FOUND") - } - - var objmap map[string]interface{} - if err := json.Unmarshal([]byte(respData), &objmap); err != nil { - log.Error(err, "Failed to get state of LRPDB :"+lrpdbName, "err", err.Error()) + if len(lrpdbList.Items) == 0 { + log.Info("No lrpdbs found for LRPDBName: "+lrpdbResName, "CDBResName", lrestResName) + return nil } - lrpdb.Status.OpenMode = objmap["open_mode"].(string) - - /* if lrpdb.Status.Phase == lrpdbPhaseCreate && sqlcode == 1403 { - - if lrpdb.Status.OpenMode == "READ WRITE" { - err := r.mapLRPDB(ctx, req, lrpdb) - if err != nil { - log.Info("Fail to Map resource getting LRPDB state") - } - } - - if lrpdb.Status.OpenMode == "MOUNTED" { - err := r.mapLRPDB(ctx, req, lrpdb) - if err != nil { - log.Info("Fail to Map resource getting LRPDB state") - } - } - }*/ - lrpdb.Status.Msg = "check lrpdb ok" - if err := r.Status().Update(ctx, lrpdb); err != nil { - log.Error(err, "Failed to update status for :"+lrpdb.Name, "err", err.Error()) + for _, p := range lrpdbList.Items { + log.Info("Found LRPDB: " + p.Name) + if (p.Name != lrpdb.Name) && (p.Spec.CDBResName == lrestResName) { + log.Info("Duplicate LRPDB found") + lrpdb.Status.Msg = "LRPDB Resource already exists" + lrpdb.Status.Status = false + return errors.New("Duplicate LRPDB found") + } } - - log.Info("Successfully obtained LRPDB state", "LRPDB Name", lrpdb.Spec.LRPDBName, "State", objmap["open_mode"].(string)) return nil } +*/ /* -************************************************ - - Map Database LRPDB to Kubernetes LRPDB CR +********************************************************************* + - GET THE CUSTOM RESOURCE FOR THE LREST MENTIONED IN THE LRPDB SPEC -/*********************************************** +********************************************************************* */ -func (r *LRPDBReconciler) mapLRPDB(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) error { +func (r *LRPDBReconciler) getLRESTResource(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) (dbapi.LREST, error) { - log := r.Log.WithValues("mapLRPDB", req.NamespacedName) + log := r.Log.WithValues("getLRESTResource", req.NamespacedName) - var err error + var lrest dbapi.LREST // LREST CR corresponding to the LREST name specified in the LRPDB spec - lrest, err := r.getLRESTResource(ctx, req, lrpdb) - if err != nil { - return err - } + // Name of the LREST CR that holds the LREST container + lrestResName := lrpdb.Spec.CDBResName + lrestNamespace := lrpdb.Spec.CDBNamespace - log.Info("callapi get to map lrpdb") + log.Info("lrestResName...........:" + lrestResName) + log.Info("lrestNamespace.........:" + lrestNamespace) - lrpdbName := lrpdb.Spec.LRPDBName - url := r.BaseUrl(ctx, req, lrpdb, lrest) + lrpdbName - log.Info("DEBUG NEW URL " + url) + // Get LREST CR corresponding to the LREST name specified in the LRPDB spec + err := r.Get(context.Background(), client.ObjectKey{ + Namespace: lrestNamespace, + Name: lrestResName, + }, &lrest) - respData, err := NewCallLAPI(r, ctx, req, lrpdb, url, nil, "GET") if err != nil { - log.Error(err, "Failure NewCallLAPI( "+url+")", "err", err.Error()) - return err + log.Info("Failed to get CRD for LREST", "Name", lrestResName, "Namespace", lrestNamespace, "Error", err.Error()) + lrpdb.Status.Msg = "Unable to get CRD for LREST : " + lrestResName + r.Status().Update(ctx, lrpdb) + return lrest, err } - var objmap map[string]interface{} - if err := json.Unmarshal([]byte(respData), &objmap); err != nil { - log.Error(err, "Failed json.Unmarshal :"+lrpdbName, "err", err.Error()) - } + return lrest, nil +} - //fmt.Printf("%+v\n", objmap) - totSizeInBytes := objmap["total_size"].(float64) - totSizeInGB := totSizeInBytes / 1024 / 1024 / 1024 +/* +********************************************************************* + - GET THE LREST POD FOR THE LREST MENTIONED IN THE LRPDB SPEC + - Decommissione function +********************************************************************* +*/ +/* +func (r *LRPDBReconciler) getLRESTPod(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) (corev1.Pod, error) { - lrpdb.Status.OpenMode = objmap["open_mode"].(string) - lrpdb.Status.TotalSize = fmt.Sprintf("%4.2f", totSizeInGB) + "G" - assertiveLpdbDeletion = lrpdb.Spec.AssertiveLrpdbDeletion - if lrpdb.Spec.AssertiveLrpdbDeletion == true { - r.Recorder.Eventf(lrpdb, corev1.EventTypeNormal, "Map", "PDB '%s' assertive pdb deletion turned on", lrpdb.Spec.LRPDBName) - } + log := r.Log.WithValues("getLRESTPod", req.NamespacedName) - if lrest.Spec.DBServer != "" { - lrpdb.Status.ConnString = lrest.Spec.DBServer + ":" + strconv.Itoa(lrest.Spec.DBPort) + "/" + lrpdb.Spec.LRPDBName - } else { - lrpdb.Status.ConnString = lrest.Spec.DBTnsurl - parseTnsAlias(&(lrpdb.Status.ConnString), &(lrpdb.Spec.LRPDBName)) - } + var lrestPod corev1.Pod // LREST Pod container with connection to the concerned LREST - lrpdb.Status.Phase = lrpdbPhaseReady + // Name of the LREST CR that holds the LREST container + lrestResName := lrpdb.Spec.CDBResName - if err := r.Status().Update(ctx, lrpdb); err != nil { - log.Error(err, "Failed to update status for :"+lrpdb.Name, "err", err.Error()) + // Get LREST Pod associated with the LREST Name specified in the LRPDB Spec + err := r.Get(context.Background(), client.ObjectKey{ + Namespace: req.Namespace, + Name: lrestResName + "-lrest", + }, &lrestPod) + + if err != nil { + log.Info("Failed to get Pod for LREST", "Name", lrestResName, "Namespace", req.Namespace, "Error", err.Error()) + lrpdb.Status.Msg = "Unable to get LREST Pod for LREST : " + lrestResName + return lrestPod, err } - log.Info("Successfully mapped LRPDB to Kubernetes resource", "LRPDB Name", lrpdb.Spec.LRPDBName) - lrpdb.Status.Status = true - return nil + log.Info("Found LREST Pod for LREST", "Name", lrestResName, "Pod Name", lrestPod.Name, "LREST Container hostname", lrestPod.Spec.Hostname) + return lrestPod, nil } +*/ /* -************************************************ - - Delete a LRPDB - /*********************************************** +********************************************************************* + - GET SECRET KEY FOR A SECRET NAME + +********************************************************************* */ -func (r *LRPDBReconciler) deleteLRPDB(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) error { +func (r *LRPDBReconciler) getSecret(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB, secretName string, keyName string) (string, error) { - log := r.Log.WithValues("deleteLRPDB", req.NamespacedName) + log := r.Log.WithValues("getSecret", req.NamespacedName) - errstate := r.getLRPDBState(ctx, req, lrpdb) - if errstate != nil { - if lrpdb.Status.SqlCode == 1403 { - // BUG 36752336: - log.Info("Database does not exists ") - r.Delete(context.Background(), lrpdb, client.GracePeriodSeconds(1)) - return nil - } - if apierrors.IsNotFound(errstate) { - log.Info("Warning LRPDB does not exist", "LRPDB Name", lrpdb.Spec.LRPDBName) - r.Delete(context.Background(), lrpdb, client.GracePeriodSeconds(1)) - return nil + secret := &corev1.Secret{} + err := r.Get(ctx, types.NamespacedName{Name: secretName, Namespace: lrpdb.Namespace}, secret) + if err != nil { + if apierrors.IsNotFound(err) { + log.Info("Secret not found:" + secretName) + lrpdb.Status.Msg = "Secret not found:" + secretName + return "", err } - log.Error(errstate, "Failed to update status for :"+lrpdb.Name, "err", errstate.Error()) - return errstate - //* if the pdb does not exists delete the crd *// - + log.Error(err, "Unable to get the secret.") + return "", err } - if lrpdb.Status.OpenMode == "READ WRITE" { + return string(secret.Data[keyName]), nil +} - errdel := errors.New("pdb is open cannot delete it") - log.Info("LRPDB is open in read write cannot drop ") - lrpdb.Status.Msg = "LRPDB is open in read write cannot drop " - if err := r.Status().Update(ctx, lrpdb); err != nil { - log.Error(err, "Failed to update status for :"+lrpdb.Name, "err", err.Error()) - } +/* +********************************************************************* + - CREATE PDB - return errdel - } +********************************************************************* +*/ +func (r *LRPDBReconciler) CreateLRPDB(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) error { - err := r.deleteLRPDBInstance(req, ctx, lrpdb) + log := r.Log.WithValues("CreateLRPDB", req.NamespacedName) + log.Info("call getLRESTResource \n") + lrest, err := r.getLRESTResource(ctx, req, lrpdb) if err != nil { - log.Info("Could not delete LRPDB", "LRPDB Name", lrpdb.Spec.LRPDBName, "err", err.Error()) return err } + /* If it's not created by lrest autodiscover */ + if Bit(lrpdb.Status.PDBBitMask, PDBAUT) == false && lrpdb.Spec.PDBBitMask == 0 { - if controllerutil.ContainsFinalizer(lrpdb, LRPDBFinalizer) { - log.Info("Removing finalizer") - controllerutil.RemoveFinalizer(lrpdb, LRPDBFinalizer) - err := r.Update(ctx, lrpdb) + var err error + var tde_Password string + var tde_Secret string + + AutoDiscover := lrest.Spec.PdbAutoDiscover + err = r.AutoDiscoverActivation(ctx, req, lrpdb, false) + + /*** reset sqlcode***/ + lrpdb.Status.SqlCode = 0 + + lrpdbAdminName, err := r.getEncriptedSecret(ctx, req, lrpdb, lrpdb.Spec.AdminpdbUser.Secret.SecretName, lrpdb.Spec.AdminpdbUser.Secret.Key, lrpdb.Spec.LRPDBPriKey.Secret.SecretName, lrpdb.Spec.LRPDBPriKey.Secret.Key) if err != nil { - log.Info("Could not remove finalizer", "err", err.Error()) + log.Error(err, "Unable to find pdb admin user ") + _ = r.AutoDiscoverActivation(ctx, req, lrpdb, AutoDiscover) return err } - lrpdb.Status.Status = true - err = r.Delete(context.Background(), lrpdb, client.GracePeriodSeconds(1)) + + lrpdbAdminPwd, err := r.getEncriptedSecret(ctx, req, lrpdb, lrpdb.Spec.AdminpdbPass.Secret.SecretName, lrpdb.Spec.AdminpdbPass.Secret.Key, lrpdb.Spec.LRPDBPriKey.Secret.SecretName, lrpdb.Spec.LRPDBPriKey.Secret.Key) + if err != nil { - log.Info("Could not delete LRPDB resource", "err", err.Error()) + log.Error(err, "Unable to find pdb admin password ") + _ = r.AutoDiscoverActivation(ctx, req, lrpdb, AutoDiscover) return err } - } - - r.Recorder.Eventf(lrpdb, corev1.EventTypeNormal, "Deleted", "LRPDB '%s' dropped successfully", lrpdb.Spec.LRPDBName) - - log.Info("Successfully deleted LRPDB resource") - return nil -} - -/* -************************************************ - - Check LRPDB deletion - /*********************************************** -*/ -func (r *LRPDBReconciler) manageLRPDBDeletion(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) error { - log := r.Log.WithValues("manageLRPDBDeletion", req.NamespacedName) - - // Check if the LRPDB instance is marked to be deleted, which is - // indicated by the deletion timestamp being set. - isLRPDBMarkedToBeDeleted := lrpdb.GetDeletionTimestamp() != nil - if isLRPDBMarkedToBeDeleted { - log.Info("Marked to be deleted") - lrpdb.Status.Phase = lrpdbPhaseDelete - lrpdb.Status.Status = true - r.Status().Update(ctx, lrpdb) - if controllerutil.ContainsFinalizer(lrpdb, LRPDBFinalizer) { - // Remove LRPDBFinalizer. Once all finalizers have been - // removed, the object will be deleted. - log.Info("Removing finalizer") - controllerutil.RemoveFinalizer(lrpdb, LRPDBFinalizer) - err := r.Update(ctx, lrpdb) + values := map[string]string{ + "method": "CREATE", + "pdb_name": lrpdb.Spec.LRPDBName, + "adminName": lrpdbAdminName, + "adminPwd": lrpdbAdminPwd, + "fileNameConversions": lrpdb.Spec.FileNameConversions, + "reuseTempFile": strconv.FormatBool(*(lrpdb.Spec.ReuseTempFile)), + "unlimitedStorage": strconv.FormatBool(*(lrpdb.Spec.UnlimitedStorage)), + "totalSize": lrpdb.Spec.TotalSize, + "tempSize": lrpdb.Spec.TempSize, + "getScript": strconv.FormatBool(*(lrpdb.Spec.GetScript))} + + fmt.Printf("===== PAYLOAD ===\n") + fmt.Print(" method ", values["method"], "\n") + fmt.Print(" pdb_name ", values["pdb_name"], "\n") + fmt.Print(" adminName ", values["adminName"], "\n") + fmt.Print(" adminPwd --------------\n") + fmt.Print(" fileNameConversions ", values["fileNameConversions"], "\n") + fmt.Print(" unlimitedStorage ", values["unlimitedStorage"], "\n") + fmt.Print(" reuseTempFile ", values["reuseTempFile"], "\n") + fmt.Print(" tempSize ", values["tempSize"], "\n") + fmt.Print(" totalSize ", values["totalSize"], "\n") + fmt.Print(" getScript ", values["getScript"], "\n") + + if *(lrpdb.Spec.LTDEImport) { + tde_Password, err = r.getSecret(ctx, req, lrpdb, lrpdb.Spec.LTDEPassword.Secret.SecretName, lrpdb.Spec.LTDEPassword.Secret.Key) if err != nil { - log.Info("Could not remove finalizer", "err", err.Error()) + _ = r.AutoDiscoverActivation(ctx, req, lrpdb, AutoDiscover) return err } - log.Info("Successfully removed LRPDB resource") - return nil + tde_Secret, err = r.getSecret(ctx, req, lrpdb, lrpdb.Spec.LTDESecret.Secret.SecretName, lrpdb.Spec.LTDESecret.Secret.Key) + if err != nil { + _ = r.AutoDiscoverActivation(ctx, req, lrpdb, AutoDiscover) + return err + } + + tde_Secret = tde_Secret[:len(tde_Secret)-1] + tde_Password = tde_Secret[:len(tde_Password)-1] + values["tde_Password"] = tde_Password + values["tdeKeystorePath"] = lrpdb.Spec.LTDEKeystorePath + values["tde_Secret"] = tde_Secret } - } - // Add finalizer for this CR - if !controllerutil.ContainsFinalizer(lrpdb, LRPDBFinalizer) { - log.Info("Adding finalizer") - controllerutil.AddFinalizer(lrpdb, LRPDBFinalizer) - err := r.Update(ctx, lrpdb) + url := r.BaseUrl(ctx, req, lrpdb, lrest) + fmt.Print("============================================================\n") + fmt.Print(url) + fmt.Print("\n============================================================\n") + lrpdb.Status.Msg = "create:[op in progress]" + + r.UpdateStatus(ctx, req, lrpdb) + + respData, err := NewCallAPISQL(r, ctx, req, lrpdb, url, values, "POST") if err != nil { - log.Info("Could not add finalizer", "err", err.Error()) + log.Error(err, "Failure NewCallAPISQL( "+url+")", "err", err.Error()) + _ = r.AutoDiscoverActivation(ctx, req, lrpdb, AutoDiscover) return err } - lrpdb.Status.Status = false + + r.GetSqlCode(respData, &(lrpdb.Status.SqlCode)) + globalsqlcode = lrpdb.Status.SqlCode + if lrpdb.Status.SqlCode != 0 { + lrpdb.Status.PDBBitMask = Bis(lrpdb.Status.PDBBitMask, PDBCRE) + lrpdb.Status.PDBBitMaskStr = Bitmaskprint(lrpdb.Status.PDBBitMask) + oer := fmt.Sprintf("ORA-%d", lrpdb.Status.SqlCode) + lrpdb.Status.Msg = "create:[" + oer + "]" + r.UpdateStatus(ctx, req, lrpdb) + return errors.New(oer) + } else { + lrpdb.Status.PDBBitMask = Bis(lrpdb.Status.PDBBitMask, PDBCRT) + lrpdb.Status.PDBBitMaskStr = Bitmaskprint(lrpdb.Status.PDBBitMask) + r.UpdateStatus(ctx, req, lrpdb) + } + + r.getLRPDBState(ctx, req, lrpdb) + r.Recorder.Eventf(lrpdb, corev1.EventTypeNormal, + "Created", "LRPDB '%s' created successfully", lrpdb.Spec.LRPDBName) + + log.Info("Parsing connectstring") + lrpdb.Status.ConnString = strings.TrimSpace(lrest.Spec.DBTnsurl) + parseTnsAlias(&(lrpdb.Status.ConnString), &(lrpdb.Spec.LRPDBName)) + r.UpdateStatus(ctx, req, lrpdb) + + imperativeLpdbDeletion = lrpdb.Spec.ImperativeLrpdbDeletion + if lrpdb.Spec.ImperativeLrpdbDeletion == true { + r.Recorder.Eventf(lrpdb, corev1.EventTypeNormal, "Created", "PDB '%s' imperative pdb deletion turned on", lrpdb.Spec.LRPDBName) + } + + _ = r.AutoDiscoverActivation(ctx, req, lrpdb, AutoDiscover) + + lrpdb.Status.Msg = "create:[op completed]" + r.UpdateStatus(ctx, req, lrpdb) + } else { + log.Info("CRD created by autodiscover") + lrpdb.Status.PDBBitMask = Bis(lrpdb.Status.PDBBitMask, PDBCRT) + lrpdb.Status.PDBBitMask = Bis(lrpdb.Status.PDBBitMask, PDBAUT) + lrpdb.Status.PDBBitMaskStr = Bitmaskprint(lrpdb.Status.PDBBitMask) + lrpdb.Status.ConnString = strings.TrimSpace(lrest.Spec.DBTnsurl) + parseTnsAlias(&(lrpdb.Status.ConnString), &(lrpdb.Spec.LRPDBName)) + lrpdb.Status.Msg = "autodiscover:[op completed]" + r.UpdateStatus(ctx, req, lrpdb) } return nil } -/* -************************************************ - - Finalization logic for LRPDBFinalizer +/************************************************** +ALTER SYSTEM lRPDB +**************************************************/ -*********************************************** -*/ -func (r *LRPDBReconciler) deleteLRPDBInstance(req ctrl.Request, ctx context.Context, lrpdb *dbapi.LRPDB) error { +/**just push the trasnsaction **/ +func (r *LRPDBReconciler) alterSystemLRPDB(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) error { - log := r.Log.WithValues("deleteLRPDBInstance", req.NamespacedName) + log := r.Log.WithValues("alterSystemLRPDB", req.NamespacedName) + globalsqlcode = 0 var err error + err = r.getLRPDBState(ctx, req, lrpdb) + if err != nil { + if apierrors.IsNotFound(err) { + log.Info("Warning LRPDB does not exist", "LRPDB Name", lrpdb.Spec.LRPDBName) + return nil + } + return err + } lrest, err := r.getLRESTResource(ctx, req, lrpdb) if err != nil { + log.Info("Cannot find LREST server") return err } - values := map[string]string{ - "action": "KEEP", - "getScript": strconv.FormatBool(*(lrpdb.Spec.GetScript))} + /* alter system payload */ - if lrpdb.Spec.DropAction != "" { - values["action"] = lrpdb.Spec.DropAction + values := map[string]string{ + "state": "ALTER", + "alterSystemParameter": lrpdb.Spec.AlterSystemParameter, + "alterSystemValue": lrpdb.Spec.AlterSystemValue, + "parameterScope": lrpdb.Spec.ParameterScope, } lrpdbName := lrpdb.Spec.LRPDBName - url := r.BaseUrl(ctx, req, lrpdb, lrest) + lrpdbName + "/" + url := r.BaseUrl(ctx, req, lrpdb, lrest) + lrpdbName + log.Info("alter system payload...:", "lrpdb.Spec.AlterSystemValue=", lrpdb.Spec.AlterSystemValue) + log.Info("alter system payload...:", "lrpdb.Spec.AlterSystemParameter=", lrpdb.Spec.AlterSystemParameter) + log.Info("alter system payload...:", "lrpdb.Spec.ParameterScope=", lrpdb.Spec.ParameterScope) + log.Info("alter system path.......:", "url=", url) - lrpdb.Status.Phase = lrpdbPhaseDelete - lrpdb.Status.Msg = "Waiting for LRPDB to be deleted" - if err := r.Status().Update(ctx, lrpdb); err != nil { - log.Error(err, "Failed to update status for :"+lrpdb.Name, "err", err.Error()) - } + lrpdb.Status.ModifyOption = lrpdb.Spec.AlterSystem + " " + lrpdb.Spec.ParameterScope + lrpdb.Status.Msg = "alter system:[op. in progress]" + r.UpdateStatus(ctx, req, lrpdb) - respData, err := NewCallLAPI(r, ctx, req, lrpdb, url, values, "DELETE") + respData, err := NewCallAPISQL(r, ctx, req, lrpdb, url, values, "POST") if err != nil { - log.Error(err, "Failure NewCallLAPI( "+url+")", "err", err.Error()) + log.Error(err, "Failure NewCallAPISQL( "+url+")", "err", err.Error()) return err } r.GetSqlCode(respData, &(lrpdb.Status.SqlCode)) globalsqlcode = lrpdb.Status.SqlCode - log.Info("Successfully dropped LRPDB", "LRPDB Name", lrpdbName) - return nil -} + if lrpdb.Status.SqlCode == 0 { -/* -*********************************************************** - - SetupWithManager sets up the controller with the Manager + r.Recorder.Eventf(lrpdb, corev1.EventTypeNormal, "Altered", "LRPDB(name,cmd,sqlcode) '%s %s %d' ", lrpdb.Spec.LRPDBName, lrpdb.Spec.AlterSystem, lrpdb.Status.SqlCode) + lrpdb.Status.Msg = "alter system:[op. completed]" + r.UpdateStatus(ctx, req, lrpdb) -************************************************************ -*/ -func (r *LRPDBReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&dbapi.LRPDB{}). - WithEventFilter(predicate.Funcs{ - UpdateFunc: func(e event.UpdateEvent) bool { - // Ignore updates to CR status in which case metadata.Generation does not change - return e.ObjectOld.GetGeneration() != e.ObjectNew.GetGeneration() - }, - DeleteFunc: func(e event.DeleteEvent) bool { - // Evaluates to false if the object has been confirmed deleted. - //return !e.DeleteStateUnknown - return false - }, - }). - WithOptions(controller.Options{MaxConcurrentReconciles: 100}). - Complete(r) -} + /* Reset parameters */ + lrpdb.Spec.AlterSystemValue = "" + lrpdb.Spec.AlterSystemParameter = "" + lrpdb.Spec.ParameterScope = "" -/************************************************************* -Enh 35357707 - PROVIDE THE LRPDB TNSALIAS INFORMATION -**************************************************************/ + if err := r.Update(ctx, lrpdb); err != nil { + log.Error(err, "Cannot rest lrpdb Spec :"+lrpdb.Name, "err", err.Error()) + return err + } -func parseTnsAlias(tns *string, lrpdbsrv *string) { - fmt.Printf("Analyzing string [%s]\n", *tns) - fmt.Printf("Relacing srv [%s]\n", *lrpdbsrv) - var swaptns string + return nil - if strings.Contains(strings.ToUpper(*tns), "SERVICE_NAME") == false { - fmt.Print("Cannot generate tns alias for lrpdb") - return } - if strings.Contains(strings.ToUpper(*tns), "ORACLE_SID") == true { - fmt.Print("Cannot generate tns alias for lrpdb") - return - } + if lrpdb.Status.SqlCode != 0 { + r.Recorder.Eventf(lrpdb, corev1.EventTypeWarning, "alter system failure", "LRPDB(name,cmd,sqlcode) '%s %s %d' ", lrpdb.Spec.LRPDBName, lrpdb.Spec.AlterSystem, lrpdb.Status.SqlCode) + erralter := errors.New("Error: cannot modify parameter") - swaptns = fmt.Sprintf("SERVICE_NAME=%s", *lrpdbsrv) - tnsreg := regexp.MustCompile(`SERVICE_NAME=\w+`) - *tns = tnsreg.ReplaceAllString(*tns, swaptns) + lrpdb.Status.Msg = "alter system:[op. failure]" + r.UpdateStatus(ctx, req, lrpdb) - fmt.Printf("Newstring [%s]\n", *tns) + lrpdb.Spec.AlterSystem = "" + lrpdb.Spec.ParameterScope = "" + lrpdb.Spec.ParameterScope = "" -} + if err := r.Update(ctx, lrpdb); err != nil { + log.Error(err, "Cannot rest lrpdb Spec :"+lrpdb.Name, "err", err.Error()) + return err + } -// Compose url -func (r *LRPDBReconciler) BaseUrl(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB, lrest dbapi.LREST) string { - log := r.Log.WithValues("BaseUrl", req.NamespacedName) - baseurl := "https://" + lrpdb.Spec.CDBResName + "-lrest." + lrpdb.Spec.CDBNamespace + ":" + strconv.Itoa(lrest.Spec.LRESTPort) + "/database/pdbs/" - log.Info("Baseurl:" + baseurl) - return baseurl + return erralter + } + + return nil } -func (r *LRPDBReconciler) DecryptWithPrivKey(Key string, Buffer string, req ctrl.Request) (string, error) { - log := r.Log.WithValues("DecryptWithPrivKey", req.NamespacedName) - Debug := 0 - block, _ := pem.Decode([]byte(Key)) - pkcs8PrivateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes) - if err != nil { - log.Error(err, "Failed to parse private key - "+err.Error()) - return "", err - } - if Debug == 1 { - fmt.Printf("======================================\n") - fmt.Printf("%s\n", Key) - fmt.Printf("======================================\n") - } +func (r *LRPDBReconciler) execPLSQL(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) error { + log := r.Log.WithValues("execPLSQL", req.NamespacedName) - encString64, err := base64.StdEncoding.DecodeString(string(Buffer)) + // TO BE DONE Ad control for the pdb existence + lrpdbName := lrpdb.Spec.LRPDBName + + log.Info("Reading code config Map ") + configmap, err := r.GetConfigMapCode(ctx, req, lrpdb) if err != nil { - log.Error(err, "Failed to decode encrypted string to base64 - "+err.Error()) - return "", err + log.Error(err, "Fail to fetch code configmap", "err", err.Error()) + return err } - decryptedB, err := rsa.DecryptPKCS1v15(nil, pkcs8PrivateKey.(*rsa.PrivateKey), encString64) + lrest, err := r.getLRESTResource(ctx, req, lrpdb) if err != nil { - log.Error(err, "Failed to decrypt string - "+err.Error()) - return "", err + return err } - if Debug == 1 { - fmt.Printf("[%s]\n", string(decryptedB)) + + lrpdb.Status.Msg = "plsql/sql apply[op. in progress]" + r.UpdateStatus(ctx, req, lrpdb) + + var tokens []string + var CodeSize int + /** Sort keys **/ + keys := reflect.ValueOf(configmap.Data).MapKeys() + keysOrder := func(i, j int) bool { return keys[i].Interface().(string) < keys[j].Interface().(string) } + sort.Slice(keys, keysOrder) + /** End of sort section **/ + + for _, key := range keys { + Value := configmap.Data[key.Interface().(string)] + fmt.Printf("Code Block Name (SQL/PLSQL):%s\n", key) + tokens = strings.Split(Value, "\n") + /* Debug Section */ + for cnt := range tokens { + fmt.Printf("line[%d]:%s\n", cnt, tokens[cnt]) + CodeSize += len(tokens[cnt]) + } + + //* removing laste null emlements + if len(tokens) > 0 { + tokens = tokens[:len(tokens)-1] + } + + fmt.Printf("call to restsertver (%s,%d)\n", key, CodeSize) + + jsonpayload := &PLSQLPayLoad{Values: map[string]string{"method": "APPLYSQL"}, Sqltokens: tokens} + //* Debug section **// + + encjson, _ := json.Marshal(jsonpayload) + fmt.Println(string(encjson)) + + url := r.BaseUrl(ctx, req, lrpdb, lrest) + lrpdbName + + respData, err := NewCallAPISQL(r, ctx, req, lrpdb, url, jsonpayload, "POST") + if err != nil { + log.Error(err, "Failure NewCallAPISQL( "+url+")", "err", err.Error()) + return err + } + + r.GetSqlCode(respData, &(lrpdb.Status.SqlCode)) + + EvLevel := corev1.EventTypeNormal + skey := fmt.Sprintf("[%s]", key) + if lrpdb.Status.SqlCode != 0 { + oer := fmt.Sprintf("ORA-%d", lrpdb.Status.SqlCode) + lrpdb.Status.Msg = skey + ":[" + oer + "]" + r.UpdateStatus(ctx, req, lrpdb) + EvLevel = corev1.EventTypeWarning + } + /* + Add the timestamp to the event + */ + t := time.Now() + formatted := fmt.Sprintf("APPLYSQL-%02d%02d%02d", t.Hour(), t.Minute(), t.Second()) + r.Recorder.Eventf(lrpdb, EvLevel, formatted, " CODE:SQLCODE '%s':'%d'", skey, lrpdb.Status.SqlCode) + + /* sql execution complete successfully than report the name of the tag */ + if lrpdb.Status.SqlCode == 0 { + lrpdb.Status.LastPLSQL = skey + r.UpdateStatus(ctx, req, lrpdb) + /* reset code buffer */ + } + tokens = nil + CodeSize = 0 } - return strings.TrimSpace(string(decryptedB)), err + lrpdb.Spec.PLSQLBlock = "" /* rest block */ + if err := r.Update(ctx, lrpdb); err != nil { + log.Error(err, "Failred to update lrpdb Spec :"+lrpdb.Name, "err", err.Error()) + return err + } + lrpdb.Status.Msg = "plsql/sql apply[op. completed]" + r.UpdateStatus(ctx, req, lrpdb) + log.Info("plsql block reset :[" + lrpdb.Spec.PLSQLBlock + "]") + return nil } -// New function to decrypt credential using private key -func (r *LRPDBReconciler) getEncriptedSecret(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB, secretName string, keyName string, secretNamePk string, keyNamePk string) (string, error) { +/* +************************************************ + - Get LRPDB State - log := r.Log.WithValues("getEncriptedSecret", req.NamespacedName) +************************************************ +*/ +func (r *LRPDBReconciler) getLRPDBState(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) error { - log.Info("getEncriptedSecret :" + secretName) - secret1 := &corev1.Secret{} - err := r.Get(ctx, types.NamespacedName{Name: secretName, Namespace: lrpdb.Namespace}, secret1) + log := r.Log.WithValues("getLRPDBState", req.NamespacedName) + + var err error + + lrest, err := r.getLRESTResource(ctx, req, lrpdb) if err != nil { - if apierrors.IsNotFound(err) { - log.Info("Secret not found:" + secretName) - lrpdb.Status.Msg = "Secret not found:" + secretName - return "", err - } - log.Error(err, "Unable to get the secret.") - return "", err + return err } - secret2 := &corev1.Secret{} - err = r.Get(ctx, types.NamespacedName{Name: secretNamePk, Namespace: lrpdb.Namespace}, secret2) + lrpdbName := lrpdb.Spec.LRPDBName + url := r.BaseUrl(ctx, req, lrpdb, lrest) + lrpdbName + "/status/" + + respData, err := NewCallAPISQL(r, ctx, req, lrpdb, url, nil, "GET") if err != nil { - if apierrors.IsNotFound(err) { - log.Info("Secret not found:" + secretNamePk) - lrpdb.Status.Msg = "Secret not found:" + secretNamePk - return "", err - } - log.Error(err, "Unable to get the secret.") - return "", err + lrpdb.Status.Msg = "getLRPDBState failure : check lrpdb status" + log.Error(err, "Failure NewCallLAPI( "+url+")", "err", err.Error()) + r.UpdateStatus(ctx, req, lrpdb) + return err } - Encval := string(secret1.Data[keyName]) - Encval = strings.TrimSpace(Encval) + r.GetSqlCode(respData, &(lrpdb.Status.SqlCode)) + globalsqlcode = lrpdb.Status.SqlCode + if lrpdb.Status.SqlCode == 1403 { + lrpdb.Status.OpenMode = "N/A" + lrpdb.Status.Msg = "N/A ORA-1403" + return errors.New("NO_DATA_FOUND") + } - privKey := string(secret2.Data[keyNamePk]) - privKey = strings.TrimSpace(privKey) + r.GetOpenMode(respData, &(lrpdb.Status.OpenMode)) + r.GetRestricted(respData, &(lrpdb.Status.Restricted)) + r.GetPdbSize2(respData, &(lrpdb.Status.TotalSize)) - /* Debuug info for dev phase - fmt.Printf("DEBUG Secretename:secretName :%s\n", secretName) - fmt.Printf("DEBUG privKey :%s\n", privKey) - fmt.Printf("DEBUG Encval :%s\n", Encval) + r.UpdateStatus(ctx, req, lrpdb) + + /* lrpdb.Status.Msg = "check lrpdb ok" + if err := r.Status().Update(ctx, lrpdb); err != nil { + log.Error(err, "Failed to update status for :"+lrpdb.Name, "err", err.Error()) + } */ - DecVal, err := r.DecryptWithPrivKey(privKey, Encval, req) + log.Info("Successfully obtained LRPDB state", "LRPDB Name", lrpdb.Spec.LRPDBName, "State", lrpdb.Status.OpenMode) + return nil +} + +/* +************************************************ + - Map Database LRPDB to Kubernetes LRPDB CR + - Decommissioned function +************************************************/ + +/* +func (r *LRPDBReconciler) mapLRPDB(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) error { + + log := r.Log.WithValues("mapLRPDB", req.NamespacedName) + + var err error + + lrest, err := r.getLRESTResource(ctx, req, lrpdb) if err != nil { - log.Error(err, "Fail to decrypt secret:"+secretName) - lrpdb.Status.Msg = " Fail to decrypt secret:" + secretName - return "", err + return err } - return DecVal, nil -} -func (r *LRPDBReconciler) manageLRPDBDeletion2(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) error { - log := r.Log.WithValues("manageLRPDBDeletion", req.NamespacedName) - if lrpdb.ObjectMeta.DeletionTimestamp.IsZero() { - if !controllerutil.ContainsFinalizer(lrpdb, LRPDBFinalizer) { - controllerutil.AddFinalizer(lrpdb, LRPDBFinalizer) - if err := r.Update(ctx, lrpdb); err != nil { - return err - } - } - } else { - log.Info("Pdb marked to be delted") - if controllerutil.ContainsFinalizer(lrpdb, LRPDBFinalizer) { - if assertiveLpdbDeletion == true { - log.Info("Deleting lrpdb CRD: Assertive approach is turned on ") - lrest, err := r.getLRESTResource(ctx, req, lrpdb) - if err != nil { - log.Error(err, "Cannont find cdb resource ", "err", err.Error()) - return err - } + log.Info("callapi get to map lrpdb") - lrpdbName := lrpdb.Spec.LRPDBName - if lrpdb.Status.OpenMode == "READ WRITE" { - valuesclose := map[string]string{ - "state": "CLOSE", - "modifyOption": "IMMEDIATE", - "getScript": "FALSE"} - lrpdbName := lrpdb.Spec.LRPDBName - url := r.BaseUrl(ctx, req, lrpdb, lrest) + lrpdbName + "/status/" - _, errclose := r.callAPI(ctx, req, lrpdb, url, valuesclose, "POST") - if errclose != nil { - log.Info("Warning error closing lrpdb continue anyway") - } - } + lrpdbName := lrpdb.Spec.LRPDBName + url := r.BaseUrl(ctx, req, lrpdb, lrest) + lrpdbName + "/status/" + log.Info("DEBUG NEW URL " + url) - valuesdrop := map[string]string{ - "action": "INCLUDING", - "getScript": "FALSE"} - url := r.BaseUrl(ctx, req, lrpdb, lrest) + lrpdbName + "/" + respData, err := NewCallAPISQL(r, ctx, req, lrpdb, url, nil, "GET") + if err != nil { + log.Error(err, "Failure NewCallAPISQL( "+url+")", "err", err.Error()) + return err + } - log.Info("Call Delete()") - _, errdelete := r.callAPI(ctx, req, lrpdb, url, valuesdrop, "DELETE") - if errdelete != nil { - log.Error(errdelete, "Fail to delete lrpdb :"+lrpdb.Name, "err", err.Error()) - return errdelete - } - } /* END OF ASSERTIVE SECTION */ + var objmap map[string]interface{} + if err := json.Unmarshal([]byte(respData), &objmap); err != nil { + log.Error(err, "Failed json.Unmarshal :"+lrpdbName, "err", err.Error()) + } - log.Info("Marked to be deleted") - lrpdb.Status.Phase = lrpdbPhaseDelete - lrpdb.Status.Status = true - r.Status().Update(ctx, lrpdb) + //fmt.Printf("%+v\n", objmap) + totSizeInBytes := objmap["total_size"].(float64) + totSizeInGB := totSizeInBytes / 1024 / 1024 / 1024 - controllerutil.RemoveFinalizer(lrpdb, LRPDBFinalizer) - if err := r.Update(ctx, lrpdb); err != nil { - log.Info("Cannot remove finalizer") - return err - } + lrpdb.Status.OpenMode = objmap["open_mode"].(string) + lrpdb.Status.TotalSize = fmt.Sprintf("%4.2f", totSizeInGB) + "G" + imperativeLpdbDeletion = lrpdb.Spec.ImperativeLrpdbDeletion + if lrpdb.Spec.ImperativeLrpdbDeletion == true { + r.Recorder.Eventf(lrpdb, corev1.EventTypeNormal, "Map", "PDB '%s' imperative pdb deletion turned on", lrpdb.Spec.LRPDBName) + } - } + if lrest.Spec.DBServer != "" { + lrpdb.Status.ConnString = lrest.Spec.DBServer + ":" + strconv.Itoa(lrest.Spec.DBPort) + "/" + lrpdb.Spec.LRPDBName + } else { + lrpdb.Status.ConnString = lrest.Spec.DBTnsurl + parseTnsAlias(&(lrpdb.Status.ConnString), &(lrpdb.Spec.LRPDBName)) + } - return nil + lrpdb.Status.Status = true + + if err := r.Status().Update(ctx, lrpdb); err != nil { + log.Error(err, "Failed to update status for :"+lrpdb.Name, "err", err.Error()) } + log.Info("Successfully mapped LRPDB to Kubernetes resource", "LRPDB Name", lrpdb.Spec.LRPDBName) return nil } +*/ -func (r *LRPDBReconciler) InitConfigMap(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) *corev1.ConfigMap { - log := r.Log.WithValues("InitConfigMap", req.NamespacedName) - log.Info("ConfigMap..............:" + "ConfigMap" + lrpdb.Name) - log.Info("ConfigMap nmsp.........:" + lrpdb.Namespace) - /* - * PDB SYSTEM PARAMETER - * record [name,value=[paramete_val|reset],level=[session|system]] - */ - - if lrpdb.Spec.PDBConfigMap == "" { - /* if users does not specify a config map - we generate an empty new one for possible - future pdb parameter modification */ - - var SystemParameters map[string]string +/* +************************************************ + - Delete a LRPDB + - Decommissioned function +************************************************/ +/* +func (r *LRPDBReconciler) deleteLRPDB2(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) error { - log.Info("Generating an empty configmap") - globalconfigmap = "configmap-" + lrpdb.Spec.LRPDBName + "-default" - DbParameters := &corev1.ConfigMap{ - TypeMeta: metav1.TypeMeta{ - Kind: "configmap", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: globalconfigmap, - Namespace: lrpdb.Namespace, - }, - Data: SystemParameters, - } + log := r.Log.WithValues("deleteLRPDB2", req.NamespacedName) - if err := ctrl.SetControllerReference(lrpdb, DbParameters, r.Scheme); err != nil { - log.Error(err, "Fail to set SetControllerReference", "err", err.Error()) + errstate := r.getLRPDBState(ctx, req, lrpdb) + if errstate != nil { + if lrpdb.Status.SqlCode == 1403 { + log.Info("Database does not exists ") + r.Delete(context.Background(), lrpdb, client.GracePeriodSeconds(1)) return nil } - - /* Update Spec.PDBConfigMap */ - lrpdb.Spec.PDBConfigMap = "configmap" + lrpdb.Spec.LRPDBName + "default" - if err := r.Update(ctx, lrpdb); err != nil { - log.Error(err, "Failure updating Spec.PDBConfigMap ", "err", err.Error()) + if apierrors.IsNotFound(errstate) { + log.Info("Warning LRPDB does not exist", "LRPDB Name", lrpdb.Spec.LRPDBName) + r.Delete(context.Background(), lrpdb, client.GracePeriodSeconds(1)) return nil } - lrpdb.Status.Bitstat = bis(lrpdb.Status.Bitstat, MPEMPT) - return DbParameters + log.Error(errstate, "Failed to update status for :"+lrpdb.Name, "err", errstate.Error()) + return errstate + + } + + if lrpdb.Status.OpenMode == "READ WRITE" { + + errdel := errors.New("pdb is open cannot delete it") + log.Info("LRPDB is open in read write cannot drop ") + lrpdb.Status.Msg = "LRPDB is open in read write cannot drop " + if err := r.Status().Update(ctx, lrpdb); err != nil { + log.Error(err, "Failed to update status for :"+lrpdb.Name, "err", err.Error()) + } + + return errdel + } + + err := r.deleteLRPDBInstance(req, ctx, lrpdb) + if err != nil { + log.Info("Could not delete LRPDB", "LRPDB Name", lrpdb.Spec.LRPDBName, "err", err.Error()) + return err + } + + if controllerutil.ContainsFinalizer(lrpdb, LRPDBFinalizer) { + log.Info("Removing finalizer") + controllerutil.RemoveFinalizer(lrpdb, LRPDBFinalizer) + err := r.Update(ctx, lrpdb) + if err != nil { + log.Info("Could not remove finalizer", "err", err.Error()) + return err + } + lrpdb.Status.Status = true + err = r.Delete(context.Background(), lrpdb, client.GracePeriodSeconds(1)) + if err != nil { + log.Info("Could not delete LRPDB resource", "err", err.Error()) + return err + } + } + + r.Recorder.Eventf(lrpdb, corev1.EventTypeNormal, "Deleted", "LRPDB '%s' dropped successfully", lrpdb.Spec.LRPDBName) + + log.Info("Successfully deleted LRPDB resource") + return nil +} +*/ + +/* +************************************************ + - Check LRPDB deletion + - Decommissioned function +*********************************************** +/* +func (r *LRPDBReconciler) manageLRPDBDeletion(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) error { + log := r.Log.WithValues("manageLRPDBDeletion", req.NamespacedName) + + isLRPDBMarkedToBeDeleted := lrpdb.GetDeletionTimestamp() != nil + if isLRPDBMarkedToBeDeleted { + log.Info("Marked to be deleted") + lrpdb.Status.Status = true + r.Status().Update(ctx, lrpdb) + + if controllerutil.ContainsFinalizer(lrpdb, LRPDBFinalizer) { + log.Info("Removing finalizer") + controllerutil.RemoveFinalizer(lrpdb, LRPDBFinalizer) + err := r.Update(ctx, lrpdb) + if err != nil { + log.Info("Could not remove finalizer", "err", err.Error()) + return err + } + log.Info("Successfully removed LRPDB resource") + return nil + } + } + + if !controllerutil.ContainsFinalizer(lrpdb, LRPDBFinalizer) { + log.Info("Adding finalizer") + controllerutil.AddFinalizer(lrpdb, LRPDBFinalizer) + err := r.Update(ctx, lrpdb) + if err != nil { + log.Info("Could not add finalizer", "err", err.Error()) + return err + } + lrpdb.Status.Status = false + } + return nil +} +*/ + +/* +************************************************ + - Finalization logic for LRPDBFinalizer + - Decommisssioned function +*********************************************** +*/ +/* +func (r *LRPDBReconciler) deleteLRPDBInstance(req ctrl.Request, ctx context.Context, lrpdb *dbapi.LRPDB) error { + + log := r.Log.WithValues("deleteLRPDBInstance", req.NamespacedName) + + var err error + + lrest, err := r.getLRESTResource(ctx, req, lrpdb) + if err != nil { + return err + } + + values := map[string]string{ + "action": "KEEP", + "getScript": strconv.FormatBool(*(lrpdb.Spec.GetScript))} + + if lrpdb.Spec.DropAction != "" { + values["action"] = lrpdb.Spec.DropAction + } + + lrpdbName := lrpdb.Spec.LRPDBName + url := r.BaseUrl(ctx, req, lrpdb, lrest) + lrpdbName + + lrpdb.Status.Msg = "Waiting for LRPDB to be deleted" + if err := r.Status().Update(ctx, lrpdb); err != nil { + log.Error(err, "Failed to update status for :"+lrpdb.Name, "err", err.Error()) + } + + respData, err := NewCallAPISQL(r, ctx, req, lrpdb, url, values, "DELETE") + if err != nil { + log.Error(err, "Failure NewCallAPISQL( "+url+")", "err", err.Error()) + return err + } + + r.GetSqlCode(respData, &(lrpdb.Status.SqlCode)) + globalsqlcode = lrpdb.Status.SqlCode + + log.Info("Successfully dropped LRPDB", "LRPDB Name", lrpdbName) + return nil +} +*/ + +/* +*********************************************************** + - SetupWithManager sets up the controller with the Manager + +************************************************************ +*/ +func (r *LRPDBReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&dbapi.LRPDB{}). + WithEventFilter(predicate.Funcs{ + UpdateFunc: func(e event.UpdateEvent) bool { + // Ignore updates to CR status in which case metadata.Generation does not change + return e.ObjectOld.GetGeneration() != e.ObjectNew.GetGeneration() + }, + DeleteFunc: func(e event.DeleteEvent) bool { + // Evaluates to false if the object has been confirmed deleted. + //return !e.DeleteStateUnknown + return false + }, + }). + WithOptions(controller.Options{MaxConcurrentReconciles: 100}). + Complete(r) +} + +/************************************************************* +Enh 35357707 - PROVIDE THE LRPDB TNSALIAS INFORMATION +**************************************************************/ + +func parseTnsAlias(tns *string, lrpdbsrv *string) { + fmt.Printf("Analyzing string [%s]\n", *tns) + fmt.Printf("Relacing srv [%s]\n", *lrpdbsrv) + var swaptns string + + if strings.Contains(strings.ToUpper(*tns), "SERVICE_NAME") == false { + fmt.Print("Cannot generate tns alias for lrpdb") + return + } + + if strings.Contains(strings.ToUpper(*tns), "ORACLE_SID") == true { + fmt.Print("Cannot generate tns alias for lrpdb") + return + } + + *tns = strings.ReplaceAll(*tns, " ", "") + + swaptns = fmt.Sprintf("SERVICE_NAME=%s", *lrpdbsrv) + tnsreg := regexp.MustCompile(`SERVICE_NAME=\w+`) + *tns = tnsreg.ReplaceAllString(*tns, swaptns) + + fmt.Printf("Newstring [%s]\n", *tns) + +} + +// Compose url +func (r *LRPDBReconciler) BaseUrl(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB, lrest dbapi.LREST) string { + log := r.Log.WithValues("BaseUrl", req.NamespacedName) + baseurl := "https://" + lrpdb.Spec.CDBResName + "-lrest." + lrpdb.Spec.CDBNamespace + ":" + strconv.Itoa(lrest.Spec.LRESTPort) + "/database/pdbs/" + log.Info("Baseurl:" + baseurl) + return baseurl +} + +func (r *LRPDBReconciler) DecryptWithPrivKey(Key string, Buffer string, req ctrl.Request) (string, error) { + log := r.Log.WithValues("DecryptWithPrivKey", req.NamespacedName) + Debug := 0 + block, _ := pem.Decode([]byte(Key)) + pkcs8PrivateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + log.Error(err, "Failed to parse private key - "+err.Error()) + return "", err + } + if Debug == 1 { + fmt.Printf("======================================\n") + fmt.Printf("%s\n", Key) + fmt.Printf("======================================\n") + } + + encString64, err := base64.StdEncoding.DecodeString(string(Buffer)) + if err != nil { + log.Error(err, "Failed to decode encrypted string to base64 - "+err.Error()) + return "", err + } + + decryptedB, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, pkcs8PrivateKey.(*rsa.PrivateKey), encString64, nil) + if err != nil { + log.Error(err, "Failed to decrypt string - "+err.Error()) + return "", err + } + if Debug == 1 { + fmt.Printf("[%s]\n", string(decryptedB)) + } + return strings.TrimSpace(string(decryptedB)), err + +} + +// New function to decrypt credential using private key +func (r *LRPDBReconciler) getEncriptedSecret(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB, secretName string, keyName string, secretNamePk string, keyNamePk string) (string, error) { + + log := r.Log.WithValues("getEncriptedSecret", req.NamespacedName) + + log.Info("getEncriptedSecret :" + secretName) + secret1 := &corev1.Secret{} + err := r.Get(ctx, types.NamespacedName{Name: secretName, Namespace: lrpdb.Namespace}, secret1) + if err != nil { + if apierrors.IsNotFound(err) { + log.Info("Secret not found:" + secretName) + lrpdb.Status.Msg = "Secret not found:" + secretName + return "", err + } + log.Error(err, "Unable to get the secret.") + return "", err + } + + secret2 := &corev1.Secret{} + err = r.Get(ctx, types.NamespacedName{Name: secretNamePk, Namespace: lrpdb.Namespace}, secret2) + if err != nil { + if apierrors.IsNotFound(err) { + log.Info("Secret not found:" + secretNamePk) + lrpdb.Status.Msg = "Secret not found:" + secretNamePk + return "", err + } + log.Error(err, "Unable to get the secret.") + return "", err + } + + Encval := string(secret1.Data[keyName]) + Encval = strings.TrimSpace(Encval) + + privKey := string(secret2.Data[keyNamePk]) + privKey = strings.TrimSpace(privKey) + + /* Debuug info for dev phase + fmt.Printf("DEBUG Secretename:secretName :%s\n", secretName) + fmt.Printf("DEBUG privKey :%s\n", privKey) + fmt.Printf("DEBUG Encval :%s\n", Encval) + */ + + DecVal, err := r.DecryptWithPrivKey(privKey, Encval, req) + if err != nil { + log.Error(err, "Fail to decrypt secret:"+secretName) + lrpdb.Status.Msg = " Fail to decrypt secret:" + secretName + return "", err + } + return DecVal, nil +} + +/* +func (r *LRPDBReconciler) manageLRPDBDeletion2(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) error { + log := r.Log.WithValues("manageLRPDBDeletion", req.NamespacedName) + if lrpdb.ObjectMeta.DeletionTimestamp.IsZero() { + if !controllerutil.ContainsFinalizer(lrpdb, LRPDBFinalizer) { + controllerutil.AddFinalizer(lrpdb, LRPDBFinalizer) + if err := r.Update(ctx, lrpdb); err != nil { + return err + } + } + } else { + log.Info("Pdb marked to be delted") + if controllerutil.ContainsFinalizer(lrpdb, LRPDBFinalizer) { + if imperativeLpdbDeletion == true { + log.Info("Deleting lrpdb CRD: Imperative approach is turned on ") + lrest, err := r.getLRESTResource(ctx, req, lrpdb) + if err != nil { + log.Error(err, "Cannont find cdb resource ", "err", err.Error()) + return err + } + + lrpdbName := lrpdb.Spec.LRPDBName + if lrpdb.Status.OpenMode == "READ WRITE" { + valuesclose := map[string]string{ + "state": "CLOSE", + "modifyOption": "IMMEDIATE", + "getScript": "FALSE"} + lrpdbName := lrpdb.Spec.LRPDBName + url := r.BaseUrl(ctx, req, lrpdb, lrest) + lrpdbName + "/status/" + _, errclose := r.callAPI(ctx, req, lrpdb, url, valuesclose, "POST") + if errclose != nil { + log.Info("Warning error closing lrpdb continue anyway") + } + } + + valuesdrop := map[string]string{ + "action": "INCLUDING", + "getScript": "FALSE"} + url := r.BaseUrl(ctx, req, lrpdb, lrest) + lrpdbName + + log.Info("Call Delete()") + _, errdelete := r.callAPI(ctx, req, lrpdb, url, valuesdrop, "DELETE") + if errdelete != nil { + log.Error(errdelete, "Fail to delete lrpdb :"+lrpdb.Name, "err", errdelete.Error()) + return errdelete + } + + } + + log.Info("Marked to be deleted") + lrpdb.Status.Status = true + r.Status().Update(ctx, lrpdb) + + controllerutil.RemoveFinalizer(lrpdb, LRPDBFinalizer) + if err := r.Update(ctx, lrpdb); err != nil { + log.Info("Cannot remove finalizer") + return err + } + + } + + return nil + } + + return nil +} +*/ + +func (r *LRPDBReconciler) InitConfigMap(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) *corev1.ConfigMap { + log := r.Log.WithValues("InitConfigMap", req.NamespacedName) + log.Info("ConfigMap..............:" + "ConfigMap" + lrpdb.Name) + log.Info("ConfigMap nmsp.........:" + lrpdb.Namespace) + /* + * PDB SYSTEM PARAMETER + * record [name,value=[paramete_val|reset],level=[session|system]] + */ + + if lrpdb.Spec.PDBConfigMap == "" { + /* if users does not specify a config map + we generate an empty new one for possible + future pdb parameter modification */ + + var SystemParameters map[string]string + + log.Info("Generating an empty configmap") + globalconfigmap = "configmap-" + lrpdb.Spec.LRPDBName + "-default" + // RFC 1123 + globalconfigmap = strings.ToLower(globalconfigmap) + globalconfigmap = strings.ReplaceAll(globalconfigmap, "_", "-") + + DbParameters := &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + Kind: "configmap", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: globalconfigmap, + Namespace: lrpdb.Namespace, + }, + Data: SystemParameters, + } + + if err := ctrl.SetControllerReference(lrpdb, DbParameters, r.Scheme); err != nil { + log.Error(err, "Fail to set SetControllerReference", "err", err.Error()) + return nil + } + + if err := r.Create(ctx, DbParameters); err != nil { + log.Error(err, "Failed to create the default configmap", "Namespace", lrpdb.Namespace, "Default configmap", globalconfigmap) + return nil + } + + /* Update Spec.PDBConfigMap */ + //lrpdb.Spec.PDBConfigMap = "configmap" + lrpdb.Spec.LRPDBName + "default" + lrpdb.Spec.PDBConfigMap = globalconfigmap + if err := r.Update(ctx, lrpdb); err != nil { + log.Error(err, "Failure updating Spec.PDBConfigMap ", "err", err.Error()) + return nil + } + lrpdb.Status.CmBitstat = bis(lrpdb.Status.CmBitstat, MPEMPT) + lrpdb.Status.CmBitStatStr = CMBitmaskprint(lrpdb.Status.CmBitstat) + r.UpdateStatus(ctx, req, lrpdb) + return DbParameters + + } else { + + lrpdb.Status.CmBitstat = bis(lrpdb.Status.CmBitstat, MPINIT) + lrpdb.Status.CmBitStatStr = CMBitmaskprint(lrpdb.Status.CmBitstat) + globalconfigmap = lrpdb.Spec.PDBConfigMap + DbParameters, err := r.GetConfigMap(ctx, req, lrpdb) + if err != nil { + log.Error(err, "Fail to fetch configmap ", "err", err.Error()) + return nil + } + + //ParseConfigMapData(DbParameters) + + r.UpdateStatus(ctx, req, lrpdb) + return DbParameters + } + + // return nil +} + +func (r *LRPDBReconciler) GetConfigMap(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) (*corev1.ConfigMap, error) { + log := r.Log.WithValues("GetConfigMap", req.NamespacedName) + log.Info("ConfigMapGlobal.............:" + globalconfigmap) + DbParameters, err := k8s.FetchConfigMap(r.Client, lrpdb.Namespace, globalconfigmap) + if err != nil { + log.Error(err, "Fail to fetch configmap", "err", err.Error()) + return nil, err + } + + return DbParameters, nil +} + +func (r *LRPDBReconciler) GetConfigMapCode(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) (*corev1.ConfigMap, error) { + log := r.Log.WithValues("GetConfigMapCode", req.NamespacedName) + log.Info("CodeMapGlobal.............:" + lrpdb.Spec.PLSQLBlock) + CodeBlock, err := k8s.FetchConfigMap(r.Client, lrpdb.Namespace, lrpdb.Spec.PLSQLBlock) + if err != nil { + log.Error(err, "Fail to fetch configmap", "err", err.Error()) + return nil, err + } + + return CodeBlock, nil +} + +func (r *LRPDBReconciler) ApplyConfigMap(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) (int32, error) { + log := r.Log.WithValues("ApplyConfigMap", req.NamespacedName) + /* We read the config map and apply the setting to the pdb */ + + log.Info("Starting Apply Config Map Process") + configmap, err := r.GetConfigMap(ctx, req, lrpdb) + if err != nil { + log.Info("Cannot get config map in the open yaml file") + return 0, nil + } + Cardinality := int32(len(configmap.Data)) + if Cardinality == 0 { + log.Info("Empty config map... nothing to do ") + return 0, nil + } + log.Info("GetConfigMap completed") + + lrest, err := r.getLRESTResource(ctx, req, lrpdb) + if err != nil { + log.Info("Cannot find lrest server") + return 0, nil + } + tokens := ParseConfigMapData(configmap) + for cnt := range tokens { + if len(tokens[cnt]) != 0 { + /* avoid null token and check malformed value */ + fmt.Printf("token=[%s]\n", tokens[cnt]) + Parameter := strings.Split(tokens[cnt], " ") + if len(Parameter) != 3 { + log.Info("WARNING malformed value in the configmap") + } else { + fmt.Printf("alter system set %s=%s scope=%s instances=all\n", Parameter[0], Parameter[1], Parameter[2]) + /* Preparing PayLoad + ----------------- + WARNING: event setting is not yet supported. It will be implemented in future release + */ + AlterSystemPayload := map[string]string{ + "state": "ALTER", + "alterSystemParameter": Parameter[0], + "alterSystemValue": Parameter[1], + "parameterScope": Parameter[2], + } + url := r.BaseUrl(ctx, req, lrpdb, lrest) + lrpdb.Spec.LRPDBName + respData, err := r.callAPI(ctx, req, lrpdb, url, AlterSystemPayload, "POST") + if err != nil { + log.Error(err, "callAPI failure durring Apply Config Map", "err", err.Error()) + return 0, err + } + /* check sql code execution */ + var retJson map[string]interface{} + if err := json.Unmarshal([]byte(respData), &retJson); err != nil { + log.Error(err, "failed to get Data from callAPI", "err", err.Error()) + return 0, err + } + /* We do not the execution if something goes wrong for a single parameter + just report the error in the event queue */ + SqlCode := strconv.Itoa(int(retJson["sqlcode"].(float64))) + AlterMsg := fmt.Sprintf("pdb=%s:%s:%s:%s:%s", lrpdb.Spec.LRPDBName, Parameter[0], Parameter[1], Parameter[2], SqlCode) + log.Info("Config Map Apply:......." + AlterMsg) + + if SqlCode != "0" { + r.Recorder.Eventf(lrpdb, corev1.EventTypeWarning, "lrpdbinfo", AlterMsg) + lrpdb.Status.CmBitstat = bis(lrpdb.Status.CmBitstat, MPWARN) + } + + } + } + + } + + if err := r.Update(ctx, lrpdb); err != nil { + log.Error(err, "Cannot rest lrpdb Spec :"+lrpdb.Name, "err", err.Error()) + } + + lrpdb.Status.CmBitstat = bis(lrpdb.Status.CmBitstat, MPAPPL) + lrpdb.Status.CmBitStatStr = CMBitmaskprint(lrpdb.Status.CmBitstat) + r.UpdateStatus(ctx, req, lrpdb) + + return Cardinality, nil +} + +func (r *LRPDBReconciler) ManageConfigMapForCloningAndPlugin(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) error { + log := r.Log.WithValues("ManageConfigMapForCloningAndPlugin", req.NamespacedName) + log.Info("Frame:") + /* + If configmap parameter is set and init flag is not set + then we need to iniialized the init mask. This is the case for + pdb generated by clone and plug action + */ + if lrpdb.Spec.Action != "CREATE" && lrpdb.Spec.Action != "APPLYSQL" && lrpdb.Spec.PDBConfigMap != "" && Bit(lrpdb.Status.CmBitstat, MPINIT) == false { + if r.InitConfigMap(ctx, req, lrpdb) == nil { + log.Info("Cannot initialize config map for pdb.........:" + lrpdb.Spec.LRPDBName) + return nil + } + log.Info("Call...........:ApplyConfigMap(ctx, req, lrpdb)") + Cardinality, _ := r.ApplyConfigMap(ctx, req, lrpdb) + log.Info("Cardnality:....:" + strconv.Itoa(int(Cardinality))) + if Cardinality == 0 { + return nil + } + + } + return nil +} + +func NewCallLAPI(intr interface{}, ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB, url string, payload map[string]string, action string) (string, error) { + var c client.Client + var r logr.Logger + var e record.EventRecorder + var err error + + recpdb, ok1 := intr.(*LRPDBReconciler) + if ok1 { + fmt.Printf("func NewCallLApi ((*PDBReconciler),......)\n") + c = recpdb.Client + e = recpdb.Recorder + r = recpdb.Log + } + + reccdb, ok2 := intr.(*LRESTReconciler) + if ok2 { + fmt.Printf("func NewCallLApi ((*CDBReconciler),......)\n") + c = reccdb.Client + e = reccdb.Recorder + r = reccdb.Log + } + + log := r.WithValues("NewCallAPISQL", req.NamespacedName) + + secret := &corev1.Secret{} + + err = c.Get(ctx, types.NamespacedName{Name: lrpdb.Spec.LRPDBTlsKey.Secret.SecretName, Namespace: lrpdb.Namespace}, secret) + + if err != nil { + if apierrors.IsNotFound(err) { + log.Info("Secret not found:" + lrpdb.Spec.LRPDBTlsKey.Secret.SecretName) + return "", err + } + log.Error(err, "Unable to get the secret.") + return "", err + } + rsaKeyPEM := secret.Data[lrpdb.Spec.LRPDBTlsKey.Secret.Key] + + err = c.Get(ctx, types.NamespacedName{Name: lrpdb.Spec.LRPDBTlsCrt.Secret.SecretName, Namespace: lrpdb.Namespace}, secret) + + if err != nil { + if apierrors.IsNotFound(err) { + log.Info("Secret not found:" + lrpdb.Spec.LRPDBTlsCrt.Secret.SecretName) + return "", err + } + log.Error(err, "Unable to get the secret.") + return "", err + } + + rsaCertPEM := secret.Data[lrpdb.Spec.LRPDBTlsCrt.Secret.Key] + + err = c.Get(ctx, types.NamespacedName{Name: lrpdb.Spec.LRPDBTlsCat.Secret.SecretName, Namespace: lrpdb.Namespace}, secret) + + if err != nil { + if apierrors.IsNotFound(err) { + log.Info("Secret not found:" + lrpdb.Spec.LRPDBTlsCat.Secret.SecretName) + return "", err + } + log.Error(err, "Unable to get the secret.") + return "", err + } + + caCert := secret.Data[lrpdb.Spec.LRPDBTlsCat.Secret.Key] + /* + r.Recorder.Eventf(lrpdb, corev1.EventTypeWarning, "LRESTINFO", string(rsaKeyPEM)) + r.Recorder.Eventf(lrpdb, corev1.EventTypeWarning, "LRESTINFO", string(rsaCertPEM)) + r.Recorder.Eventf(lrpdb, corev1.EventTypeWarning, "LRESTINFO", string(caCert)) + */ + + certificate, err := tls.X509KeyPair([]byte(rsaCertPEM), []byte(rsaKeyPEM)) + if err != nil { + lrpdb.Status.Msg = "Error tls.X509KeyPair" + return "", err + } + + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(caCert) + /* + tlsConf := &tls.Config{Certificates: []tls.Certificate{certificate}, + RootCAs: caCertPool} + */ + tlsConf := &tls.Config{Certificates: []tls.Certificate{certificate}, + RootCAs: caCertPool, + //MinVersion: tls.VersionTLS12, + CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256}, + PreferServerCipherSuites: true, + CipherSuites: []uint16{ + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + tls.TLS_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_RSA_WITH_AES_256_CBC_SHA, + }, + } + + tr := &http.Transport{TLSClientConfig: tlsConf} + + httpclient := &http.Client{Transport: tr} + + log.Info("Issuing REST call", "URL", url, "Action", action) + + // Get Web Server User + //secret := &corev1.Secret{} + err = c.Get(ctx, types.NamespacedName{Name: lrpdb.Spec.WebLrpdbServerUser.Secret.SecretName, Namespace: lrpdb.Namespace}, secret) + if err != nil { + if apierrors.IsNotFound(err) { + log.Info("Secret not found:" + lrpdb.Spec.WebLrpdbServerUser.Secret.SecretName) + return "", err + } + log.Error(err, "Unable to get the secret.") + return "", err + } + webUserEnc := string(secret.Data[lrpdb.Spec.WebLrpdbServerUser.Secret.Key]) + webUserEnc = strings.TrimSpace(webUserEnc) + + err = c.Get(ctx, types.NamespacedName{Name: lrpdb.Spec.LRPDBPriKey.Secret.SecretName, Namespace: lrpdb.Namespace}, secret) + if err != nil { + if apierrors.IsNotFound(err) { + log.Info("Secret not found:" + lrpdb.Spec.LRPDBPriKey.Secret.SecretName) + return "", err + } + log.Error(err, "Unable to get the secret.") + return "", err + } + privKey := string(secret.Data[lrpdb.Spec.LRPDBPriKey.Secret.Key]) + webUser, err := CommonDecryptWithPrivKey2(privKey, webUserEnc, req) + + // Get Web Server User Password + secret = &corev1.Secret{} + err = c.Get(ctx, types.NamespacedName{Name: lrpdb.Spec.WebLrpdbServerPwd.Secret.SecretName, Namespace: lrpdb.Namespace}, secret) + if err != nil { + if apierrors.IsNotFound(err) { + log.Info("Secret not found:" + lrpdb.Spec.WebLrpdbServerPwd.Secret.SecretName) + return "", err + } + log.Error(err, "Unable to get the secret.") + return "", err + } + webUserPwdEnc := string(secret.Data[lrpdb.Spec.WebLrpdbServerPwd.Secret.Key]) + webUserPwdEnc = strings.TrimSpace(webUserPwdEnc) + webUserPwd, err := CommonDecryptWithPrivKey2(privKey, webUserPwdEnc, req) + + var httpreq *http.Request + if action == "GET" { + httpreq, err = http.NewRequest(action, url, nil) + } else { + jsonValue, _ := json.Marshal(payload) + httpreq, err = http.NewRequest(action, url, bytes.NewBuffer(jsonValue)) + } + + if err != nil { + log.Info("Unable to create HTTP Request for LRPDB : "+lrpdb.Name, "err", err.Error()) + return "", err + } + + httpreq.Header.Add("Accept", "application/json") + httpreq.Header.Add("Content-Type", "application/json") + httpreq.SetBasicAuth(webUser, webUserPwd) + + resp, err := httpclient.Do(httpreq) + if err != nil { + errmsg := err.Error() + log.Error(err, "Failed - Could not connect to LREST Pod", "err", err.Error()) + lrpdb.Status.Msg = "Error: Could not connect to LREST Pod" + e.Eventf(lrpdb, corev1.EventTypeWarning, "LRESTError", errmsg) + return "", err + } + + if resp.StatusCode != http.StatusOK { + bb, _ := ioutil.ReadAll(resp.Body) + + if resp.StatusCode == 404 { + lrpdb.Status.ConnString = "" + lrpdb.Status.Msg = lrpdb.Spec.LRPDBName + " not found" + + } else { + if flood_control == false { + lrpdb.Status.Msg = "LREST Error - HTTP Status Code:" + strconv.Itoa(resp.StatusCode) + } + } + + if flood_control == false { + log.Info("LREST Error - HTTP Status Code :"+strconv.Itoa(resp.StatusCode), "Err", string(bb)) + } + + var apiErr LRESTError + json.Unmarshal([]byte(bb), &apiErr) + if flood_control == false { + e.Eventf(lrpdb, corev1.EventTypeWarning, "LRESTError", "Failed: %s", apiErr.Message) + } + fmt.Printf("\n================== APIERR ======================\n") + fmt.Printf("URL=%s\n", url) + fmt.Printf("resp.StatusCode=%s\n", strconv.Itoa(resp.StatusCode)) + fmt.Printf("\n================== APIERR ======================\n") + flood_control = true + return "", errors.New("LREST Error") + } + flood_control = false + + defer resp.Body.Close() + + bodyBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + fmt.Print(err.Error()) + } + respData := string(bodyBytes) + fmt.Print("CALL API return msg.....:") + fmt.Println(string(bodyBytes)) + + var apiResponse restSQLCollection + json.Unmarshal([]byte(bodyBytes), &apiResponse) + fmt.Printf("===> %#v\n", apiResponse) + fmt.Printf("===> %+v\n", apiResponse) + + errFound := false + for _, sqlItem := range apiResponse.Items { + if sqlItem.ErrorDetails != "" { + log.Info("LREST Error - Oracle Error Code :" + strconv.Itoa(sqlItem.ErrorCode)) + if !errFound { + lrpdb.Status.Msg = sqlItem.ErrorDetails + } + e.Eventf(lrpdb, corev1.EventTypeWarning, "OraError", "%s", sqlItem.ErrorDetails) + errFound = true + } + } + + if errFound { + return "", errors.New("Oracle Error") + } + + return respData, nil +} + +func (r *LRPDBReconciler) GetSqlCode(rsp string, sqlcode *int) error { + log := r.Log.WithValues("GetSqlCode", "callAPI(...)") + + var objmap map[string]interface{} + if err := json.Unmarshal([]byte(rsp), &objmap); err != nil { + log.Error(err, "failed to get respData from callAPI", "err", err.Error()) + return err + } + + *sqlcode = int(objmap["sqlcode"].(float64)) + log.Info("sqlcode.......:ora-" + strconv.Itoa(*sqlcode)) + if *sqlcode != 0 { + switch strconv.Itoa(*sqlcode) { + case "65019": /* already open */ + return nil + case "65020": /* already closed */ + return nil + } + err := fmt.Errorf("%v", sqlcode) + return err + } + return nil +} + +func (r *LRPDBReconciler) GetRestricted(rsp string, restrictmode *string) error { + log := r.Log.WithValues("GetRestriced", "callAPI(...)") + + var objmap map[string]interface{} + if err := json.Unmarshal([]byte(rsp), &objmap); err != nil { + log.Error(err, "failed to get respData from callAPI", "err", err.Error()) + return err + } + + *restrictmode = string(objmap["restricted"].(string)) + + return nil +} + +func (r *LRPDBReconciler) GetPdbSize2(rsp string, pdbsize *string) error { + log := r.Log.WithValues("GetPdbSize2", "callAPI(...)") + var objmap map[string]interface{} + if err := json.Unmarshal([]byte(rsp), &objmap); err != nil { + log.Error(err, "failed to get respData from callAPI", "err", err.Error()) + return err + } + *pdbsize = fmt.Sprintf("%4.2f", ((objmap["total_size"].(float64))/1024/1024/1024)) + "G" + return nil +} + +func (r *LRPDBReconciler) GetOpenMode(rsp string, openmode *string) error { + log := r.Log.WithValues("GetRestriced", "callAPI(...)") + + var objmap map[string]interface{} + if err := json.Unmarshal([]byte(rsp), &objmap); err != nil { + log.Error(err, "failed to get respData from callAPI", "err", err.Error()) + return err + } + + *openmode = string(objmap["open_mode"].(string)) + + return nil +} + +/* +************************************************ + - Issue a REST API Call to the LREST container + /*********************************************** +*/ +func (r *LRPDBReconciler) callAPI(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB, url string, payload map[string]string, action string) (string, error) { + log := r.Log.WithValues("callAPI", req.NamespacedName) + + var err error + + secret := &corev1.Secret{} + + err = r.Get(ctx, types.NamespacedName{Name: lrpdb.Spec.LRPDBTlsKey.Secret.SecretName, Namespace: lrpdb.Namespace}, secret) + + if err != nil { + if apierrors.IsNotFound(err) { + log.Info("Secret not found:" + lrpdb.Spec.LRPDBTlsKey.Secret.SecretName) + return "", err + } + log.Error(err, "Unable to get the secret.") + return "", err + } + rsaKeyPEM := secret.Data[lrpdb.Spec.LRPDBTlsKey.Secret.Key] + + err = r.Get(ctx, types.NamespacedName{Name: lrpdb.Spec.LRPDBTlsCrt.Secret.SecretName, Namespace: lrpdb.Namespace}, secret) + + if err != nil { + if apierrors.IsNotFound(err) { + log.Info("Secret not found:" + lrpdb.Spec.LRPDBTlsCrt.Secret.SecretName) + return "", err + } + log.Error(err, "Unable to get the secret.") + return "", err + } + + rsaCertPEM := secret.Data[lrpdb.Spec.LRPDBTlsCrt.Secret.Key] + + err = r.Get(ctx, types.NamespacedName{Name: lrpdb.Spec.LRPDBTlsCat.Secret.SecretName, Namespace: lrpdb.Namespace}, secret) + + if err != nil { + if apierrors.IsNotFound(err) { + log.Info("Secret not found:" + lrpdb.Spec.LRPDBTlsCat.Secret.SecretName) + return "", err + } + log.Error(err, "Unable to get the secret.") + return "", err + } + + caCert := secret.Data[lrpdb.Spec.LRPDBTlsCat.Secret.Key] + /* + r.Recorder.Eventf(lrpdb, corev1.EventTypeWarning, "LRESTINFO", string(rsaKeyPEM)) + r.Recorder.Eventf(lrpdb, corev1.EventTypeWarning, "LRESTINFO", string(rsaCertPEM)) + r.Recorder.Eventf(lrpdb, corev1.EventTypeWarning, "LRESTINFO", string(caCert)) + */ + + certificate, err := tls.X509KeyPair([]byte(rsaCertPEM), []byte(rsaKeyPEM)) + if err != nil { + lrpdb.Status.Msg = "Error tls.X509KeyPair" + return "", err + } + + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(caCert) + /* + tlsConf := &tls.Config{Certificates: []tls.Certificate{certificate}, + RootCAs: caCertPool} + */ + tlsConf := &tls.Config{Certificates: []tls.Certificate{certificate}, + RootCAs: caCertPool, + //MinVersion: tls.VersionTLS12, + CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256}, + PreferServerCipherSuites: true, + CipherSuites: []uint16{ + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + tls.TLS_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_RSA_WITH_AES_256_CBC_SHA, + }, + } - } else { + tr := &http.Transport{TLSClientConfig: tlsConf} - lrpdb.Status.Bitstat = bis(lrpdb.Status.Bitstat, MPINIT) - globalconfigmap = lrpdb.Spec.PDBConfigMap - DbParameters, err := r.GetConfigMap(ctx, req, lrpdb) - if err != nil { - log.Error(err, "Fail to fetch configmap ", "err", err.Error()) - return nil - } + httpclient := &http.Client{Transport: tr} - //ParseConfigMapData(DbParameters) + log.Info("Issuing REST call", "URL", url, "Action", action) - return DbParameters + webUser, err := r.getEncriptedSecret(ctx, req, lrpdb, lrpdb.Spec.WebLrpdbServerUser.Secret.SecretName, lrpdb.Spec.WebLrpdbServerUser.Secret.Key, lrpdb.Spec.LRPDBPriKey.Secret.SecretName, lrpdb.Spec.LRPDBPriKey.Secret.Key) + if err != nil { + log.Error(err, "Unable to get webuser account name ") + return "", err } - return nil -} - -func (r *LRPDBReconciler) GetConfigMap(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) (*corev1.ConfigMap, error) { - log := r.Log.WithValues("GetConfigMap", req.NamespacedName) - log.Info("ConfigMapGlobal.............:" + globalconfigmap) - DbParameters, err := k8s.FetchConfigMap(r.Client, lrpdb.Namespace, globalconfigmap) + webUserPwd, err := r.getEncriptedSecret(ctx, req, lrpdb, lrpdb.Spec.WebLrpdbServerPwd.Secret.SecretName, lrpdb.Spec.WebLrpdbServerPwd.Secret.Key, lrpdb.Spec.LRPDBPriKey.Secret.SecretName, lrpdb.Spec.LRPDBPriKey.Secret.Key) if err != nil { - log.Error(err, "Fail to fetch configmap", "err", err.Error()) - return nil, err + log.Error(err, "Unable to get webuser account password ") + return "", err } - return DbParameters, nil -} - -func (r *LRPDBReconciler) ApplyConfigMap(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) (int32, error) { - log := r.Log.WithValues("ApplyConfigMap", req.NamespacedName) - /* We read the config map and apply the setting to the pdb */ + var httpreq *http.Request + if action == "GET" { + httpreq, err = http.NewRequest(action, url, nil) + } else { + jsonValue, _ := json.Marshal(payload) + httpreq, err = http.NewRequest(action, url, bytes.NewBuffer(jsonValue)) + } - log.Info("Starting Apply Config Map Process") - configmap, err := r.GetConfigMap(ctx, req, lrpdb) if err != nil { - log.Info("Cannot get config map in the open yaml file") - return 0, nil - } - Cardinality := int32(len(configmap.Data)) - if Cardinality == 0 { - log.Info("Empty config map... nothing to do ") - return 0, nil + log.Info("Unable to create HTTP Request for LRPDB : "+lrpdb.Name, "err", err.Error()) + return "", err } - log.Info("GetConfigMap completed") - lrest, err := r.getLRESTResource(ctx, req, lrpdb) + httpreq.Header.Add("Accept", "application/json") + httpreq.Header.Add("Content-Type", "application/json") + httpreq.SetBasicAuth(webUser, webUserPwd) + + resp, err := httpclient.Do(httpreq) if err != nil { - log.Info("Cannot find lrest server") - return 0, nil + errmsg := err.Error() + log.Error(err, "Failed - Could not connect to LREST Pod", "err", err.Error()) + lrpdb.Status.Msg = "Error: Could not connect to LREST Pod" + r.Recorder.Eventf(lrpdb, corev1.EventTypeWarning, "LRESTError", errmsg) + return "", err } - tokens := lrcommons.ParseConfigMapData(configmap) - for cnt := range tokens { - if len(tokens[cnt]) != 0 { - /* avoid null token and check malformed value */ - fmt.Printf("token=[%s]\n", tokens[cnt]) - Parameter := strings.Split(tokens[cnt], " ") - if len(Parameter) != 3 { - log.Info("WARNING malformed value in the configmap") - } else { - fmt.Printf("alter system set %s=%s scope=%s instances=all\n", Parameter[0], Parameter[1], Parameter[2]) - /* Preparing PayLoad - ----------------- - WARNING: event setting is not yet supported. It will be implemented in future release - */ - AlterSystemPayload := map[string]string{ - "state": "ALTER", - "alterSystemParameter": Parameter[0], - "alterSystemValue": Parameter[1], - "parameterScope": Parameter[2], - } - url := r.BaseUrl(ctx, req, lrpdb, lrest) + lrpdb.Spec.LRPDBName - respData, err := r.callAPI(ctx, req, lrpdb, url, AlterSystemPayload, "POST") - if err != nil { - log.Error(err, "callAPI failure durring Apply Config Map", "err", err.Error()) - return 0, err - } - /* check sql code execution */ - var retJson map[string]interface{} - if err := json.Unmarshal([]byte(respData), &retJson); err != nil { - log.Error(err, "failed to get Data from callAPI", "err", err.Error()) - return 0, err - } - /* We do not the execution if something goes wrong for a single parameter - just report the error in the event queue */ - SqlCode := strconv.Itoa(int(retJson["sqlcode"].(float64))) - AlterMsg := fmt.Sprintf("pdb=%s:%s:%s:%s:%s", lrpdb.Spec.LRPDBName, Parameter[0], Parameter[1], Parameter[2], SqlCode) - log.Info("Config Map Apply:......." + AlterMsg) - if SqlCode != "0" { - r.Recorder.Eventf(lrpdb, corev1.EventTypeWarning, "LRESTINFO", AlterMsg) - lrpdb.Status.Bitstat = bis(lrpdb.Status.Bitstat, MPWARN) - } + if resp.StatusCode != http.StatusOK { + bb, _ := ioutil.ReadAll(resp.Body) + + if resp.StatusCode == 404 { + lrpdb.Status.ConnString = "" + lrpdb.Status.Msg = lrpdb.Spec.LRPDBName + " not found" + } else { + if flood_control == false { + lrpdb.Status.Msg = "LREST Error - HTTP Status Code:" + strconv.Itoa(resp.StatusCode) } } + if flood_control == false { + log.Info("LREST Error - HTTP Status Code :"+strconv.Itoa(resp.StatusCode), "Err", string(bb)) + } + + var apiErr LRESTError + json.Unmarshal([]byte(bb), &apiErr) + if flood_control == false { + r.Recorder.Eventf(lrpdb, corev1.EventTypeWarning, "LRESTError", "Failed: %s", apiErr.Message) + } + fmt.Printf("\n================== APIERR ======================\n") + fmt.Printf("%+v \n", apiErr) + fmt.Printf("URL=%s\n", url) + fmt.Printf("resp.StatusCode=%s\n", strconv.Itoa(resp.StatusCode)) + fmt.Printf("\n================== APIERR ======================\n") + flood_control = true + return "", errors.New("LREST Error") } + flood_control = false - lrpdb.Status.Bitstat = bis(lrpdb.Status.Bitstat, MPAPPL) + defer resp.Body.Close() - return Cardinality, nil -} + bodyBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + fmt.Print(err.Error()) + } + respData := string(bodyBytes) + fmt.Print("CALL API return msg.....:") + fmt.Println(string(bodyBytes)) -func (r *LRPDBReconciler) ManageConfigMapForCloningAndPlugin(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB) error { - log := r.Log.WithValues("ManageConfigMapForCloningAndPlugin", req.NamespacedName) - log.Info("Frame:") - /* - If configmap parameter is set and init flag is not set - then we need to iniialized the init mask. This is the case for - pdb generated by clone and plug action - */ - if lrpdb.Spec.Action != "CREATE" && lrpdb.Spec.PDBConfigMap != "" && bit(lrpdb.Status.Bitstat, MPINIT) == false { - if r.InitConfigMap(ctx, req, lrpdb) == nil { - log.Info("Cannot initialize config map for pdb.........:" + lrpdb.Spec.LRPDBName) - return nil - } - log.Info("Call...........:ApplyConfigMap(ctx, req, lrpdb)") - Cardinality, _ := r.ApplyConfigMap(ctx, req, lrpdb) - log.Info("Cardnality:....:" + strconv.Itoa(int(Cardinality))) - if Cardinality == 0 { - return nil + var apiResponse restSQLCollection + json.Unmarshal([]byte(bodyBytes), &apiResponse) + fmt.Printf("===> %#v\n", apiResponse) + fmt.Printf("===> %+v\n", apiResponse) + + errFound := false + for _, sqlItem := range apiResponse.Items { + if sqlItem.ErrorDetails != "" { + log.Info("LREST Error - Oracle Error Code :" + strconv.Itoa(sqlItem.ErrorCode)) + if !errFound { + lrpdb.Status.Msg = sqlItem.ErrorDetails + } + r.Recorder.Eventf(lrpdb, corev1.EventTypeWarning, "OraError", "%s", sqlItem.ErrorDetails) + errFound = true } + } + if errFound { + return "", errors.New("Oracle Error") } - return nil + + return respData, nil } -func NewCallLAPI(intr interface{}, ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB, url string, payload map[string]string, action string) (string, error) { +func NewCallAPISQL(intr interface{}, ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB, url string, payload interface{}, action string) (string, error) { var c client.Client var r logr.Logger var e record.EventRecorder + var TestBuffer string + var jsonMap map[string]interface{} var err error recpdb, ok1 := intr.(*LRPDBReconciler) @@ -2143,7 +2901,7 @@ func NewCallLAPI(intr interface{}, ctx context.Context, req ctrl.Request, lrpdb r = reccdb.Log } - log := r.WithValues("NewCallLAPI", req.NamespacedName) + log := r.WithValues("NewCallAPISQL", req.NamespacedName) secret := &corev1.Secret{} @@ -2203,8 +2961,7 @@ func NewCallLAPI(intr interface{}, ctx context.Context, req ctrl.Request, lrpdb RootCAs: caCertPool} */ tlsConf := &tls.Config{Certificates: []tls.Certificate{certificate}, - RootCAs: caCertPool, - //MinVersion: tls.VersionTLS12, + RootCAs: caCertPool, CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256}, PreferServerCipherSuites: true, CipherSuites: []uint16{ @@ -2245,7 +3002,7 @@ func NewCallLAPI(intr interface{}, ctx context.Context, req ctrl.Request, lrpdb return "", err } privKey := string(secret.Data[lrpdb.Spec.LRPDBPriKey.Secret.Key]) - webUser, err := lrcommons.CommonDecryptWithPrivKey(privKey, webUserEnc, req) + webUser, err := CommonDecryptWithPrivKey2(privKey, webUserEnc, req) // Get Web Server User Password secret = &corev1.Secret{} @@ -2260,26 +3017,52 @@ func NewCallLAPI(intr interface{}, ctx context.Context, req ctrl.Request, lrpdb } webUserPwdEnc := string(secret.Data[lrpdb.Spec.WebLrpdbServerPwd.Secret.Key]) webUserPwdEnc = strings.TrimSpace(webUserPwdEnc) - webUserPwd, err := lrcommons.CommonDecryptWithPrivKey(privKey, webUserPwdEnc, req) + webUserPwd, err := CommonDecryptWithPrivKey2(privKey, webUserPwdEnc, req) + + var Httpreq *http.Request - var httpreq *http.Request if action == "GET" { - httpreq, err = http.NewRequest(action, url, nil) + Httpreq, err = http.NewRequest(action, url, nil) } else { - jsonValue, _ := json.Marshal(payload) - httpreq, err = http.NewRequest(action, url, bytes.NewBuffer(jsonValue)) - } - - if err != nil { - log.Info("Unable to create HTTP Request for LRPDB : "+lrpdb.Name, "err", err.Error()) - return "", err + /* Section to execute sql and plsql code */ + payloadsql, ok4 := payload.(*PLSQLPayLoad) + if ok4 { + TestBuffer = ParseSQLPayload(payloadsql) + json.Unmarshal([]byte(TestBuffer), &jsonMap) + jsonValue, _ := json.Marshal(jsonMap) + Httpreq, err = http.NewRequest(action, url, bytes.NewBuffer(jsonValue)) + if Bit(lrpdb.Spec.Debug, DBGAPI) { + fmt.Println("=========================PLSQLDEBUG==============================") + fmt.Println(string(jsonValue)) + fmt.Println("=========================PLSQLDEBUG==============================") + } + if err != nil { + log.Info("Unable to create HTTP Request for LRPDB : "+lrpdb.Name, "err", err.Error()) + return "", err + } + } + /* Section to execute standard pdb operation */ + payloadpdb, ok3 := payload.(map[string]string) + if ok3 { + jsonValue, _ := json.Marshal(payloadpdb) + Httpreq, err = http.NewRequest(action, url, bytes.NewBuffer(jsonValue)) + if Bit(lrpdb.Spec.Debug, DBGAPI) { + fmt.Println("=========================PLSQLDEBUG==============================") + fmt.Println(string(jsonValue)) + fmt.Println("=========================PLSQLDEBUG==============================") + } + if err != nil { + log.Info("Unable to create HTTP Request for LRPDB : "+lrpdb.Name, "err", err.Error()) + return "", err + } + } } - httpreq.Header.Add("Accept", "application/json") - httpreq.Header.Add("Content-Type", "application/json") - httpreq.SetBasicAuth(webUser, webUserPwd) + Httpreq.Header.Add("Accept", "application/json") + Httpreq.Header.Add("Content-Type", "application/json") + Httpreq.SetBasicAuth(webUser, webUserPwd) - resp, err := httpclient.Do(httpreq) + resp, err := httpclient.Do(Httpreq) if err != nil { errmsg := err.Error() log.Error(err, "Failed - Could not connect to LREST Pod", "err", err.Error()) @@ -2288,7 +3071,6 @@ func NewCallLAPI(intr interface{}, ctx context.Context, req ctrl.Request, lrpdb return "", err } - e.Eventf(lrpdb, corev1.EventTypeWarning, "Done", lrpdb.Spec.CDBResName) if resp.StatusCode != http.StatusOK { bb, _ := ioutil.ReadAll(resp.Body) @@ -2313,7 +3095,6 @@ func NewCallLAPI(intr interface{}, ctx context.Context, req ctrl.Request, lrpdb } fmt.Printf("\n================== APIERR ======================\n") fmt.Printf("%+v \n", apiErr) - fmt.Printf(string(bb)) fmt.Printf("URL=%s\n", url) fmt.Printf("resp.StatusCode=%s\n", strconv.Itoa(resp.StatusCode)) fmt.Printf("\n================== APIERR ======================\n") @@ -2330,6 +3111,8 @@ func NewCallLAPI(intr interface{}, ctx context.Context, req ctrl.Request, lrpdb } respData := string(bodyBytes) fmt.Print("CALL API return msg.....:") + fmt.Printf("%s\n", respData) + fmt.Println(string(bodyBytes)) var apiResponse restSQLCollection @@ -2356,26 +3139,90 @@ func NewCallLAPI(intr interface{}, ctx context.Context, req ctrl.Request, lrpdb return respData, nil } -func (r *LRPDBReconciler) GetSqlCode(rsp string, sqlcode *int) error { - log := r.Log.WithValues("GetSqlCode", "callAPI(...)") +func ParseSQLPayload(payload *PLSQLPayLoad) string { + var Buffer string - var objmap map[string]interface{} - if err := json.Unmarshal([]byte(rsp), &objmap); err != nil { - log.Error(err, "failed to get respData from callAPI", "err", err.Error()) - return err + cnt := 0 + Buffer = "{" + for key, value := range payload.Values { + Buffer += "\"" + key + "\" : \"" + value + "\"," } - *sqlcode = int(objmap["sqlcode"].(float64)) - log.Info("sqlcode.......:ora-" + strconv.Itoa(*sqlcode)) - if *sqlcode != 0 { - switch strconv.Itoa(*sqlcode) { - case "65019": /* already open */ - return nil - case "65020": /* already closed */ - return nil + Nelem := len(payload.Sqltokens) + fmt.Printf("ParseSQLPayload :: Num tokens %d\n", Nelem) + Buffer += "\"Sqltokens\":[" + for _, value := range payload.Sqltokens { + Buffer += "\"" + value + "\"" + if cnt < (Nelem - 1) { + Buffer += "," } - err := fmt.Errorf("%v", sqlcode) + cnt++ + } + + Buffer += "]}" + fmt.Printf("ParseSQLPayload :: %s\n", Buffer) + return Buffer +} + +func (r *LRPDBReconciler) AutoDiscoverActivation(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB, active bool) error { + + log := r.Log.WithValues("AutoDiscoverActivation", req.NamespacedName) + if active == false { + log.Info("Disable autodiscover") + } else { + log.Info("Enable autodiscover") + } + + var lrest dbapi.LREST + lrestResName := lrpdb.Spec.CDBResName + lrestNamespace := lrpdb.Spec.CDBNamespace + err := r.Get(context.Background(), client.ObjectKey{ + Namespace: lrestNamespace, + Name: lrestResName, + }, &lrest) + lrest.Spec.PdbAutoDiscover = active + err = r.Update(context.TODO(), &lrest) + if err != nil { return err } + return nil } + +func (r *LRPDBReconciler) GetPdbSize(ctx context.Context, req ctrl.Request, lrpdb *dbapi.LRPDB, pdbaname string) string { + log := r.Log.WithValues("GetPdbSize", req.NamespacedName) + var PdbSize string + // if we cannot get the pdbsize ,whatever reason, we return "undefined" size + lrest, err := r.getLRESTResource(ctx, req, lrpdb) + if err != nil { + log.Info("Cannot get lrest server") + return "undefined" + } + + lrpdbName := lrpdb.Spec.LRPDBName + url := r.BaseUrl(ctx, req, lrpdb, lrest) + lrpdbName + "/status/" + + respData, err := NewCallAPISQL(r, ctx, req, lrpdb, url, nil, "GET") + if err != nil { + log.Error(err, "Failure NewCallAPISQL( "+url+")", "err", err.Error()) + return "undefined" + } + + var objmap map[string]interface{} + if err := json.Unmarshal([]byte(respData), &objmap); err != nil { + log.Error(err, "Failed json.Unmarshal :"+lrpdbName, "err", err.Error()) + return "undefined" + } + + PdbSize = fmt.Sprintf("%4.2f", ((objmap["total_size"].(float64))/1024/1024/1024)) + "G" + return PdbSize +} + +func (r *LRPDBReconciler) UpdateStatus(ctx context.Context, req ctrl.Request, lrpdb *databasev4.LRPDB) { + log := r.Log.WithValues("UpdateStatus", req.NamespacedName) + err := r.Status().Update(ctx, lrpdb) + if err != nil { + fmt.Printf("[1]Error updating status\n") + log.Error(err, err.Error()) + } +} diff --git a/controllers/database/oraclerestart_controller.go b/controllers/database/oraclerestart_controller.go new file mode 100644 index 00000000..b42f8302 --- /dev/null +++ b/controllers/database/oraclerestart_controller.go @@ -0,0 +1,3529 @@ +/* +** Copyright (c) 2022 Oracle and/or its affiliates. +** +** The Universal Permissive License (UPL), Version 1.0 +** +** Subject to the condition set forth below, permission is hereby granted to any +** person obtaining a copy of this software, associated documentation and/or data +** (collectively the "Software"), free of charge and under any and all copyright +** rights in the Software, and any and all patent rights owned or freely +** licensable by each licensor hereunder covering either (i) the unmodified +** Software as contributed to or provided by such licensor, or (ii) the Larger +** Works (as defined below), to deal in both +** +** (a) the Software, and +** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +** one is included with the Software (each a "Larger Work" to which the Software +** is contributed by such licensors), +** +** without restriction, including without limitation the rights to copy, create +** derivative works of, display, perform, and distribute the Software and make, +** use, sell, offer for sale, import, export, have made, and have sold the +** Software and the Larger Work(s), and to sublicense the foregoing rights on +** either these or other terms. +** +** This license is subject to the following condition: +** The above copyright notice and either this complete permission notice or at +** a minimum a reference to the UPL must be included in all copies or +** substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +** SOFTWARE. + */ + +package controllers + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "reflect" + "strconv" + "strings" + "time" + + "github.com/go-logr/logr" + oraclerestartdb "github.com/oracle/oracle-database-operator/apis/database/v4" + oraclerestartcommon "github.com/oracle/oracle-database-operator/commons/oraclerestart" + utils "github.com/oracle/oracle-database-operator/commons/oraclerestart/utils" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + storagev1 "k8s.io/api/storage/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/tools/record" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +// OracleRestartReconciler reconciles a OracleRestart object +type OracleRestartReconciler struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme + Config *rest.Config + kubeClient kubernetes.Interface + kubeConfig clientcmd.ClientConfig + Recorder record.EventRecorder +} + +const oracleRestartFinalizer = "database.oracle.com/oraclerestartfinalizer" + +//+kubebuilder:rbac:groups="database.oracle.com",resources=oraclerestarts,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups="database.oracle.com",resources=oraclerestarts/status,verbs=get;update;patch +//+kubebuilder:rbac:groups="database.oracle.com",resources=oraclerestarts/finalizers,verbs=get;create;update;patch;delete +//+kubebuilder:rbac:groups="",resources=pods;pods/log;pods/exec;secrets;endpoints;services;events;configmaps;persistentvolumes;persistentvolumeclaims;namespaces,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups="apps",resources=statefulsets,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups='',resources=statefulsets/finalizers,verbs=get;list;watch;create;update;patch;delete + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the OracleRestart object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.6.4/pkg/reconcile +func (r *OracleRestartReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + + //ctx := context.Background() + _ = r.Log.WithValues("oraclerestart", req.NamespacedName) + r.Log.Info("Reconcile requested") + var result ctrl.Result + var err error + completed := false + blocked := false + // var svcType string + var nilErr error = nil + + resultNq := ctrl.Result{Requeue: false} + resultQ := ctrl.Result{Requeue: true, RequeueAfter: 60 * time.Second} + + oracleRestart := &oraclerestartdb.OracleRestart{} + configMapData := make(map[string]string) + + // Execute for every reconcile + defer r.updateReconcileStatus(oracleRestart, ctx, req, &result, &err, &blocked, &completed) + + err = r.Get(ctx, types.NamespacedName{Namespace: req.Namespace, Name: req.Name}, oracleRestart) + if err != nil { + if apierrors.IsNotFound(err) { + r.Log.Info("Resource not found") + return requeueN, nil + } + r.Log.Error(err, err.Error()) + oracleRestart.Spec.IsFailed = true + return resultQ, err + } + + // Retrieve the old spec from annotations + oldSpec, err := r.GetOldSpec(oracleRestart) + if err != nil { + r.Log.Error(err, "Failed to update old spec annotation") + oracleRestart.Spec.IsFailed = true + return resultQ, nil + } + + // Initialize oracleRestart.Status if it's not already initialized + if oracleRestart.Status.ConfigParams == nil { + oracleRestart.Status.ConfigParams = &oraclerestartdb.InitParams{} + } + + // Initialize ConfigParams fields if they are not already initialized + if oracleRestart.Status.ConfigParams.DbHome == "" { + oracleRestart.Status.ConfigParams.DbHome = string(oraclerestartdb.OracleRestartFieldNotDefined) + } + if oracleRestart.Status.DbState == "" { + oracleRestart.Status.State = string(oraclerestartdb.OracleRestartPendingState) + oracleRestart.Status.DbState = string(oraclerestartdb.OracleRestartPendingState) + oracleRestart.Status.Role = string(oraclerestartdb.OracleRestartFieldNotDefined) + oracleRestart.Status.ConnectString = string(oraclerestartdb.OracleRestartFieldNotDefined) + oracleRestart.Status.PdbConnectString = string(oraclerestartdb.OracleRestartFieldNotDefined) + oracleRestart.Status.ExternalConnectString = string(oraclerestartdb.OracleRestartFieldNotDefined) + oracleRestart.Status.ReleaseUpdate = string(oraclerestartdb.OracleRestartFieldNotDefined) + oracleRestart.Status.ConfigParams.DbHome = string(oraclerestartdb.OracleRestartFieldNotDefined) + oracleRestart.Status.ConfigParams.GridHome = string(oraclerestartdb.OracleRestartFieldNotDefined) + r.Status().Update(ctx, oracleRestart) + } + + // Kube Client Config Setup + if r.kubeConfig == nil && r.kubeClient == nil { + r.kubeConfig, r.kubeClient, err = oraclerestartcommon.GetRacK8sClientConfig(r.Client) + if err != nil { + return ctrl.Result{}, err + } + } + + // Manage OracleRestart Deletion , if delete topology is called + err = r.manageOracleRestartDeletion(req, ctx, oracleRestart) + if err != nil { + result = resultNq + return result, err + } + + // cleanup RAC Instance + // This is a special case if user wants just delete the pod and recreate it + _, err = r.cleanupOracleRestartInstance(req, ctx, oracleRestart) + if err != nil { + result = resultQ + r.Log.Info(err.Error()) + return result, nilErr + } + + // debugging + err = checkOracleRestartState(oracleRestart) + if err != nil { + result = resultQ + r.Log.Info("Oracle Restart object is in restricted state, returning back") + return result, nilErr + } + + // First Validate + err = r.validateSpex(oracleRestart, oldSpec, ctx) + if err != nil { + r.Log.Info("Spec validation failed") + result = resultQ + r.Log.Info(err.Error()) + return result, nilErr + } + + err = r.setDefaults(oracleRestart) + if err != nil { + // time.Sleep(30 * time.Second) + result = resultQ + r.Log.Info(err.Error()) + return result, nilErr + } + + // Update RAC ConfigParams + err = r.updateGiConfigParamStatus(oracleRestart) + if err != nil { + // time.Sleep(30 * time.Second) + result = resultQ + r.Log.Info(err.Error()) + return result, nilErr + } + + err = r.updateDbConfigParamStatus(oracleRestart) + if err != nil { + // time.Sleep(30 * time.Second) + result = resultQ + r.Log.Info(err.Error()) + err = nilErr + return result, err + } + + if oracleRestart.Spec.ConfigParams != nil { + configMapData, err = r.generateConfigMap(oracleRestart) + if err != nil { + result = resultNq + return result, err + } + } + var svcType string + + result, err = r.createOrReplaceService(ctx, oracleRestart, oraclerestartcommon.BuildServiceDefForOracleRestart(oracleRestart, 0, oracleRestart.Spec.InstDetails, "local")) + if err != nil { + result = resultNq + return result, err + } + + if len(oracleRestart.Spec.NodePortSvc.PortMappings) != 0 { + result, err = r.createOrReplaceService(ctx, oracleRestart, oraclerestartcommon.BuildExternalServiceDefForOracleRestart(oracleRestart, 0, oracleRestart.Spec.InstDetails, svcType, "nodeport")) + if err != nil { + result = resultNq + return result, err + } + } + + if len(oracleRestart.Spec.LbService.PortMappings) != 0 { + result, err = r.createOrReplaceService(ctx, oracleRestart, oraclerestartcommon.BuildExternalServiceDefForOracleRestart(oracleRestart, 0, oracleRestart.Spec.InstDetails, svcType, "lbservice")) + if err != nil { + result = resultNq + return result, err + } + } + + r.ensureAsmStorageStatus(oracleRestart) + isNewSetup := true + for _, n := range oracleRestart.Status.OracleRestartNodes { + if n.NodeDetails.State == "AVAILABLE" && + n.NodeDetails.InstanceState == "OPEN" && + n.NodeDetails.PodState == "AVAILABLE" && + n.NodeDetails.ClusterState == "HEALTHY" && + len(n.NodeDetails.MountedDevices) > 0 { + // Found at least one node that is healthy and operational + isNewSetup = false + break + } + } + + isDiskChanged := false + addedAsmDisks := []string{} + removedAsmDisks := []string{} + if !isNewSetup { + if oldSpec != nil { // old spec required for comparison + // Check each RAC node for mounted devices + for index, _ := range oracleRestart.Status.OracleRestartNodes { + addedAsmDisks, removedAsmDisks = getAddedAndRemovedDisks(oracleRestart, oldSpec, index) + if len(addedAsmDisks) > 0 && len(removedAsmDisks) > 0 { + r.Log.Info("Detected Addition as well as Deletion, setup cannot run both together", "addedAsmDisks", addedAsmDisks, "removedAsmDisks", removedAsmDisks) + result = resultQ + return result, err + } + // You can now use the added and removed disks as needed + if len(addedAsmDisks) > 0 { + r.Log.Info("Detected Addition of ASM Disks:", "addedAsmDisks", addedAsmDisks) + } + + if len(removedAsmDisks) > 0 { + r.Log.Info("Detected Removal of ASM Disks:", "removedAsmDisks", removedAsmDisks) + } + if len(addedAsmDisks) > 0 || len(removedAsmDisks) > 0 { + + isDiskChanged = true + break // Exit loop once a difference is found + } + } + } + } + + var autoUpdate bool + // Initialize autoUpdate based on the AutoUpdate field in the specification + switch strings.ToLower(oracleRestart.Spec.AsmStorageDetails.AutoUpdate) { + case "false": + // If AutoUpdate is explicitly set to "false" by the user, set autoUpdate to false + autoUpdate = false + r.Log.Info("Initialized autoUpdate from provided specification", "autoUpdate", autoUpdate) + default: + // If AutoUpdate is not set or is set to any value other than "false", default to true + autoUpdate = true + r.Log.Info("Initialized autoUpdate as true (default)") + } + + // PV Creation + if oraclerestartcommon.CheckStorageClass(oracleRestart) == "NOSC" { + if isNewSetup || isDiskChanged { + if oracleRestart.Spec.AsmStorageDetails != nil { + for _, diskBySize := range oracleRestart.Spec.AsmStorageDetails.DisksBySize { + for _, diskName := range diskBySize.DiskNames { + pvName := oraclerestartcommon.GetAsmPvName(oracleRestart.Name, diskName, oracleRestart) + pvVolume := oraclerestartcommon.VolumePVForASM( + oracleRestart, + diskName, + diskBySize.StorageSizeInGb, + oracleRestart.Spec.AsmStorageDetails, + pvName, + r.Client, + ) + // if pvVolume == nil { + // r.Log.Info("VolumePVForASM returned nil for Dynamic Provisioning", "diskName", diskName, "index", index) + // continue // or return error + // } + _, result, err = r.createOrReplaceAsmPv(ctx, oracleRestart, pvVolume) + if err != nil { + result = resultNq + return result, err + } + } + } + } + } + + // PVC Creation + if isNewSetup || isDiskChanged { + if oracleRestart.Spec.AsmStorageDetails != nil { + for _, diskBySize := range oracleRestart.Spec.AsmStorageDetails.DisksBySize { + for _, diskName := range diskBySize.DiskNames { + dgType := oraclerestartcommon.CheckDiskInAsmDeviceList(oracleRestart, diskName) + pvcName := oraclerestartcommon.GetAsmPvcName(oracleRestart.Name, diskName, oracleRestart) + pvcVolume := oraclerestartcommon.VolumePVCForASM( + oracleRestart, + diskBySize.StorageSizeInGb, + diskName, + diskBySize.StorageSizeInGb, + oracleRestart.Spec.AsmStorageDetails, + pvcName, + dgType, + r.Client, + ) + + _, result, err = r.createOrReplaceAsmPvC(ctx, oracleRestart, pvcVolume) + if err != nil { + result = resultNq + return result, err + } + } + } + } + } + } + + index := 0 + isLast := true + oldState := oracleRestart.Status.State + if !utils.CheckStatusFlag(oracleRestart.Spec.InstDetails.IsDelete) { + switch { + case isNewSetup && !isDiskChanged: + cmName := oracleRestart.Spec.InstDetails.Name + oracleRestart.Name + "-cmap" + cm := oraclerestartcommon.ConfigMapSpecs(oracleRestart, configMapData, cmName) + result, configmapEnvKeyChanged, err := r.createConfigMap(ctx, *oracleRestart, cm) + if err != nil { + // handle error + } + if err != nil { + result = resultNq + return result, err + } + err = oraclerestartcommon.CreateServiceAccountIfNotExists(oracleRestart, r.Client) + if err != nil { + result = resultNq + return result, err + } + + oracleRestart.Spec.InstDetails.EnvFile = cmName + dep := oraclerestartcommon.BuildStatefulSetForOracleRestart(oracleRestart, oracleRestart.Spec.InstDetails, r.Client) + result, err = r.createOrReplaceSfs(ctx, req, *oracleRestart, dep, index, isLast, oldState, configmapEnvKeyChanged) + if err != nil { + result = resultNq + return result, err + } + + case isDiskChanged && !isNewSetup: + if oraclerestartcommon.CheckStorageClass(oracleRestart) == "NOSC" { + if len(addedAsmDisks) > 0 { + err = r.validateASMDisks(oracleRestart, ctx) + if err != nil { + result = resultQ + r.Log.Info(err.Error()) + err = nilErr + return result, err + } + if ready, err := checkDaemonSetStatus(ctx, r, oracleRestart); err != nil || !ready { + msg := "Any of provided ASM Disks are invalid, pls check disk-check daemon set for logs. Fix the asm disk to the valid one and redeploy." + r.Log.Info(msg) + err = r.cleanupDaemonSet(oracleRestart, ctx) + if err != nil { + result = resultQ + r.Log.Info(err.Error()) + err = nilErr + return result, err + } + addedAsmDisksMap := make(map[string]bool) + for _, disk := range addedAsmDisks { + addedAsmDisksMap[disk] = true + } + for pindex, diskBySize := range oracleRestart.Spec.AsmStorageDetails.DisksBySize { + for cindex, diskName := range diskBySize.DiskNames { + if _, ok := addedAsmDisksMap[diskName]; ok { + // r.Log.Info("Found disk at index", "index", index) + + err = oraclerestartcommon.DelORestartPVC(oracleRestart, pindex, cindex, diskName, oracleRestart.Spec.AsmStorageDetails, r.Client, r.Log) + if err != nil { + return resultQ, err + } + + err = oraclerestartcommon.DelORestartPv(oracleRestart, pindex, cindex, diskName, oracleRestart.Spec.AsmStorageDetails, r.Client, r.Log) + if err != nil { + return resultQ, err + } + } + } + } + + if err = r.SetCurrentSpec(ctx, oracleRestart, req); err != nil { + r.Log.Error(err, "Failed to set current spec annotation") + oracleRestart.Spec.IsFailed = true + return resultQ, err + } + return result, errors.New(msg) + } else { + r.Log.Info("Provided ASM Disks are valid, proceeding further") + } + } + } + cmName := oracleRestart.Spec.InstDetails.Name + oracleRestart.Name + "-cmap" + configMapDataAutoUpdate, err := r.generateConfigMapAutoUpdate(ctx, oracleRestart, cmName) + if err != nil { + result = resultNq + return result, err + } + result, err = r.updateConfigMap(ctx, oracleRestart, configMapDataAutoUpdate, cmName) + if err != nil { + result = resultNq + return result, err + } + r.Log.Info("Config Map updated successfully with new asm details") + oracleRestart.Spec.InstDetails.EnvFile = cmName + result, err = r.createOrReplaceSfsAsm(ctx, req, oracleRestart, oraclerestartcommon.BuildStatefulSetForOracleRestart(oracleRestart, oracleRestart.Spec.InstDetails, r.Client), autoUpdate, index, isLast, oldSpec) + if err != nil { + if autoUpdate { + result = resultQ + } else { + result = resultNq + } + result = resultQ + return result, err + } + } + } + if oraclerestartcommon.CheckStorageClass(oracleRestart) == "NOSC" { + if len(addedAsmDisks) > 0 { + + err = r.cleanupDaemonSet(oracleRestart, ctx) + if err != nil { + result = resultQ + r.Log.Info(err.Error()) + err = nilErr + return result, err + } + } + } + + if oracleRestart.Spec.EnableOns == "enable" || oracleRestart.Spec.EnableOns == "disable" { + OraRestartSpex := oracleRestart.Spec.InstDetails + orestartSfSet, err := oraclerestartcommon.CheckSfset(OraRestartSpex.Name, oracleRestart, r.Client) + if err != nil { + r.updateOracleRestartInstStatus(oracleRestart, ctx, req, OraRestartSpex, string(oraclerestartdb.StatefulSetNotFound), r.Client, false) + return ctrl.Result{}, err + } + + podList, err := oraclerestartcommon.GetPodList(orestartSfSet.Name, oracleRestart, r.Client, oracleRestart.Spec.InstDetails) + if err != nil { + r.Log.Error(err, "Failed to list pods") + return ctrl.Result{}, err + } + // default is to start + onsOp := "start" + if oracleRestart.Spec.EnableOns == "disable" { + onsOp = "stop" + } + + err = r.updateONS(ctx, podList, oracleRestart, onsOp) + if err != nil { + return ctrl.Result{}, err + } + } + + err = r.expandStorageClassSWVolume(ctx, oracleRestart, oldSpec) + if err != nil { + return ctrl.Result{}, err + } + + completed = true + // // Update the current spec after successful reconciliation + if err = r.SetCurrentSpec(ctx, oracleRestart, req); err != nil { + r.Log.Error(err, "Failed to set current spec annotation") + oracleRestart.Spec.IsFailed = true + return resultQ, err + } + r.Log.Info("Reconcile completed. Requeuing....") + // uncomment this only to debugging null pointer exception + // r.updateReconcileStatus(OracleRestart, ctx, req, &result, &err, &blocked, &completed) + // time.Sleep(1 * time.Minute) + return resultQ, nil +} + +// Function to check the RAC topology state and return/dont proceed when matched. +func checkOracleRestartState(oracleRestart *oraclerestartdb.OracleRestart) error { + if oracleRestart.Status.State == string(oraclerestartdb.OracleRestartProvisionState) || + oracleRestart.Status.State == string(oraclerestartdb.OracleRestartUpdateState) || + oracleRestart.Status.State == string(oraclerestartdb.OracleRestartPodAvailableState) || + oracleRestart.Status.State == string(oraclerestartdb.OracleRestartAddInstState) || + oracleRestart.Status.State == string(oraclerestartdb.OracleRestartDeletingState) || + oracleRestart.Status.State == string(oraclerestartdb.OracleRestartFailedState) || + oracleRestart.Status.State == string(oraclerestartdb.OracleRestartManualState) || + oracleRestart.Spec.IsFailed || + oracleRestart.Spec.IsManual { + return errors.New(fmt.Sprintf("oracle restart database is in a restricted state: %s", oracleRestart.Status.State)) + } + return nil +} + +func (r *OracleRestartReconciler) generateConfigMapAutoUpdate(ctx context.Context, instance *oraclerestartdb.OracleRestart, cmName string) (map[string]string, error) { + // Fetch the existing ConfigMap + cm := &corev1.ConfigMap{} + err := r.Client.Get(ctx, types.NamespacedName{Name: cmName, Namespace: instance.Namespace}, cm) + if err != nil { + return nil, err + } + r.Log.Info("Updating existing configmap") + // Get the existing config map data + configMapData := cm.Data + envFileData := configMapData["envfile"] + envVars := make(map[string]string) + + // Parse the envfile into a map + lines := strings.Split(envFileData, "\r\n") + for _, line := range lines { + parts := strings.SplitN(line, "=", 2) + if len(parts) == 2 { + envVars[parts[0]] = parts[1] + } + } + + // Get latest ASM devices + asmDevices := oraclerestartcommon.GetAsmDevices(instance) + + // Update selective fields + if instance.Spec.ConfigParams.CrsAsmDeviceList != "" { + envVars["CRS_ASM_DEVICE_LIST"] = instance.Spec.ConfigParams.CrsAsmDeviceList + } else { + envVars["CRS_ASM_DEVICE_LIST"] = asmDevices + } + + if instance.Spec.ConfigParams.RecoAsmDeviceList != "" { + envVars["RECO_ASM_DEVICE_LIST"] = instance.Spec.ConfigParams.RecoAsmDeviceList + } else if instance.Status.ConfigParams != nil && instance.Status.ConfigParams.RecoAsmDeviceList != "" { + envVars["RECO_ASM_DEVICE_LIST"] = instance.Status.ConfigParams.RecoAsmDeviceList + } + + if instance.Spec.ConfigParams.RedoAsmDeviceList != "" { + envVars["REDO_ASM_DEVICE_LIST"] = instance.Spec.ConfigParams.RedoAsmDeviceList + } else if instance.Status.ConfigParams != nil && instance.Status.ConfigParams.RedoAsmDeviceList != "" { + envVars["REDO_ASM_DEVICE_LIST"] = instance.Status.ConfigParams.RedoAsmDeviceList + } + + // Convert the envVars map back to a single string + var updatedData []string + for key, value := range envVars { + updatedData = append(updatedData, fmt.Sprintf("%s=%s", key, value)) + } + configMapData["envfile"] = strings.Join(updatedData, "\r\n") + + return configMapData, nil +} + +func (r *OracleRestartReconciler) updateConfigMap(ctx context.Context, instance *oraclerestartdb.OracleRestart, configMapData map[string]string, cmName string) (ctrl.Result, error) { + cm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: cmName, + Namespace: instance.Namespace, + }, + Data: configMapData, + } + + err := r.Client.Update(ctx, cm) + if err != nil { + return ctrl.Result{}, err + } + + return ctrl.Result{}, nil +} + +// ############################################################################# +// +// Update each reconcile condition/status +// +// ############################################################################# +func (r *OracleRestartReconciler) updateReconcileStatus(oracleRestart *oraclerestartdb.OracleRestart, ctx context.Context, req ctrl.Request, result *ctrl.Result, err *error, blocked *bool, completed *bool) { + const maxRetries = 5 + const retryDelay = 2 * time.Second + + // First update RAC topology + podNames, nodeDetails, err1 := r.updateOracleRestartInstTopologyStatus(oracleRestart, ctx, req) + + // Update RAC DB topology + if err1 == nil { + _ = r.updateoraclerestartdbTopologyStatus(oracleRestart, ctx, req, podNames, nodeDetails) + } else { + r.Log.Info("Error during Oracle Restart update", "err1", err1) + } + + var condition metav1.Condition + + if *completed { + condition = metav1.Condition{ + Type: string(oraclerestartdb.CrdReconcileCompeleteState), + LastTransitionTime: metav1.Now(), + ObservedGeneration: oracleRestart.GetGeneration(), + Reason: string(oraclerestartdb.OracleRestartCrdReconcileCompleteReason), + Message: "reconcile completed successfully", // no error text + Status: metav1.ConditionTrue, + } + } else if *blocked { + condition = metav1.Condition{ + Type: string(oraclerestartdb.CrdReconcileWaitingState), + LastTransitionTime: metav1.Now(), + ObservedGeneration: oracleRestart.GetGeneration(), + Reason: string(oraclerestartdb.OracleRestartCrdReconcileWaitingReason), + Message: "reconcile is waiting on dependencies", // neutral message + Status: metav1.ConditionTrue, + } + } else if result.Requeue { + condition = metav1.Condition{ + Type: string(oraclerestartdb.CrdReconcileQueuedState), + LastTransitionTime: metav1.Now(), + ObservedGeneration: oracleRestart.GetGeneration(), + Reason: string(oraclerestartdb.CrdReconcileQueuedReason), + Message: "reconcile has been queued", // neutral message + Status: metav1.ConditionTrue, + } + } else if err != nil && *err != nil { + condition = metav1.Condition{ + Type: string(oraclerestartdb.CrdReconcileErrorState), + LastTransitionTime: metav1.Now(), + ObservedGeneration: oracleRestart.GetGeneration(), + Reason: string(oraclerestartdb.CrdReconcileErrorReason), + Message: (*err).Error(), // show actual error only here + Status: metav1.ConditionTrue, + } + } else { + return + } + + if len(oracleRestart.Status.Conditions) > 0 { + meta.RemoveStatusCondition(&oracleRestart.Status.Conditions, condition.Type) + } + meta.SetStatusCondition(&oracleRestart.Status.Conditions, condition) + + if oracleRestart.Status.State == string(oraclerestartdb.OracleRestartPodAvailableState) && + condition.Type == string(oraclerestartdb.CrdReconcileCompeleteState) { + r.Log.Info("All validations and updation are completed. Changing State to AVAILABLE") + oracleRestart.Status.State = string(oraclerestartdb.OracleRestartAvailableState) + } + + for attempt := 0; attempt < maxRetries; attempt++ { + // Fetch the latest version of the object + latestInstance := &oraclerestartdb.OracleRestart{} + err := r.Client.Get(ctx, req.NamespacedName, latestInstance) + if err != nil { + r.Log.Error(err, "Failed to fetch the latest version of Oracle Restart instance, retrying...") + time.Sleep(retryDelay) + continue // Retry fetching the latest instance + } + + // Merge the instance fields into latestInstance + err = mergeInstancesFromLatest(oracleRestart, latestInstance) + if err != nil { + r.Log.Error(err, "Failed to merge instances, retrying...") + time.Sleep(retryDelay) + continue // Retry merging + } + + // Update the ResourceVersion of instance from latestInstance to avoid conflict + oracleRestart.ResourceVersion = latestInstance.ResourceVersion + err = r.Client.Status().Patch(ctx, oracleRestart, client.MergeFrom(latestInstance)) + + if err != nil { + if apierrors.IsConflict(err) { + r.Log.Info("Conflict detected, retrying update...", "attempt", attempt+1) + time.Sleep(retryDelay) + continue // Retry on conflict + } + r.Log.Error(err, "Failed to update the Oracle Restart DB instance, retrying...") + time.Sleep(retryDelay) + continue // Retry on other errors + } + + // If update was successful, exit the loop + r.Log.Info("Updated Oracle Restart instance status successfully", "Instance", oracleRestart.Name) + break + } + + r.Log.Info("Returning from updateReconcileStatus") +} + +// ############################################################################# +// +// Validate the CRD specs +// +// ############################################################################# +func (r *OracleRestartReconciler) validateSpex(oracleRestart *oraclerestartdb.OracleRestart, oldSpec *oraclerestartdb.OracleRestartSpec, ctx context.Context) error { + var err error + eventReason := "Spec Error" + + //var eventMsgs []string + + r.Log.Info("Entering reconcile validation") + + //First check image pull secrets + if oracleRestart.Spec.ImagePullSecret != "" { + secret := &corev1.Secret{} + err = r.Get(ctx, types.NamespacedName{Name: oracleRestart.Spec.ImagePullSecret, Namespace: oracleRestart.Namespace}, secret) + if err != nil { + if apierrors.IsNotFound(err) { + // Secret not found + r.Recorder.Eventf(oracleRestart, corev1.EventTypeWarning, eventReason, err.Error()) + r.Log.Info(err.Error()) + return err + } + r.Log.Error(err, err.Error()) + return err + } + } + + // ======== Config Params Checks + // Checking Secret for ssh key + privKeyFlag, pubKeyFlag := oraclerestartcommon.GetSSHkey(oracleRestart, oracleRestart.Spec.SshKeySecret.Name, r.Client) + if !privKeyFlag { + return errors.New("private key name is not set to " + oracleRestart.Spec.SshKeySecret.PrivKeySecretName + " in SshKeySecret") + } + if !pubKeyFlag { + return errors.New("public key name is not set to " + oracleRestart.Spec.SshKeySecret.PubKeySecretName + " in SshKeySecret") + } + + // Checking Gi Responsefile + if oracleRestart.Spec.ConfigParams.GridResponseFile.ConfigMapName != "" { + giRspFlg, _ := oraclerestartcommon.GetGiResponseFile(oracleRestart, r.Client) + if !(giRspFlg) { + return errors.New("GridResponseFile name must be " + oracleRestart.Spec.ConfigParams.GridResponseFile.Name) + } + } + + if oracleRestart.Spec.ConfigParams.DbResponseFile.ConfigMapName != "" { + DbRspFlg, _ := oraclerestartcommon.GetDbResponseFile(oracleRestart, r.Client) + if !(DbRspFlg) { + return errors.New("DbResponseFile name must be " + oracleRestart.Spec.ConfigParams.DbResponseFile.Name) + } + } + r.ensureAsmStorageStatus(oracleRestart) + + specDisks := flattenDisksBySize(&oracleRestart.Spec) + + // Loop through all disk groups in Status.AsmDetails + for _, diskgroup := range oracleRestart.Status.AsmDetails.Diskgroup { + // Compare the number of disks in each diskgroup to the number of disks in Spec + if len(specDisks) < len(diskgroup.Disks) { + r.Log.Info("Validating Disk to remove for Diskgroup", "DiskgroupName", diskgroup.Name) + + // Call findDisksToRemove for this diskgroup to validate disk removal + _, err := findDisksToRemove(specDisks, diskgroup.Disks, oracleRestart) + if err != nil { + oracleRestart.Spec.IsFailed = true + return errors.New("required Disk is part of the disk group " + diskgroup.Name + " and cannot be removed. Review it manually.") + } + // else { + // r.Log.Info("Disks to be removed validated for Diskgroup", "DiskgroupName", diskgroup.Name) + // } + } + } + + // Validation to check if new ASM Disk is already part of POD; return error if it is. + // Loop through all disk groups in Status.AsmDetails + for _, diskgroup := range oracleRestart.Status.AsmDetails.Diskgroup { + // Compare the number of disks in each diskgroup to the number of disks in Spec + for _, diskgroupDisks := range diskgroup.Disks { + disks := strings.Split(diskgroupDisks, ",") + if len(specDisks) > len(disks) { + // r.Log.Info("Validating newly added Disk for Diskgroup", "DiskgroupName", diskgroup.Name) + + // Call findDisksToAdd to validate the newly added disks + _, err := findDisksToAdd(specDisks, diskgroup.Disks, oracleRestart, oldSpec) + if err != nil { + return err + } + // else { + // r.Log.Info("Disk to be added validated for Diskgroup", "DiskgroupName", diskgroup.Name, "Disk", fmt.Sprintf("%v", disk)) + // } + } + } + } + + // Checking the network cards in response files + + if oracleRestart.Spec.ConfigParams.GridResponseFile.ConfigMapName != "" { + _, err := oraclerestartcommon.CheckRspData(oracleRestart, r.Client, "networkInterfaceList", oracleRestart.Spec.ConfigParams.GridResponseFile.ConfigMapName, oracleRestart.Spec.ConfigParams.GridResponseFile.Name) + if err != nil { + oracleRestart.Spec.IsFailed = true + return err + } + + // Check if IsDelete is defined + switch isDeleteStr := oracleRestart.Spec.InstDetails.IsDelete; isDeleteStr { + case "true": + + r.Log.Info("Performing operation for IsDelete true") + + default: + // Validate network cards for both "false" and when IsDelete is not defined + if isDeleteStr != "" { + r.Log.Info("Unexpected value for IsDelete: " + isDeleteStr) + } + + } + + } + + r.Log.Info("Completed reconcile validation") + + return nil + +} + +// Helper function to flatten DisksBySize into a single slice of disk names +func flattenDisksBySize(oraclerestartdbSpec *oraclerestartdb.OracleRestartSpec) []string { + disksBySize := oraclerestartdbSpec.AsmStorageDetails.DisksBySize + var allDisks []string + for _, diskBySize := range disksBySize { + allDisks = append(allDisks, diskBySize.DiskNames...) + } + return allDisks +} + +// ############################################################################# +// +// Validate the CRD specs +// +// ############################################################################# +func (r *OracleRestartReconciler) validateASMDisks(oracleRestart *oraclerestartdb.OracleRestart, ctx context.Context) error { + //var eventMsgs []string + + r.Log.Info("Validate New ASM Disks") + desiredDaemonSet := oraclerestartcommon.BuildDiskCheckDaemonSet(oracleRestart) + + // Try to get the existing DaemonSet + existingDaemonSet := &appsv1.DaemonSet{} + err := r.Client.Get(ctx, types.NamespacedName{ + Name: desiredDaemonSet.Name, + Namespace: desiredDaemonSet.Namespace, + }, existingDaemonSet) + + if err != nil { + if apierrors.IsNotFound(err) { + // DaemonSet does not exist, so create it + r.Log.Info("Creating DaemonSet:", "desiredDaemonSet.Name", desiredDaemonSet.Name) + if err := r.Client.Create(ctx, desiredDaemonSet); err != nil { + oracleRestart.Spec.IsFailed = true + return err + } + } else { + // Some other error occurred in fetching the DaemonSet + oracleRestart.Spec.IsFailed = true + return err + } + } else { + // DaemonSet exists, so check if an update is necessary + if !reflect.DeepEqual(existingDaemonSet.Spec.Template.Spec.Volumes, desiredDaemonSet.Spec.Template.Spec.Volumes) { + // Update the existing DaemonSet with the desired state + r.Log.Info("Updating DaemonSet:", "desiredDaemonSet.Name", desiredDaemonSet.Name) + existingDaemonSet.Spec = desiredDaemonSet.Spec + if err := r.Client.Update(ctx, existingDaemonSet); err != nil { + return err + } + r.Log.Info("Updating Daemon set, takes upto 1 minute") + time.Sleep(1 * time.Second * 60) + //update takes times to terminate and recreate + } + } + + // r.Log.Info("Checking ASM DaemonSet Pod Status") + + return nil + +} + +func (r *OracleRestartReconciler) cleanupDaemonSet(OracleRestart *oraclerestartdb.OracleRestart, ctx context.Context) error { + // r.Log.Info("CleanupDaemonSet") + desiredDaemonSet := oraclerestartcommon.BuildDiskCheckDaemonSet(OracleRestart) + + // Try to get the existing DaemonSet + existingDaemonSet := &appsv1.DaemonSet{} + err := r.Client.Get(ctx, types.NamespacedName{ + Name: desiredDaemonSet.Name, + Namespace: desiredDaemonSet.Namespace, + }, existingDaemonSet) + + if err != nil { + if apierrors.IsNotFound(err) { + // DaemonSet does not exist, so nothing to delete + // r.Log.Info("No DaemonSet Found To Delete") + return nil + } + // Some other error occurred in fetching the DaemonSet + // r.Log.Error(err, "Some other error occurred in fetching the DaemonSet") + return err + } + + // DaemonSet exists, attempt to delete it + // r.Log.Info("Deleting DaemonSet", "DaemonSet.Name", existingDaemonSet.Name) + if err := r.Client.Delete(ctx, existingDaemonSet); err != nil { + r.Log.Error(err, "Failed to delete DaemonSet", "DaemonSet.Name", existingDaemonSet.Name) + return err + } + + // Poll for the DaemonSet to be deleted + timeout := 30 * time.Second + pollInterval := 5 * time.Second + startTime := time.Now() + + for { + // Check if we have exceeded the timeout + if time.Since(startTime) > timeout { + return fmt.Errorf("timeout waiting for DaemonSet %s to be deleted", existingDaemonSet.Name) + } + + // Check if the DaemonSet still exists + err = r.Client.Get(ctx, types.NamespacedName{ + Name: existingDaemonSet.Name, + Namespace: existingDaemonSet.Namespace, + }, existingDaemonSet) + + if err != nil { + if apierrors.IsNotFound(err) { + // DaemonSet no longer exists + r.Log.Info("DaemonSet deleted successfully", "DaemonSet.Name", existingDaemonSet.Name) + return nil + } + // Some other error occurred in fetching the DaemonSet + r.Log.Error(err, "Error checking for DaemonSet deletion", "DaemonSet.Name", existingDaemonSet.Name) + return err + } + + // Wait before checking again + time.Sleep(pollInterval) + } +} + +func findDisksToRemove(specDisks, statusDisks []string, instance *oraclerestartdb.OracleRestart) ([]string, error) { + // Convert specDisks to a set for fast lookups + specDiskSet := make(map[string]struct{}) + for _, disk := range specDisks { + specDiskSet[disk] = struct{}{} + } + + // Find disks in statusDisks that are not in specDiskSet + var disksToRemove []string + for _, disk := range statusDisks { + if _, found := specDiskSet[disk]; !found { + disksToRemove = append(disksToRemove, disk) + } + } + + // Validate that disks to be removed are not part of any other ASM device list + combinedList := strings.Join([]string{ + instance.Spec.ConfigParams.CrsAsmDeviceList, + instance.Spec.ConfigParams.RecoAsmDeviceList, + instance.Spec.ConfigParams.RedoAsmDeviceList, + instance.Spec.ConfigParams.DbAsmDeviceList, + }, ",") + combinedSet := make(map[string]struct{}) + for _, disk := range strings.Split(combinedList, ",") { + combinedSet[disk] = struct{}{} + } + + // Check for any disks to remove that are part of the combined ASM device list + var validatedDisks []string + for _, disk := range disksToRemove { + if _, found := combinedSet[disk]; found { + return nil, fmt.Errorf("disk %s to be removed is part of a disk group, hence cannot be removed", disk) + } + validatedDisks = append(validatedDisks, disk) + } + + return validatedDisks, nil +} + +func findDisksToAdd(newSpecDisks, statusDisks []string, instance *oraclerestartdb.OracleRestart, oldSpec *oraclerestartdb.OracleRestartSpec) ([]string, error) { + // Create a set for statusDisks to allow valid reuse of existing disks + // Step 1: Check for duplicates within newSpecDisks itself + oldAsmDisks := flattenDisksBySize(oldSpec) + + if len(oldAsmDisks) == len(newSpecDisks) { + oldDiskSet := make(map[string]struct{}) + for _, disk := range oldAsmDisks { + oldDiskSet[strings.TrimSpace(disk)] = struct{}{} + } + + allDisksMatch := true + for _, newDisk := range newSpecDisks { + if _, found := oldDiskSet[strings.TrimSpace(newDisk)]; !found { + allDisksMatch = false + break + } + } + + if allDisksMatch { + return nil, nil // No new disks to add + } + } + + seenDisks := make(map[string]struct{}) + for _, newDisk := range newSpecDisks { + trimmedDisk := strings.TrimSpace(newDisk) + + // Check if the disk is already in the seenDisks set, indicating a duplicate within newSpecDisks + if _, found := seenDisks[trimmedDisk]; found { + return nil, fmt.Errorf("disk '%s' is defined more than once in the new spec and cannot be added multiple times", trimmedDisk) + } + seenDisks[trimmedDisk] = struct{}{} + } + + // Step 2: Create a set for the actual statusDisks by splitting each entry + statusDiskSet := make(map[string]struct{}) + for _, diskEntry := range statusDisks { + // Split the disk entry by commas to handle multiple disks in a single string + for _, disk := range strings.Split(diskEntry, ",") { + statusDiskSet[strings.TrimSpace(disk)] = struct{}{} + } + } + + // Create sets for each of the individual ASM device lists + crsAsmDeviceSet := make(map[string]struct{}) + recoAsmDeviceSet := make(map[string]struct{}) + redoAsmDeviceSet := make(map[string]struct{}) + dbAsmDeviceSet := make(map[string]struct{}) + + // Step 4: Create a set to track newly added disks that are valid for addition + var validDisksToAdd []string + newDiskSet := make(map[string]struct{}) + + for _, newDisk := range newSpecDisks { + trimmedDisk := strings.TrimSpace(newDisk) + + // If the disk is already part of the statusDisks (existing disks), allow it to stay + if _, found := statusDiskSet[trimmedDisk]; found { + continue + } + + // Check if the disk is already part of any individual ASM device list + if _, found := crsAsmDeviceSet[trimmedDisk]; found { + return nil, fmt.Errorf("disk '%s' is already part of CRS ASM device list and cannot be added again", trimmedDisk) + } + if _, found := recoAsmDeviceSet[trimmedDisk]; found { + return nil, fmt.Errorf("disk '%s' is already part of RECO ASM device list and cannot be added again", trimmedDisk) + } + if _, found := redoAsmDeviceSet[trimmedDisk]; found { + return nil, fmt.Errorf("disk '%s' is already part of REDO ASM device list and cannot be added again", trimmedDisk) + } + if _, found := dbAsmDeviceSet[trimmedDisk]; found { + return nil, fmt.Errorf("disk '%s' is already part of DB ASM device list and cannot be added again", trimmedDisk) + } + + // Add the disk to newDiskSet and consider it valid for addition + newDiskSet[trimmedDisk] = struct{}{} + validDisksToAdd = append(validDisksToAdd, trimmedDisk) + } + + return validDisksToAdd, nil +} + +func (r *OracleRestartReconciler) setDefaults(oracleRestart *oraclerestartdb.OracleRestart) error { + + if oracleRestart.Spec.ImagePullPolicy == nil { + *oracleRestart.Spec.ImagePullPolicy = "Always" + } + + if oracleRestart.Spec.SshKeySecret != nil { + if oracleRestart.Spec.SshKeySecret.KeyMountLocation == "" { + oracleRestart.Spec.SshKeySecret.KeyMountLocation = utils.OraRacSshSecretMount + } + } + + if oracleRestart.Spec.DbSecret != nil { + if oracleRestart.Spec.DbSecret.Name != "" { + if oracleRestart.Spec.DbSecret.PwdFileMountLocation == "" { + oracleRestart.Spec.DbSecret.PwdFileMountLocation = utils.OraRacDbPwdFileSecretMount + } + if oracleRestart.Spec.DbSecret.KeyFileMountLocation == "" { + oracleRestart.Spec.DbSecret.KeyFileMountLocation = utils.OraRacDbKeyFileSecretMount + } + } + } + + if oracleRestart.Spec.TdeWalletSecret != nil { + if oracleRestart.Spec.TdeWalletSecret.Name != "" { + if oracleRestart.Spec.TdeWalletSecret.PwdFileMountLocation == "" { + oracleRestart.Spec.TdeWalletSecret.PwdFileMountLocation = utils.OraRacTdePwdFileSecretMount + } + if oracleRestart.Spec.TdeWalletSecret.KeyFileMountLocation == "" { + oracleRestart.Spec.TdeWalletSecret.KeyFileMountLocation = utils.OraRacTdeKeyFileSecretMount + } + } + } + + if oracleRestart.Spec.ConfigParams != nil { + if oracleRestart.Spec.ConfigParams.SwMountLocation == "" { + oracleRestart.Spec.ConfigParams.SwMountLocation = utils.OraSwLocation + } + + if oracleRestart.Spec.ConfigParams.GridResponseFile.ConfigMapName == "" { + if oracleRestart.Spec.ConfigParams.CrsAsmDiskDg == "" { + oracleRestart.Spec.ConfigParams.CrsAsmDiskDg = "+DATA" + } + + if oracleRestart.Spec.ConfigParams.CrsAsmDiskDgRedundancy == "" { + oracleRestart.Spec.ConfigParams.CrsAsmDiskDgRedundancy = "external" + } + } + + if oracleRestart.Spec.ConfigParams.DbResponseFile.ConfigMapName == "" { + if oracleRestart.Spec.ConfigParams.DbDataFileDestDg == "" { + oracleRestart.Spec.ConfigParams.DbDataFileDestDg = oracleRestart.Spec.ConfigParams.CrsAsmDiskDg + } + + if oracleRestart.Spec.ConfigParams.DbRecoveryFileDest == "" { + oracleRestart.Spec.ConfigParams.DbRecoveryFileDest = oracleRestart.Spec.ConfigParams.DbDataFileDestDg + } + + if oracleRestart.Spec.ConfigParams.DbCharSet == "" { + oracleRestart.Spec.ConfigParams.DbCharSet = "AL32UTF8" + } + } + + } + return nil + +} + +func (r *OracleRestartReconciler) updateGiConfigParamStatus(oracleRestart *oraclerestartdb.OracleRestart) error { + + //orestartPod := &corev1.Pod{} + + var cName, fName string + + if oracleRestart.Spec.ConfigParams.GridResponseFile.ConfigMapName != "" { + cName = oracleRestart.Spec.ConfigParams.GridResponseFile.ConfigMapName + } + if oracleRestart.Spec.ConfigParams.GridResponseFile.Name != "" { + fName = oracleRestart.Spec.ConfigParams.GridResponseFile.Name + } + + if oracleRestart.Status.ConfigParams == nil { + oracleRestart.Status.ConfigParams = new(oraclerestartdb.InitParams) + } + + if oracleRestart.Spec.ConfigParams != nil { + if oracleRestart.Status.ConfigParams.CrsAsmDeviceList == "" { + if oracleRestart.Spec.ConfigParams.CrsAsmDeviceList != "" { + oracleRestart.Status.ConfigParams.CrsAsmDeviceList = oracleRestart.Spec.ConfigParams.CrsAsmDeviceList + } else { + diskList, err := oraclerestartcommon.CheckRspData(oracleRestart, r.Client, "diskList", cName, fName) + if err != nil { + return errors.New(("error in responsefile, unable to read diskList")) + } + oracleRestart.Status.ConfigParams.CrsAsmDeviceList = diskList + } + } + if oracleRestart.Status.ConfigParams.Inventory == "" { + if oracleRestart.Spec.ConfigParams.Inventory != "" { + oracleRestart.Status.ConfigParams.Inventory = oracleRestart.Spec.ConfigParams.Inventory + } else { + invlocation, err := oraclerestartcommon.CheckRspData(oracleRestart, r.Client, "INVENTORY_LOCATION", cName, fName) + if err != nil { + oracleRestart.Spec.IsFailed = true + return errors.New(("error in responsefile, unable to read inventory_location")) + } else { + oracleRestart.Status.ConfigParams.Inventory = invlocation + } + } + } + + if oracleRestart.Status.ConfigParams.GridBase == "" { + if oracleRestart.Spec.ConfigParams.GridBase != "" { + oracleRestart.Status.ConfigParams.GridBase = oracleRestart.Spec.ConfigParams.GridBase + } else { + gibase, err := oraclerestartcommon.CheckRspData(oracleRestart, r.Client, "ORACLE_BASE", cName, fName) + if err != nil { + oracleRestart.Spec.IsFailed = true + return errors.New(("error in responsefile, unable to read oracle_base")) + } else { + oracleRestart.Status.ConfigParams.GridBase = gibase + } + } + } + if oracleRestart.Status.ConfigParams.GridHome == "NOT_DEFINED" { + if oracleRestart.Spec.ConfigParams.GridHome != "" { + oracleRestart.Status.ConfigParams.GridHome = oracleRestart.Spec.ConfigParams.GridHome + } else { + gihome, err := oraclerestartcommon.CheckRspData(oracleRestart, r.Client, "GRID_HOME", cName, fName) + if err != nil { + oracleRestart.Spec.IsFailed = true + return errors.New(("error in responsefile, unable to read oracle_base")) + } else { + oracleRestart.Status.ConfigParams.GridHome = gihome + } + } + } + + // if oracleRestart.Status.ScanSvcName == "" { + // if oracleRestart.Spec.ScanSvcName != "" { + // oracleRestart.Status.ScanSvcName = oracleRestart.Spec.ScanSvcName + // } else { + // scanname, err := oraclerestartcommon.CheckRspData(OracleRestart, r.Client, "scanName", cName, fName) + // if err != nil { + // oracleRestart.Spec.IsFailed = true + // return errors.New(("error in responsefile, unable to read scanName")) + // } else { + // oracleRestart.Status.ScanSvcName = scanname + // } + // } + // } + + if oracleRestart.Status.ConfigParams.CrsAsmDiskDg == "" { + if oracleRestart.Spec.ConfigParams.CrsAsmDiskDg != "" { + oracleRestart.Status.ConfigParams.CrsAsmDiskDg = oracleRestart.Spec.ConfigParams.CrsAsmDiskDg + } else { + diskGroupName, err := oraclerestartcommon.CheckRspData(oracleRestart, r.Client, "diskGroupName", cName, fName) + if err != nil { + oracleRestart.Spec.IsFailed = true + return errors.New(("error in responsefile, unable to read diskGroupName")) + } else { + oracleRestart.Status.ConfigParams.CrsAsmDiskDg = diskGroupName + } + } + } + + if oracleRestart.Status.ConfigParams.CrsAsmDiskDgRedundancy == "" { + if oracleRestart.Spec.ConfigParams.CrsAsmDiskDgRedundancy != "" { + oracleRestart.Status.ConfigParams.CrsAsmDiskDgRedundancy = oracleRestart.Spec.ConfigParams.CrsAsmDiskDgRedundancy + } else { + redundancy, err := oraclerestartcommon.CheckRspData(oracleRestart, r.Client, "redundancy", cName, fName) + if err != nil { + oracleRestart.Spec.IsFailed = true + return errors.New(("error in responsefile, unable to read redundancy")) + } else { + oracleRestart.Status.ConfigParams.CrsAsmDiskDgRedundancy = redundancy + } + } + } + } + + return nil + +} + +func (r *OracleRestartReconciler) updateDbConfigParamStatus(oracleRestart *oraclerestartdb.OracleRestart) error { + + //orestartPod := &corev1.Pod{} + + var cName, fName string + + if oracleRestart.Spec.ConfigParams.DbResponseFile.ConfigMapName != "" { + cName = oracleRestart.Spec.ConfigParams.DbResponseFile.ConfigMapName + } + if oracleRestart.Spec.ConfigParams.DbResponseFile.Name != "" { + fName = oracleRestart.Spec.ConfigParams.DbResponseFile.Name + } + + if oracleRestart.Status.ConfigParams == nil { + oracleRestart.Status.ConfigParams = new(oraclerestartdb.InitParams) + } + + if oracleRestart.Spec.ConfigParams != nil { + if oracleRestart.Spec.ConfigParams.DbAsmDeviceList != "" { + oracleRestart.Status.ConfigParams.DbAsmDeviceList = oracleRestart.Spec.ConfigParams.DbAsmDeviceList + } + + if oracleRestart.Spec.ConfigParams.RecoAsmDeviceList != "" { + oracleRestart.Status.ConfigParams.RecoAsmDeviceList = oracleRestart.Spec.ConfigParams.RecoAsmDeviceList + } + if oracleRestart.Spec.ConfigParams.DBAsmDiskDgRedundancy != "" { + oracleRestart.Status.ConfigParams.DBAsmDiskDgRedundancy = oracleRestart.Spec.ConfigParams.DBAsmDiskDgRedundancy + } + + if oracleRestart.Status.ConfigParams.DbName == "" { + if oracleRestart.Spec.ConfigParams.DbName != "" { + oracleRestart.Status.ConfigParams.DbName = oracleRestart.Spec.ConfigParams.DbName + } else { + variable, err := oraclerestartcommon.CheckRspData(oracleRestart, r.Client, "variables=", cName, fName) + if err != nil { + oracleRestart.Spec.IsFailed = true + return errors.New(("error in responsefile, unable to read variable")) + } + dbName := utils.GetValue(variable, "DB_NAME") + if err != nil { + oracleRestart.Spec.IsFailed = true + return errors.New(("error in responsefile, unable to read DB_NAME")) + } + oracleRestart.Status.ConfigParams.DbName = dbName + } + } + + if oracleRestart.Status.ConfigParams.DbDataFileDestDg == "" { + if oracleRestart.Spec.ConfigParams.DbDataFileDestDg != "" { + oracleRestart.Status.ConfigParams.DbDataFileDestDg = oracleRestart.Spec.ConfigParams.DbDataFileDestDg + if oracleRestart.Spec.ConfigParams.DbAsmDeviceList != "" { + oracleRestart.Status.ConfigParams.DbAsmDeviceList = oracleRestart.Spec.ConfigParams.DbAsmDeviceList + // Logic to validate and set Disk group using grid response file and dbca response file + var gcName, gfName string + + if oracleRestart.Spec.ConfigParams.GridResponseFile.ConfigMapName != "" { + gcName = oracleRestart.Spec.ConfigParams.GridResponseFile.ConfigMapName + } + if oracleRestart.Spec.ConfigParams.GridResponseFile.Name != "" { + gfName = oracleRestart.Spec.ConfigParams.GridResponseFile.Name + } + if gcName != "" && gfName != "" { + diskGroupName, err := oraclerestartcommon.CheckRspData(oracleRestart, r.Client, "diskGroupName", gcName, gfName) + if err != nil { + oracleRestart.Spec.IsFailed = true + return errors.New(("error in responsefile, unable to read diskGroupName")) + } + + if oracleRestart.Status.ConfigParams.DbDataFileDestDg != diskGroupName { + return nil + } else { + oracleRestart.Status.ConfigParams.DbDataFileDestDg = diskGroupName + } + } + } + } else { + // Logic to validate and set Disk group using grid response file and dbca response file + var gcName, gfName string + + if oracleRestart.Spec.ConfigParams.GridResponseFile.ConfigMapName != "" { + gcName = oracleRestart.Spec.ConfigParams.GridResponseFile.ConfigMapName + } + if oracleRestart.Spec.ConfigParams.GridResponseFile.Name != "" { + gfName = oracleRestart.Spec.ConfigParams.GridResponseFile.Name + } + if gcName != "" && gfName != "" { + diskGroupName, err := oraclerestartcommon.CheckRspData(oracleRestart, r.Client, "diskGroupName", gcName, gfName) + if err != nil { + oracleRestart.Spec.IsFailed = true + return errors.New(("error in grid responsefile, unable to read diskGroupName to set DbDataFileDestDg")) + } + oracleRestart.Status.ConfigParams.DbDataFileDestDg = diskGroupName + dbdgloc, err := oraclerestartcommon.CheckRspData(oracleRestart, r.Client, "datafileDestination", cName, fName) + if err != nil { + oracleRestart.Spec.IsFailed = true + oracleRestart.Status.ConfigParams.DbDataFileDestDg = diskGroupName + } else { + dbdg := strings.Split(dbdgloc, "/") + if len(dbdg) == 0 { + return errors.New("error in responsefile, unable to read datafileDestination diskgroup") + } + oracleRestart.Status.ConfigParams.DbDataFileDestDg = dbdg[0] + } + } else { + return errors.New("neither DbDataFileDestDg is set , nor grid response file is set. One of them is required") + } + } + } + + if oracleRestart.Status.ConfigParams.DbRecoveryFileDest == "" { + if oracleRestart.Spec.ConfigParams.DbRecoveryFileDest != "" { + oracleRestart.Status.ConfigParams.DbRecoveryFileDest = oracleRestart.Spec.ConfigParams.DbRecoveryFileDest + } else { + if cName != "" && fName != "" { + recodgloc, err := oraclerestartcommon.CheckRspData(oracleRestart, r.Client, "recoveryAreaDestination", cName, fName) + if err != nil { + oracleRestart.Spec.IsFailed = true + return errors.New(("error in responsefile, unable to read recoveryAreaDestination")) + } + recodg := strings.Split(recodgloc, "/") + if len(recodg) == 0 { + return errors.New("error in responsefile, unable to read recoveryAreaDestination diskgroup") + } + oracleRestart.Status.ConfigParams.DbDataFileDestDg = recodg[0] + } + } + } + + if oracleRestart.Status.ConfigParams.DbBase == "" { + if oracleRestart.Spec.ConfigParams.DbBase != "" { + oracleRestart.Status.ConfigParams.DbBase = oracleRestart.Spec.ConfigParams.DbBase + } else { + variable, err := oraclerestartcommon.CheckRspData(oracleRestart, r.Client, "variables=", cName, fName) + if err != nil { + oracleRestart.Spec.IsFailed = true + return errors.New(("error in responsefile, unable to read variable")) + } + obase := utils.GetValue(variable, "ORACLE_BASE") + if len(obase) == 0 { + return errors.New(("error in responsefile, unable to read ORACLE_BASE")) + } + oracleRestart.Status.ConfigParams.DbBase = obase + } + } + if oracleRestart.Status.ConfigParams.DbHome == "NOT_DEFINED" { + if oracleRestart.Spec.ConfigParams.DbHome != "" { + oracleRestart.Status.ConfigParams.DbHome = oracleRestart.Spec.ConfigParams.DbHome + } else { + variable, err := oraclerestartcommon.CheckRspData(oracleRestart, r.Client, "variables=", cName, fName) + if err != nil { + oracleRestart.Spec.IsFailed = true + return errors.New(("error in responsefile, unable to read variable")) + } + ohome := utils.GetValue(variable, "ORACLE_HOME") + if len(ohome) == 0 { + return errors.New(("error in responsefile, unable to read ORACLE_BASE")) + } + oracleRestart.Status.ConfigParams.DbHome = ohome + } + } + + if oracleRestart.Status.ConfigParams.DbHome == "" { + if oracleRestart.Spec.ConfigParams.DbHome != "" { + oracleRestart.Status.ConfigParams.DbHome = oracleRestart.Spec.ConfigParams.DbHome + } else { + variable, err := oraclerestartcommon.CheckRspData(oracleRestart, r.Client, "variables=", cName, fName) + if err != nil { + oracleRestart.Spec.IsFailed = true + return errors.New(("error in responsefile, unable to read variable")) + } + ohome := utils.GetValue(variable, "ORACLE_HOME") + if len(ohome) == 0 { + return errors.New(("error in responsefile, unable to read ORACLE_HOME")) + } + oracleRestart.Status.ConfigParams.DbHome = ohome + } + } + } + + if oracleRestart.Status.ConfigParams.GridHome == "" { + if oracleRestart.Spec.ConfigParams.GridHome != "" { + oracleRestart.Status.ConfigParams.GridHome = oracleRestart.Spec.ConfigParams.GridHome + } else { + variable, err := oraclerestartcommon.CheckRspData(oracleRestart, r.Client, "variables=", cName, fName) + if err != nil { + oracleRestart.Spec.IsFailed = true + return errors.New(("error in responsefile, unable to read variable")) + } + ghome := utils.GetValue(variable, "ORACLE_HOME") + if len(ghome) == 0 { + return errors.New(("error in responsefile, unable to read ORACLE_HOME")) + } + oracleRestart.Status.ConfigParams.GridHome = ghome + } + } + + return nil + +} + +func (r *OracleRestartReconciler) updateOracleRestartInstTopologyStatus(oracleRestart *oraclerestartdb.OracleRestart, ctx context.Context, req ctrl.Request) ([]string, map[string]*corev1.Node, error) { + + //orestartPod := &corev1.Pod{} + var podNames []string + nodeDetails := make(map[string]*corev1.Node) + + if strings.ToLower(oracleRestart.Spec.InstDetails.IsDelete) != "true" { + _, pod, err := r.validateOracleRestartInst(oracleRestart, ctx, req, oracleRestart.Spec.InstDetails, 0) + if err != nil { + return podNames, nodeDetails, err + } + podNames = append(podNames, pod.Name) + + // Get node details for the node where the pod is running + node, err := r.getNodeDetails(pod.Spec.NodeName) + if err != nil { + return podNames, nodeDetails, fmt.Errorf("failed to get node details for pod %s: %v", pod.Name, err) + } + nodeDetails[pod.Name] = node + } + + if len(podNames) == 0 || len(nodeDetails) == 0 { + oracleRestart.Spec.IsFailed = true + return podNames, nodeDetails, errors.New("error occurred while collecting Oracle Restart pod or node details") + } else { + oracleRestart.Spec.IsFailed = false + } + + return podNames, nodeDetails, nil +} + +func (r *OracleRestartReconciler) getNodeDetails(nodeName string) (*corev1.Node, error) { + node := &corev1.Node{} + err := r.Client.Get(context.TODO(), client.ObjectKey{ + Namespace: "", + Name: nodeName, + }, node) + if err != nil { + return nil, err + } + return node, nil +} + +func (r *OracleRestartReconciler) updateoraclerestartdbTopologyStatus(OracleRestart *oraclerestartdb.OracleRestart, ctx context.Context, req ctrl.Request, podNames []string, nodeDetails map[string]*corev1.Node) error { + + //orestartPod := &corev1.Pod{} + var err error + _, _, err = r.validateoraclerestartdb(OracleRestart, ctx, req, podNames, nodeDetails) + if err != nil { + return err + } + return nil +} + +func (r *OracleRestartReconciler) validateoraclerestartdb(oracleRestart *oraclerestartdb.OracleRestart, ctx context.Context, req ctrl.Request, podNames []string, nodeDetails map[string]*corev1.Node, +) (*appsv1.StatefulSet, *corev1.Pod, error) { + + orestartSfSet := &appsv1.StatefulSet{} + orestartPod := &corev1.Pod{} + const maxRetries = 5 + const retryDelay = 2 * time.Second + oraclerestartcommon.UpdateoraclerestartdbStatusData(oracleRestart, ctx, req, podNames, r.kubeClient, r.kubeConfig, r.Log, nodeDetails) + // Log the start of the status update process + r.Log.Info("Updating Oracle Restart instance status with validateoraclerestartdb", "Instance", oracleRestart.Name) + + for attempt := 0; attempt < maxRetries; attempt++ { + // // Fetch the latest version of the object + latestInstance := &oraclerestartdb.OracleRestart{} + err := r.Client.Get(ctx, req.NamespacedName, latestInstance) + if err != nil { + r.Log.Error(err, "Failed to fetch the latest version of Oracle Restart instance") + return orestartSfSet, orestartPod, err // Return the error if fetching the latest version fails + } + + // Merge the instance fields into latestInstance + err = mergeInstancesFromLatest(oracleRestart, latestInstance) + if err != nil { + r.Log.Error(err, "Failed to merge instances") + return orestartSfSet, orestartPod, err + } + + // Attempt to update the status of the instance directly + // err = r.Status().Update(ctx, instance) + + // Update the ResourceVersion of instance from latestInstance to avoid conflict + oracleRestart.ResourceVersion = latestInstance.ResourceVersion + err = r.Client.Status().Patch(ctx, oracleRestart, client.MergeFrom(latestInstance)) + + if err != nil { + if apierrors.IsConflict(err) { + // Handle the conflict and retry + r.Log.Info("Conflict detected in validateoraclerestartdb, retrying...", "attempt", attempt+1) + time.Sleep(retryDelay) + continue + // Retry + } + // For other errors, log and continue the retry loop + r.Log.Error(err, "Failed to update the Oracle Restart DB instance, retrying") + continue + } + + // If update was successful, exit the loop + r.Log.Info("Updated Oracle Restart instance status with validateoraclerestartdb", "Instance", oracleRestart.Name) + + return orestartSfSet, orestartPod, nil + } + + // If all retries fail, return an error + return orestartSfSet, orestartPod, fmt.Errorf("failed to update Oracle Restart DB Status after %d attempts", maxRetries) +} + +// ======= Function to validate Shard +func (r *OracleRestartReconciler) validateOracleRestartInst(oracleRestart *oraclerestartdb.OracleRestart, ctx context.Context, req ctrl.Request, OraRestartSpex oraclerestartdb.OracleRestartInstDetailSpec, specId int) (*appsv1.StatefulSet, *corev1.Pod, error) { + + var err error + orestartSfSet := &appsv1.StatefulSet{} + orestartPod := &corev1.Pod{} + + orestartSfSet, err = oraclerestartcommon.CheckSfset(OraRestartSpex.Name, oracleRestart, r.Client) + if err != nil { + //msg := "Unable to find Oracle Restart statefulset " + oraclerestartcommon.GetFmtStr(OraRestartSpex.Name) + "." + //oraclerestartcommon.LogMessages("INFO", msg, nil, instance, r.Log) + r.updateOracleRestartInstStatus(oracleRestart, ctx, req, OraRestartSpex, string(oraclerestartdb.StatefulSetNotFound), r.Client, false) + return orestartSfSet, orestartPod, err + } + + podList, err := oraclerestartcommon.GetPodList(orestartSfSet.Name, oracleRestart, r.Client, OraRestartSpex) + if err != nil { + msg := "Unable to find any pod in statefulset " + oraclerestartcommon.GetFmtStr(orestartSfSet.Name) + "." + oraclerestartcommon.LogMessages("INFO", msg, nil, oracleRestart, r.Log) + r.updateOracleRestartInstStatus(oracleRestart, ctx, req, OraRestartSpex, string(oraclerestartdb.PodNotFound), r.Client, false) + return orestartSfSet, orestartPod, err + } + // Validate the pod list and get the list of not ready pods + isPodExist, orestartPod, notReadyPod := oraclerestartcommon.PodListValidation(podList, orestartSfSet.Name, oracleRestart, r.Client) + // Check if the pod is ready + if !isPodExist { + msg := "" + if notReadyPod != nil { + // Log the name of the first not ready pod + msg = "unable to validate Oracle Restart pod. The pod not ready is: " + notReadyPod.Name + oraclerestartcommon.LogMessages("INFO", msg, nil, oracleRestart, r.Log) + return orestartSfSet, orestartPod, fmt.Errorf(msg) + } else { + // Handle the case where no pods were found at all + msg = "unable to validate Oracle Restart pod. No pods matching the criteria were found" + oraclerestartcommon.LogMessages("INFO", msg, nil, oracleRestart, r.Log) + return orestartSfSet, orestartPod, fmt.Errorf(msg) + } + + } + // Update status when PODs are ready + state := oracleRestart.Status.State + if oracleRestart.Spec.IsManual { // if user changes spec to manual mode, lets change status column same as well + state = string(oraclerestartdb.OracleRestartManualState) + } + if oracleRestart.Spec.IsFailed { // if controller changes spec to failed mode, lets change status column same as well + state = string(oraclerestartdb.OracleRestartFailedState) + } + + switch { + case isPodExist && (state == string(oraclerestartdb.OracleRestartProvisionState) || + state == string(oraclerestartdb.OracleRestartUpdateState) || + state == string(oraclerestartdb.OracleRestartPendingState)): + // When previous update or provision is there, change to POD available intermittent state + state = string(oraclerestartdb.OracleRestartPodAvailableState) + case state == string(oraclerestartdb.OracleRestartFailedState): + // Failed state handling, remain in failed state or take specific action + state = string(oraclerestartdb.OracleRestartFailedState) + case state == string(oraclerestartdb.OracleRestartManualState): + // Manual state handling, e.g., do not modify state automatically + state = string(oraclerestartdb.OracleRestartManualState) + default: + // Continue with the current state for others, if no conditions are met + state = oracleRestart.Status.State + } + + r.updateOracleRestartInstStatus(oracleRestart, ctx, req, OraRestartSpex, state, r.Client, true) + r.Log.Info("Completed Update of Oracle Restart instance status") + return orestartSfSet, orestartPod, nil +} + +func (r *OracleRestartReconciler) updateOracleRestartInstStatus( + oracleRestart *oraclerestartdb.OracleRestart, + ctx context.Context, + req ctrl.Request, + OraRestartSpex oraclerestartdb.OracleRestartInstDetailSpec, + state string, + kClient client.Client, + mergingRequired bool, +) { + const maxRetries = 5 + const retryDelay = 2 * time.Second + + var lastErr error + var failedUpdate bool + // Get/Update RAC instance status data + oraclerestartcommon.UpdateOracleRestartInstStatusData(oracleRestart, ctx, req, OraRestartSpex, state, r.kubeClient, r.kubeConfig, r.Log, r.Client) + + for attempt := 0; attempt < maxRetries; attempt++ { + + // Fetch the latest version of the object + latestInstance := &oraclerestartdb.OracleRestart{} + err := r.Client.Get(ctx, req.NamespacedName, latestInstance) + if err != nil { + r.Log.Error(err, "Failed to fetch the latest version of Oracle Restart instance") + lastErr = err + continue // Continue to retry + } + latestInstance.Status.OracleRestartNodes = oracleRestart.Status.OracleRestartNodes + if mergingRequired { + + // Ensure latestInstance has the most recent version + r.ensureAsmStorageStatus(latestInstance) + + // Merge the instance fields into latestInstance + err = mergeInstancesFromLatest(oracleRestart, latestInstance) + if err != nil { + r.Log.Error(err, "Failed to merge instances") + } + } + + // Attempt to update the combined instance back to the Kubernetes API + // err = r.Status().Update(ctx, instance) + oracleRestart.ResourceVersion = latestInstance.ResourceVersion + + err = r.Status().Update(ctx, oracleRestart) + if err != nil { + if apierrors.IsConflict(err) { + r.Log.Info("Conflict detected in updateOracleRestartInstStatus, retrying...", "attempt", attempt+1) + time.Sleep(retryDelay) + failedUpdate = true + continue // Retry + } + // For other errors, log and return + r.Log.Error(err, "Failed to update the Oracle Restart instance") + lastErr = err + failedUpdate = true + continue // Continue to retry + } + r.Log.Info("Oracle Restart Object updated with updateOracleRestartInstStatus") + failedUpdate = false + break //break if its updated successfully + } + + // If we exhaust all retries, print the last error encountered + if failedUpdate { + r.Log.Info("failed to update Oracle Restart instance after 5 attempts", "lastErr", lastErr) + } +} + +// GetRestrictedFields returns a set of field names that are restricted from being updated. +func GetRestrictedFields() map[string]struct{} { + return map[string]struct{}{ + "ConfigParams.DbName": {}, + "ConfigParams.GridBase": {}, + "ConfigParams.GridHome": {}, + "ConfigParams.DbBase": {}, + "ConfigParams.DbHome": {}, + "ConfigParams.CrsAsmDiskDg": {}, + "ConfigParams.CrsAsmDiskDgRedundancy": {}, + "ConfigParams.DBAsmDiskDgRedundancy": {}, + "ConfigParams.DbCharSet": {}, + "ConfigParams.DbConfigType": {}, + "ConfigParams.DbDataFileDestDg": {}, + "ConfigParams.DbUniqueName": {}, + "ConfigParams.DbRecoveryFileDest": {}, + "ConfigParams.DbRedoFileSize": {}, + "ConfigParams.DbStorageType": {}, + "ConfigParams.DbSwZipFile": {}, + "ConfigParams.GridSwZipFile": {}, + "ConfigParams.GridResponseFile.ConfigMapName": {}, + "ConfigParams.GridResponseFile.Name": {}, + "ConfigParams.DbResponseFile.ConfigMapName": {}, + "ConfigParams.DbResponseFile.Name": {}, + } +} + +func mergeInstancesFromLatest(instance, latestInstance *oraclerestartdb.OracleRestart) error { + instanceVal := reflect.ValueOf(instance).Elem() + latestVal := reflect.ValueOf(latestInstance).Elem() + + // Assuming `Status` is a field in `OracleRestart` + instanceStatus := instanceVal.FieldByName("Status") + latestStatus := latestVal.FieldByName("Status") + + if !instanceStatus.IsValid() || !latestStatus.IsValid() { + return fmt.Errorf("status field is not valid in one of the instances") + } + + // Merge the Status field + return mergeStructFields(instanceStatus, latestStatus) +} + +func mergeStructFields(instanceField, latestField reflect.Value) error { + if instanceField.Kind() != reflect.Struct || latestField.Kind() != reflect.Struct { + return fmt.Errorf("fields to be merged must be of struct type") + } + + for i := 0; i < instanceField.NumField(); i++ { + subField := instanceField.Type().Field(i) + instanceSubField := instanceField.Field(i) + latestSubField := latestField.Field(i) + + if !isExported(subField) || !instanceSubField.CanSet() { + continue + } + + switch latestSubField.Kind() { + case reflect.Ptr: + if !latestSubField.IsNil() && instanceSubField.IsNil() { + instanceSubField.Set(latestSubField) + } + case reflect.String: + if latestSubField.String() != "" && latestSubField.String() != "NOT_DEFINED" && instanceSubField.String() == "" { + instanceSubField.Set(latestSubField) + } + case reflect.Struct: + if err := mergeStructFields(instanceSubField, latestSubField); err != nil { + return err + } + default: + if reflect.DeepEqual(instanceSubField.Interface(), reflect.Zero(instanceSubField.Type()).Interface()) { + instanceSubField.Set(latestSubField) + } + } + } + return nil +} + +func isExported(field reflect.StructField) bool { + return field.PkgPath == "" +} + +// Create Configmap +func (r *OracleRestartReconciler) generateConfigMap(instance *oraclerestartdb.OracleRestart) (map[string]string, error) { + configMapData := make(map[string]string, 0) + // new_crs_nodes, existing_crs_nodes_healthy, existing_crs_nodes_not_healthy, install_node, new_crs_nodes_list := oraclerestartcommon.GetCrsNodes(instance, r.kubeClient, r.kubeConfig, r.Log, r.Client) + install_node := instance.Spec.InstDetails.Name + "-0" + asm_devices := oraclerestartcommon.GetAsmDevices(instance) + var data []string + var addnodeFlag bool + + data = append(data, "OP_TYPE=setuprac") + // --- Pick ALL envVars directly from CR spec --- + for _, e := range instance.Spec.InstDetails.EnvVars { + data = append(data, fmt.Sprintf("%s=%s", e.Name, e.Value)) + } + + // Service Parameters + if instance.Spec.ServiceDetails.Name != "" { + sparams := oraclerestartcommon.GetServiceParams(instance) + data = append(data, "DB_SERVICE="+sparams) + } + data = append(data, "CRS_GPC=true") + + if instance.Spec.ConfigParams.PdbName != "" { + data = append(data, "ORACLE_PDB="+instance.Spec.ConfigParams.PdbName) + } + + if instance.Spec.ConfigParams.DbHome != "" { + data = append(data, "DB_HOME="+instance.Spec.ConfigParams.DbHome) + } else { + if instance.Status.ConfigParams != nil { + if instance.Status.ConfigParams.DbHome != "" { + data = append(data, "DB_HOME="+instance.Status.ConfigParams.DbHome) + } + } + } + + if instance.Spec.ConfigParams.DbBase != "" { + data = append(data, "DB_BASE="+instance.Spec.ConfigParams.DbBase) + } else { + if instance.Status.ConfigParams != nil { + if instance.Status.ConfigParams.DbBase != "" { + data = append(data, "DB_BASE="+instance.Status.ConfigParams.DbBase) + } + } + } + + if instance.Spec.ConfigParams.GridBase != "" { + data = append(data, "GRID_BASE="+instance.Spec.ConfigParams.GridBase) + } else { + if instance.Status.ConfigParams != nil { + if instance.Status.ConfigParams.GridBase != "" { + data = append(data, "GRID_BASE="+instance.Status.ConfigParams.GridBase) + } + } + } + + if instance.Spec.ConfigParams.GridHome != "" { + data = append(data, "GRID_HOME="+instance.Spec.ConfigParams.GridHome) + } else { + if instance.Status.ConfigParams != nil { + if instance.Status.ConfigParams.GridHome != "" { + data = append(data, "GRID_HOME="+instance.Status.ConfigParams.GridHome) + } + } + } + + if instance.Spec.ConfigParams.Inventory != "" { + data = append(data, "INVENTORY="+instance.Spec.ConfigParams.Inventory) + } else { + if instance.Status.ConfigParams != nil { + if instance.Status.ConfigParams.Inventory != "" { + data = append(data, "INVENTORY="+instance.Status.ConfigParams.Inventory) + } + } + } + + if instance.Spec.SshKeySecret.Name != " " { + //SecretMap check is done in ValidateSpex + data = append(data, "SSH_PRIVATE_KEY="+instance.Spec.SshKeySecret.KeyMountLocation+"/"+instance.Spec.SshKeySecret.PrivKeySecretName) + data = append(data, "SSH_PUBLIC_KEY="+instance.Spec.SshKeySecret.KeyMountLocation+"/"+instance.Spec.SshKeySecret.PubKeySecretName) + } + + if instance.Spec.DbSecret != nil { + if instance.Spec.DbSecret.Name != "" { + data = append(data, "SECRET_VOLUME="+instance.Spec.DbSecret.PwdFileMountLocation) + commonpassflag, pwdkeyflag, _ := oraclerestartcommon.GetDbSecret(instance, instance.Spec.DbSecret.Name, r.Client) + if commonpassflag && pwdkeyflag { + data = append(data, "DB_PWD_FILE="+instance.Spec.DbSecret.PwdFileName) + data = append(data, "PWD_KEY="+instance.Spec.DbSecret.KeyFileName) + } else { + data = append(data, "PASSWORD_FILE=pwdfile") + } + } + } + + if instance.Spec.TdeWalletSecret != nil { + if instance.Spec.TdeWalletSecret.Name != "" { + data = append(data, "TDE_SECRET_VOLUME="+instance.Spec.TdeWalletSecret.PwdFileMountLocation) + data = append(data, "SETUP_TDE_WALLET=true") + tdepassflag, tdepwdkeyflag, _ := oraclerestartcommon.GetTdeWalletSecret(instance, instance.Spec.TdeWalletSecret.Name, r.Client) + if tdepassflag && tdepwdkeyflag { + data = append(data, "TDE_PWD_FILE="+instance.Spec.TdeWalletSecret.PwdFileName) + data = append(data, "TDE_PWD_KEY="+instance.Spec.TdeWalletSecret.KeyFileName) + } else { + data = append(data, "PASSWORD_FILE=tdepwdfile") + } + } + } + + data = append(data, "PROFILE_FLAG=true") + // data = append(data, "SCAN_NAME="+scan_name) + + data = append(data, "INSTALL_NODE="+install_node) + + if instance.Spec.ConfigParams.DbName != "" { + data = append(data, "DB_NAME="+instance.Spec.ConfigParams.DbName) + } else { + if instance.Status.ConfigParams != nil { + if instance.Status.ConfigParams.DbName != "" { + data = append(data, "DB_NAME="+instance.Status.ConfigParams.DbName) + } + } + } + + if instance.Spec.ConfigParams.PdbName != "" { + data = append(data, "ORACLE_PDB_NAME="+instance.Spec.ConfigParams.PdbName) + } else { + if instance.Status.ConfigParams != nil { + if instance.Status.ConfigParams.PdbName != "" { + data = append(data, "ORACLE_PDB_NAME="+instance.Status.ConfigParams.PdbName) + } + } + } + + if instance.Spec.ConfigParams.DbUniqueName != "" { + // Configmap check is done in ValidateSpex + data = append(data, "DB_UNIQUE_NAME="+instance.Spec.ConfigParams.DbUniqueName) + } else { + if instance.Status.ConfigParams != nil { + if instance.Status.ConfigParams.DbUniqueName != "" { + data = append(data, "DB_UNIQUE_NAME="+instance.Status.ConfigParams.DbUniqueName) + } + } + } + + if instance.Spec.ConfigParams.GridSwZipFile != "" { + data = append(data, "GRID_SW_ZIP_FILE="+instance.Spec.ConfigParams.GridSwZipFile) + //data = append(data, "COPY_GRID_SOFTWARE=true") + } + + if instance.Spec.ConfigParams.HostSwStageLocation != "" { + data = append(data, "STAGING_SOFTWARE_LOC="+instance.Spec.ConfigParams.HostSwStageLocation) + } else { + data = append(data, "STAGING_SOFTWARE_LOC="+utils.OraSwStageLocation) + } + + if instance.Spec.ConfigParams.RuPatchLocation != "" { + data = append(data, "APPLY_RU_LOCATION="+instance.Spec.ConfigParams.RuPatchLocation) + } + + if instance.Spec.ConfigParams.RuFolderName != "" { + data = append(data, "RU_FOLDER_NAME="+instance.Spec.ConfigParams.RuFolderName) + } + + if instance.Spec.ConfigParams.OPatchLocation != "" { + data = append(data, "OPATCH_ZIP_FILE="+instance.Spec.ConfigParams.OPatchLocation+"/"+instance.Spec.ConfigParams.OPatchSwZipFile) + } + if instance.Spec.ConfigParams.OneOffLocation != "" { + data = append(data, "ONEOFF_FOLDER_NAME="+instance.Spec.ConfigParams.OneOffLocation) + } + if instance.Spec.ConfigParams.DbOneOffIds != "" { + data = append(data, "DB_ONEOFF_IDS="+instance.Spec.ConfigParams.DbOneOffIds) + } + + if instance.Spec.ConfigParams.GridOneOffIds != "" { + data = append(data, "GRID_ONEOFF_IDS="+instance.Spec.ConfigParams.GridOneOffIds) + } + + if instance.Spec.ConfigParams.DbSwZipFile != "" { + data = append(data, "DB_SW_ZIP_FILE="+instance.Spec.ConfigParams.DbSwZipFile) + //data = append(data, "COPY_DB_SOFTWARE=true") + } + + if instance.Spec.ConfigParams.CrsAsmDiskDg != "" { + data = append(data, "CRS_ASM_DISKGROUP="+instance.Spec.ConfigParams.CrsAsmDiskDg) + } else { + if instance.Status.ConfigParams != nil { + if instance.Status.ConfigParams.CrsAsmDiskDg != "" { + data = append(data, "CRS_ASM_DISKGROUP="+instance.Status.ConfigParams.CrsAsmDiskDg) + } + } + } + + if instance.Spec.ConfigParams.DbAsmDeviceList != "" { + data = append(data, "DB_ASM_DEVICE_LIST="+instance.Spec.ConfigParams.DbAsmDeviceList) + } else { + if instance.Status.ConfigParams != nil { + if instance.Status.ConfigParams.DbAsmDeviceList != "" { + data = append(data, "DB_ASM_DEVICE_LIST="+instance.Status.ConfigParams.DbAsmDeviceList) + } + } + } + + if instance.Spec.ConfigParams.CrsAsmDeviceList != "" { + data = append(data, "CRS_ASM_DEVICE_LIST="+instance.Spec.ConfigParams.CrsAsmDeviceList) + } else { + data = append(data, "CRS_ASM_DEVICE_LIST="+asm_devices) + } + + if instance.Spec.ConfigParams.RecoAsmDeviceList != "" { + data = append(data, "RECO_ASM_DEVICE_LIST="+instance.Spec.ConfigParams.RecoAsmDeviceList) + } else { + if instance.Status.ConfigParams != nil { + if instance.Status.ConfigParams.RecoAsmDeviceList != "" { + data = append(data, "RECO_ASM_DEVICE_LIST="+instance.Status.ConfigParams.RecoAsmDeviceList) + } + } + } + + if instance.Spec.ConfigParams.RedoAsmDeviceList != "" { + data = append(data, "REDO_ASM_DEVICE_LIST="+instance.Spec.ConfigParams.RedoAsmDeviceList) + } else { + if instance.Status.ConfigParams != nil { + if instance.Status.ConfigParams.RedoAsmDeviceList != "" { + data = append(data, "REDO_ASM_DEVICE_LIST="+instance.Status.ConfigParams.RedoAsmDeviceList) + } + } + } + + // Perform following if operation is not add node + if !addnodeFlag { + if instance.Spec.ConfigParams.DbDataFileDestDg != "" { + data = append(data, "DB_DATA_FILE_DEST="+instance.Spec.ConfigParams.DbDataFileDestDg) + } + + if instance.Spec.ConfigParams.DbStorageType != "" { + data = append(data, "DB_STORAGE_TYPE="+instance.Spec.ConfigParams.DbStorageType) + } + + if instance.Spec.ConfigParams.DbCharSet != "" { + data = append(data, "DB_CHARACTERSET="+instance.Spec.ConfigParams.DbCharSet) + } + + if instance.Spec.ConfigParams.DbRedoFileSize != "" { + data = append(data, "DB_REDO_FILE_SIZE="+instance.Spec.ConfigParams.DbRedoFileSize) + } + + if instance.Spec.ConfigParams.DbType != "" { + data = append(data, "DB_TYPE="+instance.Spec.ConfigParams.DbType) + } + + if instance.Spec.ConfigParams.DbConfigType != "" { + data = append(data, "DB_CONFIG_TYPE="+instance.Spec.ConfigParams.DbConfigType) + } + + if instance.Spec.ConfigParams.EnableArchiveLog != "" { + data = append(data, "ENABLE_ARCHIVELOG="+instance.Spec.ConfigParams.EnableArchiveLog) + } + + if instance.Spec.ConfigParams.GridResponseFile.ConfigMapName != "" { + // Configmap check is done in ValidateSpex + data = append(data, "GRID_RESPONSE_FILE="+utils.OraGiRsp+"/"+instance.Spec.ConfigParams.GridResponseFile.Name) + } + + if instance.Spec.ConfigParams.DbResponseFile.ConfigMapName != "" { + // Configmap check is done in ValidateSpex + data = append(data, "DBCA_RESPONSE_FILE="+utils.OraDbRsp+"/"+instance.Spec.ConfigParams.DbResponseFile.Name) + } + + // Getting DB Related paraeters + + if instance.Spec.ConfigParams.SgaSize != "" { + // Configmap check is done in ValidateSpex + data = append(data, "INIT_SGA_SIZE="+instance.Spec.ConfigParams.SgaSize) + } + + if instance.Spec.ConfigParams.PgaSize != "" { + // Configmap check is done in ValidateSpex + data = append(data, "INIT_PGA_SIZE="+instance.Spec.ConfigParams.PgaSize) + } + + if instance.Spec.ConfigParams.Processes > 0 { + // Configmap check is done in ValidateSpex + data = append(data, "INIT_PROCESSES="+strconv.Itoa(instance.Spec.ConfigParams.Processes)) + } + + if instance.Spec.ConfigParams.CpuCount > 0 { + // Configmap check is done in ValidateSpex + data = append(data, "CPU_COUNT="+strconv.Itoa(instance.Spec.ConfigParams.CpuCount)) + } + + if instance.Spec.ConfigParams.DbRecoveryFileDest != "" { + // Configmap check is done in ValidateSpex + data = append(data, "DB_RECOVERY_FILE_DEST="+instance.Spec.ConfigParams.DbRecoveryFileDest) + } + + if instance.Spec.ConfigParams.RedoAsmDiskDg != "" { + // Configmap check is done in ValidateSpex + data = append(data, "LOG_FILE_DEST="+instance.Spec.ConfigParams.RedoAsmDiskDg) + } + + if instance.Spec.ConfigParams.DbRecoveryFileDestSize != "" { + // Configmap check is done in ValidateSpex + data = append(data, "DB_RECOVERY_FILE_DEST_SIZE="+instance.Spec.ConfigParams.DbRecoveryFileDestSize) + } + if instance.Spec.ConfigParams.DBAsmDiskDgRedundancy != "" { + data = append(data, "DB_ASMDG_PROPERTIES="+"redundancy:"+instance.Spec.ConfigParams.DBAsmDiskDgRedundancy) + } + + if instance.Spec.ConfigParams.RedoAsmDiskDgRedundancy != "" { + data = append(data, "REDO_ASMDG_PROPERTIES="+"redundancy:"+instance.Spec.ConfigParams.RedoAsmDiskDgRedundancy) + } + + if instance.Spec.ConfigParams.RecoAsmDiskDgRedundancy != "" { + data = append(data, "RECO_ASMDG_PROPERTIES="+"redundancy:"+instance.Spec.ConfigParams.RecoAsmDiskDgRedundancy) + } + + if instance.Spec.ConfigParams.CrsAsmDiskDgRedundancy != "" { + data = append(data, "CRS_ASMDG_REDUNDANCY="+"redundancy="+instance.Spec.ConfigParams.CrsAsmDiskDgRedundancy) + } + } + + configMapData["envfile"] = strings.Join(data, "\r\n") + + return configMapData, nil +} + +// ================================== CREATE FUNCTIONS ============================= + +// Create the configmap + +func (r *OracleRestartReconciler) createConfigMap( + ctx context.Context, + instance oraclerestartdb.OracleRestart, + cm *corev1.ConfigMap, +) (ctrl.Result, bool, error) { // Added `bool` return + reqLogger := r.Log.WithValues("Instance.Namespace", instance.Namespace, "Instance.Name", instance.Name) + + found := &corev1.ConfigMap{} + err := r.Get(ctx, types.NamespacedName{ + Name: cm.Name, + Namespace: instance.Namespace, + }, found) + if err != nil && apierrors.IsNotFound(err) { + // ConfigMap does not exist - create it + reqLogger.Info("Creating Configmap Normally") + if err = r.Create(ctx, cm); err != nil { + reqLogger.Error(err, "failed to create configmap", "namespace", instance.Namespace) + return ctrl.Result{}, false, err + } + return ctrl.Result{Requeue: true}, true, nil // Indicate configmap was created + } else if err != nil { + // Error getting ConfigMap + reqLogger.Error(err, "failed to find the configmap details") + return ctrl.Result{}, false, err + } + + // At this point, ConfigMap exists: found + // Compare data and update if needed only for environment variables changes + if found.Data["envfile"] != cm.Data["envfile"] { + reqLogger.Info("ConfigMap env key changed, updating") + found.Data["envfile"] = cm.Data["envfile"] + if err := r.Update(ctx, found); err != nil { + reqLogger.Error(err, "failed to update configmap", "namespace", instance.Namespace) + return ctrl.Result{}, false, err + } + return ctrl.Result{Requeue: true}, true, nil // Indicate data was changed + } + + // No changes needed + return ctrl.Result{}, false, nil +} + +// This function create a service based isExtern parameter set in the yaml file +func (r *OracleRestartReconciler) createOrReplaceService(ctx context.Context, instance *oraclerestartdb.OracleRestart, + dep *corev1.Service, +) (ctrl.Result, error) { + reqLogger := r.Log.WithValues("Instance.Namespace", instance.Namespace, "Instance.Name", instance.Name) + // See if Service already exists and create if it doesn't + + found := &corev1.Service{} + + err := r.Get(ctx, types.NamespacedName{ + Name: dep.Name, + Namespace: instance.Namespace, + }, found) + + jsn, _ := json.Marshal(dep) + oraclerestartcommon.LogMessages("DEBUG", string(jsn), nil, instance, r.Log) + if err != nil && apierrors.IsNotFound(err) { + // Create the Service + reqLogger.Info("Creating a service") + err = r.Create(ctx, dep) + if err != nil { + // Service creation failed + instance.Spec.IsFailed = true + reqLogger.Error(err, "Failed to create Service", "Service.Namespace", dep.Namespace, "Service.Name", dep.Name) + return ctrl.Result{}, nil + } else { + // Service creation was successful + return ctrl.Result{Requeue: true}, nil + } + } else if err != nil { + // Error that isn't due to the Service not existing + reqLogger.Error(err, "Failed to find the Service details") + return ctrl.Result{}, err + } + + return ctrl.Result{}, nil +} + +// ================================== CREATE FUNCTIONS ============================= +// This function create a service based isExtern parameter set in the yaml file + +func (r *OracleRestartReconciler) createOrReplaceAsmPv( + ctx context.Context, + instance *oraclerestartdb.OracleRestart, + dep *corev1.PersistentVolume, +) (string, ctrl.Result, error) { + reqLogger := r.Log.WithValues("Instance.Namespace", instance.Namespace, "Instance.Name", instance.Name) + found := &corev1.PersistentVolume{} + if dep == nil { + reqLogger.Error(nil, "PersistentVolume spec (dep) is nil") + return "", ctrl.Result{}, fmt.Errorf("PV object is nil") + } + + // Fetch the existing PV + err := r.Get(context.TODO(), types.NamespacedName{ + Name: dep.Name, + }, found) + + jsn, _ := json.Marshal(dep) + oraclerestartcommon.LogMessages("DEBUG", string(jsn), nil, instance, r.Log) + + if err != nil && apierrors.IsNotFound(err) { + // PV does not exist, create it + reqLogger.Info("Creating a new PV", "dep.Name", dep.Name) + err = r.Create(context.TODO(), dep) + if err != nil { + // PV creation failed + instance.Spec.IsFailed = true + reqLogger.Error(err, "Failed to create Persistent Volume", "PV.Name", dep.Name) + return "", ctrl.Result{}, err + } + return dep.Name, ctrl.Result{}, nil + } else if err != nil { + // Other errors fetching the PV + reqLogger.Error(err, "Failed to get Persistent Volume details") + return "", ctrl.Result{}, err + } + + // Check if the disk path or configuration differs from the existing PV + if !reflect.DeepEqual(dep.Spec.PersistentVolumeSource.Local, found.Spec.PersistentVolumeSource.Local) { + // Disk configuration has changed, delete the old PV and create a new one + reqLogger.Info("Detected existing PV with different disk details and as the configuration has changed, setup cannot continue", "dep.Name", dep.Name) + return "", ctrl.Result{}, fmt.Errorf("persistent volume %s has a different disk configuration. Please delete or update the existing PV to proceed", dep.Name) + } + + reqLogger.Info("PV Found", "dep.Name", dep.Name) + + return found.Name, ctrl.Result{}, nil +} + +// ================================== CREATE FUNCTIONS ============================= +// This function create a PVC set in the yaml file +func (r *OracleRestartReconciler) createOrReplaceAsmPvC(ctx context.Context, instance *oraclerestartdb.OracleRestart, + dep *corev1.PersistentVolumeClaim, +) (string, ctrl.Result, error) { + reqLogger := r.Log.WithValues("Instance.Namespace", instance.Namespace, "Instance.Name", instance.Name) + found := &corev1.PersistentVolumeClaim{} + + err := r.Get(ctx, types.NamespacedName{ + Name: dep.Name, + Namespace: instance.Namespace, + }, found) + + jsn, _ := json.Marshal(dep) + oraclerestartcommon.LogMessages("DEBUG", string(jsn), nil, instance, r.Log) + if err != nil && apierrors.IsNotFound(err) { + // Create the Service + reqLogger.Info("Creating a PVC") + dep.Spec.Selector = nil + err = r.Create(ctx, dep) + if err != nil { + // Service creation failed + instance.Spec.IsFailed = true + reqLogger.Error(err, "Failed to create Persistent Volume", "PVC.Namespace", dep.Namespace, "PersistentVolume.Name", dep.Name) + return "", ctrl.Result{}, err + } else { + // Service creation was successful + return dep.Name, ctrl.Result{}, nil + } + } else if err != nil { + // Error that isn't due to the Service not existing + reqLogger.Error(err, "Failed to find the persistent volume Claim details") + return "", ctrl.Result{}, err + } + + return found.Name, ctrl.Result{}, nil +} + +// ensureAsmStorageStatus initializes AsmStorageDetails and AsmStorageStatus if they are nil +func (r *OracleRestartReconciler) ensureAsmStorageStatus(oracleRestart *oraclerestartdb.OracleRestart) { + // Check if AsmDetails is nil and initialize it if necessary + if oracleRestart.Status.AsmDetails == nil { + oracleRestart.Status.AsmDetails = &oraclerestartdb.AsmInstanceStatus{ + Diskgroup: []oraclerestartdb.AsmDiskgroupStatus{}, + } + } + +} + +func (r *OracleRestartReconciler) ensureStatefulSetUpdated(ctx context.Context, + reqLogger logr.Logger, + oracleRestart *oraclerestartdb.OracleRestart, + desired *appsv1.StatefulSet, + asmAutoUpdate bool, + // isDelete bool, + req ctrl.Request) error { + timeout := 15 * time.Minute // Set a timeout for the update wait + timeoutCtx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + // Fetch the existing StatefulSet + existing := &appsv1.StatefulSet{} + err := r.Get(ctx, types.NamespacedName{ + Name: desired.Name, + Namespace: oracleRestart.Namespace, + }, existing) + if err != nil { + if apierrors.IsNotFound(err) { + // If the StatefulSet doesn't exist, create it + reqLogger.Info("StatefulSet not found, creating new one", "StatefulSet.Namespace", oracleRestart.Namespace, "StatefulSet.Name", desired.Name) + return r.Create(ctx, desired) + } + reqLogger.Error(err, "Failed to get StatefulSet", "StatefulSet.Namespace", oracleRestart.Namespace, "StatefulSet.Name", desired.Name) + return err + } + + // Compare the existing StatefulSet spec with the desired spec, sfs is replaced when ASM devices are added or removed + if len(existing.Spec.Template.Spec.Containers[0].VolumeDevices) != len(desired.Spec.Template.Spec.Containers[0].VolumeDevices) { + r.Log.Info("Change State to UPDATING") + + // Update status for each instance + r.updateOracleRestartInstStatus(oracleRestart, ctx, req, oracleRestart.Spec.InstDetails, string(oraclerestartdb.OracleRestartUpdateState), r.Client, true) + + reqLogger.Info("StatefulSet spec differs for volume devices, updating StatefulSet (pods may be recreated)", "StatefulSet.Namespace", oracleRestart.Namespace, "StatefulSet.Name", desired.Name) + + // Perform the update + err := r.Update(ctx, desired) + if err != nil { + reqLogger.Error(err, "Failed to update StatefulSet", "StatefulSet.Namespace", oracleRestart.Namespace, "StatefulSet.Name", desired.Name) + return err + } + + reqLogger.Info("StatefulSet update applied, waiting for pod recreation", "StatefulSet.Namespace", oracleRestart.Namespace, "StatefulSet.Name", desired.Name) + // } else { + // r.Log.Info("Change State to UPDATING") + + // Wait for the update to be applied + for { + select { + case <-timeoutCtx.Done(): + reqLogger.Error(timeoutCtx.Err(), "Timed out waiting for StatefulSet update", "StatefulSet.Namespace", oracleRestart.Namespace, "StatefulSet.Name", desired.Name) + return timeoutCtx.Err() + + default: + updated := &appsv1.StatefulSet{} + err := r.Get(ctx, client.ObjectKey{ + Name: desired.Name, + Namespace: oracleRestart.Namespace, + }, updated) + + if err != nil { + reqLogger.Error(err, "Failed to get StatefulSet after update", "StatefulSet.Namespace", oracleRestart.Namespace, "StatefulSet.Name", desired.Name) + return err + } + + if reflect.DeepEqual(updated.Spec.Template.Spec.Containers[0].VolumeDevices, desired.Spec.Template.Spec.Containers[0].VolumeDevices) { + reqLogger.Info("StatefulSet update is applied successfully", "StatefulSet.Namespace", oracleRestart.Namespace, "StatefulSet.Name", desired.Name) + return nil + } + + reqLogger.Info("Waiting for StatefulSet update to be applied", "StatefulSet.Namespace", oracleRestart.Namespace, "StatefulSet.Name", desired.Name) + time.Sleep(5 * time.Second) + } + } + // } + } else { + reqLogger.Info("StatefulSet matches for ASM devices, SFS wont be updated", "StatefulSet.Namespace", oracleRestart.Namespace, "StatefulSet.Name", desired.Name) + return nil + } +} + +func executeDiskGroupCommand(podName string, cmd []string, kubeClient kubernetes.Interface, kubeConfig clientcmd.ClientConfig, instance *oraclerestartdb.OracleRestart, logger logr.Logger) (string, string, error) { + return oraclerestartcommon.ExecCommand(podName, cmd, kubeClient, kubeConfig, instance, logger) +} + +// Function to get the disk group name +func getDiskGroupName(deviceDg string, oracleRestart *oraclerestartdb.OracleRestart) string { + switch deviceDg { + case oracleRestart.Spec.ConfigParams.CrsAsmDeviceList: + return oracleRestart.Spec.ConfigParams.CrsAsmDiskDg + case oracleRestart.Spec.ConfigParams.DbAsmDeviceList: + return oracleRestart.Spec.ConfigParams.DbDataFileDestDg + default: + return "" + } +} + +// Function to check if a disk group exists +func (r *OracleRestartReconciler) diskGroupExists(podName, diskGroupName string, kubeClient kubernetes.Interface, kubeConfig clientcmd.ClientConfig, instance *oraclerestartdb.OracleRestart, logger logr.Logger) (bool, error) { + reqLogger := r.Log.WithValues("Instance.Namespace", instance.Namespace, "Instance.Name", instance.Name) + cmd := "python3 /opt/scripts/startup/scripts/main.py --getasmdiskgroup=true" + stdout, _, err := oraclerestartcommon.ExecCommand(podName, []string{"bash", "-c", cmd}, r.kubeClient, r.kubeConfig, instance, reqLogger) + if err != nil { + return false, err + } + if strings.Contains(stdout, diskGroupName) { + return true, nil + } + return false, nil +} + +// Function to add disks +func (r *OracleRestartReconciler) addDisks(ctx context.Context, podList *corev1.PodList, instance *oraclerestartdb.OracleRestart, diskGroupName string, deviceList []string) error { + reqLogger := r.Log.WithValues("Instance.Namespace", instance.Namespace, "Instance.Name", instance.Name) + // Remove '+' prefix if present + if strings.HasPrefix(diskGroupName, "+") { + diskGroupName = strings.TrimPrefix(diskGroupName, "+") + } + + for _, pod := range podList.Items { + podName := pod.Name + + // Check if the disk group exists before trying to add disks + exists, err := r.diskGroupExists(podName, diskGroupName, r.kubeClient, r.kubeConfig, instance, reqLogger) + if err != nil { + reqLogger.Error(err, "Failed to check if disk group exists", "Pod.Name", podName, "DiskGroup", diskGroupName) + return err + } + if !exists { + err = fmt.Errorf("disk group %s does not exist", diskGroupName) + reqLogger.Error(err, "Disk group does not exist", "Pod.Name", podName, "DiskGroup", diskGroupName) + return err + } + + for _, disk := range deviceList { + cmd := fmt.Sprintf("python3 /opt/scripts/startup/scripts/main.py --updateasmdevices=\"diskname=%s;diskgroup=%s;processtype=addition\"", disk, diskGroupName) + reqLogger.Info("Executing command to add disk", "Pod.Name", podName, "Command", cmd) + stdout, stderr, err := oraclerestartcommon.ExecCommand(podName, []string{"bash", "-c", cmd}, r.kubeClient, r.kubeConfig, instance, reqLogger) + if err != nil { + instance.Spec.IsFailed = true + reqLogger.Error(err, "Failed to execute command", "Pod.Name", podName, "Command", cmd, "Stdout", stdout, "Stderr", stderr) + return err + } + } + } + return nil +} + +// Function to check DaemonSet status with retry, timeout, and log analysis +func checkDaemonSetStatus(ctx context.Context, r *OracleRestartReconciler, oracleRestart *oraclerestartdb.OracleRestart) (bool, error) { + timeout := time.After(2 * time.Minute) + tick := time.NewTicker(10 * time.Second) // Poll every 10 seconds + defer tick.Stop() + // Sleep for 60 seconds + for { + select { + case <-timeout: + // Timeout reached + ds := &appsv1.DaemonSet{} + err := r.Client.Get(ctx, types.NamespacedName{ + Name: "disk-check-daemonset", + Namespace: oracleRestart.Namespace, + }, ds) + if err != nil { + return false, err + } + + // Fetch the list of Pods managed by the DaemonSet + pods, err := r.kubeClient.CoreV1().Pods(oracleRestart.Namespace).List(ctx, metav1.ListOptions{ + LabelSelector: "app=disk-check", + }) + if err != nil { + return false, err + } + + // Check logs from each Pod + for _, pod := range pods.Items { + if pod.Status.Phase != corev1.PodRunning { + // Pod is not running, check for logs and errors + logs, err := r.kubeClient.CoreV1().Pods(oracleRestart.Namespace).GetLogs( + pod.Name, + &corev1.PodLogOptions{}, + ).DoRaw(ctx) + if err != nil { + return false, err + } + + if bytes.Contains(logs, []byte("not a valid block device")) { + // Disk validation failed + return false, nil + } + } + } + + // DaemonSet did not become ready or running within the timeout + return false, fmt.Errorf("DaemonSet %s/%s did not become ready or running within 5 minutes", oracleRestart.Namespace, "disk-check-daemonset") + + case <-tick.C: + // Check DaemonSet status + ds := &appsv1.DaemonSet{} + err := r.Client.Get(ctx, types.NamespacedName{ + Name: "disk-check-daemonset", + Namespace: oracleRestart.Namespace, + }, ds) + if err != nil { + return false, err + } + + // Check DaemonSet readiness + if ds.Status.NumberReady == ds.Status.DesiredNumberScheduled && ds.Status.NumberReady > 0 { + // DaemonSet is running and ready + return true, nil + } + + // If DaemonSet is not ready, fetch the list of Pods managed by the DaemonSet + pods, err := r.kubeClient.CoreV1().Pods(oracleRestart.Namespace).List(ctx, metav1.ListOptions{ + LabelSelector: "app=disk-check", + }) + if err != nil { + return false, err + } + + // Check logs from each Pod + for _, pod := range pods.Items { + // Pod is not running, check for logs and errors + logs, err := r.kubeClient.CoreV1().Pods(oracleRestart.Namespace).GetLogs( + pod.Name, + &corev1.PodLogOptions{}, + ).DoRaw(ctx) + if err != nil { + return false, err + } + + if bytes.Contains(logs, []byte("not a valid block device")) { + // Disk validation failed + return false, nil + } + + } + } + } +} + +// ================================== CREATE FUNCTIONS ============================= +func (r *OracleRestartReconciler) createOrReplaceSfs( + ctx context.Context, + req ctrl.Request, + oracleRestart oraclerestartdb.OracleRestart, + dep *appsv1.StatefulSet, + index int, + isLast bool, + oldState string, + configmapChanged bool, +) (ctrl.Result, error) { + reqLogger := r.Log.WithValues("Instance.Namespace", oracleRestart.Namespace, "Instance.Name", oracleRestart.Name) + found := &appsv1.StatefulSet{} + + err := r.Get(ctx, types.NamespacedName{ + Name: dep.Name, + Namespace: oracleRestart.Namespace, + }, found) + + jsn, _ := json.Marshal(dep) + oraclerestartcommon.LogMessages("DEBUG", string(jsn), nil, &oracleRestart, r.Log) + + if err != nil && apierrors.IsNotFound(err) { + // CREATE + r.updateOracleRestartInstStatus(&oracleRestart, ctx, req, oracleRestart.Spec.InstDetails, + string(oraclerestartdb.OracleRestartProvisionState), r.Client, true) + reqLogger.Info("Creating a StatefulSet Normally", "StatefulSetName", dep.Name) + err = r.Create(ctx, dep) + if err != nil { + oracleRestart.Spec.IsFailed = true + reqLogger.Error(err, "Failed to create StatefulSet", "StatefulSet.Namespace", dep.Namespace, "StatefulSet.Name", dep.Name) + return ctrl.Result{}, err + } else if !isLast { + // StatefulSet creation was successful + return ctrl.Result{}, nil + } + } else if err != nil { + // Any other Get error + reqLogger.Error(err, "Failed to find the StatefulSet details") + return ctrl.Result{}, err + } else { + // Compare resource requirements + foundRes := found.Spec.Template.Spec.Containers[0].Resources + depRes := dep.Spec.Template.Spec.Containers[0].Resources + resourcesChanged := !reflect.DeepEqual(foundRes, depRes) + + // Compare configMap relevant data (example: pass in variable configmapChanged) + if resourcesChanged || configmapChanged { + // Copy metadata fields that must be preserved + dep.ResourceVersion = found.ResourceVersion + dep.UID = found.UID + dep.CreationTimestamp = found.CreationTimestamp + dep.ManagedFields = found.ManagedFields + dep.Status = found.Status + + reason := "unknown" + if resourcesChanged && configmapChanged { + reason = "resource and configmap change" + } else if resourcesChanged { + reason = "resource change" + } else if configmapChanged { + reason = "configmap change" + } + + reqLogger.Info("Updating StatefulSet due to "+reason, "StatefulSetName", dep.Name) + err = r.Update(ctx, dep) + if err != nil { + oracleRestart.Spec.IsFailed = true + reqLogger.Error(err, "Failed to update StatefulSet", "StatefulSet.Namespace", dep.Namespace, "StatefulSet.Name", dep.Name) + return ctrl.Result{}, err + } + } + } + + return ctrl.Result{}, nil +} + +// ================================== CREATE FUNCTIONS ============================= +// This function create a PVC set in the yaml file +func (r *OracleRestartReconciler) createOrReplaceSfsAsm(ctx context.Context, req ctrl.Request, oracleRestart *oraclerestartdb.OracleRestart, + dep *appsv1.StatefulSet, asmAutoUpdate bool, index int, isLast bool, oldSpec *oraclerestartdb.OracleRestartSpec, +) (ctrl.Result, error) { + reqLogger := r.Log.WithValues("oracleRestart.Namespace", oracleRestart.Namespace, "oracleRestart.Name", oracleRestart.Name) + + found := &appsv1.StatefulSet{} + + // Check if the StatefulSet was found successfully + err := r.Get(ctx, types.NamespacedName{ + Name: dep.Name, + Namespace: oracleRestart.Namespace, + }, found) + if err != nil { + reqLogger.Error(err, "Failed to find existing StatefulSet to update") + return ctrl.Result{}, err + } + + addedAsmDisks, removedAsmDisks := getAddedAndRemovedDisks(oracleRestart, oldSpec, index) + + // Deletion Process execution + // isDelete := false + inUse := false + if len(removedAsmDisks) > 0 { + OraRacSpex := oracleRestart.Spec.InstDetails + racSfSet, err := oraclerestartcommon.CheckSfset(OraRacSpex.Name, oracleRestart, r.Client) + if err != nil { + errMsg := fmt.Errorf("failed to retrieve StatefulSet for RAC database '%s': %w", OraRacSpex.Name, err) + r.Log.Error(err, errMsg.Error()) + return reconcile.Result{}, errMsg + } + + // Step 2: Get the Pod list + podList, err := oraclerestartcommon.GetPodList(racSfSet.Name, oracleRestart, r.Client, OraRacSpex) + if err != nil { + errMsg := fmt.Errorf("failed to retrieve pod list for StatefulSet '%s': %w", racSfSet.Name, err) + r.Log.Error(err, errMsg.Error()) + return reconcile.Result{}, errMsg + } + if len(podList.Items) == 0 { + errMsg := fmt.Errorf("no pods found for StatefulSet '%s'", racSfSet.Name) + r.Log.Error(errMsg, "Empty pod list") + return reconcile.Result{}, errMsg + } + + // Step 3: Use last pod to get ASM state + podName := podList.Items[len(podList.Items)-1].Name + oracleRestart.Status.AsmDetails = oraclerestartcommon.GetAsmInstState(podName, oracleRestart, 0, r.kubeClient, r.kubeConfig, r.Log) + + // Check if removed disks are in use in any diskgroup + asmInstanceStatus := oracleRestart.Status.AsmDetails + // isDelete = true + for _, removedAsmDisk := range removedAsmDisks { + for _, diskgroup := range asmInstanceStatus.Diskgroup { + for _, asmDiskStr := range diskgroup.Disks { + asmDisks := strings.Split(asmDiskStr, ",") + // Now compare each disk with removedAsmDisk + for _, asmDisk := range asmDisks { + if removedAsmDisk == asmDisk { + inUse = true + // / Disk is in use, return a message to the user and dont proceed further + err := fmt.Errorf("disk '%s' is part of diskgroup '%s' and must be manually removed before proceeding", removedAsmDisk, diskgroup.Name) + r.Log.Info("Disk is in use and cannot be removed. Must be manually removed before proceeding", "disk", removedAsmDisk, "diskgroup", diskgroup.Name) + return reconcile.Result{}, err + } + } + } + } + } + } + + r.ensureAsmStorageStatus(oracleRestart) + + // Ensure the StatefulSet is updated or re-created based on autoUpdate set to true/false + // err = r.ensureStatefulSetUpdated(ctx, reqLogger, OracleRestart, dep, autoUpdate, isDelete, req) + err = r.ensureStatefulSetUpdated(ctx, reqLogger, oracleRestart, dep, asmAutoUpdate, req) + if err != nil { + oracleRestart.Spec.IsFailed = true + reqLogger.Error(err, "Failed to ensure StatefulSet is updated or created") + r.updateOracleRestartInstStatus(oracleRestart, ctx, req, oracleRestart.Spec.InstDetails, string(oraclerestartdb.OracleRestartFailedState), r.Client, true) + return ctrl.Result{}, err + } + + // Wait for all Pods to be created and running + podList := &corev1.PodList{} + timeout := time.After(15 * time.Minute) // 15-minute timeout + ticker := time.NewTicker(1 * time.Minute) + defer ticker.Stop() + + allPodsRunning := false + +waitLoop: + for { + select { + case <-timeout: + reqLogger.Info("Timed out waiting for all Pods to be created and running", "StatefulSet.Namespace", dep.Namespace, "StatefulSet.Name", dep.Name) + return reconcile.Result{}, fmt.Errorf("timed out waiting for all Pods to be created and running") + case <-ticker.C: + err = r.List(ctx, podList, client.InNamespace(dep.Namespace), client.MatchingLabels(dep.Spec.Template.Labels)) + if err != nil { + reqLogger.Error(err, "Failed to list Pods", "StatefulSet.Namespace", dep.Namespace, "StatefulSet.Name", dep.Name) + return reconcile.Result{}, err + } + + allPodsRunning = true + for _, pod := range podList.Items { + if pod.Status.Phase != corev1.PodRunning { + allPodsRunning = false + reqLogger.Info("Waiting for Pod to be running", "Pod.Namespace", pod.Namespace, "Pod.Name", pod.Name) + break + } + } + + if allPodsRunning { + reqLogger.Info("All Pods are running", "StatefulSet.Namespace", dep.Namespace, "StatefulSet.Name", dep.Name) + break waitLoop + } + } + } + + if allPodsRunning { + const ( + podCheckInterval = 15 * time.Second // Interval between pod readiness checks + podReadyTimeout = 15 * time.Minute // Maximum wait time for pod readiness + ) + + timeoutCtx, cancel := context.WithTimeout(ctx, podReadyTimeout) + defer cancel() + + // Wait for StatefulSet pods to be ready + for i := 0; i < len(dep.Spec.Template.Spec.Containers); i++ { + podName := fmt.Sprintf("%s-%d", dep.Name, i) + var isPodReady bool + + waitForPodASM: + for { + select { + case <-timeoutCtx.Done(): + reqLogger.Error(timeoutCtx.Err(), "Timed out waiting for pod to be ready", "Pod.Name", podName) + r.updateOracleRestartInstStatus(oracleRestart, ctx, req, oracleRestart.Spec.InstDetails, string(oraclerestartdb.OracleRestartFailedState), r.Client, true) + return ctrl.Result{}, timeoutCtx.Err() + default: + podList, err := oraclerestartcommon.GetPodList(dep.Name, oracleRestart, r.Client, oracleRestart.Spec.InstDetails) + if err != nil { + reqLogger.Error(err, "Failed to list pods") + return ctrl.Result{}, err + } + time.Sleep(podCheckInterval) + isPodReady, _, _ = oraclerestartcommon.PodListValidation(podList, dep.Name, oracleRestart, r.Client) + if isPodReady { + reqLogger.Info("Pod is ready", "Pod.Name", podName) + break waitForPodASM // Break out of the labeled loop + } else { + reqLogger.Info("Pod is not ready yet", "Pod.Name", podName) + time.Sleep(podCheckInterval) + } + } + } + } + + } + + // Disk is not in use, proceed with PV and PVC deletion in last stage + if isLast && !inUse { + // Use oraclerestartcommon.GetAsmPvcName and oraclerestartcommon.getAsmPvName to generate PVC and PV names + + // Find and delete the corresponding PVC + for _, diskName := range oracleRestart.Status.OracleRestartNodes[index].NodeDetails.MountedDevices { + for _, removedAsmDisk := range removedAsmDisks { + if diskName == removedAsmDisk { + pvcName := oraclerestartcommon.GetAsmPvcName(oracleRestart.Name, diskName, oracleRestart) // Use the existing function + pvc := &corev1.PersistentVolumeClaim{} + err := r.Get(ctx, client.ObjectKey{ + Name: pvcName, + Namespace: oracleRestart.Namespace, + }, pvc) + if err != nil { + if !apierrors.IsNotFound(err) { + r.Log.Error(err, "Failed to get PVC", "PVC.Name", pvcName) + return reconcile.Result{}, err + } + // PVC already deleted + } else { + err = r.Delete(ctx, pvc) + if err != nil { + r.Log.Error(err, "Failed to delete PVC", "PVC.Name", pvcName) + return reconcile.Result{}, err + } + r.Log.Info("Successfully deleted PVC", "PVC.Name", pvcName) + } + + // Find and delete the corresponding PV + pvName := oraclerestartcommon.GetAsmPvName(oracleRestart.Name, diskName, oracleRestart) // Use the existing function + pv := &corev1.PersistentVolume{} + err = r.Get(ctx, client.ObjectKey{ + Name: pvName, + }, pv) + if err != nil { + if !apierrors.IsNotFound(err) { + r.Log.Error(err, "Failed to get PV", "PV.Name", pvName) + return reconcile.Result{}, err + } + // PV already deleted + } else { + err = r.Delete(ctx, pv) + if err != nil { + r.Log.Error(err, "Failed to delete PV", "PV.Name", pvName) + return reconcile.Result{}, err + } + r.Log.Info("Successfully deleted PV", "PV.Name", pvName) + } + } + } + } + } + + if isLast && asmAutoUpdate { + // last iteration + deviceDg := "" + for _, disk := range addedAsmDisks { + if isDiskInDeviceList(disk, oracleRestart.Spec.ConfigParams.CrsAsmDeviceList) { + reqLogger.Info("New disk to be added to CRS ASM device list ", "disk", disk) + deviceDg = oracleRestart.Spec.ConfigParams.CrsAsmDiskDg + } + if isDiskInDeviceList(disk, oracleRestart.Spec.ConfigParams.DbAsmDeviceList) { + reqLogger.Info("New disk to be added to DB ASM device list ", "disk", disk) + deviceDg = oracleRestart.Spec.ConfigParams.DbDataFileDestDg + } + if isDiskInDeviceList(disk, oracleRestart.Spec.ConfigParams.RecoAsmDeviceList) { + reqLogger.Info("New disk to be added to RECO ASM device list ", "disk", disk) + deviceDg = oracleRestart.Spec.ConfigParams.RecoAsmDiskDgRedundancy + } + if isDiskInDeviceList(disk, oracleRestart.Spec.ConfigParams.RedoAsmDeviceList) { + reqLogger.Info("New disk to be added to REDO ASM device list ", "disk", disk) + deviceDg = oracleRestart.Spec.ConfigParams.RedoAsmDiskDgRedundancy + } + } + if deviceDg != "" { + // Add disks after POD recreation + podList, err := oraclerestartcommon.GetPodList(dep.Name, oracleRestart, r.Client, oracleRestart.Spec.InstDetails) + if err != nil { + reqLogger.Error(err, "Failed to list pods") + return ctrl.Result{}, err + } + err = r.addDisks(ctx, podList, oracleRestart, deviceDg, addedAsmDisks) + if err != nil { + return ctrl.Result{}, err + } + reqLogger.Info("New Disks added to CRS Disks Group") + } + } + + return ctrl.Result{}, nil +} + +func getAddedAndRemovedDisks(oracleRestart *oraclerestartdb.OracleRestart, oldSpec *oraclerestartdb.OracleRestartSpec, index int) ([]string, []string) { + addedAsmDisks := []string{} + removedAsmDisks := []string{} + + // Helper function to compare desired and previous disk lists + compareDisks := func(newDisks, oldDisks []string) ([]string, []string) { + newDiskMap := make(map[string]bool) + oldDiskMap := make(map[string]bool) + + for _, disk := range newDisks { + if disk != "" { + newDiskMap[disk] = true + } + } + for _, disk := range oldDisks { + if disk != "" { + oldDiskMap[disk] = true + } + } + + // Initialize added and removed slices for this comparison + added := []string{} + removed := []string{} + + for _, disk := range newDisks { + if disk != "" && !oldDiskMap[disk] { + added = append(added, disk) + } + } + for _, disk := range oldDisks { + if disk != "" && !newDiskMap[disk] { + removed = append(removed, disk) + } + } + + return added, removed + } + + // Flatten the new desired ASM disk lists + desiredAsmDisks := flattenDisksBySize(&oracleRestart.Spec) + oldAsmDisks := flattenDisksBySize(oldSpec) + + // Additional device lists + newCrsAsmDisks := strings.Split(oracleRestart.Spec.ConfigParams.CrsAsmDeviceList, ",") + oldCrsAsmDisks := strings.Split(oldSpec.ConfigParams.CrsAsmDeviceList, ",") + + newDbAsmDisks := strings.Split(oracleRestart.Spec.ConfigParams.DbAsmDeviceList, ",") + oldDbAsmDisks := strings.Split(oldSpec.ConfigParams.DbAsmDeviceList, ",") + + newRecoAsmDisks := strings.Split(oracleRestart.Spec.ConfigParams.RecoAsmDeviceList, ",") + oldRecoAsmDisks := strings.Split(oldSpec.ConfigParams.RecoAsmDeviceList, ",") + + newRedoAsmDisks := strings.Split(oracleRestart.Spec.ConfigParams.RedoAsmDeviceList, ",") + oldRedoAsmDisks := strings.Split(oldSpec.ConfigParams.RedoAsmDeviceList, ",") + + // Track unique added and removed disks + addedDiskSet := make(map[string]bool) + removedDiskSet := make(map[string]bool) + + // Compare ASM and other device lists + for _, diskLists := range [][2][]string{ + {desiredAsmDisks, oldAsmDisks}, + {newCrsAsmDisks, oldCrsAsmDisks}, + {newDbAsmDisks, oldDbAsmDisks}, + {newRecoAsmDisks, oldRecoAsmDisks}, + {newRedoAsmDisks, oldRedoAsmDisks}, + } { + added, removed := compareDisks(diskLists[0], diskLists[1]) + for _, disk := range added { + addedDiskSet[disk] = true + } + for _, disk := range removed { + removedDiskSet[disk] = true + } + } + + // Convert sets back to slices + for disk := range addedDiskSet { + addedAsmDisks = append(addedAsmDisks, disk) + } + for disk := range removedDiskSet { + removedAsmDisks = append(removedAsmDisks, disk) + } + + // Return the final list of added and removed disks + return addedAsmDisks, removedAsmDisks +} + +// Function to check if a disk is part of a device list +func isDiskInDeviceList(disk string, deviceList string) bool { + devices := strings.Split(deviceList, ",") + for _, device := range devices { + if strings.TrimSpace(device) == disk { + return true + } + } + return false +} + +// ############################################################################# +// +// Manage Finalizer to cleanup before deletion of OracleRestart +// +// ############################################################################# + +// manageOracleRestartDeletion manages the deletion of the OracleRestart resource +func (r *OracleRestartReconciler) manageOracleRestartDeletion(req ctrl.Request, ctx context.Context, oracleRestart *oraclerestartdb.OracleRestart) error { + log := r.Log.WithValues("manageOracleRestartDeletion", req.NamespacedName) + + // Check if the OracleRestart instance is marked to be deleted + isOracleRestartMarkedToBeDeleted := oracleRestart.GetDeletionTimestamp() != nil + if isOracleRestartMarkedToBeDeleted { + if controllerutil.ContainsFinalizer(oracleRestart, oracleRestartFinalizer) { + // Run finalization logic + err := r.cleanupOracleRestart(req, oracleRestart) + if err != nil { + return err + } + + // Remove finalizer and update the resource + if err := r.patchFinalizer(ctx, oracleRestart, false); err != nil { + log.Error(err, "Failed to remove finalizer") + return err + } + log.Info("Successfully removed OracleRestart finalizer") + } + return errors.New("deletion pending") + } + + // Add finalizer for this CR if not present + if !controllerutil.ContainsFinalizer(oracleRestart, oracleRestartFinalizer) { + if err := r.patchFinalizer(ctx, oracleRestart, true); err != nil { + log.Error(err, "Failed to add finalizer") + return err + } + } + return nil +} + +// patchFinalizer updates the finalizer for the given resource +func (r *OracleRestartReconciler) patchFinalizer(ctx context.Context, cr *oraclerestartdb.OracleRestart, add bool) error { + var finalizers []string + if add { + finalizers = append(cr.GetFinalizers(), oracleRestartFinalizer) + } else { + for _, finalizer := range cr.GetFinalizers() { + if finalizer != oracleRestartFinalizer { + finalizers = append(finalizers, finalizer) + } + } + } + + // Prepare patch payload + patchData := map[string]interface{}{ + "metadata": map[string]interface{}{ + "finalizers": finalizers, + }, + } + patchBytes, err := json.Marshal(patchData) + if err != nil { + return err + } + + patch := client.RawPatch(types.MergePatchType, patchBytes) + return r.Client.Patch(ctx, cr, patch, &client.PatchOptions{ + FieldManager: "rac-database-finalizer-manager", + }) +} + +// ############################################################################# +// +// Finalization logic for OracleRestartFinalizer +// +// ############################################################################# +func (r *OracleRestartReconciler) cleanupOracleRestart(req ctrl.Request, + oracleRestart *oraclerestartdb.OracleRestart) error { + log := r.Log.WithValues("cleanupOracleRestart", req.NamespacedName) + // Cleanup steps that the operator needs to do before the CR can be deleted. + + sfSetFound := &appsv1.StatefulSet{} + + var err error + + oraRestartSpex := oracleRestart.Spec.InstDetails + sfSetFound, err = oraclerestartcommon.CheckSfset(oraRestartSpex.Name, oracleRestart, r.Client) + if err == nil { + log.Info("Deleting ORestart Statefulset " + sfSetFound.Name) + if err := r.Client.Delete(context.Background(), sfSetFound); err != nil { + return err + } + } + + cmName := oraRestartSpex.Name + oracleRestart.Name + "-cmap" + configMapFound, err := oraclerestartcommon.CheckConfigMap(oracleRestart, cmName, r.Client) + if err == nil { + log.Info("Deleting Oracle Restart Configmap " + configMapFound.Name) + if err := r.Client.Delete(context.Background(), configMapFound); err != nil { + return err + } + } + + if !utils.CheckStatusFlag(oraRestartSpex.IsKeepPVC) { + if err := oraclerestartcommon.DelRestartSwPvc(oracleRestart, oraRestartSpex, r.Client, r.Log); err != nil { + return err + } + } + + // // Deleting the DaemonSet + daemonSetName := "disk-check-daemonset" + daemonSet := &appsv1.DaemonSet{} + + // Attempt to get the DaemonSet + err = r.Client.Get(context.TODO(), types.NamespacedName{ + Name: daemonSetName, + Namespace: oracleRestart.Namespace, + }, daemonSet) + + if err != nil { + if apierrors.IsNotFound(err) { + r.Log.Info("DaemonSet not found, skipping deletion", "DaemonSet.Name", daemonSetName) + } else { + r.Log.Error(err, "Failed to get DaemonSet", "DaemonSet.Name", daemonSetName) + return err + } + } else { + // DaemonSet exists, attempt to delete it + // r.Log.Info("Deleting DaemonSet", "DaemonSet.Name", daemonSetName) + err = r.Client.Delete(context.TODO(), daemonSet) + if err != nil { + r.Log.Error(err, "Failed to delete DaemonSet", "DaemonSet.Name", daemonSetName) + return err + } + } + + if !utils.CheckStatusFlag(oraRestartSpex.IsKeepPVC) { + if oracleRestart.Spec.AsmStorageDetails != nil { + // Delete PVCs for each disk in DisksBySize + for pindex, diskBySize := range oracleRestart.Spec.AsmStorageDetails.DisksBySize { + for cindex, disk := range diskBySize.DiskNames { + err = oraclerestartcommon.DelORestartPVC(oracleRestart, pindex, cindex, disk, oracleRestart.Spec.AsmStorageDetails, r.Client, r.Log) + if err != nil { + return err + } + } + } + } + } + + if !utils.CheckStatusFlag(oraRestartSpex.IsKeepPVC) { + if oraclerestartcommon.IsStaticProvisioning(r.Client, oracleRestart) { + if oracleRestart.Spec.AsmStorageDetails != nil { + // Delete PVs for each disk in DisksBySize + for pindex, diskBySize := range oracleRestart.Spec.AsmStorageDetails.DisksBySize { + for cindex, disk := range diskBySize.DiskNames { + err = oraclerestartcommon.DelORestartPv(oracleRestart, pindex, cindex, disk, oracleRestart.Spec.AsmStorageDetails, r.Client, r.Log) + if err != nil { + return err + } + } + } + } + } + } + + svcTypes := []string{"local", "lbservice", "nodeport"} + for _, svcType := range svcTypes { + svcFound, err := oraclerestartcommon.CheckORestartSvc(oracleRestart, svcType, oraRestartSpex, "", r.Client) + if err == nil { + log.Info("Deleting ORestart Service " + svcFound.Name) + if err := r.Client.Delete(context.Background(), svcFound); err != nil { + return err + } + } + } + + log.Info("Successfully cleaned up OracleRestart") + return nil +} + +// ############################################################################# +// +// CLeanup RAC Instance +// +// ############################################################################# +func (r *OracleRestartReconciler) cleanupOracleRestartInstance(req ctrl.Request, ctx context.Context, oracleRestart *oraclerestartdb.OracleRestart) (int32, error) { + log := r.Log.WithValues("cleanupOracleRestartInstance", req.NamespacedName) + // Cleanup steps that the operator needs to do before the CR can be deleted. + + var i int32 + var err error + + OraRestartSpex := oracleRestart.Spec.InstDetails + if utils.CheckStatusFlag(OraRestartSpex.IsDelete) { + if len(oracleRestart.Status.OracleRestartNodes) > 0 { + for _, oraRacSatus := range oracleRestart.Status.OracleRestartNodes { + if strings.ToUpper(oraRacSatus.Name) == (strings.ToUpper(OraRestartSpex.Name) + "-0") { + if !utils.CheckStatusFlag(oraRacSatus.NodeDetails.IsDelete) { + oraRacSatus.NodeDetails.IsDelete = "true" + log.Info("Setting Oracle Restart status instance " + oraRacSatus.Name + " delete flag true") + err = r.deleteOracleRestartInst(OraRestartSpex, req, ctx, oracleRestart) + oraRacSatus.NodeDetails.IsDelete = "false" + if err != nil { + log.Info("Error occurred Oracle Restart instance " + oraRacSatus.Name + " deletion") + return 0, err // return value should be adjusted according to the function signature + } + } + } + } + } + } + + return i, nil +} + +func (r *OracleRestartReconciler) deleteOracleRestartInst(OraRestartSpex oraclerestartdb.OracleRestartInstDetailSpec, req ctrl.Request, ctx context.Context, oracleRestart *oraclerestartdb.OracleRestart) error { + log := r.Log.WithValues("cleanupOracleRestartInstance", req.NamespacedName) + // delete steps that the operator needs to do before the CR can be deleted. + + //var i int32 + var err error + var cmName string + + //nodeCount, err = oraclerestartcommon.GetHealthyNodeCounts(oracleRestart) + //healthyNode, err = oraclerestartcommon.GetHealthyNode(oracleRestart) + if err != nil { + return fmt.Errorf("no healthy node found in the cluster to perform delete node operator. manual intervention required") + } + + // var endp string = "" + // _, endp, err = oraclerestartcommon.GetDBLsnrEndPoints(oracleRestart) + // if err != nil { + // return fmt.Errorf("endpoint generation error in delete block") + // } + + sfSetFound := &appsv1.StatefulSet{} + svcFound := &corev1.Service{} + configMapFound := &corev1.ConfigMap{} + + sfSetFound, err = oraclerestartcommon.CheckSfset(OraRestartSpex.Name, oracleRestart, r.Client) + if err == nil { + // See if StatefulSets already exists and create if it doesn't + if strings.ToLower(OraRestartSpex.IsDelete) != "force" { + err = oraclerestartcommon.DelOracleRestartNode(sfSetFound.Name+"-0", oracleRestart, r.kubeClient, r.kubeConfig, r.Log) + if err != nil { + return err + } + } + err = r.Client.Delete(context.Background(), sfSetFound) + if err != nil { + return err + } + } + if !utils.CheckStatusFlag(OraRestartSpex.IsKeepPVC) { + err = oraclerestartcommon.DelRestartSwPvc(oracleRestart, OraRestartSpex, r.Client, r.Log) + if err != nil { + return err + } + } + + //cmName = oracleRestart.Spec.InstDetails[i].Name + oracleRestart.Name + "-cmap" + cmName = OraRestartSpex.Name + oracleRestart.Name + "-cmap" + configMapFound, err = oraclerestartcommon.CheckConfigMap(oracleRestart, cmName, r.Client) + if err == nil { + + err = r.Client.Delete(context.Background(), configMapFound) + if err != nil { + return err + } + } + + svcFound, err = oraclerestartcommon.CheckORestartSvc(oracleRestart, "local", OraRestartSpex, "", r.Client) + if err == nil { + // See if service already exists and create if it doesn't + err = r.Client.Delete(context.Background(), svcFound) + if err != nil { + return err + } + } + + //NodePort Service + if len(oracleRestart.Spec.NodePortSvc.PortMappings) != 0 { + svcFound, err = oraclerestartcommon.CheckORestartSvc(oracleRestart, "nodeport", OraRestartSpex, "", r.Client) + if err == nil { + // See if service already exists and create if it doesn't + err = r.Client.Delete(context.Background(), svcFound) + if err != nil { + return err + } + } + } + + //NodePort Service + if len(oracleRestart.Spec.LbService.PortMappings) != 0 { + svcFound, err = oraclerestartcommon.CheckORestartSvc(oracleRestart, "lbservice", OraRestartSpex, "", r.Client) + if err == nil { + // See if service already exists and create if it doesn't + err = r.Client.Delete(context.Background(), svcFound) + if err != nil { + return err + } + } + } + + log.Info("Successfully cleaned up OracleRestartInstance") + return nil +} + +func IsStaticProvisioningUsed(ctx context.Context, c client.Client, storageClassName string) bool { + if storageClassName != "" { + return false + } + + var scList storagev1.StorageClassList + err := c.List(ctx, &scList) + if err != nil { + // Can't determine SCs — safest to assume static provisioning + return true + } + + for _, sc := range scList.Items { + if sc.Annotations["storageclass.kubernetes.io/is-default-class"] == "true" || + sc.Annotations["storageclass.beta.kubernetes.io/is-default-class"] == "true" { + return false + } + } + + // No default SC found → static provisioning is expected + return true +} + +// SetupWithManager sets up the controller with the Manager. +func (r *OracleRestartReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&oraclerestartdb.OracleRestart{}). + Owns(&corev1.ConfigMap{}). + Owns(&corev1.Secret{}). + Owns(&corev1.Service{}). + Owns(&corev1.Pod{}). + Owns(&appsv1.StatefulSet{}). + WithOptions(controller.Options{MaxConcurrentReconciles: 100}). //ReconcileHandler is never invoked concurrently with the same object. + Complete(r) +} + +const oldSpecAnnotation = "OracleRestarts.database.oracle.com/old-spec" + +// GetOldSpec retrieves the old spec from annotations. +// Returns nil, nil if the annotation does not exist. +func (r *OracleRestartReconciler) GetOldSpec(oracleRestart *oraclerestartdb.OracleRestart) (*oraclerestartdb.OracleRestartSpec, error) { + // Check if annotations exist + annotations := oracleRestart.GetAnnotations() + if annotations == nil { + r.Log.Info("No annotations found on OracleRestart") + return nil, nil + } + + // Retrieve the specific annotation + val, ok := annotations[oldSpecAnnotation] + if !ok { + r.Log.Info("Old spec annotation not found") + return nil, nil + } + + // Unmarshal the old spec JSON string + specBytes := []byte(val) + var oldSpec oraclerestartdb.OracleRestartSpec + if err := json.Unmarshal(specBytes, &oldSpec); err != nil { + r.Log.Error(err, "Failed to unmarshal old spec from annotation") + return nil, fmt.Errorf("failed to unmarshal old spec from annotation: %w", err) + } + + // r.Log.Info("Successfully retrieved old spec from annotation", "spec", oldSpec) + return &oldSpec, nil +} + +// SetCurrentSpec stores the current spec as an annotation with retry logic, updating only if the annotation value has changed. +func (r *OracleRestartReconciler) SetCurrentSpec(ctx context.Context, oracleRestart *oraclerestartdb.OracleRestart, req ctrl.Request) error { + // Marshal the current spec into JSON + currentSpecData, err := json.Marshal(oracleRestart.Spec) + if err != nil { + return fmt.Errorf("failed to marshal current spec: %w", err) + } + currentSpecStr := string(currentSpecData) + + // Ensure Annotations map is initialized + if oracleRestart.Annotations == nil { + oracleRestart.Annotations = make(map[string]string) + } + + // Check if the annotation value has changed + existingSpecStr, exists := oracleRestart.Annotations[oldSpecAnnotation] + if exists && existingSpecStr == currentSpecStr { + r.Log.Info("Annotations are already up to date. Skipping update.") + return nil // No update needed + } + + // Update the annotation with the new spec + oracleRestart.Annotations[oldSpecAnnotation] = currentSpecStr + + // // Create a patch to update only the annotations + patchData, err := json.Marshal(map[string]interface{}{ + "metadata": map[string]interface{}{ + "annotations": oracleRestart.Annotations, + }, + }) + if err != nil { + r.Log.Error(err, "Failed to marshal annotation patch data") + return err + } + + // Apply the patch + err = r.Patch(ctx, oracleRestart, client.RawPatch(types.MergePatchType, patchData)) + if err != nil { + if apierrors.IsConflict(err) { + r.Log.Info("Conflict detected while updating annotations, retrying...") + return fmt.Errorf("conflict occurred while updating annotations: %w", err) + } + r.Log.Error(err, "Failed to update Oracle Restart instance annotations") + return fmt.Errorf("failed to update Oracle Restart instance annotations: %w", err) + } + + r.Log.Info("Oracle Restart Object annotations updated with current spec annotation") + return nil +} + +func (r *OracleRestartReconciler) updateONS(ctx context.Context, podList *corev1.PodList, instance *oraclerestartdb.OracleRestart, onsState string) error { + reqLogger := r.Log.WithValues("Instance.Namespace", instance.Namespace, "Instance.Name", instance.Name) + + for _, pod := range podList.Items { + podName := pod.Name + + cmd := fmt.Sprintf("python3 /opt/scripts/startup/scripts/main.py --ons=%s", onsState) + // reqLogger.Info("Executing command to update ONS", "Pod.Name", podName, "Command", cmd) + + stdout, stderr, err := oraclerestartcommon.ExecCommand( + podName, + []string{"bash", "-c", cmd}, + r.kubeClient, + r.kubeConfig, + instance, + reqLogger, + ) + if err != nil { + instance.Spec.IsFailed = true + reqLogger.Error(err, "Failed to execute command", "Pod.Name", podName, "Command", cmd, "Stdout", stdout, "Stderr", stderr) + return err + } + r.Log.Info("ONS Running successfully", "podName", podName) + } + + return nil +} +func (r *OracleRestartReconciler) expandStorageClassSWVolume(ctx context.Context, instance *oraclerestartdb.OracleRestart, oldSpec *oraclerestartdb.OracleRestartSpec) error { + //reqLogger := r.Log.WithValues("Instance.Namespace", instance.Namespace, "Instance.Name", instance.Name) + + if oldSpec != nil { + // fmt.Printf("Received OldSpec", oldSpec.InstDetails.SwLocStorageSizeInGb) + if instance.Spec.InstDetails.SwLocStorageSizeInGb > oldSpec.InstDetails.SwLocStorageSizeInGb { + fmt.Printf("Inside OldSpec and newSpec Change", oldSpec.InstDetails.SwLocStorageSizeInGb, instance.Spec.InstDetails.SwLocStorageSizeInGb) + storageClass := &storagev1.StorageClass{} + pvc := &corev1.PersistentVolumeClaim{} + + if instance.Spec.SwStorageClass != "" { + + err := r.Get(ctx, types.NamespacedName{Name: instance.Spec.SwStorageClass}, storageClass) + if err != nil { + return fmt.Errorf("error while fetching the storage class") + } + + pvcName := oraclerestartcommon.GetSwPvcName(instance.Name, instance) + err = r.Get(ctx, types.NamespacedName{ + Name: pvcName, + Namespace: instance.Namespace, + }, pvc) + + // fmt.Printf("PvcName set to ", pvc.Name) + + if err == nil { + if storageClass.AllowVolumeExpansion == nil || !*storageClass.AllowVolumeExpansion { + r.Recorder.Eventf(instance, corev1.EventTypeWarning, "PVC not resizable", "The storage class doesn't support volume expansion") + return fmt.Errorf("the storage class %s doesn't support volume expansion", instance.Spec.SwStorageClass) + } + + newPVCSize := resource.MustParse(strconv.Itoa(instance.Spec.InstDetails.SwLocStorageSizeInGb) + "Gi") + newPVCSizeAdd := &newPVCSize + + fmt.Printf("New PvcSize set to ", newPVCSizeAdd) + if newPVCSizeAdd.Cmp(pvc.Spec.Resources.Requests["storage"]) < 0 { + return fmt.Errorf("Resizing PVC to lower size volume not allowed") + } + + pvc.Spec.Resources.Requests["storage"] = resource.MustParse(strconv.Itoa(instance.Spec.InstDetails.SwLocStorageSizeInGb) + "Gi") + fmt.Printf("Updating PVC", "pvc", pvc.Name, "volume", pvc.Spec.VolumeName) + err = r.Update(ctx, pvc) + if err != nil { + return fmt.Errorf("error while updating the PVCs") + } + + } + } + } + } + return nil +} diff --git a/controllers/database/ordssrvs_controller.go b/controllers/database/ordssrvs_controller.go index 14c7f46e..db831805 100644 --- a/controllers/database/ordssrvs_controller.go +++ b/controllers/database/ordssrvs_controller.go @@ -40,6 +40,7 @@ package controllers import ( "context" + "crypto/rand" "crypto/rsa" "crypto/sha256" "crypto/x509" @@ -59,6 +60,7 @@ import ( "k8s.io/apimachinery/pkg/api/equality" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" @@ -69,23 +71,29 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log" // dbapi "example.com/oracle-ords-operator/api/v1" + "github.com/go-logr/logr" dbapi "github.com/oracle/oracle-database-operator/apis/database/v4" + dbcommons "github.com/oracle/oracle-database-operator/commons/database" + "github.com/oracle/oracle-database-operator/commons/k8s" ) // Definitions of Standards const ( - ordsSABase = "/opt/oracle/sa" - serviceHTTPPortName = "svc-http-port" - serviceHTTPSPortName = "svc-https-port" - serviceMongoPortName = "svc-mongo-port" - targetHTTPPortName = "pod-http-port" - targetHTTPSPortName = "pod-https-port" - targetMongoPortName = "pod-mongo-port" - globalConfigMapName = "settings-global" - poolConfigPreName = "settings-" // Append PoolName - controllerLabelKey = "oracle.com/ords-operator-filter" - controllerLabelVal = "oracle-database-operator" - specHashLabel = "oracle.com/ords-operator-spec-hash" + ordsSABase = "/opt/oracle/sa" + serviceHTTPPortName = "svc-http-port" + serviceHTTPSPortName = "svc-https-port" + serviceMongoPortName = "svc-mongo-port" + targetHTTPPortName = "pod-http-port" + targetHTTPSPortName = "pod-https-port" + targetMongoPortName = "pod-mongo-port" + globalConfigMapName = "settings-global" + poolConfigPreName = "settings-" // Append PoolName + controllerLabelKey = "oracle.com/ords-operator-filter" + controllerLabelVal = "oracle-database-operator" + specHashLabel = "oracle.com/ords-operator-spec-hash" + APEXInstallationPV = "apex-installation-pv" + APEXInstallationPVC = "apex-installation-pvc" + APEXInstallationMount = "/opt/oracle/apex" ) // Definitions to manage status conditions @@ -96,6 +104,12 @@ const ( typeUnsyncedORDS = "Unsynced" ) +// Definitions used in the controller +var ordsInitScript string = "" +var ordsStartScript string = "" +var ordsGlobalConfig string = "" +var APEXInstallationExternal string = "false" + // Trigger a restart of Pods on Config Changes var RestartPods bool = false @@ -104,6 +118,7 @@ type OrdsSrvsReconciler struct { client.Client Scheme *runtime.Scheme Recorder record.EventRecorder + Log logr.Logger } //+kubebuilder:rbac:groups=database.oracle.com,resources=ordssrvs,verbs=get;list;watch;create;update;patch;delete @@ -116,6 +131,8 @@ type OrdsSrvsReconciler struct { //+kubebuilder:rbac:groups=core,resources=secrets/status,verbs=get //+kubebuilder:rbac:groups=core,resources=services,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=core,resources=services/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=core,resources=persistentvolumeclaim,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=core,resources=persistentvolumeclaim/status,verbs=get;update;patch //+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=core,resources=deployments/status,verbs=get;update;patch //+kubebuilder:rbac:groups=apps,resources=daemonsets,verbs=get;list;watch;create;update;patch;delete @@ -133,63 +150,92 @@ func (r *OrdsSrvsReconciler) SetupWithManager(mgr ctrl.Manager) error { Owns(&appsv1.StatefulSet{}). Owns(&appsv1.DaemonSet{}). Owns(&corev1.Service{}). + Owns(&corev1.PersistentVolumeClaim{}). Complete(r) } func (r *OrdsSrvsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - logr := log.FromContext(ctx) - ords := &dbapi.OrdsSrvs{} + logger := log.FromContext(ctx) + ordssrvs := &dbapi.OrdsSrvs{} // Check if resource exists or was deleted - if err := r.Get(ctx, req.NamespacedName, ords); err != nil { + if err := r.Get(ctx, req.NamespacedName, ordssrvs); err != nil { if apierrors.IsNotFound(err) { - logr.Info("Resource deleted") + logger.Info("Resource deleted") return ctrl.Result{}, nil } - logr.Error(err, "Error retrieving resource") + logger.Error(err, "Error retrieving resource") return ctrl.Result{Requeue: true, RequeueAfter: time.Minute}, err } + ordsInitScript = ordssrvs.Name + "-init-script" + ordsStartScript = ordssrvs.Name + "-start-script" + ordsGlobalConfig = ordssrvs.Name + "-" + globalConfigMapName + + // APEXInstallationExternal + // true: persistence volume + // false: no persistence volume + if ordssrvs.Spec.GlobalSettings.APEXInstallationPersistence.VolumeName != "" || ordssrvs.Spec.GlobalSettings.APEXInstallationPersistence.StorageClass != "" { + APEXInstallationExternal = "true" + } + logger.Info("Setting env external_apex to " + APEXInstallationExternal) + // Set the status as Unknown when no status are available - if ords.Status.Conditions == nil || len(ords.Status.Conditions) == 0 { + if len(ordssrvs.Status.Conditions) == 0 { condition := metav1.Condition{Type: typeUnsyncedORDS, Status: metav1.ConditionUnknown, Reason: "Reconciling", Message: "Starting reconciliation"} - if err := r.SetStatus(ctx, req, ords, condition); err != nil { + if err := r.SetStatus(ctx, req, ordssrvs, condition); err != nil { return ctrl.Result{}, err } } // ConfigMap - Init Script - if err := r.ConfigMapReconcile(ctx, ords, ords.Name+"-"+"init-script", 0); err != nil { - logr.Error(err, "Error in ConfigMapReconcile (init-script)") + if err := r.ConfigMapReconcile(ctx, ordssrvs, ordsInitScript, 0); err != nil { + logger.Error(err, "Error in ConfigMapReconcile (init-script)") return ctrl.Result{}, err } + // ConfigMap - Start Script + if err := r.ConfigMapReconcile(ctx, ordssrvs, ordsStartScript, 0); err != nil { + logger.Error(err, "Error in ConfigMapReconcile (start-script)") + return ctrl.Result{}, err + } + + if APEXInstallationExternal == "true" { + // ApexInstallation PVC + if err := r.ApexInstallationPVCReconcile(ctx, ordssrvs); err != nil { + logger.Error(err, "Error in ApexInstallation PVC reconcile") + return ctrl.Result{}, err + } + } else { + logger.Info("ApexInstallation PVC not defined, no external APEX installation files") + } + // ConfigMap - Global Settings - if err := r.ConfigMapReconcile(ctx, ords, ords.Name+"-"+globalConfigMapName, 0); err != nil { - logr.Error(err, "Error in ConfigMapReconcile (Global)") + if err := r.ConfigMapReconcile(ctx, ordssrvs, ordsGlobalConfig, 0); err != nil { + logger.Error(err, "Error in ConfigMapReconcile (Global)") return ctrl.Result{}, err } // ConfigMap - Pool Settings definedPools := make(map[string]bool) - for i := 0; i < len(ords.Spec.PoolSettings); i++ { - poolName := strings.ToLower(ords.Spec.PoolSettings[i].PoolName) - poolConfigMapName := ords.Name + "-" + poolConfigPreName + poolName + for i := 0; i < len(ordssrvs.Spec.PoolSettings); i++ { + poolName := strings.ToLower(ordssrvs.Spec.PoolSettings[i].PoolName) + poolConfigMapName := ordssrvs.Name + "-" + poolConfigPreName + poolName if definedPools[poolConfigMapName] { return ctrl.Result{}, errors.New("poolName: " + poolName + " is not unique") } definedPools[poolConfigMapName] = true - if err := r.ConfigMapReconcile(ctx, ords, poolConfigMapName, i); err != nil { - logr.Error(err, "Error in ConfigMapReconcile (Pools)") + if err := r.ConfigMapReconcile(ctx, ordssrvs, poolConfigMapName, i); err != nil { + logger.Error(err, "Error in ConfigMapReconcile (Pools)") return ctrl.Result{}, err } } - if err := r.ConfigMapDelete(ctx, req, ords, definedPools); err != nil { - logr.Error(err, "Error in ConfigMapDelete (Pools)") + if err := r.ConfigMapDelete(ctx, req, ordssrvs, definedPools); err != nil { + logger.Error(err, "Error in ConfigMapDelete (Pools)") return ctrl.Result{}, err } - if err := r.Get(ctx, req.NamespacedName, ords); err != nil { - logr.Error(err, "Failed to re-fetch") + if err := r.Get(ctx, req.NamespacedName, ordssrvs); err != nil { + logger.Error(err, "Failed to re-fetch") return ctrl.Result{}, err } @@ -204,40 +250,40 @@ func (r *OrdsSrvsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c // Set the Type as Unsynced when a pod restart is required if RestartPods { condition := metav1.Condition{Type: typeUnsyncedORDS, Status: metav1.ConditionTrue, Reason: "Unsynced", Message: "Configurations have changed"} - if err := r.SetStatus(ctx, req, ords, condition); err != nil { + if err := r.SetStatus(ctx, req, ordssrvs, condition); err != nil { return ctrl.Result{}, err } } // Workloads - if err := r.WorkloadReconcile(ctx, req, ords, ords.Spec.WorkloadType); err != nil { - logr.Error(err, "Error in WorkloadReconcile") + if err := r.WorkloadReconcile(ctx, req, ordssrvs, ordssrvs.Spec.WorkloadType); err != nil { + logger.Error(err, "Error in WorkloadReconcile") return ctrl.Result{}, err } - if err := r.WorkloadDelete(ctx, req, ords, ords.Spec.WorkloadType); err != nil { - logr.Error(err, "Error in WorkloadDelete") + if err := r.WorkloadDelete(ctx, req, ordssrvs, ordssrvs.Spec.WorkloadType); err != nil { + logger.Error(err, "Error in WorkloadDelete") return ctrl.Result{}, err } - if err := r.Get(ctx, req.NamespacedName, ords); err != nil { - logr.Error(err, "Failed to re-fetch") + if err := r.Get(ctx, req.NamespacedName, ordssrvs); err != nil { + logger.Error(err, "Failed to re-fetch") return ctrl.Result{}, err } // Service - if err := r.ServiceReconcile(ctx, ords); err != nil { - logr.Error(err, "Error in ServiceReconcile") + if err := r.ServiceReconcile(ctx, ordssrvs); err != nil { + logger.Error(err, "Error in ServiceReconcile") return ctrl.Result{}, err } // Set the Type as Available when a pod restart is not required if !RestartPods { condition := metav1.Condition{Type: typeAvailableORDS, Status: metav1.ConditionTrue, Reason: "Available", Message: "Workload in Sync"} - if err := r.SetStatus(ctx, req, ords, condition); err != nil { + if err := r.SetStatus(ctx, req, ordssrvs, condition); err != nil { return ctrl.Result{}, err } } - if err := r.Get(ctx, req.NamespacedName, ords); err != nil { - logr.Error(err, "Failed to re-fetch") + if err := r.Get(ctx, req.NamespacedName, ordssrvs); err != nil { + logger.Error(err, "Failed to re-fetch") return ctrl.Result{}, err } @@ -284,12 +330,13 @@ func (r *OrdsSrvsReconciler) SetStatus(ctx context.Context, req ctrl.Request, or } var workloadStatus string - if readyWorkload == 0 { + switch readyWorkload { + case 0: workloadStatus = "Preparing" - } else if readyWorkload == desiredWorkload { + case desiredWorkload: workloadStatus = "Healthy" ords.Status.OrdsInstalled = true - } else { + default: workloadStatus = "Progressing" } @@ -313,25 +360,58 @@ func (r *OrdsSrvsReconciler) SetStatus(ctx context.Context, req ctrl.Request, or return nil } +/************************************************ + * APEX Installation PVC Reconcile + *************************************************/ +func (r *OrdsSrvsReconciler) ApexInstallationPVCReconcile(ctx context.Context, ordssrvs *dbapi.OrdsSrvs) (err error) { + logr := log.FromContext(ctx).WithName("ApexInstallationPVCReconcile") + + if ordssrvs.Spec.GlobalSettings.APEXInstallationPersistence.Size == "" { + msg := "APEX Installation PVC Size not defined" + err = r.Create(ctx, ordssrvs) + logr.Error(err, msg) + return err + } + + pvc := &corev1.PersistentVolumeClaim{} + err = r.Get(ctx, types.NamespacedName{Name: APEXInstallationPVC, Namespace: ordssrvs.Namespace}, pvc) + if err == nil { + logr.Info("Found APEX Installation PVC : " + APEXInstallationPVC) + return nil + } + + volumeName := ordssrvs.Spec.GlobalSettings.APEXInstallationPersistence.VolumeName + pvc = r.APEXInstallationPVCDefine(ctx, ordssrvs) + + message := fmt.Sprintf("APEX Installation PVC : %s for PV : %s", APEXInstallationPVC, volumeName) + logr.Info("Creating " + message) + err = r.Create(ctx, pvc) + if err != nil { + logr.Error(err, "Failed to create "+message) + } + + return err +} + /************************************************ * ConfigMaps *************************************************/ -func (r *OrdsSrvsReconciler) ConfigMapReconcile(ctx context.Context, ords *dbapi.OrdsSrvs, configMapName string, poolIndex int) (err error) { +func (r *OrdsSrvsReconciler) ConfigMapReconcile(ctx context.Context, ordssrvs *dbapi.OrdsSrvs, configMapName string, poolIndex int) (err error) { logr := log.FromContext(ctx).WithName("ConfigMapReconcile") - desiredConfigMap := r.ConfigMapDefine(ctx, ords, configMapName, poolIndex) + desiredConfigMap := r.ConfigMapDefine(ctx, ordssrvs, configMapName, poolIndex) // Create if ConfigMap not found definedConfigMap := &corev1.ConfigMap{} - if err = r.Get(ctx, types.NamespacedName{Name: configMapName, Namespace: ords.Namespace}, definedConfigMap); err != nil { + if err = r.Get(ctx, types.NamespacedName{Name: configMapName, Namespace: ordssrvs.Namespace}, definedConfigMap); err != nil { if apierrors.IsNotFound(err) { if err := r.Create(ctx, desiredConfigMap); err != nil { return err } logr.Info("Created: " + configMapName) RestartPods = true - r.Recorder.Eventf(ords, corev1.EventTypeNormal, "Create", "ConfigMap %s Created", configMapName) + r.Recorder.Eventf(ordssrvs, corev1.EventTypeNormal, "Create", "ConfigMap %s Created", configMapName) // Requery for comparison - if err := r.Get(ctx, types.NamespacedName{Name: configMapName, Namespace: ords.Namespace}, definedConfigMap); err != nil { + if err := r.Get(ctx, types.NamespacedName{Name: configMapName, Namespace: ordssrvs.Namespace}, definedConfigMap); err != nil { return err } } else { @@ -344,7 +424,7 @@ func (r *OrdsSrvsReconciler) ConfigMapReconcile(ctx context.Context, ords *dbapi } logr.Info("Updated: " + configMapName) RestartPods = true - r.Recorder.Eventf(ords, corev1.EventTypeNormal, "Update", "ConfigMap %s Updated", configMapName) + r.Recorder.Eventf(ordssrvs, corev1.EventTypeNormal, "Update", "ConfigMap %s Updated", configMapName) } return nil } @@ -387,22 +467,24 @@ func (r *OrdsSrvsReconciler) ConfigMapReconcile(ctx context.Context, ords *dbapi /************************************************ * Workloads *************************************************/ -func (r *OrdsSrvsReconciler) WorkloadReconcile(ctx context.Context, req ctrl.Request, ords *dbapi.OrdsSrvs, kind string) (err error) { +func (r *OrdsSrvsReconciler) WorkloadReconcile(ctx context.Context, req ctrl.Request, ordssrvs *dbapi.OrdsSrvs, kind string) (err error) { logr := log.FromContext(ctx).WithName("WorkloadReconcile") - objectMeta := objectMetaDefine(ords, ords.Name) - selector := selectorDefine(ords) - template := r.podTemplateSpecDefine(ords, ctx, req) + objectMeta := objectMetaDefine(ordssrvs, ordssrvs.Name) + selector := selectorDefine(ordssrvs) + template := r.podTemplateSpecDefine(ordssrvs, ctx, req) var desiredWorkload client.Object var desiredSpecHash string var definedSpecHash string + var ProgressDeadlineSeconds int32 = 3600 + switch kind { case "StatefulSet": desiredWorkload = &appsv1.StatefulSet{ ObjectMeta: objectMeta, Spec: appsv1.StatefulSetSpec{ - Replicas: &ords.Spec.Replicas, + Replicas: &ordssrvs.Spec.Replicas, Selector: &selector, Template: template, }, @@ -423,37 +505,38 @@ func (r *OrdsSrvsReconciler) WorkloadReconcile(ctx context.Context, req ctrl.Req desiredWorkload = &appsv1.Deployment{ ObjectMeta: objectMeta, Spec: appsv1.DeploymentSpec{ - Replicas: &ords.Spec.Replicas, - Selector: &selector, - Template: template, + Replicas: &ordssrvs.Spec.Replicas, + Selector: &selector, + Template: template, + ProgressDeadlineSeconds: &ProgressDeadlineSeconds, }, } desiredSpecHash = generateSpecHash(desiredWorkload.(*appsv1.Deployment).Spec) desiredWorkload.(*appsv1.Deployment).ObjectMeta.Labels[specHashLabel] = desiredSpecHash } - if err := ctrl.SetControllerReference(ords, desiredWorkload, r.Scheme); err != nil { + if err := ctrl.SetControllerReference(ordssrvs, desiredWorkload, r.Scheme); err != nil { return err } definedWorkload := reflect.New(reflect.TypeOf(desiredWorkload).Elem()).Interface().(client.Object) - if err = r.Get(ctx, types.NamespacedName{Name: ords.Name, Namespace: ords.Namespace}, definedWorkload); err != nil { + if err = r.Get(ctx, types.NamespacedName{Name: ordssrvs.Name, Namespace: ordssrvs.Namespace}, definedWorkload); err != nil { if apierrors.IsNotFound(err) { if err := r.Create(ctx, desiredWorkload); err != nil { condition := metav1.Condition{ Type: typeAvailableORDS, Status: metav1.ConditionFalse, Reason: "Reconciling", - Message: fmt.Sprintf("Failed to create %s for the custom resource (%s): (%s)", kind, ords.Name, err), + Message: fmt.Sprintf("Failed to create %s for the custom resource (%s): (%s)", kind, ordssrvs.Name, err), } - if statusErr := r.SetStatus(ctx, req, ords, condition); statusErr != nil { + if statusErr := r.SetStatus(ctx, req, ordssrvs, condition); statusErr != nil { return statusErr } return err } logr.Info("Created: " + kind) RestartPods = false - r.Recorder.Eventf(ords, corev1.EventTypeNormal, "Create", "Created %s", kind) + r.Recorder.Eventf(ordssrvs, corev1.EventTypeNormal, "Create", "Created %s", kind) return nil } else { @@ -477,10 +560,10 @@ func (r *OrdsSrvsReconciler) WorkloadReconcile(ctx context.Context, req ctrl.Req return err } RestartPods = true - r.Recorder.Eventf(ords, corev1.EventTypeNormal, "Update", "Updated %s", kind) + r.Recorder.Eventf(ordssrvs, corev1.EventTypeNormal, "Update", "Updated %s", kind) } - if RestartPods && ords.Spec.ForceRestart { + if RestartPods && ordssrvs.Spec.ForceRestart { logr.Info("Cycling: " + kind) labelsField := reflect.ValueOf(desiredWorkload).Elem().FieldByName("Spec").FieldByName("Template").FieldByName("ObjectMeta").FieldByName("Labels") if labelsField.IsValid() { @@ -490,7 +573,7 @@ func (r *OrdsSrvsReconciler) WorkloadReconcile(ctx context.Context, req ctrl.Req if err := r.Update(ctx, desiredWorkload); err != nil { return err } - r.Recorder.Eventf(ords, corev1.EventTypeNormal, "Restart", "Restarted %s", kind) + r.Recorder.Eventf(ordssrvs, corev1.EventTypeNormal, "Restart", "Restarted %s", kind) RestartPods = false } } @@ -588,9 +671,9 @@ func selectorDefine(ords *dbapi.OrdsSrvs) metav1.LabelSelector { } } -func (r *OrdsSrvsReconciler) podTemplateSpecDefine(ords *dbapi.OrdsSrvs, ctx context.Context, req ctrl.Request) corev1.PodTemplateSpec { +func (r *OrdsSrvsReconciler) podTemplateSpecDefine(ords *dbapi.OrdsSrvs, ctx context.Context, _ ctrl.Request) corev1.PodTemplateSpec { labels := getLabels(ords.Name) - specVolumes, specVolumeMounts := VolumesDefine(ords) + specVolumes, specVolumeMounts := VolumesDefine(ctx, ords) envPorts := []corev1.ContainerPort{ { @@ -611,27 +694,20 @@ func (r *OrdsSrvsReconciler) podTemplateSpecDefine(ords *dbapi.OrdsSrvs, ctx con envPorts = append(envPorts, mongoPort) } - // Environment From Source podSpecTemplate := corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: labels, }, Spec: corev1.PodSpec{ - Volumes: specVolumes, - SecurityContext: &corev1.PodSecurityContext{ - RunAsNonRoot: &[]bool{true}[0], - FSGroup: &[]int64{54321}[0], - SeccompProfile: &corev1.SeccompProfile{ - Type: corev1.SeccompProfileTypeRuntimeDefault, - }, - }, + Volumes: specVolumes, + SecurityContext: podSecurityContextDefine(), InitContainers: []corev1.Container{{ Image: ords.Spec.Image, Name: ords.Name + "-init", ImagePullPolicy: corev1.PullIfNotPresent, SecurityContext: securityContextDefine(), - Command: []string{"sh", "-c", ordsSABase + "/bin/init_script.sh"}, + Command: []string{"/bin/bash", "-c", ordsSABase + "/init/ords_init.sh"}, Env: r.envDefine(ords, true, ctx), VolumeMounts: specVolumeMounts, }}, @@ -641,28 +717,44 @@ func (r *OrdsSrvsReconciler) podTemplateSpecDefine(ords *dbapi.OrdsSrvs, ctx con ImagePullPolicy: corev1.PullIfNotPresent, SecurityContext: securityContextDefine(), Ports: envPorts, - Command: []string{"/bin/bash", "-c", "ords --config $ORDS_CONFIG serve --apex-images /opt/oracle/apex/$APEX_VER/images --debug"}, + Command: []string{"/bin/bash", "-c", ordsSABase + "/start/ords_start.sh"}, Env: r.envDefine(ords, false, ctx), VolumeMounts: specVolumeMounts, - }}}, + }}, + ServiceAccountName: ords.Spec.ServiceAccountName, + }, } return podSpecTemplate } // Volumes -func VolumesDefine(ords *dbapi.OrdsSrvs) ([]corev1.Volume, []corev1.VolumeMount) { +func VolumesDefine(ctx context.Context, ords *dbapi.OrdsSrvs) ([]corev1.Volume, []corev1.VolumeMount) { // Initialize the slice to hold specifications var volumes []corev1.Volume var volumeMounts []corev1.VolumeMount // SecretHelper - secretHelperVolume := volumeBuild(ords.Name+"-"+"init-script", "ConfigMap", 0770) - secretHelperVolumeMount := volumeMountBuild(ords.Name+"-"+"init-script", ordsSABase+"/bin", true) - + secretHelperVolume := volumeBuild(ordsInitScript, "ConfigMap", 0770) + secretHelperVolumeMount := volumeMountBuild(ordsInitScript, ordsSABase+"/init", true) volumes = append(volumes, secretHelperVolume) volumeMounts = append(volumeMounts, secretHelperVolumeMount) + // start-script + startScriptVolume := volumeBuild(ordsStartScript, "ConfigMap", 0770) + startScriptVolumeMount := volumeMountBuild(ordsStartScript, ordsSABase+"/start", true) + volumes = append(volumes, startScriptVolume) + volumeMounts = append(volumeMounts, startScriptVolumeMount) + + if APEXInstallationExternal == "true" { + // volume for APEX installation, same optional folder as for ORDS image + apexInstallationVolume := APEXInstallationVolumeDefine(ctx, ords) + apexInstallationReadOnly := false + apexInstallationVolumeMount := volumeMountBuild(APEXInstallationPV, APEXInstallationMount, apexInstallationReadOnly) + volumes = append(volumes, apexInstallationVolume) + volumeMounts = append(volumeMounts, apexInstallationVolumeMount) + } + // Build volume specifications for globalSettings standaloneVolume := volumeBuild("standalone", "EmptyDir") standaloneVolumeMount := volumeMountBuild("standalone", ordsSABase+"/config/global/standalone/", false) @@ -673,8 +765,8 @@ func VolumesDefine(ords *dbapi.OrdsSrvs) ([]corev1.Volume, []corev1.VolumeMount) globalLogVolume := volumeBuild("sa-log-global", "EmptyDir") globalLogVolumeMount := volumeMountBuild("sa-log-global", ordsSABase+"/log/global/", false) - globalConfigVolume := volumeBuild(ords.Name+"-"+globalConfigMapName, "ConfigMap") - globalConfigVolumeMount := volumeMountBuild(ords.Name+"-"+globalConfigMapName, ordsSABase+"/config/global/", true) + globalConfigVolume := volumeBuild(ordsGlobalConfig, "ConfigMap") + globalConfigVolumeMount := volumeMountBuild(ordsGlobalConfig, ordsSABase+"/config/global/", true) globalDocRootVolume := volumeBuild("sa-doc-root", "EmptyDir") globalDocRootVolumeMount := volumeMountBuild("sa-doc-root", ordsSABase+"/config/global/doc_root/", false) @@ -826,81 +918,165 @@ func (r *OrdsSrvsReconciler) ServiceDefine(ctx context.Context, ords *dbapi.Ords return def } +func podSecurityContextDefine() *corev1.PodSecurityContext { + + return &corev1.PodSecurityContext{ + RunAsNonRoot: k8s.BoolPointer(true), + RunAsUser: k8s.Int64Pointer(dbcommons.ORACLE_UID), + RunAsGroup: k8s.Int64Pointer(dbcommons.ORACLE_GUID), + FSGroup: k8s.Int64Pointer(dbcommons.ORACLE_GUID), + } + +} + func securityContextDefine() *corev1.SecurityContext { + return &corev1.SecurityContext{ - RunAsNonRoot: &[]bool{true}[0], - RunAsUser: &[]int64{54321}[0], - AllowPrivilegeEscalation: &[]bool{false}[0], + RunAsNonRoot: k8s.BoolPointer(true), + RunAsUser: k8s.Int64Pointer(dbcommons.ORACLE_UID), + RunAsGroup: k8s.Int64Pointer(dbcommons.ORACLE_GUID), + AllowPrivilegeEscalation: k8s.BoolPointer(false), Capabilities: &corev1.Capabilities{ Drop: []corev1.Capability{ "ALL", }, }, } + } -func (r *OrdsSrvsReconciler) envDefine(ords *dbapi.OrdsSrvs, initContainer bool, ctx context.Context) []corev1.EnvVar { - envVarSecrets := []corev1.EnvVar{ - { - Name: "ORDS_CONFIG", - Value: ordsSABase + "/config", - }, - { - Name: "JAVA_TOOL_OPTIONS", - Value: "-Doracle.ml.version_check=false", - }, +func addEnvVar(envVars []corev1.EnvVar, name string, value string) []corev1.EnvVar { + newEnvVar := corev1.EnvVar{ + Name: name, + Value: value, } + return append(envVars, newEnvVar) +} + +// Sets environment variables in the containers +func (r *OrdsSrvsReconciler) envDefine(ordssrvs *dbapi.OrdsSrvs, initContainer bool, ctx context.Context) []corev1.EnvVar { + logger := log.FromContext(ctx).WithName("envDefine") + + envVars := []corev1.EnvVar{} + + // ORDS_CONFIG + ORDS_CONFIG := ordsSABase + "/config" + logger.Info("Setting ORDS_CONFIG to " + ORDS_CONFIG) + envVars = addEnvVar(envVars, "ORDS_CONFIG", ORDS_CONFIG) + + // avoid Java warning about JAVA_TOOL_OPTIONS + envVars = addEnvVar(envVars, "JAVA_TOOL_OPTIONS", "-Doracle.ml.version_check=false") // Limitation case for ADB/mTLS/OraOper edge - if len(ords.Spec.PoolSettings) == 1 { - poolName := strings.ToLower(ords.Spec.PoolSettings[0].PoolName) - tnsAdmin := corev1.EnvVar{ - Name: "TNS_ADMIN", - Value: ordsSABase + "/config/databases/" + poolName + "/network/admin/", - } - envVarSecrets = append(envVarSecrets, tnsAdmin) + if len(ordssrvs.Spec.PoolSettings) == 1 { + poolName := strings.ToLower(ordssrvs.Spec.PoolSettings[0].PoolName) + tnsAdmin := ordsSABase + "/config/databases/" + poolName + "/network/admin/" + envVars = addEnvVar(envVars, "TNS_ADMIN", tnsAdmin) } + + // passwords are set for the init container only if initContainer { - for i := 0; i < len(ords.Spec.PoolSettings); i++ { - poolName := strings.ReplaceAll(strings.ToLower(ords.Spec.PoolSettings[i].PoolName), "-", "_") + envVars = addEnvVar(envVars, "download_apex", strconv.FormatBool(ordssrvs.Spec.GlobalSettings.APEXDownload)) + envVars = addEnvVar(envVars, "download_url_apex", ordssrvs.Spec.GlobalSettings.APEXDownloadUrl) + envVars = addEnvVar(envVars, "external_apex", APEXInstallationExternal) + + for i := 0; i < len(ordssrvs.Spec.PoolSettings); i++ { + poolName := strings.ReplaceAll(strings.ToLower(ordssrvs.Spec.PoolSettings[i].PoolName), "-", "_") + + // dbpassword + secretName := poolName + "_dbpassword" + secretValue := r.CommonDecryptWithPrivKey3(ordssrvs, ordssrvs.Spec.PoolSettings[i].DBSecret.SecretName, ordssrvs.Spec.PoolSettings[i].DBSecret.PasswordKey, ctx) + envVars = addEnvVar(envVars, secretName, secretValue) + + // dbadminuserpassword + if ordssrvs.Spec.PoolSettings[i].DBAdminUserSecret.SecretName != "" { + envVars = addEnvVar(envVars, poolName+"_autoupgrade_ords", strconv.FormatBool(ordssrvs.Spec.PoolSettings[i].AutoUpgradeORDS)) + envVars = addEnvVar(envVars, poolName+"_autoupgrade_apex", strconv.FormatBool(ordssrvs.Spec.PoolSettings[i].AutoUpgradeAPEX)) + dbAdminUserPassword := r.CommonDecryptWithPrivKey3(ordssrvs, ordssrvs.Spec.PoolSettings[i].DBAdminUserSecret.SecretName, ordssrvs.Spec.PoolSettings[i].DBAdminUserSecret.PasswordKey, ctx) + envVars = addEnvVar(envVars, poolName+"_dbadminuserpassword", dbAdminUserPassword) + } - dbSecret := corev1.EnvVar{ - Name: poolName + "_dbsecret", - Value: r.CommonDecryptWithPrivKey3(ords, ords.Spec.PoolSettings[i].DBSecret.SecretName, ords.Spec.PoolSettings[i].DBSecret.PasswordKey, ctx), + // dbcdbadminuserpassword + if ordssrvs.Spec.PoolSettings[i].DBCDBAdminUserSecret.SecretName != "" { + dbCDBAdminUserPassword := r.CommonDecryptWithPrivKey3(ordssrvs, ordssrvs.Spec.PoolSettings[i].DBCDBAdminUserSecret.SecretName, ordssrvs.Spec.PoolSettings[i].DBCDBAdminUserSecret.PasswordKey, ctx) + envVars = addEnvVar(envVars, poolName+"_dbcdbadminuserpassword", dbCDBAdminUserPassword) } + } + } - envVarSecrets = append(envVarSecrets, dbSecret) + return envVars +} - if ords.Spec.PoolSettings[i].DBAdminUserSecret.SecretName != "" { - autoUpgradeORDSEnv := corev1.EnvVar{ - Name: poolName + "_autoupgrade_ords", - Value: strconv.FormatBool(ords.Spec.PoolSettings[i].AutoUpgradeORDS), - } - autoUpgradeAPEXEnv := corev1.EnvVar{ - Name: poolName + "_autoupgrade_apex", - Value: strconv.FormatBool(ords.Spec.PoolSettings[i].AutoUpgradeAPEX), - } +func APEXInstallationVolumeDefine(ctx context.Context, ordssrvs *dbapi.OrdsSrvs) corev1.Volume { + logger := log.FromContext(ctx).WithName("APEXInstallationVolumeDefine") - dbAdminUserSecret := corev1.EnvVar{ - Name: poolName + "_dbadminusersecret", - Value: r.CommonDecryptWithPrivKey3(ords, ords.Spec.PoolSettings[i].DBAdminUserSecret.SecretName, ords.Spec.PoolSettings[i].DBAdminUserSecret.PasswordKey, ctx), - } - envVarSecrets = append(envVarSecrets, dbAdminUserSecret, autoUpgradeORDSEnv, autoUpgradeAPEXEnv) - } + var vs corev1.VolumeSource + if APEXInstallationExternal == "false" { + vs = corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + } + logger.Info("APEX installation on empty dir") + } else { - if ords.Spec.PoolSettings[i].DBCDBAdminUserSecret.SecretName != "" { + vs = corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: APEXInstallationPVC, + ReadOnly: false, + }, + } + logger.Info("APEX installation PVC : " + APEXInstallationPVC) + } - dbCDBAdminUserSecret := corev1.EnvVar{ - Name: poolName + "_dbcdbadminusersecret", - Value: r.CommonDecryptWithPrivKey3(ords, ords.Spec.PoolSettings[i].DBCDBAdminUserSecret.SecretName, ords.Spec.PoolSettings[i].DBCDBAdminUserSecret.PasswordKey, ctx), - } + volume := corev1.Volume{ + Name: APEXInstallationPV, + VolumeSource: vs, + } + + return volume +} + +func (r *OrdsSrvsReconciler) APEXInstallationPVCDefine(ctx context.Context, ordssrvs *dbapi.OrdsSrvs) *corev1.PersistentVolumeClaim { + logger := log.FromContext(ctx).WithName("APEXInstallationPVCDefine") + + size := ordssrvs.Spec.GlobalSettings.APEXInstallationPersistence.Size + + if size == "" { - envVarSecrets = append(envVarSecrets, dbCDBAdminUserSecret) - } - } } - return envVarSecrets + volumeName := ordssrvs.Spec.GlobalSettings.APEXInstallationPersistence.VolumeName + storageClassName := ordssrvs.Spec.GlobalSettings.APEXInstallationPersistence.StorageClass + accessMode := ordssrvs.Spec.GlobalSettings.APEXInstallationPersistence.AccessMode + + message := fmt.Sprintf("Preparing PVC definition, volumeName %s, storageClass %s, size %s, accessMode %s", volumeName, storageClassName, size, accessMode) + logger.Info(message) + + // PVC Definition + pvc := &corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: APEXInstallationPVC, + Namespace: ordssrvs.Namespace, + Labels: getLabels(ordssrvs.Name), + }, + Spec: corev1.PersistentVolumeClaimSpec{ + AccessModes: []corev1.PersistentVolumeAccessMode{corev1.PersistentVolumeAccessMode(accessMode)}, + Resources: corev1.VolumeResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceStorage: resource.MustParse(size), + }, + }, + VolumeName: volumeName, + StorageClassName: &storageClassName, + }, + } + + // Set the ownerRef + if err := ctrl.SetControllerReference(ordssrvs, pvc, r.Scheme); err != nil { + return nil + } + + return pvc + } /************************************************* @@ -918,7 +1094,7 @@ func (r *OrdsSrvsReconciler) ConfigMapDelete(ctx context.Context, req ctrl.Reque } for _, configMap := range configMapList.Items { - if configMap.Name == ords.Name+"-"+globalConfigMapName || configMap.Name == ords.Name+"-init-script" { + if configMap.Name == ordsGlobalConfig || configMap.Name == ordsInitScript || configMap.Name == ordsStartScript { continue } if _, exists := definedPools[configMap.Name]; !exists { @@ -1069,7 +1245,7 @@ func CommonDecryptWithPrivKey(Key string, Buffer string) (string, error) { fmt.Printf("======================================\n") } - decryptedB, err := rsa.DecryptPKCS1v15(nil, pkcs8PrivateKey.(*rsa.PrivateKey), encString64) + decryptedB, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, pkcs8PrivateKey.(*rsa.PrivateKey), encString64, nil) if err != nil { fmt.Printf("Failed to decrypt string %s\n", err.Error()) return "", err diff --git a/controllers/database/ordssrvs_ordsconfig.go b/controllers/database/ordssrvs_ordsconfig.go index edb2e0f6..5cd29536 100644 --- a/controllers/database/ordssrvs_ordsconfig.go +++ b/controllers/database/ordssrvs_ordsconfig.go @@ -49,20 +49,38 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ctrl "sigs.k8s.io/controller-runtime" + ctrllog "sigs.k8s.io/controller-runtime/pkg/log" ) func (r *OrdsSrvsReconciler) ConfigMapDefine(ctx context.Context, ords *dbapi.OrdsSrvs, configMapName string, poolIndex int) *corev1.ConfigMap { + + log := ctrllog.FromContext(ctx).WithName("ConfigMapDefine") + var defData map[string]string - if configMapName == ords.Name+"-init-script" { + switch configMapName { + case ords.Name + "-init-script": // Read the file from controller's filesystem filePath := "/ords_init.sh" scriptData, err := os.ReadFile(filePath) if err != nil { + log.Error(err, "Error reading /ords_init.sh") return nil } + log.Info("adding ords_init.sh") defData = map[string]string{ - "init_script.sh": string(scriptData)} - } else if configMapName == ords.Name+"-"+globalConfigMapName { + "ords_init.sh": string(scriptData)} + case ords.Name + "-start-script": + // Read the file from controller's filesystem + filePath := "/ords_start.sh" + scriptData, err := os.ReadFile(filePath) + if err != nil { + log.Error(err, "Error reading /ords_start.sh") + return nil + } + log.Info("adding ords_start.sh") + defData = map[string]string{ + "ords_start.sh": string(scriptData)} + case ords.Name + "-" + globalConfigMapName: // GlobalConfigMap var defStandaloneAccessLog string if ords.Spec.GlobalSettings.EnableStandaloneAccessLog { @@ -78,7 +96,7 @@ func (r *OrdsSrvsReconciler) ConfigMapDefine(ctx context.Context, ords *dbapi.Or ` ` + ordsSABase + `/config/certficate/` + ords.Spec.GlobalSettings.CertSecret.CertificateKey + `` + "\n" } defData = map[string]string{ - "settings.xml": fmt.Sprintf(`` + "\n" + + "settings.xml": fmt.Sprint(`` + "\n" + `` + "\n" + `` + "\n" + conditionalEntry("cache.metadata.graphql.expireAfterAccess", ords.Spec.GlobalSettings.CacheMetadataGraphQLExpireAfterAccess) + @@ -139,7 +157,7 @@ func (r *OrdsSrvsReconciler) ConfigMapDefine(ctx context.Context, ords *dbapi.Or `java.util.logging.FileHandler.pattern = ` + ordsSABase + `/log/global/debug.log` + "\n" + `java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter`), } - } else { + default: // PoolConfigMap poolName := strings.ToLower(ords.Spec.PoolSettings[poolIndex].PoolName) var defDBNetworkPath string @@ -150,7 +168,7 @@ func (r *OrdsSrvsReconciler) ConfigMapDefine(ctx context.Context, ords *dbapi.Or defDBNetworkPath = ` ` + ordsSABase + `/config/databases/` + poolName + `/network/admin/` + "\n" } defData = map[string]string{ - "pool.xml": fmt.Sprintf(`` + "\n" + + "pool.xml": fmt.Sprint(`` + "\n" + `` + "\n" + `` + "\n" + ` ` + ords.Spec.PoolSettings[poolIndex].DBUsername + `` + "\n" + diff --git a/controllers/database/pdb_controller.go b/controllers/database/pdb_controller.go deleted file mode 100644 index a2ca0f85..00000000 --- a/controllers/database/pdb_controller.go +++ /dev/null @@ -1,1631 +0,0 @@ -/* -** Copyright (c) 2022 Oracle and/or its affiliates. -** -** The Universal Permissive License (UPL), Version 1.0 -** -** Subject to the condition set forth below, permission is hereby granted to any -** person obtaining a copy of this software, associated documentation and/or data -** (collectively the "Software"), free of charge and under any and all copyright -** rights in the Software, and any and all patent rights owned or freely -** licensable by each licensor hereunder covering either (i) the unmodified -** Software as contributed to or provided by such licensor, or (ii) the Larger -** Works (as defined below), to deal in both -** -** (a) the Software, and -** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if -** one is included with the Software (each a "Larger Work" to which the Software -** is contributed by such licensors), -** -** without restriction, including without limitation the rights to copy, create -** derivative works of, display, perform, and distribute the Software and make, -** use, sell, offer for sale, import, export, have made, and have sold the -** Software and the Larger Work(s), and to sublicense the foregoing rights on -** either these or other terms. -** -** This license is subject to the following condition: -** The above copyright notice and either this complete permission notice or at -** a minimum a reference to the UPL must be included in all copies or -** substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -** SOFTWARE. - */ - -package controllers - -import ( - "bytes" - "context" - "crypto/tls" - "crypto/x509" - "encoding/json" - - //"encoding/pem" - "errors" - "fmt" - "io/ioutil" - "net/http" - "regexp" - "strconv" - "strings" - "time" - - dbapi "github.com/oracle/oracle-database-operator/apis/database/v4" - lrcommons "github.com/oracle/oracle-database-operator/commons/multitenant/lrest" - - "github.com/go-logr/logr" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - - //metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/tools/record" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "sigs.k8s.io/controller-runtime/pkg/event" - "sigs.k8s.io/controller-runtime/pkg/predicate" -) - -// PDBReconciler reconciles a PDB object -type PDBReconciler struct { - client.Client - Log logr.Logger - Scheme *runtime.Scheme - Interval time.Duration - Recorder record.EventRecorder -} - -type controllers struct { - Pdbc PDBReconciler - Cdbc CDBReconciler -} - -type RESTSQLCollection struct { - Env struct { - DefaultTimeZone string `json:"defaultTimeZone,omitempty"` - } `json:"env"` - Items []SQLItem `json:"items"` -} - -type SQLItem struct { - StatementId int `json:"statementId,omitempty"` - Response []string `json:"response"` - ErrorCode int `json:"errorCode,omitempty"` - ErrorLine int `json:"errorLine,omitempty"` - ErrorColumn int `json:"errorColumn,omitempty"` - ErrorDetails string `json:"errorDetails,omitempty"` - Result int `json:"result,omitempty"` -} - -type ORDSError struct { - Code string `json:"code,omitempty"` - Message string `json:"message,omitempty"` - Type string `json:"type,omitempty"` - Instance string `json:"instance,omitempty"` -} - -var ( - pdbPhaseCreate = "Creating" - pdbPhasePlug = "Plugging" - pdbPhaseUnplug = "Unplugging" - pdbPhaseClone = "Cloning" - pdbPhaseFinish = "Finishing" - pdbPhaseReady = "Ready" - pdbPhaseDelete = "Deleting" - pdbPhaseModify = "Modifying" - pdbPhaseMap = "Mapping" - pdbPhaseStatus = "CheckingState" - pdbPhaseFail = "Failed" -) - -const PDBFinalizer = "database.oracle.com/PDBfinalizer" -const ONE = 1 -const ZERO = 0 - -var tdePassword string -var tdeSecret string -var floodcontrol bool = false -var assertivePdbDeletion bool = false /* Global variable for assertive pdb deletion */ - -//+kubebuilder:rbac:groups=database.oracle.com,resources=pdbs,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=database.oracle.com,resources=pdbs/status,verbs=get;update;patch -//+kubebuilder:rbac:groups=database.oracle.com,resources=pdbs/finalizers,verbs=get;create;update;patch;delete - -// +kubebuilder:rbac:groups=core,resources=pods;pods/log;pods/exec;secrets;containers;services;events;configmaps;namespaces,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=core,resources=pods/exec,verbs=create -// +kubebuilder:rbac:groups=apps,resources=statefulsets,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups='',resources=statefulsets/finalizers,verbs=get;list;watch;create;update;patch;delete - -// Reconcile is part of the main kubernetes reconciliation loop which aims to -// move the current state of the cluster closer to the desired state. -// TODO(user): Modify the Reconcile function to compare the state specified by -// the PDB object against the actual cluster state, and then -// perform operations to make the cluster state reflect the state specified by -// the user. -// -// For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.9.2/pkg/reconcile - -func (r *PDBReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - log := r.Log.WithValues("multitenantoperator", req.NamespacedName) - log.Info("Reconcile requested") - - reconcilePeriod := r.Interval * time.Second - requeueY := ctrl.Result{Requeue: true, RequeueAfter: reconcilePeriod} - requeueN := ctrl.Result{} - - var err error - pdb := &dbapi.PDB{} - - // Execute for every reconcile - defer func() { - //log.Info("DEFER PDB", "Name", pdb.Name, "Phase", pdb.Status.Phase, "Status", strconv.FormatBool(pdb.Status.Status)) - if !pdb.Status.Status { - if pdb.Status.Phase == pdbPhaseReady { - pdb.Status.Status = true - } - if err := r.Status().Update(ctx, pdb); err != nil { - log.Error(err, "Failed to update status for :"+pdb.Name, "err", err.Error()) - } - } - }() - - err = r.Client.Get(context.TODO(), req.NamespacedName, pdb) - if err != nil { - if apierrors.IsNotFound(err) { - log.Info("PDB Resource Not found", "Name", pdb.Name) - // Request object not found, could have been deleted after reconcile req. - // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. - // Return and don't requeue - pdb.Status.Status = true - return requeueN, nil - } - // Error reading the object - requeue the req. - return requeueY, err - } - - // Finalizer section - err = r.managePDBDeletion2(ctx, req, pdb) - if err != nil { - log.Info("managePDBDeletion2 Error Deleting resource ") - return requeueY, nil - } - - // Check for Duplicate PDB - if !pdb.Status.Status { - err = r.checkDuplicatePDB(ctx, req, pdb) - if err != nil { - return requeueN, nil - } - } - - action := strings.ToUpper(pdb.Spec.Action) - - if pdb.Status.Phase == pdbPhaseReady { - //log.Info("PDB:", "Name", pdb.Name, "Phase", pdb.Status.Phase, "Status", strconv.FormatBool(pdb.Status.Status)) - if (pdb.Status.Action != "") && (action == "MODIFY" || action == "STATUS" || pdb.Status.Action != action) { - pdb.Status.Status = false - } else { - err = r.getPDBState(ctx, req, pdb) - if err != nil { - pdb.Status.Phase = pdbPhaseFail - } else { - pdb.Status.Phase = pdbPhaseReady - pdb.Status.Msg = "Success" - } - r.Status().Update(ctx, pdb) - } - } - - if !pdb.Status.Status { - r.validatePhase(ctx, req, pdb) - phase := pdb.Status.Phase - log.Info("PDB:", "Name", pdb.Name, "Phase", phase, "Status", strconv.FormatBool(pdb.Status.Status)) - - switch phase { - case pdbPhaseCreate: - err = r.createPDB(ctx, req, pdb) - case pdbPhaseClone: - err = r.clonePDB(ctx, req, pdb) - case pdbPhasePlug: - err = r.plugPDB(ctx, req, pdb) - case pdbPhaseUnplug: - err = r.unplugPDB(ctx, req, pdb) - case pdbPhaseModify: - err = r.modifyPDB(ctx, req, pdb) - case pdbPhaseDelete: - err = r.deletePDB(ctx, req, pdb) - case pdbPhaseStatus: - err = r.getPDBState(ctx, req, pdb) - case pdbPhaseMap: - err = r.mapPDB(ctx, req, pdb) - case pdbPhaseFail: - err = r.mapPDB(ctx, req, pdb) - default: - log.Info("DEFAULT:", "Name", pdb.Name, "Phase", phase, "Status", strconv.FormatBool(pdb.Status.Status)) - return requeueN, nil - } - pdb.Status.Action = strings.ToUpper(pdb.Spec.Action) - if err != nil { - pdb.Status.Phase = pdbPhaseFail - } else { - pdb.Status.Phase = pdbPhaseReady - pdb.Status.Msg = "Success" - } - } - - log.Info("Reconcile completed") - return requeueY, nil -} - -/* -************************************************ - - Validate the PDB Spec - /*********************************************** -*/ -func (r *PDBReconciler) validatePhase(ctx context.Context, req ctrl.Request, pdb *dbapi.PDB) { - - log := r.Log.WithValues("validatePhase", req.NamespacedName) - - action := strings.ToUpper(pdb.Spec.Action) - - log.Info("Validating PDB phase for: "+pdb.Name, "Action", action) - - switch action { - case "CREATE": - pdb.Status.Phase = pdbPhaseCreate - case "CLONE": - pdb.Status.Phase = pdbPhaseClone - case "PLUG": - pdb.Status.Phase = pdbPhasePlug - case "UNPLUG": - pdb.Status.Phase = pdbPhaseUnplug - case "MODIFY": - pdb.Status.Phase = pdbPhaseModify - case "DELETE": - pdb.Status.Phase = pdbPhaseDelete - case "STATUS": - pdb.Status.Phase = pdbPhaseStatus - case "MAP": - pdb.Status.Phase = pdbPhaseMap - } - - log.Info("Validation complete") -} - -/* -*************************************************************** - - Check for Duplicate PDB. Same PDB name on the same CDB resource. - /************************************************************** -*/ -func (r *PDBReconciler) checkDuplicatePDB(ctx context.Context, req ctrl.Request, pdb *dbapi.PDB) error { - - // Name of the CDB CR that holds the ORDS container - cdbResName := pdb.Spec.CDBResName - - log := r.Log.WithValues("checkDuplicatePDB", pdb.Spec.CDBNamespace) - pdbResName := pdb.Spec.PDBName - - pdbList := &dbapi.PDBList{} - - listOpts := []client.ListOption{client.InNamespace(pdb.Spec.CDBNamespace), client.MatchingFields{"spec.pdbName": pdbResName}} - - // List retrieves list of objects for a given namespace and list options. - err := r.List(ctx, pdbList, listOpts...) - if err != nil { - log.Info("Failed to list pdbs", "Namespace", pdb.Spec.CDBNamespace, "Error", err) - return err - } - - if len(pdbList.Items) == 0 { - log.Info("No pdbs found for PDBName: "+pdbResName, "CDBResName", cdbResName) - return nil - } - - for _, p := range pdbList.Items { - log.Info("Found PDB: " + p.Name) - if (p.Name != pdb.Name) && (p.Spec.CDBResName == cdbResName) { - log.Info("Duplicate PDB found") - pdb.Status.Msg = "PDB Resource already exists" - pdb.Status.Status = false - pdb.Status.Phase = pdbPhaseFail - return errors.New("Duplicate PDB found") - } - } - return nil -} - -/* -*************************************************************** - - Get the Custom Resource for the CDB mentioned in the PDB Spec - /************************************************************** -*/ -func (r *PDBReconciler) getCDBResource(ctx context.Context, req ctrl.Request, pdb *dbapi.PDB) (dbapi.CDB, error) { - - log := r.Log.WithValues("getCDBResource", req.NamespacedName) - - var cdb dbapi.CDB // CDB CR corresponding to the CDB name specified in the PDB spec - - // Name of the CDB CR that holds the ORDS container - cdbResName := pdb.Spec.CDBResName - cdbNamespace := pdb.Spec.CDBNamespace - - // Get CDB CR corresponding to the CDB name specified in the PDB spec - err := r.Get(context.Background(), client.ObjectKey{ - Namespace: cdbNamespace, - Name: cdbResName, - }, &cdb) - - if err != nil { - log.Info("Failed to get CRD for CDB", "Name", cdbResName, "Namespace", pdb.Spec.CDBNamespace, "Error", err.Error()) - pdb.Status.Msg = "Unable to get CRD for CDB : " + cdbResName - r.Status().Update(ctx, pdb) - return cdb, err - } - - log.Info("Found CR for CDB", "Name", cdbResName, "CR Name", cdb.Name) - return cdb, nil -} - -/* -*************************************************************** - - Get the ORDS Pod for the CDB mentioned in the PDB Spec - /************************************************************** -*/ -func (r *PDBReconciler) getORDSPod(ctx context.Context, req ctrl.Request, pdb *dbapi.PDB) (corev1.Pod, error) { - - log := r.Log.WithValues("getORDSPod", req.NamespacedName) - - var cdbPod corev1.Pod // ORDS Pod container with connection to the concerned CDB - - // Name of the CDB CR that holds the ORDS container - cdbResName := pdb.Spec.CDBResName - - // Get ORDS Pod associated with the CDB Name specified in the PDB Spec - err := r.Get(context.Background(), client.ObjectKey{ - Namespace: pdb.Spec.CDBNamespace, - Name: cdbResName + "-ords", - }, &cdbPod) - - if err != nil { - log.Info("Failed to get Pod for CDB", "Name", cdbResName, "Namespace", pdb.Spec.CDBNamespace, "Error", err.Error()) - pdb.Status.Msg = "Unable to get ORDS Pod for CDB : " + cdbResName - return cdbPod, err - } - - log.Info("Found ORDS Pod for CDB", "Name", cdbResName, "Pod Name", cdbPod.Name, "ORDS Container hostname", cdbPod.Spec.Hostname) - return cdbPod, nil -} - -/* -************************************************ - - Get Secret Key for a Secret Name - /*********************************************** -*/ -func (r *PDBReconciler) getSecret(ctx context.Context, req ctrl.Request, pdb *dbapi.PDB, secretName string, keyName string) (string, error) { - - log := r.Log.WithValues("getSecret", req.NamespacedName) - - secret := &corev1.Secret{} - err := r.Get(ctx, types.NamespacedName{Name: secretName, Namespace: pdb.Namespace}, secret) - if err != nil { - if apierrors.IsNotFound(err) { - log.Info("Secret not found:" + secretName) - pdb.Status.Msg = "Secret not found:" + secretName - return "", err - } - log.Error(err, "Unable to get the secret.") - return "", err - } - - return string(secret.Data[keyName]), nil -} - -/* -************************************************ - - Issue a REST API Call to the ORDS container - -*********************************************** -*/ -func (r *PDBReconciler) callAPI(ctx context.Context, req ctrl.Request, pdb *dbapi.PDB, url string, payload map[string]string, action string) (string, error) { - log := r.Log.WithValues("callAPI", req.NamespacedName) - - var err error - - secret := &corev1.Secret{} - - err = r.Get(ctx, types.NamespacedName{Name: pdb.Spec.PDBTlsKey.Secret.SecretName, Namespace: pdb.Namespace}, secret) - - if err != nil { - if apierrors.IsNotFound(err) { - log.Info("Secret not found:" + pdb.Spec.PDBTlsKey.Secret.SecretName) - return "", err - } - log.Error(err, "Unable to get the secret.") - return "", err - } - rsaKeyPEM := secret.Data[pdb.Spec.PDBTlsKey.Secret.Key] - - err = r.Get(ctx, types.NamespacedName{Name: pdb.Spec.PDBTlsCrt.Secret.SecretName, Namespace: pdb.Namespace}, secret) - - if err != nil { - if apierrors.IsNotFound(err) { - log.Info("Secret not found:" + pdb.Spec.PDBTlsCrt.Secret.SecretName) - return "", err - } - log.Error(err, "Unable to get the secret.") - return "", err - } - - rsaCertPEM := secret.Data[pdb.Spec.PDBTlsCrt.Secret.Key] - - err = r.Get(ctx, types.NamespacedName{Name: pdb.Spec.PDBTlsCat.Secret.SecretName, Namespace: pdb.Namespace}, secret) - - if err != nil { - if apierrors.IsNotFound(err) { - log.Info("Secret not found:" + pdb.Spec.PDBTlsCat.Secret.SecretName) - return "", err - } - log.Error(err, "Unable to get the secret.") - return "", err - } - - caCert := secret.Data[pdb.Spec.PDBTlsCat.Secret.Key] - - certificate, err := tls.X509KeyPair([]byte(rsaCertPEM), []byte(rsaKeyPEM)) - if err != nil { - pdb.Status.Msg = "Error tls.X509KeyPair" - return "", err - } - - caCertPool := x509.NewCertPool() - caCertPool.AppendCertsFromPEM(caCert) - - tlsConf := &tls.Config{Certificates: []tls.Certificate{certificate}, RootCAs: caCertPool} - - tr := &http.Transport{TLSClientConfig: tlsConf} - - httpclient := &http.Client{Transport: tr} - - log.Info("Issuing REST call", "URL", url, "Action", action) - - /* - cdb, err := r.getCDBResource(ctx, req, pdb) - if err != nil { - return "", err - } - */ - - err = r.Get(ctx, types.NamespacedName{Name: pdb.Spec.WebServerUsr.Secret.SecretName, Namespace: pdb.Namespace}, secret) - if err != nil { - if apierrors.IsNotFound(err) { - log.Info("Secret not found:" + pdb.Spec.WebServerUsr.Secret.SecretName) - return "", err - } - log.Error(err, "Unable to get the secret.") - return "", err - } - - webUser := string(secret.Data[pdb.Spec.WebServerUsr.Secret.Key]) - webUser = strings.TrimSpace(webUser) - - secret = &corev1.Secret{} - err = r.Get(ctx, types.NamespacedName{Name: pdb.Spec.WebServerPwd.Secret.SecretName, Namespace: pdb.Namespace}, secret) - if err != nil { - if apierrors.IsNotFound(err) { - log.Info("Secret not found:" + pdb.Spec.WebServerPwd.Secret.SecretName) - return "", err - } - log.Error(err, "Unable to get the secret.") - return "", err - } - webUserPwd := string(secret.Data[pdb.Spec.WebServerPwd.Secret.Key]) - webUserPwd = strings.TrimSpace(webUserPwd) - - var httpreq *http.Request - if action == "GET" { - httpreq, err = http.NewRequest(action, url, nil) - } else { - jsonValue, _ := json.Marshal(payload) - httpreq, err = http.NewRequest(action, url, bytes.NewBuffer(jsonValue)) - } - - if err != nil { - log.Info("Unable to create HTTP Request for PDB : "+pdb.Name, "err", err.Error()) - return "", err - } - - httpreq.Header.Add("Accept", "application/json") - httpreq.Header.Add("Content-Type", "application/json") - httpreq.SetBasicAuth(webUser, webUserPwd) - - resp, err := httpclient.Do(httpreq) - if err != nil { - errmsg := err.Error() - log.Error(err, "Failed - Could not connect to ORDS Pod", "err", err.Error()) - pdb.Status.Msg = "Error: Could not connect to ORDS Pod" - r.Recorder.Eventf(pdb, corev1.EventTypeWarning, "ORDSError", errmsg) - return "", err - } - - r.Recorder.Eventf(pdb, corev1.EventTypeWarning, "Done", pdb.Spec.CDBResName) - if resp.StatusCode != http.StatusOK { - bb, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode == 404 { - pdb.Status.ConnString = "" - pdb.Status.Msg = pdb.Spec.PDBName + " not found" - - } else { - if floodcontrol == false { - pdb.Status.Msg = "ORDS Error - HTTP Status Code:" + strconv.Itoa(resp.StatusCode) - } - } - - if floodcontrol == false { - log.Info("ORDS Error - HTTP Status Code :"+strconv.Itoa(resp.StatusCode), "Err", string(bb)) - } - - var apiErr ORDSError - json.Unmarshal([]byte(bb), &apiErr) - if floodcontrol == false { - r.Recorder.Eventf(pdb, corev1.EventTypeWarning, "ORDSError", "Failed: %s", apiErr.Message) - } - //fmt.Printf("%+v", apiErr) - //fmt.Println(string(bb)) - floodcontrol = true - return "", errors.New("ORDS Error") - } - floodcontrol = false - - defer resp.Body.Close() - - bodyBytes, err := ioutil.ReadAll(resp.Body) - if err != nil { - fmt.Print(err.Error()) - } - respData := string(bodyBytes) - //fmt.Println(string(bodyBytes)) - - var apiResponse RESTSQLCollection - json.Unmarshal([]byte(bodyBytes), &apiResponse) - //fmt.Printf("%#v", apiResponse) - //fmt.Printf("%+v", apiResponse) - - errFound := false - for _, sqlItem := range apiResponse.Items { - if sqlItem.ErrorDetails != "" { - log.Info("ORDS Error - Oracle Error Code :" + strconv.Itoa(sqlItem.ErrorCode)) - if !errFound { - pdb.Status.Msg = sqlItem.ErrorDetails - } - r.Recorder.Eventf(pdb, corev1.EventTypeWarning, "OraError", "%s", sqlItem.ErrorDetails) - errFound = true - } - } - - if errFound { - return "", errors.New("Oracle Error") - } - - return respData, nil -} - -/* -************************************************ - - Create a PDB - -*********************************************** -*/ -func (r *PDBReconciler) createPDB(ctx context.Context, req ctrl.Request, pdb *dbapi.PDB) error { - - log := r.Log.WithValues("createPDB", req.NamespacedName) - - var err error - var tdePassword string - var tdeSecret string - - cdb, err := r.getCDBResource(ctx, req, pdb) - if err != nil { - return err - } - - /*** BEGIN GET ENCPASS ***/ - secret := &corev1.Secret{} - - err = r.Get(ctx, types.NamespacedName{Name: pdb.Spec.AdminName.Secret.SecretName, Namespace: pdb.Namespace}, secret) - if err != nil { - if apierrors.IsNotFound(err) { - log.Info("Secret not found:" + pdb.Spec.AdminName.Secret.SecretName) - return err - } - log.Error(err, "Unable to get the secret.") - return err - } - pdbAdminNameEnc := string(secret.Data[pdb.Spec.AdminName.Secret.Key]) - pdbAdminNameEnc = strings.TrimSpace(pdbAdminNameEnc) - - err = r.Get(ctx, types.NamespacedName{Name: pdb.Spec.PDBPriKey.Secret.SecretName, Namespace: pdb.Namespace}, secret) - if err != nil { - if apierrors.IsNotFound(err) { - log.Info("Secret not found:" + pdb.Spec.PDBPriKey.Secret.SecretName) - return err - } - log.Error(err, "Unable to get the secret.") - return err - } - privKey := string(secret.Data[pdb.Spec.PDBPriKey.Secret.Key]) - pdbAdminName, err := lrcommons.CommonDecryptWithPrivKey(privKey, pdbAdminNameEnc, req) - - // Get Web Server User Password - secret = &corev1.Secret{} - err = r.Get(ctx, types.NamespacedName{Name: pdb.Spec.AdminPwd.Secret.SecretName, Namespace: pdb.Namespace}, secret) - if err != nil { - if apierrors.IsNotFound(err) { - log.Info("Secret not found:" + pdb.Spec.AdminPwd.Secret.SecretName) - return err - } - log.Error(err, "Unable to get the secret.") - return err - } - pdbAdminPwdEnc := string(secret.Data[pdb.Spec.AdminPwd.Secret.Key]) - pdbAdminPwdEnc = strings.TrimSpace(pdbAdminPwdEnc) - pdbAdminPwd, err := lrcommons.CommonDecryptWithPrivKey(privKey, pdbAdminPwdEnc, req) - pdbAdminName = strings.TrimSuffix(pdbAdminName, "\n") - pdbAdminPwd = strings.TrimSuffix(pdbAdminPwd, "\n") - /*** END GET ENCPASS ***/ - - log.Info("====================> " + pdbAdminName + ":" + pdbAdminPwd) - /* Prevent creating an existing pdb */ - err = r.getPDBState(ctx, req, pdb) - if err != nil { - if apierrors.IsNotFound(err) { - log.Info("Check PDB not existence completed", "PDB Name", pdb.Spec.PDBName) - } - - } else { - log.Info("Database already exists ", "PDB Name", pdb.Spec.PDBName) - return nil - } - - values := map[string]string{ - "method": "CREATE", - "pdb_name": pdb.Spec.PDBName, - "adminName": pdbAdminName, - "adminPwd": pdbAdminPwd, - "fileNameConversions": pdb.Spec.FileNameConversions, - "reuseTempFile": strconv.FormatBool(*(pdb.Spec.ReuseTempFile)), - "unlimitedStorage": strconv.FormatBool(*(pdb.Spec.UnlimitedStorage)), - "totalSize": pdb.Spec.TotalSize, - "tempSize": pdb.Spec.TempSize, - "getScript": strconv.FormatBool(*(pdb.Spec.GetScript))} - - if *(pdb.Spec.TDEImport) { - tdePassword, err = r.getSecret(ctx, req, pdb, pdb.Spec.TDEPassword.Secret.SecretName, pdb.Spec.TDEPassword.Secret.Key) - if err != nil { - return err - } - tdeSecret, err = r.getSecret(ctx, req, pdb, pdb.Spec.TDESecret.Secret.SecretName, pdb.Spec.TDESecret.Secret.Key) - if err != nil { - return err - } - - tdeSecret = tdeSecret[:len(tdeSecret)-1] - tdePassword = tdeSecret[:len(tdePassword)-1] - values["tdePassword"] = tdePassword - values["tdeKeystorePath"] = pdb.Spec.TDEKeystorePath - values["tdeSecret"] = tdeSecret - } - - url := "https://" + pdb.Spec.CDBResName + "-ords." + pdb.Spec.CDBNamespace + ":" + strconv.Itoa(cdb.Spec.ORDSPort) + "/ords/_/db-api/latest/database/pdbs/" - - pdb.Status.TotalSize = pdb.Spec.TotalSize - pdb.Status.Phase = pdbPhaseCreate - pdb.Status.Msg = "Waiting for PDB to be created" - if err := r.Status().Update(ctx, pdb); err != nil { - log.Error(err, "Failed to update status for :"+pdb.Name, "err", err.Error()) - } - _, err = NewCallApi(r, ctx, req, pdb, url, values, "POST") - if err != nil { - log.Error(err, "callAPI error", "err", err.Error()) - return err - } - - r.Recorder.Eventf(pdb, corev1.EventTypeNormal, "Created", "PDB '%s' created successfully", pdb.Spec.PDBName) - - if cdb.Spec.DBServer != "" { - pdb.Status.ConnString = cdb.Spec.DBServer + ":" + strconv.Itoa(cdb.Spec.DBPort) + "/" + pdb.Spec.PDBName - } else { - pdb.Status.ConnString = cdb.Spec.DBTnsurl - ParseTnsAlias(&(pdb.Status.ConnString), &(pdb.Spec.PDBName)) - } - - assertivePdbDeletion = pdb.Spec.AssertivePdbDeletion - if pdb.Spec.AssertivePdbDeletion == true { - r.Recorder.Eventf(pdb, corev1.EventTypeNormal, "Created", "PDB '%s' assertive pdb deletion turned on", pdb.Spec.PDBName) - } - log.Info("New connect strinng", "tnsurl", cdb.Spec.DBTnsurl) - log.Info("Created PDB Resource", "PDB Name", pdb.Spec.PDBName) - r.getPDBState(ctx, req, pdb) - return nil -} - -/* -************************************************ - - Clone a PDB - -*********************************************** -*/ -func (r *PDBReconciler) clonePDB(ctx context.Context, req ctrl.Request, pdb *dbapi.PDB) error { - - if pdb.Spec.PDBName == pdb.Spec.SrcPDBName { - return nil - } - - log := r.Log.WithValues("clonePDB", req.NamespacedName) - - var err error - - cdb, err := r.getCDBResource(ctx, req, pdb) - if err != nil { - return err - } - - /* Prevent cloning an existing pdb */ - err = r.getPDBState(ctx, req, pdb) - if err != nil { - if apierrors.IsNotFound(err) { - log.Info("Check PDB not existence completed", "PDB Name", pdb.Spec.PDBName) - } - - } else { - log.Info("Database already exists ", "PDB Name", pdb.Spec.PDBName) - return nil - } - - values := map[string]string{ - "method": "CLONE", - "clonePDBName": pdb.Spec.PDBName, - "reuseTempFile": strconv.FormatBool(*(pdb.Spec.ReuseTempFile)), - "unlimitedStorage": strconv.FormatBool(*(pdb.Spec.UnlimitedStorage)), - "getScript": strconv.FormatBool(*(pdb.Spec.GetScript))} - - if pdb.Spec.SparseClonePath != "" { - values["sparseClonePath"] = pdb.Spec.SparseClonePath - } - if pdb.Spec.FileNameConversions != "" { - values["fileNameConversions"] = pdb.Spec.FileNameConversions - } - if pdb.Spec.TotalSize != "" { - values["totalSize"] = pdb.Spec.TotalSize - } - if pdb.Spec.TempSize != "" { - values["tempSize"] = pdb.Spec.TempSize - } - - url := "https://" + pdb.Spec.CDBResName + "-ords." + pdb.Spec.CDBNamespace + ":" + strconv.Itoa(cdb.Spec.ORDSPort) + "/ords/_/db-api/latest/database/pdbs/" + pdb.Spec.SrcPDBName + "/" - - pdb.Status.Phase = pdbPhaseClone - pdb.Status.Msg = "Waiting for PDB to be cloned" - if err := r.Status().Update(ctx, pdb); err != nil { - log.Error(err, "Failed to update status for :"+pdb.Name, "err", err.Error()) - } - _, err = NewCallApi(r, ctx, req, pdb, url, values, "POST") - if err != nil { - return err - } - - r.Recorder.Eventf(pdb, corev1.EventTypeNormal, "Created", "PDB '%s' cloned successfully", pdb.Spec.PDBName) - - if cdb.Spec.DBServer != "" { - pdb.Status.ConnString = cdb.Spec.DBServer + ":" + strconv.Itoa(cdb.Spec.DBPort) + "/" + pdb.Spec.PDBName - } else { - pdb.Status.ConnString = cdb.Spec.DBTnsurl - ParseTnsAlias(&(pdb.Status.ConnString), &(pdb.Spec.PDBName)) - } - - assertivePdbDeletion = pdb.Spec.AssertivePdbDeletion - if pdb.Spec.AssertivePdbDeletion == true { - r.Recorder.Eventf(pdb, corev1.EventTypeNormal, "Clone", "PDB '%s' assertive pdb deletion turned on", pdb.Spec.PDBName) - } - - log.Info("Cloned PDB successfully", "Source PDB Name", pdb.Spec.SrcPDBName, "Clone PDB Name", pdb.Spec.PDBName) - r.getPDBState(ctx, req, pdb) - return nil -} - -/* -************************************************ - - Plug a PDB - -*********************************************** -*/ -func (r *PDBReconciler) plugPDB(ctx context.Context, req ctrl.Request, pdb *dbapi.PDB) error { - - log := r.Log.WithValues("plugPDB", req.NamespacedName) - - var err error - var tdePassword string - var tdeSecret string - - cdb, err := r.getCDBResource(ctx, req, pdb) - if err != nil { - return err - } - - values := map[string]string{ - "method": "PLUG", - "xmlFileName": pdb.Spec.XMLFileName, - "pdb_name": pdb.Spec.PDBName, - //"adminName": pdbAdminName, - //"adminPwd": pdbAdminPwd, - "sourceFileNameConversions": pdb.Spec.SourceFileNameConversions, - "copyAction": pdb.Spec.CopyAction, - "fileNameConversions": pdb.Spec.FileNameConversions, - "unlimitedStorage": strconv.FormatBool(*(pdb.Spec.UnlimitedStorage)), - "reuseTempFile": strconv.FormatBool(*(pdb.Spec.ReuseTempFile)), - "totalSize": pdb.Spec.TotalSize, - "tempSize": pdb.Spec.TempSize, - "getScript": strconv.FormatBool(*(pdb.Spec.GetScript))} - - if *(pdb.Spec.TDEImport) { - tdePassword, err = r.getSecret(ctx, req, pdb, pdb.Spec.TDEPassword.Secret.SecretName, pdb.Spec.TDEPassword.Secret.Key) - if err != nil { - return err - } - tdeSecret, err = r.getSecret(ctx, req, pdb, pdb.Spec.TDESecret.Secret.SecretName, pdb.Spec.TDESecret.Secret.Key) - if err != nil { - return err - } - - tdeSecret = tdeSecret[:len(tdeSecret)-1] - tdePassword = tdeSecret[:len(tdePassword)-1] - values["tdePassword"] = tdePassword - values["tdeKeystorePath"] = pdb.Spec.TDEKeystorePath - values["tdeSecret"] = tdeSecret - values["tdeImport"] = strconv.FormatBool(*(pdb.Spec.TDEImport)) - } - if *(pdb.Spec.AsClone) { - values["asClone"] = strconv.FormatBool(*(pdb.Spec.AsClone)) - } - - url := "https://" + pdb.Spec.CDBResName + "-ords." + pdb.Spec.CDBNamespace + ":" + strconv.Itoa(cdb.Spec.ORDSPort) + "/ords/_/db-api/latest/database/pdbs/" - - pdb.Status.TotalSize = pdb.Spec.TotalSize - pdb.Status.Phase = pdbPhasePlug - pdb.Status.Msg = "Waiting for PDB to be plugged" - if err := r.Status().Update(ctx, pdb); err != nil { - log.Error(err, "Failed to update status for :"+pdb.Name, "err", err.Error()) - } - _, err = NewCallApi(r, ctx, req, pdb, url, values, "POST") - if err != nil { - return err - } - - r.Recorder.Eventf(pdb, corev1.EventTypeNormal, "Created", "PDB '%s' plugged successfully", pdb.Spec.PDBName) - - if cdb.Spec.DBServer != "" { - pdb.Status.ConnString = cdb.Spec.DBServer + ":" + strconv.Itoa(cdb.Spec.DBPort) + "/" + pdb.Spec.PDBName - } else { - pdb.Status.ConnString = cdb.Spec.DBTnsurl - ParseTnsAlias(&(pdb.Status.ConnString), &(pdb.Spec.PDBName)) - } - - assertivePdbDeletion = pdb.Spec.AssertivePdbDeletion - if pdb.Spec.AssertivePdbDeletion == true { - r.Recorder.Eventf(pdb, corev1.EventTypeNormal, "Plugged", "PDB '%s' assertive pdb deletion turned on", pdb.Spec.PDBName) - } - - log.Info("Successfully plugged PDB", "PDB Name", pdb.Spec.PDBName) - r.getPDBState(ctx, req, pdb) - return nil -} - -/* -************************************************ - - Unplug a PDB - -*********************************************** -*/ -func (r *PDBReconciler) unplugPDB(ctx context.Context, req ctrl.Request, pdb *dbapi.PDB) error { - - log := r.Log.WithValues("unplugPDB", req.NamespacedName) - - var err error - var tdePassword string - var tdeSecret string - - cdb, err := r.getCDBResource(ctx, req, pdb) - if err != nil { - return err - } - - values := map[string]string{ - "method": "UNPLUG", - "xmlFileName": pdb.Spec.XMLFileName, - "getScript": strconv.FormatBool(*(pdb.Spec.GetScript))} - - if *(pdb.Spec.TDEExport) { - // Get the TDE Password - tdePassword, err = r.getSecret(ctx, req, pdb, pdb.Spec.TDEPassword.Secret.SecretName, pdb.Spec.TDEPassword.Secret.Key) - if err != nil { - return err - } - tdeSecret, err = r.getSecret(ctx, req, pdb, pdb.Spec.TDESecret.Secret.SecretName, pdb.Spec.TDESecret.Secret.Key) - if err != nil { - return err - } - - tdeSecret = tdeSecret[:len(tdeSecret)-1] - tdePassword = tdeSecret[:len(tdePassword)-1] - values["tdePassword"] = tdePassword - values["tdeKeystorePath"] = pdb.Spec.TDEKeystorePath - values["tdeSecret"] = tdeSecret - values["tdeExport"] = strconv.FormatBool(*(pdb.Spec.TDEExport)) - } - - url := "https://" + pdb.Spec.CDBResName + "-ords." + pdb.Spec.CDBNamespace + ":" + strconv.Itoa(cdb.Spec.ORDSPort) + "/ords/_/db-api/latest/database/pdbs/" + pdb.Spec.PDBName + "/" - - log.Info("CallAPI(url)", "url", url) - - pdb.Status.Phase = pdbPhaseUnplug - pdb.Status.Msg = "Waiting for PDB to be unplugged" - - if cdb.Spec.DBServer != "" { - pdb.Status.ConnString = cdb.Spec.DBServer + ":" + strconv.Itoa(cdb.Spec.DBPort) + "/" + pdb.Spec.PDBName - } else { - pdb.Status.ConnString = cdb.Spec.DBTnsurl - ParseTnsAlias(&(pdb.Status.ConnString), &(pdb.Spec.PDBName)) - } - - if err := r.Status().Update(ctx, pdb); err != nil { - log.Error(err, "Failed to update status for :"+pdb.Name, "err", err.Error()) - } - _, err = NewCallApi(r, ctx, req, pdb, url, values, "POST") - if err != nil { - return err - } - - if controllerutil.ContainsFinalizer(pdb, PDBFinalizer) { - log.Info("Removing finalizer") - controllerutil.RemoveFinalizer(pdb, PDBFinalizer) - err := r.Update(ctx, pdb) - if err != nil { - log.Info("Could not remove finalizer", "err", err.Error()) - return err - } - pdb.Status.Status = true - err = r.Delete(context.Background(), pdb, client.GracePeriodSeconds(1)) - if err != nil { - log.Info("Could not delete PDB resource", "err", err.Error()) - return err - } - } - - r.Recorder.Eventf(pdb, corev1.EventTypeNormal, "Unplugged", "PDB '%s' unplugged successfully", pdb.Spec.PDBName) - - log.Info("Successfully unplugged PDB resource") - return nil -} - -/* -************************************************ - - Modify a PDB state - /*********************************************** -*/ -func (r *PDBReconciler) modifyPDB(ctx context.Context, req ctrl.Request, pdb *dbapi.PDB) error { - - log := r.Log.WithValues("modifyPDB", req.NamespacedName) - - var err error - - err = r.getPDBState(ctx, req, pdb) - if err != nil { - if apierrors.IsNotFound(err) { - log.Info("Warning PDB does not exist", "PDB Name", pdb.Spec.PDBName) - return nil - } - return err - } - - if pdb.Status.OpenMode == "READ WRITE" && pdb.Spec.PDBState == "OPEN" && pdb.Spec.ModifyOption == "READ WRITE" { - /* Database is already open no action required */ - return nil - } - - if pdb.Status.OpenMode == "MOUNTED" && pdb.Spec.PDBState == "CLOSE" && pdb.Spec.ModifyOption == "IMMEDIATE" { - /* Database is already close no action required */ - return nil - } - - // To prevent Reconcile from Modifying again whenever the Operator gets re-started - /* - modOption := pdb.Spec.PDBState + "-" + pdb.Spec.ModifyOption - if pdb.Status.ModifyOption == modOption { - return nil - } - */ - - cdb, err := r.getCDBResource(ctx, req, pdb) - if err != nil { - return err - } - - values := map[string]string{ - "state": pdb.Spec.PDBState, - "modifyOption": pdb.Spec.ModifyOption, - "getScript": strconv.FormatBool(*(pdb.Spec.GetScript))} - log.Info("MODIFY PDB", "pdb.Spec.PDBState=", pdb.Spec.PDBState, "pdb.Spec.ModifyOption=", pdb.Spec.ModifyOption) - log.Info("PDB STATUS OPENMODE", "pdb.Status.OpenMode=", pdb.Status.OpenMode) - - pdbName := pdb.Spec.PDBName - url := "https://" + pdb.Spec.CDBResName + "-ords." + pdb.Spec.CDBNamespace + ":" + strconv.Itoa(cdb.Spec.ORDSPort) + "/ords/_/db-api/latest/database/pdbs/" + pdbName + "/status" - - pdb.Status.Phase = pdbPhaseModify - pdb.Status.ModifyOption = pdb.Spec.PDBState + "-" + pdb.Spec.ModifyOption - pdb.Status.Msg = "Waiting for PDB to be modified" - if err := r.Status().Update(ctx, pdb); err != nil { - log.Error(err, "Failed to update status for :"+pdb.Name, "err", err.Error()) - } - _, err = NewCallApi(r, ctx, req, pdb, url, values, "POST") - if err != nil { - return err - } - - r.Recorder.Eventf(pdb, corev1.EventTypeNormal, "Modified", "PDB '%s' modified successfully", pdb.Spec.PDBName) - - if cdb.Spec.DBServer != "" { - pdb.Status.ConnString = cdb.Spec.DBServer + ":" + strconv.Itoa(cdb.Spec.DBPort) + "/" + pdb.Spec.PDBName - } else { - pdb.Status.ConnString = cdb.Spec.DBTnsurl - } - - log.Info("Successfully modified PDB state", "PDB Name", pdb.Spec.PDBName) - r.getPDBState(ctx, req, pdb) - return nil -} - -/* -************************************************ - - Get PDB State - /*********************************************** -*/ -func (r *PDBReconciler) getPDBState(ctx context.Context, req ctrl.Request, pdb *dbapi.PDB) error { - - log := r.Log.WithValues("getPDBState", req.NamespacedName) - - var err error - - cdb, err := r.getCDBResource(ctx, req, pdb) - if err != nil { - return err - } - - pdbName := pdb.Spec.PDBName - url := "https://" + pdb.Spec.CDBResName + "-ords." + pdb.Spec.CDBNamespace + ":" + strconv.Itoa(cdb.Spec.ORDSPort) + "/ords/_/db-api/latest/database/pdbs/" + pdbName + "/status" - - pdb.Status.Msg = "Getting PDB state" - if err := r.Status().Update(ctx, pdb); err != nil { - log.Error(err, "Failed to update status for :"+pdb.Name, "err", err.Error()) - } - - respData, err := NewCallApi(r, ctx, req, pdb, url, nil, "GET") - - if err != nil { - pdb.Status.OpenMode = "UNKNOWN" - pdb.Status.Msg = "CHECK PDB STATUS" - pdb.Status.Status = false - return err - } - - var objmap map[string]interface{} - if err := json.Unmarshal([]byte(respData), &objmap); err != nil { - log.Error(err, "Failed to get state of PDB :"+pdbName, "err", err.Error()) - } - - pdb.Status.OpenMode = objmap["open_mode"].(string) - - if pdb.Status.OpenMode == "READ WRITE" { - err := r.mapPDB(ctx, req, pdb) - if err != nil { - log.Info("Fail to Map resource getting PDB state") - } - } - - log.Info("Successfully obtained PDB state", "PDB Name", pdb.Spec.PDBName, "State", objmap["open_mode"].(string)) - return nil -} - -/* -************************************************ - - Map Database PDB to Kubernetes PDB CR - /*********************************************** -*/ -func (r *PDBReconciler) mapPDB(ctx context.Context, req ctrl.Request, pdb *dbapi.PDB) error { - - log := r.Log.WithValues("mapPDB", req.NamespacedName) - - var err error - - cdb, err := r.getCDBResource(ctx, req, pdb) - if err != nil { - return err - } - - pdbName := pdb.Spec.PDBName - url := "https://" + pdb.Spec.CDBResName + "-ords." + pdb.Spec.CDBNamespace + ":" + strconv.Itoa(cdb.Spec.ORDSPort) + "/ords/_/db-api/latest/database/pdbs/" + pdbName + "/" - - pdb.Status.Msg = "Mapping PDB" - if err := r.Status().Update(ctx, pdb); err != nil { - log.Error(err, "Failed to update status for :"+pdb.Name, "err", err.Error()) - } - - respData, err := NewCallApi(r, ctx, req, pdb, url, nil, "GET") - - if err != nil { - pdb.Status.OpenMode = "UNKNOWN" - return err - } - - var objmap map[string]interface{} - if err := json.Unmarshal([]byte(respData), &objmap); err != nil { - log.Error(err, "Failed to get state of PDB :"+pdbName, "err", err.Error()) - } - - totSizeInBytes := objmap["total_size"].(float64) - totSizeInGB := totSizeInBytes / 1024 / 1024 / 1024 - - pdb.Status.OpenMode = objmap["open_mode"].(string) - pdb.Status.TotalSize = fmt.Sprintf("%.2f", totSizeInGB) + "G" - assertivePdbDeletion = pdb.Spec.AssertivePdbDeletion - if pdb.Spec.AssertivePdbDeletion == true { - r.Recorder.Eventf(pdb, corev1.EventTypeNormal, "Mapped", "PDB '%s' assertive pdb deletion turned on", pdb.Spec.PDBName) - } - - if cdb.Spec.DBServer != "" { - pdb.Status.ConnString = cdb.Spec.DBServer + ":" + strconv.Itoa(cdb.Spec.DBPort) + "/" + pdb.Spec.PDBName - } else { - pdb.Status.ConnString = cdb.Spec.DBTnsurl - ParseTnsAlias(&(pdb.Status.ConnString), &(pdb.Spec.PDBName)) - } - - if err := r.Status().Update(ctx, pdb); err != nil { - log.Error(err, "Failed to update status for :"+pdb.Name, "err", err.Error()) - } - - log.Info("Successfully mapped PDB to Kubernetes resource", "PDB Name", pdb.Spec.PDBName) - return nil -} - -/* -************************************************ - - Delete a PDB - /*********************************************** -*/ -func (r *PDBReconciler) deletePDB(ctx context.Context, req ctrl.Request, pdb *dbapi.PDB) error { - - log := r.Log.WithValues("deletePDB", req.NamespacedName) - - err := r.deletePDBInstance(req, ctx, pdb) - if err != nil { - log.Info("Could not delete PDB", "PDB Name", pdb.Spec.PDBName, "err", err.Error()) - return err - } - - if controllerutil.ContainsFinalizer(pdb, PDBFinalizer) { - log.Info("Removing finalizer") - controllerutil.RemoveFinalizer(pdb, PDBFinalizer) - err := r.Update(ctx, pdb) - if err != nil { - log.Info("Could not remove finalizer", "err", err.Error()) - return err - } - pdb.Status.Status = true - err = r.Delete(context.Background(), pdb, client.GracePeriodSeconds(1)) - if err != nil { - log.Info("Could not delete PDB resource", "err", err.Error()) - return err - } - } - - r.Recorder.Eventf(pdb, corev1.EventTypeNormal, "Deleted", "PDB '%s' dropped successfully", pdb.Spec.PDBName) - - log.Info("Successfully deleted PDB resource") - return nil -} - -/************************************************* - - Check PDB deletion -**************************************************/ - -func (r *PDBReconciler) managePDBDeletion2(ctx context.Context, req ctrl.Request, pdb *dbapi.PDB) error { - log := r.Log.WithValues("managePDBDeletion", req.NamespacedName) - if pdb.ObjectMeta.DeletionTimestamp.IsZero() { - if !controllerutil.ContainsFinalizer(pdb, PDBFinalizer) { - controllerutil.AddFinalizer(pdb, PDBFinalizer) - if err := r.Update(ctx, pdb); err != nil { - return err - } - } - } else { - log.Info("Pdb marked to be delted") - if controllerutil.ContainsFinalizer(pdb, PDBFinalizer) { - if assertivePdbDeletion == true { - log.Info("Deleting pdb CRD: Assertive approach is turned on ") - cdb, err := r.getCDBResource(ctx, req, pdb) - if err != nil { - log.Error(err, "Cannont find cdb resource ", "err", err.Error()) - return err - } - - var errclose error - pdbName := pdb.Spec.PDBName - if pdb.Status.OpenMode == "READ WRITE" { - valuesclose := map[string]string{ - "state": "CLOSE", - "modifyOption": "IMMEDIATE", - "getScript": "FALSE"} - url := "https://" + pdb.Spec.CDBResName + "-ords." + pdb.Spec.CDBNamespace + ":" + strconv.Itoa(cdb.Spec.ORDSPort) + "/ords/_/db-api/latest/database/pdbs/" + pdbName + "/status" - _, errclose = NewCallApi(r, ctx, req, pdb, url, valuesclose, "POST") - if errclose != nil { - log.Info("Warning error closing pdb continue anyway") - } - } - - if errclose == nil { - valuesdrop := map[string]string{ - "action": "INCLUDING", - "getScript": "FALSE"} - url := "https://" + pdb.Spec.CDBResName + "-ords." + pdb.Spec.CDBNamespace + ":" + strconv.Itoa(cdb.Spec.ORDSPort) + "/ords/_/db-api/latest/database/pdbs/" + pdbName + "/" - - log.Info("Call Delete()") - _, errdelete := NewCallApi(r, ctx, req, pdb, url, valuesdrop, "DELETE") - if errdelete != nil { - log.Error(errdelete, "Fail to delete pdb :"+pdb.Name, "err", errdelete.Error()) - return errdelete - } - } - - } /* END OF ASSERTIVE SECTION */ - - log.Info("Marked to be deleted") - pdb.Status.Phase = pdbPhaseDelete - pdb.Status.Status = true - r.Status().Update(ctx, pdb) - - controllerutil.RemoveFinalizer(pdb, PDBFinalizer) - if err := r.Update(ctx, pdb); err != nil { - log.Info("Cannot remove finalizer") - return err - } - - } - - return nil - } - - return nil -} - -/* -************************************************ - - Finalization logic for PDBFinalizer - /*********************************************** -*/ -func (r *PDBReconciler) deletePDBInstance(req ctrl.Request, ctx context.Context, pdb *dbapi.PDB) error { - - log := r.Log.WithValues("deletePDBInstance", req.NamespacedName) - - var err error - - cdb, err := r.getCDBResource(ctx, req, pdb) - if err != nil { - return err - } - - values := map[string]string{ - "action": "KEEP", - "getScript": strconv.FormatBool(*(pdb.Spec.GetScript))} - - if pdb.Spec.DropAction != "" { - values["action"] = pdb.Spec.DropAction - } - - pdbName := pdb.Spec.PDBName - url := "https://" + pdb.Spec.CDBResName + "-ords." + pdb.Spec.CDBNamespace + ":" + strconv.Itoa(cdb.Spec.ORDSPort) + "/ords/_/db-api/latest/database/pdbs/" + pdbName + "/" - - pdb.Status.Phase = pdbPhaseDelete - pdb.Status.Msg = "Waiting for PDB to be deleted" - if err := r.Status().Update(ctx, pdb); err != nil { - log.Error(err, "Failed to update status for :"+pdb.Name, "err", err.Error()) - } - _, err = NewCallApi(r, ctx, req, pdb, url, values, "DELETE") - if err != nil { - pdb.Status.ConnString = "" - return err - } - - log.Info("Successfully dropped PDB", "PDB Name", pdbName) - return nil -} - -/* -************************************************************* - - SetupWithManager sets up the controller with the Manager. - /************************************************************ -*/ -func (r *PDBReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&dbapi.PDB{}). - WithEventFilter(predicate.Funcs{ - UpdateFunc: func(e event.UpdateEvent) bool { - // Ignore updates to CR status in which case metadata.Generation does not change - return e.ObjectOld.GetGeneration() != e.ObjectNew.GetGeneration() - }, - DeleteFunc: func(e event.DeleteEvent) bool { - // Evaluates to false if the object has been confirmed deleted. - //return !e.DeleteStateUnknown - return false - }, - }). - WithOptions(controller.Options{MaxConcurrentReconciles: 100}). - Complete(r) -} - -/************************************************************* -Enh 35357707 - PROVIDE THE PDB TNSALIAS INFORMATION -**************************************************************/ - -func ParseTnsAlias(tns *string, pdbsrv *string) { - var swaptns string - fmt.Printf("Analyzing string [%s]\n", *tns) - fmt.Printf("Relacing srv [%s]\n", *pdbsrv) - - if strings.Contains(strings.ToUpper(*tns), "SERVICE_NAME") == false { - fmt.Print("Cannot generate tns alias for pdb") - return - } - - if strings.Contains(strings.ToUpper(*tns), "ORACLE_SID") == true { - fmt.Print("Cannot generate tns alias for pdb") - return - } - - swaptns = fmt.Sprintf("SERVICE_NAME=%s", *pdbsrv) - tnsreg := regexp.MustCompile(`SERVICE_NAME=\w+`) - *tns = tnsreg.ReplaceAllString(*tns, swaptns) - - fmt.Printf("Newstring [%s]\n", *tns) - -} - -func NewCallApi(intr interface{}, ctx context.Context, req ctrl.Request, pdb *dbapi.PDB, url string, payload map[string]string, action string) (string, error) { - - var c client.Client - var r logr.Logger - var e record.EventRecorder - var err error - - recpdb, ok1 := intr.(*PDBReconciler) - if ok1 { - fmt.Printf("func NewCallApi ((*PDBReconciler),......)\n") - c = recpdb.Client - e = recpdb.Recorder - r = recpdb.Log - } - - reccdb, ok2 := intr.(*CDBReconciler) - if ok2 { - fmt.Printf("func NewCallApi ((*CDBReconciler),......)\n") - c = reccdb.Client - e = reccdb.Recorder - r = reccdb.Log - } - - secret := &corev1.Secret{} - - log := r.WithValues("NewCallApi", req.NamespacedName) - log.Info("Call c.Get") - err = c.Get(ctx, types.NamespacedName{Name: pdb.Spec.PDBTlsKey.Secret.SecretName, Namespace: pdb.Namespace}, secret) - - if err != nil { - if apierrors.IsNotFound(err) { - log.Info("Secret not found:" + pdb.Spec.PDBTlsKey.Secret.SecretName) - return "", err - } - log.Error(err, "Unable to get the secret.") - return "", err - } - rsaKeyPEM := secret.Data[pdb.Spec.PDBTlsKey.Secret.Key] - - err = c.Get(ctx, types.NamespacedName{Name: pdb.Spec.PDBTlsCrt.Secret.SecretName, Namespace: pdb.Namespace}, secret) - - if err != nil { - if apierrors.IsNotFound(err) { - log.Info("Secret not found:" + pdb.Spec.PDBTlsCrt.Secret.SecretName) - return "", err - } - log.Error(err, "Unable to get the secret.") - return "", err - } - - rsaCertPEM := secret.Data[pdb.Spec.PDBTlsCrt.Secret.Key] - - err = c.Get(ctx, types.NamespacedName{Name: pdb.Spec.PDBTlsCat.Secret.SecretName, Namespace: pdb.Namespace}, secret) - - if err != nil { - if apierrors.IsNotFound(err) { - log.Info("Secret not found:" + pdb.Spec.PDBTlsCat.Secret.SecretName) - return "", err - } - log.Error(err, "Unable to get the secret.") - return "", err - } - - caCert := secret.Data[pdb.Spec.PDBTlsCat.Secret.Key] - /* - r.Recorder.Eventf(pdb, corev1.EventTypeWarning, "ORDSINFO", string(rsaKeyPEM)) - r.Recorder.Eventf(pdb, corev1.EventTypeWarning, "ORDSINFO", string(rsaCertPEM)) - r.Recorder.Eventf(pdb, corev1.EventTypeWarning, "ORDSINFO", string(caCert)) - */ - - certificate, err := tls.X509KeyPair([]byte(rsaCertPEM), []byte(rsaKeyPEM)) - if err != nil { - pdb.Status.Msg = "Error tls.X509KeyPair" - return "", err - } - - caCertPool := x509.NewCertPool() - caCertPool.AppendCertsFromPEM(caCert) - - tlsConf := &tls.Config{Certificates: []tls.Certificate{certificate}, RootCAs: caCertPool} - - tr := &http.Transport{TLSClientConfig: tlsConf} - - httpclient := &http.Client{Transport: tr} - - log.Info("Issuing REST call", "URL", url, "Action", action) - - /* - cdb, err := r.getCDBResource(ctx, req, pdb) - if err != nil { - return "", err - } - */ - - err = c.Get(ctx, types.NamespacedName{Name: pdb.Spec.WebServerUsr.Secret.SecretName, Namespace: pdb.Namespace}, secret) - if err != nil { - if apierrors.IsNotFound(err) { - log.Info("Secret not found:" + pdb.Spec.WebServerUsr.Secret.SecretName) - return "", err - } - log.Error(err, "Unable to get the secret.") - return "", err - } - webUserEnc := string(secret.Data[pdb.Spec.WebServerUsr.Secret.Key]) - webUserEnc = strings.TrimSpace(webUserEnc) - - err = c.Get(ctx, types.NamespacedName{Name: pdb.Spec.PDBPriKey.Secret.SecretName, Namespace: pdb.Namespace}, secret) - if err != nil { - if apierrors.IsNotFound(err) { - log.Info("Secret not found:" + pdb.Spec.PDBPriKey.Secret.SecretName) - return "", err - } - log.Error(err, "Unable to get the secret.") - return "", err - } - privKey := string(secret.Data[pdb.Spec.PDBPriKey.Secret.Key]) - webUser, err := lrcommons.CommonDecryptWithPrivKey(privKey, webUserEnc, req) - - // Get Web Server User Password - secret = &corev1.Secret{} - err = c.Get(ctx, types.NamespacedName{Name: pdb.Spec.WebServerPwd.Secret.SecretName, Namespace: pdb.Namespace}, secret) - if err != nil { - if apierrors.IsNotFound(err) { - log.Info("Secret not found:" + pdb.Spec.WebServerPwd.Secret.SecretName) - return "", err - } - log.Error(err, "Unable to get the secret.") - return "", err - } - webUserPwdEnc := string(secret.Data[pdb.Spec.WebServerPwd.Secret.Key]) - webUserPwdEnc = strings.TrimSpace(webUserPwdEnc) - webUserPwd, err := lrcommons.CommonDecryptWithPrivKey(privKey, webUserPwdEnc, req) - /////////////////////////////////////////////////////////////////////////////////// - - var httpreq *http.Request - if action == "GET" { - httpreq, err = http.NewRequest(action, url, nil) - } else { - jsonValue, _ := json.Marshal(payload) - httpreq, err = http.NewRequest(action, url, bytes.NewBuffer(jsonValue)) - } - - if err != nil { - log.Info("Unable to create HTTP Request for PDB : "+pdb.Name, "err", err.Error()) - return "", err - } - - httpreq.Header.Add("Accept", "application/json") - httpreq.Header.Add("Content-Type", "application/json") - httpreq.SetBasicAuth(webUser, webUserPwd) - - resp, err := httpclient.Do(httpreq) - if err != nil { - errmsg := err.Error() - log.Error(err, "Failed - Could not connect to ORDS Pod", "err", err.Error()) - pdb.Status.Msg = "Error: Could not connect to ORDS Pod" - e.Eventf(pdb, corev1.EventTypeWarning, "ORDSError", errmsg) - return "", err - } - - e.Eventf(pdb, corev1.EventTypeWarning, "Done", pdb.Spec.CDBResName) - if resp.StatusCode != http.StatusOK { - bb, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode == 404 { - pdb.Status.ConnString = "" - pdb.Status.Msg = pdb.Spec.PDBName + " not found" - - } else { - if floodcontrol == false { - pdb.Status.Msg = "ORDS Error - HTTP Status Code:" + strconv.Itoa(resp.StatusCode) - } - } - - if floodcontrol == false { - log.Info("ORDS Error - HTTP Status Code :"+strconv.Itoa(resp.StatusCode), "Err", string(bb)) - } - - var apiErr ORDSError - json.Unmarshal([]byte(bb), &apiErr) - if floodcontrol == false { - e.Eventf(pdb, corev1.EventTypeWarning, "ORDSError", "Failed: %s", apiErr.Message) - } - //fmt.Printf("%+v", apiErr) - //fmt.Println(string(bb)) - floodcontrol = true - return "", errors.New("ORDS Error") - } - floodcontrol = false - - defer resp.Body.Close() - - bodyBytes, err := ioutil.ReadAll(resp.Body) - if err != nil { - fmt.Print(err.Error()) - } - respData := string(bodyBytes) - //fmt.Println(string(bodyBytes)) - - var apiResponse RESTSQLCollection - json.Unmarshal([]byte(bodyBytes), &apiResponse) - //fmt.Printf("%#v", apiResponse) - //fmt.Printf("%+v", apiResponse) - - errFound := false - for _, sqlItem := range apiResponse.Items { - if sqlItem.ErrorDetails != "" { - log.Info("ORDS Error - Oracle Error Code :" + strconv.Itoa(sqlItem.ErrorCode)) - if !errFound { - pdb.Status.Msg = sqlItem.ErrorDetails - } - e.Eventf(pdb, corev1.EventTypeWarning, "OraError", "%s", sqlItem.ErrorDetails) - errFound = true - } - } - - if errFound { - return "", errors.New("Oracle Error") - } - - return respData, nil -} diff --git a/controllers/database/shardingdatabase_controller.go b/controllers/database/shardingdatabase_controller.go index 1ec77253..3d3ca021 100644 --- a/controllers/database/shardingdatabase_controller.go +++ b/controllers/database/shardingdatabase_controller.go @@ -48,8 +48,6 @@ import ( "time" "github.com/go-logr/logr" - "github.com/oracle/oci-go-sdk/v65/common" - "github.com/oracle/oci-go-sdk/v65/ons" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" @@ -71,15 +69,6 @@ import ( shardingv1 "github.com/oracle/oracle-database-operator/commons/sharding" ) -// Struct keeping Oracle Notification Server Info -type OnsStatus struct { - Topicid string `json:"topicid,omitempty"` - Instance *databasev4.ShardingDatabase `json:"instance,omitempty"` - OnsProvider common.ConfigurationProvider `json:"onsProvider,omitempty"` - OnsProviderFlag bool `json:"onsProviderFlag,omitempty"` - Rclient ons.NotificationDataPlaneClient `json:"rclient,omitempty"` -} - // ShardingDatabaseReconciler reconciles a ShardingDatabase object type ShardingDatabaseReconciler struct { client.Client @@ -88,14 +77,10 @@ type ShardingDatabaseReconciler struct { kubeClient kubernetes.Interface kubeConfig clientcmd.ClientConfig Recorder record.EventRecorder - InCluster bool - Namespace string } -var sentFailMsg = make(map[string]bool) -var sentCompleteMsg = make(map[string]bool) - -var oshMap = make(map[string]*OnsStatus) +var exportedTDEKeys bool = false +var importedTDEKeys []bool // +kubebuilder:rbac:groups=database.oracle.com,resources=shardingdatabases,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=database.oracle.com,resources=shardingdatabases/status,verbs=get;update;patch @@ -147,7 +132,7 @@ func (r *ShardingDatabaseReconciler) Reconcile(ctx context.Context, req ctrl.Req } // Fetch the ProvShard instance instance := &databasev4.ShardingDatabase{} - err = r.Client.Get(context.TODO(), req.NamespacedName, instance) + err = r.Get(ctx, types.NamespacedName{Namespace: req.Namespace, Name: req.Name}, instance) if err != nil { if errors.IsNotFound(err) { // Request object not found, could have been deleted after reconcile req. @@ -158,12 +143,6 @@ func (r *ShardingDatabaseReconciler) Reconcile(ctx context.Context, req ctrl.Req // Error reading the object - requeue the req. return ctrl.Result{}, err } - - instFlag := r.checkProvInstance(instance) - if !instFlag { - oshMap[instance.Name] = &OnsStatus{} - oshMap[instance.Name].Instance = instance - } defer r.setCrdLifeCycleState(instance, &result, &err, &stateType) defer r.updateShardTopologyStatus(instance) // =============================== Check Deletion TimeStamp======== @@ -182,23 +161,17 @@ func (r *ShardingDatabaseReconciler) Reconcile(ctx context.Context, req ctrl.Req } } - // ======== Setting the flag and Index to be used later in this function ======== - // instFlag = r.checkProvInstance(instance) - // if !instFlag { - //r.setCrdLifeCycleState(instance, &result, &err, stateType) - //// result = resultNq - // return result, fmt.Errorf("DId not find the instance in checkProvInstance") - // } - - // ================================ OCI Notification Provider =========== - r.getOnsConfigProvider(instance) - - // =============================== Checking Namespace ============== + if len(importedTDEKeys) == 0 { + importedTDEKeys = make([]bool, int32(len(instance.Spec.Shard)), int32(len(instance.Spec.Shard))) + for i = 0; i < int32(len(instance.Spec.Shard)); i++ { + importedTDEKeys[i] = false + shardingv1.LogMessages("INFO", "Initializing importedTDEKeys to false", nil, instance, r.Log) + } + } // ======================== Validate Specs ============== err = r.validateSpex(instance) if err != nil { - //r.setCrdLifeCycleState(instance, &result, &err, stateType) result = resultNq return result, err } @@ -384,6 +357,13 @@ func (r *ShardingDatabaseReconciler) Reconcile(ctx context.Context, req ctrl.Req result = resultNq return result, err } + + if shardingv1.CheckIsTDEWalletFlag(instance, r.Log) && !exportedTDEKeys { + exportTDEfname := "expTDEFile" + shardingv1.LogMessages("INFO", "Catalog calling ExportTDEKey", nil, instance, r.Log) + shardingv1.ExportTDEKey(OraCatalogSpex.Name+"-0", exportTDEfname, instance, r.kubeClient, r.kubeConfig, r.Log) + exportedTDEKeys = true + } } // ====================== Update Setup for Shard ============================== @@ -403,6 +383,14 @@ func (r *ShardingDatabaseReconciler) Reconcile(ctx context.Context, req ctrl.Req return result, err } } + if shardingv1.CheckIsTDEWalletFlag(instance, r.Log) && exportedTDEKeys { + importTDEfname := "impTDEFile" + shardingv1.LogMessages("INFO", "Calling ImportTDEKey()", nil, instance, r.Log) + if !importedTDEKeys[i] { + shardingv1.ImportTDEKey(OraShardSpex.Name+"-0", importTDEfname, instance, r.kubeClient, r.kubeConfig, r.Log) + } + importedTDEKeys[i] = true + } } // ====================== Update Setup for Gsm ============================== @@ -455,51 +443,32 @@ func (r *ShardingDatabaseReconciler) eventFilterPredicate() predicate.Predicate return true }, UpdateFunc: func(e event.UpdateEvent) bool { - instance := &databasev4.ShardingDatabase{} - if old, ok := e.ObjectOld.(*corev1.Secret); ok { - if new, ok := e.ObjectNew.(*corev1.Secret); ok { - oshInst := instance - if (new.Name == oshInst.Spec.DbSecret.Name) && (new.Name == old.Name) { - _, ok := old.Data[oshInst.Spec.DbSecret.PwdFileName] - if ok { - if !reflect.DeepEqual(old.Data[oshInst.Spec.DbSecret.PwdFileName], new.Data[oshInst.Spec.DbSecret.PwdFileName]) { - shardingv1.LogMessages("INFO", "Secret Changed", nil, oshInst, r.Log) - } - } - shardingv1.LogMessages("INFO", "Secret update block", nil, oshInst, r.Log) - } - } - } return true }, DeleteFunc: func(e event.DeleteEvent) bool { - instance := &databasev4.ShardingDatabase{} - _, podOk := e.Object.GetLabels()["statefulset.kubernetes.io/pod-name"] - if oshMap[instance.Name] != nil { - oshInst := instance - if instance.DeletionTimestamp == nil { - - if e.Object.GetLabels()[string(databasev4.ShardingDelLabelKey)] == string(databasev4.ShardingDelLabelTrueValue) { - } - - if podOk { - delObj := e.Object.(*corev1.Pod) - if e.Object.GetLabels()["type"] == "Shard" && e.Object.GetLabels()["app"] == "OracleSharding" && e.Object.GetLabels()["oralabel"] == oshInst.Name { - - if delObj.DeletionTimestamp != nil { - go r.gsmInvitedNodeOp(oshInst, delObj.Name) - } - } - - if e.Object.GetLabels()["type"] == "Catalog" && e.Object.GetLabels()["app"] == "OracleSharding" && e.Object.GetLabels()["oralabel"] == oshInst.Name { - - if delObj.DeletionTimestamp != nil { - go r.gsmInvitedNodeOp(oshInst, delObj.Name) - } - } - } - } - } + // //instance := &databasev4.ShardingDatabase{} + // _, podOk := e.Object.GetLabels()["statefulset.kubernetes.io/pod-name"] + // instance, _ := e.Object.DeepCopyObject().(*databasev4.ShardingDatabase) + // if e.Object.GetDeletionTimestamp() == nil { + // if e.Object.GetLabels()[string(databasev4.ShardingDelLabelKey)] == string(databasev4.ShardingDelLabelTrueValue) { + // } + // if podOk { + // delObj := e.Object.(*corev1.Pod) + // if e.Object.GetLabels()["type"] == "Shard" && e.Object.GetLabels()["app"] == "OracleSharding" && e.Object.GetLabels()["oralabel"] == instance.Name { + // if delObj.DeletionTimestamp != nil { + // go r.gsmInvitedNodeOp(instance, delObj.Name) + // } + // } + + // if e.Object.GetLabels()["type"] == "Catalog" && e.Object.GetLabels()["app"] == "OracleSharding" && e.Object.GetLabels()["oralabel"] == instance.Name { + + // if delObj.DeletionTimestamp != nil { + // go r.gsmInvitedNodeOp(instance, delObj.Name) + // } + // } + // } + + // } return true }, } @@ -524,59 +493,6 @@ func (r *ShardingDatabaseReconciler) UpdateSecret(instance *databasev4.ShardingD return ctrl.Result{}, nil } -// ================== Function to get the Notification controller ============== -func (r *ShardingDatabaseReconciler) getOnsConfigProvider(instance *databasev4.ShardingDatabase) { - var err error - if instance.Spec.DbSecret.NsConfigMap != "" && instance.Spec.DbSecret.NsSecret != "" && oshMap[instance.Name].OnsProviderFlag != true { - cmName := instance.Spec.DbSecret.NsConfigMap - secName := instance.Spec.DbSecret.NsSecret - shardingv1.LogMessages("DEBUG", "Received parameters are "+shardingv1.GetFmtStr(cmName)+","+shardingv1.GetFmtStr(secName), nil, instance, r.Log) - region, user, tenancy, passphrase, fingerprint, topicid := shardingv1.ReadConfigMap(cmName, instance, r.Client, r.Log) - privatekey := shardingv1.ReadSecret(secName, instance, r.Client, r.Log) - - oshMap[instance.Name].Topicid = topicid - oshMap[instance.Name].OnsProvider = common.NewRawConfigurationProvider(tenancy, user, region, fingerprint, privatekey, &passphrase) - //VV instance.Spec.TopicId = topicid - oshMap[instance.Name].Rclient, err = ons.NewNotificationDataPlaneClientWithConfigurationProvider(oshMap[instance.Name].OnsProvider) - if err != nil { - msg := "Error occurred in getting the OCI notification service based client." - oshMap[instance.Name].OnsProviderFlag = false - r.Log.Error(err, msg) - shardingv1.LogMessages("Error", msg, nil, instance, r.Log) - } else { - oshMap[instance.Name].OnsProviderFlag = true - } - } -} - -func (r ShardingDatabaseReconciler) marshalOnsInfo(instance *databasev4.ShardingDatabase) (OnsStatus, error) { - onsData := OnsStatus{} - specBytes, err := instance.GetLastSuccessfulOnsInfo() - if err != nil { - shardingv1.LogMessages("Error", "error occurred while getting the data from getLastSuccessfulOnsInfo", nil, instance, r.Log) - return onsData, err - } else { - shardingv1.LogMessages("Error", "error occurred while getting the data from getLastSuccessfulOnsInfo and unmarshaling the object", nil, instance, r.Log) - err := json.Unmarshal(specBytes, &onsData) - if err != nil { - return onsData, err - } - } - return onsData, nil -} - -// ================== Function the Message ============== -func (r *ShardingDatabaseReconciler) sendMessage(instance *databasev4.ShardingDatabase, title string, body string) { - instFlag := r.checkProvInstance(instance) - if instFlag { - shardingv1.LogMessages("INFO", "sendMessage():instFlag true", nil, instance, r.Log) - if oshMap[instance.Name].OnsProviderFlag { - shardingv1.LogMessages("INFO", "sendMessage():OnsProviderFlag true", nil, instance, r.Log) - shardingv1.SendNotification(title, body, instance, oshMap[instance.Name].Topicid, oshMap[instance.Name].Rclient, r.Log) - } - } -} - func (r *ShardingDatabaseReconciler) publishEvents(instance *databasev4.ShardingDatabase, eventMsg string, state string) { if state == string(databasev4.AvailableState) || state == string(databasev4.AddingShardState) || state == string(databasev4.ShardOnlineState) || state == string(databasev4.ProvisionState) || state == string(databasev4.DeletingState) || state == string(databasev4.Terminated) { @@ -649,8 +565,6 @@ func (r *ShardingDatabaseReconciler) finalizeShardingDatabase(instance *database var i int32 var err error var pvcName string - - r.checkProvInstance(instance) sfSetFound := &appsv1.StatefulSet{} svcFound := &corev1.Service{} if len(instance.Spec.Shard) > 0 { @@ -839,27 +753,9 @@ func (r *ShardingDatabaseReconciler) finalizeShardingDatabase(instance *database } } - oshMap[instance.Name].Instance = &databasev4.ShardingDatabase{} - return nil } -// Get the current instance -func (r *ShardingDatabaseReconciler) checkProvInstance(instance *databasev4.ShardingDatabase, -) bool { - - var status bool = false - if oshMap[instance.Name] != nil { - title := "checkProvInstance()" - message := "oshMap.Instance.Name=[" + oshMap[instance.Name].Instance.Name + "]. instance.Name=[" + instance.Name + "]." - shardingv1.LogMessages("INFO", title+":"+message, nil, instance, r.Log) - if oshMap[instance.Name].Instance.Name == instance.Name { - status = true - } - } - return status -} - // =========== validate Specs ============ func (r *ShardingDatabaseReconciler) validateSpex(instance *databasev4.ShardingDatabase) error { @@ -1567,14 +1463,7 @@ func (r *ShardingDatabaseReconciler) addPrimaryShards(instance *databasev4.Shard if err != nil { r.updateGsmShardStatus(instance, OraShardSpex.Name, string(databasev4.AddingShardErrorState)) title = instance.Namespace + ":Shard Addition Failure" - message = "TopicId:" + oshMap[instance.Name].Topicid + ":Error occurred during shard " + shardingv1.GetFmtStr(OraShardSpex.Name) + " addition." shardingv1.LogMessages("Error", title+":"+message, nil, instance, r.Log) - msgKey := instance.Namespace + "-" + OraShardSpex.Name - if sentFailMsg[msgKey] != true { - r.sendMessage(instance, title, message) - } - sentFailMsg[msgKey] = true - sentCompleteMsg[msgKey] = false deployFlag = false } } @@ -1624,15 +1513,8 @@ func (r *ShardingDatabaseReconciler) verifyShards(instance *databasev4.ShardingD // Following logic will sent a email only once if oldStateStr != string(databasev4.ShardOnlineState) { title = instance.Namespace + ":Shard Addition Completed" - message = "TopicId:" + oshMap[instance.Name].Topicid + ":Shard addition completed for shard " + shardingv1.GetFmtStr(shardSfSet.Name) + " in GSM." + message = ":Shard addition completed for shard " + shardingv1.GetFmtStr(shardSfSet.Name) + " in GSM." shardingv1.LogMessages("INFO", title+":"+message, nil, instance, r.Log) - msgKey := instance.Namespace + "-" + shardSfSet.Name - if sentCompleteMsg[msgKey] != true { - r.sendMessage(instance, title, message) - } - - sentCompleteMsg[msgKey] = true - sentFailMsg[msgKey] = false } return nil } @@ -1653,8 +1535,6 @@ func (r *ShardingDatabaseReconciler) delGsmShard(instance *databasev4.ShardingDa //gsmSfSet := &appsv1.StatefulSet{} gsmPod := &corev1.Pod{} var msg string - var title string - var message string var setLifeCycleFlag = false shardingv1.LogMessages("DEBUG", "Starting shard deletion operation.", nil, instance, r.Log) @@ -1716,9 +1596,6 @@ func (r *ShardingDatabaseReconciler) delGsmShard(instance *databasev4.ShardingDa err = shardingv1.MoveChunks(gsmPod.Name, sparams, instance, r.kubeClient, r.kubeConfig, r.Log) if err != nil { r.updateGsmShardStatus(instance, OraShardSpex.Name, string(databasev4.ChunkMoveError)) - title = "Chunk Movement Failure" - message = "Error occurred during chunk movement in shard " + shardingv1.GetFmtStr(OraShardSpex.Name) + " deletion." - r.sendMessage(instance, title, message) instance.Spec.Shard[i].IsDelete = "failed" err = shardingv1.InstanceShardPatch(instance, instance, r.Client, i, "isDelete", "failed") if err != nil { @@ -1776,9 +1653,6 @@ func (r *ShardingDatabaseReconciler) delGsmShard(instance *databasev4.ShardingDa r.delShard(instance, shardSfSet.Name, shardSfSet, shardPod, int(i)) r.updateGsmShardStatus(instance, OraShardSpex.Name, string(databasev4.Terminated)) r.updateShardStatus(instance, int(i), string(databasev4.Terminated)) - title = "Shard Deletion Completed" - message = "Shard deletion completed for shard " + shardingv1.GetFmtStr(OraShardSpex.Name) + " in GSM." - r.sendMessage(instance, title, message) } } } diff --git a/controllers/database/singleinstancedatabase_controller.go b/controllers/database/singleinstancedatabase_controller.go index 13f2ec6f..b9352d83 100644 --- a/controllers/database/singleinstancedatabase_controller.go +++ b/controllers/database/singleinstancedatabase_controller.go @@ -3241,6 +3241,7 @@ func (r *SingleInstanceDatabaseReconciler) manageConvPhysicalToSnapshot(ctx cont if err != nil { return requeueY, err } + if sidbReadyPod.Name == "" { log.Info("No ready Pod for the requested singleinstancedatabase") return requeueY, nil @@ -3248,25 +3249,30 @@ func (r *SingleInstanceDatabaseReconciler) manageConvPhysicalToSnapshot(ctx cont if singleInstanceDatabase.Spec.ConvertToSnapshotStandby { // Convert a PHYSICAL_STANDBY -> SNAPSHOT_STANDBY - singleInstanceDatabase.Status.Status = dbcommons.StatusUpdating + if singleInstanceDatabase.Status.Status != dbcommons.StatusPending { + singleInstanceDatabase.Status.Status = dbcommons.StatusUpdating + } + r.Status().Update(ctx, &singleInstanceDatabase) if err := convertPhysicalStdToSnapshotStdDB(r, &singleInstanceDatabase, &sidbReadyPod, ctx, req); err != nil { + singleInstanceDatabase.Status.Status = dbcommons.StatusPending + r.Status().Update(ctx, &singleInstanceDatabase) switch err { case ErrNotPhysicalStandby: - r.Recorder.Event(&singleInstanceDatabase, corev1.EventTypeWarning, "Conversion to Snapshot Standby Not allowed", "Database not in physical standby role") - log.Info("Conversion to Snapshot Standby not allowed as database not in physical standby role") + r.Recorder.Event(&singleInstanceDatabase, corev1.EventTypeWarning, "Error: Conversion to Snapshot Standby Not allowed", "Database not in physical standby role") + log.Info("Error: Conversion to Snapshot Standby not allowed as database not in physical standby role") return requeueY, nil case ErrDBNotConfiguredWithDG: // cannot convert to snapshot database - r.Recorder.Event(&singleInstanceDatabase, corev1.EventTypeWarning, "Conversion to Snapshot Standby Not allowed", "Database is not configured with dataguard") + r.Recorder.Event(&singleInstanceDatabase, corev1.EventTypeWarning, "Error: Conversion to Snapshot Standby Not allowed", "Database is not configured with dataguard") log.Info("Conversion to Snapshot Standby not allowed as requested database is not configured with dataguard") return requeueY, nil case ErrFSFOEnabledForDGConfig: - r.Recorder.Event(&singleInstanceDatabase, corev1.EventTypeWarning, "Conversion to Snapshot Standby Not allowed", "Database is a FastStartFailover target") + r.Recorder.Event(&singleInstanceDatabase, corev1.EventTypeWarning, "Error: Conversion to Snapshot Standby Not allowed", "Database is a FastStartFailover target") log.Info("Conversion to Snapshot Standby Not allowed as database is a FastStartFailover target") return requeueY, nil case ErrAdminPasswordSecretNotFound: - r.Recorder.Event(&singleInstanceDatabase, corev1.EventTypeWarning, "Admin Password", "Database admin password secret not found") + r.Recorder.Event(&singleInstanceDatabase, corev1.EventTypeWarning, "Error: Admin Password", "Database admin password secret not found") log.Info("Database admin password secret not found") return requeueY, nil default: diff --git a/controllers/dataguard/dataguard_utils.go b/controllers/dataguard/dataguard_utils.go index 4c16f82b..a528dbdb 100644 --- a/controllers/dataguard/dataguard_utils.go +++ b/controllers/dataguard/dataguard_utils.go @@ -545,6 +545,8 @@ func patchService(r *DataguardBrokerReconciler, broker *dbapi.DataguardBroker, c lbAddress = svc.Status.LoadBalancer.Ingress[0].IP } broker.Status.ExternalConnectString = lbAddress + ":" + fmt.Sprint(svc.Spec.Ports[0].Port) + "/DATAGUARD" + } else { + return errors.New("load balancer ingress IP not available") } } else { nodeip := dbcommons.GetNodeIp(r, ctx, req) diff --git a/controllers/dataguard/dataguardbroker_controller.go b/controllers/dataguard/dataguardbroker_controller.go index 4d7ae044..6ed4e4d8 100644 --- a/controllers/dataguard/dataguardbroker_controller.go +++ b/controllers/dataguard/dataguardbroker_controller.go @@ -202,7 +202,7 @@ func (r *DataguardBrokerReconciler) Reconcile(ctx context.Context, req ctrl.Requ // Update Status for broker and sidb resources if err := updateReconcileStatus(r, &dataguardBroker, ctx, req); err != nil { - return ctrl.Result{Requeue: false}, err + return ctrl.Result{Requeue: true, RequeueAfter: 30 * time.Second}, err } dataguardBroker.Status.Status = dbcommons.StatusReady diff --git a/controllers/observability/databaseobserver_controller.go b/controllers/observability/databaseobserver_controller.go index e17ee0b3..468f35c5 100644 --- a/controllers/observability/databaseobserver_controller.go +++ b/controllers/observability/databaseobserver_controller.go @@ -197,7 +197,7 @@ func (r *DatabaseObserverReconciler) initialize(ctx context.Context, a *api.Data }) a.Status.Status = string(constants.StatusObservabilityPending) - a.Status.ExporterConfig = constants.UnknownValue + a.Status.MetricsConfig = constants.UnknownValue a.Status.Version = constants.UnknownValue if e := r.Status().Update(ctx, a); e != nil { r.Log.WithName(constants.LogReconcile).Error(e, constants.ErrorStatusUpdate) @@ -212,42 +212,74 @@ func (r *DatabaseObserverReconciler) initialize(ctx context.Context, a *api.Data // validateSpecs method checks the values and secrets passed in the spec func (r *DatabaseObserverReconciler) validateSpecs(a *api.DatabaseObserver) error { - // If either Vault Fields are empty, then assume a DBPassword secret is supplied. If the DBPassword secret not found, then error out - if a.Spec.Database.DBPassword.VaultOCID == "" || a.Spec.Database.DBPassword.VaultSecretName == "" { - dbSecret := &corev1.Secret{} - if e := r.Get(context.TODO(), types.NamespacedName{Name: a.Spec.Database.DBPassword.SecretName, Namespace: a.Namespace}, dbSecret); e != nil { - r.Recorder.Event(a, corev1.EventTypeWarning, constants.EventReasonSpecError, constants.EventMessageSpecErrorDBPasswordSecretMissing) + // if Database Wallet is defined, validate resource + + // if Sidecar volumes set, validate volumeSource + + // if Custom Exporter is defined, validate resource + if exporterConfig := a.Spec.ExporterConfig.ConfigMap.Name; exporterConfig != "" { + configMap := &corev1.ConfigMap{} + resource := types.NamespacedName{Name: exporterConfig, Namespace: a.Namespace} + + if e := r.Get(context.TODO(), resource, configMap); e != nil { + r.Recorder.Event(a, corev1.EventTypeWarning, constants.EventReasonSpecError, constants.EventMessageSpecErrorConfigMapSpecifiedMissing) + return e + } + } + + // if metrics custom is set, check configMaps + for _, cm := range a.Spec.Metrics.Configmap { + configMap := &corev1.ConfigMap{} + resource := types.NamespacedName{Name: cm.Name, Namespace: a.Namespace} + + if e := r.Get(context.TODO(), resource, configMap); e != nil { + r.Recorder.Event(a, corev1.EventTypeWarning, constants.EventReasonSpecError, constants.EventMessageSpecErrorConfigMapSpecifiedMissing) return e } } + checked := map[string]bool{} // Does DB Connection String Secret Name actually exist - dbConnectSecret := &corev1.Secret{} - if e := r.Get(context.TODO(), types.NamespacedName{Name: a.Spec.Database.DBConnectionString.SecretName, Namespace: a.Namespace}, dbConnectSecret); e != nil { - r.Recorder.Event(a, corev1.EventTypeWarning, constants.EventReasonSpecError, constants.EventMessageSpecErrorDBConnectionStringSecretMissing) - return e + if connectionString := a.Spec.Database.DBConnectionString.SecretName; connectionString != "" { + secret := &corev1.Secret{} + resource := types.NamespacedName{Name: connectionString, Namespace: a.Namespace} + + if e := r.Get(context.TODO(), resource, secret); e != nil { + r.Recorder.Event(a, corev1.EventTypeWarning, constants.EventReasonSpecError, constants.EventMessageSpecErrorDBConnectionStringSecretMissing) + return e + } + checked[connectionString] = true } // Does DB User String Secret Name actually exist - dbUserSecret := &corev1.Secret{} - if e := r.Get(context.TODO(), types.NamespacedName{Name: a.Spec.Database.DBUser.SecretName, Namespace: a.Namespace}, dbUserSecret); e != nil { - r.Recorder.Event(a, corev1.EventTypeWarning, constants.EventReasonSpecError, constants.EventMessageSpecErrorDBPUserSecretMissing) - return e + if usernameString := a.Spec.Database.DBUser.SecretName; usernameString != "" && !checked[usernameString] { + secret := &corev1.Secret{} + resource := types.NamespacedName{Name: usernameString, Namespace: a.Namespace} + + if e := r.Get(context.TODO(), resource, secret); e != nil { + r.Recorder.Event(a, corev1.EventTypeWarning, constants.EventReasonSpecError, constants.EventMessageSpecErrorDBPUserSecretMissing) + return e + } + checked[usernameString] = true } - // Does a custom configuration configmap actually exist, if provided - if configurationCMName := a.Spec.ExporterConfig.Configmap.Name; configurationCMName != "" { - configurationCM := &corev1.ConfigMap{} - if e := r.Get(context.TODO(), types.NamespacedName{Name: configurationCMName, Namespace: a.Namespace}, configurationCM); e != nil { - r.Recorder.Event(a, corev1.EventTypeWarning, constants.EventReasonSpecError, constants.EventMessageSpecErrorConfigmapMissing) + // Does DB Password String Secret Name actually exist + if passwordString := a.Spec.Database.DBPassword.SecretName; passwordString != "" && !checked[passwordString] { + secret := &corev1.Secret{} + resource := types.NamespacedName{Name: passwordString, Namespace: a.Namespace} + + if e := r.Get(context.TODO(), resource, secret); e != nil { + r.Recorder.Event(a, corev1.EventTypeWarning, constants.EventReasonSpecError, constants.EventMessageSpecErrorDBPwdSecretMissing) return e } + checked[passwordString] = true } // Does DBWallet actually exist, if provided - if dbWalletSecretName := a.Spec.Database.DBWallet.SecretName; dbWalletSecretName != "" { - dbWalletSecret := &corev1.Secret{} - if e := r.Get(context.TODO(), types.NamespacedName{Name: dbWalletSecretName, Namespace: a.Namespace}, dbWalletSecret); e != nil { + if walletString := a.Spec.Wallet.SecretName; walletString != "" { + secret := &corev1.Secret{} + resource := types.NamespacedName{Name: walletString, Namespace: a.Namespace} + if e := r.Get(context.TODO(), resource, secret); e != nil { r.Recorder.Event(a, corev1.EventTypeWarning, constants.EventReasonSpecError, constants.EventMessageSpecErrorDBWalletSecretMissing) return e } @@ -446,7 +478,7 @@ func (r *DatabaseObserverReconciler) validateDeploymentReadiness(a *api.Database Message: constants.MessageExporterDeploymentSuccessful, }) a.Status.Version = constants.GetExporterVersion(a) - a.Status.ExporterConfig = constants.GetExporterConfig(a) + a.Status.MetricsConfig = constants.GetMetricsConfig(a) return ctrl.Result{}, nil } @@ -471,6 +503,7 @@ func (r *DatabaseObserverReconciler) validateCustomResourceReadiness(ctx context Message: constants.MessageCRValidationWaiting, }) a.Status.Status = string(constants.StatusObservabilityPending) + } else if meta.IsStatusConditionFalse(a.Status.Conditions, constants.IsExporterDeploymentReady) || meta.IsStatusConditionFalse(a.Status.Conditions, constants.IsExporterServiceReady) || meta.IsStatusConditionFalse(a.Status.Conditions, constants.IsExporterServiceMonitorReady) { diff --git a/controllers/observability/databaseobserver_resource.go b/controllers/observability/databaseobserver_resource.go index 6be6f693..82febe43 100644 --- a/controllers/observability/databaseobserver_resource.go +++ b/controllers/observability/databaseobserver_resource.go @@ -40,8 +40,8 @@ func (resource *ObservabilityDeploymentResource) generate(a *api.DatabaseObserve rReplicas := constants.GetExporterReplicas(a) rEnvs := constants.GetExporterEnvs(a) - rLabels := constants.GetLabels(a, a.Spec.Exporter.Deployment.Labels) - rPodLabels := constants.GetLabels(a, a.Spec.Exporter.Deployment.DeploymentPodTemplate.Labels) + rLabels := constants.GetLabels(a, a.Spec.Deployment.Labels) + rPodLabels := constants.GetLabels(a, a.Spec.Deployment.DeploymentPodTemplate.Labels) rSelector := constants.GetSelectorLabel(a) rDeploymentSecurityContext := constants.GetExporterDeploymentSecurityContext(a) @@ -108,7 +108,7 @@ func (resource *ObservabilityDeploymentResource) generate(a *api.DatabaseObserve func (resource *ObservabilityServiceResource) generate(a *api.DatabaseObserver, scheme *runtime.Scheme) (*unstructured.Unstructured, error) { rServiceName := a.Name - rLabels := constants.GetLabels(a, a.Spec.Exporter.Service.Labels) + rLabels := constants.GetLabels(a, a.Spec.Service.Labels) rSelector := constants.GetSelectorLabel(a) rPorts := constants.GetExporterServicePort(a) @@ -141,7 +141,7 @@ func (resource *ObservabilityServiceMonitorResource) generate(a *api.DatabaseObs rEndpoints := constants.GetEndpoints(a) rSelector := constants.GetSelectorLabel(a) - rLabels := constants.GetLabels(a, a.Spec.Prometheus.ServiceMonitor.Labels) + rLabels := constants.GetLabels(a, a.Spec.ServiceMonitor.Labels) smSpec := monitorv1.ServiceMonitorSpec{ Endpoints: rEndpoints, diff --git a/docs/adb/ACD.md b/docs/adb/ACD.md index 81ee1a65..5f06eb56 100644 --- a/docs/adb/ACD.md +++ b/docs/adb/ACD.md @@ -1,10 +1,10 @@ # Managing Oracle Autonomous Container Databases on Dedicated Exadata Infrastructure -Oracle Database Operator for Kubernetes (`OraOperator`) includes the Oracle Autonomous Container Database Controller. Autonomous Container Database is one of the resources of Oracle Autonomous Database dedicated Exadata infrastructure feature. You can create multiple Autonomous Container Database resources in a single Autonomous Exadata VM Cluster resource, but you must create at least one before you can create any Autonomous Databases. +Oracle Database Operator for Kubernetes (`OraOperator`) includes the Oracle Autonomous Container Database Controller. Autonomous Container Database is one of the resources of the Oracle Autonomous Database on Dedicated Exadata Infrastructure feature. You can create multiple Autonomous Container Database resources in a single Autonomous Exadata VM Cluster resource, but you must create at least one before you can create any Autonomous Databases. -Before you use the Oracle Database Operator for Kubernetes (the operator), ensure your system meets all of the Oracle Autonomous Database (ADB) Prerequisites [ADB_PREREQUISITES](./../adb/ADB_PREREQUISITES.md). +Before you use the Oracle Database Operator for Kubernetes (the operator), ensure that your system meets all of the Oracle Autonomous Database (ADB) Prerequisites [ADB_PREREQUISITES](./../adb/ADB_PREREQUISITES.md). -As indicated in the prerequisites (see above), to interact with OCI services, either the cluster has to be authorized using Principal Instance, or using the API Key Authentication by specifying the configMap and the secret under the `ociConfig` field. +As indicated in the prerequisites (see above), to interact with OCI services, either the cluster must be authorized by using Principal Instance, or by using the API Key Authentication and specifying the configMap and the secret under the `ociConfig` field. ## Required Permissions diff --git a/docs/adb/ADB_LONG_TERM_BACKUP.md b/docs/adb/ADB_LONG_TERM_BACKUP.md index 312dac0d..37c8611e 100644 --- a/docs/adb/ADB_LONG_TERM_BACKUP.md +++ b/docs/adb/ADB_LONG_TERM_BACKUP.md @@ -14,8 +14,8 @@ To back up an Autonomous Database, complete this procedure. | `spec.displayName` | string | The user-friendly name for the backup. This name does not have to be unique. | Yes | | `spec.isLongTermBackup` | boolean | Indicates whether the backup is long-term. | Yes | | `spec.retentionPeriodInDays` | string | Retention period, in days, for long-term backups. Minimum retention period is 90 days. | Yes | - | `spec.target.k8sADB.name` | string | The name of custom resource of the target Autonomous Database. Choose either the `spec.target.k8sADB.name` or the `spec.target.ociADB.ocid`, but not both. | Conditional | - | `spec.target.ociADB.ocid` | string | The [OCID](https://docs.cloud.oracle.com/Content/General/Concepts/identifiers.htm) of the target AutonomousDatabase. Choose either the `spec.target.k8sADB.name` or the `spec.target.ociADB.ocid`, but not both. | Conditional | + | `spec.target.k8sAdb.name` | string | The name of custom resource of the target Autonomous Database. Choose either the `spec.target.k8sAdb.name` or the `spec.target.ociAdb.id`, but not both. | Conditional | + | `spec.target.ociAdb.id` | string | The [OCID](https://docs.cloud.oracle.com/Content/General/Concepts/identifiers.htm) of the target AutonomousDatabase. Choose either the `spec.target.k8sAdb.name` or the `spec.target.ociAdb.id`, but not both. | Conditional | | `spec.ociConfig` | dictionary | Not required when the Operator is authorized with [Instance Principal](./ADB_PREREQUISITES.md#authorized-with-instance-principal). Otherwise, you will need the values from this section: [Authorized with API Key Authentication](./ADB_PREREQUISITES.md#authorized-with-api-key-authentication). | Conditional | | `spec.ociConfig.configMapName` | string | Name of the ConfigMap that holds the local OCI configuration | Conditional | | `spec.ociConfig.secretName`| string | Name of the Kubernetes (K8s) Secret that holds the private key value | Conditional | @@ -28,11 +28,11 @@ To back up an Autonomous Database, complete this procedure. name: autonomousdatabasebackup-sample spec: target: - k8sADB: + k8sAdb: name: autonomousdatabase-sample # # Uncomment the below block if you use ADB OCID as the input of the target ADB - # ociADB: - # ocid: ocid1.autonomousdatabase... + # ociAdb: + # id: ocid1.autonomousdatabase... displayName: autonomousdatabasebackup-sample isLongTermBackup: true retentionPeriodInDays: 90 diff --git a/docs/adb/ADB_RESTORE.md b/docs/adb/ADB_RESTORE.md index 7a80090a..9d607787 100644 --- a/docs/adb/ADB_RESTORE.md +++ b/docs/adb/ADB_RESTORE.md @@ -11,10 +11,10 @@ To restore an Autonomous Database from a backup, or by using point-in-time resto 1. Add the following fields to the AutonomousDatabaseBackup resource definition. An example `.yaml` file is available here: [`config/samples/adb/autonomousdatabase_restore.yaml`](./../../config/samples/adb/autonomousdatabase_restore.yaml) | Attribute | Type | Description | Required? | |----|----|----|----| - | `spec.target.k8sADB.name` | string | The name of custom resource of the target Autonomous Database (`AutonomousDatabase`). Choose either the `spec.target.k8sADB.name` or the `spec.target.ociADB.ocid`, but not both. | Conditional | - | `spec.target.ociADB.ocid` | string | The [OCID](https://docs.cloud.oracle.com/Content/General/Concepts/identifiers.htm) of the target `AutonomousDatabase`. Choose either the `spec.target.k8sADB.name` or the `spec.target.ociADB.ocid`, but not both. | Conditional | - | `spec.source.k8sADBBackup.name` | string | The name of custom resource of the `AutonomousDatabaseBackup` that you want to restore from. Choose either the `spec.source.k8sADBBackup.name` or the `spec.source.pointInTime.timestamp`, but not both. | Conditional | - | `spec.source.pointInTime.timestamp` | string | The timestamp to specify the point in time to which you want the database restored. Your Autonomous Database identifies which backup to use for the fastest restore. The timestamp must follow this format: YYYY-MM-DD HH:MM:SS GMT. Choose either the `spec.source.k8sADBBackup.name` or the `spec.source.pointInTime.timestamp`, but not both. | Conditional | + | `spec.target.k8sAdb.name` | string | The name of custom resource of the target Autonomous Database (`AutonomousDatabase`). Choose either the `spec.target.k8sAdb.name` or the `spec.target.ociAdb.id`, but not both. | Conditional | + | `spec.target.ociAdb.id` | string | The [OCID](https://docs.cloud.oracle.com/Content/General/Concepts/identifiers.htm) of the target `AutonomousDatabase`. Choose either the `spec.target.k8sAdb.name` or the `spec.target.ociAdb.id`, but not both. | Conditional | + | `spec.source.k8sAdbBackup.name` | string | The name of custom resource of the `AutonomousDatabaseBackup` that you want to restore from. Choose either the `spec.source.k8sAdbBackup.name` or the `spec.source.pointInTime.timestamp`, but not both. | Conditional | + | `spec.source.pointInTime.timestamp` | string | The timestamp to specify the point in time to which you want the database restored. Your Autonomous Database identifies which backup to use for the fastest restore. The timestamp must follow this format: YYYY-MM-DD HH:MM:SS GMT. Choose either the `spec.source.k8sAdbBackup.name` or the `spec.source.pointInTime.timestamp`, but not both. | Conditional | | `spec.ociConfig` | dictionary | Not required when the Operator is authorized with [Instance Principal](./ADB_PREREQUISITES.md#authorized-with-instance-principal). Otherwise, you will need the values from this section: [Authorized with API Key Authentication](./ADB_PREREQUISITES.md#authorized-with-api-key-authentication). | Conditional | | `spec.ociConfig.configMapName` | string | Name of the `ConfigMap` that holds the local OCI configuration | Conditional | | `spec.ociConfig.secretName`| string | Name of the Kubernetes (K8s) Secret that holds the private key value | Conditional | @@ -27,13 +27,13 @@ To restore an Autonomous Database from a backup, or by using point-in-time resto name: autonomousdatabaserestore-sample spec: target: - k8sADB: + k8sAdb: name: autonomousdatabase-sample # # Uncomment the below block if you use ADB OCID as the input of the target ADB - # ociADB: - # ocid: ocid1.autonomousdatabase... + # ociAdb: + # id: ocid1.autonomousdatabase... source: - k8sADBBackup: + k8sAdbBackup: name: autonomousdatabasebackup-sample # # Uncomment the following field to perform point-in-time restore # pointInTime: diff --git a/docs/adb/NETWORK_ACCESS_OPTIONS.md b/docs/adb/NETWORK_ACCESS_OPTIONS.md index e7eb0a56..e945df04 100644 --- a/docs/adb/NETWORK_ACCESS_OPTIONS.md +++ b/docs/adb/NETWORK_ACCESS_OPTIONS.md @@ -17,7 +17,7 @@ Review the following options available to you with Autonomous Database. ## Configuring Network Access with Allowing Secure Access from Anywhere -Before changing the Network Access to Allowing Secure Access from Anywhere, ensure that your network security protocol requries only mTLS (Mutual TLS) authentication. For more details, see: [Allow both TLS and mutual TLS (mTLS) authentication](#allow-both-tls-and-mutual-tls-mtls-authentication). If mTLS enforcement is already enabled on your Autonomous Database, then you can skip this step. +Before changing the Network Access to Allowing Secure Access from Anywhere, ensure that the enforcement mTLS (Mutual TLS) authentication is set to **true** in your network security protocol. For more details, see: [Allow both TLS and mutual TLS (mTLS) authentication](#allow-both-tls-and-mutual-tls-mtls-authentication). If mTLS enforcement is already enabled on your Autonomous Database, then you can skip this step. To specify that Autonomous Database can be connected from any location with a valid credential, complete one of the following procedures, based on your network access configuration. @@ -58,7 +58,7 @@ To specify that Autonomous Database can be connected from any location with a va | Attribute | Type | Description | |----|----|----| - | `privateEndpointLabel` | string | The hostname prefix for the resource. | + | `privateEndpointLabel` | string | The resource's private endpoint label.
- Setting the endpoint label to a non-empty string creates a private endpoint database.
- Resetting the endpoint label to an empty string, after the creation of the private endpoint database, changes the private endpoint database to a public endpoint database.
- Setting the endpoint label to a non-empty string value, updates to a new private endpoint database, when the database is disabled and re-enabled.

This setting cannot be updated in parallel with any of the following: licenseModel, dbEdition, cpuCoreCount, computeCount, computeModel, adminPassword, whitelistedIps, isMTLSConnectionRequired, dbWorkload, dbVersion, isRefreshable, dbName, scheduledOperations, dbToolsDetails, or isFreeTier. | ```yaml --- @@ -104,14 +104,13 @@ To configure Network Access with ACLs, complete this procedure. action: Update details: autonomousDatabaseOCID: ocid1.autonomousdatabase... - networkAccess: - # Restrict access by defining access control rules in an Access Control List (ACL). - whitelistedIps: - - 1.1.1.1 - - 1.1.0.0/16 - - ocid1.vcn... - - ocid1.vcn...;1.1.1.1 - - ocid1.vcn...;1.1.0.0/16 + # Restrict access by defining access control rules in an Access Control List (ACL). + whitelistedIps: + - 1.1.1.1 + - 1.1.0.0/16 + - ocid1.vcn... + - ocid1.vcn...;1.1.1.1 + - ocid1.vcn...;1.1.0.0/16 ociConfig: configMapName: oci-cred secretName: oci-privatekey diff --git a/docs/adb/README.md b/docs/adb/README.md index e8164697..15e78a2c 100644 --- a/docs/adb/README.md +++ b/docs/adb/README.md @@ -2,11 +2,14 @@ Before you use the Oracle Database Operator for Kubernetes (the operator), ensure that your system meets all of the Oracle Autonomous Database (ADB) Prerequisites [ADB_PREREQUISITES](./ADB_PREREQUISITES.md). -As indicated in the prerequisites (see above), to interact with OCI services, either the cluster must be authorized using Principal Instance, or the cluster must be authorized using the API Key Authentication by specifying the configMap and the secret under the `ociConfig` field. +To allow your Kubernetes cluster to interact with OCI services, your cluster must be authorized with one of the following: +- Instance Principal authentication +- API Key Authentication (specify the required configMap and Secret under `ociConfig`). + ## Required Permissions -The operator must be given the required type of access in a policy written by an administrator to manage the Autonomous Databases. For examples of Autonomous Database policies, see: [Let database and fleet admins manage Autonomous Databases](https://docs.oracle.com/en-us/iaas/Content/Identity/Concepts/commonpolicies.htm#db-admins-manage-adb) +The operator requires appropriate OCI policies, written by an administrator, to manage Autonomous Databases. For examples of Autonomous Database policies, see: [Let database and fleet admins manage Autonomous Databases](https://docs.oracle.com/en-us/iaas/Content/Identity/Concepts/commonpolicies.htm#db-admins-manage-adb) Permissions to view the work requests are also required, so that the operator can update the resources when the work is done. For example work request policies, see: [Viewing Work Requests](https://docs.oracle.com/en-us/iaas/Content/ContEng/Tasks/contengviewingworkrequests.htm#contengviewingworkrequests) @@ -26,6 +29,8 @@ After you create the resource, you can use the operator to perform the following * [Stop/Start/Terminate](#stopstartterminate) an Autonomous Database * [Delete the resource](#delete-the-resource) from the cluster * [Clone](#clone-an-existing-autonomous-database) an existing Autonomous Database +* [Switchover](#switchover-an-existing-autonomous-database) an existing Autonomous Database +* [Perform Manual Failover](#manually-failover-an-existing-autonomous-database) to an existing Autonomous Database To debug the Oracle Autonomous Databases with Oracle Database Operator, see [Debugging and troubleshooting](#debugging-and-troubleshooting) @@ -113,7 +118,8 @@ To provision an Autonomous Database that will map objects in your cluster, compl compartmentId: ocid1.compartment... dbName: NewADB displayName: NewADB - cpuCoreCount: 1 + computeModel: ECPU + computeCount: 1 adminPassword: k8sSecret: name: admin-password # use the name of the secret from step 2 @@ -187,7 +193,7 @@ The operator also generates the `AutonomousBackup` custom resources if a databas > Note: this operation requires an `AutonomousDatabase` object to be in your cluster. To use this example, either the provision operation or the bind operation must be completed, and the operator must be authorized with API Key Authentication. -You can scale up or scale down the Oracle Autonomous Database OCPU core count or storage by updating the `cpuCoreCount` and `dataStorageSizeInTBs` parameters. The `isAutoScalingEnabled` indicates whether auto scaling is enabled. In this example, the CPU count and storage size (TB) are scaled up to 2 and the auto-scaling is turned off by updating the `autonomousdatabase-sample` custom resource. +You can scale up or scale down the Oracle Autonomous Database OCPU core count or storage by updating the `computeCount` and `dataStorageSizeInTBs` parameters. The `isAutoScalingEnabled` indicates whether auto scaling is enabled. In this example, the CPU count and storage size (TB) are scaled up to 2 and the auto-scaling is turned off by updating the `autonomousdatabase-sample` custom resource. 1. An example YAML file is available here: [config/samples/adb/autonomousdatabase_scale.yaml](./../../config/samples/adb/autonomousdatabase_scale.yaml) @@ -201,7 +207,7 @@ You can scale up or scale down the Oracle Autonomous Database OCPU core count or action: Update details: id: ocid1.autonomousdatabase... - cpuCoreCount: 2 + computeCount: 2 dataStorageSizeInTBs: 2 isAutoScalingEnabled: false ociConfig: @@ -310,7 +316,7 @@ A client Wallet is required to connect to a shared Oracle Autonomous Database. U \* The password must be at least 8 characters long and must include at least 1 letter and either 1 numeric character or 1 special character. -2. Update the example [config/samples/adb/autonomousdatabase_wallet.yaml](./../../config/samples/adb/autonomousdatabase_wallet.yaml) +2. Update the example [config/samples/adb/autonomousdatabase_download_wallet.yaml](./../../config/samples/adb/autonomousdatabase_download_wallet.yaml) ```yaml --- @@ -487,7 +493,8 @@ To clone an existing Autonomous Database, complete these steps: compartmentId: ocid1.compartment... OR ocid1.tenancy... dbName: ClonedADB displayName: ClonedADB - cpuCoreCount: 1 + computeModel: ECPU + computeCount: 1 adminPassword: k8sSecret: name: admin-password @@ -508,6 +515,78 @@ To clone an existing Autonomous Database, complete these steps: Now, you can verify that a cloned database with name "ClonedADB" is being provisioned on the Cloud Console. +## Switchover an existing Autonomous Database + +> Note: this operation requires an `AutonomousDatabase` object to be in your cluster. This example assumes the provision operation or the bind operation has been done by the users and the operator is authorized with API Key Authentication. + +To switchover an existing Autonomous Database, complete these steps: + +1. Add the following fields to the AutonomousDatabase resource definition. An example YAML file is available here: [config/samples/adb/autonomousdatabase_switchover.yaml](./../../config/samples/adb/autonomousdatabase_switchover.yaml) + | Attribute | Type | Description | Required? | + |----|----|----|----| + | `spec.details.id` | string | The [OCID](https://docs.cloud.oracle.com/Content/General/Concepts/identifiers.htm) of the source Autonomous Database that you will clone to create a new Autonomous Database. | Yes | + | `spec.ociConfig` | dictionary | Not required when the Operator is authorized with [Instance Principal](./ADB_PREREQUISITES.md#authorized-with-instance-principal). Otherwise, you will need the values from the [Authorized with API Key Authentication](./ADB_PREREQUISITES.md#authorized-with-api-key-authentication) section. | Conditional | + | `spec.ociConfig.configMapName` | string | Name of the ConfigMap that holds the local OCI configuration | Conditional | + | `spec.ociConfig.secretName`| string | Name of the K8s Secret that holds the private key value | Conditional | + + ```yaml + --- + apiVersion: database.oracle.com/v4 + kind: AutonomousDatabase + metadata: + name: autonomousdatabase-sample + spec: + action: Switchover + details: + id: ocid1.autonomousdatabase... + ociConfig: + configMapName: oci-cred + secretName: oci-privatekey + ``` + +2. Apply the yaml + + ```sh + kubectl apply -f config/samples/adb/autonomousdatabase_switchover.yaml + autonomousdatabase.database.oracle.com/autonomousdatabase-sample configured + ``` + +## Manually failover an existing Autonomous Database + +> Note: this operation requires an `AutonomousDatabase` object to be in your cluster. This example assumes the provision operation or the bind operation has been done by the users and the operator is authorized with API Key Authentication. + +To manually failover an existing Autonomous Database, complete these steps: + +1. Add the following fields to the AutonomousDatabase resource definition. An example YAML file is available here: [config/samples/adb/autonomousdatabase_manual_failover.yaml](./../../config/samples/adb/autonomousdatabase_manual_failover.yaml) + | Attribute | Type | Description | Required? | + |----|----|----|----| + | `spec.details.id` | string | The [OCID](https://docs.cloud.oracle.com/Content/General/Concepts/identifiers.htm) of the source Autonomous Database that you will clone to create a new Autonomous Database. | Yes | + | `spec.ociConfig` | dictionary | Not required when the Operator is authorized with [Instance Principal](./ADB_PREREQUISITES.md#authorized-with-instance-principal). Otherwise, you will need the values from the [Authorized with API Key Authentication](./ADB_PREREQUISITES.md#authorized-with-api-key-authentication) section. | Conditional | + | `spec.ociConfig.configMapName` | string | Name of the ConfigMap that holds the local OCI configuration | Conditional | + | `spec.ociConfig.secretName`| string | Name of the K8s Secret that holds the private key value | Conditional | + + ```yaml + --- + apiVersion: database.oracle.com/v4 + kind: AutonomousDatabase + metadata: + name: autonomousdatabase-sample + spec: + action: Failover + details: + id: ocid1.autonomousdatabase... + ociConfig: + configMapName: oci-cred + secretName: oci-privatekey + ``` + +2. Apply the yaml + + ```sh + kubectl apply -f config/samples/adb/autonomousdatabase_failover.yaml + autonomousdatabase.database.oracle.com/autonomousdatabase-sample configured + ``` + ## Roles and Privileges requirements for Oracle Autonomous Database Controller Autonomous Database controller uses Kubernetes objects such as: @@ -520,6 +599,18 @@ Autonomous Database controller uses Kubernetes objects such as: The defintion of all the Kubernetes Objects, which are to be used by the Oracle Autonomous Database Controller, comes from the `oracle-database-operator.yaml` file which is applied to deploy the **Oracle Database Operator**. +## OpenShift Support + +The Autonomous Database (ADB) Controller has been tested on OpenShift clusters and verified to work with the following use cases: + +* **Create** – Provision a new Autonomous Database +* **Sync (Binding)** – Synchronize with existing Autonomous Database resources +* **Update** – Apply configuration changes +* **Stop** – Stop an Autonomous Database instance +* **Start** – Start an Autonomous Database instance +* **Terminate** – Delete an Autonomous Database instance +* **Clone** – Create a clone from an existing Autonomous Database + ## Debugging and troubleshooting ### Show the details of the resource @@ -547,3 +638,24 @@ To check the logs, use these steps: ```sh kubectl logs -f pod/oracle-database-operator-controller-manager-78666fdddb-s4xcm -n oracle-database-operator-system ``` + +## Known Issues + +### Failed to validate Wallet: "read-only file system" + +In some environments, e.g. OKE using the Operator add-on, the operator fails to validate the wallet due to encountering a **read-only file system** error. This prevents successful wallet validation and can disrupt operator functionality. + +For example, logs from the controller pod may show: + +```text +"error": "Failed to validate Wallet: open /tmp/wallet1208873634.zip: read-only file system" +``` + +#### Workaround + +* Ensure wallet directories and mounted volumes have correct **read-write** permissions. +* Confirm that file system mounts used by the operator are writable. + +#### Reference + +See GitHub issue **#193** in the [oracle-database-operator repository](https://github.com/oracle/oracle-database-operator/issues/193) for details and steps to work around. diff --git a/docs/config/samples/orestart/custom-kubeletconfig.yaml b/docs/config/samples/orestart/custom-kubeletconfig.yaml new file mode 100644 index 00000000..7549ae2a --- /dev/null +++ b/docs/config/samples/orestart/custom-kubeletconfig.yaml @@ -0,0 +1,13 @@ +apiVersion: machineconfiguration.openshift.io/v1 +kind: KubeletConfig +metadata: + name: enable-unsafe-sysctls +spec: + machineConfigPoolSelector: + matchLabels: + custom-kubelet: enable-unsafe-sysctls + kubeletConfig: + allowedUnsafeSysctls: + - "kernel.shm*" + - "kernel.sem" + - "net.*" \ No newline at end of file diff --git a/docs/config/samples/orestart/custom-scc.yaml b/docs/config/samples/orestart/custom-scc.yaml new file mode 100644 index 00000000..92facf67 --- /dev/null +++ b/docs/config/samples/orestart/custom-scc.yaml @@ -0,0 +1,50 @@ +apiVersion: security.openshift.io/v1 +kind: SecurityContextConstraints +metadata: + name: custom-scc +allowPrivilegedContainer: true +allowedCapabilities: + - '*' +allowedUnsafeSysctls: + - kernel.shm* + - net.* + - kernel.sem +runAsUser: + type: RunAsAny +seLinuxContext: + type: RunAsAny +supplementalGroups: + type: RunAsAny +fsGroup: + type: RunAsAny +volumes: + - awsElasticBlockStore + - azureDisk + - azureFile + - cephFS + - cinder + - configMap + - csi + - downwardAPI + - emptyDir + - ephemeral + - fc + - flexVolume + - flocker + - gcePersistentDisk + - gitRepo + - glusterfs + - hostPath + - image + - iscsi + - nfs + - persistentVolumeClaim + - photonPersistentDisk + - portworxVolume + - projected + - quobyte + - rbd + - scaleIO + - secret + - storageOS + - vsphere \ No newline at end of file diff --git a/docs/dbcs/README.md b/docs/dbcs/README.md index 2c06511c..4be4e95a 100644 --- a/docs/dbcs/README.md +++ b/docs/dbcs/README.md @@ -44,7 +44,8 @@ default Active 118d kube-node-lease Active 118d kube-public Active 118d kube-system Active 118d -oracle-database-operator-system Active 10m <<<< namespace to deploy the Oracle Database Operator +oracle-database-operator-system Active 10m # <<<< NAMESPACE TO DEPLOY ORACLE DB OPERATOR + [root@test-server oracle-database-operator]# kubectl get all -n oracle-database-operator-system @@ -73,7 +74,7 @@ certificaterequests.cert-manager.io 2022-02-22T23:21:35Z certificates.cert-manager.io 2022-02-22T23:21:36Z challenges.acme.cert-manager.io 2022-02-22T23:21:36Z clusterissuers.cert-manager.io 2022-02-22T23:21:36Z -dbcssystems.database.oracle.com 2022-02-22T23:23:25Z <<<< CRD for OBDS Controller +dbcssystems.database.oracle.com 2022-02-22T23:23:25Z # <<<< CRD for OBDS Controller issuers.cert-manager.io 2022-02-22T23:21:36Z orders.acme.cert-manager.io 2022-02-22T23:21:37Z shardingdatabases.database.oracle.com 2022-02-22T23:23:25Z @@ -107,21 +108,21 @@ kubectl create secret generic oci-privatekey --from-file=privatekey=/root/.oci/o ``` -## 3. Create a Kubernetes secret named `admin-password`; This passward must meet the minimum passward requirements for the OCI OBDS Service. +## 3. Create a Kubernetes secret named `admin-password`; This password must meet the minimum password requirements for the OCI OBDS Service. For example: ```bash -#-- assuming the passward has been added to a text file named "admin-password": +#-- assuming the password has been added to a text file named "admin-password": kubectl create secret generic admin-password --from-file=./admin-password -n default ``` -## 4. Create a Kubernetes secret named `tde-password`; this passward must meet the minimum passward requirements for the OCI OBDS Service. +## 4. Create a Kubernetes secret named `tde-password`; this password must meet the minimum password requirements for the OCI OBDS Service. For example: ```bash -# -- assuming the passward has been added to a text file named "tde-password": +# -- assuming the password has been added to a text file named "tde-password": kubectl create secret generic tde-password --from-file=./tde-password -n default ``` @@ -161,20 +162,26 @@ For more informatoin about the multiple use cases available to you to deploy and [1. Deploy a DB System using OCI OBDS Service with minimal parameters](./provisioning/dbcs_service_with_minimal_parameters.md) [2. Binding to an existing OBDS System already deployed in OCI Oracle Base Database Service](./provisioning/bind_to_existing_dbcs_system.md) [3. Scale UP the shape of an existing OBDS System](./provisioning/scale_up_dbcs_system_shape.md) -[4. Scale DOWN the shape of an existing OBDS System](./provisioning/scale_down_dbcs_system_shape.md) -[5. Scale UP the storage of an existing OBDS System](./provisioning/scale_up_storage.md) -[6. Update License type of an existing OBDS System](./provisioning/update_license.md) +[4. Scale DOWN the shape of an existing OBDS System](./provisioning/scale_down_dbcs_system_shape.md) +[5. Scale UP the storage of an existing OBDS System](./provisioning/scale_up_storage.md) +[6. Update License type of an existing OBDS System](./provisioning/update_license.md) [7. Terminate an existing OBDS System](./provisioning/terminate_dbcs_system.md) [8. Create OBDS with All Parameters with Storage Management as LVM](./provisioning/dbcs_service_with_all_parameters_lvm.md) [9. Create OBDS with All Parameters with Storage Management as ASM](./provisioning/dbcs_service_with_all_parameters_asm.md) [10. Deploy a 2 Node RAC DB System using OCI OBDS Service](./provisioning/dbcs_service_with_2_node_rac.md) -[11. Create PDB to an existing OBDS System already deployed in OCI OBDS Service](./provisioning/create_pdb_to_existing_dbcs_system.md) -[12. Create OBDS with PDB in OCI](./provisioning/create_dbcs_with_pdb.md) -[13. Create OBDS with KMS Vault Encryption in OCI](./provisioning/create_dbcs_with_kms.md) +[11. Create PDB to an existing OBDS System already deployed in OCI OBDS Service](./provisioning/create_pdb_to_existing_dbcs_system.md) +[12. Create OBDS with PDB in OCI](./provisioning/create_dbcs_with_pdb.md) +[13. Create OBDS with KMS Vault Encryption in OCI](./provisioning/create_dbcs_with_kms.md) [14. Migrate to KMS vault from TDE Wallet password encryption of an existing OBDS System already deployed in OCI Base OBDS Service](./provisioning/migrate_to_kms.md) [15. Clone DB System from Existing DB System in OCI OBDS Service](./provisioning/clone_from_existing_dbcs.md) [16. Clone DB System from Backup of Existing DB System in OCI OBDS Service](./provisioning/clone_from_backup_dbcs.md) [17. Clone DB System from Existing Database of DB System in OCI OBDS Service](./provisioning/clone_from_database.md) +[18. Create Backup of Existing Database of DB System in OCI OBDS Service](./provisioning/backup_of_database.md) +[19. Restore from Backup of Existing Database of DB System in OCI OBDS Service](./provisioning/restore_of_database.md) +[20. Setup Dataguard Association to Existing Database of DB System in OCI Base DBCS Service](./provisioning/dataguard_to_database.md) +[21. Disable Dataguard Association to Existing Database of DB System and Terminate Peer DB System in OCI Base DBCS Service](./provisioning/disable_dataguard_to_database.md) +[22. Patching Existing Database of DB System in OCI Base DBCS Service](./provisioning/patching_database.md) +[23. Upgrading Existing Database of DB System in OCI Base DBCS Service](./provisioning/upgrading_database.md) ## Connecting to OCI OBDS database deployed using Oracle DB Operator OBDS Controller diff --git a/docs/dbcs/provisioning/backup_database_sample_output.log b/docs/dbcs/provisioning/backup_database_sample_output.log new file mode 100644 index 00000000..6d76dc4c --- /dev/null +++ b/docs/dbcs/provisioning/backup_database_sample_output.log @@ -0,0 +1,100 @@ +2025-04-09T08:57:48Z INFO Backup creation initiated {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "a1edd2a5-7a6a-4dde-a071-e2a634b4b3b9", "BackupID": "ocid1.dbbackup.oc1.ap-mumbai-1.anrg6ljrabf7htyadzfhqva7rs7ttlz3ql3vwexfl4yoghzsllg7hy7oxdga"} +2025-04-09T08:57:48Z INFO Waiting for backup to reach ACTIVE state... {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "a1edd2a5-7a6a-4dde-a071-e2a634b4b3b9"} +2025-04-09T08:58:19Z INFO Polling backup status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "a1edd2a5-7a6a-4dde-a071-e2a634b4b3b9", "BackupID": "ocid1.dbbackup.oc1.ap-mumbai-1.anrg6ljrabf7htyadzfhqva7rs7ttlz3ql3vwexfl4yoghzsllg7hy7oxdga", "State": "CREATING"} +2025-04-09T08:58:48Z INFO Polling backup status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "a1edd2a5-7a6a-4dde-a071-e2a634b4b3b9", "BackupID": "ocid1.dbbackup.oc1.ap-mumbai-1.anrg6ljrabf7htyadzfhqva7rs7ttlz3ql3vwexfl4yoghzsllg7hy7oxdga", "State": "CREATING"} +2025-04-09T08:59:18Z INFO Polling backup status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "a1edd2a5-7a6a-4dde-a071-e2a634b4b3b9", "BackupID": "ocid1.dbbackup.oc1.ap-mumbai-1.anrg6ljrabf7htyadzfhqva7rs7ttlz3ql3vwexfl4yoghzsllg7hy7oxdga", "State": "CREATING"} +2025-04-09T08:59:48Z INFO Polling backup status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "a1edd2a5-7a6a-4dde-a071-e2a634b4b3b9", "BackupID": "ocid1.dbbackup.oc1.ap-mumbai-1.anrg6ljrabf7htyadzfhqva7rs7ttlz3ql3vwexfl4yoghzsllg7hy7oxdga", "State": "CREATING"} +2025-04-09T09:00:18Z INFO Polling backup status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "a1edd2a5-7a6a-4dde-a071-e2a634b4b3b9", "BackupID": "ocid1.dbbackup.oc1.ap-mumbai-1.anrg6ljrabf7htyadzfhqva7rs7ttlz3ql3vwexfl4yoghzsllg7hy7oxdga", "State": "CREATING"} +2025-04-09T09:00:48Z INFO Polling backup status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "a1edd2a5-7a6a-4dde-a071-e2a634b4b3b9", "BackupID": "ocid1.dbbackup.oc1.ap-mumbai-1.anrg6ljrabf7htyadzfhqva7rs7ttlz3ql3vwexfl4yoghzsllg7hy7oxdga", "State": "CREATING"} +2025-04-09T09:09:48Z INFO Polling backup status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "a1edd2a5-7a6a-4dde-a071-e2a634b4b3b9", "BackupID": "ocid1.dbbackup.oc1.ap-mumbai-1.anrg6ljrabf7htyadzfhqva7rs7ttlz3ql3vwexfl4yoghzsllg7hy7oxdga", "State": "CREATING"} +2025-04-09T09:10:18Z INFO Polling backup status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "a1edd2a5-7a6a-4dde-a071-e2a634b4b3b9", "BackupID": "ocid1.dbbackup.oc1.ap-mumbai-1.anrg6ljrabf7htyadzfhqva7rs7ttlz3ql3vwexfl4yoghzsllg7hy7oxdga", "State": "CREATING"} +2025-04-09T09:10:48Z INFO Polling backup status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "a1edd2a5-7a6a-4dde-a071-e2a634b4b3b9", "BackupID": "ocid1.dbbackup.oc1.ap-mumbai-1.anrg6ljrabf7htyadzfhqva7rs7ttlz3ql3vwexfl4yoghzsllg7hy7oxdga", "State": "CREATING"} +2025-04-09T09:11:18Z INFO Polling backup status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "a1edd2a5-7a6a-4dde-a071-e2a634b4b3b9", "BackupID": "ocid1.dbbackup.oc1.ap-mumbai-1.anrg6ljrabf7htyadzfhqva7rs7ttlz3ql3vwexfl4yoghzsllg7hy7oxdga", "State": "CREATING"} +2025-04-09T09:11:48Z INFO Polling backup status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "a1edd2a5-7a6a-4dde-a071-e2a634b4b3b9", "BackupID": "ocid1.dbbackup.oc1.ap-mumbai-1.anrg6ljrabf7htyadzfhqva7rs7ttlz3ql3vwexfl4yoghzsllg7hy7oxdga", "State": "CREATING"} +2025-04-09T09:12:18Z INFO Polling backup status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "a1edd2a5-7a6a-4dde-a071-e2a634b4b3b9", "BackupID": "ocid1.dbbackup.oc1.ap-mumbai-1.anrg6ljrabf7htyadzfhqva7rs7ttlz3ql3vwexfl4yoghzsllg7hy7oxdga", "State": "CREATING"} +2025-04-09T09:12:48Z INFO Polling backup status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "a1edd2a5-7a6a-4dde-a071-e2a634b4b3b9", "BackupID": "ocid1.dbbackup.oc1.ap-mumbai-1.anrg6ljrabf7htyadzfhqva7rs7ttlz3ql3vwexfl4yoghzsllg7hy7oxdga", "State": "ACTIVE"} +2025-04-09T09:12:48Z INFO Backup completed successfully {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "a1edd2a5-7a6a-4dde-a071-e2a634b4b3b9", "BackupID": "ocid1.dbbackup.oc1.ap-mumbai-1.anrg6ljrabf7htyadzfhqva7rs7ttlz3ql3vwexfl4yoghzsllg7hy7oxdga"} +2025-04-09T09:12:51Z INFO Backup Completed successfully {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "a1edd2a5-7a6a-4dde-a071-e2a634b4b3b9", "BackupID": "ocid1.dbbackup.oc1.ap-mumbai-1.anrg6ljrabf7htyadzfhqva7rs7ttlz3ql3vwexfl4yoghzsllg7hy7oxdga"} + + +kubectl describe dbcssystems.database.oracle.com/dbcssystem-existing +Warning: To increase security of your API key located at /home/sauahuja/.oci/oci_api_key.pem, append an extra line with 'OCI_API_KEY' at the end. For more information, refer to https://docs.oracle.com/iaas/Content/API/Concepts/apisigningkey.htm. To suppress the warning, set the env variable SUPPRESS_LABEL_WARNING=True +Name: dbcssystem-existing +Namespace: default +Labels: +Annotations: lastSuccessfulSpec: + {"dbSystem":{"compartmentId":"","availabilityDomain":"","subnetId":"","shape":"","hostName":"","backupDisplayName":"Full-Backup","dbAdminP... +API Version: database.oracle.com/v4 +Kind: DbcsSystem +Metadata: + Creation Timestamp: 2025-04-09T08:57:34Z + Generation: 2 + Resource Version: 168679344 + UID: 0633f774-92a9-4ab0-b65e-616be3fc7adb +Spec: + Db System: + Availability Domain: + Backup Display Name: Full-Backup + Compartment Id: + Db Admin Password Secret: + Db Backup Config: + Host Name: + Kms Config: + Shape: + Subnet Id: + Enable Backup: true + Id: ocid1.dbsystem.oc1.ap-mumbai-1.anrg6ljrabf7htyaezbusohovqidnjud2w5wmeisqydsmkqlzcghctvrscfq + Kms Config: + Oci Config Map: oci-cred-mumbai + Oci Secret: oci-privatekey +Status: + Availability Domain: OLou:AP-MUMBAI-1-AD-1 + Backups: + Backup Id: ocid1.dbbackup.oc1.ap-mumbai-1.anrg6ljrabf7htyadzfhqva7rs7ttlz3ql3vwexfl4yoghzsllg7hy7oxdga + Name: Full-Backup-20250409-085744 + Timestamp: 2025-04-09T08:57:53Z + Backup Id: ocid1.dbbackup.oc1.ap-mumbai-1.anrg6ljrabf7htyazmu2w7bjtivgbjj37aym6cfsnutruzyrb2t7uiuidksq + Name: Saurabh-JustInTime-manual-20250408-144538 + Timestamp: 2025-04-08T14:45:48Z + Backup Id: ocid1.dbbackup.oc1.ap-mumbai-1.anrg6ljrabf7htyab6cgmv5vs6rtyfusfw3w7dj5e5ck5h6x32xuz253qcla + Name: backup-20250408-142512 + Timestamp: 2025-04-08T14:25:22Z + Cpu Core Count: 1 + Data Storage Percentage: 80 + Data Storage Size In G Bs: 256 + Db Clone Status: + Db Db Unique Name: + Host Name: + Db Edition: ENTERPRISE_EDITION_HIGH_PERFORMANCE + Display Name: dbsystem123 + Id: ocid1.dbsystem.oc1.ap-mumbai-1.anrg6ljrabf7htyaezbusohovqidnjud2w5wmeisqydsmkqlzcghctvrscfq + Kms Details Status: + License Model: BRING_YOUR_OWN_LICENSE + Network: + Client Subnet: oke-nodesubnet-quick-cluster1-2bebe95db-regional + Domain Name: subdda0b5eaa.cluster1.oraclevcn.com + Host Name: host123 + Listener Port: 1521 + Scan Dns Name: host123-scan.subdda0b5eaa.cluster1.oraclevcn.com + Vcn Name: oke-vcn-quick-cluster1-2bebe95db + Node Count: 1 + Pdb Details Status: + Pdb Config Status: + Pdb Name: cdb123_pdb1 + Pdb State: AVAILABLE + Pluggable Database Id: ocid1.pluggabledatabase.oc1.ap-mumbai-1.anrg6ljrabf7htyao76wku3parutii5ojxmejzfbevloxcizvt3lt5pyzvja + Reco Storage Size In GB: 256 + Shape: VM.Standard2.1 + State: AVAILABLE + Storage Management: ASM + Subnet Id: ocid1.subnet.oc1.ap-mumbai-1.aaaaaaaa5zpzfax66omtbmjwlv4thruyru7focnu7fjcjksujmgwmr6vpbvq + Time Zone: UTC + Work Requests: + Operation Id: ocid1.coreservicesworkrequest.oc1.ap-mumbai-1.abrg6ljrjajnyzhsjbuaxu76mc3ku2xr2itlip45hj75rhyd7xtdcvtar77q + Operation Type: Create DB System + Percent Complete: 100 + Time Accepted: 2025-04-06 04:55:35.586 +0000 UTC + Time Finished: 2025-04-06 07:24:02.505 +0000 UTC + Time Started: 2025-04-06 04:55:39.439 +0000 UTC +Events: +basedb/ $ \ No newline at end of file diff --git a/docs/dbcs/provisioning/backup_of_database.md b/docs/dbcs/provisioning/backup_of_database.md new file mode 100644 index 00000000..6d17f2ef --- /dev/null +++ b/docs/dbcs/provisioning/backup_of_database.md @@ -0,0 +1,35 @@ +# Create Backup of Existing Database of DB System in OCI OBDS Service + +In this example, an existing OCI OBDS system previously deployed with an existing Database is updated to have a full manual backup in the OCI Base OBDS Service using the existing Compartment ID and database system ID. + +To use this example on your system, before you begin, obtain the details of the database OCID for the existing OBDS System which you want to backup. + +**NOTE:** We are assuming that before this step, you have followed the [prerequisite](./../README.md#prerequisites-to-deploy-a-dbcs-system-using-oracle-db-operator-dbcs-controller) steps to create the configmap and the secrets required during the deployment. + +This example uses `backup_of_database.yaml` to clone a Single Instance OBDS VMDB using Oracle DB Operator OBDS Controller with the following configuration: +- OCID of existing as DB System as `id` +- OCI Configmap as `oci-cred` +- OCI Secret as `oci-privatekey` +- enableBackup: as `true` +- Specification of backup prefix as - Details of `backupDisplayName`. This field is optional. If it is not provided, then the controller uses the keyword `backup` as the prefix. In both cases of displayName, whether you provide a backup display name or not, the suffix of the backup name is the timestamp. The timestamp ensure that the backup name created for the database is unique. +**NOTE:** For the details of the parameters to be used in the .yaml file, see [DBCS controller parameters](./dbcs_controller_parameters.md). + +Use the file: [backup_of_database.yaml](./backup_of_database.yaml) for this use case. Example: + +1. Deploy the .yaml file: +```sh +[root@docker-test-server OBDS]# kubectl apply -f backup_of_database.yaml +dbcssystem.database.oracle.com/dbcssystem-backup configured +``` + +2. Monitor the Oracle Database Operator Pod `pod/oracle-database-operator-controller-manager-665874bd57-g2cgw` for the progress of the OBDS VMDB creation of PDBs. + +NOTE: Check the DB Operator Pod name in your environment. + +``` +[root@docker-test-server OBDS]# kubectl logs -f pod/oracle-database-operator-controller-manager-665874bd57-g2cgw -n oracle-database-operator-system +``` + +## Sample Output + +[Backup Database Sample Output Log](./backup_database_sample_output.log) is an example output for a backup an existing OBDS System deployed in OCI using the Oracle Database Operator OBDS Controller. \ No newline at end of file diff --git a/docs/dbcs/provisioning/backup_of_database.yaml b/docs/dbcs/provisioning/backup_of_database.yaml new file mode 100644 index 00000000..f9edd741 --- /dev/null +++ b/docs/dbcs/provisioning/backup_of_database.yaml @@ -0,0 +1,11 @@ +apiVersion: database.oracle.com/v4 +kind: DbcsSystem +metadata: + name: dbcssystem-backup +spec: + id: "ocid1.dbsystem.oc1.ap-mumbai-1.anrg6ljrabf7htyaezbusohovqidnjud2w5wmeisqydsmkqlzcghctvrscfq" + ociConfigMap: "oci-cred" + ociSecret: "oci-privatekey" + enableBackup: true + dbSystem: + backupDisplayName: "Full-Backup" \ No newline at end of file diff --git a/docs/dbcs/provisioning/bind_to_existing_dbcs_system.md b/docs/dbcs/provisioning/bind_to_existing_dbcs_system.md index eced7538..0cad1db4 100644 --- a/docs/dbcs/provisioning/bind_to_existing_dbcs_system.md +++ b/docs/dbcs/provisioning/bind_to_existing_dbcs_system.md @@ -1,25 +1,25 @@ # Binding to an existing OBDS System already deployed in OCI Oracle Base Database Service -In this use case, we bind the Oracle DB Operator OBDS Controller to an existing OCI OBDS System which has already been deployed earlier. This will help to manage the life cycle of that OBDS System using the Oracle DB Operator OBDS Controller. +In this use case, we bind the Oracle DB Operator OBDS Controller to an existing OCI OBDS System that has already been deployed. After you bind the Controller, you are able to use it to manage the lifecycle of that OBDS System. **NOTE** We are assuming that before this step, you have followed the [prerequisite](./../README.md#prerequsites-to-deploy-a-dbcs-system-using-oracle-db-operator-dbcs-controller) steps to create the configmap and the secrets required during the deployment. -This example uses `bind_to_existing_dbcs_system.yaml` to bind to an existing OBDS VMDB using Oracle DB Operator OBDS Controller with: +This example uses `bind_to_existing_dbcs_system.yaml` to bind to an existing OBDS VMDB using Oracle DB Operator OBDS Controller with the following: - OCI Configmap as `oci-cred-mumbai` - OCI Secret as `oci-privatekey` - OCID of the existing OBDS System as `ocid1.dbsystem.oc1.ap-mumbai-1.anrg6ljrabf7htyadgsso7aessztysrwaj5gcl3tp7ce6asijm2japyvmroa` -Use the file: [bind_to_existing_dbcs_system.yaml](./bind_to_existing_dbcs_system.yaml) for this use case as below: +Use the file: [bind_to_existing_dbcs_system.yaml](./bind_to_existing_dbcs_system.yaml) for this use case, as described in the following steps: -1. Deploy the .yaml file: +1. Deploy the `.yaml` file: ```bash kubectl apply -f bind_to_existing_dbcs_system.yaml dbcssystem.database.oracle.com/dbcssystem-existing created ``` -2. Monitor the Oracle DB Leader Operator Pod `pod/oracle-database-operator-controller-manager-665874bd57-g2cgw` for the progress of the OBDS VMDB deployment. +2. Monitor the Oracle DB Leader Operator Pod `pod/oracle-database-operator-controller-manager-665874bd57-g2cgw` to follow the progress of the OBDS VMDB deployment. NOTE: Check the DB Operator Pod name in your environment. @@ -29,4 +29,4 @@ NOTE: Check the DB Operator Pod name in your environment. ## Sample Output -[Here](./bind_to_existing_dbcs_system_sample_output.log) is the sample output for binding to an existing OBDS System already deployed in OCI using Oracle DB Operator OBDS Controller. +[Here](./bind_to_existing_dbcs_system_sample_output.log) is an example of the output for binding to an existing OBDS System already deployed in OCI using Oracle DB Operator OBDS Controller. diff --git a/docs/dbcs/provisioning/bind_to_existing_dbcs_system.yaml b/docs/dbcs/provisioning/bind_to_existing_dbcs_system.yaml index 6ff24bc8..05885fdb 100644 --- a/docs/dbcs/provisioning/bind_to_existing_dbcs_system.yaml +++ b/docs/dbcs/provisioning/bind_to_existing_dbcs_system.yaml @@ -1,8 +1,21 @@ +# The API group and version this custom resource belongs to (Oracle Database Operator CRD v4) apiVersion: database.oracle.com/v4 + +# The type of resource (DbcsSystem manages an OCI DB System) kind: DbcsSystem + +# Standard Kubernetes object metadata metadata: + # The name of this DbcsSystem resource (must be unique within the namespace) name: dbcssystem-existing + +# Specification of the desired state for the DbcsSystem spec: - id: "ocid1.dbsystem.oc1.ap-mumbai-1.anrg6ljrabf7htyadgsso7aessztysrwaj5gcl3tp7ce6asijm2japyvmroa" - ociConfigMap: "oci-cred" - ociSecret: "oci-privatekey" + # The OCID of the existing DB System in OCI that this resource manages + id: "ocid1.dbsystem.oc1.ap-mumbai-1.anrg6ljrabf7htyagag6vwgkdcf5g6srfykrsqnfnryfskfhblntsjypiajq" + + # The name of the ConfigMap containing OCI credentials (tenancy, region, etc.) + ociConfigMap: "oci-cred-mumbai" + + # The name of the Secret containing the OCI private key used for authentication + ociSecret: "oci-privatekey" \ No newline at end of file diff --git a/docs/dbcs/provisioning/clone_dbcs_system_from_backup_sample_output.log b/docs/dbcs/provisioning/clone_dbcs_system_from_backup_sample_output.log index 82531993..43480edc 100644 --- a/docs/dbcs/provisioning/clone_dbcs_system_from_backup_sample_output.log +++ b/docs/dbcs/provisioning/clone_dbcs_system_from_backup_sample_output.log @@ -72,4 +72,68 @@ 2024-09-18T14:05:43Z INFO DB System current state is still:PROVISIONING. Sleeping for 60 seconds. {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-clone","namespace":"default"}, "namespace": "default", "name": "dbcssystem-clone", "reconcileID": "a299bb7c-22eb-4db4-9037-ce81897345df"} 2024-09-18T14:06:44Z INFO DB System current state is still:PROVISIONING. Sleeping for 60 seconds. {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-clone","namespace":"default"}, "namespace": "default", "name": "dbcssystem-clone", "reconcileID": "a299bb7c-22eb-4db4-9037-ce81897345df"} 2024-09-18T14:07:45Z INFO DB System current state is still:PROVISIONING. Sleeping for 60 seconds. {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-clone","namespace":"default"}, "namespace": "default", "name": "dbcssystem-clone", "reconcileID": "a299bb7c-22eb-4db4-9037-ce81897345df"} -2024-09-18T14:08:46Z INFO DB Cloning completed successfully from provided backup DB system. {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-clone","namespace":"default"}, "namespace": "default", "name": "dbcssystem-clone", "reconcileID": "a299bb7c-22eb-4db4-9037-ce81897345df"} \ No newline at end of file +2024-09-18T14:08:46Z INFO DB Cloning completed successfully from provided backup DB system. {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-clone","namespace":"default"}, "namespace": "default", "name": "dbcssystem-clone", "reconcileID": "a299bb7c-22eb-4db4-9037-ce81897345df"} + + +#kubectl describe dbcssystems/dbcssystem-clone +Name: dbcssystem-clone +Namespace: default +Labels: +Annotations: +API Version: database.oracle.com/v4 +Kind: DbcsSystem +Metadata: + Creation Timestamp: 2024-12-10T14:05:34Z + Generation: 1 + Resource Version: 118098180 + UID: 6b96d17c-b788-4bb7-bc0e-098ade53100c +Spec: + Db Clone: + Db Admin Pasword Secret: admin-password + Db Db Unique Name: + Db Name: db1212 + Display Name: dbsystem01312 + Domain: subdda0b5eaa.cluster1.oraclevcn.com + Host Name: host1213 + License Model: BRING_YOUR_OWN_LICENSE + Ssh Public Keys: + oci-publickey + Subnet Id: ocid1.subnet.oc1.ap-mumbai-1.aaaaaaaa5zpzfax66omtbmjwlv4thruyru7focnu7fjcjksujmgwmr6vpbvq + Db System: + Availability Domain: + Compartment Id: + Db Admin Pasword Secret: + Db Backup Config: + Host Name: + Kms Config: + Shape: + Subnet Id: + Id: ocid1.dbsystem.oc1.ap-mumbai-1.anrg6ljrabf7htyadgsso7aessztysrwaj5gcl3tp7ce6asijm2japyvmroa + Kms Config: + Oci Config Map: oci-cred-mumbai + Oci Secret: oci-privatekey + Setup DB Cloning: true +Status: + Availability Domain: OLou:AP-MUMBAI-1-AD-1 + Cpu Core Count: 1 + Data Storage Percentage: 80 + Data Storage Size In G Bs: 512 + Db Edition: ENTERPRISE_EDITION_HIGH_PERFORMANCE + Display Name: dbsystem1234 + Id: ocid1.dbsystem.oc1.ap-mumbai-1.anrg6ljrabf7htyadgsso7aessztysrwaj5gcl3tp7ce6asijm2japyvmroa + License Model: BRING_YOUR_OWN_LICENSE + Network: + Client Subnet: oke-nodesubnet-quick-cluster1-2bebe95db-regional + Domain Name: subdda0b5eaa.cluster1.oraclevcn.com + Host Name: host1234 + Listener Port: 1521 + Scan Dns Name: host1234-scan.subdda0b5eaa.cluster1.oraclevcn.com + Vcn Name: oke-vcn-quick-cluster1-2bebe95db + Node Count: 1 + Reco Storage Size In GB: 256 + Shape: VM.Standard2.1 + State: AVAILABLE + Storage Management: ASM + Subnet Id: ocid1.subnet.oc1.ap-mumbai-1.aaaaaaaa5zpzfax66omtbmjwlv4thruyru7focnu7fjcjksujmgwmr6vpbvq + Time Zone: UTC +Events: \ No newline at end of file diff --git a/docs/dbcs/provisioning/clone_dbcs_system_from_database_sample_output.log b/docs/dbcs/provisioning/clone_dbcs_system_from_database_sample_output.log index 2881051d..9889daa2 100644 --- a/docs/dbcs/provisioning/clone_dbcs_system_from_database_sample_output.log +++ b/docs/dbcs/provisioning/clone_dbcs_system_from_database_sample_output.log @@ -36,4 +36,68 @@ .oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-clone","namespace":"default"}, "namespace": "default", "name": "dbcssystem-clone" , "reconcileID": "3ea6b5b1-1196-4279-bf8f-9e663f9a5543"} 2024-09-20T10:00:46Z INFO DB System current state is still:PROVISIONING. Sleeping for 60 seconds. {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-clone","namespace":"default"}, "namespace": "default", "name": "dbcssystem-clone", "reconcileID": "3ea6b5b1-1196-4279-bf8f-9e663f9a5543"} -2024-09-20T10:01:47Z INFO DB Cloning completed successfully from provided backup DB system {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-clone","namespace":"default"}, "namespace": "default", "name": "dbcssystem-clone", "reconcileID": "3ea6b5b1-1196-4279-bf8f-9e663f9a5543"} \ No newline at end of file +2024-09-20T10:01:47Z INFO DB Cloning completed successfully from provided backup DB system {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-clone","namespace":"default"}, "namespace": "default", "name": "dbcssystem-clone", "reconcileID": "3ea6b5b1-1196-4279-bf8f-9e663f9a5543"} + + +#kubectl describe dbcssystems/dbcssystem-clone +Name: dbcssystem-clone +Namespace: default +Labels: +Annotations: +API Version: database.oracle.com/v4 +Kind: DbcsSystem +Metadata: + Creation Timestamp: 2024-12-10T14:05:34Z + Generation: 1 + Resource Version: 118098180 + UID: 6b96d17c-b788-4bb7-bc0e-098ade53100c +Spec: + Db Clone: + Db Admin Pasword Secret: admin-password + Db Db Unique Name: + Db Name: db1212 + Display Name: dbsystem01312 + Domain: subdda0b5eaa.cluster1.oraclevcn.com + Host Name: host1213 + License Model: BRING_YOUR_OWN_LICENSE + Ssh Public Keys: + oci-publickey + Subnet Id: ocid1.subnet.oc1.ap-mumbai-1.aaaaaaaa5zpzfax66omtbmjwlv4thruyru7focnu7fjcjksujmgwmr6vpbvq + Db System: + Availability Domain: + Compartment Id: + Db Admin Pasword Secret: + Db Backup Config: + Host Name: + Kms Config: + Shape: + Subnet Id: + Id: ocid1.dbsystem.oc1.ap-mumbai-1.anrg6ljrabf7htyadgsso7aessztysrwaj5gcl3tp7ce6asijm2japyvmroa + Kms Config: + Oci Config Map: oci-cred-mumbai + Oci Secret: oci-privatekey + Setup DB Cloning: true +Status: + Availability Domain: OLou:AP-MUMBAI-1-AD-1 + Cpu Core Count: 1 + Data Storage Percentage: 80 + Data Storage Size In G Bs: 512 + Db Edition: ENTERPRISE_EDITION_HIGH_PERFORMANCE + Display Name: dbsystem1234 + Id: ocid1.dbsystem.oc1.ap-mumbai-1.anrg6ljrabf7htyadgsso7aessztysrwaj5gcl3tp7ce6asijm2japyvmroa + License Model: BRING_YOUR_OWN_LICENSE + Network: + Client Subnet: oke-nodesubnet-quick-cluster1-2bebe95db-regional + Domain Name: subdda0b5eaa.cluster1.oraclevcn.com + Host Name: host1234 + Listener Port: 1521 + Scan Dns Name: host1234-scan.subdda0b5eaa.cluster1.oraclevcn.com + Vcn Name: oke-vcn-quick-cluster1-2bebe95db + Node Count: 1 + Reco Storage Size In GB: 256 + Shape: VM.Standard2.1 + State: AVAILABLE + Storage Management: ASM + Subnet Id: ocid1.subnet.oc1.ap-mumbai-1.aaaaaaaa5zpzfax66omtbmjwlv4thruyru7focnu7fjcjksujmgwmr6vpbvq + Time Zone: UTC +Events: \ No newline at end of file diff --git a/docs/dbcs/provisioning/clone_dbcs_system_sample_output.log b/docs/dbcs/provisioning/clone_dbcs_system_sample_output.log index 22d86e1e..b85588db 100644 --- a/docs/dbcs/provisioning/clone_dbcs_system_sample_output.log +++ b/docs/dbcs/provisioning/clone_dbcs_system_sample_output.log @@ -58,3 +58,66 @@ 2024-09-17T12:35:21Z INFO DB System current state is still:PROVISIONING. Sleeping for 60 seconds. {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-clone.yaml","namespace":"default"}, "namespace": "default", "name": "dbcssystem-clone.yaml", "reconcileID": "bf0c98c6-13b6-4c15-938a-3594cd4cf1f3"} 2024-09-17T12:36:22Z INFO DB System current state is still:PROVISIONING. Sleeping for 60 seconds. {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-clone.yaml","namespace":"default"}, "namespace": "default", "name": "dbcssystem-clone.yaml", "reconcileID": "bf0c98c6-13b6-4c15-938a-3594cd4cf1f3"} 2024-09-17T12:36:22Z INFO DB Cloning completed successfully from provided db system {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-clone.yaml","namespace":"default"}, "namespace": "default", "name": "dbcssystem-clone.yaml", "reconcileID": "bf0c98c6-13b6-4c15-938a-3594cd4cf1f3"} + +#kubectl describe dbcssystems/dbcssystem-clone +Name: dbcssystem-clone +Namespace: default +Labels: +Annotations: +API Version: database.oracle.com/v4 +Kind: DbcsSystem +Metadata: + Creation Timestamp: 2024-12-10T14:05:34Z + Generation: 1 + Resource Version: 118098180 + UID: 6b96d17c-b788-4bb7-bc0e-098ade53100c +Spec: + Db Clone: + Db Admin Pasword Secret: admin-password + Db Db Unique Name: + Db Name: db1212 + Display Name: dbsystem01312 + Domain: subdda0b5eaa.cluster1.oraclevcn.com + Host Name: host1213 + License Model: BRING_YOUR_OWN_LICENSE + Ssh Public Keys: + oci-publickey + Subnet Id: ocid1.subnet.oc1.ap-mumbai-1.aaaaaaaa5zpzfax66omtbmjwlv4thruyru7focnu7fjcjksujmgwmr6vpbvq + Db System: + Availability Domain: + Compartment Id: + Db Admin Pasword Secret: + Db Backup Config: + Host Name: + Kms Config: + Shape: + Subnet Id: + Id: ocid1.dbsystem.oc1.ap-mumbai-1.anrg6ljrabf7htyadgsso7aessztysrwaj5gcl3tp7ce6asijm2japyvmroa + Kms Config: + Oci Config Map: oci-cred-mumbai + Oci Secret: oci-privatekey + Setup DB Cloning: true +Status: + Availability Domain: OLou:AP-MUMBAI-1-AD-1 + Cpu Core Count: 1 + Data Storage Percentage: 80 + Data Storage Size In G Bs: 512 + Db Edition: ENTERPRISE_EDITION_HIGH_PERFORMANCE + Display Name: dbsystem1234 + Id: ocid1.dbsystem.oc1.ap-mumbai-1.anrg6ljrabf7htyadgsso7aessztysrwaj5gcl3tp7ce6asijm2japyvmroa + License Model: BRING_YOUR_OWN_LICENSE + Network: + Client Subnet: oke-nodesubnet-quick-cluster1-2bebe95db-regional + Domain Name: subdda0b5eaa.cluster1.oraclevcn.com + Host Name: host1234 + Listener Port: 1521 + Scan Dns Name: host1234-scan.subdda0b5eaa.cluster1.oraclevcn.com + Vcn Name: oke-vcn-quick-cluster1-2bebe95db + Node Count: 1 + Reco Storage Size In GB: 256 + Shape: VM.Standard2.1 + State: AVAILABLE + Storage Management: ASM + Subnet Id: ocid1.subnet.oc1.ap-mumbai-1.aaaaaaaa5zpzfax66omtbmjwlv4thruyru7focnu7fjcjksujmgwmr6vpbvq + Time Zone: UTC +Events: \ No newline at end of file diff --git a/docs/dbcs/provisioning/clone_from_backup_dbcs.md b/docs/dbcs/provisioning/clone_from_backup_dbcs.md index 4597cff7..fbc2ad5e 100644 --- a/docs/dbcs/provisioning/clone_from_backup_dbcs.md +++ b/docs/dbcs/provisioning/clone_from_backup_dbcs.md @@ -1,8 +1,8 @@ # Clone DB System from Backup of Existing DB System in OCI Oracle Base Database System (OBDS) -In this use case, an existing OCI OBDS system deployed earlier with the Backup is going to be cloned. +In this use case, you can see how to clone an existing OCI OBDS system deployed earlier with the Backup. -In order to clone OBDS to an existing OBDS system using Backup, get the details of OCID of backup in OCI OBDS. +To clone OBDS to an existing OBDS system using Backup, you must obtain the OCID details for the backup in OCI OBDS. **NOTE:** We are assuming that before this step, you have followed the [prerequisite](./../README.md#prerequisites-to-deploy-a-dbcs-system-using-oracle-db-operator-dbcs-controller) steps to create the configmap and the secrets required during the deployment. @@ -15,15 +15,15 @@ This example uses `clone_dbcs_system_from_backup.yaml` to clone a Single Instanc - Specification for DB Cloning as `dbClone`-> `dbAdminPasswordSecret`,`tdeWalletPasswordSecret`, `dbName`,`hostName`,`displayName`,`licenseModel`,`domain`,`sshPublicKeys`,`subnetId`, `initialDataStorageSizeInGB` **NOTE:** For the details of the parameters to be used in the .yaml file, please refer [here](./dbcs_controller_parameters.md). -Use the file: [clone_dbcs_system_from_backup.yaml](./clone_dbcs_system_from_backup.yaml) for this use case as below: +Use the file: [clone_dbcs_system_from_backup.yaml](./clone_dbcs_system_from_backup.yaml) for this use case, as described in the following steps: -1. Deploy the .yaml file: +1. Deploy the `.yaml` file: ```sh [root@docker-test-server OBDS]# kubectl apply -f clone_dbcs_system_from_backup.yaml dbcssystem.database.oracle.com/dbcssystem-clone created ``` -2. Monitor the Oracle DB Operator Pod `pod/oracle-database-operator-controller-manager-665874bd57-g2cgw` for the progress of the OBDS VMDB creation of PDBs. +2. Monitor the Oracle DB Operator Pod `pod/oracle-database-operator-controller-manager-665874bd57-g2cgw` to track the progress of the OBDS VMDB creation of PDBs. NOTE: Check the DB Operator Pod name in your environment. @@ -33,4 +33,4 @@ NOTE: Check the DB Operator Pod name in your environment. ## Sample Output -[Here](./clone_dbcs_system_from_backup_sample_output.log) is the sample output for cloning an existing OBDS System deployed in OCI using Oracle DB Operator OBDS Controller. +[Here](./clone_dbcs_system_from_backup_sample_output.log) is an example output log for cloning an existing OBDS System deployed in OCI using Oracle DB Operator OBDS Controller. diff --git a/docs/dbcs/provisioning/clone_from_database.md b/docs/dbcs/provisioning/clone_from_database.md index 05b294b5..70b5792e 100644 --- a/docs/dbcs/provisioning/clone_from_database.md +++ b/docs/dbcs/provisioning/clone_from_database.md @@ -2,11 +2,11 @@ In this use case, an existing OCI OBDS system deployed earlier with existing Database is going to be cloned in OCI Base OBDS Service using existing Database ID. -As an pre-requisite, get the details of OCID of database of an existing OBDS System which you want to clone. +As an prerequisite, obtain the details of OCID of database of an existing OBDS System that you want to clone. -**NOTE:** We are assuming that before this step, you have followed the [prerequisite](./../README.md#prerequisites-to-deploy-a-dbcs-system-using-oracle-db-operator-dbcs-controller) steps to create the configmap and the secrets required during the deployment. +**NOTE:** We are assuming that before this step, you have followed the [prerequisite steps](./../README.md#prerequisites-to-deploy-a-dbcs-system-using-oracle-db-operator-dbcs-controller) to create the configmap and the secrets required during the deployment. -This example uses `clone_dbcs_system_from_database.yaml` to clone a Single Instance OBDS VMDB using Oracle DB Operator OBDS Controller with: +This example uses `clone_dbcs_system_from_database.yaml` to clone a Single Instance OBDS VMDB using Oracle DB Operator OBDS Controller with the following: - OCID of existing as `databaseId` - OCI Configmap as `oci-cred` - OCI Secret as `oci-privatekey` @@ -14,7 +14,7 @@ This example uses `clone_dbcs_system_from_database.yaml` to clone a Single Insta - Specification of dbClone as - Details of new DB system for cloning `dbAdminPasswordSecret`,`tdeWalletPasswordSecret`, `dbName`,`hostName`,`displayName`,`licenseModel`,`domain`,`sshPublicKeys`,`subnetId`, `initialDataStorageSizeInGB` **NOTE:** For the details of the parameters to be used in the .yaml file, please refer [here](./dbcs_controller_parameters.md). -Use the file: [clone_dbcs_system_from_database.yaml](./clone_dbcs_system_from_database.yaml) for this use case as below: +Use the file: [clone_dbcs_system_from_database.yaml](./clone_dbcs_system_from_database.yaml) for this use case as described in the following steps: 1. Deploy the .yaml file: ```sh @@ -22,7 +22,7 @@ Use the file: [clone_dbcs_system_from_database.yaml](./clone_dbcs_system_from_da dbcssystem.database.oracle.com/dbcssystem-clone created ``` -2. Monitor the Oracle DB Operator Pod `pod/oracle-database-operator-controller-manager-665874bd57-g2cgw` for the progress of the OBDS VMDB creation of PDBs. +2. Monitor the Oracle DB Operator Pod `pod/oracle-database-operator-controller-manager-665874bd57-g2cgw` to follow the progress of the OBDS VMDB creation of PDBs. NOTE: Check the DB Operator Pod name in your environment. @@ -32,4 +32,4 @@ NOTE: Check the DB Operator Pod name in your environment. ## Sample Output -[Here](./clone_dbcs_system_from_database_sample_output.log) is the sample output for cloning an existing OBDS System deployed in OCI using Oracle DB Operator OBDS Controller. +[This log file](./clone_dbcs_system_from_database_sample_output.log) is an example output log file for cloning an existing OBDS System deployed in OCI using Oracle DB Operator OBDS Controller. diff --git a/docs/dbcs/provisioning/clone_from_existing_dbcs.md b/docs/dbcs/provisioning/clone_from_existing_dbcs.md index 61665188..48f2b375 100644 --- a/docs/dbcs/provisioning/clone_from_existing_dbcs.md +++ b/docs/dbcs/provisioning/clone_from_existing_dbcs.md @@ -1,12 +1,12 @@ # Clone DB System from Existing DB System in OCI Oracle Base Database System (OBDS) -In this use case, an existing OCI OBDS system deployed earlier is going to be cloned in OCI Oracle Base Database System (OBDS). Its a 2 Step operation. +In this use case, an existing OCI OBDS system deployed earlier is going to be cloned in OCI Oracle Base Database System (OBDS). It is a two-step operation. -In order to clone OBDS to an existing OBDS system, get the OCID of DB System ID you want to clone. +To clone OBDS to an existing OBDS system, obtain the OCID of the database system ID that you want to clone. -**NOTE:** We are assuming that before this step, you have followed the [prerequisite](./../README.md#prerequisites-to-deploy-a-dbcs-system-using-oracle-db-operator-dbcs-controller) steps to create the configmap and the secrets required during the deployment. +**NOTE:** We are assuming that before this step, you have followed the [prerequisite steps](./../README.md#prerequisites-to-deploy-a-dbcs-system-using-oracle-db-operator-dbcs-controller) to create the configmap and the secrets required during the deployment. -This example uses `clone_dbcs_system.yaml` to clone a Single Instance OBDS VMDB using Oracle DB Operator OBDS Controller with: +This example uses `clone_dbcs_system.yaml` to clone a Single Instance OBDS VMDB using Oracle DB Operator OBDS Controller with the following: - OCID of existing VMDB as `id` to be cloned. - OCI Configmap as `oci-cred` @@ -15,15 +15,15 @@ This example uses `clone_dbcs_system.yaml` to clone a Single Instance OBDS VMDB - Specification of DB System been cloned as `dbClone` -> `dbAdminPaswordSecret`, `dbName`,`hostName`,`displayName`,`licenseModel`,`domain`,`sshPublicKeys`,`subnetId`. These must be unique and new details for new cloned DB system to be created. **NOTE:** For the details of the parameters to be used in the .yaml file, please refer [here](./dbcs_controller_parameters.md). -Use the file: [clone_dbcs_system.yaml](./clone_dbcs_system.yaml) for this use case as below: +Use the file: [clone_dbcs_system.yaml](./clone_dbcs_system.yaml) for this use case as described in the following steps: -1. Deploy the .yaml file: +1. Deploy the `.yaml` file: ```sh [root@docker-test-server DBCS]# kubectl apply -f clone_dbcs_system.yaml dbcssystem.database.oracle.com/dbcssystem-clone created ``` -2. Monitor the Oracle DB Operator Pod `pod/oracle-database-operator-controller-manager-665874bd57-g2cgw` for the progress of the DBCS VMDB creation of PDBs. +2. Monitor the Oracle DB Operator Pod `pod/oracle-database-operator-controller-manager-665874bd57-g2cgw` to follow the progress of the DBCS VMDB creation of PDBs. NOTE: Check the DB Operator Pod name in your environment. @@ -33,4 +33,4 @@ NOTE: Check the DB Operator Pod name in your environment. ## Sample Output -[Here](./clone_dbcs_system_sample_output.log) is the sample output for cloning an existing DBCS System deployed in OCI using Oracle DB Operator DBCS Controller. +[This log file](./clone_dbcs_system_sample_output.log) is an example output log file for cloning an existing DBCS System deployed in OCI using Oracle DB Operator DBCS Controller. diff --git a/docs/dbcs/provisioning/create_dbcs_with_kms.md b/docs/dbcs/provisioning/create_dbcs_with_kms.md index 97d912d4..b6005e7f 100644 --- a/docs/dbcs/provisioning/create_dbcs_with_kms.md +++ b/docs/dbcs/provisioning/create_dbcs_with_kms.md @@ -2,12 +2,13 @@ In this use case, an OCI OBDS system is deployed using Oracle DB Operator OBDS controller along with KMS Vault configuration -**NOTE** It is assumed that before this step, you have followed the [prerequisite](./../README.md#prerequsites-to-deploy-a-dbcs-system-using-oracle-db-operator-dbcs-controller) steps to create the configmap and the secrets required during the deployment. +**NOTE** We assume that before this procedure, you have followed the [prerequisite steps](./../README.md#prerequsites-to-deploy-a-dbcs-system-using-oracle-db-operator-dbcs-controller) to create the configmap and the secrets required during the deployment. ## Pre-requisites for KMS Vaults related to OBDS System -There is also other set of pre-requisites for KMS Vaults related to dynamic group and policies. Please follow instructions for same. -1. Create Dynamic group with rule `ALL {resource.compartment.id =` and give it some name. -2. Create policy in your compartment for this dynamic group to access to key/vaults by database. +You also must have completed the prerequisites for KMS Vaults related to dynamic group and policies. + +1. Create Dynamic group with rule `ALL {resource.compartment.id =` and give it a name. +2. Create a policy in your compartment for this dynamic group to grant it access to key/vaults by database. ```txt Allow dynamic-group <> to manage secret-family in compartment <> @@ -17,7 +18,7 @@ Allow dynamic-group <> to manage keys in compartment <> Allow dynamic-group <> to manage vaults in compartment <> ``` -E.g +For example: ```txt ALL {resource.compartment.id = 'ocid1.compartment.oc1..aaaaaaaa63yqilqhgxv3dszur3a2fgwc64ohpfy43vpqjm7q5zq4q4yaw72a'} @@ -29,9 +30,9 @@ Allow dynamic-group db_dynamic_group to manage database-family in compartment sa Allow dynamic-group db_dynamic_group to manage keys in compartment sauahuja Allow dynamic-group db_dynamic_group to manage vaults in compartment sauahuja ``` -3. Do also create KMS Vault and KMS Key in order to use it during OBDS provisioning. We are going to refer those variables (`vaultName`, `keyName`) in the yaml file. +3. Create KMS Vault and KMS Keys so that you can use it during OBDS provisioning. We refer to those variables (`vaultName`, `keyName`) in the yaml file. -This example uses `dbcs_service_with_kms.yaml` to deploy a Single Instance OBDS VMDB using Oracle DB Operator OBDS Controller with: +This example uses `dbcs_service_with_kms.yaml` to deploy a Single Instance OBDS VMDB using Oracle DB Operator OBDS Controller with the following: - OCI Configmap as `oci-cred` - OCI Secret as `oci-privatekey` @@ -50,17 +51,17 @@ This example uses `dbcs_service_with_kms.yaml` to deploy a Single Instance OBDS - KMS Compartment Id as `ocid1.compartment.oc1..aaaaaaaa63yqilqhgxv3dszur3a2fgwc64ohpfy43vpqjm7q5zq4q4yaw72a` - KMS Key Name as `dbkey` -**NOTE:** For the details of the parameters to be used in the .yaml file, please refer [here](./dbcs_controller_parameters.md). While giving KMS Vault make sure not to pass TDE wallet password in DB creation as either of them can be only used for encryption. +**NOTE:** For the details of the parameters used in the `.yaml` file, see: [DBCS Controller Parameters](./dbcs_controller_parameters.md). When providing the KMS Vault, ensure that you do not pass the TDE wallet password in database creation, because either of them can be used only for encryption. -Use the file: [dbcs_service_with_kms.yaml](./dbcs_service_with_kms.yaml) for this use case as below: +For the steps that follow, use this file: [dbcs_service_with_kms.yaml](./dbcs_service_with_kms.yaml). Complete the following steps: -1. Deploy the .yaml file: +1. Deploy the `.yaml` file: ```bash [root@docker-test-server OBDS]# kubectl apply -f dbcs_service_with_kms.yaml dbcssystem.database.oracle.com/dbcssystem-create created ``` -2. Monitor the Oracle DB Operator Pod `pod/oracle-database-operator-controller-manager-665874bd57-g2cgw` for the progress of the OBDS VMDB deployment. +2. Monitor the Oracle DB Operator Pod `pod/oracle-database-operator-controller-manager-665874bd57-g2cgw` to monitor the progress of the OBDS VMDB deployment. NOTE: Check the DB Operator Pod name in your environment. @@ -70,4 +71,4 @@ NOTE: Check the DB Operator Pod name in your environment. ## Sample Output -[Here](./dbcs_service_with_kms_sample_output.log) is the sample output for a OBDS System deployed in OCI using Oracle DB Operator OBDS Controller with KMS configurations. +[This log file](./dbcs_service_with_kms_sample_output.log) is an example output log file for a OBDS System deployed in OCI using Oracle DB Operator OBDS Controller with KMS configurations. diff --git a/docs/dbcs/provisioning/dataguard_in_database.yaml b/docs/dbcs/provisioning/dataguard_in_database.yaml new file mode 100644 index 00000000..25fa8dd1 --- /dev/null +++ b/docs/dbcs/provisioning/dataguard_in_database.yaml @@ -0,0 +1,20 @@ +apiVersion: database.oracle.com/v4 +kind: DbcsSystem +metadata: + name: dbcssystem-existing +spec: + id: "ocid1.dbsystem.oc1.ap-mumbai-1.anrg6ljrabf7htyagag6vwgkdcf5g6srfykrsqnfnryfskfhblntsjypiajq" + ociConfigMap: "oci-cred" + ociSecret: "oci-privatekey" + dataGuard: + primaryDatabaseId: "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a" + dbAdminPasswordSecret: "admin-password" + protectionMode: "MAXIMUM_PERFORMANCE" + transportType: "ASYNC" + enabled: true + displayName: "standbydbsystem2" + availabilityDomain: "OLou:AP-MUMBAI-1-AD-1" + shape: "VM.Standard2.1" + subnetId: "ocid1.subnet.oc1.ap-mumbai-1.aaaaaaaaklvzufcgma65ibn4al7xukjmyfz7nnrpaewfzbffz5zrnz3htweq" + hostName: "shost1234" + diff --git a/docs/dbcs/provisioning/dataguard_in_database_sample_output.log b/docs/dbcs/provisioning/dataguard_in_database_sample_output.log new file mode 100644 index 00000000..e21c3130 --- /dev/null +++ b/docs/dbcs/provisioning/dataguard_in_database_sample_output.log @@ -0,0 +1,226 @@ +2025-06-18T11:52:25Z INFO No Data Guard association found for the database {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a"} +2025-06-18T11:52:28Z INFO Data Guard association created successfully. {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62"} +2025-06-18T11:53:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T11:53:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T11:54:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T11:54:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T11:55:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T11:55:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T11:56:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T11:56:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T11:57:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T11:57:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T11:58:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T11:58:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T11:59:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T11:59:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:00:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:00:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:01:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:01:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:02:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:02:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:03:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:03:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:04:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:04:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:05:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:05:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:06:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:06:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:07:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:07:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:08:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:08:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:09:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:09:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:10:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:10:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:11:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:11:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:12:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:12:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:13:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:13:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:14:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:14:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:15:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:15:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:16:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:16:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:17:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:17:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:18:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:18:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:19:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:19:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:20:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:20:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:21:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:21:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:22:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:22:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:23:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:23:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:24:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:24:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:25:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:25:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:26:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:26:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:27:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:27:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:28:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:28:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:29:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:29:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:30:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:30:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:31:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:31:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:32:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:32:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:33:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:33:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:34:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:34:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:35:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:35:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:36:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:36:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:37:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:37:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:38:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:38:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:39:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:39:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:40:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:40:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:41:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:41:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:42:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:42:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:43:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:43:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:44:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T12:44:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T12:45:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T13:42:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T13:42:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T13:43:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T13:43:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T13:44:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T13:44:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} +2025-06-18T13:45:28Z INFO Polling Data Guard association state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a", "AssociationID": "ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra"} +2025-06-18T13:45:29Z INFO Current state {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "60685efa-37f7-4185-b109-9b25c334ec62", "State": "PROVISIONING"} + + + + kubectl describe dbcssystems.database.oracle.com dbcssystem-existing +Name: dbcssystem-existing +Namespace: default +Labels: +Annotations: lastSuccessfulSpec: + {"dbSystem":{"compartmentId":"","availabilityDomain":"","subnetId":"","shape":"","hostName":"","dbAdminPasswordSecret":"","dbBackupConfig"... +API Version: database.oracle.com/v4 +Kind: DbcsSystem +Metadata: + Creation Timestamp: 2025-06-18T11:47:51Z + Generation: 3 + Resource Version: 198491318 + UID: dd584c17-bed5-477c-b1ae-90deed556ee9 +Spec: + Data Guard: + Availability Domain: OLou:AP-MUMBAI-1-AD-1 + Db Admin Password Secret: admin-password + Display Name: sdbsystem1234 + Enabled: true + Host Name: shost1234 + Primary Database Id: ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a + Protection Mode: MAXIMUM_PERFORMANCE + Shape: VM.Standard2.1 + Subnet Id: ocid1.subnet.oc1.ap-mumbai-1.aaaaaaaaklvzufcgma65ibn4al7xukjmyfz7nnrpaewfzbffz5zrnz3htweq + Transport Type: ASYNC + Db System: + Availability Domain: + Compartment Id: + Db Admin Password Secret: + Db Backup Config: + Host Name: + Kms Config: + Shape: + Subnet Id: + Kms Config: + Oci Config Map: oci-cred-mumbai + Oci Secret: oci-privatekey +Status: + Availability Domain: OLou:AP-MUMBAI-1-AD-1 + Cpu Core Count: 1 + Data Guard Status: + Db Admin Password Secret: admin-password + Id: ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyaqnw7fmovwf6hn4ja2zw6lk663tmq22bl6333m53q36ra + Lifecycle State: AVAILABLE + Peer Data Guard Association Id: ocid1.dgassociation.oc1.ap-mumbai-1.anrg6ljrabf7htyabee2lili5bw2fhfykp6ex762nvg5vihc4zl33oxvzp4q + Peer Database Id: ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya5zaiiz6ydeomvqysoin5cylevcy5z5ritblpnl3qjhga + Peer Db Home Id: ocid1.dbhome.oc1.ap-mumbai-1.anrg6ljrqlb5nxiaewh5smvdfbv7t5xoz4pjagwcm5py72eoyoejlxfcyhzq + Peer Db System Id: ocid1.dbsystem.oc1.ap-mumbai-1.anrg6ljrabf7htyaywwjkya5wcimrcsxip777dld3fluu2nlb3kf6mjjtnuq + Peer Role: STANDBY + Primary Database Id: ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a + Protection Mode: MAXIMUM_PERFORMANCE + Transport Type: ASYNC + Data Storage Percentage: 80 + Data Storage Size In G Bs: 256 + Db Clone Status: + Db Db Unique Name: + Host Name: + Db Edition: ENTERPRISE_EDITION_HIGH_PERFORMANCE + Display Name: sdbsystem1234 + Id: ocid1.dbsystem.oc1.ap-mumbai-1.anrg6ljrabf7htyaywwjkya5wcimrcsxip777dld3fluu2nlb3kf6mjjtnuq + Kms Details Status: + License Model: BRING_YOUR_OWN_LICENSE + Network: + Client Subnet: oke-k8sApiEndpoint-subnet-quick-cluster1-2bebe95db-regional + Domain Name: sub0ea2e07ef.cluster1.oraclevcn.com + Host Name: shost1234 + Listener Port: 1521 + Scan Dns Name: shost1234-scan.sub0ea2e07ef.cluster1.oraclevcn.com + Vcn Name: oke-vcn-quick-cluster1-2bebe95db + Node Count: 1 + Reco Storage Size In GB: 256 + Shape: VM.Standard2.1 + State: AVAILABLE + Storage Management: ASM + Subnet Id: ocid1.subnet.oc1.ap-mumbai-1.aaaaaaaaklvzufcgma65ibn4al7xukjmyfz7nnrpaewfzbffz5zrnz3htweq + Time Zone: UTC + Work Requests: + Operation Id: ocid1.coreservicesworkrequest.oc1.ap-mumbai-1.abrg6ljr745m6kvhdlchnbfpgfi53oabqpbp6edr4sihrrlhh3vpx272q32a + Operation Type: Create Data Guard + Percent Complete: 100 + Time Accepted: 2025-06-18 11:23:18.919 +0000 UTC + Time Finished: 2025-06-18 11:28:17.598 +0000 UTC + Time Started: 2025-06-18 11:23:28.016 +0000 UTC + Operation Id: ocid1.coreservicesworkrequest.oc1.ap-mumbai-1.abrg6ljrxow6rzn63igski66j7264ft3bnnjkvg74vd6xf77rqg6tkakrlkq + Operation Type: Create Data Guard + Percent Complete: 100 + Time Accepted: 2025-06-18 11:15:30.139 +0000 UTC + Time Finished: 2025-06-18 11:22:04.763 +0000 UTC + Time Started: 2025-06-18 11:15:36.599 +0000 UTC + Operation Id: ocid1.coreservicesworkrequest.oc1.ap-mumbai-1.abrg6ljrzj4cnxwitxjjlcwwymbpby7chckrq5vtm2hpykprgxrm5rtw2eqq + Operation Type: Create Data Guard + Percent Complete: 100 + Time Accepted: 2025-06-18 10:59:14.372 +0000 UTC + Time Finished: 2025-06-18 11:15:15.936 +0000 UTC + Time Started: 2025-06-18 10:59:20.854 +0000 UTC + Operation Id: ocid1.coreservicesworkrequest.oc1.ap-mumbai-1.abrg6ljrsfschtlkucf65m575jyswb6xsk3mpfx26gsrumrfa6huzhjswbxq + Operation Type: Create Data Guard + Percent Complete: 100 + Time Accepted: 2025-06-18 07:20:00.043 +0000 UTC + Time Finished: 2025-06-18 07:43:54.273 +0000 UTC + Time Started: 2025-06-18 07:20:07.169 +0000 UTC + Operation Id: ocid1.coreservicesworkrequest.oc1.ap-mumbai-1.abrg6ljryitxrazcuutk3ttxscpudqtaeq7o3havhhh3aov5tdtvuhmnclma + Operation Type: Create DB System + Percent Complete: 100 + Time Accepted: 2025-06-13 11:53:36.483 +0000 UTC + Time Finished: 2025-06-13 14:10:52.388 +0000 UTC + Time Started: 2025-06-13 11:53:43.916 +0000 UTC +Events: diff --git a/docs/dbcs/provisioning/dataguard_to_database.md b/docs/dbcs/provisioning/dataguard_to_database.md new file mode 100644 index 00000000..67c923b6 --- /dev/null +++ b/docs/dbcs/provisioning/dataguard_to_database.md @@ -0,0 +1,33 @@ +# Setup Dataguard Association to Existing Database of DB System in OCI Base DBCS Service + +In this use case, an existing OCI DBCS system previously deployed with an existing Database is provided with an Oracle Data Guard association in OCI Base DBCS Service using the existing Database ID. + +As a prerequisite, obtain the OCID details for the database of the existing DBCS System that you want to clone. + +**NOTE:** We are assuming that before this step, you have followed the [prerequisite](./../README.md#prerequisites-to-deploy-a-dbcs-system-using-oracle-db-operator-dbcs-controller) steps to create the configmap and the secrets required during the deployment. + +This example uses `dataguard_in_database.yaml` to clone a Single Instance DBCS VMDB using Oracle DB Operator DBCS Controller with: +- OCI Configmap as `oci-cred` +- OCI Secret as `oci-privatekey` +- Specification of dataGuard as - Details for dataguard setup `primaryDatabaseId`,`dbAdminPasswordSecret`, `protectionMode`,`transportType`,`displayName`,`availabilityDomain`,`shape`,`subnetId`,`hostName`. +**NOTE:** For the details of the parameters to be used in the .yaml file, please refer [here](./dbcs_controller_parameters.md). + +Use the file: [dataguard_in_database.yaml](./dataguard_in_database.yaml) for this use case as below: + +1. Deploy the `.yaml` file: +```sh +[root@docker-test-server DBCS]# kubectl apply -f dataguard_in_database.yaml +dbcssystem.database.oracle.com/dbcssystem-existing configured +``` + +2. Monitor the Oracle DB Operator Pod `pod/oracle-database-operator-controller-manager-665874bd57-g2cgw` to follow the progress of the DBCS VMDB creation of PDBs. + +NOTE: Check the DB Operator Pod name in your environment. + +``` +[root@docker-test-server DBCS]# kubectl logs -f pod/oracle-database-operator-controller-manager-665874bd57-g2cgw -n oracle-database-operator-system +``` + +## Sample Output + +[Here](./dataguard_in_database_sample_output.log) is the example output log for cloning an existing DBCS System deployed in OCI using Oracle DB Operator DBCS Controller. diff --git a/docs/dbcs/provisioning/delete_pdb.md b/docs/dbcs/provisioning/delete_pdb.md index 84d676bc..b9bb8b8d 100644 --- a/docs/dbcs/provisioning/delete_pdb.md +++ b/docs/dbcs/provisioning/delete_pdb.md @@ -1,32 +1,32 @@ # Delete PDB of an existing DBCS System -In this use case, an existing OCI DBCS system deployed earlier is going to have PDB/PDBs deleted. Its a 2 Step operation. +In this use case, an existing OCI DBCS system deployed earlier is going to have PDB/PDBs deleted. This is a two-step operation. -In order to create PDBs to an existing DBCS system, the steps will be: +To create PDBs and add them to an existing DBCS system, the two steps are as follows: 1. Bind the existing DBCS System to DBCS Controller. 2. Apply the change to delete PDBs. -**NOTE:** We are assuming that before this step, you have followed the [prerequisite](./../README.md#prerequsites-to-deploy-a-dbcs-system-using-oracle-db-operator-dbcs-controller) steps to create the configmap and the secrets required during the deployment. +**NOTE:** We are assuming that before this step, you have followed the [prerequisite steps](./../README.md#prerequsites-to-deploy-a-dbcs-system-using-oracle-db-operator-dbcs-controller) to create the configmap and the secrets required during the deployment. -As step 1, first bind the existing DBCS System to DBCS Controller following [documentation](./../provisioning/bind_to_existing_dbcs_system.md). After successful binding, it will show as below- +In the first step, you first bind the existing DBCS System to DBCS Controller following [the Bind to Existing DBCS System documentation](./../provisioning/bind_to_existing_dbcs_system.md). After successful binding, it will appear as follows: ```bash kubectl get dbcssystems NAME AGE dbcssystem-existing 3m33s ``` -This example uses `deletepdb_in_existing_dbcs_system_list.yaml` to delete PDBs of a Single Instance DBCS VMDB using Oracle DB Operator DBCS Controller with: +This example uses `deletepdb_in_existing_dbcs_system_list.yaml` to delete PDBs of a Single Instance DBCS VMDB using Oracle DB Operator DBCS Controller with the following: - OCID of existing VMDB as `ocid1.dbsystem.oc1.iad.anuwcljsabf7htyag4akvoakzw4qk7cae55qyp7hlffbouozvyl5ngoputza` - OCI Configmap as `oci-cred` - OCI Secret as `oci-privatekey` - PDB Name to be deleted e.g `pdb_sauahuja_11` and `pdb_sauahuja_12` -**NOTE:** For the details of the parameters to be used in the .yaml file, please refer [here](./dbcs_controller_parameters.md). +**NOTE:** For the details of the parameters to be used in the `.yaml` file, see: [DBCS Controller Parameters](./dbcs_controller_parameters.md). -Use the file: [deletepdb_in_existing_dbcs_system_list.yaml](./deletepdb_in_existing_dbcs_system_list.yaml) for this use case as below: +Use the file: [deletepdb_in_existing_dbcs_system_list.yaml](./deletepdb_in_existing_dbcs_system_list.yaml) for this use case as described in the following steps: -1. Deploy the .yaml file: +1. Deploy the `.yaml` file: ```sh [root@docker-test-server DBCS]# kubectl apply -f deletepdb_in_existing_dbcs_system_list.yaml dbcssystem.database.oracle.com/dbcssystem-existing configured @@ -40,11 +40,11 @@ NOTE: Check the DB Operator Pod name in your environment. [root@docker-test-server DBCS]# kubectl logs -f pod/oracle-database-operator-controller-manager-665874bd57-g2cgw -n oracle-database-operator-system ``` -3. Remove DBCS Systems resource- +3. Remove the DBCS Systems resource- ```bash kubectl delete -f deletepdb_in_existing_dbcs_system_list.yaml ``` ## Sample Output -[Here](./deletepdb_in_existing_dbcs_system_list_sample_output.log) is the sample output for deletion of PDBs from an existing DBCS System deployed in OCI using Oracle DB Operator DBCS Controller. \ No newline at end of file +[This example log file](./deletepdb_in_existing_dbcs_system_list_sample_output.log) is an example log file output for deletion of PDBs from an existing DBCS System deployed in OCI using Oracle DB Operator DBCS Controller. \ No newline at end of file diff --git a/docs/dbcs/provisioning/disable_dataguard_in_database.yaml b/docs/dbcs/provisioning/disable_dataguard_in_database.yaml new file mode 100644 index 00000000..f8cb8463 --- /dev/null +++ b/docs/dbcs/provisioning/disable_dataguard_in_database.yaml @@ -0,0 +1,12 @@ +apiVersion: database.oracle.com/v4 +kind: DbcsSystem +metadata: + name: dbcssystem-existing +spec: + id: "ocid1.dbsystem.oc1.ap-mumbai-1.anrg6ljrabf7htyagag6vwgkdcf5g6srfykrsqnfnryfskfhblntsjypiajq" + ociConfigMap: "oci-cred-mumbai" + ociSecret: "oci-privatekey" + dataGuard: + enabled: false # <=== Mark for deletion (don't enable) + isDelete: true + peerDbSystemId: ocid1.dbsystem.oc1.ap-mumbai-1.anrg6ljrabf7htyakjqrailvboa6efrypg3oxaqis44433rjd3hjuzeygcuq \ No newline at end of file diff --git a/docs/dbcs/provisioning/disable_dataguard_in_database_sample_output.log b/docs/dbcs/provisioning/disable_dataguard_in_database_sample_output.log new file mode 100644 index 00000000..af0c1e61 --- /dev/null +++ b/docs/dbcs/provisioning/disable_dataguard_in_database_sample_output.log @@ -0,0 +1,141 @@ +2025-06-20T16:40:02Z INFO Terminating peer DB system {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "61f3d6fa-6bc2-480f-a16b-2bf3f0262c36", "peerDbSystemId": "ocid1.dbsystem.oc1.ap-mumbai-1.anrg6ljrabf7htyakjqrailvboa6efrypg3oxaqis44433rjd3hjuzeygcuq"} +2025-06-20T16:40:03Z INFO Waiting for work request to complete {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "61f3d6fa-6bc2-480f-a16b-2bf3f0262c36", "workRequestID": "ocid1.coreservicesworkrequest.oc1.ap-mumbai-1.abrg6ljrm6qzswsn5x3payealiktawjfpnfggonbbznu3ylxzth2nofn3p4a"} +2025-06-20T16:40:04Z INFO Polling WorkRequest status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "61f3d6fa-6bc2-480f-a16b-2bf3f0262c36", "status": "ACCEPTED"} +2025-06-20T16:40:35Z INFO Polling WorkRequest status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "61f3d6fa-6bc2-480f-a16b-2bf3f0262c36", "status": "IN_PROGRESS"} +2025-06-20T16:41:06Z INFO Polling WorkRequest status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "61f3d6fa-6bc2-480f-a16b-2bf3f0262c36", "status": "IN_PROGRESS"} +2025-06-20T16:41:36Z INFO Polling WorkRequest status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "61f3d6fa-6bc2-480f-a16b-2bf3f0262c36", "status": "IN_PROGRESS"} +2025-06-20T16:42:07Z INFO Polling WorkRequest status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "61f3d6fa-6bc2-480f-a16b-2bf3f0262c36", "status": "IN_PROGRESS"} +2025-06-20T16:42:38Z INFO Polling WorkRequest status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "61f3d6fa-6bc2-480f-a16b-2bf3f0262c36", "status": "IN_PROGRESS"} +2025-06-20T16:43:09Z INFO Polling WorkRequest status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "61f3d6fa-6bc2-480f-a16b-2bf3f0262c36", "status": "IN_PROGRESS"} +2025-06-20T16:43:40Z INFO Polling WorkRequest status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "61f3d6fa-6bc2-480f-a16b-2bf3f0262c36", "status": "IN_PROGRESS"} +2025-06-20T16:44:10Z INFO Polling WorkRequest status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "61f3d6fa-6bc2-480f-a16b-2bf3f0262c36", "status": "IN_PROGRESS"} +2025-06-20T16:44:41Z INFO Polling WorkRequest status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "61f3d6fa-6bc2-480f-a16b-2bf3f0262c36", "status": "IN_PROGRESS"} +2025-06-20T16:45:12Z INFO Polling WorkRequest status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "61f3d6fa-6bc2-480f-a16b-2bf3f0262c36", "status": "IN_PROGRESS"} +2025-06-20T16:45:43Z INFO Polling WorkRequest status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "61f3d6fa-6bc2-480f-a16b-2bf3f0262c36", "status": "IN_PROGRESS"} +2025-06-20T16:46:14Z INFO Polling WorkRequest status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "61f3d6fa-6bc2-480f-a16b-2bf3f0262c36", "status": "IN_PROGRESS"} +2025-06-20T16:46:45Z INFO Polling WorkRequest status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "61f3d6fa-6bc2-480f-a16b-2bf3f0262c36", "status": "IN_PROGRESS"} +2025-06-20T16:47:15Z INFO Polling WorkRequest status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "61f3d6fa-6bc2-480f-a16b-2bf3f0262c36", "status": "IN_PROGRESS"} +2025-06-20T16:47:46Z INFO Polling WorkRequest status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "61f3d6fa-6bc2-480f-a16b-2bf3f0262c36", "status": "IN_PROGRESS"} +2025-06-20T16:48:17Z INFO Polling WorkRequest status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "61f3d6fa-6bc2-480f-a16b-2bf3f0262c36", "status": "IN_PROGRESS"} +2025-06-20T16:48:48Z INFO Polling WorkRequest status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "61f3d6fa-6bc2-480f-a16b-2bf3f0262c36", "status": "IN_PROGRESS"} +2025-06-20T16:49:19Z INFO Polling WorkRequest status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "61f3d6fa-6bc2-480f-a16b-2bf3f0262c36", "status": "IN_PROGRESS"} +2025-06-20T16:49:50Z INFO Polling WorkRequest status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "61f3d6fa-6bc2-480f-a16b-2bf3f0262c36", "status": "IN_PROGRESS"} +2025-06-20T16:50:21Z INFO Polling WorkRequest status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "61f3d6fa-6bc2-480f-a16b-2bf3f0262c36", "status": "IN_PROGRESS"} +2025-06-20T16:50:52Z INFO Polling WorkRequest status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "61f3d6fa-6bc2-480f-a16b-2bf3f0262c36", "status": "IN_PROGRESS"} +2025-06-20T16:51:22Z INFO Polling WorkRequest status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "61f3d6fa-6bc2-480f-a16b-2bf3f0262c36", "status": "IN_PROGRESS"} +2025-06-20T16:51:53Z INFO Polling WorkRequest status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "61f3d6fa-6bc2-480f-a16b-2bf3f0262c36", "status": "IN_PROGRESS"} +2025-06-20T16:52:24Z INFO Polling WorkRequest status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "61f3d6fa-6bc2-480f-a16b-2bf3f0262c36", "status": "IN_PROGRESS"} +2025-06-20T16:52:55Z INFO Polling WorkRequest status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "61f3d6fa-6bc2-480f-a16b-2bf3f0262c36", "status": "SUCCEEDED"} +2025-06-20T16:52:55Z INFO WorkRequest succeeded {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "61f3d6fa-6bc2-480f-a16b-2bf3f0262c36", "workRequestID": "ocid1.coreservicesworkrequest.oc1.ap-mumbai-1.abrg6ljrm6qzswsn5x3payealiktawjfpnfggonbbznu3ylxzth2nofn3p4a"} +2025-06-20T16:52:55Z INFO Successfully deleted Data Guard association {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-existing","namespace":"default"}, "namespace": "default", "name": "dbcssystem-existing", "reconcileID": "61f3d6fa-6bc2-480f-a16b-2bf3f0262c36"} + +kubectl describe dbcssystems.database.oracle.com dbcssystem-existing +Name: dbcssystem-existing +Namespace: default +Labels: +Annotations: lastSuccessfulSpec: + {"id":"ocid1.dbsystem.oc1.ap-mumbai-1.anrg6ljrabf7htyagag6vwgkdcf5g6srfykrsqnfnryfskfhblntsjypiajq","ociConfigMap":"oci-cred-mumbai","ociS... +API Version: database.oracle.com/v4 +Kind: DbcsSystem +Metadata: + Creation Timestamp: 2025-06-20T16:39:48Z + Generation: 2 + Resource Version: 199426196 + UID: 36dedd91-369a-4f7f-bde7-3dcd0fb80bc0 +Spec: + Data Guard: + Is Delete: true + Peer Db System Id: ocid1.dbsystem.oc1.ap-mumbai-1.anrg6ljrabf7htyakjqrailvboa6efrypg3oxaqis44433rjd3hjuzeygcuq + Id: ocid1.dbsystem.oc1.ap-mumbai-1.anrg6ljrabf7htyagag6vwgkdcf5g6srfykrsqnfnryfskfhblntsjypiajq + Oci Config Map: oci-cred-mumbai + Oci Secret: oci-privatekey +Status: + Availability Domain: OLou:AP-MUMBAI-1-AD-1 + Cpu Core Count: 1 + Data Guard Status: + Data Storage Percentage: 80 + Data Storage Size In G Bs: 256 + Db Clone Status: + Db Db Unique Name: + Host Name: + Db Edition: ENTERPRISE_EDITION_HIGH_PERFORMANCE + Db Info: + Db Home Id: ocid1.dbhome.oc1.ap-mumbai-1.anrg6ljrqlb5nxia62a4renaws56iay2nboazrndrw2p5fxgdtxbjggjkjbq + Db Name: cdb1234 + Db Unique Name: cdb1234_dgk_bom + Db Workload: OLTP + Id: ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a + Display Name: dbsystem123 + Id: ocid1.dbsystem.oc1.ap-mumbai-1.anrg6ljrabf7htyagag6vwgkdcf5g6srfykrsqnfnryfskfhblntsjypiajq + Kms Details Status: + License Model: BRING_YOUR_OWN_LICENSE + Network: + Client Subnet: oke-k8sApiEndpoint-subnet-quick-cluster1-2bebe95db-regional + Domain Name: sub0ea2e07ef.cluster1.oraclevcn.com + Host Name: host123 + Listener Port: 1521 + Scan Dns Name: host123-scan.sub0ea2e07ef.cluster1.oraclevcn.com + Vcn Name: oke-vcn-quick-cluster1-2bebe95db + Node Count: 1 + Reco Storage Size In GB: 256 + Shape: VM.Standard2.1 + State: AVAILABLE + Storage Management: ASM + Subnet Id: ocid1.subnet.oc1.ap-mumbai-1.aaaaaaaaklvzufcgma65ibn4al7xukjmyfz7nnrpaewfzbffz5zrnz3htweq + Time Zone: UTC + Work Requests: + Operation Id: ocid1.coreservicesworkrequest.oc1.ap-mumbai-1.abrg6ljrezh3gctuqydvhcwp3s7ciihu2wnjzzrmz7rfw5jnkiwarjxbz6ia + Operation Type: Create Data Guard + Percent Complete: 100 + Time Accepted: 2025-06-20 11:51:32.803 +0000 UTC + Time Finished: 2025-06-20 14:07:19.763 +0000 UTC + Time Started: 2025-06-20 11:51:39.534 +0000 UTC + Operation Id: ocid1.coreservicesworkrequest.oc1.ap-mumbai-1.abrg6ljromubu2srfzznxbgv6ussbgbii6ln6t7sb6nkpwkab4meqxoionjq + Operation Type: Create Data Guard + Percent Complete: 100 + Time Accepted: 2025-06-19 16:39:05.542 +0000 UTC + Time Finished: 2025-06-19 18:37:19.007 +0000 UTC + Time Started: 2025-06-19 16:39:13.226 +0000 UTC + Operation Id: ocid1.coreservicesworkrequest.oc1.ap-mumbai-1.abrg6ljr7oy4dlwckl7r6ukwg5mxxnjjocp5wqp5ehsx3zblow2zza6h7j4q + Operation Type: Create Data Guard + Percent Complete: 100 + Time Accepted: 2025-06-19 09:30:22.587 +0000 UTC + Time Finished: 2025-06-19 11:51:06.723 +0000 UTC + Time Started: 2025-06-19 09:30:30.03 +0000 UTC + Operation Id: ocid1.coreservicesworkrequest.oc1.ap-mumbai-1.abrg6ljrlansxzbmlry2uw3f2wlg3c32poxxpqqxiqvs4jeza5kp3a3zjdaa + Operation Type: Create Data Guard + Percent Complete: 100 + Time Accepted: 2025-06-18 11:52:28.133 +0000 UTC + Time Finished: 2025-06-18 13:54:02.717 +0000 UTC + Time Started: 2025-06-18 11:52:31.256 +0000 UTC + Operation Id: ocid1.coreservicesworkrequest.oc1.ap-mumbai-1.abrg6ljr745m6kvhdlchnbfpgfi53oabqpbp6edr4sihrrlhh3vpx272q32a + Operation Type: Create Data Guard + Percent Complete: 100 + Time Accepted: 2025-06-18 11:23:18.919 +0000 UTC + Time Finished: 2025-06-18 11:28:17.598 +0000 UTC + Time Started: 2025-06-18 11:23:28.016 +0000 UTC + Operation Id: ocid1.coreservicesworkrequest.oc1.ap-mumbai-1.abrg6ljrxow6rzn63igski66j7264ft3bnnjkvg74vd6xf77rqg6tkakrlkq + Operation Type: Create Data Guard + Percent Complete: 100 + Time Accepted: 2025-06-18 11:15:30.139 +0000 UTC + Time Finished: 2025-06-18 11:22:04.763 +0000 UTC + Time Started: 2025-06-18 11:15:36.599 +0000 UTC + Operation Id: ocid1.coreservicesworkrequest.oc1.ap-mumbai-1.abrg6ljrzj4cnxwitxjjlcwwymbpby7chckrq5vtm2hpykprgxrm5rtw2eqq + Operation Type: Create Data Guard + Percent Complete: 100 + Time Accepted: 2025-06-18 10:59:14.372 +0000 UTC + Time Finished: 2025-06-18 11:15:15.936 +0000 UTC + Time Started: 2025-06-18 10:59:20.854 +0000 UTC + Operation Id: ocid1.coreservicesworkrequest.oc1.ap-mumbai-1.abrg6ljrsfschtlkucf65m575jyswb6xsk3mpfx26gsrumrfa6huzhjswbxq + Operation Type: Create Data Guard + Percent Complete: 100 + Time Accepted: 2025-06-18 07:20:00.043 +0000 UTC + Time Finished: 2025-06-18 07:43:54.273 +0000 UTC + Time Started: 2025-06-18 07:20:07.169 +0000 UTC + Operation Id: ocid1.coreservicesworkrequest.oc1.ap-mumbai-1.abrg6ljryitxrazcuutk3ttxscpudqtaeq7o3havhhh3aov5tdtvuhmnclma + Operation Type: Create DB System + Percent Complete: 100 + Time Accepted: 2025-06-13 11:53:36.483 +0000 UTC + Time Finished: 2025-06-13 14:10:52.388 +0000 UTC + Time Started: 2025-06-13 11:53:43.916 +0000 UTC +Events: \ No newline at end of file diff --git a/docs/dbcs/provisioning/disable_dataguard_to_database.md b/docs/dbcs/provisioning/disable_dataguard_to_database.md new file mode 100644 index 00000000..f6682e03 --- /dev/null +++ b/docs/dbcs/provisioning/disable_dataguard_to_database.md @@ -0,0 +1,40 @@ +# Disable Dataguard Association to Existing Database of DB System and Terminate Peer DB System in OCI Base DBCS Service + +In this use case, we are going to disable Dataguard and Terminate Peer DB System of an existing OCI OCS system deployed earlier with existing Database and dataguard association. Note: Both disable Dataguard and Terminate Peer DB System will happen together. + +As an pre-requisite, get the details of OCID of database of an existing DBCS System which you want to clone. + +**NOTE:** We are assuming that before this step, you have followed the [prerequisite](./../README.md#prerequisites-to-deploy-a-dbcs-system-using-oracle-db-operator-dbcs-controller) steps to create the configmap and the secrets required during the deployment. + +This example uses `dataguard_in_database.yaml` to clone a Single Instance DBCS VMDB using Oracle DB Operator DBCS Controller with: +- OCI Configmap as `oci-cred` +- OCI Secret as `oci-privatekey` +- Specification of dataGuard as - Details for dataguard setup `peerDbSystemId`, `enabled: false` and `isDelete: true`. +**NOTE:** For the details of the parameters to be used in the .yaml file, please refer [here](./dbcs_controller_parameters.md). + +Use the file: [disable_dataguard_in_database.yaml](./disable_dataguard_in_database.yaml) for this use case as below: + +1. Deploy the .yaml file: +```sh +[root@docker-test-server DBCS]# kubectl apply -f disable_dataguard_in_database.yaml +dbcssystem.database.oracle.com/dbcssystem-existing configured +``` + +2. Monitor the Oracle DB Operator Pod `pod/oracle-database-operator-controller-manager-665874bd57-g2cgw` for the progress of the DBCS VMDB creation of PDBs. + +NOTE: Check the DB Operator Pod name in your environment. + +``` +[root@docker-test-server DBCS]# kubectl logs -f pod/oracle-database-operator-controller-manager-665874bd57-g2cgw -n oracle-database-operator-system +``` +3. Describe the Kubernetes object to see more details +```bash +kubectl describe dbcssystems.database.oracle.com dbcssystem-existing +``` +More precisely +```bash +kubectl get dbcssystems.database.oracle.com dbcssystem-existing -o jsonpath='{.status.dataGuardStatus.lifecycleState}' +TERMINATED +``` + + diff --git a/docs/dbcs/provisioning/disabled_dataguard_to_database.md b/docs/dbcs/provisioning/disabled_dataguard_to_database.md new file mode 100644 index 00000000..e69de29b diff --git a/docs/dbcs/provisioning/patch_dbcs_system.yaml b/docs/dbcs/provisioning/patch_dbcs_system.yaml new file mode 100644 index 00000000..6f6e6ed0 --- /dev/null +++ b/docs/dbcs/provisioning/patch_dbcs_system.yaml @@ -0,0 +1,29 @@ +# The API group and version this custom resource belongs to (Oracle Database Operator CRD v4) +apiVersion: database.oracle.com/v4 + +# The type of resource (DbcsSystem manages an OCI DB System) +kind: DbcsSystem + +# Standard Kubernetes object metadata +metadata: + # The name of this DbcsSystem resource (must be unique within the namespace) + name: dbcssystem-existing + +# Specification of the desired state for the DbcsSystem +spec: + # The OCID of the existing DB System in OCI that this resource manages + id: "ocid1.dbsystem.oc1.ap-mumbai-1.anrg6ljrabf7htyagag6vwgkdcf5g6srfykrsqnfnryfskfhblntsjypiajq" + + # The name of the ConfigMap containing OCI credentials (tenancy, region, etc.) + ociConfigMap: "oci-cred-mumbai" + + # The name of the Secret containing the OCI private key used for authentication + ociSecret: "oci-privatekey" + + # Indicates that this CR should apply a patch/update instead of creating a new DB system + isPatch: true + + # DB system-specific configuration (nested object) + dbSystem: + # The OCID of the database patch (db update) you want to apply to this DB system + dbPatchOcid: "ocid1.dbupdate.oc1.ap-mumbai-1.anrg6ljrt5t4sqqa2z6zz3vxd4u2gmkvrofeatpufsbw332yksvuzc2xmgxq" diff --git a/docs/dbcs/provisioning/patch_dbcs_system_sample_output.log b/docs/dbcs/provisioning/patch_dbcs_system_sample_output.log new file mode 100644 index 00000000..e69de29b diff --git a/docs/dbcs/provisioning/patching_database.md b/docs/dbcs/provisioning/patching_database.md new file mode 100644 index 00000000..cfee83d9 --- /dev/null +++ b/docs/dbcs/provisioning/patching_database.md @@ -0,0 +1,45 @@ +# Patching Existing Database of DB System in OCI Base DBCS Service + +In this use case, see how an existing OCI OBDS system deployed earlier can be patched using the OCI Oracle Base Database System (OBDS). This is a two-step operation. + +To patch OBDS to an existing OBDS system, obtain the OCID of the database system ID that you want to patch. + +**NOTE:** We are assuming that before this step, you have followed the [prerequisite](./../README.md#prerequisites-to-deploy-a-dbcs-system-using-oracle-db-operator-dbcs-controller) steps to create the configmap and the secrets required during the deployment. +As step 1, first bind the existing DBCS System to DBCS Controller following [documentation](./../provisioning/bind_to_existing_dbcs_system.md). After successful binding, it will appear as follows: +```bash +kubectl get dbcssystems +NAME AGE +dbcssystem-existing 3m33s +``` + +Step 2 uses `patch_dbcs_system.yaml` to patch a Single Instance OBDS VMDB using Oracle DB Operator OBDS Controller with the following: + +- OCID of the existing VMDB as `id` to be patched. +- OCI Configmap as `oci-cred` +- OCI Secret as `oci-privatekey` +- `isPatch` as true +- Specification of the database system that you are patching as `dbPatchOcid`. The OCIDs must be unique, and you must provide new details for new patched DB system that are to be created. +**NOTE:** For the details of the parameters to be used in the `.yaml` file, see: [here](./dbcs_controller_parameters.md). + +Use the file: [patch_dbcs_system.yaml](./patch_dbcs_system.yaml) for this use case as described in the following steps: + +1. Deploy the `.yaml` file: +```sh +[root@docker-test-server DBCS]# kubectl apply -f patch_dbcs_system.yaml +dbcssystem.database.oracle.com/dbcssystem-existing configured +``` + +2. Monitor the Oracle DB Operator Pod `pod/oracle-database-operator-controller-manager-665874bd57-g2cgw` to follow the progress of the DBCS VMDB creation of PDBs. + +NOTE: Check the DB Operator Pod name in your environment. + +``` +[root@docker-test-server DBCS]# kubectl logs -f pod/oracle-database-operator-controller-manager-665874bd57-g2cgw -n oracle-database-operator-system +``` +3. Check details of the Kubernetes object post patching to ensure that it is complete, and verify that the patched version is as expected: +```bash +kubectl describe dbcssystems.database.oracle.com dbcssystem-existing + +kubectl get dbcssystems.database.oracle.com dbcssystem-existing -o jsonpath='{.status.dbVersion}' +19.28.0.0.0 +``` \ No newline at end of file diff --git a/docs/dbcs/provisioning/restore_database_sample_output.log b/docs/dbcs/provisioning/restore_database_sample_output.log new file mode 100644 index 00000000..8b5b738a --- /dev/null +++ b/docs/dbcs/provisioning/restore_database_sample_output.log @@ -0,0 +1,22 @@ +2025-04-15T10:28:10Z INFO Initiating restore operation {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-restore","namespace":"default"}, "namespace": "default", "name": "dbcssystem-restore", "reconcileID": "0777d0ff-0746-447e-adac-6d59e37958eb", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htyarrvtdd42dwcncvrj3bic2yds2es2ubic3bjiwq4omgxq", "RestoreOption": {"latest":true}} +2025-04-15T10:28:11Z INFO Restore initiated {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-restore","namespace":"default"}, "namespace": "default", "name": "dbcssystem-restore", "reconcileID": "0777d0ff-0746-447e-adac-6d59e37958eb", "WorkRequestID": "ocid1.coreservicesworkrequest.oc1.ap-mumbai-1.abrg6ljrr3kjbgleo3d3w75q3flj5mk2l4feiptvbzqxlpmdnzgld6jqqyaq"} +2025-04-15T10:28:42Z INFO Polling Restore Operation {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-restore","namespace":"default"}, "namespace": "default", "name": "dbcssystem-restore", "reconcileID": "0777d0ff-0746-447e-adac-6d59e37958eb", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htyarrvtdd42dwcncvrj3bic2yds2es2ubic3bjiwq4omgxq", "State": "UPDATING"} +2025-04-15T10:29:11Z INFO Polling Restore Operation {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-restore","namespace":"default"}, "namespace": "default", "name": "dbcssystem-restore", "reconcileID": "0777d0ff-0746-447e-adac-6d59e37958eb", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htyarrvtdd42dwcncvrj3bic2yds2es2ubic3bjiwq4omgxq", "State": "UPDATING"} +2025-04-15T10:29:41Z INFO Polling Restore Operation {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-restore","namespace":"default"}, "namespace": "default", "name": "dbcssystem-restore", "reconcileID": "0777d0ff-0746-447e-adac-6d59e37958eb", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htyarrvtdd42dwcncvrj3bic2yds2es2ubic3bjiwq4omgxq", "State": "UPDATING"} +2025-04-15T10:30:11Z INFO Polling Restore Operation {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-restore","namespace":"default"}, "namespace": "default", "name": "dbcssystem-restore", "reconcileID": "0777d0ff-0746-447e-adac-6d59e37958eb", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htyarrvtdd42dwcncvrj3bic2yds2es2ubic3bjiwq4omgxq", "State": "UPDATING"} +2025-04-15T10:30:41Z INFO Polling Restore Operation {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-restore","namespace":"default"}, "namespace": "default", "name": "dbcssystem-restore", "reconcileID": "0777d0ff-0746-447e-adac-6d59e37958eb", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htyarrvtdd42dwcncvrj3bic2yds2es2ubic3bjiwq4omgxq", "State": "UPDATING"} +2025-04-15T10:31:11Z INFO Polling Restore Operation {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-restore","namespace":"default"}, "namespace": "default", "name": "dbcssystem-restore", "reconcileID": "0777d0ff-0746-447e-adac-6d59e37958eb", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htyarrvtdd42dwcncvrj3bic2yds2es2ubic3bjiwq4omgxq", "State": "UPDATING"} +2025-04-15T10:31:41Z INFO Polling Restore Operation {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-restore","namespace":"default"}, "namespace": "default", "name": "dbcssystem-restore", "reconcileID": "0777d0ff-0746-447e-adac-6d59e37958eb", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htyarrvtdd42dwcncvrj3bic2yds2es2ubic3bjiwq4omgxq", "State": "UPDATING"} +2025-04-15T10:32:11Z INFO Polling Restore Operation {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-restore","namespace":"default"}, "namespace": "default", "name": "dbcssystem-restore", "reconcileID": "0777d0ff-0746-447e-adac-6d59e37958eb", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htyarrvtdd42dwcncvrj3bic2yds2es2ubic3bjiwq4omgxq", "State": "UPDATING"} +2025-04-15T10:32:41Z INFO Polling Restore Operation {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-restore","namespace":"default"}, "namespace": "default", "name": "dbcssystem-restore", "reconcileID": "0777d0ff-0746-447e-adac-6d59e37958eb", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htyarrvtdd42dwcncvrj3bic2yds2es2ubic3bjiwq4omgxq", "State": "UPDATING"} +2025-04-15T10:33:11Z INFO Polling Restore Operation {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-restore","namespace":"default"}, "namespace": "default", "name": "dbcssystem-restore", "reconcileID": "0777d0ff-0746-447e-adac-6d59e37958eb", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htyarrvtdd42dwcncvrj3bic2yds2es2ubic3bjiwq4omgxq", "State": "UPDATING"} +2025-04-15T10:33:41Z INFO Polling Restore Operation {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-restore","namespace":"default"}, "namespace": "default", "name": "dbcssystem-restore", "reconcileID": "0777d0ff-0746-447e-adac-6d59e37958eb", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htyarrvtdd42dwcncvrj3bic2yds2es2ubic3bjiwq4omgxq", "State": "UPDATING"} +2025-04-15T10:42:41Z INFO Polling Restore Operation {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-restore","namespace":"default"}, "namespace": "default", "name": "dbcssystem-restore", "reconcileID": "0777d0ff-0746-447e-adac-6d59e37958eb", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htyarrvtdd42dwcncvrj3bic2yds2es2ubic3bjiwq4omgxq", "State": "UPDATING"} +2025-04-15T10:43:11Z INFO Polling Restore Operation {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-restore","namespace":"default"}, "namespace": "default", "name": "dbcssystem-restore", "reconcileID": "0777d0ff-0746-447e-adac-6d59e37958eb", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htyarrvtdd42dwcncvrj3bic2yds2es2ubic3bjiwq4omgxq", "State": "UPDATING"} +2025-04-15T10:43:41Z INFO Polling Restore Operation {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-restore","namespace":"default"}, "namespace": "default", "name": "dbcssystem-restore", "reconcileID": "0777d0ff-0746-447e-adac-6d59e37958eb", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htyarrvtdd42dwcncvrj3bic2yds2es2ubic3bjiwq4omgxq", "State": "UPDATING"} +2025-04-15T10:44:11Z INFO Polling Restore Operation {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-restore","namespace":"default"}, "namespace": "default", "name": "dbcssystem-restore", "reconcileID": "0777d0ff-0746-447e-adac-6d59e37958eb", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htyarrvtdd42dwcncvrj3bic2yds2es2ubic3bjiwq4omgxq", "State": "UPDATING"} +2025-04-15T10:44:41Z INFO Polling Restore Operation {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-restore","namespace":"default"}, "namespace": "default", "name": "dbcssystem-restore", "reconcileID": "0777d0ff-0746-447e-adac-6d59e37958eb", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htyarrvtdd42dwcncvrj3bic2yds2es2ubic3bjiwq4omgxq", "State": "AVAILABLE"} +2025-04-15T10:44:41Z INFO Database restore completed {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-restore","namespace":"default"}, "namespace": "default", "name": "dbcssystem-restore", "reconcileID": "0777d0ff-0746-447e-adac-6d59e37958eb", "DatabaseID": "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htyarrvtdd42dwcncvrj3bic2yds2es2ubic3bjiwq4omgxq"} +2025-04-15T10:44:44Z INFO dbcssystem-resource default {"name": "dbcssystem-restore"} +2025-04-15T10:44:44Z INFO dbcssystem-resource default {"name": "dbcssystem-restore"} +2025-04-15T10:44:44Z INFO Restore Operation Completed and lastSuccessfulSpec updated {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-restore","namespace":"default"}, "namespace": "default", "name": "dbcssystem-restore", "reconcileID": "0777d0ff-0746-447e-adac-6d59e37958eb", "DbcsSystem": "dbcssystem-restore"} diff --git a/docs/dbcs/provisioning/restore_of_database.md b/docs/dbcs/provisioning/restore_of_database.md new file mode 100644 index 00000000..3630259d --- /dev/null +++ b/docs/dbcs/provisioning/restore_of_database.md @@ -0,0 +1,34 @@ +# Restore from Backup of Existing Database of DB System in OCI OBDS Service + +In this use case, an existing OCI OBDS system deployed earlier with existing backup of Database is configured to use restore in OCI Base OBDS Service with an existing database system ID. + +As an prerequisite, obtain the OCID details for the database of an existing OBDS System that you want to back up. + +**NOTE:** We are assuming that before this step, you have followed the [prerequisite](./../README.md#prerequisites-to-deploy-a-dbcs-system-using-oracle-db-operator-dbcs-controller) steps to create the configmap and the secrets required during the deployment. + +This example uses `restore_of_database.yaml` to clone a Single Instance OBDS VMDB using Oracle DB Operator OBDS Controller with the following: +- OCID of existing as DB System as `id` +- OCI Configmap as `oci-cred` +- OCI Secret as `oci-privatekey` +- Restore Configuration with the restore taken from a backup. To perform the restoration, the backup is provided with one of `latest` , `scn` and `timestamp` under `restoreConfig`. For restorations, do not provide more than one option. +**NOTE:** For the details of the parameters to be used in the `.yaml` file, see [dBCS Controller Parameters](./dbcs_controller_parameters.md). + +Use the file: [restore_of_database.yaml](./restore_of_database.yaml) for this use case, as described in the following steps: + +1. Deploy the .yaml file: +```sh +[root@docker-test-server OBDS]# kubectl apply -f restore_of_database.yaml +dbcssystem.database.oracle.com/dbcssystem-restore configured +``` + +2. Monitor the Oracle DB Operator Pod `pod/oracle-database-operator-controller-manager-665874bd57-g2cgw` for the progress of the OBDS VMDB creation of PDBs. + +NOTE: Check the DB Operator Pod name in your environment. + +``` +[root@docker-test-server OBDS]# kubectl logs -f pod/oracle-database-operator-controller-manager-665874bd57-g2cgw -n oracle-database-operator-system +``` + +## Sample Output + +[Here](./restore_database_sample_output.log) is an example output log of a restore from an existing OBDS System deployed in OCI using Oracle DB Operator OBDS Controller. \ No newline at end of file diff --git a/docs/dbcs/provisioning/restore_of_database.yaml b/docs/dbcs/provisioning/restore_of_database.yaml new file mode 100644 index 00000000..af0bbe64 --- /dev/null +++ b/docs/dbcs/provisioning/restore_of_database.yaml @@ -0,0 +1,11 @@ +apiVersion: database.oracle.com/v4 +kind: DbcsSystem +metadata: + name: dbcssystem-restore +spec: + id: "ocid1.dbsystem.oc1.ap-mumbai-1.anrg6ljrabf7htyaezbusohovqidnjud2w5wmeisqydsmkqlzcghctvrscfq" + ociConfigMap: "oci-cred" + ociSecret: "oci-privatekey" + dbSystem: + restoreConfig: + latest: true \ No newline at end of file diff --git a/docs/dbcs/provisioning/scale_down_dbcs_system_shape.md b/docs/dbcs/provisioning/scale_down_dbcs_system_shape.md index 1f03ff9f..149b8e10 100644 --- a/docs/dbcs/provisioning/scale_down_dbcs_system_shape.md +++ b/docs/dbcs/provisioning/scale_down_dbcs_system_shape.md @@ -1,15 +1,15 @@ # Scale Down the shape of an existing OBDS System -In this use case, an existing OCI OBDS system deployed earlier is scaled down for its shape using Oracle DB Operator OBDS controller. Its a 2 Step operation. +In this use case, an existing OCI OBDS system deployed earlier is scaled down for its shape using Oracle DB Operator OBDS controller. This is a two-step operation. -In order to scale down an existing OBDS system, the steps will be: +To scale down an existing OBDS system, the two steps are as follows: 1. Bind the existing OBDS System to OBDS Controller. 2. Apply the change to scale down its shape. -**NOTE** We are assuming that before this step, you have followed the [prerequisite](./../README.md#prerequsites-to-deploy-a-dbcs-system-using-oracle-db-operator-dbcs-controller) steps to create the configmap and the secrets required during the deployment. +**NOTE** We are assuming that before this step, you have followed the [prerequisite steps](./../README.md#prerequsites-to-deploy-a-dbcs-system-using-oracle-db-operator-dbcs-controller) to create the configmap and the secrets required during the deployment. -This example uses `scale_down_dbcs_system_shape.yaml` to scale down a Single Instance OBDS VMDB using Oracle DB Operator OBDS Controller with: +This example uses `scale_down_dbcs_system_shape.yaml` to scale down a Single Instance OBDS VMDB using Oracle DB Operator OBDS Controller with the following: - OCID of existing VMDB as `ocid1.dbsystem.oc1.ap-mumbai-1.anrg6ljrabf7htyadgsso7aessztysrwaj5gcl3tp7ce6asijm2japyvmroa` - OCI Configmap as `oci-cred` @@ -22,9 +22,9 @@ This example uses `scale_down_dbcs_system_shape.yaml` to scale down a Single Ins - SSH Public key for the OBDS system being deployed as `oci-publickey` - OCID of the Subnet as `ocid1.subnet.oc1.ap-mumbai-1.aaaaaaaa5zpzfax66omtbmjwlv4thruyru7focnu7fjcjksujmgwmr6vpbvq` -**NOTE:** For the details of the parameters to be used in the .yaml file, please refer [here](./dbcs_controller_parameters.md). +**NOTE:** For the details of the parameters to be used in the `.yaml` file, see: [DBCS Controller Parameters](./dbcs_controller_parameters.md). -Use the file: [scale_down_dbcs_system_shape.yaml](./scale_down_dbcs_system_shape.yaml) for this use case as below: +Use the file: [scale_down_dbcs_system_shape.yaml](./scale_down_dbcs_system_shape.yaml) for this use case as follows: 1. Deploy the .yaml file: ```sh @@ -42,4 +42,4 @@ NOTE: Check the DB Operator Pod name in your environment. ## Sample Output -[Here](./scale_down_dbcs_system_shape_sample_output.log) is the sample output for scaling down the shape of an existing OBDS System deployed in OCI using Oracle DB Operator OBDS Controller. +[This log file](./scale_down_dbcs_system_shape_sample_output.log) is an example output log file for scaling down the shape of an existing OBDS System deployed in OCI using Oracle DB Operator OBDS Controller. diff --git a/docs/dbcs/provisioning/scale_up_dbcs_system_shape.md b/docs/dbcs/provisioning/scale_up_dbcs_system_shape.md index 924a8517..5ec4f3da 100644 --- a/docs/dbcs/provisioning/scale_up_dbcs_system_shape.md +++ b/docs/dbcs/provisioning/scale_up_dbcs_system_shape.md @@ -1,8 +1,8 @@ # Scale UP the shape of an existing OBDS System -In this use case, an existing OCI OBDS system deployed earlier is scaled up for its shape using Oracle DB Operator OBDS controller. Its a 2 Step operation. +In this use case, an existing OCI OBDS system deployed earlier is scaled up for its shape using Oracle DB Operator OBDS controller. This is a two-step operation. -In order to scale up an existing OBDS system, the steps will be: +To scale up an existing OBDS system, the two steps are: 1. Bind the existing OBDS System to OBDS Controller. 2. Apply the change to scale up its shape. @@ -22,17 +22,17 @@ This example uses `scale_up_dbcs_system_shape.yaml` to scale up a Single Instanc - SSH Public key for the OBDS system being deployed as `oci-publickey` - OCID of the Subnet as `ocid1.subnet.oc1.ap-mumbai-1.aaaaaaaa5zpzfax66omtbmjwlv4thruyru7focnu7fjcjksujmgwmr6vpbvq` -**NOTE:** For the details of the parameters to be used in the .yaml file, please refer [here](./dbcs_controller_parameters.md). +**NOTE:** For the details of the parameters to be used in the `.yaml` file, see: [DBCS Controller Parameters](./dbcs_controller_parameters.md). -Use the file: [scale_up_dbcs_system_shape.yaml](./scale_up_dbcs_system_shape.yaml) for this use case as below: +Use the file: [scale_up_dbcs_system_shape.yaml](./scale_up_dbcs_system_shape.yaml) for this use case as described in the following steps: -1. Deploy the .yaml file: +1. Deploy the `.yaml` file: ```sh [root@docker-test-server OBDS]# kubectl apply -f scale_up_dbcs_system_shape.yaml dbcssystem.database.oracle.com/dbcssystem-existing configured ``` -2. Monitor the Oracle DB Operator Pod `pod/oracle-database-operator-controller-manager-665874bd57-g2cgw` for the progress of the OBDS VMDB Scale up. +2. Monitor the Oracle DB Operator Pod `pod/oracle-database-operator-controller-manager-665874bd57-g2cgw` to see the progress of the OBDS VMDB Scale up. NOTE: Check the DB Operator Pod name in your environment. @@ -42,4 +42,4 @@ NOTE: Check the DB Operator Pod name in your environment. ## Sample Output -[Here](./scale_up_dbcs_system_shape_sample_output.log) is the sample output for scaling up the shape of an existing OBDS System deployed in OCI using Oracle DB Operator OBDS Controller. +[this log file](./scale_up_dbcs_system_shape_sample_output.log) is an example log output for scaling up the shape of an existing OBDS System deployed in OCI using Oracle DB Operator OBDS Controller. diff --git a/docs/dbcs/provisioning/scale_up_dbcs_system_shape.yaml b/docs/dbcs/provisioning/scale_up_dbcs_system_shape.yaml index 0be84c53..8d78fc5d 100644 --- a/docs/dbcs/provisioning/scale_up_dbcs_system_shape.yaml +++ b/docs/dbcs/provisioning/scale_up_dbcs_system_shape.yaml @@ -3,16 +3,24 @@ kind: DbcsSystem metadata: name: dbcssystem-existing spec: +<<<<<<< HEAD + id: "ocid1.dbsystem.oc1.ap-mumbai-1.anrg6ljrabf7htyamvvn4n2yg6gv6s5c42qgfhtxrzcbdootuiki4wb3s5yq" +======= id: "ocid1.dbsystem.oc1.ap-mumbai-1.anrg6ljrabf7htyadgsso7aessztysrwaj5gcl3tp7ce6asijm2japyvmroa" +>>>>>>> origin/master ociConfigMap: "oci-cred" ociSecret: "oci-privatekey" dbSystem: availabilityDomain: "OLou:AP-MUMBAI-1-AD-1" compartmentId: "ocid1.compartment.oc1..aaaaaaaa63yqilqhgxv3dszur3a2fgwc64ohpfy43vpqjm7q5zq4q4yaw72a" dbAdminPasswordSecret: "admin-password" +<<<<<<< HEAD + hostName: "host12345" +======= hostName: "host1234" +>>>>>>> origin/master shape: "VM.Standard2.2" - domain: "subdda0b5eaa.cluster1.oraclevcn.com" + domain: "sub0ea2e07ef.cluster1.oraclevcn.com" sshPublicKeys: - "oci-publickey" - subnetId: "ocid1.subnet.oc1.ap-mumbai-1.aaaaaaaa5zpzfax66omtbmjwlv4thruyru7focnu7fjcjksujmgwmr6vpbvq" + subnetId: "ocid1.subnet.oc1.ap-mumbai-1.aaaaaaaaklvzufcgma65ibn4al7xukjmyfz7nnrpaewfzbffz5zrnz3htweq" diff --git a/docs/dbcs/provisioning/scale_up_storage.md b/docs/dbcs/provisioning/scale_up_storage.md index ff16cbf9..f5bfa8b8 100644 --- a/docs/dbcs/provisioning/scale_up_storage.md +++ b/docs/dbcs/provisioning/scale_up_storage.md @@ -1,15 +1,15 @@ # Scale UP the storage of an existing OBDS System -In this use case, an existing OCI OBDS system deployed earlier is scaled up for its storage using Oracle DB Operator OBDS controller. Its a 2 Step operation. +In this use case, an existing OCI OBDS system deployed earlier is scaled up for its storage using Oracle DB Operator OBDS controller. This is a two-step operation. -In order to scale up storage of an existing OBDS system, the steps will be: +To scale up storage of an existing OBDS system, the two steps are as follows: 1. Bind the existing OBDS System to OBDS Controller. 2. Apply the change to scale up its storage. -**NOTE** We are assuming that before this step, you have followed the [prerequisite](./../README.md#prerequsites-to-deploy-a-dbcs-system-using-oracle-db-operator-dbcs-controller) steps to create the configmap and the secrets required during the deployment. +**NOTE** We are assuming that before this step, you have followed the [prerequisite steps](./../README.md#prerequsites-to-deploy-a-dbcs-system-using-oracle-db-operator-dbcs-controller) to create the configmap and the secrets required during the deployment. -This example uses `scale_up_storage.yaml` to scale up storage of an existing Single Instance OBDS VMDB using Oracle DB Operator OBDS Controller with: +This example uses `scale_up_storage.yaml` to scale up storage of an existing Single Instance OBDS VMDB using Oracle DB Operator OBDS Controller with the following: - OCID of existing VMDB as `ocid1.dbsystem.oc1.ap-mumbai-1.anrg6ljrabf7htyadgsso7aessztysrwaj5gcl3tp7ce6asijm2japyvmroa` - OCI Configmap as `oci-cred` @@ -24,9 +24,9 @@ This example uses `scale_up_storage.yaml` to scale up storage of an existing Sin - OCID of the Subnet as `ocid1.subnet.oc1.ap-mumbai-1.aaaaaaaa5zpzfax66omtbmjwlv4thruyru7focnu7fjcjksujmgwmr6vpbvq` -Use the file: [scale_up_storage.yaml](./scale_up_storage.yaml) for this use case as below: +Use the file: [scale_up_storage.yaml](./scale_up_storage.yaml) for this use case as described in the following steps: -1. Deploy the .yaml file: +1. Deploy the `.yaml` file: ```sh [root@test-server OBDS]# kubectl apply -f scale_storage.yaml dbcssystem.database.oracle.com/dbcssystem-existing configured @@ -42,4 +42,4 @@ NOTE: Check the DB Operator Pod name in your environment. ## Sample Output -[Here](./scale_up_storage_sample_output.log) is the sample output for scaling up the storage of an existing OBDS System deployed in OCI using Oracle DB Operator OBDS Controller with minimal parameters. +[this log file](./scale_up_storage_sample_output.log) is an example of an output log file for scaling up the storage of an existing OBDS System deployed in OCI using Oracle DB Operator OBDS Controller with minimal parameters. diff --git a/docs/dbcs/provisioning/terminate_dbcs_system.md b/docs/dbcs/provisioning/terminate_dbcs_system.md index f3b19cbc..8dae2818 100644 --- a/docs/dbcs/provisioning/terminate_dbcs_system.md +++ b/docs/dbcs/provisioning/terminate_dbcs_system.md @@ -1,25 +1,25 @@ # Terminate an existing Oracle Base Database System (OBDS) -In this use case, an existing OCI OBDS system deployed earlier is terminated using Oracle DB Operator OBDS controller. Its a 2 Step operation. +In this use case, an existing OCI OBDS system deployed earlier is terminated using Oracle DB Operator OBDS controller. This is a two-step operation. -In order to terminate an existing OBDS system, the steps will be: +In order to terminate an existing OBDS system, the two steps are as follows: 1. Bind the existing OBDS System to OBDS Controller. 2. Apply the change to terminate this OBDS System. -**NOTE** We are assuming that before this step, you have followed the [prerequisite](./../README.md#prerequsites-to-deploy-a-dbcs-system-using-oracle-db-operator-dbcs-controller) steps to create the configmap and the secrets required during the deployment. +**NOTE** We are assuming that before this step, you have followed the [prerequisite steps](./../README.md#prerequsites-to-deploy-a-dbcs-system-using-oracle-db-operator-dbcs-controller) to create the configmap and the secrets required during the deployment. -This example uses `terminate_dbcs_system.yaml` to terminated a Single Instance OBDS VMDB using Oracle DB Operator OBDS Controller with: +This example uses `terminate_dbcs_system.yaml` to terminated a Single Instance OBDS VMDB using Oracle DB Operator OBDS Controller with the following: - OCID of existing VMDB as `ocid1.dbsystem.oc1.phx.anyhqljrabf7htyanr3lnp6wtu5ld7qwszohiteodvwahonr2yymrftarkqa` - OCI Configmap as `oci-cred` - OCI Secret as `oci-privatekey` -**NOTE:** For the details of the parameters to be used in the .yaml file, please refer [here](./dbcs_controller_parameters.md). +**NOTE:** For the details of the parameters to be used in the `.yaml` file, see [DBCS Controller Parameters](./dbcs_controller_parameters.md). -Use the file: [terminate_dbcs_system.yaml](./terminate_dbcs_system.yaml) for this use case as below: +Use the file: [terminate_dbcs_system.yaml](./terminate_dbcs_system.yaml) for this use case as described in the following steps: -1. Deploy the .yaml file: +1. Deploy the `.yaml` file: ```sh [root@test-server OBDS]# kubectl apply -f terminate_dbcs_system.yaml dbcssystem.database.oracle.com/dbcssystem-terminate created @@ -29,13 +29,13 @@ dbcssystem.database.oracle.com/dbcssystem-terminate created dbcssystem.database.oracle.com "dbcssystem-terminate" deleted ``` -2. Check the logs of Oracle DB Operator Pod `pod/oracle-database-operator-controller-manager-665874bd57-g2cgw` for an update on the terminate operation been accepted. +2. Check the logs of Oracle DB Operator Pod `pod/oracle-database-operator-controller-manager-665874bd57-g2cgw` for an update on the terminate operation to confirm it has been accepted. ``` [root@test-server OBDS]# kubectl logs -f pod/oracle-database-operator-controller-manager-665874bd57-g2cgw -n oracle-database-operator-system ``` -3. Check and confirm if the existing OCI OBDS system is NO longer available after sometime because of termination: +3. Give some time for the termination operation to be completed, and then check and confirm if the existing OCI OBDS system is no longer available: ``` [root@test-server OBDS]# kubectl describe dbcssystems.database.oracle.com dbcssystem-terminate @@ -43,4 +43,4 @@ dbcssystem.database.oracle.com "dbcssystem-terminate" deleted ## Sample Output -[Here](./terminate_dbcs_system_sample_output.log) is the sample output for terminating an existing OBDS System deployed in OCI using Oracle DB Operator OBDS Controller with minimal parameters. +[This example log](./terminate_dbcs_system_sample_output.log) is an example output log for terminating an existing OBDS System deployed in OCI using Oracle DB Operator OBDS Controller with minimal parameters. diff --git a/docs/dbcs/provisioning/update_license.md b/docs/dbcs/provisioning/update_license.md index 6f32c31b..aef88fd7 100644 --- a/docs/dbcs/provisioning/update_license.md +++ b/docs/dbcs/provisioning/update_license.md @@ -1,15 +1,15 @@ # Update License type of an existing OBDS System -In this use case, the license type of an existing OCI OBDS system deployed earlier is changed from `License Included` to `Bring your own license` using Oracle DB Operator OBDS controller. Its a 2 Step operation. +In this use case, the license type of an existing OCI OBDS system deployed earlier is changed from `License Included` to `Bring your own license` using Oracle DB Operator OBDS controller. This is a two-step operation. -In order to update the license type an existing OBDS system, the steps will be: +To update the license type an existing OBDS system, the two steps are as follows: 1. Bind the existing OBDS System to OBDS Controller. 2. Apply the change to change its license type. -**NOTE** We are assuming that before this step, you have followed the [prerequisite](./../README.md#prerequsites-to-deploy-a-dbcs-system-using-oracle-db-operator-dbcs-controller) steps to create the configmap and the secrets required during the deployment. +**NOTE** We are assuming that before this step, you have followed the [prerequisite steps](./../README.md#prerequsites-to-deploy-a-dbcs-system-using-oracle-db-operator-dbcs-controller) to create the configmap and the secrets required during the deployment. -This example uses `update_license.yaml` to change the license type of a Single Instance OBDS VMDB using Oracle DB Operator OBDS Controller with: +This example uses `update_license.yaml` to change the license type of a Single Instance OBDS VMDB using Oracle DB Operator OBDS Controller with the following: - OCID of existing VMDB as `ocid1.dbsystem.oc1.ap-mumbai-1.anrg6ljrabf7htyadgsso7aessztysrwaj5gcl3tp7ce6asijm2japyvmroa` - OCI Configmap as `oci-cred` @@ -23,17 +23,17 @@ This example uses `update_license.yaml` to change the license type of a Single I - SSH Public key for the OBDS system being deployed as `oci-publickey` - OCID of the Subnet as `ocid1.subnet.oc1.ap-mumbai-1.aaaaaaaa5zpzfax66omtbmjwlv4thruyru7focnu7fjcjksujmgwmr6vpbvq` -**NOTE:** For the details of the parameters to be used in the .yaml file, please refer [here](./dbcs_controller_parameters.md). +**NOTE:** For the details of the parameters to be used in the `.yaml` file, see: [DBCS Controller Parameters](./dbcs_controller_parameters.md). -Use the file: [update_license.yaml](./update_license.yaml) for this use case as below: +Use the file: [update_license.yaml](./update_license.yaml) for this use case as described in the following steps: -1. Deploy the .yaml file: +1. Deploy the `.yaml` file: ```sh [root@test-server OBDS]# kubectl apply -f update_license.yaml dbcssystem.database.oracle.com/dbcssystem-existing configured ``` -2. Monitor the Oracle DB Operator Pod `pod/oracle-database-operator-controller-manager-665874bd57-g2cgw` for the progress of the OBDS VMDB Scale up. +2. Monitor the Oracle DB Operator Pod `pod/oracle-database-operator-controller-manager-665874bd57-g2cgw` to follow the progress of the OBDS VMDB Scale up. NOTE: Check the DB Operator Pod name in your environment. @@ -43,4 +43,4 @@ NOTE: Check the DB Operator Pod name in your environment. ## Sample Output -[Here](./update_license_sample_output.log) is the sample output for updating the license type an existing OBDS System deployed in OCI using Oracle DB Operator OBDS Controller. +[This log file](./update_license_sample_output.log) is an example output log file for updating the license type an existing OBDS System deployed in OCI using Oracle DB Operator OBDS Controller. diff --git a/docs/dbcs/provisioning/upgrade_dbcs_system.yaml b/docs/dbcs/provisioning/upgrade_dbcs_system.yaml new file mode 100644 index 00000000..38b3df6f --- /dev/null +++ b/docs/dbcs/provisioning/upgrade_dbcs_system.yaml @@ -0,0 +1,32 @@ +# API group and version for Oracle DBCS operator +apiVersion: database.oracle.com/v4 + +# Kubernetes resource kind — represents a DB system in OCI +kind: DbcsSystem + +# Standard Kubernetes metadata (object name, namespace, labels etc.) +metadata: + # The name of this DbcsSystem object inside the cluster + name: dbcssystem-existing + +# Specification of the DBCS system +spec: + # OCID of the existing DB System in OCI (the one we want to manage/upgrade) + id: "ocid1.dbsystem.oc1.ap-mumbai-1.anrg6ljrabf7htyagag6vwgkdcf5g6srfykrsqnfnryfskfhblntsjypiajq" + + # Name of the ConfigMap that holds OCI credentials (tenancy, user, fingerprint, region) + ociConfigMap: "oci-cred-mumbai" + + # Name of the Secret that contains the private key for authentication + ociSecret: "oci-privatekey" + + # Flag indicating this spec is meant to perform an upgrade on the DB system + isUpgrade: true + + # OCID of the specific database inside the DB System (not the DB system itself) + databaseId: "ocid1.database.oc1.ap-mumbai-1.anrg6ljrabf7htya2mo5an5blhlvx5euyqnazi45hli7zqs5uig7juzgme5a" + + # dbSystem section holds DB system upgrade target details + dbSystem: + # Target database version for upgrade (new GI/DB version) + dbUpgradeVersion: "23.6.0.24.10" diff --git a/docs/dbcs/provisioning/upgrade_dbcs_system_sample_output.log b/docs/dbcs/provisioning/upgrade_dbcs_system_sample_output.log new file mode 100644 index 00000000..9f83ebf5 --- /dev/null +++ b/docs/dbcs/provisioning/upgrade_dbcs_system_sample_output.log @@ -0,0 +1,2 @@ +2025-07-15T14:13:15Z INFO Database upgrade work request status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-upgrade","namespace":"default"}, "namespace": "default", "name": "dbcssystem-upgrade", "reconcileID": "a07a5fe3-cbf0-4094-a603-51bc8cbb1caf", "Status": "IN_PROGRESS", "WorkRequestID": "ocid1.coreservicesworkrequest.oc1.ap-mumbai-1.abrg6ljr4cqbpeuh6rhrrn2anmz3bsvk5b2jp4jxbebr6c7lr5ybmzt6unkq"} +2025-07-15T14:14:16Z INFO Database upgrade work request status {"controller": "dbcssystem", "controllerGroup": "database.oracle.com", "controllerKind": "DbcsSystem", "DbcsSystem": {"name":"dbcssystem-upgrade","namespace":"default"}, "namespace": "default", "name": "dbcssystem-upgrade", "reconcileID": "a07a5fe3-cbf0-4094-a603-51bc8cbb1caf", "Status": "IN_PROGRESS", "WorkRequestID": "ocid1.coreservicesworkrequest.oc1.ap-mumbai-1.abrg6ljr4cqbpeuh6rhrrn2anmz3bsvk5b2jp4jxbebr6c7lr5ybmzt6unkq"} \ No newline at end of file diff --git a/docs/dbcs/provisioning/upgrading_database.md b/docs/dbcs/provisioning/upgrading_database.md new file mode 100644 index 00000000..a82a922c --- /dev/null +++ b/docs/dbcs/provisioning/upgrading_database.md @@ -0,0 +1,45 @@ +# Upgrade Existing Database of DB System in OCI Base DBCS Service + +In this use case, an existing OCI OBDS system deployed earlier is going to be upgraded in OCI Oracle Base Database System (OBDS). This is a two-step operation. + +To upgrade OBDS to an existing OBDS system, obtain the OCID of DB System ID that you want to upgrade. + +**NOTE:** We are assuming that before this step, you have followed the [prerequisite steps](./../README.md#prerequisites-to-deploy-a-dbcs-system-using-oracle-db-operator-dbcs-controller) to create the configmap and the secrets required during the deployment. + +For the first step, bind the existing DBCS System to DBCS Controller following the [Bind ot Existing DBCS System documentation](./../provisioning/bind_to_existing_dbcs_system.md). After successful binding, it will show as follows: +```bash +kubectl get dbcssystems +NAME AGE +dbcssystem-existing 3m33s +``` + +Step 2 uses `upgrade_dbcs_system.yaml` to upgrade a Single Instance OBDS VMDB using Oracle DB Operator OBDS Controller with the following: + +- OCID of existing VMDB as `id` to be upgraded. +- OCI Configmap as `oci-cred` +- OCI Secret as `oci-privatekey` +- `isupgrade` as true +- Specification of the DB System being upgraded as `dbUpgradeVersion`. The specification details must be unique and new for the new upgraded DB system that is created. +**NOTE:** For the details of the parameters to be used in the `.yaml` file, see: [DBCS Controller Parameters](./dbcs_controller_parameters.md). + +Use the file: [upgrade_dbcs_system.yaml](./upgrade_dbcs_system.yaml) for this use case as described in the following steps: + +1. Deploy the `.yaml` file: +```sh +[root@docker-test-server DBCS]# kubectl apply -f upgrade_dbcs_system.yaml +dbcssystem.database.oracle.com/dbcssystem-existing configured +``` + +2. Monitor the Oracle DB Operator Pod `pod/oracle-database-operator-controller-manager-665874bd57-g2cgw` for the progress of the DBCS VMDB creation of PDBs. + +NOTE: Check the DB Operator Pod name in your environment. + +``` +[root@docker-test-server DBCS]# kubectl logs -f pod/oracle-database-operator-controller-manager-665874bd57-g2cgw -n oracle-database-operator-system +``` + +3. After the upgrade, describe the Kubernetes object to ensure that the correct database version is completed: +```bash +kubectl get dbcssystems.database.oracle.com dbcssystem-existing +kubectl get dbcssystems.database.oracle.com dbcssystem-existing -o jsonpath='{.status.dbVersion}' +``` \ No newline at end of file diff --git a/docs/multitenant/NamespaceSeg.md b/docs/multitenant/NamespaceSeg.md deleted file mode 100644 index 6738fe56..00000000 --- a/docs/multitenant/NamespaceSeg.md +++ /dev/null @@ -1,14 +0,0 @@ - - -# Namespace segregation - -With the namespace segregation pdb controller and cdb controller run in different namespaces. The new functionality introduces a new parameter (the cdb namespace) in pdb crd definition. In case you don't need the namespace segregation you have to sepcify the namespace name that you are using for yours crd and pods anyway. Refer to usercase01 and usecase02 to see single namespace configuration. Refer to usecase03 to see examples of namespace segregation. - -# Secrets - -In order to use multiple namespace we need to create approriate secrets in each namespace. Tls certificate secrets must be created in all namespaces (db-ca db-tls). - -![general_schema](./images/K8S_NAMESPACE_SEG.png) - - - diff --git a/docs/multitenant/README.md b/docs/multitenant/README.md index 0d3057fc..8d9e7024 100644 --- a/docs/multitenant/README.md +++ b/docs/multitenant/README.md @@ -1,11 +1,798 @@ -# Multitenant Controllers + +* 1. [WHAT'S NEW](#WHATSNEW) + * 1.1. [Kubectl get lrpdb format](#Kubectlgetlrpdbformat) + * 1.2. [Pdb status table](#Pdbstatustable) +* 2. [STEP BY STEP CONFIGURATION](#STEPBYSTEPCONFIGURATION) + * 2.1. [Multiple namespace setup](#Multiplenamespacesetup) + * 2.2. [Apply rolebinding](#Applyrolebinding) + * 2.3. [Create the operator](#Createtheoperator) + * 2.4. [ClusterRole and ClusterRoleBinding for NodePort services](#ClusterRoleandClusterRoleBindingforNodePortservices) + * 2.5. [Container database setup](#Containerdatabasesetup) + * 2.6. [Certificate and credentials](#Certificateandcredentials) + * 2.6.1. [Private key 🔑](#Privatekey) + * 2.6.2. [Public Key 🔑](#PublicKey) + * 2.6.3. [Certificates](#Certificates) + * 2.7. [Create secrets for certificate and keys](#Createsecretsforcertificateandkeys) + * 2.8. [Create secrets with encrypted password](#Createsecretswithencryptedpassword) + * 2.9. [Create lrest pod](#Createlrestpod) + * 2.10. [Openshift configuration](#Openshiftconfiguration) + * 2.11. [Create PDB](#CreatePDB) + * 2.11.1. [pdb config map](#pdbconfigmap) + * 2.12. [Open PDB](#OpenPDB) + * 2.13. [Close PDB](#ClosePDB) + * 2.14. [Clone PDB ###](#ClonePDB) + * 2.15. [Unplug PDB](#UnplugPDB) + * 2.16. [Plug PDB](#PlugPDB) + * 2.17. [Delete PDB](#DeletePDB) +* 3. [SQL/PLSQL SCRIPT EXECUTION](#SQLPLSQLSCRIPTEXECUTION) + * 3.1. [Apply plsql configmap](#Applyplsqlconfigmap) + * 3.2. [Limitation](#Limitation) +* 4. [TROUBLESHOOTING](#TROUBLESHOOTING) + * 4.1. [Get rid of error status](#Getridoferrorstatus) +* 5. [UPGRADE EXISTING INSTALLATION](#UPGRADEEXISTINGINSTALLATION) +* 6. [KNOWN ISSUES](#KNOWNISSUES) -Starting from OraOperator version 1.2.0, there are two classes of multitenant controllers: one based on [ORDS](https://www.oracle.com/uk/database/technologies/appdev/rest.html) and another based on a dedicated REST server for the operator, called LREST. In both cases, the features remains unchanged (a part from CRD name changes). A pod running a REST server (either LREST or ORDS) acts as the proxy server connected to the container database (CDB) for all incoming kubectl requests. We plan to discontinue the ORDS based controller, in the next release; no regression (a part form CRD name changes). + + -## What are the differences -- Regarding the YAML file, the parameters for the existing functionalities are unchanged. -- The **CRD** names are different: for controllers based on [ORDS](./ords-based/README.md), we have **PDB** and **CDB**, while for controllers based on [LREST](./lrest-based/README.md), we have **LRPDB** and **LREST**. -- If you use an LREST-based controller, there is no need to manually create the REST server pod. The image is available for download on OCR. -- Controllers based on **LREST** allow you to manage PDB parameters using kubectl. -- ORDS controllers currently do not support ORDS version 24.1. + + + +**Lrpdb** and **lrest** are two controllers for PDB lifecycle management (**PDBLCM**). They rely on a dedicated REST server (Lite Rest Server) container image. The `lrest` controller is available on the Oracle Container Registry (OCR). The container database can be anywhere (on-premises or in the Cloud). + +![generaleschema](./images/Generalschema2.jpg) + +## 1. WHAT'S NEW + +- Version 2.0 mplements the request described in the github [issue 170](https://github.com/oracle/oracle-database-operator/issues/170): remove The **action** fileds to reduce the number of input directives and simplif the logic of the reconciliation loop. In addition to that the *kubectl get lrpdb* commands exposes a bitmask which represent the CRD status. + +![kubectlget_format](./images/KubectlGetSchema2.jpg) + +- The **Map** action has been replaced by the **autodoscovery** option. If a pluggable database is created manually via command line then the lrest detects the new pdb and automatically creates the CRD. + +- [sql/plsql sript execution](#sqlplsql-script-execution) this functionality enables the capability of sql/plsql code execution. + +### 1.1. Kubectl get lrpdb format +| Name | Description | +|---------------|---------------------------------------------------------| +| NAME | The name of the **CRD** | +| CDB NAME | The name of the container DB | +| PDB NAME | The name of the **Pluggable database** | +| PDB STATE | The pdb open mode | +| PDB SIZE | Size of the Pluggable database | +| MESSAGE | Message to verify the progress of the current request | +| RESTRICED | Bool variable: database open in restricted mode | +| LAST SQLCODE | Sqlcode of the last command (see [OCIErrorGet](https://docs.oracle.com/en/database/oracle/oracle-database/19/lnoci/miscellaneous-functions.html#GUID-4B99087C-74F6-498A-8310-D6645172390A)) | +| LAST PLSQL | Sqlcode of the last plsql execution | +| BITMASK STATUS| The status of pdb represented using a bitmask | +| CONNECT_STRING| The tns string for pdb connection | + +### 1.2. Pdb status table + +| Name | Value | Description | +|---------|-----------|---------------------------------------------------| +| PDBCRT |0x00000001 | Create PDB | +| PDBOPN |0x00000002 | Open pdb read write | +| PDBCLS |0x00000004 | Close pdb | +| PDBDIC |0x00000008 | Drop pdb include datafiles | +| OCIHDL |0x00000010 | OCI handle allocation (**for future use**) | +| OCICON |0x00000020 | Rdbms connection (**for future use**) | +| FNALAZ |0x00000040 | Finalizer configured | +| **ERROR CODES** | +| PDBCRE |0x00001000 | PDB creation error | +| PDBOPE |0x00002000 | PDB open error | +| PDBCLE |0x00004000 | PDB close error | +| OCIHDE |0x00008000 | Allocation Handle Error (**for future use**) | +| OCICOE |0x00010000 | CDB connection Error (**for future use**) | +| PDBPLE |0x00080000 | Plug Error | +| **OTHER INFO** | +| PDBAUT | 0x01000000 | Autodisover | + +> In case of error codes the reconciliation loop does not take any action see +[Get rid of error status](#get-rid-of-error-status); human action is required. + +## 2. STEP BY STEP CONFIGURATION +Complete each of these steps in the order given. + +### 2.1. Multiple namespace setup + +Before proceeding with controllers setup, ensure that the Oracle Database Operator (operator) is configured to work with multiple namespaces, as specified in the [README](../../../README.md). +In this document, each controller is running in a dedicated namespace: lrest controller is running in **cdbnamespace** , lrpdb controller is running in **pdbnamespace**. The [usecase directory](./usecase/README.md) contains sample files and additional scripts for yaml files customization. + +Configure the **WACTH_NAMESPACE** list of the operator `yaml` file + +```bash +sed -i 's/value: ""/value: "oracle-database-operator-system,pdbnamespace,cdbnamespace"/g' oracle-database-operator.yaml +``` +### 2.2. Apply rolebinding + + +Apply the following files : [`pdbnamespace_binding.yaml`](./usecase/pdbnamespace_binding.yaml) [`cdbnamespace_binding.yaml`](./usecase/cdbnamespace_binding.yaml) +```bash +kubectl apply -f pdbnamespace_binding.yaml +kubectl apply -f cdbnamespace_binding.yaml +``` + +### 2.3. Create the operator +Run the following command: + +```bash +kubectl apply -f oracle-database-operator.yaml +``` +Check the controller: +```bash +kubectl get pods -n oracle-database-operator-system +NAME READY STATUS RESTARTS AGE +oracle-database-operator-controller-manager-796c9b87df-6xn7c 1/1 Running 0 22m +oracle-database-operator-controller-manager-796c9b87df-sckf2 1/1 Running 0 22m +oracle-database-operator-controller-manager-796c9b87df-t4qns 1/1 Running 0 22m +``` + +### 2.4. ClusterRole and ClusterRoleBinding for NodePort services + +To expose services on each node's IP and port (the NodePort), apply the node-rbac.yaml. Note that this step is not required for LoadBalancer services. + +```bash + kubectl apply -f rbac/node-rbac.yaml +``` + + +### 2.5. Container database setup + +On the container database, use the following commands to configure the account for PDB administration: + +```sql +alter session set "_oracle_script"=true; +create user identified by ; +grant create session to container=all; +grant sysdba to container=all; +``` + +### 2.6. Certificate and credentials +You must create the public key, private key, certificates and Kubernetes Secrets for the security configuration. + +#### 2.6.1. Private key 🔑 +> Note: Only private key **PCKS8** format is supported by LREST controllers. Before you start configuration, ensure that you can use it. If you are using [`openssl3`](https://docs.openssl.org/master/) then `pcks8` is generated by default. If it is not already generated, then use the following command to create a `pcks8` private key + +```bash +openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_keygen_pubexp:65537 -out private.key +``` +#### 2.6.2. Public Key 🔑 +Create the public key. + +```bash +/usr/bin/openssl rsa -in private.key -outform PEM -pubout -out public.pem +``` +#### 2.6.3. Certificates +Create certificates. +```bash +openssl req -new -x509 -days 365 -key private.key -subj "/C=CN/ST=GD/L=SZ/O=oracle, Inc./CN=oracle Root CA" -out ca.crt +``` +```bash +openssl req -newkey rsa:2048 -nodes -keyout tls.key -subj "/C=CN/ST=GD/L=SZ/O=oracle, Inc./CN=cdb-dev-lrest.cdbnamespace" -out server.csr +``` +```bash +/usr/bin/echo "subjectAltName=DNS:cdb-dev-lrest.cdbnamespace,DNS:www.example.com" > extfile.txt +``` +```bash +/usr/bin/openssl x509 -req -extfile extfile.txt -days 365 -in server.csr -CA ca.crt -CAkey private.key -CAcreateserial -out tls.crt +``` + +### 2.7. Create secrets for certificate and keys +Create the Kubernetes Secrets. + +```bash +kubectl create secret tls db-tls --key="tls.key" --cert="tls.crt" -n oracle-database-operator-system +kubectl create secret generic db-ca --from-file="ca.crt" -n oracle-database-operator-system +kubectl create secret tls db-tls --key="tls.key" --cert="tls.crt" -n cdbnamespace +kubectl create secret generic db-ca --from-file="ca.crt" -n cdbnamespace +kubectl create secret tls db-tls --key="tls.key" --cert="tls.crt" -n pdbnamespace +kubectl create secret generic db-ca --from-file="ca.crt" -n pdbnamespace +``` + +```bash +kubectl create secret tls prvkey --key="private.key" --cert=ca.crt -n cdbnamespace +kubectl create secret generic pubkey --from-file=publicKey=public.pem -n cdbnamespace +kubectl create secret generic prvkey --from-file=privateKey="private.key" -n pdbnamespace +``` + +### 2.8. Create secrets with encrypted password + +In this example, we create the Secrets for each credential (username and password) + +| secret usr | secrets pwd | credential description | +| -----------|-------------|-----------------------------------------------------------| +| **dbuser** |**dbpass** | the administrative user created on the container database | +| **wbuser** |**wbpass** | the user for https authentication | +| **pdbusr** |**pdbpwd** | the administrative user of the pdbs | + + +```bash +echo "[ADMINUSERNAME]" > dbuser.txt +echo "[ADMINUSERNAME PASSWORD]" > dbpass.txt +echo "[WEBUSER]" > wbuser.txt +echo "[WEBUSER PASSWORD]" > wbpass.txt +echo "[PDBUSERNAME]" > pdbusr.txt +echo "[PDBUSERNAME PASSWORD]" > pdbpwd.txt + +## 3. Encrypt the credentials +openssl pkeyutl -encrypt -pubin -inkey public.pem -in dbuser.txt -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 |base64 > e_dbuser.txt +openssl pkeyutl -encrypt -pubin -inkey public.pem -in dbpass.txt -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 |base64 > e_dbpass.txt +openssl pkeyutl -encrypt -pubin -inkey public.pem -in wbuser.txt -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 |base64 > e_wbuser.txt +openssl pkeyutl -encrypt -pubin -inkey public.pem -in wbpass.txt -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 |base64 > e_wbpass.txt +openssl pkeyutl -encrypt -pubin -inkey public.pem -in pdbusr.txt -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 |base64 > e_pdbusr.txt +openssl pkeyutl -encrypt -pubin -inkey public.pem -in pdbpwd.txt -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 |base64 > e_pdbpwd.txt + +kubectl create secret generic dbuser --from-file=e_dbuser.txt -n cdbnamespace +kubectl create secret generic dbpass --from-file=e_dbpass.txt -n cdbnamespace +kubectl create secret generic wbuser --from-file=e_wbuser.txt -n cdbnamespace +kubectl create secret generic wbpass --from-file=e_wbpass.txt -n cdbnamespace +kubectl create secret generic wbuser --from-file=e_wbuser.txt -n pdbnamespace +kubectl create secret generic wbpass --from-file=e_wbpass.txt -n pdbnamespace +kubectl create secret generic pdbusr --from-file=e_pdbusr.txt -n pdbnamespace +kubectl create secret generic pdbpwd --from-file=e_pdbpwd.txt -n pdbnamespace + +rm dbuser.txt dbpass.txt wbuser.txt wbpass.txt pdbusr.txt pdbpwd.txt \ + e_dbuser.txt e_dbpass.txt e_wbuser.txt e_wbpass.txt e_pdbusr.txt e_pdbpwd.txt +``` + +### 2.9. Create lrest pod + +To create the REST pod and monitor its processing, use the `yaml` file [`create_lrest_pod.yaml`](./usecase/create_lrest_pod.yaml) + +Ensure that you update the **lrestImage** with the latest version available on the [Oracle Container Registry (OCR)](https://container-registry.oracle.com/ords/f?p=113:4:104288359787984:::4:P4_REPOSITORY,AI_REPOSITORY,AI_REPOSITORY_NAME,P4_REPOSITORY_NAME,P4_EULA_ID,P4_BUSINESS_AREA_ID:1283,1283,This%20image%20is%20part%20of%20and%20for%20use%20with%20the%20Oracle%20Database%20Operator%20for%20Kubernetes,This%20image%20is%20part%20of%20and%20for%20use%20with%20the%20Oracle%20Database%20Operator%20for%20Kubernetes,1,0&cs=3076h-hg1qX3eJANBcUHBNBCmYWjMvxLkZyTAhDn2e8VR8Gxb_a-I8jZLhf9j6gmnimHwlP_a0OQjX6vjBfSAqQ) + +```bash +--> for amd64 +lrestImage: container-registry.oracle.com/database/operator:lrest-241210-amd64 + +--> for arm64 +lrestImage: container-registry.oracle.com/database/operator:lrest-241210-arm64 +``` + +```bash +kubectl apply -f create_lrest_pod.yaml +``` + +monitor the file processing: + +```bash +kubectl get pods -n cdbnamespace --watch +NAME READY STATUS RESTARTS AGE +cdb-dev-lrest-rs-9gvx2 0/1 Pending 0 0s +cdb-dev-lrest-rs-9gvx2 0/1 Pending 0 0s +cdb-dev-lrest-rs-9gvx2 0/1 ContainerCreating 0 0s +cdb-dev-lrest-rs-9gvx2 1/1 Running 0 2s + +kubectl get lrest -n cdbnamespace +NAME CDB NAME DB SERVER DB PORT TNS STRING REPLICAS STATUS MESSAGE +cdb-dev DB12 (DESCRIPTION=(CONNECT_TIMEOUT=90)(RETRY_COUNT=30)(RETRY_DELAY=10)(TRANSPORT_CONNECT_TIMEOUT=70)(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL=TCP)(HOST=scan12.testrac.com)(PORT=1521)(IP=V4_ONLY))(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL=TCP)(HOST=scan34.testrac.com)(PORT=1521)(IP=V4_ONLY))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=TESTORDS))) 1 Ready +``` + +Check the Pod logs: + +```bash +/usr/local/go/bin/kubectl logs -f `/usr/local/go/bin/kubectl get pods -n cdbnamespace|grep lrest|cut -d ' ' -f 1` -n cdbnamespace +``` + +Output example: + +```text +... +... +2024/09/05 12:44:09 wallet file /opt/oracle/lrest/walletfile exists completed +2024/09/05 12:44:09 call: C.ReadWallet +LENCHECK: 7 11 7 8 +2024/09/05 12:44:09 ===== DUMP INFO ==== +00000000 28 44 45 53 43 52 49 50 54 49 4f 4e 3d 28 43 4f |(DESCRIPTION=(CO| +00000010 4e 4e 45 43 54 5f 54 49 4d 45 4f 55 54 3d 39 30 |NNECT_TIMEOUT=90| +00000020 29 28 52 45 54 52 59 5f 43 4f 55 4e 54 3d 33 30 |)(RETRY_COUNT=30| +00000030 29 28 52 45 54 52 59 5f 44 45 4c 41 59 3d 31 30 |)(RETRY_DELAY=10| +00000040 29 28 54 52 41 4e 53 50 4f 52 54 5f 43 4f 4e 4e |)(TRANSPORT_CONN| +00000050 45 43 54 5f 54 49 4d 45 4f 55 54 3d 37 30 29 28 |ECT_TIMEOUT=70)(| +00000060 4c 4f 41 44 5f 42 41 4c 4c 41 4e 43 45 3d 4f 4e |LOAD_BALLANCE=ON| +00000070 29 28 41 44 44 52 45 53 53 3d 28 50 52 4f 54 4f |)(ADDRESS=(PROTO| +00000080 43 4f 4c 3d 54 43 50 29 28 48 4f 53 54 3d 73 63 |COL=TCP)(HOST=sc| +00000090 61 6e 31 32 2e 74 65 73 74 72 61 63 2e 63 6f 6d |an12.testrac.com| +000000a0 29 28 50 4f 52 54 3d 31 35 32 31 29 28 49 50 3d |)(PORT=1521)(IP=| +000000b0 56 34 5f 4f 4e 4c 59 29 29 28 4c 4f 41 44 5f 42 |V4_ONLY))(LOAD_B| +000000c0 41 4c 4c 41 4e 43 45 3d 4f 4e 29 28 41 44 44 52 |ALLANCE=ON)(ADDR| +000000d0 45 53 53 3d 28 50 52 4f 54 4f 43 4f 4c 3d 54 43 |ESS=(PROTOCOL=TC| +000000e0 50 29 28 48 4f 53 54 3d 73 63 61 6e 33 34 2e 74 |P)(HOST=scan34.t| +000000f0 65 73 74 72 61 63 2e 63 6f 6d 29 28 50 4f 52 54 |estrac.com)(PORT| +00000100 3d 31 35 32 31 29 28 49 50 3d 56 34 5f 4f 4e 4c |=1521)(IP=V4_ONL| +00000110 59 29 29 28 43 4f 4e 4e 45 43 54 5f 44 41 54 41 |Y))(CONNECT_DATA| +00000120 3d 28 53 45 52 56 45 52 3d 44 45 44 49 43 41 54 |=(SERVER=DEDICAT| +00000130 45 44 29 28 53 45 52 56 49 43 45 5f 4e 41 4d 45 |ED)(SERVICE_NAME| +00000140 3d 54 45 53 54 4f 52 44 53 29 29 29 |=TESTORDS)))| +00000000 2f 6f 70 74 2f 6f 72 61 63 6c 65 2f 6c 72 65 73 |/opt/oracle/lres| +00000010 74 2f 77 61 6c 6c 65 74 66 69 6c 65 |t/walletfile| +2024/09/05 12:44:09 Get credential from wallet +7 +8 +2024/09/05 12:44:09 dbuser: restdba webuser :welcome +2024/09/05 12:44:09 Connections Handle +2024/09/05 12:44:09 Working Session Aarry dbhanlde=0x1944120 +2024/09/05 12:44:09 Monitor Session Array dbhanlde=0x1a4af70 +2024/09/05 12:44:09 Open cursors +Parsing sqltext=select inst_id,con_id,open_mode,nvl(restricted,'NONE'),total_size from gv$pdbs where inst_id = SYS_CONTEXT('USERENV','INSTANCE') and name =upper(:b1) +Parsing sqltext=select count(*) from pdb_plug_in_violations where name =:b1 +2024/09/05 12:44:11 Protocol=https +2024/09/05 12:44:11 starting HTTPS/SSL server +2024/09/05 12:44:11 ==== TLS CONFIGURATION === +2024/09/05 12:44:11 srv=0xc000106000 +2024/09/05 12:44:11 cfg=0xc0000a2058 +2024/09/05 12:44:11 mux=0xc0000a2050 +2024/09/05 12:44:11 tls.minversion=771 +2024/09/05 12:44:11 CipherSuites=[49200 49172 157 53] +2024/09/05 12:44:11 cer=/opt/oracle/lrest/certificates/tls.crt +2024/09/05 12:44:11 key=/opt/oracle/lrest/certificates/tls.key +2024/09/05 12:44:11 ========================== +2024/09/05 12:44:11 HTTPS: Listening port=8888 +2024/09/05 12:44:23 call BasicAuth Succeded +2024/09/05 12:44:23 HTTP: [1:0] Invalid credential <-- This message can be ignored + +``` + +**lrest Pod creation** - parameters list +| Name | Dcription | +--------------------------|-------------------------------------------------------------------------------| +|cdbName | Name of the container database (db) | +|lrestImage (DO NOT EDIT) | **container-registry.oracle.com/database/lrest-dboper:latest** use the latest label availble on OCR | +|dbTnsurl | The string of the tns alias to connect to cdb. Attention: remove all white space from string | +|deletePdbCascade | Delete all of the PDBs associated to a CDB resource when the CDB resource is dropped | +|autodiscover | boolean parameter: enable the capability of automatic CRD/LRPDB creation if a PDB is manually created via CLI | +|namespaceAutoDiscover | Namespace name used by autodiscery | +|cdbAdminUser | Secret: the administrative (admin) user | +|cdbAdminPwd | Secret: the admin user password | +|webServerUser | Secret: the HTTPS user | +|webServerPwd | Secret: the HTTPS user password | +|cdbTlsCrt | Secret: the `tls.crt ` | +|cdbPubKey | Secret: the public key | +|cdbPrvKey | Secret: the private key | +|loadBalancer | Expose lrest pod ip | +|clusterip | Assigne a cluster ip | +|trace_level_client | Turn on the sqlnet **trace_level_client** | + +### 2.10. Openshift configuration + +For the open shift installation you need to do the following +- Before lrest pod creation; create a security context by appling the yaml file [security_context.yaml](./usecase/security_context.yaml): mind to specify the correnct **namespace** and the **service name account**. + +```yaml +[...] +apiVersion: v1 +kind: ServiceAccount +metadata: + name: lrest-sa + namespace: cdbnamespace +[...] +``` + +- Specify `serviceAccountName` parameter in the lrest server yaml file + +```yaml +[...] + serviceAccountName: lrest-sa +[...] +``` + + +### 2.11. Create PDB + +To create a pluggable database, apply the yaml file [`create_pdb1_resource.yaml`](./usecase/create_pdb1_resource.yaml) + +```bash +kubectl apply -f create_pdb1_resource.yaml +``` +Check the status of the resource and the PDB existence on the container db: + +```bash +kubectl get lrpdb -n pdbnamespace +NAME CONNECT_STRING CDB NAME LRPDB NAME LRPDB STATE LRPDB SIZE STATUS MESSAGE LAST SQLCODE +lrpdb1 (DESCRIPTION=(CONNECT_TIMEOUT=90)(RETRY_COUNT=30)(RETRY_DELAY=10)(TRANSPORT_CONNECT_TIMEOUT=70)(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL=TCP)(HOST=scan12.testrac.com)(PORT=1521)(IP=V4_ONLY))(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL=TCP)(HOST=scan34.testrac.com)(PORT=1521)(IP=V4_ONLY))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=pdbdev))) DB12 pdbdev MOUNTED 2G Ready Success +``` + +```bash +SQL> show pdbs + + CON_ID CON_NAME OPEN MODE RESTRICTED +---------- ------------------------------ ---------- ---------- + 2 PDB$SEED READ ONLY NO + 3 PDBDEV MOUNTED +SQL> +``` + +> Note that after creation, the PDB is not open. You must explicitly open it using a dedicated `yaml` file. + +**pdb creation** - parameters list + +| Name | Dcription | +|-------------------------|-------------------------------------------------------------------------------| +|cdbResName | REST server resource name | +|cdbNamespace | Namespace of the REST server | +|cdbName | Name of the container database | +|pdbName | Name of the PDB that you want to create | +|assertiveLrpdbDeletion | Boolean: true - CRD and PDB are deleted both ; false - olny CRD is deleted | +|adminpdbUser | Secret: PDB admin user | +|adminpdbPass | Secret: password of PDB admin user | +|lrpdbTlsKey | Secret: `tls.key ` | +|lrpdbTlsCrt | Secret: `tls.crt` | +|lrpdbTlsCat | Secret: `ca.crt` | +|webServerUser | Secret: the HTTPS user | +|webServerPwd | Secret: the HTTPS user password | +|cdbPrvKey | Secret: private key | +|cdbPubKey | Secret: public key | +|pdbconfigmap | kubernetes config map that contains the PDB initialization (init) parameters | + +> NOTE: **assertiveLrpdbDeletion** needs to be explicitly set for PDB **CLONE** **CREATE** **PLUG** . + +🔥 **assertiveLrpdbDeletion** drops pluggable database using **INCLUDE DATAFILES** option + +The following parameters **adminpdbUser** **adminpdbPass** **lrpdbTlsKey** **lrpdbTlsCrt** **lrpdbTlsCat** **webServerUser** **webServerPwd** **cdbPrvKey** **cdbPubKey** must be specified in all PDB lifecycle management `yaml` files. For the sake of presentation they will be omitted in the subsequent tables. + + +#### 2.11.1. pdb config map + +**pdbconfigmap** parameters specifies a kubernetes `configmap` with init PDB parameters. The config map payload has the following format: + + +``` +;; +;; +;; +.... +.... +;; +``` + +Example of `configmap` creation: + +```bash +cat < parameters.txt +session_cached_cursors;100;spfile +open_cursors;100;spfile +db_file_multiblock_read_count;16;spfile +EOF + +kubectl create configmap config-map-pdb -n pdbnamespace --from-file=./parameters.txt + +kubectl describe configmap config-map-pdb -n pdbnamespace +Name: config-map-pdb +Namespace: pdbnamespace +Labels: +Annotations: + +Data +==== +parameters.txt: +---- +session_cached_cursors;100;spfile +open_cursors;100;spfile +db_file_multiblock_read_count;16;spfile +test_invalid_parameter;16;spfile +``` + +- If specified, the `configmap` is applied during PDB **cloning**, **opening** and **plugging** +- The `configmap` is not monitored by the reconciliation loop; this feature will be available in future releases. This means that if someone decides to manually alter an init parameter, then the operator does not take any actions to syncronize PDB configuration with the `configmap`. +- **Alter system parameter feature** will be available in future releases. +- A `configmap` misconfiguration (typo, invalid parameter, invalid value) does not stop the operation. A warning with the associated SQL code is written in the log file. + +- **PDB configmap bitmap** status is not reported by the *kubectl get lrpdb* command; You can describe the resource to verify the bitmap status (*kubectl describe lrpdb ....*). + +| Name | Value | Description | +|---------|-----------|---------------------------------------------------| +| MPAPPL | 0x00000001|The map config has been applyed | +| MPSYNC | 0x00000002|The map config is in sync with v$parameters where is default=flase (**not yet available**)| +| MPEMPT | 0x00000004| The map is empty - not specify | +| MPWARN | 0x00000008| Map applied with warnings | +| MPINIT | 0x00000010| Config map init + + + +### 2.12. Open PDB + +To open the PDB, use the file [`open_pdb1_resource.yaml`](./usecase/open_pdb1_resource.yaml): + +```bash +kubectl apply -f open_pdb1_resource.yaml +``` + + **pdb opening** - parameters list + +| Name | Description/Value | +|-------------------------|------------------------------------------------------------------------| +|cdbResName | REST server resource name | +|cdbNamespace | Namespace of the REST server | +|cdbName | Name of the container database (CDB) | +|pdbName | Name of the pluggable database (PDB) that you are creating | +|pdbState | Use `OPEN` to open the PDB | +|modifyOption | Use **READ WRITE** to open the PDB | +|modifyOprion2 | Default is NONE, set to **RESTRICT** to open the pdb in restrited mode | + +**Imperative command:** + +```bash +kubectl patch lrpdb [lrpdb_resource_name] -n [ppdb_namespace] -p \ + '{"spec":{"pdbState":"OPEN","modifyOption":"READ WRITE","modifyOption2":"NONE"}}' --type=merge +``` + +### 2.13. Close PDB + +To close the PDB, use the file [`close_pdb1_resource.yaml`](./usecase/close_pdb1_resource.yaml): + +```bash +kubectl apply -f close_pdb1_resource.yaml +``` +**pdb closing** - parameters list +| Name | Description/Value | +|-------------------------|-------------------------------------------------------------------------------| +|cdbResName | REST server resource name | +|cdbNamespace | Namespace of the REST server | +|cdbName | Name of the container database (CDB) | +|pdbName | Name of the pluggable database (PDB) that you want to create | +|pdbState | Use `CLOSE` to close the PDB | +|modifyOption | Use **IMMEDIATE** to close the PDB | + + +**Imperative command:** + +```bash +kubectl patch lrpdb [lrpdb_resource_name] -n [ppdb_namespace] -p \ + '{"spec":{"pdbState":"CLOSE","modifyOption":"IMMEDIATE"}}' --type=merge +``` + +### 2.14. Clone PDB ### + +To clone the PDB, use the file [`clone_pdb1_resource.yaml`](./usecase/clone_pdb1_resource.yaml): + +```bash +kubeclt apply -f clone_pdb1_resource.yaml +``` +**pdb cloning** - parameters list +| Name | Description/Value | +|-------------------------|-------------------------------------------------------------------------------| +|cdbResName | REST server resource name | +|cdbNamespace | Namespace of the REST server | +|cdbName | Name of the container database (CDB) | +|pdbName | The name of the new pluggable database (PDB) | +|`srcPdbName` | The name of the source PDB | +|fileNameConversions | File name convert pattern **("path1","path2")** or **NONE** | +|totalSize | Set **unlimited** for cloning | +|tempSize | Set **unlimited** for cloning | +|pdbconfigmap | kubernetes **configmap** which contains the PDB init parameters | + + +### 2.15. Unplug PDB + +To unplug the PDB, use the file [`unplug_pdb1_resource.yaml`](./usecase/unplug_pdb1_resource.yaml): + +**pdb unplugging** +| Name | Description/Value | +|-------------------------|-------------------------------------------------------------------------------| +|cdbResName | REST server resource name | +|cdbNamespace | Namespace of the REST server | +|cdbName | Name of the container database (CDB) | +|pdbName | Name of the pluggable database (PDB) | +|xmlFileName | Unplug xmlfile path | +|pdbState | `UNPLUG` + +### 2.16. Plug PDB + +To plug in the PDB, use the file [`plug_pdb1_resource.yaml`](./usecase/plug_pdb1_resource.yaml). In this example, we plug in the PDB that was unpluged in the previous step: + +**pdb plugging** +| Name | Description/Value | +|-------------------------|-------------------------------------------------------------------------------| +|cdbResName | REST server resource name | +|cdbNamespace | Namespace of the REST server | +|cdbName | Name of the container database (CDB)| | +|pdbName | Name of the pluggable database (PDB) | +|**xmlFileName** | XML file path | +|fileNameConversions | File name convert pattern **("path1","path2")** or **NONE** | +|sourceFileNameConversion | See parameter [SOURCE_FILE_NAME_CONVERT](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/CREATE-PLUGGABLE-DATABASE.html#GUID-F2DBA8DD-EEA8-4BB7-A07F-78DC04DB1FFC__CCHEJFID) documentation | +|pdbconfigmap | Kubernetes `configmap` that contains the PDB init parameters | +|pdbState | `PLUG` + +### 2.17. Delete PDB + +To delete the PDB, use the file [`delete_pdb1_resource.yaml`](./usecase/delete_pdb1_resource.yaml) + +**pdb deletion** + +| Name | Dcription/Value | +|-------------------------|-------------------------------------------------------------------------------| +|cdbResName | REST server resource name | +|cdbNamespace | Namespace of the REST server | +|cdbName | Name of the container database (CDB) | +|pdbState | `DELETE` | +|dropAction | **INCLUDING** - Including datafiles or **NONE** | +|imperativeLrpdbDeletion | boolean: if true pdb and k8s resource will be deleted if false only resource is deleted| + + +In order to delete crd and pdbs using yaml file the **imperativeLrpdbDeletion: true** must be specified in the yaml. **If the parameter is not specified PDB will not be deleted; regardless the setting during creation**. Imperative command (kubectl delete lrpdb ) acts according to imperativeLrdbDeletion setting. You can check the imperativeLrpdbDeletion setting using + +**Imperative command** + +```bash +kubectl delete lrpdb -n +``` + +**Check imperativelrpdbdeletion setting** +```bash +/usr/bin/kubectl get lrpdb -n pdbnamespace \ + -o jsonpath='{range .items[*]}{.metadata.name}{" "}{.spec.pdbName}{" "}{.status.openMode}{" "}{.spec.imperativeLrpdbDeletion}{" "}{"\t\t"}{"\n"}{end}'| sed 's/READ WRITE/READ_WRITE/g' |awk ' BEGIN { printf( "%-20s %-10s %-10s %10s\n","CRD","PDB NAME","OPEN MODE","IMPERATIVELRPDBDELETION"); \ + printf( "%-20s %-10s %-10s %-23s\n","--------------------","----------","----------","-----------------------");\ + } { printf("%-20s %-10s %-10s %-23s\n",$1,$2,$3,$4) }' + +CRD PDB NAME OPEN MODE IMPERATIVELRPDBDELETION +-------------------- ---------- ---------- ----------------------- +pdb1 pdbdev READ_WRITE true +pdb2 pdbprd MOUNTED true +``` + +## 3. SQL/PLSQL SCRIPT EXECUTION + + +Plsql and sql script can be stored in a kubernetes configmap, each block can be tagged with a label as describe in the example. + +```yaml + +## 4. PLSQL / SQL Block config schema + +apiVersion +kind CofigMap + name: + namespace: +data: +:| + +:| + +[...] + + +``` + +![plsqlblock](./images/plsqlmap.jpg) + +The sql and plsqlcode must be indented using tab (makefile stile). The code blocks will be executed following the alphabetic tag order. + +### 3.1. Apply plsql configmap + +```bash +kubectl patch lrpdb pdb1 -n pdbnamespace -p '{"spec":{"codeconfigmap":""}}' --type=merge +``` + +The **kubectl get** commands shows only the return code of the last plsql code executed. Describe the resource if you need to verify the overall status of the whole config map execution; see the events history in the example + +```bash +/usr/bin/kubectl patch lrpdb pdb1 -n pdbnamespace -p \ + '{"spec":{"codeconfigmap":"sql-map-example1"}}' --type=merge +lrpdb.database.oracle.com/pdb1 patched + + +/usr/bin/kubectl get events --sort-by='.lastTimestamp' -n pdbnamespace +LAST SEEN TYPE REASON OBJECT MESSAGE +66s Normal Created lrpdb/pdb1 LRPDB 'pdbdev' created successfully +66s Normal Created lrpdb/pdb1 PDB 'pdbdev' imperative pdb deletion turned on +57s Normal Modify lrpdb/pdb1 Info:'pdbdev OPEN ' +50s Normal Modified lrpdb/pdb1 'pdbdev' modified successfully 'OPEN' +38s Warning lrpdbinfo lrpdb/pdb1 pdb=pdbdev:test_invalid_parameter:16:spfile:2065 +11s Normal APPLYSQL-143002 lrpdb/pdb1 CODE:SQLCODE '[plblock1.sql]':'0' +8s Normal APPLYSQL-143005 lrpdb/pdb1 CODE:SQLCODE '[plblock2.sql]':'0' +5s Normal APPLYSQL-143008 lrpdb/pdb1 CODE:SQLCODE '[plblock3.sql]':'0' +2s Normal APPLYSQL-143011 lrpdb/pdb1 CODE:SQLCODE '[plblock4.sql]':'0' +``` +The message format for the **APPLYSQL** is `CODE:SQLCODE '[]':''` + +### 3.2. Limitation + +- All the objects in the plsql confgmap must be rapresented in the form `.`. Due to this constraint it's not possible to rename table + +```bash ++----------------------------------------------------------------------+ +| plblock1.sql: | | +| rename plsqltestuser.k8splsqltab to plsqltestuser.tablerename |--------------+ ++----------------------------------------------------------------------+ | + | + + +3m55s Warning APPLYSQL-100536 lrpdb/pdb1 CODE:SQLCODE '[plblock1.sql]':'1765' + +``` + +- The nummber of code lines is limited by the configmap capability. To workaround this limitation you can use more configmaps. + +## 4. TROUBLESHOOTING + +### 4.1. Get rid of error status + +If for some reason you get an operation failure it's possible to manually fix the problem and then rest the bitmask status to re-execute the operation. Consider the following example: the unplug command does not complete successfully because xmlfile already exists ; **ORA-65170** and **PDBUPE** flag is on. You can remove the file and then rest the bitmask status in order to retry the operation. + +```text +RESOURCE STATUS: +~~~~~~~~~~~~~~~~ +kubectl get lrpdb -n pdbnamespace +NAME CDB NAME PDB NAME PDB STATE PDB SIZE MESSAGE RESTRICTED LAST SQLCODE LAST PLSQL BITMASK STATUS CONNECT_STRING +pdb1 DB12 pdbdev MOUNTED 0.80G close:[ORA-65170] NONE 65170 [262213]|PDBCRT|PDBCLS|FNALAZ|PDBUPE| (DESCRIPTION=(CONNECT_TIMEOUT.... + +FIX THE PROBLEM: +~~~~~~~~~~~~~~~~ +RM THE XMLFILE + +UPDATE THE BITMASK STATUS: +~~~~~~~~~~~~~~~~~~~~~~~~~ +Calculate the bitmask status without the PDBUPE flag and patch the resource +[262213]|PDBCRT|PDBCLS|FNALAZ|PDBUPE| -> [69]|PDBCRT|PDBCLS|FNALAZ| = 0x00000001 | 0x00000004 | 0x00000040 + +------+ +kubectl patch lrpdb pdb1 -n pdbnamespace -p \ + '{"spec":{"pdbState":"RESET","reststate":69}}' --type=merge + +kubectl get lrpdb -n pdbnamespace +NAME CDB NAME PDB NAME PDB STATE PDB SIZE MESSAGE RESTRICTED LAST SQLCODE LAST PLSQL BITMASK STATUS CONNECT_STRING +pdb1 DB12 pdbdev MOUNTED 0.80G close:[ORA-65170] NONE 65170 [69]|PDBCRT|PDBCLS|FNALAZ| (DESCRIPTION=(CONNECT_TIMEOUT.... + ^^^^^^^^^^^^^^^^^^^^^^^^^^ + [READY TO BE UNPLUGGED] +``` +## 5. UPGRADE EXISTING INSTALLATION + +In order to migrante an existing installation to the new version of controller you can laverage on the autodiscover installation. Patch all your lrpdb resources by setting **assertiveLrpdbDeletion** false. After that you can delete lrest resource and all lrpdbs. Upgrade the operator and create lrest server with **autodiscover** and **namespaceAutoDiscover** configured. + +- For each CRD/LRPDB turn off **assertiveLrpdbDeletion** + +```bash +kubectl patch lrpdb -n -p '{"spec":{"imperativeLrpdbDeletion":false}}' --type=merge + +kubectl wait --for jsonpath='{.spec.imperativeLrpdbDeletion'}=false lrpdb -n --timeout=3m + +``` + +- Delete **LRPDB** resource + +```bash +kubectl delete lrpdb -n +``` + +- Delete **LREST** resource +```bash +kubectl delete rest -n +``` +- Upgrade the operator + +```bash +kubectl replace -f oracle-database-database.yaml +``` + +- (**A**) deploy **LREST** controller + +``` +kubectl apply -f create_lrest_pod.yaml +``` +- Turn on the **autodiscover** + +```bash +kubectl patch lrest -n -p '{"spec":{"autodiscover":true}}' --type=merge +kubectl patch lrest -n -p '{"spec":{"namespaceAutoDiscover":"}}' --type=merge +``` +Check the lrpdb resource existence + +- Turn off the **autodiscover** + +```bash +kubectl patch lrest cdb-dev -n -p '{"spec":{"autodiscover":false}}' --type=merge +``` + +## DEPLOY MULTITENANT CONTROLLERS ON CDB WITH EXISTINGS PDBS + +- To deploy multitenant controllers on a container database with existing pdbs start the previous procedure at point (**A**) + +## 6. KNOWN ISSUES + +- Error message `ORA-01005` is not reported in the lrest database login phase if password is mistakenly null. Trace log shows the message ORA-1012. + +- + diff --git a/docs/multitenant/lrest-based/images/Generalschema2.jpg b/docs/multitenant/images/Generalschema2.jpg similarity index 100% rename from docs/multitenant/lrest-based/images/Generalschema2.jpg rename to docs/multitenant/images/Generalschema2.jpg diff --git a/docs/multitenant/images/KubectlGetSchema.jpg b/docs/multitenant/images/KubectlGetSchema.jpg new file mode 100644 index 0000000000000000000000000000000000000000..93b107cc4c92bbbddb5071d3c92582c0ca7ffb57 GIT binary patch literal 206768 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 z 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

#N%6u{LazDD?+Sr^fbdP4nocznTMUdH0K{C5 ziTSh9nQ@=i!+nQo#)vwD;&?rEnLpHr&drVizS+m7R)|uO$GeML`FevLE-Zr%I{DuA zTNpWf7)MT)yJybde#$vU@nG-%7H+TD@P5rUJ>OmebxAPf^suLKj}RC8$Fi^FvlrBx z28rd57pzY;O)j*nXYAcQk*A>vd)zbT2cgbI8GjwZW_QDZX%aCPd-lMj4p!Dj3z8|c zP==Z^X{^rCiw2_WeE#ECitPKBhfY6>PFA+nHjH06pV66V>u>6U@J9Bq$M{c+QQ&ns zxZ*c`2%Au~8qj9-bG_7rt%Tw?k!FaZ?&p^I+*R0#fNu>``A^xGu**qF+1T0pyUo|4 zXCZdM(Q}0oorpU&v8bT%9c$Y*%snL4+ z?>yJ#f~I=D7ZA1uh9Vk&%|)x{mA$7pLNep$g$nOAMjG|)B7Wnl#l<_ke-4pog0b8H z7}HCs0>-`Er7M_*Ua+5O`V0~Ie4*#GP;>qK4$=R=3%RyOjW^-`>iHfT;3_59%N17Dj}?aNnkh3{N#2TTaSq#7Fyo~mO6 zW6*%cQeNn&5Q~+vwx%mvf5&Bou??MhckF=Jv}za@V{*fN`cKM0CXpK7o_;RpAYt=9 zjw``n?oJ*HcSyQe`ReEFp9|h2UtP;F)_n1B$mDh*7i@lK08>}A;g7#FujJekosNLo zs?OMfR~yBt55*6{GtVh}o4mK%i#ah)ox0F$?>8!Jy=&|1bm#5A>B5!!?v88XIumzH zD)s2foiK6|Nh74{efNbSjyxS3-vqHd-MMMk3Bw;E27I!)!%Tp15Hm}u? zDB`?4g`uoqbfEu=t&GG#ZWyq93!mK4jpSo$e@;4PAR{!7j9=T&+rRd|H$Y6g%?CUW$HACS2W@DDyxTK zPrA~STdf|9NCGjr(r+fhwp?zx__YkhfmLMK))J&3&#w!t(Hd%u>-7{_wCQ=Gp*_X@6b!m5cv+%Bbhd8CF|Uvd$|&*y;x~ zYWLpl*6xzHrq0k=;38X_)X-`64k3jQa@F@Yzd1%* zU!CWAKZt7Fr9VGp{ZE^J9}NOlsD=tPHpQyc-9$9`pDQ`!+Hc%bP4CKt0CAV)sZ0?= zl4f^aV#dnhf(iSdOF?`*AFO||?5$r~;PHz~cm8w1*XjRAl3bzR8ymH*yZ6M^h&xQP zdmT}{ZuExg-DHmJRNxyH7Y4pDmc1|xuyOOjwjvZh<-ZnUS!7i`tZyE=QfVfZzD_HA z8(Kw^8qy>7%9z6Joz$VO+}*X3ack9G`HbcDw3vpe+Rg{Nbl0n_Ir6_df2s`+vXetN zX(zemjJ)Bn({BOK%EQ62yXm#!p$7hQs;(LSs@+kf}_X zDqK|s9fM-u69BLJz!pnh;5B+ob8N-IDL)4(v=+pN{ z{dbrr9izO9c?L7+C4W(f#A;l zQ&kNs9R1|<9ep#pSbh)O|Nf_CbM9AfH$D>m-*n-8Vgd4of?yTfut|=w3v*E$ zHaVg4P_R>EijOrOoJ1Vt@E}t-`M8GLe@uYKbM@qndw3i6ezHO`^4Thv;_8C_!?@|h zGU1QqX_y;eH!*p;#Zn4Ce00jG!aL}6gd51rHkmYk=xc3F%=-#9y5jmGSi&1tx`jPF zdgfZ{B_?HOaZKu+tD!v|ZR5Y@;3njqqji|np!faEAror-kHE#d!;m<)ocq(SR@)-2 zkb`efg(SHnrTX1pN@6>C0^RPO^)&tqEs+0o^*{J={~^hLNb;vs{~s{?2MqrXFtFaA z5~>%ilM`PN4-z47ydA^&TRa=Zm;2op<<$fyt^p^B*)ALn?pI4`33D+LlzWw7k#s$vRimzW8nEStx zej-!=8SivsJju|bHh}#gQc_sPoSyGmidZmKQTjn-GyL$6CO`keOye5pNi!_J4z4l( zAetU5LC<_TwHa2_8;=_ptTH?lhQzl1AgarX922SfL6ji$=vUKo{>E0fcdE)S!(7mF zn8aNp2BC;mLC{=cN%_pS#M7jK03RQPk3V~y;C|yXl1ewuWj)uZrPz?eJ0H|KZ*nFD4W7@QNo=`J~^JAFqFY*$>j0nF`9C{F{I32_t zx6=^-P%3@Ov9tYy=+-vhLHL6UdS~uF`}JJhvW9cseIYgq975PHj8}b)ei)At2}Zpy ze~2>f6n|)Xx{zm()Vvo2tWm;{_O_RlRw-1?6m2h zJI%kq`R5Ic{eRV~^sj;bMW;k-((@U@8on3o6!*7z(z$*Ry&YDhPJD8b;Rc|8O5CyP zI&%qnz)#7k#=2O;R5=kjnT^P@0tE>{H@UK3+7xG9K-u)! z7Tv#1Mc5*;LzA0rvvA8id%{}g^`v2YvT!MXw%q2V4uYe#P*v+*w3V0g2pk|Sz)UD! zyT|pjJnw(c&rNKFW5#%fF2m^sic;SL)JW6mKz<p}Qg+l9sjaIFcO4jgC*j~UM6EE}4Tjn=qIGcTY^374pmy2HSgf{pGkss0U zLzl+#+2WpU^bjZ3P+^fv2#RAuF^W#OBj!y8v2js-3x0LbaCjeny** zeHE;Ob(FJkv^ufBHl3xqFbG1rDzEVMpGWjRAMd~G>fZF8?dm*wvZn(iNrdx+s9uR- zHW{xjxPn)9Q>u-f3$Y<2Cpf@hF!7t-LDR9)tjU~v!gae$pJ&of^~JDrZv z3FVd4TwTP6@*fW+pg_&!Z3sSQ#bkrfe1U$2;JFUD%xM)WL_(x|y2@Z-=$^J_&#T2f zHh0701Xt|X#Kus-Sn{~#7JRVS(ymGScU^V%VQ23o=n0sa#Zy>BXO2okSkZ~{*-jjcLr?U?xeE(m0#eZu0-|Js$4beQYyr{X|8W#6d%WENaq?pjGDb!9u z%RHyUa5VQXsr6aA@YV+ z3`a~>`ZD!{L3c2^(GnmXmBG_(m(3ZxSSljb{F<`^X;OUWd}u<5j-D3Hrm%dE(n*MQ zTd(=?!CCN(ZjuxzK!5xeO{ zuv9iK9&!?j4ca;PRQk?KqH6GS)QtMgYb~2pn!o7bH)r+k^qz$ePI;f{0?KX%JzF)v z-(661HI6^NR4h~u#meoT#XUgX2{Qg!><$Y6a?HPt^|iGE4(ESAf3MC0dy-Lx0+1UY zVNdn3k?~6rRm8_L#yb6L68rO&3xIG~gXn5l``wPiN=65^z!$?d#!dM&rtwSZn`+t> zqKHdxNPD?o$>rS5Qi4c6rtg*bzANgChpbi3Z-*HCB9?4b=$_wTLDjvmP@KsGMGcN+ z^n<&DwB>c+IDZ&nm)2nBeSaVi{hcaRJFV4;o+=?URxCS+9Adx6!YNuUZ-qYSsY(%vbImCgp|b`ok9k+8Fb|)Z954G3#+I9! z6L1E+#9h_`i|64hg_e(ym7Uq-0IXx+q%LUA7Bn{LZf_O&B?a$!uV-(2(vg3oclQPf zE>LLCqj1vA8NtJhQ}Ad@GB)5<`3@RSAoIGWvb9i%HWLdC=~1HZW_EE|_(3G^@Ssh} zZqr0+O7r&j@dD|OqYpu zv=$>!u#ryNPsbk~**6W+Unpgs+GpNT8%`E4?tt?f)yP{Fl#11e2-#@g!9`trou*N% zvAKj|;bno)1Sux>1a@+QoTsXCBvI9WM+rmIFO&qJ+qEiKLuynS-j|G|flJ;zoJnZ*QvvhDLOB zgsRQgz{shG`=JicORsex>#?`a7bJw}>B>(y^A#bs!t~}75j-txwc1vYA4F529v5vl z9k%urq{^C98#&boK!94c2C!tr)Ix?EK~A=5LQ|*u2rh)#M*$v&qo^-U z9rt~6A1*l+>4d%1BYmS+?Bk&7(o#=%Cm^a#DI_A~4dpPU7O4kTSIt0R!!`$zftdO9 zi$-TT>1Hx=aNhb#4!kq^DLC<)`RQ*_Q%C!%*lF=a&AO-I3~_RKLz5?3wBhdPf%-fb z+Wan-w_lp$ERV!uy~v_yI55iY{2j1M9K;y=49k=mvqrhZwyo(J+&FE*W@rS~d~fzy zh}kdhI4eqma3^qFgJv}Trb=JSd^)+l0!<&<4YJify zEdQ2h$nPj6#h-zxo|`dM@v?uMm6%Pv4n?5`M8;g+MfG0fQX7ZXNWVdVi>8#MhKUE& z5u(Ox?qm2zF^zit$4?5vb?(0%6VX04SX!vC2QZ~%+N-sYmvsq zAC1~IMokn`+A^4o*CeRNK~yGDW^hN<;Fx^*r)1@%?QZl!sI*zxnmhD09@pkDjw+r2 z=Qfa3VZdb2EbHkn2D zFtT!g7Tj=<(vl8PL0d~p8FM>Hk==|Yfs>4QkK7#DNGI(Rp;7wNiN449mBZ8ahCqwcasYO z`vLAj4y+KZ7{=`J4rr1~-unz^Nx6qd`gR)eQ@jwj8db-l%Nq5 zXrU&1wkRq8kOIz#s*e%C%YnC&M;ek;!TYlMIScf=K4ur4M^J~^$w}pn-wp$Vl?m)= zGh^rWHVlDAE0(XrI;sfuDB30nB9$~6>O$CT@>B0Htk5eCCGgI3?q6;E=^3ur z&VDn>PDXsl=x;ekZb57A1%4 z+0=9N^yY6(aUv#wHJ5qxF1^9_rAuoTl*tR5YLkUyJ@e!hmhQyk&F2qb&Qw2nWKs1s z8&fB;-h>8ZECA2X4SSZzYy~)NZ~CQg@15ZtU0!i@n=D#AWHxM&KFHbN+muIS-lA&o z6p0rJ;cOiY(Pkf4tLw-?rxxK|$-i8;b}KV}PjQdmF0xNPu$5RFRsoh={@ThNIG2c< zglQSGiSUFupgba*XuE3(7)+58_!|geXCA8OnoU)_WktYD@Ed^#g`KixHxz3RHj7DW1C=`1#AMU0-V%QudI4F)R zjeXSIrRSodr2CqEPpQjckit`>qhk;*tz3C~k3Oqhr4$GsbkWv9Zn|#^Uzc>P8krZ5 z5bOey(ZW@xHR>n84YmGOJ`w4Kh16CMan6*EsXrOe{>;1kQpVXHlG*8B2tpCgp#C7D zCRkwyU&MlBa<9hEQ~GF9yiFk6Xnxj^%#Teua_w?;SN~KY*-KY_;8;1|5U#ZT)HksB zD`iq0S2jY3z-q=x_Gs(R_X3BrI2|e(AQDuObxE ztD0P3(vHd%T`fSL#@%hUSx`(*2;}lXOqLJ|_k?yIp~=y87inSk0MxO3kfS*^gy`DO=7TcRhxwa;Qhv z848zaEGG+%K(u4}KGNv-nFFq%oSQUOUJ4b}>miq?8`sLQ`As!Db;K8Bly{B$>Y-yVuNW6>fsPPrvf7!D zcqJ>IR%LMhimK0o*Zw-a288W+gT8@5$@UCde9DdqcEaGQVzWfwT31nU9#1~B4Au@# zhXf(m-k0ZD*N&jK0F&PR-7dG?Czi4(TtEp&67T&pzaU0M-6t&T%OXDQuPZB2FdDUo zi8EE!6!Z*295PWYuo{@wGXkSHiz+J7B7bKTp}%Gn&cQmxB{~y_CJymB>)G+inujem z)$`FI=SvWAj7Wh4;|5E1h^I-bAAdF=cY>QMQzjBU z)s2f_y6Q%#qZs=-_0wd}GuNt@E45#b^H*0ML zL@(`!IIt})NSwjfWutxbR$q}K-*SVH+xt0o${Eh;Ib zrzapFAT(nwRgEtKK$V#5{>3~P_QMgh-NquzNG?{4*b>#|bV>Uvc0ic+8h(2~kHx=B z`%x4?DyU!&#D7q{CX~KsINhkohl}ZVMw0H3|wotS&ud7W)7dn;6E&R8F8X zwgCKbf5>W#bJE`|Jl`|)$6DSnSQS!Y7%FS!R~(j;ah8A7cwZUxET^N$6Wd>NC?Q1* z+?@5Qty?#7*NeJ5)FZ8hdSX~Vp2M6q#4kk|W|BXS(l6gu(iW_o^thOrvLqlw7UCVc z$N#n0Wy(wwJ39^|hk}9zqb}2u-fI%pt+_5cqAtK7MzwR_&P@+?DWCAE`hX1fQTrRj`9U6$GQGxrpmXE9@$Vgh(a-RTwxYlu1v))aQVhYw z8&s&t4Ai9Uety=rdh#!ZaB(kYFfLo*d`(Z|z~b8vr6BD4Vzwx5Rh#5+hrGZLR^9BL znD@;pxW$gZjt*O9&TeFv_pS@0s{?=$kc{E|>9YE@yVSSo!^ugzN{oZoPfR)A$9QJ7eXDE3CTR&jQ1}dUK1{F>?z4LauqUMTE6STihx$)i~}$tiP}?IWWw~a_7%{d~WG@xeWm=2v=F2Q%cxj-4wS> zSv8nis$dHAo~#GMQ~(G15aX+atFy-5eIhiB`wAlnF>8#v{J4m%|rO^LWPYD3KQwyG$rq$gp; zs6BuuC!+<$%B7}icD1MjjkC1^F&ISIdf2d}PGEvU%XpHVP4*P$LZQxov#qJlI5uZJ z(Ush8{-epn9-n;Q$37w~@$R8Ju_^1@1tLt>@ae!}_Y?#qvnM3B?Pu8+Y-oD0e_KlS z;8PXYafNJ=IS)^3?ELJ5q>-kW#Nhl^#pRto9RBMe5%HG5gnmxczLT z4sK=Ys;i3uV7*6xD#1Gm6 zgKJ+McP@nuYMTP7IW9->K9*a|HSW!!F+4V`saP3cjBg(N*kin;lO%8TY!_M^*jtUb z9}dTFzmCP#IHZ%egC-Xidrt#@8o3k4zf%GH*Q2<4GtSI4inJ;W>=BoCt22zjHaUz$ zK6k)U*(fal-vO%-pv<9ARCb0v`ht05slp;L_O&&{|KJ}rvI8Txprtab1)k6>jDuO+=)yf z)NUH8jyG$*i~cnAK%}4g_}s=x%4dm9A4^1)P^UDnx7EPy^CuE*#=+tY>y6$8Qnxc{ zlSfE>x(OL7yvCQ3gBro< z>vPU3WG%2Ew63OQ2KKgeBvNN5ZD~=nTo?)`+_DONpPA4n+$zC)sxa)E5h0 zwS66HWua4Tx7);uQ|K3@l?e_VrnfsK+Gi-ko1OM#$U=huWP(oJs zu!_1D^KZp5qC4%ZVT;$940bJcVKdtXSqj^;SB2uN(xW<%O zU5dq_#y{!jx7Fz5F%JsMtjS0pAQHDa=6pWLaEFHCUH1+M{F$YMVlZ*fYVod$_qgVq z1JUBRArp(w#CT9s6At?`Bd1D}T?kTX78Dxp4z2zfM!oXO+hyX*8GqAf@C^@HHgt=! zS=D*G;>^maqdFtbhYqqulpUlpPe$}bZS$|(7K0En=QAz>%(gn@h9hAZMP4lm3xj8) zopdv~5UQj&Mqqn>(si5s1PcobyQZdW>3Ng3&J@L0T*hx=)SI2lYKsajykdIaT*_vM zv=`3mKu=y1;*Kl>lU6Hw0*^x$sI+89ns#_a7K!g158hv$cN-3PY3kZn9h9=j_VSz4 zR`~7T-nWL}^juro8k`00gz2Yr*7g52qPZVR*(t-!I0$~u@Xk1!UIsx78T_@o^(Ii) zWm>;kaQHA6qq?`qx#=i!UdWW<$l#N3q^eVAu6=DOFtejqQQmBgoZ8($_jloZSlt`Ti%I3gY2TUCuwjV>hC>&b0fU88jyuJ(@{Jj#4Z9H&1Qgy5-ZwSBw< zra`Ck^%+b6)Sa-`YwLrO1~4M>0|sfKWP4z+bDQp#ku1hPT_h-bN*nVj_Z7kmox)a; z!PRXIBUoFgzX#NKq<^_@XYegsY(62bn9mH|<|*BMuW>iO+)RkI)Kc@kUNEhRU}21) z22GM`$CirPpclH4HHOr1YN&^@5NxMx(qW|W_(ZID5-U8~`{>$wP(y#jt?d*O52R#h zMKm&;i{WWnhoaEpa{m_$bfp6MT9XM7|}%F}Q}D%Lv7yRM}!- z#xP7r&h;)Yzr9#|2b(WkY8X~Ux$%QYv93My$lr$9I+ag`mqxdU#2K(u9I9bN2zUJ< z0#i?aU+UQTYj^jSgHH5Eb6s*!XnJQgW#nyD@7*kwNjEZWw2Jaj1&0bD>Wa!!(IVKo zsOymFw4H{e=glml)B>n{mS`^ap4mGj{V%?hI+A6|s_CGct;UgTCI}u3_4CKDTF_K! z;ZL(*78x2YjQ^bh{{?J0i`>HpAJkf!7gScc58rh&2j#35mUN4qDnFZKjm2pL(ncf8 z+)7m@Wzkl}fGx>}3BMv^k8G0P6pa@Y)hC|1wnP~PPfRGfIo1ur(tYTxA#5f_SseC5 zs#ZCjI*VrN3IY-jv}GnUG0s3{b;^r&c&v(HO@&NURT-_xIyNFpz`@7bjUQne`}2njt8H&?P>r|SPjRDPkh3QXx*qsSzwHY(Dz`rHxV2nYNa=*J@ED)=g9y_`PRA5u z_Gw>n5z7=B!rAri8&_v;g`T{64%y+tv~VWTg%w7IUi>nNCl|0OtL^@TC@MEQ{raaw z_f=~;;|$O#{&kyEU|=fqk)r_k;P%NGoh~t!dI--=P2%?L`f|hYz}}>ZGgKv=YnWUD z)hR8vQX1H?1*|JS(2?#uh!z6HElJ#2bFv+OfMQ6G>W1!r)eRIcOZ!3xrC-nUb z8k~(&>TRYBBq+pbsC!GEpy@pF92Jn>mqIUFvx@L;PCt(PK#R5k=JsH;`=OZQ*6QzC zdkF(3ezY~+_vVpfMQ=0pRYksB2Y)l;Ot+ufC{&94z~CLFqSlFEg8IlST0xb3{#b?s zgyR3O=pbh-x-+l|h`2m#&!z<(Gnmui){@G*SqHk5B{-26TD~t86~I8K8-_De|De(- zq4qHyFctcIyZ2$c>qNvH!=vHBx8%t}Mft&Q`4fa}8-;CON|!&5vs|BU1x6~BP@^!U z?SFT0otL)6&Hk|PH~w0O7g(O8yCwoXX(C7#W@s^?In0k@Dn(&N-$DW-W^PW1I#1&L zZ1z=CZ8CPWt%{lU%0^GP@?Tzm-#xxH@Wkbi*g#t|`QE^_Z3~@z3vn(tV&NqxoqRCB zMKf!O>VM!Mi7{6mvzAis<&y{59PHbWJxBG74j`bG*3IdjYt_1Szt5MU`BLA$&B+10 zd+{&%__~6pS6@N%$~{7%dT&IMRkmF8hfYLea}Wzt3wxt$9;>3#Q5z>ULp3~>4b4Z< z&=lD+LLb~xMT?EyQRW4MSV=h{2u=yT2ez7I9xc>_d_w%Oo74_Q0yaOxRu2y75S>mx z*BJW$nnqah2}X6JzFQjlZSZdg4>BoSKR?Y!D66(~Hflyoq1xGQhSZZCLZ}%fBb;|z z#AV?cE-oYi3^f&^`YPyz-M;pMCra;h=)&-=KB~kOEMOpaPl9$tFoiEabtLDn!#p#qc|6)vJ^;2V7RyA4P5^{$f)`M zzSwf`Bq1iiRvuo;FMdsfw*uduNag05aNr!9Nj9iKNR{b$FZvA`+E#`va327oayIHN zt+m1tggL%V3M>x#XSsBiCkU8)6UOG@m-;@X%4nO z45v;%21H0p6%X^;_W*|=EzL;SnB8-Gt}jNde#sA8%pAtE3P;pCqBx@-r>Kh$!ok{K z$jwU7Ug!)K&YEZS_^)3BUXIqsj?UF2L+IG;ukS!fyJ97&KZ*4wMEVJC{)o;=<-bom9iPImV z<`L((m_3^=uvU07Z46d6q9L}OdOcZ=kws*|m0G4^-1$~+nlw-`UTky=&<^V9*Y!(o zuR~W?X4je=HfZwFdW09AldwS8c??(Y_*Bs^jl|m0QGbWjc!f4Pi1TjnJ`mCv6`p+7 z+FZK0FS??t#Nl*dpHvPV4D*#-tsLbD>+Jf{AKSg#jts9{Ik9|!$(F^yK;s1 zdq%i|LDa98QEkp0#vXvWgzlg_0xZm2CjD+uskRdugoNwIafZfhg68!(dwF&&`xFf zfUM=v-%F_RQ}`Z0b51hob2wzzOravgzZsw3nc10DMQcRWi-~&e1FM`?Nba$1zLA#l zzFQbSV_AG}zRl!mFK0H(FJ1&I%k|aMy@~uM2WG?tQoyl^ZUnL5MuuIdhDq z9M>|c6lXYPj|Kx%L7n*=pw(s#rzGYwP2_j?yW~cuF)fL7K2Qp;2Ja+ArgojRj0T?h zUigQv+nQ8fJrO~Sh!+cOlG#=hKZy1{d%vDI=&$x0p#@UrMo1WZpHhZiqSP~^p5l*pwdI{9g!|wdR1@EoSEzQ%$zfK?#%C*IrsOR z4}Xv+`Ru;-UTg2Y*83*s4O@$?Pt)bWM2N>Be%!sJvO(+IQ!3BKT&Sxm!x}M8lROkX z_POlI9u`oR*-Z`}(Hxd;l10&K%zmR!XqLIzQe35%Bjhe#r1FrQ zvqd6FD^o}^TH+nYvv2{;I5rFB1qBgWInG;z^XMQsi~1A=VRRXnH>B&nq!=R0mu9q$Oz`=$(vb6#`DZg%Cjw6Y1R5Sffo4K{*|J=S; zv1R4V@qmb7^FDh@Thyj*XZ1#IV!B$ePQ8!;jmwU`jcIi#ZTc=PjiF&|*R6-2jc|u! zz7*ezEoM%$7hHTr&I#@v-=^Ge`&dDJ0|X1#>V_2nt@-VxCIX&LYh@1CldO6QczItkzmrdF6Z zc0m@Z%3=r6Z2&;M;usbVX?1nqSSRy(g{_i)_l=O}>C9U7)bCd)@#=ho# zFoJ=SXUINcK)yn3Uoc61=1G}ln;A=e< z6=D;_N|mp)mL3TA$ards-L9|SAj_2Vuy&AP2T|Y|}4$Rnp-SaHs=j$T|F@}cuKY5LKZ6;%y zgtK^_?Azqe`P{zxQvP$`_1*AaczG$|=dE2=W%pBt)$314&zntuq+feT{tHhPN9hyl%D5JlwIzYdl2dA=;vEEgulSdG#=J zz@;v=i}q9cSxa8doSJi}KPhLnzN(FT9oFwO20-fAHAKNX*oesdYOovIiVmIY>RMY? zH|lflR$~+rUbt05spEOd7b`+oP{znPHMWSAs+;q8NtotW8})JE;K_+Mrq0IRUqL%} zgxx~B+3ri3P2(`NnK})Ub%rTJ07v^hqMB=~dDR|YFxZWBMW^5=YzSTMO}2mRTV7WF z?65v4?>){sU!Ry{xd{bRq4zo>j)Z970tRT+ZKFh#^aQ?S&6Ls6l#DlK4f;~BvY7iz_S9>H1K^-m`Br&yX0ghjy z+DQ@OBMkI(PcwGsPt(+=q$p8~Bi1xP(>=+{O_0WcorTTJa|*G=QL%^G;nh7l_HJ4A zV<^4;dRhn(raNHObMcwW;&pFJkufX?Mzk}>nPFCY0v%;;TFQO7s7SRB>SxPp=}2VKRK;8QTIVk zt8*ZSt@Eiq;|M}oPAg=ZXHGy6vp8LB1DGPxcs>yu%m_wKQ!jY-x6SonX9!xd5PD*)OG!e8TxAk- zK25SomIfLh=Q9vMR2#ch4g1raK(#f)0-}7f<8{8&q5+Ko7w!{%E4X_@ z0s+huVSyIZ*;Qy`^dzolufkB;_nJ%h1x@mZho>JazwnMnqx}(c0XF6I^LD@RdVfJQvdmU&xD`kXD;+zH2 zG7G$at}Zau;>z@Bf;c~AG`z2T;BcNS%BeGV*ixhFPEWDt%3#WIs9_HmIhB!~CQ-`t zw;N@ZL+IEyLTp&YxmRKpF{wTZlhigX(=WX5vT<08Bq(c&$W(pG)~H1^r)i2^^RqvA zi3+oqJS1x7DN7RKuww&r$_cw0^+n827iG;vDijv&ajs6P+8Sr2#qb0JHwit+DA9uK zVck)jkjUOXWXu|WKHfl+%GRceww1)poj{4^?&|P)X``1D6b;2A>F+Tb$MK>>=@i9Q z+aW1l2~I-dAy|dY+iC6}1?Eside1_-hmkQVd^^f4J>aJD$4yusjtUGqUMv48Dg#DF z?pQx6IWcR)r)JwP-eqjxjIHN<;irBRxamg;(LD>SXVz(&T=c2lwB8 z@|qf)5mT81lVZFowGr`PqNWS1GA}WMwyWAET@TlE3x32Vz|yv)?D}5K9jFH>eWcKt zmN{0I9km5yE+wh1giK^88Bpcgg3sBzkJ`E-5aiTNgPBLAIx3Y&8Sa=jc8P_$D=&Ag z6w#(*68gfd40ggqLB6Dffd;)XRL-*yW4V!RXgJHhfL=Q^(t%FjF@94)w($I>P61Mv zd}@G?%z_gJM~2>BQ(3BHR`>F&2C&t3_UY+mvZ?!V5VA0`gE1(jusQ(&oMAuM*L4Kb z1Dq%V5+jTp$!YtyZCE#=QDe4r@=~1~0Z>@fYQh}{Nc-?biV7W7o*VfL2!3M$IYJ}w zTFYVhLPM|%Nv3%MrZoDUzAT!0cRpn5y))*3l-_`m148S;&-b=YD+q97_^7h z#t$|5VvMjX*_$jg4cK$>4fDrel_Cy@ma)1-z+3$Kt$H7{ql zqi4UAvlmm|evoE6eVV7K6b^gQOHS+(+xHNvl0;5dgX%uZ)EHY7gaGG_$?!#I_HaP@ z+X7M!7IisTU_Zr<6iVM*e~139fbFi7F>mSF2|0};bvQnOCM{SxLG;QROk8-QikGcx zgo%FMBi!|_%!u7usr#Kb6z)la(MBc89`Yr}ihBUU5e+ zZzJtgch)9Uh70>1iuoLQhu-B=V=gqvQI-<*PGK%L(@ssW5Y1=k?v*nKjtSwSMyw7V zIfftbpe%)(AGDW#r=$7&1bRAhoFNgVr>pABwUQv+t~cw{{qx)fylwvN8dv*3B#2Zs z@9XQ4g`+^+mRyV)ryJzRTW=``j1WJ&TCw|SaNE0HU{MDbg^ZpJ{VrL&sd&zA!xa1n z4M`K>CpgvH1_J)?U4QO&0+<|J~7 zvPxL;qujJ}<-*|Dy2tCP4if6LUL>7Nv9)u8h04&Npk0Na?TbRnHr9{B&>~2r7%8(= zE{sF*<_a$hY}oK^T5NDm;%8U)90m=PWlb(yB{IzR6Q5|^q=WyFJmuK3pXK(xYl^8I zeU@UBQ|R(+eL;=NWNB-+_P$GDav8Iu2szzhYS$YbV>MUBJ&C&RsLk@(VFb4bu6#y zdOB5|9m!ZV#{4$9jQ%j%XxhHzB>1To8nR^Xb|1Qx)KWlQ_2 zzCJ1HP%y5HPFz@CHMC#WGZ4B~%xfiDA5Ff^6KEDnCr2@&?7$OWs8Ex*wFHrVjSq1a zfcIC9s;=$o_dAt>`km76juiEao6oe=W(l3BQ(mY<)I;0e%o53Y%P~vayj%gZg_mIp zE>r53fJir=u^a8g>F@5B7(tdBnFXaHK%dZJ_OZ)r6|MZ?y7s zsyrleQ~>g0yexi3_>U%54vU=yjqvaTpZ>*0o^oGfoB?+dd58-AY5hnA;U>zwO!QBu zT1t4gW?M9qmjX8OvizNJ9wA&oJm!Ue+~3v2M0Wlx4KOSpFx76A8PDGS{;}&R-gS}S zo(sin>PQjQ{bJvrtHfRH(B@S6sEQb=J9b(tS@xD?(1Di47C%BhCkVA=Oc5HWhow6H zB9pu+9V!VHRhhkd2-n~_h`F_8&vv6 zc~3N#Rg8*9{zK$kI1TT+4zvg!_FY@_)&T>~T2vJbG@LU`HUMydy>Risq@=z}pRRKj z4-|{N#GPaDSW)XIS&8a}Yw7Fs2Rh3~N-&12%R6%v)yLdnem|g?gxvuI#pOb&!xA$W zC`#lg0>uM@EzfzGi|YwAYJm@zW-Y4K)e~;7LK}bKeI9-B!1Ej+KkUrum64g{Yb2Yo zKK)b(nT3GIh@TdJJD%4a!H6OI1v+v!DWDV$0yU)i2+|<3`|a5XDG-^KEz7pg1xq&5 zd01bhbI4|^i@4NNtbzAcL&gB@p+pAahNki_08wN#@m`2Drw36sCn%j z(KnVY8fUxUsqS`4`J9=k>nvkUdgXU(GL?~vo!rj3BDC04$W+6%ow+iYb>)L)C6R1E z>FH94$ffhlR~{7rOnIdfN>I!6r$Z;>*g z&GP$;6h{w@gr)@c6RE_H0@UB}IvcC<_KBHz@bK>BUjNH0olN*2wyHP$OmF#8#z#_grXBL_i`@RV^UI3Y`}MA|bP zNo`YKUsrkST;L?D>9O~q8};pbBvDw4pLB=K)8(ND+mO7*H4J7_K=h^eFT9@yr@;-k z%2YmX9%p=`r(B_L0RF-&p741)^i(Yx>y1leESBgy*w*%=Ms$&T#pb!e& zgbWPv6b-SiAjPdRQBMe_riCdr;q8&rt5{J*_U^(WBvqs?JUInkY-IA~f-S@6Z+E!G zpBwJ&_FriE-{r`x{&kf7Sad}a4SksIIRk|AT zxu)*3oTBl*MoD%e5o!rgziqu>5K1e3fDy9|GO@CjQ5^**~%Vu1Xvb0`l>&3;N6Mnz5y!Ctf2;L{A zLmf%1d*uTMF=sCWYBFC!W4%{;f0i5UzW$wp_{Q}=X9w@ATJxu8*s=cdhj~YzXo{ht zuUK7ET(NZiYxB#Ni=r#b{CU3~5B|DwlqPX9cl~}`ecr4rBWr0`*q}Mj2@}uH{X5k^ zIAebtH}9xrS-N$(1ISi7L*@WNaR@0|0!WqU3~QHCDbFt zb3%lSmoFg_VoQJV_aaV3^Mn{+kyd&pInooYZ0QkW|Txx2zw1#?m1Cuw}?Atyj4 znatC6>puztkxJxs%EGcnM}ec#;5I4~5(-hlegReStRqd`tV0Q_KRFHjP4T!X=%GBc zm%g>}*W}~3NCnE`4NvnuYw=8RHNiUvQV(aF0qlvZza>xjBKF z{JR0@2n3-)tX9`x+^FpPPR%~1^@Y_P9FmKbu`vj}(|r)h;dFbYGclYWT;*Q(-0Pk& zd=sD~qguU-L&8p6VS9>DQw>(2UDT#u^P=}YQ|D~&%H~LEo~P`u27wG6DZ|2|8cziN z=6c`t=V^2FSLe-tW54`oruBcYdK{ETRMm9$9fHT{W<4a8dz9V0mW7&j-Z@ts?nISf zefKF8{Cz;2y$^=DuU6{xb%JHUmG;K^Plh$qLs}<657nWoQ*;}~@+bhOIdlJ@F8{yQ zu>Vt2`wva}cP#+&mkRg4P|m+gKaOgT49^u;!C)=Zw1ax><4A!s_d!Z9hRwZ}H7lRU zCwCVuUp^AD9#+lYgT=I->uH9H3iVkH%xcPuO!`%ilGifH-b`6?J~X7zRH{tEi&lZ# zpdX})K;3cb%PJ#FC-7)i4d?Goi1Wvy0N$#$AwAOK`%43sg(S{XphlE*dsG?g@h$hf;H*_R(% zQ)(*}CHXQ%%knQ5FDR>6s7#W@I7Uyb3}dvJnruH9$dSiIe6P#>M(srAR3?8eC;*3A z^V=Bo7Bp3=_;Gi1iW(MfIM<(3 zUx49GozMPgYhUluzqK;1GA_84nL8Ox+iRYj zchsN7#3xTK99C_YVr4ZGaP3vs6Kubk^oU>p) z7%j$)j&Q;tP)w{)x!d7H**}a==lTYnt?=keB*mQyBiJKBV&@8);R#D>!h&B4>sS=J zt8F7ehD^gR@NQ21eeGHQ`)c#A;l_w{{xz-`4#)D}tqxIfb=}-%_Ak8qPteI6Ge5qwvnn04sU<4unZG|do=H;1$vOyn)WxK{0l(u9 z=^kn0Sh0hKK$e2ov7AyUH%^oB>IrWj$AZnGe2ix33P3e+Ap4_g8at^sv7`s7=`-*i z@*U8kuytuBHsfPlVa6TeZv;HCTgN}>GcCN@(*)@9Br)r0W=+NX<|&dkX?iO#>4O== zo@f1N>!D{jGV)x211WRd1jWp5&lffNmug9Mu)9yX_ET$3)4k@#sXQH&9Rzq8`RC_i z>x0J(RZbvda7fgRgis~CwF0{L2t-B0`M7_A$i745!>)tgLcRs3*mh;s2M0rK06MT& z=l$T6aAgE16EEl72@k=*GI!w)A96XY&z{8s!^;?8e#|_)618ZJ-br@My(WO8QZXymzSD52 zF-UAoD+bLe2;g-vzWpB+hU>{6ggkJ-hG-9$ktQ_D5V1&YU2Ut)x_YJr#j7ifi}9SA}_$8EI{=lwGJpJ?i}~W@P+Kfb5UokZ;sUVCiSWXI8M= zWq)fiod`#-@>>|yc|Ix4K*Fn>E9Vm}g-402HHWIJHv~s$z@<4U4WuPW7vW_uSgdYqKw5jJ2oIz7rp; ze^=lh^^=9q=Z=*g8N0MNBs{wJQG~62CLgUz`vR!9quY%lnL1cksB|Q67w~%%JhHVH zVtX8JG#aiA?4HpMW0Gdc6&MmiNAUDZ%U7+5zv$G*EQ@Wcv|KXObv{ZZ%$;~3&txR# zAnyY5L<2%2-y{&mfLll&l25}LVnY9&O4z88ZysL?IrvtT(glZG?#^qjQ|Wkna_7xa z0x|qOrR1=}j&4sZDmF{ITzn6#&XMJ8l_g8XhPa_|Wz!aLYqbSC<5T}~IOONOUO#m8 zsnfEkl;}>NoKvQL`E;?%*|pGR1)4J%o)fjv`L<4^$ajC}c6$DYP^!-xUG~)EK^M*b z{m`UO4`hD#4!xk+$H0RH%N?mXQCysq#EYRcw(+eK+qo9JcW1u~+RrAwVo1DXgYPbO zI(a+K5(F(umz!4Ut$x3G?+*q2ZaaxU;Ar%eARFPa_m7l1zE|x2zYCK5-4^FG`ZSNv z??ly$C%4BNeDR0wZ(bu!IfrSF;k`4;x*OuLLTdQ$5pdC*3xn!54Nl3g1-oGaBGuA3Lqn%g*7qMO&D$4rs@ZCpkCGbI^11?T>3 zwoCV@-#gm8FaN5HY97Oq?wf_`)(w5;T>WFz{vVZ3LT*Q$ecG}O(H~l+ z(6stph%)XnwhbUS6Pr-T=~qowD?-D}Bp{>ohX4J#o*Reh4cM7mzx!8xQtCBVYaK=2 zj(R+TvX_5H`=slF!-EaHR9Au5FM{sA%Kx@Lmqhea_Vo`r95*;RKp`eehD|xdAJ`+^ z98mNWzenaM1XlLqWm)tZa_G5<7`RyQFtw!IF%1*p@Zgx7Cx5R{0VMwm@8xvAyAz3i z){a=ABeU#$rR%K;8k)$OI3`DO?l9ohhJNcCOcp7=te|r|PC|X7cQFU-uTeB57c+9Zz6c_ay zuyJinpgYY`^{D^DZQVyFVt&H8ZXOfl+v9KwvH?l2`?JDKH1_o%rKNlY{j3mg zrJZoL-Gpu+ykC#%*1gf`vG03n%^cQniRSm=Bt@Ktz-N-wCfH2?s3w0MOoh26i?>n= zsNNxWo_+Y-StV7hlWi%1zu+OTP-DHYHcKgnds^iIuVx+HDw*~dO1PG>U>r*GJ$hR` z!XjB%v%EeUph}sSRjxT_Mf<4x>nLvLa39N|{)JBff9$Eim!XuR=KI4&ZMri>Tx=DM zU4RNwh+yt|9ZPKjl^4pZP!id6GUrNsir{e{rF56h)^@h1Y&|6ojmRt6Kgs9h8Y!bS z^lDPbrxTuKQpeV2*a>EI`hoUIzT&rv$IUQQvwBTy>z5X5(~vi|A{#8ZHH}8|Mk>0m zE^4~t^Kv>+p?)_xtd^8tV@OdWut{-o?~qcdLQn0Tg6*<-vpN!aNyX*LJe{BvF~F&@ zK|}3TLa&7kkO|)%zt_0<{kF0A&h)GXB!baWd0;9}H&cCefb?0)ro{oy&wNZPp>7Wo z$y&16S>L{MH~O1HtitD6)d4mUMzmR6XX2@cHW}2WJ~vdeFQ!1gZCGl&Hxaw^_S=10 zvs90UNqc6IF;H*TCxCg1X{>qm$&T<``?RWju_kt0QM1toAiUKhc%Yc`W~$dMB|105 zjxu+PpmZbW5@K&fa8JHG+QOaCtZQIeeX@ch;RBA)XZqcMvL+drHJXEYz1M~03jdQ- zL`|gD`($xwzHs$fACf_)+m&->eOdH;Rsln4)L`qrB(OmPnMR<||mhg}8`>8QAUrws3Oy1h5J80Efq@#l#Iw`z%K1?H|SBuhT7Ol3v@w{H4dgWSS_H?>zu}ky`RIF}pyI zOtg~vSr&64b2uIxl=dOs^eZ#JEFo7NCzVS`^`Wwbd8mjccS-S=xY#PjPKyUGtu820 zuy!gO=|Rrv>IzDb>VVh3e#ftbx-GAQ-&dWq?VX_k{HK0EVuRGV)U@VSu9(TP`zCl- z*%_`ik}sCa0mOwd3y>V+|1>od4=kdFn#mSWTU@6|+!dG(p!!g4^`i^RCyU%FcF!Y3 zGjFlCj6RVbeSr638XaR({xvm>zRiU<-wZ^ivQ-j#fEmn5Txz zMKjx+X=)@}s#`+m*5kG2DxiwKY0-D;50Vm(yR6ooo_INZ)rO|coaUrJg_&4J3~=V{!RxAI@xq-mLm*cmW2I5r_lfvKpy?Zg)IXMH>z zny5Oac^0$Tr($G^c2eMurA-Hsg2SUPF)Z!UJlLYDXOuuY2HROz756U7Nx=DSWe1ej z#Ju)bU#L1WyOC?{TE69!7H5%jiP_bze_ma_?jh{+`_e+73kO7HGD~R&s-Og5erpS$ zt_A9Ky(byR$2QK-SQ}P*=-tdaEat!Eij1bnO~{RhNTiC_PDd;P;`zy_c=bgHJI^^3 z;5JwjyLpK{Z???Y&BS;MsvkeuM5wA8-XyJ!slX8*hz|sm!vsrEr420otd4VD{GltZ09M|9w@0+BoOy_OpL+_85;x~u>MZ-M% z-x>ywM(pjsDgR#lzXXX(XRrQqVEBzv^_CDG9Eq39mGU{+!($H8hvMhC^*>GdZqQ;yXFu06PZpExZh}{?u}Vf^K5JW@ zpg+W^pI`qDOynH$3s0v}bKd@w!I_D%+NdhMTOn7XoLgE(K9P%{sNQ>VBOPx z&3={=b`?acHaY_q{$fmS*Ef@H(&my^!*;F3qTzi@{w-7O*pWo2zWjV*6-%b74?~vu z9W}KPA1Fw_qvX?)NVt?-@q?vAYZGmK{v=fNiHe)((MYz4*im<{g(gGnk9muR>x~Je zhcj94jqL2Np(uQ?5mgRGAwBD+2X5VCORaV4Siv zqK?w-Kwp(PuQi;-3S}IRB`H;An))J`iMp-))muLfK5u25d+{z`Ad3GqL!ca5rOzs*U3`B>_@1II*xk zu|}tDPlWHSNwLoUc47N&7zs;!5C%1dQcRj9oU;(0Y|_^fiE;u zY0n%uSVCX2_j77n<>bP(h_GPIhn%Y~zZBYjkueVIsT+eoniUV!Nhv`g7{^UT@D`=D z=AnM`*>K`g%b;#3aq*~3T6dz)#4EZTy&DsmD`%far_FdN?o6~_kGa;77@HB)onb^= zXDG-424|`cWLvLgN{kwf3&-oKfQ(=CA4?FAc0E?auB&o*q?@eXX(`i1){P9a%o|#C zAi7jF5FewPITWPYO%S7X;!*fva876VLoceTwz3#fY zX?o~%hG_=J5jf!gurU_sVT&)ZnB%3TT zHFQZ=>~nueJetRCv9WC{_s&CSnm@1Gn|z}>b&i>pD;)EMEH8_pr&b{^9wkJ7X+O$U z%H|Q@UKzs4EqzA~--`}w7qMgrKeZm`sWs9}>~;nt+E}QQ%7YGsd30aP5U0kn$G-G1RoGM1+88hN3t~=78}13RuY(EryzO?_JbI8dRJlI~iDh?+ z2bqANUiU~ebLlcF={yR%U1-63q!0%%k3 zc>JrBYqgAuy^x%Al~j!6gc)7p@)aX>kC9u;3|aL%zW1|*u%9eT-Hio$v**4Ybr?g5 z?6Zuw(5;!GQC0sVcNs_c_on2bIYg}}#|Du*gg?blJvCBcMZRUVb{Rxir_dso`ZR=85rRr+2xdh-q?`^71jmF95TM_)L4CAwb&;!Xtqv z+`(K?en1nI9i;V$4QoG%vfnAO1ONmAEjrI3Yx%85>kWl+4J~J<6qHRL-bUt1M^YKk zptEWrn#?^$NomuxVJPr=drWs&i&;`b!sz-V_(Rt1zmK3M~I7C5B ze6)`O$V4*)Wq`&NCS?Jo!YU*^41Wo#P#gutZz5RNM#>UpLpjo8Sv(oy| z+wob{^1c%QZmi6SHSt|~ZGST1@2 z-2JyUGILuL!`25sn@A;Y@+273A&KY}c&I((h9$^&t~iRVeAM=xIoM2;RoGEUx91@0 z5%D1Dn#OeLD$T5whXy~>;eA{4LhY-)!Dx^cR77X>%|xFiCzN1XFgHb&)egukOF!!s z9Fbb_g~qjf`e%)8h?KJ5_x>IE30_1mdVr(-%_ysjAuqA0<(BVIg1Z)U*GQ!=cy*ws zkzn^NvyP+;f{M!%7MBIpBb$}8KTFYNNmkCm@>u$3zN_p**#W4Pdi4HEmoJF;`iYz*lSVa_YEXwQChBLTmL=yTrW@Kt3n*B zI~u~XvxB2#0W$+k4rNSHDhhgo4>fPtN*kS1Up1uE@|4ugr&U`3&~d#Dt+9s~i}(TP z_jEu`x2{&h(`lZ1uziY5(=i`+1C5e^97w?_F zb{vw)C=MjI*9FMWD^M!Z4X@xGe3_vc+t2RqF6!qyjUv8dAlU1asx{uhg7c+Do-hee zmIMlQ^4q^SE zzNR;FS(7O*T9$bw<2kHmGUSMB$86;K!fn9?sM5Nru1Thz{VP?9-fnJfQsD{yO*2ew zjXSKVj7Q{K+)j7zx>Q)pd2IgX`Y*hEx30tC@^iQE)Huhbdq`~T*1L~pW<1Fi=C%2m ze(Qw9)iX-{%33j44kl&+fdeMSlLtR6F5|2jQau8ujqvTwL0+1(NmM8EW@F@0jlpEG z)l?1;WE!=-=F`; zSkZCdcD%7VpI+P971jH) z>lGS)1;_bN)aK?AfU4lO2IN6#AoG%SMP_nZ7ck9&zeM#5bKLkBeI8o`aLY;rkifxK ziBh)5ohSr|Ij%hMt>Q^dIf3@`!iC zd=`*@*b;=A3Jd!!JX3) zYdn5;nTpHz0SEIFAn z?K$T1FXoo*tu}^-tM+L1ODi44`zoE{kA2_a;x3^Juy-PyXDEhq7A9V`* zar3+Loh2XoUhq|?oaCV|ccpo23}4P!YmWP!{6&Hf8`&|u#w123%j+!*BFQ~&vs!L@ z)MKh@9WIiG^u>+u2eh2I@}?{MFgI3>U1&C{YsiwYmT;c1Qy`F!e}8yu+obYlS%%@X zK4>i8Q&u5eUta92m5@+p*Ih;W-V)$31S)z046H(#~t(wio3xND_4TPAozzSiJ^ zh0=C&jBtP?6WOcDH_ilXW=2^b8&0h^SpypTgZRs3R}I0wGnx_GgeNxORV+1+OYcsRg0-|Oh;TW7?!ldRzp!oyVuWfqp*XNx|SKYwwXVn z9_yVE-{QQmyPM2k!V@+2DeNeSal>lHt6uf-AUaKZgDafAW6l3r8=rV9*8wX(;dvCT z-82YdUb;n~$ulC8Jc2Dw!G-jik-#SGD;?wugsF4GErbo|6cXDCF?W{AKq8D)W6li| zwgS)%XShrKa!ypd*hl7tJJE;T#Yp9GsC%a#+h*Kj%t)i@79_+8hMC_n#KDWy5 z%2#~seNDe6fB55o{#%2zh!91zT(=zy(*~!pZA#)ykF8dQt*}bW>TUD}RlXli$U|vl4EHJ(W!E_VWVLNY|D4@QDOJ( zwHEOE`1rQi_c^A^EQB*GH*SZM*SCcBC%jXm)=HeyEVMZf2RpNxC_EMKyWvOhOg3Rm z@ngsCSLlTDioNPjTo<>7eE%6} z@l`_M=}^QU02IG*cypZ6x5|hV|H7+!YfK;}a%6MCeBdT(z094xHF=SPhhLTQFB$&+ ze-8f9x3^F6K12|qZTqY-S0L7>a;LQkXpgoeI7z4CNL@K z{d3tN9+1;p9OBxo0UUIoj;LhT@b%Pb%>PxC0I@EzU& zeZ%{ZQZFj794ePSqz-N=-8E)%hXjlr%Mw)&*>T}1c2b+U2q`l;nSx|i|wg; z6Pz{U19FBgg_l&0`obf6${PZt4yi=7b`YJsSvjq2unY6ln`2k%l*dFnWrHb7DRtj+ zE$%(nQ}Qdkb*E>$8IhpP27Q5nhPmMbBBCd<0r!OJ-Ps0g1qkY#O zyNaS$=Lh{;B_j0!;&UBMF84RK<;RtSS~wtH&S262Ymb1DV#+1jB90#i*Kbk-vOQgA zE6r%*Yn)P&BIHzU4aaK8Q|oO73<{(s`3tk~21;-n4ylqLH6OYJdfP`*uxC4`q)UZ_c<-_Yi8ZxesV>h9ZCxt_%w<-rA~-O7Tl7 zr;sm@F*NR$r8_j^p>-5fiI;=!^1aWn31irlCc;X9@JqHWqU#|Q37XSO$#5h;ieM2H zuTU;<&&}56t3bV9rd#w4ukn3n-Mre*DCCa8w6d-X6`RIpsx4KcJutX5uOE&eWE0)4 z)TD|_-agNQ4P`V(D>ZlEi^QVm~~KHUf!u(>8A z_>^jL>N00Q*O$;D3R@GRei|==a1?IKS!9bKu2FKgnikGv#j=9Ftl6+&Fqy|c2WPZ_ zwL-9TqD!)%I^#%Qzqt)dU9;?u;~Q97P7$42gg_FCR7HwzrFW9ho6A zH!qD(^HwGprJDX7r@JPae6y^TMt#%CSlyJkzeHWow0Di<#-9I= z1+@R(&;P4`=TOG81yOsx^T#^BNQw}`wm3Di(|(?rx^?a1r4XUdr3czge+pfx+}wH8 zjj=iN%b@xHs*(SP?){SG{x|4?*}mVlLFA)be=_;tKgiHu73vKMk&EAG@VE*&yc)Yz zO|TeONxZA@wKV`dJ^cE~#>g`cmREB&BKz5EZP*Ae2aFi)RY*6*O`y<_{9LJzg4M~g zxqy2l!_C%^)$URSkJEM(6x8^_!P4sq8NcVsq_)*sv(N9@rg?};`P*JuosUgNdo@nSJ<+#K>0Co5ToHjml47Yx@fY&W2sb$H%O4*> ztqzdC2)jw?Tt4Cu8AsFwr46{p@SuS2<6n(xwl&_0|9NH$3_M=ZlD^RY!L6k@(B9@U zR(~8)?(XbtVyw^&CnM>X(f36WKPuv1P2JgM9C(W0z^+V94}Uq-U712GE3(#CL=s}S zSRQqWD8kg<;Sd|x=cUn?xTQPLjW=9X8fK~4^RN3VaHGW&W`lB9OYPLr<9Kub{ci(! zgoqpm=;gF&%BI^3mL*F+##;QZodS)UVdJkkGl!maoCt2avKeySXJal**&O?tLWg#UUb=On&d`aA`ZLqxiw7LF zH6oWcF1wD*o&gdeloVXKvsuyJajnjoO5Rk}SJx=8oc;te3<$0}VE6Ss|km!EpQUBpoU#KWd^cJuFwfOf~8J4dL-AdBb;k0Q$ zJ)*5%6lyHG3t@`+{@CeRHVC!R&U%{^u8=S~ftS*1*MBF2XkwdU0y^acqz zl68)R`9~PEQ4Hy9L&PI>2HRt2OPlamK9vVeGBfGm*hQDBNItE!#c<=cDMJz^a}&Ar z=s~>isg+5e>?$lf?nATrgXxSF4R-A953RrWRg*RDn@>t7Sqx?+ry>wpc5aYf>ZvI|9-ugjW3}Mu7gJxmd51I zA7L9A__Xx3B9DqefZOxs)Q0U<^>vI}XXj$AL6CvzCZ+tVof_lxDCqIrpMKPrF3@Ud zN6N7s=(Dgd?iNE;`1?0jy7+W`plMsZ&pB6?5xLigieQ z+=M*;W*G{4rmMzh{8_&?gsr&Ul^KZRwDgSv6v7}?;?4$7qB!0}U zQYah={`%|Wu=g`>?`G?-OZwAW`fop9x_gFyE8Io8=k>05*#rCPuovwHUW}?(U4sEi zdFATJy(;rO@<2ImDqG9W>Rbvprw>%T?H+t{qv)^K`+d8m{9zyou63#_Y^QxI?!M{x zapXvhXejE1QTFg}l+=`ztZ@YF)&`r+DF0f5YN3&~icnKSc={AhtMa&WhqORHw?JRT zz>I-_q$ps}kD50Wk2`KYSJmwhEs><>Vbs8k5I{>c=%_0agQ~r1<%@<@mFCzcPO2 zfcPNW?ZUO!m~Cd$EyJhC=_fUrpMb7ur6o};Z6nJC#1erL?m3~E2HJ26GmJ(DG9LT? zo={IKUzc3RZ-V?w9MqSrS@y?k8mjA?({gF?m4=C^8rV7y4tji z{j_6Jj*T4dl_fuUSD~!63(s>hs~Sielx=BZ7L~iaJR1j&kW3DR8AN{MwQ4g=3lHgW zZS`=&@`PiQCtnt=YDPKn9Lat2^E7ZRMHlzlF7F}j5Q3jzTBHKe>3_u9d-Bexj(hjS zVWbRu+Nz*$5|!uKtz_8^M=?Sv+nA%9`_VJmZ~2=cicx=)(Mawc*|aM^@L9_H?je zQR)VJ`{-1mxnJbLO4WI{%`%-TX?M&T@?F%X*0XEYZp=RgfYbS9_k6H>A@4@=Jb}8O zHVYyB!54H>xRhg;bAe6iujOSnqALrsSA{C7+dg}^{zVDdaQt2*HucKQcFw+-vTY4F z{JOi4JZcsI*h>)U#6$4#lLWBbvwvv%q3<8jm|yxcXBXu?kQ3e$69|H=bv1Z(E|jhSi(n#2^Vk477J1wh)!W=8(=T3nu0G4QD05-u-<7#_;F-Ks;ad2AEG@p zKe3Zm?}urnH3anx>l>Kn!X^eVDQo~ncQTHb?uIc=#X};R!re_V1N>U^q;;n1mOZfG zI=q;NjW?~RDDHDsbR2^@W)gS&NVTQPN-JXZ&ioUeRMyrblpGjJ4{zX5EqkQK=g!@cVVIsS`zKSm-w~#NvkR@4I)Zfpz#~X z10%TY#qn}722hmE+7@wZnlzNMJ0D7;QE!5{WAu~Cs`RTyj@M_N^CktmC~tjlo^+bx z7RbQn?H7@Wu5U1QH-S7^j9h@{ZdAc&bF$@9R%^oVPx0)FqB)hZJgW{zuWgw!C0L1< z>?{4CpGD;7 zo#-Emszo2IUhVQVADyWyz8NtmdiSMbioF{Bi~VC?Ul+jF`$26wXhBW#$x>;S#i(9M zH5Dw;;Ue&2XqpfzG7?5yg+q*$b0WI+kE98hBg3b+&KAT(C-ghl+Ot-*aa=B?Tg{uIk+CoquI7vc$l}<&ogenw_SFK z4efoAG_d~j%;qO<%|z4>UiIjcTvncM{bJt_PRq}n@mP8MUk2k}fBq}p^*!moaER

fdit$v^scq?hy^M~iH`%@C){-_myh((7o5OI^x|(6#m6rQRrY*}kyhS7TDw{PJ6QKsNBB4v*Xjwa(rh1XcQ!SF&^Y91*@_f@UIfrN#N zV6Ll;(K7^XLyjBJ-U}G-uZev7SIr$u)}BDJC zBzw=(F=3XFn^DSn9REC$SFVb_YJz+)gGMhfT~ry5z5?(28p_#9tA;8j>o;&0FDiC0 zpsKBI#nhnG4m&bGA-8A}j#H^_w%+g&y-(#NK1eGH7^aX3>s-tIR%4g4+KX-Z@L4uQ zi?Q6U(oZ~?9rwGE8>u3LFlcZbqz9cNieE1NEda{hdfNrv8O+Kv>B8E*f&WwsJ*kA2 z_PQ^*Fr)GP5wjov3Z=YQ|E&OicUBIXmsvhyvm@t$#o+*1n?D>@K-+8q>QfEYY>G+?X$s*T4%K<}8x6g*O~S9l&Oc!J{3YjSkp*8kppo z_jay;&m%$(Zum>s&~d-=KUDqX>*V8}B8(JKXUc{k0Eg4VmZi@UT+#l$LUi9Bnyr1M zOBQz^cD_Bb7cN8R5+kaqWOfzc8&Z;22ov<`j#Me$muO{3*=9TcwR)W=g2+>P-H>-0 z??IukJsXhDHqa)RW)=;Ojw6<;{jh1e;(l-DDy_Oyf|D z_I7mwwfKJQNKF>A-iXq%iIHo}d?6Hm#Kx0e(*H@jOpSIzA*oTBkOa+@HofAk6mi*E z&5QB@fH2rZ_dtAlwT@F{c82sPObawQHyaFaMx*XL^}J(qqz;ZQQv7Z?Sq!iFvYuc! zAa3MyqpR<0iBqx*?`+vf)8)iN&oOe#sa}rFLjJEcnI#*G+W2F|x>;u@(TdjaEi2QN z(FGEUZbUOKaI@)cHzL@hI3Wg|FJ4gpAUUVEvWo*pnCaeHjqUF)&2VQU)x9>LkzRNPEvICA)7F;?e^g5^STlI8_U zW^#>Z$l73GW@4><0P%cTU!`ZgQuAR{EWEKg(+|#IkHNLBJz1kpi+SWf9XPbdBbN%1 zT=0djGcUb}4yD67^cPV2cF9x-EMBvsqwSfq97X@IelGE-m5rK>CwjNiFe+4?f`B}o z`aR2Xi^=SZaeBde$@AD&EouDCoYp}kPJ;%WECcZ>aRqojooiM9vohw`vL z@{Ij1ysLpm%QsUflE}KW0rPQ@7ezWgrM#X-_b0YB8gK|u-r}DfBQ>Fk-+H_2D9UzDjozW)lfO|%asiUD>1C8ABsr=yQS>6?2{9q+@4!Q=$ zp{xQSZlY7!UsEcV1V0NC@E6FT2xg8zsvuNa?`<*MloA;D{N}m^AuS>!ie3uFLzBvwPdU zD#Q&;Vc&NpT{t`VoRuU}O^w;p(jO3#Tr8;tdO6-1DbjPcyYGy2y$0RooK9#Vp)JXf z1Q3(978~p#mK9pW*0>@wGVYe?nsDX15w$+6&+xwUxZdtUq8_ggU_JbO?XZ=7;7 zCZ+=fk}S~Nk`Cq8y-TbimIhE{ty0I(6*pB=rPP;VHtt@xtIXd(qCctH4KP zwb(Q=`qOUFq&AD9cBPU;kt>pf*XE`%b5gjj+^&de|``Ari8qHmlEex6}5|1&Ci z-OG{aYUVq(70{LcG}aLD8{VTRc}fH%Q&_SQQJ-nL-HF)+l$8^X#$QS<-rl;By{s&A z*2F)rK@f~!Z20uwz7b;F0~DCCCK_6$+!#eT?Y9*jv{0$j_p{JM#4VcuiAI$vSM{fy z9o26NT)7U0J+UxiMgg^6MY8%gSp}I-OQpHCIU-uhV9|vJ(`O{OU)}zdn{H=XM!rHN zW3}&FMUm8SOr>h2N~yz{M)j#Sd+3K_NHth~0NBhSYFwb!{19H5j;|gnJF*WnK(Vav z(wr#g80kD?!d|k+2C-!oqGq0%9)Izo)MH9X&94i&eSMq>U^5ufGoOKSzwSfD>WVc`dWB+7S}p^Ith!7kR^&jO{@9XY^yeO zsRcRH14A~4bvUb5wX6JYf)6jqTtk0YWS6pOHS={4e4kY6u(5T7WGYY{Gs`<`S&?$=2#4e&=g{1p zcw^C+hjUgGo9^niMq1_iZMxGvr%7X9t^DLElRmo{GGt#^+%}m~#UL|A8)`g*-lNu< zvdYe|*!*AI;9viS3{PW|(+G?e8t8wtb^YSg@Xl*rN4`Iw`fx2`?C`;>h3cxuXI91k z`iFO`eUTrehQcblg$fKuugjSFVuW~TK%Cpktc z3Dwv!Y3aj0sb*L>JAE9{8B#6~B?rHHeUk@dmAbIZ>1u9UQIXN%>@WgTJD+^s`wf56 zdnqR6{VV@#SA8|?S|5$A|9FRABYrXt{7R0-&Jq$C#w^%hFkgB_5Biz>^tH2R|MrN1 zqgHTpjy7n+Jka?8@z&+}|9%0%n@eG>zD!>HvEyeCZNGEOb}aBH*cLW$ys^31>L;_*ZYmNYr@QX4jVy z*#o(KxjSVQF8jCM!@U5w)^dCKYy|MqQXx!@q=U)KJpFbM`|rKXIZu!ZDp7>!|L(ZW z1N71b&=%*Y#9B#?+mAn-IoEP{qS98lK5AC{e8b_|8{#&7u2gs4|h9~M3b5FC~2&ae*U#A z0wmj+Z|Z(z{rUN!MVn#LkWZ0>=b;)E3WbT1{=Mevudy5mIH2@g66SP2%Z(2SK9$KM z=vhRWxZ4H+=!jWy=uCWk^DdN4{cpW`;F9uvnaRIywf)CGm%-k@R>9}??y`Dupo_wt z;BG>jO5K>P!Wu+EuZRo`Vxo8R-+HT^bd^}g~Rzj?~$&4x$6*nGkm>g!vYSQOVV+O;re;zksn*JYL$b z&3_x&zb(!G(;gZB{ofq8Gyl$%$^Gw4g&HLE()OIs)$D51$(WB-kD+S7!a+Ym_E9r$ zFNJ*rTG@tEmXTnGcOh!(C-XP8XYEWd`SUUl{=0YQzW>8IyktvW8sT32zqcbv|Fk!y z|5}j$*=e#l^Z$e4mk9gMt#7>8gDttxhH!u?G7zIKFP*+PjnZm$o$+3{&VjeVwGf3-~M4F_}>4$ zU|H9>W#7<%r=t6r>PB6KaKrg@L8t=fv^xMWR>#J(soUA5`)W>DT>7cuP!tMA6%h<$ zXNnB;`_#Sov1MT)vv0`w^UPVj=>N$r`em1VocT}S-e^eC>B>J8lK%u${7c8qx> zqIFs{mNeA0(+k1RDns!BU0W{0V#<-WQeq2_%7Po;uc;bE%wp{Q@oa#`YuTl*WFN zfEc#<>Jb?aU%}}ezjjV|So~pdtcJfKiqKhlcS^-92cDj9iK-u#z@(!3eQ(U_bv6@Z z-E1>2T_)dd=RhdTqPgEKMWpyA#bn8bI;NA?bOdz&tjqZ-*Z6b?ly;QK?t4jkqBpBu zw{bf?>FNxY=yC-pX2sPz>{#{16Q}|eDFdYDi zoC-5oOB^1iJfoi@$)*cIcZKZy*f(=>Tp$I{=`#J7>&4=+HPX_)es!3{)lHmZ@=`>* z0CgO$Ps}nQ%dr8Pp~~GHM0|Kjx+@2uhGSj(j#jn|eJ_Dq2- z$7rmG;Zt*kIWJ{Yx{xEu4hA4aM~+Npo8*jsYWpL#*;N@85?^<@28ZdheXeIO=kjuf zRCuH=rMwE@>M}r|G{blW11?^$_g9TG0sIx^7;ogYoY{c;YAH=^k zG~03vR5?<2wkovX%K%4xO(p$9%Q4kZN*kWt=B@%1UkFPG|FmwEKd)u(*oh6(+IWm> z%8H8`$595rEtU!8<0ifZbf}0e`7}_XroL7-Ka1ZqbnEL(tmi~s5f9(a)V+RMg1hMS z&oidS+Dz=^v_6vE5sJpJUFkx74v^;Zm4Ah6i?}E?I+K<-0&r3${#t`VOKclSa&dWx zqf^CiL&3%1U|m4D6#FHWqU7K*!u^NcnU)(J5Ef^7^>XeD-3*EIlaf z^KMA$eu9cHs@ls(eRIJ|y6k4+W7~BWK(1Ccvd7A4cB=L%=)i~D!*ou;T5Ex$OlQG! zj5j%*?_P2t48fLBXE1E9o(saR`CnL~zn7`MSe(<`r}rq4Qk;H$O*$J(xs{A6qVsb$ z`Ybe&$~nh^8f$Y=1z%QG&E!Ub>#TCQeT{U^_$0KH4Bk-9*UO@Y!+5$^2efgt**`Rd z;nM8XN2ZV6OF%+s6^f_Tgie{5k@e@+OXw=-rUdXMX)K{R^eG5)WA-SuJCXp6G|dx@ zc1M~R0KE!qHlL5&xEc9aZ=8o*zA{{CB3eGJus-y#Ss-?CizJX{(X&Q%ixohta|WR)u?dWQEr{QHkg==n z#!uA>710RUtTfpPaLvL9j!0FhD^ThAxbMr}^2hMTN(H&ul{dts3(rQXb5f}kc`UQcQI3zVmFJ-Q%> z@omLfH)GxO4Mc2xxo3#LGkV`UzQnSZzs@nT^TIbwd4Ku5hRfH)W+_3t!#yFwNu^0W z6NiNnc`wxv!+Q*iT(7J@Z)Ix(vr>y5!UR6F)1r7ZLAtBxSg#xngOp4X^=VhT{Da?( zbC@2?H1`skqwrQnM z?OP$^P`KjH$N~SG2RozRvtoyNJdHb>b7YI6qsJE`iMCW}Ik5qF7!K#w@{&zPd9=bg zP{a*KIheHLm)B7lds~N{b6pX0(>M>73Nsq;B**u4q8#E9fe!fHeR3;Lu}Y?22GVIR zHO}My(;#zL>@TIxYOy{IvnU7GPaGxHUm?w+9M7by?UCapOI(V1*~>u|+B%!x2}j~m0?uJ+L-P9DPE<*!+@vmc*W!sZ; zZ(oEm6AjO=Z0HAh@c_l40Nw<_M+Baxx8R2^DZ9h`gI{629_i$Qi?2u1spjb1$93=TRU+otsAGHRB=;njLVW80_3(Jt|LQ3X8nd?)1;0=NOb4U z^|uFn+8a#@=02O1KvCbse1YalpE4@x1LHGvNz;~v6^_0!B44PlMVo=pUn7cj2vZ{* z*IUj&`u>yYKkd;X)W)GqkPpoN3@dwzrz_LSvL{dxzEa1@?Iao4u8(`@I~P z76QCyYB=e}Gncc@Rqe(Je)cdjd?%CZOYw`{6+^rauO~infgfp&W@{r6wTl+w$@TI} z!Sa}Ayol`alB}O+IMhFwul0`cvV@nhsMwOAI#wJ!J@lRwIy}7ASF;?R2f{hSREGzy zme-RjGGA&l+FC6l3)zs@X7irH^Ti>pVsvzg*7(cNvT_3jh0vUw&9%Lnpn>Dw;tTN6t%0g z?1n4!A%12LZkIXQEQA`<^;}iW>O)x$nOY!-xGBb><@mLd9|e94(yz6-2YM7tl_2fkh) zTCEqybihq#(OPgcO&x%NhI{DfV6Ma8*+UYoLTxXRb7&v*sv5%j7Ccs$;7&vgSiv!f zh@h5Q7FC0gh)f4Y8I? zwyfj?if-p&)os+BEwzVpmXKdDXzwBgsX@bbFf2c9m(KuUJDa>Pj?>pN+dH94&k1%E zUhMYM!#mK|rt>-ihbk-H*pBCYmTQFk<{tyHizh9dq_%~8o%R)#(1meM$rtR%Iar^} z`ix#_TgQLnxY-}?%9|v?R*^0fHRE1c*p)g;DbOxg;0NEyhlx<|WxQ&7b^fPlz zYfikTwo&7J;6uW@(47oA(_}IyowCnMH^3{7#pw z%*HSAWZ8ojI;U7(FIA^u2_kCE6L3$nd}{Fxp@;TnxvFs^|KM$Oo7HCEjLbTDhSR0 zi8H3yc{n)7zhRCg6i7cSX0S&WK*8B#kVT@FO-h(D;u?SNrcv`2Fdqoa9s(ILj+)i2|`}CPHrS! zCz+L*Zic5bbab)^odx2hslI1^Dfk}|^O*AEMw3%73^VWl7#RMydWsJprJw%TKP?Mv zIr}jnG*9|lCGpl7rN001)cIH6O6$%vn4-VvVtXL@w@U-Vzs%KlZ5_KSqs=jU;FjhR>Mr#cZokh)bCvVS`? ztMCZ@_2slG%eP2_0<(~!Gvu;@QY2%-ELeEjdg?QI74#yEBAikeS}jd)izvd4T5Zs;B21{oU#`GL& zs?Y}TA`nJ&*q4Fe;i}V(g+o{h!q>MC(d*@ENCRh>&dARz_9qXs0kUm)#HQp@JXavu zr~~8d4xTZLOLSxB8{mL{J6q{%zZ^el+M;{vGSE)dQmGCmkh~;T5?N(w{BktFz?GVe z^ZC{S#}0jNQ}8r-yKg1BS(>IjBJoh2(|U=kSUc{oYZiBmn!97fCK4iU$2NP0n8V>X zZIkjXG(X=+U`NqSNhki|W_VzsA2$7Pjak5rc{{ve zl#i8tuVB6>1qgAFy^;H<`}o`xd<9dO`Y;0=mg0qj3LTlv!ijYzH{y zF9cHkhV%f>=MYc2$M{|A93Kv|Pf}K{H%ZdP(W8_3rmy9o4aiZ~3Z>?CUs}LJdpTZoUM!$K;uW2MrF){kvPMgqJm2Ekh*qiK5y76oA|k_IOg^$Atz)=z-V%HrZHI=GLD1fZ~K zX;zWapAi{H`lfwdgV9*U(t=llDg*F)S;>p+0$Mi7kvUL6^GenC^-YVom=g4tHTH+tnY7o>?6ys3yy=hLjL1V$s$A`0oj$~r}W zaUI{Znu3Fl4f!%U(brJ8D*b%e+;)8j83QOlVMw!U!bt6eUZ;Sr9a20sz6RriHXF^@ z?!<%4Pyxvnt~9S#f=}WMxJ$KDE*781+h(VclBZZ`8wOsyf|}qfA41H7uZ;@aA8!4Y z+ewH|oj9OeQxK$;jQ7Wg!)n-BO2%%ikVVP4mfx|c%+>)_B`+-)convq*;iDqd#2&d z0P)I|9UHSai1Jbh$DF$OSB=Prs^9J3f6S-+Jfk$Sy871JFjQQ?>yHi&`4+lFU5>y^ zl1f;?%5XLUfJRRNdURuLV5VHdX1!rLsR0^^ZT35g4Yl#cCpQCy&iQv< zNuNLdc}7ZYq-DST{e$_RXO{h2w^n<-Wlr*IzFRrQnytjq?Z`G^8}XJD-bwT_!l&M* zExg;cScBe4&ef8qO@F8f36xQy>}8HH7H3gaqvnSy5qjPwJe(cMmBw3=Q$gt;8l&s& zK3{s08BZsTta(XlYllZp-8-so5CPuN3EeQ2N2S^sTuNnM&nNW>_+D+2EnO7r_w#(? z>LpJwX5b>qRnkfE9o^zT&p66v^H-NP=ce*!q&M}oJX!V(sbmdU1Mrqe7RrBY5&$eH z1YjxLA~Q6H=ZqL9wwNz~CK1`R;XP?tCS$uNQ_O|TWaVJ~r02N{GD-rFsbetSk8H{~ z-#M!b0$xfwr?QKn@^R@|RmO_I{-J>>#|_W6e9#Y|zQ+&XL-iP65u9(3Uvf^b7&(s2 zpIwg{=~XlKaQNfRcG2YR(EjuXOvUtXN1JBPq+LxWi-@fm0h@-di-eEg>8STD$OU&& z2?fQZWe9f73h8m2OusW6ETGYSr%K%wW5cSJ(39k5L;-T$AR>8nB8wH8<miwq{l z3*;U;Dvp1as+4InxEi}|e809bc5~AE9{RgS<5SyzvZ#>AOd^%H-Xb!Xk*&KUTPEgd zFE(PN5z+lnhZyC03;&`Q+dpo%v1fa}!G~CMzc%KWID}2;Oe@QkNaL>PWmPoP=l^Lv zZgOv1ChkE%rZ3S^wEp?y|Qa*Hm> zN0t6b7Kvj?_w?b~4J~B;#9O{7yFL4o z2T=6qV=DXc92Tyx#40=F0mp@;k(J-_4BoD}Dck6ka@7gz)OV zcZj6ep*$oyxG>%B6L~whsZ{QK*M-om5|>d00%|bkHX7_cP`% zwsaD*XB>Vn>)Gb3Da`tTpV8l@$4Hs6@)uKhMmmy*BwRE5BX$+RE~)!{}eF|IXJa$jbgr zW8Qc@iuhQa+iG&*=b2b){h6~lW&asp`~RU<&Wdl&u7+cow!6BFr}XC;vT&Z`j|&yh znt`wf=NDdW5H9%qJhMcviu-xy+1T&@nU}oSXT0t9D7yBOkmJR?;W!#4j8OSAMeo zc}7e1;W4+U?&GB(f{)?-C==sG2mJ?Ga(8sF!D5MpMPo{8yZ(jOi5L3_MIU$^v_pfO z@CeYkz{0j{xUEyVqt<0ahhL@@Z4V(V4ue8P)O1hZ?b-C(IVlQDTy>EmUJPW)E^2|# z6Bd$(4WI8*6_r7<64wz zn-RG?>~TLbZAfe$m^ahnv&R)(^1b-OOG_UtKZoh&KJR%>aflJET0-{W!V_1VW{ieI z7LSxpA|lTIm|dwKlL$?VPwJ?R!(u-M?XBnFe)K~y{RedG8jO8xcejMQ?Pu$ml}*Va zIVrP2NX2P-Fq+RW91uSx3d7{+(?Kr1utr>=%l`NF;M6s^il7u(JAi)PFzoxZ3zM^`U^HC)BgN*>X{1s6HMF5_q=Qs3zCa2%*TMmS z37WR7=@HC4b0UOUIm$n_f>A!A%S*~`to3pSzcFMu>mfiJ*z^HA z^J#*dY$MNYy0#myDH|UUx#C6yjc1`kni{J8^lJM$`8*^`Zhwq6X;tbUQiqD)=I!V_ zf}Cf<2Ty(Ok4&_NNXpkp1;buCQio&xW|zzP-w$AfOv{FnYxR<*Ud;dK-OG(WzW`CN zbhF5bsh!|*aZ4w7vUsXHiNK%#S(&7@We$94y_b7lCjd4F8qHYdajYj1F<4x!&Nvp& zWtYy~{;2U6*PSnMtNy!kGfZYf>YHq18v9U*TA#@Nk&2*(*g(sIaco++8g)piVzNB6 z=!)@fc2>)`ICuWtPR1vMAw3V9HYtl^%YH;6HE> z0;^oqOXCa?oJP3>W;@?}s%0SL$1* zA*Ja09++o}Q8%&1%p)DBFlGt^f|yBZ@7%hT=NJ@^Ds6{MtiHzsnlI^#F1c%n)_C%t20$JjW$H$r%~`)8 zkMT=f$b1e0UAuhk9_^Vp$L?)VH_W~=HvRg%X&T1K@VP6!;qy6!k1MTzUgJuC?N}NC zB_|)GULbeMCsSlMt|YkKoi$mjpRwm-hi~Te1lgj8_^`xnEMoOjR%44=`TGlGk^I~b zi1K{Xq&kw;ANKd+II{W&voFOL?@xnVfv4GC@*yO0zy%|*=IM8NBkUhWuxy{dJn&p@ z9GAP1Qc>w`2Ic=MIlHEV24zEbG!@fHYvr}%?QI_RL&$hx&PzK+#*3?s&(jKW)U?Do zMqw}3Q;Dx`_I2zy{-Msjohw)y^jSl?_xW3IZzV=JeiFx3uYv{$)b+xJYX$(;9? z;woR%n4hKJMCzsYBGi#ML|b&6K@lpmu#-n3st7+CM(;J|2c!wv@4Q-+S(v{NqMrZs zVfWdOQeVGB>4mM%s5@4^TRyF2>3x4|i!ao%z>>EeQNnmjPVCRWB3z#G!1Va(i#(sq zbJJ_CgROqvfsEulysdeN0u)%bs=L184Bg)|*<_)hMcaV!;|isUe2lE@|4edaBN+9WNz12S$2mUuRu)My<79Lpn-~lS2mHIe zbZ84q!dDq2Q!*(#SQD1ITB`oJv_2~>Q{S`bsQFWcO4O&y;r+On>wBdBoU9c+ohrMD zg;@gL0lXIMyg%snP<}vVJ0!SOut&GQ-!J!&pl5R;Ng#5d zSJ21Q4rvlz>c3MNt95 ztmTpvi#4eePmu+ zOsQ$9!u9>Rz-lx7qMmg96E{{~VhnVkV(hup^VH6$)YdTMPeRG-Pl&p{w|bY<5HS`2dViykmPBfvlx4&;Y7Lq9w1%}SkG-wzlG6XQ zGHlvM+jMnFC5uRjzE~fxUn^{{it)HLZdA>JQu9|no34hUrrMD9&? zT)#R+!NF5zG7;%RlzSFEtZ%ckyo6zr-Xs_dxhJ_OcY{#z%WsRb22iP$ z6eBW3v-;%h>~_1X&)l%HNlHk;9KRuaS(BHFW_?qxs7n8yo==M_u>s_~?q;|wSpGhy z8YTLgG=k#TF0i>>%9%Zpung9}eLXL~1TV|2g|+>8rYNJTNVmCftZiL%>B1)R2tDCd zw_FVwz*Naor(FWa+ABr8sGRlPhaeWakh~{((qZr$ zF0A|;`omJ(5aR#A-gyT#xv%@)<+34y(xtj60YVi*Z!RH}geIX0p-3k{z)+>SRC*_& zC3Hwa@4c$@(0d7ON$*Nk1YI|KpSiR5%(-Xgo|%2`+;Z~gJ9+1Q^3HFbCr|r+&?xjo z%E#@$u89!#xA^FdYLD4&(psihnjh;bwWa9t=L7Chlc&4?*!LG@jFQ9}>Wk_(rTGey zBw^2179Qm*Pn(kpS^Pb}1Ps0LFi(zpFc#daYWtpFp7lK^7V1{ec4D7$LUHSL6yZrR zp#`(_3~jkK%Tb&z!OWL6`p=`sN-!(wVAs3}p7{X_K;HtlnorLIA`l{qmnj6$!?y6r zgianle1ARHXmZPI>x5=%#!2c}y^}xt%U0T*p7K!FY@ivTyCk0**b#IyE?YH=cjG;1 zF}&T*hMw4AT=rGBsA}FKA{8QyLQ+W!ojXUq6#5gkVJ$I7u2Qs-egPkbMq{L~Nu^PVk@-hNgZQh#57-8<^smbCVSM zqYLq7A;~@ZMWEgX{^8m5J-SC+xHw@LBObqCP@w!fKERmfr?5v$rHyE_WVAzjtFEjHxQFx0e3;iyb>03*+yGB@?x}E8*w}58KBqUBObJse&U~#8|RF}HHs!y z8VwV10x0d8%Hb{L2Wlz9gbJ7ct~qg}NFsUZ(a&FnYkR6Y69VCeZ< zdlcDGZu@LL`+8tt8b0t)g~6v#*T(krBB2*BSkccR<1%^u1{O_hK^v4eFU%iWK}o zpsv)UMr(d?Nx8`jPW(&R!_PiVOSy3Hq%D zNf4e})XK67rU|HJ(Z%MBzy1@_-~mq3m8MRb^kswX`$t36&Dnvs(whxFD<>x_a>DF&-*8~n}*fTdEJ){;P%0m`GuO>FpVntiZSV9V*1FS!>z^wS-}Ot!wxcxAc~iBg#h6ArtB3F zYWV4>p+j#(*h)WcoqT5AC0h#U0XIdNKWKaoKj0RpXI_2KajQTz6>Y#b6sd=lofaG^ z#pY~RU|%Tsy>)_>OW|(1f{vBYN=--AcBqSKp_)V;>7{x5#l(*__%BExO!t3yF2h(^`B;IlS1rJz#y)u*tM;OY?O^#P)=(8Esn23F^M+(7w7z$ z8vfR1Kc$4EEpq{4+4S1%o#MReh)3du&0TH)U@xx1hr^AsfM0|#X3hc|*mp~F3FXSlm_~ZO#Jv}E z+p9X4Lf_3>O{Hd;EH_0)q;0-PNn&&cC;+EU2R_u~&+g!2mgNNx34q~O=e(=M#-HQY zwEwBr@oQhFfm`-+%WETrt6%>OLE-Z^1jX6EDChr|R1SWLaH(`|y}BOq;?+5>HgWoe zLFr;EdSk^aC@1|j|H8df4gva5c2|^(`91Xer(&$EG~HeT&cQ6hprGvI)_b`m25Q+b z1Fi`-;ga)`(yFs$+k~Xqhcv zDG%LhbawPq0auX&iqX!uL?a8MsXoL|7QpD}d7ie-R$(h=#~}r)JiJ7<=1R+(TF?62!doV-Fd6&Y zY9@$frm!JNS8BK1!08u{x@7T8e`hFUGkKw0Fy$k^MgbJ9f#E(LkP=`Vs1TPFIH<@a zFZLJXrusdk(u4`7K8mg57Y+F13uE-eC%G{EFn>0`z3-wLds6gdiv3W@XC8E6X7C<+ zgt!;SF>414u{%t zHAg(gk!VsVE6LYH5KK&FO8~sC`$_00Rn&;s-HdUHoBDJvdpHAW0>ieSHs-LmHC$+z z&zwdc!Ob5U0P+|67kR~8=cHwyKDIO`0VRX#P40$7Q9Y+dqi;;l*Fm#pp zJGbZZ#ODcN?v~t-Nv<(A#?v!cY0P>1jE=8EgZ=@Vgk()3u>fajCOV_-Ko4^@SvMSL z8We8T>F#AopVRIW`?5!EOAJv`wPh@t*qevWqwW!(9%~=sqWUaEr`I7oJmO(nj%6|j zTZ#T$5ZaoNn?A@hBd4`w_zXAmP;!M^ad*!Br;eTE&9{7|o;*CY_T(46YB_7)6R5>I z^6T=Ve*`317%&cp3S`HhRO9*8Xt4lj^a_5w;S*h{L+a0`=`Vt-szvr-r7xWky_fB0 zJp@i0^#N9ld)l8w)#UYosRn3tS5)LtLr4DTFxvc?I~{XvscdTcNXn)^fTQBT&M;wa zB>d}*O8likra4m&7-2F1;p)}UY{Wi{>X!0lW(X?1tF$JhF61zr!_H=uzG21&Ze(5? z&}W9KPPqC^0~)#D&eGrS=rf|~H<}hX2AP_>MLC0u{8EmE?M1`rEw%M!h*QqebV-6y zn$=heem=e(eg(gD{ zmW>O}#`lf=)KXU_$f?o{EVgcX+pBYNoYM*su(UZ;tv6-cU78y*cZJs6C4SP&Eow~A) z)@@eOVr+b{29v=4W~k$gGQD~&U2@S0TlAK}eSjrEa9))xz+uKWLx#16LrPc=)JH&X zj1WHn*&cz$3cE%Cdp!yGT@m#@F`y1lc+$aN^FSU{sP8gmb!e|S#h@Z_H+rjdYv{ay=b1UbMR?JTvH;E2Ml0m+F z{WSLDdhN{gwgZMP!D)#oJs<&U6BZ!CH{q5$fl&T>HzT;7G4$vYe+y zXd~lJQJkxHe0x?}f8*w-qGT6dT|1dcU^8VrNkl*t8#frC`xcA^EC>KV;|+;lwOf1n zpViCd+jO&EEkUV-)q>SFeSJnl`q{rE@?QH_tjxTi_ZE2K$NQSoJw*A4RxhMC zR}7Z(`V+Wq;b8C(+^3IsmPgXJEZIuB4J%WizUES0R8t3%ResdbGZKn7zFCcoNSzHQ zF5!J0+D^0gmZC6-phvGJ^v+^@``c-)Y~KhQOG~hL`p~ttL;6;CW8HpT?Ry!Inx!Gd znU+A>yW0HXTZu=ka=Kq20z{`BYGSg5}!H@T?V>x)gKRp!?Cf|EUBoVcv=o4%{K+%6F%r$d$OM~*3eNit^~i`SMYq+uGpoW%GgVPsG?o{ zUG%HD_zoak6oW7WxNKCj`HJ?DZ+R6dc*cbc9_#A%dkL~V9}F`!w!2%Mnnh0KOps-v z=M`fk^<}yxq|qa-5Yj9X^6C21?LrffX5H|T_etw4ViVJvMlI>OU8NZ+fpmtxv5hg9wjq<{0WlSvW1@1LI}eX zXXX{i5AwB(F4tX@bDo|FnG`X5`DVPWE{sDr3^RmIJkYB2Bz71gPPO9T7245D@Y2bJ z;K;~wb%Q|FdNBF;+n&CYh&9}84=H}2$D0x>ril1<9oLlvw1~aXkKl&ZO(3RQpiepJ)o2oo9Fh?Z&7hNTiwLc#;V{_7^rtiUj!Lac^ zeZ-3*Av5B#OeCC~(LBlt=)9`kXRr4Sl5$3?GWse@j{}?=r=4+;we+qC(2FNj$2=f$ z-L$cYXMsrhiO3B}2pY`!)*A^P~JDHEreI2dfpZt)mw`br}v*-!wAt z?-GD7(W%L4G~dEg8)Vs55g?G+Bl_&t@vF^2^MbwPz`Km*hAqRQ4xS{9!8&2y&l)F7 zV)+gk@uI!olJag$z*n5IpXdKC76DpDVL`%|r9eu%%}EDWsazs2x?0>*ZaA16&)bo!PSow%SN{Og7G0^^{vwWUI7F=8|`VdeRA`0peM?Zqf2`W;*hEp>Hn z5HDSml$Pvp}b>@+wE|kK5E9FesEZO%)D-EZW5CMw7$CkE?e`c?wFx>STE?8FaDE3)L--s zTaS1A#Lq4p7Oz@@f+A{p#}4$xMw+?O){wS_k`~K8_&q;@nO4~KyM-!iy#~W{lnR9_ z1_=<-rgSh2aQfp+^s`891&_#;`|GdoraJoE_I!Twi1v!{`Bqn(VuE(TmxxDmZ3-Oz z{$DoM#iYzqoiICqcnD@sX;J#Q^zf2y&k*z#)U)UD2C(R`yqHEVd{jg>6G($0ch{5M?;=$4jG6ws?01;GRw$y3tFKGxgETMmL}$%lnJRY? z6_%>=-KZt=!`o5aAF6`-tnhMXsUu z%U)h3f6?$%Or9PGNki-XI}t>@%rS@H(F5X=;U)$kfY!>?TgXvG#G7xvCeCI1o*MOf z@fN&sZ(!&c=7`+(9)n-(Tb%!Ot=}q&=me_=ce@4%*m$4ITW}132dDHa?B^X)#S0~F zLR3QBJCUog#~lV%n7Cpq$jPZ;^+QWVe=lr}qg7T+te=;I>16yb0=A+9RP9R;EA<@= zxcsZ~R}j_wA&qIu$B!blP(iF{fdCH;=v<^x!BBnFr>-+uPxJ}@77pgHE&Mf>F8j|B zhmjXFo5|1tHoLZFtqf+p0M@yLBGiQs6=f(_Owr9=YKqu6E76*|TagWv8#C%AAO-{||>o^*U)P!ONWbLYmGX(mtZc+ANh>BBllCA$C ze)rT2m;0mLZ2)d=I61OqXqec{uW;EjS^TP?4G-$8SYftUs`L0T5i|xaXxUIM^eoep zA{Ud*eiTI`gooe5C4nXs$aPWRTX%VrAP3ACC~Y2hcDWY(v4l&b@@g+-+^%AL6d$P zk}3Js2^=-4lKNwD8c++MV;hQs=Qi*>vGU1lNpUQzC}c{CRW!pbuFYZyX4=8c50)wg zMO!A3c`1ac@|N>qA?e(S`bzJdq`2D>Q<=D1?ju=_7G4NfP7b1Sgx^We=OIOvIN0PwIzI5pD+l+jP3o4`6~w8#O_4@=DPp_pD#wp( z1Rc6NoR%#h4fWEEj9Bq4?wp-jr@%}Bi8A09Zt}Rn(%h2Cd^>ir_?(Hi4M!y{#2cDM z4@)Iy0rv5@>5oEYN&39jVn5}yOE7;to>9Ipr9r<@*u{Y_F^rX2)8`jgi>h<=rz!P= zK%Phf9-P?yC~z97e!~m+Xg9f*n&!`WPg}}@BO>m51V5!X81fb(DmQg|Mnew>&R^>u zbikeJWbY1ZDwOsgekt7zQaO`qtSz?9n?Yr=^e2zXidXsSLQ)0ZPx_LbXU%QLMO>`-MwYM;sqL z`s-}tIhgD~l5<&3P6qHF9#jiA%>xvCn7(;_DBqX((l8PScYiiRDtDg`rH&{*y?DI%R57Zx`SxewgL>z4rBdXRSj2(RV8U8$&KM zF~K5lHtCdU)scPHK2xe<+dJq?1DOSPzGMhQB=SOFkHy4k7O|XK$kemg)G?Y1*0pEX zt|y4{m5vxzKr@kYt4%WVNo6?LOufLkn1%z_=9p^>b0N5%`oX)1^-~6k1)ZAkQQn%W zHQ74dPo!B@giG>OHO^X0{c^745Oa zks8t9f-Eb1&RiV2j7_S%&U;yA0z97cDA|BtRlr#Z!|lgd)Kxd5)H)Q#%3#RzeA!RK z)nO9qE|O}r`_Ow4nr%K2M!4D04}#DSjS~#_19&T=A?Bcu8?osD?&npql9H=3py#y68E;cX>NsW)0T*HuX|0 zFG3fpn#v0Wc6jZxw;IN-^F+RN#a_E+Ny&KsZ$_*9=kxVnQhXgR)3hjMfUd`sG5}LloRNbi zHe`dp^837=QRNSht@QtsM)%T{=TGPZrA^FB_ro5WzFOAXRKSW{F}HgEhQ@|xr@^so zu#Ou$Qto`I&eHx-_dGOnS6MA$t~{hke@+eN2u<%h-0w4n5w$iw6u?B?(_>Um3zD$v zQRG;`9-Bb_O^x?}x=0+}SQ*5XA0tlec< za~JLiA!f9TRY=cFmd(7LtB-_VK$OY^M-*rYMwk*}+H+S$X_POi(dRX6F}Wub=DO9l z>5{h$8LIyr#>Q2I^+?^Ned3CxEdE$jibdtf;H_pX8_kyNi`GCa+2b}fby!Us8WiA* z6fSncMMdrM(eEy%Ve>mb)rwfN)gUq=uM+2Lu#bvOQknH0Hyze=xwA)_vm3}AXIUK- zdOcCTsMS|g?H`yd^K?B!$x;t|Sy-$IXN1@I?|U^r5mH2Q3!0TkrZM@9qJ1G}R2$4c z9W7aLyLK&z`Dm-c;dDLS(r#SpWKEWMDz0fqo3Fm?uWPgZ&b$UW-xtzxF8M{xM#l7) z)8~P`Hd?kblY%6%!=xm25`}QA{HSdR!=_MBH5oc7h!DxE$I`PSxdO8=8+(w)dY4i9ucqaQhd#6A-Z{{m5H`9I$MWJdxZ)%sN6`Oao45Ll)2YG()CJU&FmA zlfim(f*Sp!%#_7Ug*%@2!sx^#F)|D!pAA;6!}ZWgAJObv8X;=%{gY7pImbJVwbbyh z2jZKp?k$%TPID;{(Fp5PUQu9UB#?ZW_})(1Y9IN_7Dj zP5Ru{t8PPsWrm9m$ul6JTkfbARJR+8kG(qQmQS!t|H-S%!qHRwV#-(*k}oBw14w0s zV2en1AQT-D&=a1Epz z!yKX?Z40DTNohbBl(s1pn1Y~~(7kilqGz55x+LcVNeu>KmC*V$PsjS|M;pgZ0uYw( z#e;*T)xn_XPrqSWAh%n>3;oK>QrufR$7Xo#c9UMZS#(yJCqrzr1g-sTW?Jy|I);RwKcg!uiFt1+^;rTtoWNo{y7f5qpe82VL5wOeoIX*lf_&9qST{4#Id zvNmh{PgWa^$VMm8PR-DOd=nuOH1+9i4%WLb!X=r5$y;xgf3`* z)D8FY9&WC;G(ZqHWoWM=Py89G?9iB~g|`n#Ld8IB_As91FL5Eu3f!{RqmEYb(Q`F4 z$ErphrJ*hc?&=18XRgLav3fbicl&r!Y|?DZME6t6kgfZyl5-WGhblxs2Fa4KM;vOZ zu(xKyKJ=pwcelnH1|6qIw#gMkb4uolWTo&+zyMvKRQ#AKy8T=ul#; zwKkv#MCU`3>)EtDS`-ylN5`yYtQ_Q(ryHKi+zgG4wohfSABq%G%@a+L9ANdxC6XXK zu67`^vb16N$yqqE*c_poIx)L`p&Iz}J}n9RRo41$TX&j>A&p7(3_&DaZCJ&FflJG6 zEwx`CulYGE#f3|(tXA8O>@Zzzzw)Vyta$H@33JNO{IGwNPW_8Q=6qRrG&#|+Rwmx5 zpu0axB1>N)!&K4hlJh7b#=*$UM90)2@yhkv_-x#l%FZRR2uc74Exq@;iqOxS*a=X& z9X(mAd~e{M?c0K9wQS%uW^^S~$M{3yMoMM`s|ml++kTs?eL{?+(xFs6)|?pUR7vHqm|t(3kiP!=4Xz48;Od$>#UHR5ATu$6bV6i(G6BCcDAU znZ_5Qm_ma~bsEsn2iE(1Ni09r5;BKtkUF^^{xsOUxHoqX;TAYs<}B&HTU#SdR6N0V zq}^FhR+sE>6P$v=qXBu2aF%BXE^P#MM}sBZN=B=uy|1*4$cO1hUyf&>I(aUa(Fy%l z(fLkBKqDQE`l=g@`xDu%!tcWSBLbSwMKXrkdhTugNdSzJ=F4kPSW{BzII6pJSj7tM z$17FnCFVt?qc~BGjQ`>x6ps2`YG1QjPbRRqbg2Eh_jJcb!@BL)Fr>JNK|-{VB{;jT|}#|UMEBn`FS>&zF*QrBFhibdoQ29&lKB@5wLoBt&vBMa0g z*(~)vi{-bvE*qtdpOz_FO~qB{6H!t9S{f7gqxBj<8tHNV(_%j7bp%t%Y-G)Npw(Va zf@6^9F|$c8B*)Qr;)J5~M17l}?xObkCP|*IMfuJ_Uinm@u>D0E7G;1-*#GP}1od_r zIT0k4mu=2LYqF5Q?H(a`<+QR{4XNS5#Ir@GDJ^YeZWIFWNOX3fShrZ-S9pHJ*IHXQ zMu6K+t_m-c9)9JrbH6!N@s}M=UVnF)LjUrOp!^1uV~E3c6svg`&e&+*^fpg# zP0`$`>#d{tlZRD0(cdPY>C9Ap$G7}-jq0wx?tQNHiL_?s9ihwk=lU)1=i|?(AB)(m z285;BRgmXFPW(npPf8WBRhP6R`8;sp;23&#jD7eey;|*l%5&OUqo6(dR;Brduqw#UVvEM_8?-$ zV^z|V)zLh#TU~BEjejZ#(}H#}@Xs!-JeOr+l$#Ef5#%??c8CM#+?{Bizw1@$MU|Rm z)2%WuxhHIkD-SO%!3R)L9;v4jyxuoGSrlo3mTUjT0My%96;>ex)sRIF3#t55&> zL>4a(vaSe+szHeogaLGJN-dw#cEdsZ@s--{)ee^H;>m2gt}Z|Z7Vb_JeF%J5#~vFy zXq6EyGZT0OaW8!EuGon)_k^6KPC}LGGlh(Bu?jVC^cx&0y0S7KYT01#dpm0I#@#0l zg`xzay3C`%!BYDL#!|gg-NuS+iY?+XO3m6vmoRz%K-TmT+E+_y1yCB!8xC^F5VY>Lp?IHT|fm#y4NRQd|5 zQTz@aTTNg8#5q4#_L{12l9y34TY|r%=Nuf*1Pk`_LS(|~2HoYiv2^7daYzHF)j~y zUpRf?!>`G5zn)YA1b!~He#5NuN*Fvp7}@lh+e5B>j5sv0YXYt@`5|>AmE~0&=ju4E zfTNL#YfadxMg;2>JKmeA>U#Eq1QX^+xkZh{K%&Vh_r~g8@`&jX+<*KGOt_~vdUo`9 z$caI(z+o>XtYe|+U?(fUO*k>KkP)kYC$y_PL&#hw+o8*pGK?(=AvPy&elL{0Idcy= zxbMra8QKR+Z-_GB7Exv;uaiUzGAod67{f@wSu$FR5ESBlcPah}ipUvNNtiN4>}9Pm zr1+`aMQLIyYvaE72|N!@-K3wn#Agd9x<3;AD%0sGAEU2m(c^7M-?e2YBMm!X{wcw{ ztf-y%-9kRztltGpqQ4)%=i>ny^~M>t#+>@DLaJUow6w~6aVkf{5v4!7RzGB-?n^VI(*VJzxjmt-4}P~oLOqd>_)Mlj?xJyg|u!ict9v* zmxRZp<|IqA$|SSmm>t1_uL_;pW=?*MO3^JXaM%9+`$#TOv}Q-fLG31_1?}V}HFNZPYKZx>L%g{UA3(>dxgJ;k%z{taKHE3EokmgCt)CD&oAm8b7|Y z;I^mYx!zgj2DXn^3)4(pGWFjhd6S=NeI#S$) z?DU@Z527d2y#GB;8>FJF!jG$sE*`#i&Fufj?Ah)zsfLm(Gear4vcSW?HPIsum8q=H zDr#=N-a|d^E&0#qG9Q>SJPMfHwV?hj-`Bt$0ccsMT^Uhl(6NW zQOXPWQPQy1J&{%PDm!gGAS26#(!J5u;V{oIDX^;G0j6$bOmeH22iJhRf)WQ^VC(?8 z$F5Q*JZAJ)C2Sy{I0s27_k_Dyce{jpJj?R+z%OI7JZlKA@8%)3VSYOd39WY~$U|{Z zzM`1%#X?0(qSDfz!_%))XE~Qi7eB?hL`|GREeu{rp>xrDp1xDVy6|dRVUmBZ)hU~d zGK1`W+tn00G#Vqg|MPK023H=D-ZVRlrK3r%KGm01E%IEq_mD7}G<5U)Z* zISnB-4h*44X3t1zw^M%j3hlkRV4dLLnEQ6bu<`&I!2k#YlcNNYjPUY|L1dBsX zsra>F$@2jg&(w22r31KbQ+;DJ6knGHEZ2;zaaT3IOsQrjdAg32Q_Xcu^Fvlfk$s9T z=hVD9#SShF_{~#~k@urjngM}N`J_XdI6KQWKON|j1u3-c zO|I{$|62uKG8HX6~nS4}R?gT;cHb(AI8}(dKarx9zc-1QZrV3}lf6L(hIyvGv|dl4cfEBgaa_%?6wIgai9R({W?mF*5jhm`#O8mw z7Kp{SO+BabX@%f7=gwYi1Fm0ya%Tr3O*y|>W35Z(8*HV*&^DrdR$jS-2zaI$FZ2P_ z0IfarT4GM~nO?MxQg% zOKAo-gvwm@%^0=g$%`OPkBPDumTwaxx2tyZ3B6-3yChqBhdk$FtC1uim2?sZp|q&o zLt(f?(tHtf#})9=y0Nval5d72h%s4j4A6oluA$~EQA+j&q6NaKi$=-$O@I3tV}((^ zZeRY$Q5=AMRQ%edo_OW#=YBc0&=Dj$yXSN9UD^HplQVzhx_~)0uO*O>_@^+S7x7qr zh|;u%xx-!f@LKBxHV3(_f$~Z78P`o$vtlS^I5KlW zZ>5VrS+`Gi{H()646nBIb8?j1B`)9D9x7YDS+Dlw2z*eIl_q@e&U;MyL$F9Dtzcqla$QEIouseK@Znl8s zH7y_x5&q(NFf59uoGe_*7~A2i;^B9Q(0Bvh=vJ>>I3y^0G|~YtX@q= zON-dG@M8S1weh)twa$i6&k=|2a5UP#8PBgKwaKA!+QevStzR4Ou6WN1u(xWTxC9rU+&=+m^p z@NF^{SNQzB_X#N7Z<2Cqb&MN!gECH2xNA>Sw!Z`Vqp+ln5mhgad_6H8Pj`~6KF67I zb-?ZWEg1W(uVR*ej@t6BZB-+DM3QThteIOZN)aM`ZwG1R0b7~;3&?y3Zi>K~1mMdW z8X9WgSu}BW@!oA@D4K}19gnPc&PC$VpNUz3E8pVO-126))UraT08-gCrp;{VphiiF zuhReaFkf2S&=7 zP zcnL1;XgK;A_O1cnMWsVAGHL%U)KyxeKS5z8+R1G+OLu}c4pHyRf+(y;b$z?Lt3Bz| z=jj%~d>Ub*C->D%TNcvwGAf7!&NuVqls7(Sa`D>`zM`e5bXh->u(!>ANUv z?tfouuOc5JcNytZCra(34Wr>hNHTHEno4L^dRMCCPereNBreXWRoBP4B)l^zs7M-j zSBo;$52?8mnHr);uT6qK=vm$6$k0)vtD54}9}pCDT$@+g9b`tXcZT>(`Z}f$iFOyI zo)p<#O{pKIDND-Ceux4|8?u#e!FX_mmP-JiJhs5su9m?{$He{J-AP?8Z>8QiX5Ea=!S{iPS*?_0#L^odnY;Xe0+1U)mUEp4 zBH+a%fNqaGzi5l-db;l>CZ(u%P`;&=q2LZQ;5WjG6;ry4FUhb)^C?FcShC~4ee804 z^c9ngOWD${a~0Ur9q(Sens|x3b;u{n1wZ62@F(UGz>8=r%5GZi1;4$W*XZ;gtlSwov2iF^(S=3lj~?`{S>+Tsv{^R@lpJ>~|+IWU@q znOC4lONVUyI)4-@XnQ!Vk~~4%^Y)tCZG%xO$b|THM-2_issy~&ri2MGP}}OgG30tf z5OvgJuDCP~jf)qKdN)W+oyw;5%h%*ZxwtJVf4iR@X-{YC`0dQMv}cB=~jgcSMFhNNH!wboqk>kP>JD3emO9=;86RC9%`c z>M@>654H97M`{V5uYTHnOh>5DWqs1!S=OW_y*lRg1E@xL$xtaj!Xw$mDin+0<+ywO zWA#9R6D(`$>hg8u&5eXzlh}%hxACR)y|&iM&x#R_dg|Ck%@of2NnT>-E9T(TWP9+s z6SHnP5USIU23rSY51adZS{8V-@e&W`0>wy2%LB2;xnH;K2a#=)B%3LQI5{M`#dRJ1 zBt5+5ItbXlpSkn&X{SWMLc077p^>aGq``!GVA-eBPi$iyX$+XLAOomCl)@U-ol6x&Oa!zOH z;Mz4PC2{#D;?Rw%f0kHO;sAVk-H|g*J9{litg^P}uWL+5`)jx5|3x##|HGW=KYj5Q z@a@x4&rJSzfQ&MEMVP3oosK^ZxQq|rE|VgNX){!A1X0qWh0p0(-wJU7TZ1a4ribt1 zIHcc61P!z{c0@e-14ue@`Pe=1^0Us6UYTySOwM=m%X@vR*@(Ryk?-XczE6+R znfZHf2I9*WCO;2{ysE!5PT2K+Nxw22R`%M+F{VIr{_%h}|Mop88J54Jgb*JqE95F`npKTsTd;{1}rOVNtC(*bMLdJDCV6cgyZbU+Qc$HQ0m#F#3lDx3Hbt z#!GEubC!SS92H(03IT3rHy@9dP^d*g|1e-u&hl<+BaWT=@063eKUow0Vfy`l7{KO4 zooDKMqGt1V%#BRuuM+=c`9B$;^T}K2aM{^S?C;Q@$v+&Q{KImUe;9zs6brLl7^VsP zJM`#3e0MaUu(8rs|NnY*|Jz<&7w>_wO_`AbMIOZ2xEAHv<^XJQ5w2Q5FJ(I3PmYX+ zP7nf+UXn83np~i;6c%2t&;K|h|KTBZH~Z@v1)yhiwr2qpJGs~MjfpK7yx&5`H9doH z{v6Q3qyfHizMT4mQMRJu!@+_rV`Il0gSQ`F{R-CBmT+#;%W)A?*VLry%d%y3u5Xu_ z+AHzt5;M@bnyG&@y3x^7P4}Sszm7D8Q}$opfa0nD-*==T7Cp5Z3x?Sshln2jKtD>5 z`8P)yVE3kqBGC1B*E@C1__SfU@3%<{lk-(bf0ms-tx+1K=7pNBNddz7B4fNj%8`-p zUzy)Fk6xosuaNg?mN(Hifh1VzTag`Yku^s$lv3{>|1P%vD+GT>X34)o&4;)?=Co%I zCkL8rXZq~QAAAsIp%3E`4>1HVE*r=#_Y3&5mh-7MC*5CWtqd^ESqif1^#qFPnL*?Z zhKjK_L3b6y7q(bE*;M&3uDk$tjT+ImUSzm)O1N33%gt;=a7t&c2aK&FEa8;5ud>=x z>N%}uY9V9aE1w&e>gy3v1%@{?G;1Pv6?6?TD*{l7TU9~C61`ti)@5f?nUm2kG+h*n z>l}!zKBvFoGy3FHf)b?A^aUVZ9^Qsv)0wT!NypK6U)*>QF<NSz(1Wm> zv9u?Ybwa&?SnNG+7#wQ!4)ovkcz={$YV6z$Q*WQ`1%7#+A3hwqS+-sF8^oP=@XyJe z1ny9!HK+kA{aBd5>kfH_8=Ys@P0&+D^#;YJ4NT^;!B1u0^Y(C+yT3K8-$(PzKJtn8 zoY^Y}lqa;6O}v$S*>SGZC`)nS5l;3dTAtdsus-Us? zWO2HM`O_Dk727$mt*JzysFWdtllsPdxa^xTEz%A4spA>8uFM=P1#QJWz=G2?R6CyQS&CGln05IP`P0pn*wx0T<5E?SwK({9Sh*M z6y#V&+wxCZOPkbze!f{@fOmCaB?4nA;bS=gOcHvcorkwjt3;GV8d;DCx7#Ww%5SA#}ZYwpHn!QUZuw@m~ zLW!&s6^5z8xM%G6)rgaK(U-{LA`+@o>bGSW6_l6n&|a)7N&52oklXQrZeDre5TDkR z9208WvLv*zl*=o(D4rPDO~!B(2%p1rUEN}*w{b&pJcH=fuO8=rh;*pX1s!HZ+hA?vfE>ZbA{xj7(oRgnn_oKY;jS;*$aykwl0*uMPJP#LR1sWAE;Ly!ye^5g7*Kt1(&y8q?O8fMcffD5fDtBRxOToylE$9yoRJmf!9>28A9?;h~?@ zz|B5tL=W}yoqm%CoRTFtk%S(RsM_^VdU>IOgze1&`{!9ISa8W;zQx)@6Kze$A8qQI zWwv(HOPvZYv|#jUlYr5AD2(|N-(*YNE&Dgt5K~Dn;Fgb`VkHieJZWjqWai0U*_WF? z(X%tQ{&eT5xe0LtkC%DpsW7n+9+3!D{q~57tLyBxu_HUu)by$ z8oL+z4pFb}kMCdKGVZ`KIJ$f3)4~%s1Ml6IpAvFw*y8K~sp{x(F>-xM`rSxiT~9aK zvDK7GjJu;iq@fe5I*4&vzJp#JOiJqrVM((t6`8`hxE%+BU9V*9w3^F2m6u|qYps$b zHdmV_-37@yb%cDG{@KNT%;>nZGQff~RG2j`w%3qW`xY`4wmkHWmNAWc5Qf++F+$LM zo3w*%9`LcSiKwiNS+vwTG^2mkf(MtUNa}PKV*-8G9+Q_P z`6FivK5g^GPFHmw75S#mB<%64tK`U~u}k3gq}&GcusoZd1`yhINj%sgzScE(yFyfh zm{cv8od5JuTC}GKVSKy77iS56XBxM5B5~>Q_Ncjh@%Pk*Tp+ced!OvoM9R$>##M9J ztM~7DASb}l6fd5GTHe`Vz8=I*<$>p^*!$n!wnD$=b|uMWy$#LNPR)-e4>xTKuuCh| zXd^Z)lHoDy{&X9CE*6>#{t*-;6RMsbUB~JU<^7vV?_z8O^Y4?CN~a)7TPcQdXnOez z#AoAI&bP&t63QmDI_?$baJ4KMOLtXs^U)Z4)KFYE3xi7Ogp$kTy@^=y`)y`o12J3` z<>~pRJcj$d$j1Ax-|z5U^%Rx3WI0!)M@}_ddz*D%-b?tMWFv3B9UBs(v%9rp72tLF zJrG!oNDe3KXM{u-G=)UBi)%F{-Io_>CRNybLwknfMQ4a9tJ!G{;yRmt1#XiaTYasR zO%#i^M#Uwg?PItu#?p*GNFmBMp=!Ro$$)XfDlz{e@twc3Tk`8b+ysTI#S8j;%E0?m z3;P2ap>VEUXwy9B(7^t7VaeHRWU$CAR5aw#6E z`MiNpE4C+LJX&RDHpRa;KPqW~e23fw@MBRhFIFGE6z;Cz2wg=wu0gl4x?|;JHO2in zt`3>3bsEFT!IBD*1@6OV1R3IDeBbMr8E#{hkPaY^*zL_Y3V;LOUuyZE(aQo!TgBu9 z>rIgUH#=ktT5KGGd`9!%-OdZJ_6|qGiz8^|ONo>cvPMNlAYXDmuX3a0MYq_EL&2Yf zUEWiIo5@HktkpJU^6)C2K0tXTfzYV$9`x6>nH3N2L30cdi*M&|d{f#)3rTm-bDj>? zlU!$;XO35Wug1V2udD&fcQ`A4dC^j!Z*~WEXSycQmlN`FQ-7-MC)iSw#J9;TX7`A` z=2r2#JQrgs&N;`2o}?^?U+(q*LZ8Zop8fzqa*K16mVL?@HJ@bZ^^|ycbck2J< zG4x+jx4$mt(ENP*?uY8lHY2IPL;di-uKo7$XWsQ?73PG%`=y^=$4dNcw=Vhp8nf4j zfA`z}=Po=-5v78`2>3WF2=5cnU)P#K{`jA`@%D)3q2J#HWS^cSU1t4_^l*XXRrR0{q(`{G8qen+?{{;zAESK1f!EN4LGYNLM^s@=DLeY@*>^@YTz(KyMR zy2>-H>r}5S6^_>(`y@}x;C8JTK(Jgco3G^JpO~eo>8qfYDZ8og#HFt)7%_L!$hUD|99w0u)Aa7nt|kKr1@)%llASdpJS3w zkgb)bgxSM=(d8WFl)f~YW|VT{irqz6;Qf)>#_Hzg)S^|n8IU$tRXIGQlpEu=1tiwcZ{)pKR8#B!?(161uBcQ61f_&tB%wEd zQW6pfC3Herv_Jv@=>noImEI(TK&Vm@I!FsuSc{qf0SQGqN|jJls?>F|&lqR#v&Y_- z|14j}GzYE{f=(+Un?{EkfTCd;_GR!St+m%5Qn>oZO37Tk#GZ`2qk&$H?_b3|Y5 z2L#KTLP}xth<-`gt2!nDqI^wZ;*EvvGoiN~%baa3_uB&qgUqk@`Ir2%d^>R0=K(ev-^~}AYR=o3ZSLJH)XRtTd)Gjzh2#*%5$}z-?qUYaMT3Ex`YQ^$!5jjpeKj)$QoH#rbwnUmY$;{#)hI+T92}{N2OM6-`BYFlUR?L} z2*)$EdP@Hj9g^gxrfEbx-zU!gZ6R)_$~5B+^jF1=rHcT1CmK|0e!yb&UV|#et%Y4A zS1?#i?+p=9z;ChcK+D$KA+}fe%8(->9iz-Z2SQYmhVc)eHQa~e0JyU&pvyFNGA;pq~ zUHS8392e*v7A#QbINlNl^6R~>1|-i5KO2sZ+EIRz@)TsecGz8``R|3BgS%6v8$VC3 zYm({KKl1!X4gd|(O+CqT*(@bl0A%56ZIi9UE)!pbatsJ-;2|-)8@%`%yJ}?)W`yH< z09;3v)D+<=cb6}18vgNJY}B{XJMd}`gH=X*B@I$ z>P?mHok@rmZs~V|supK07D6}y{tGT3nPageCjC5*DaS_W>Ml(qAYmOpuPnkM414gl;xmAt_<_RJ$8Wm2{CQM*d6Rfku?V8m)czyD3ba@Pydm>vF^RO1iF zcQ!D>2orbF1RC_em8`Gn%bPrHwKv|?uB%Dm?yb$v`&t5Z#waQG{qw2NFCZ|5<2c1$ zE0x-mX%aC=CGQNv$`#9g@hv)xJv+1bI7HsYB2QD>dp<{P82{YeHIWlvX%_5OjGuxw zj6Rf}61)M$cU8af&=7KO>EqbAUPY~tp0zEa2nSJzl^X{R8S5npr-KPSZ&|9uZ$Cb~ z;bC0Nq|t+14c)yplfbzKc5o@#0asb-@L;mvk3v#K@4EEQX`8wE_9|)90ubGBy_Rw9 zstJI%PwMeCBNcIiMM-Oz~RWdwZfD&8Y%yx{M z`5;q{?h!6b3;Wq4-drzCPFzx2xz!HLw&Q2Yj!XK;8mlhOvNElHUUmdh1s%qswX%+v zjy)ws?vHk@-|sY6SEpL@nOM4a%0%<}t^V@;`$!ISr8%Xf$s4SP5lQ=vCo%Hx3mpH; z>xXsXcd_E_5#6I^-?M@Xg#VWibLGw3_S=V}$NAG=ZbyzEJnLAjtax={P3nJljsH{U z1_J-bknqR5{VbbWxa8Ihu#Tg;3Z9FOceP-%xBsx4rX;$RP&tQ(OG=xS(NCMqvwAgI4(MRO`RAhadbVbAN(<~x#F(GUb1D?jAQ>CDXyyz&;MaBQy?}8BB#}tZf6?$eUF^U z1I1Ey!ygzBAs78&v4&Pm;G%vLQzp5V{CcH1KJ;Aob(#B#??k^^86`V4=ofzoBpc=^ z+Opw>gR0gd5;aI*G)Uy!^1y8K%%lV}?#UO}=^r^m>rksQ#xspO!Dmnb?iOsouC{pB zr`cbAf*HMk#|2<18e4nK0r~+dt!$sFW{xAv`QCtI!=9&h0YkPj(WZVUZZwh}B5=~* z;xlsFUww{SltD|$_k!{gQ&K$bu9RsN?hl&`TiPX)>+x#(_C%X{S4?2((>!t7O|B*J zJJFv!4Wnv3XAl-p;e~*u{a!z9noYE4kLzTjc}7ZkxkOEFl2fOW_d`DFsC zlSKp;c`W;$bdsC4UU#sM`U&-UEFQv4Rwl$C}yYYJ} z&+}oJ?)Ubu>UdR#x+uQr(_0}i%#2@Kuo6(aU){(p{ND?|3Xx|zkg*>*7dQXW$1`tm zrIxI3*)wMw?|#)VIB8)f!hHTf?btyp$N5^|q7;x)=-u?zy54IA&&XHqKKx|h@4DD* ze$%!KV4S-liX`?lq-2C>iNZqkfiKe=;-n?m3v^p1y5W zx5@MG1>?O#!*ajFTC8q!%I}&LZz`S&EAUAzYm!K*u(3MWH3?zh?&A(hS7!dd7Z|@) z+`U&Nz;_Ff0Pg?p;xujS*J2p%2}NCse>a&l@F*-fb5`-L_=MZ^^WdSp@eyfy~C&emoq%)Wn8;&?N#d^Nc=54Seo6IJhycZ@N zjqWWq?`mu6`Rpvoqr^d)$o4?d{0@mp8@H5qY~;`oPOE+~1}f(?ZZhDFKKr_e(@Xgu zaRn`9hoI7I57y(wTzRaDp56qnbi6YZz(-$Gn8~oZ3^(?v>=RXTnepfSF8gp`*}UgE zj7kVUE7>wwd01GF7@N)$6)(dU(Jp^%qqFwz^gHOy8ACfmLoFIvE={Yv?-mXHtJ#-+ z@aos4bLg>Ut)FHS1ax~^ZoBTC$#*pEjd_97sbg#PV(r@)e|5?7A-r2wM7t5w=V~VJ+(&ke z*SXIM$Hww+B%Gq?ONw$&U~l!j5DXM)Q-4{%#$f)T%t01d;k@HZSJ-ZuGw#6xI zX9dXu$>_5dADyZ^VRbnfG+Ut8U4$;-SbjlF zsYD*b6qFXfc+rc|JH zcn4K$=%6H#rFgmuW^s$yEV9a{pKn9ZFCkvew}$k+yUNP4Y|L`KVoReJjg&OIXv_9E z60bKJsFm=%M?t`@ ztNl zxg!z9`^hFC=-9N^len}9Frj}ewsQI13fITN0bQ>Iy-}KI$)}GLvoI-^i!eTRpN(w= z1=n}LA*d=FTLmC>5ptwR$v_Dp-`+ycy~P4jlfoPE(_*mxv0=mStHTcmYLfoFpuNPK z5)BV;r+SqepA>D>G*K22xrWdOW}?PCGaNB(kx$FJ3L@qt%cL?6fpsp8v9zCI&(qlH zZ)g6KteUChuS}a@v}>H5HDjBWPqP}@YnL(;Gm5@_NdNI%-gN!L6#OYZEA*xf04JVh zZ85z%J-6Tr9xz_kd-J5!A;8@7_qZ}{fVsd`%bzpxyj3vk%UMvswK*^37h#zU`(QZykI?IYj{jf3_0PiyvrH8*ZVSh25KYKR%erMJX3DAq0G z4BcSzkRYG%TOVVmoSrBo4jqS z#;r&*%+k40RQdV42n)xYI^Uq^rK=qu8+x6`ny<4Zu#Be-uTM#`zcw?M4Uq2K3{g-` zd8R@8*x)uW9C`36rD)&wEt`Y{;$FXu#14OPjYjOFB^_s6P(07Z)#;p1&$4ww%<_yF z?e=Xk-sY`x2i|>;keJY4f5^I0BY&g0HIF(V*(~d8Bj$VT=ai^P9pu{B3r}$2vOT!BdY}2AlL?%WakPZDZHQ)fz_Dfa7Kb+QJ-U|4g9YL?3TNCK zrtLAAcY(g|#hVor$7m+o%wIf^OGd`8Ky6AKpISkgmJsHWY| zm)b90qr0;<^gAbVT*lzR)cRc*^8@wS>cw&X&)=7?NSb?a$7`)6=(y`eHG!J_pAx{e zGuTGp(9iSf6=O=!`0`$FJ;>ZZ%&pVzpcf_#I`AAeONou zMe$lCt`fJ2p5XTixTT5*|BR^x{n6k%+2#gmg=Z!j=d%VBC{T&-Svv6@J^!F}Sb*_t z;!bwAPryv}LF*s!-2GnaHv1f8q3h7eZam50$@|#RsT)9B{^uqU)Rttq?r)$9|6p;v zj(tlj7pFh3?)D=Ry96Gb&WEsi0}xF}lw$Q4W}6lU+kc8_((26-{Czgl@TjD=)@b0G zKM6{B4OKoSwJ?i+wt9J+Z2A!qUi6 zlV9u}VYgUxN|*NCjnu`$$?RT6dD$V{>QlT`GPj5Lq|M?#IX5(EC@}pJ{>m1+|R-G7C1cS!UYDIJtfn1pA}`w(+mamu z!UnL$d6BNS2P$JlTRek&h)pE0=R_R>iZR1+R1W?k=1HyDb(N~$omQzQlNXil(Fq#5 z;uiw|iEc|hO23A?jRMJcjcC`NCiX~-tqE(z#EPLbSlK)xy0(U{NX#_1Fba*5{q8x@|gr1{BlK&53E8ItQM!R+{`p0dc@kJ$;y|cai>v^e(J4tm8RezxiI=MrTaDIc+{8 zZY}M%w_lHpH8MP`KQ||2g>4cTiqkQ-nif+KT~DXny(us=l4vm!Nj4Ccodp3G0v^EO zy^qceMk`o%gVcK`3xkCE{-~dvm68)i*-s?m&)k~cO8d5Mu6H4MGf*1tuU4qHuV!cX?*#}Ud*vta zesC_@;_v8N`kSE$m#nJ(FP`7@MBeR2B|Yc&ZbYy=3Go3EElHEdOE|Rt{FC_gy&Lhf zUK+ouvCdccDE^vZT?r88?q*ez4MdS_0OfQbS00&haH&h%2 zr+7x?qpeC@;JviEO!cEw%%bh1!6{?7xxtg71#D+*3s)?=gNR*1qUI=NO=P;`#*q1A z{VJ@|8TUv9+yYxMHXUq-C;17BfF{vu4)U%rx*o4Kn`M5?T zV2Z+Lb?YB^*t>X#*}oCcYYrMut+dwK_*TbT_Oe{k-y9&*?xTzcIH0V;GsoLk!M5|} zGe~>CGYGZRw)cm1&9qFExA1CrXXf&~^q~EryTakow)!Xrqe|0Y%5hy=4>}#B3(=5Z zTA2srG4%&Sus??KIvFFmCa>Z|l!IY8*jQ{MJIwQZlJN#%5&&B8`;k?2mYCZge=iTj z%c5hch^g4G+I^46ns@KX^btx;rMoqL>3*6BsM$M^*M|Y%++U|b>%?*T;qEbwTp64N zO8A3?4`1V>KM;&A-lBDpS2_JaJ^;4b;{Tu{RT(l)+1NvF|C0!DK~#|~b!h^IR9wLxso5_2`1v3n>?4^oI2@og><1v(DNXHUp3NCE zCZBG4Y)RE98!gc4QF7boROi=zhkVI9iJSGx&`DNL)_}9!#y|Db#6)OJHNnOSb!I$q z`Vy)XZn|%c_^}@JZ2a7~+g7=9@@~)v7cAZx%pMPA+IDYQ`Y(tB<06_+H(k$4N>QFC|5LH|Jhn^WkZ zlO`8_{sr+GwLq%hC7*7-4d}Dq?)|RcXi-lBFL}ZaWsj~NpHm(3q`j@54spt^Z$G|l zh+A3aLFD>TJ%iZsq*fuA^+oMP!vNqUHQ65)+(`bLm|0frTc){N2yCj?Yo3p$thO!V zo9aDn=6~Zmks4GeL-Fjgia_tkp|AH8nsIj5g_0z2g(8c!BEm2untt_HTPt#K_9q!! zj>^xcZS(2UDM@fjL9mVpe**!YH3SAaS!9Kh#}X4g&i(XI?#;I^mj;Xe?GJurXT*8G zg4IvmFC|lZug;rXiYWKm5O~#PR320+mtW{EX(m~xeYwu*F=(!S1O6jXtULe9bZBxN z9C~rqTmfk}t8Hjo>4lg@5E>+5ASFq#$H`(-Q-)fNs$Zp|xxnEOhu@dI_Ew_mj-OS!-Z)2QRI`bOOGd16j*j5Ful{%p$Pm3Qfkz(ibs!q76xJs!@yv_a zNp)uahHd_E@0M@gr+iD7{@nC%mD7M%Frd|6>RWtx_tiM!vB@vGpJKA*P=?A?dX3ij zIk^L813L?NJB8PcvA{*3E(u(z+0mjqhI{K9{C%RjbWgSiTe>m@-?fl8NrhY<|1~#J zIu++<($s&?xzKJfH|Sz?ZW`dF$IaI=(!HuyPh=U|lxhmhsz9GVtSl(ipPa%>B4Gy-muor)& zb?`*}KGckmSptbS)y}$0b}HxEnMRunh2w&iq}M)t%^^J6bfo0Q?aKGpNj(ov0&AEd zpEo(o-@iL$c=Tyi}#M=6ifsuzrMfmRKVZ@c(>y=#}2H8+|%?$hbgX`x?s z{ZB)Tp)92y0waJaNErTS)7K)(Dr8kyvT6cPw^%U0mse?*Fp;@nt2d@u_@yK)7M}Pw zO`zAt_o4mWbY9TA6?B(YuFhPLgh*6cNfS}|1&s|xyDknS#sYzc&vH-5v zy@@|1KFaXZHfr}bt4EB{Y6J-Zgt?79(QB8D9>&5F3DSQJ|z;Wz2~7oQQ8 zez|h2a?HJkdp)iosNABvxA@I}CDZ#2Juul&N!SA2@}}5k9xBX_8VkXC@!~0_9?c}N zMI#3IMfjcUzM8?8?Bw_)m$45RseI89J}xnifty8xV{UInV{k}fUdr=Tm!IJqlNHlL z-k;fg#=NcNhrAV9TP1?R{BB*Wep6_!BqorVm}ZDKFOt1(A6S|s3mQdwj4uQ@;S9~| zC*t|$EIlCcS3)-it`Ld#izr_Gu=M23(v5)P9WVDz<-|lh2AOhT1{jy@Ap5<)M+n9B zv$VwT`3gQ`V)AA0spvaZGcn9;XCQ(y_LTz;PE{sMRSLeQLxqLD)qH9*9+nB$O7(bZ zt2W(Jo9L6CDS0Q;`SSFibvw_^*$w>mD(hg04qwOlnlml37o}s{V(tE5>jO zY|fO6`<*7M*7ay#asa_U_ePB#__?-pcS0-!u1!}J(o1_pvv4`@^7Mx4l7q9yS~4Z< z(j%Omv44p6$oq?07?LqEs&c)5{YU_Rq3UofN6}uJ%k#%SJ#O{(oMfm>|9=c1v!6a1 z&4GXf99x(ZH_XzkX50dNw{CMKqLIMW#0&rjJz(_GD3)S6_4rEGW(y;Y2FA-I_Iz*l zRJTrLia9*i8s(ij_|I&Al+@aU)3j-Kyk{3MU8s@YUqxSr7x$%&JcbRW?4sN%VojB3vSIi^5}cLL-4lVBt7-s&@6>Xq^wHcYl0V30?))l5rjf07L@dD=^5Qe ze^!Z8CPbJv(Mew6Fup;78eSCzz7<^xu^?Rou;2a9p$bpo98Bq2oL74-hCEH=45O7W z>Tf`bbM;Z7JkJXCNguIBp~?dH_Upvk{ap4E)1ZO*sRkxc=v>#7n46APrPtWWYW)AI<^8mV5SlDSP>X9|07Ho8G=!L{j}P87`yPyT(^I3GGyMMyP9E3SXk zA1o^5{(u>{-xwOS{-M~9GR8pA8rfxKHSdRtnJ9i24s}Dh*W9g)$WgG8knobI&CbPr z7EITE{yd6{!0ooXi*Fc0_J^kP22?dxnoT>WloK*u!A$3gG)o!m z+q%K=Vxr$ciQha`jt;WERv2_!sMF=P>aZ#^wMQ@gj^3j3?6H%P|7Xyda`$dy0U1OT zxPK$)L(YNcGDMgBdSTdQt?^tZ1v`~>ISm5QPo7`SXblYXb=5o5>}8xzH#YItEFkI4 z1GsmF%;NY`1OaUahb@?ulvd@JMnX*3+5fbL_=sAuA5;RTp%PDCeqez$Ci zGEqt*9v)3fj@DN$*3u;k+$agzF^a>JwX^H+yIK>Y*UePxPqT8-(De7!^N+ycZ`9J& zUa4f7NlT!Uc27)fXT-<1wa<)Q9B|t~bJF0JI{Juar!7~Ew*=9fOyVxkBR8sC*q?$B?bJnY&yBNDm^Y8z`Z$@YC@==ZL)RXaP`#N|?!KHJ$~~cX zB0q&hRwn)U_X4yQTf`w!KPsWa0jUwHKPnkK1hK=F^sSfwy)ZU7>am0fy6nTV`q=PE zvhc1@e}yH|^4faNNRmO)X0$`Z`7M5IKG_O|#$rHJjD$C^ztxqhp1MRRbpnb z9Pg>?l_W_43)u#a%%E<`TSA@)tMKAg;4i_j!AwmjSQRoZceEEc0gM zf^mcPcNtox!u{ggF0XsJwcJkw`|7@z26K%=b=~Tev)0=py8{WYS8;P!MZA(T5jkO`3 zF-aQE?tJ9yV#9j(`T$E>iR*IgBmxd!iE-=)!fv~jf}!eqKBI%fqt~X!#pIpB-E9e< zzkdIl+~H%dvNDj$UL>c;@WwG!;b=FE&g~Zl)_;fq#{w$&${&+4_Tizc6 zhW^elG?KClLs;bMuYzGPTu~_$VUY?{ja9dxQxxv<_Xj-3o%zXo)&Bk=vm^#r{M8U; zE=zpWvtT&5oRcFcuZH;N&K@>t?{G`p{=aeU^k zGIP_8y^dSfla37u02CLt7>uD;(V}H4G841k?-9;8?VKFkL!n94lGH*7b&Md~xuH2@ z5XW3hp@nuP?cgHw-=?$SYiAor{1fD=Wk2**RJg&gvv#vp`KdRcfWDK8m|6Qd@fKC+ z(*rqiYU)}IuYi|XAS~VtYu^$gPAvhzOC8uUON)RDot9;`6-!#<=q@OG<&d1xXpolv zPg@^OZht7JiInn^t-o5ck47w-%y#3UBP5|2ub|w&ZdsF=kj|@vs51)rOmhe%dZ+G+ zEV#_E?BvBf46a?bnJApn`!p&pJd{O~e0ly88C{&QAMD_e@gGU{h4KPN4IXI>6s)JO zJ{tM$=L$6u&;CG`vcU3ORV=!Qr`a|>yzo2q|6U3H|HJKUyK;Q;givZJ!V_N{H~I$K z5o!P71&d@t|2+Ku?ZZjnOmQQC{fxo_bMiWQKEAu4`!jI2bBGw|pu5}tC}4Yi0mmjV ztAXDQvzAk3jb?R2Vbz)P*E4$tF~gqyXRdGmz2JN%lHb`N)}N$xsa-yi zX>n|wo$Q+QSyEar*9#Wft6k^`t%#>4oclk1yeE2b$lPfB`Mgc;aI;_%)~l(wRx}YF z0#R~5yV{W7(PCBYcwhSF91Y$86dO5rSb;!H=j>F=UtDS?5>tFX93Ko02JzqQ-DB>` zj!R{~=mmHm*RRYkL{2iBY*ul3VS*>%|c1YvvcO?%qIKFjr``QAegF+-rXx6 zImw>s7!qVmcU8dMe=%KAp_lVUCeh>WWVtg6Tr4lKwY7BI=$r1d3ema`C_eZoUHzg|eFHQ4H9H$Ak)DpTxG6rpbF$goB*vz-ONckY7 zZS}{jZcnF~r=rB%oC3AL=Y{pR9H8au^gL^fbg27EgcV0@t?Re|-@T^=Eivo9MMZ(y zw!l!1#O^}I?62~gzoq&m%VfD`EZdm7L*V<}^WS%O^~$Fi167P)2b@_ewGAk8pGZ!E zYcMODm6P!D&61SNeoqiM)abf zKQ7)7CGOF`dDq_G?MYVT3Yxm&O1$wiHwAq;_eMAImJqUZV$MlK$)`Z6e3)VN!?wL{ z!%?lM#U$s+pg-?K*f5FyRRIxO&2g{aAy`fS0}SyT`Hr$|0P+)sf!x|oV_p{ishmL0 zs7EoA*Ty%t%|}c$PItFBvoJ|{m|JGC-Qy8DZ&H`IChH@_N@I4n6$l1qIPogGXQd!} zm8*<3OC(7QvwPG#j7O=i<{X?=G!DyV6vd{k2O7O7(94CQ+=&kAg)!3yle1G3gQ>ej ziD<;DiWPPRcEug!&VE?+VSmK^`9r^3ym(Cg(0KGz!)ce;yI#(;`L%!t!9$SEmb;6O z%9@NdF>@2a-hLt` zmCh#fIj~4^`YLXn)Gd!4L+QO-|8k?(5?-kYrk{*_E6zDLP%f|3iOd!;*}wZ}>}J$( z9ZsMwc>K_90P_zEjM94phJd2*>6Tg?E8P=C?0JKZXV9!SZuf3AdZ@G8Z&_ux-ZuUs zbj&0{&;!9TcFaIq#MNxp2JG8vx4a@ZNc;KAjSDw^ZkT-hzTa0qR{egzsoX$(iYZ6j z*w;6%U~(T^gSbv~?W9+c`dGZ;SJ5lL*vQdPxQb{KcGpiyd=(^=hY>5ojQe_Q4^j}o z6KWIvTy^Ql^6Lsm>1>#vuG=CRUT9vCzB*Y*bX5%P6fx)q zWK~mUd2_87*|%*uTL?I7!nxde-#1men#_aeV|i9R&R@M6xXttEd)Z-RSpjC{B_Vc8 zAZH2#bax50@`7`UmJUzXR5T7b*5t5H2Kg@}`TR$W-zUF!&RGgA+vKJAeAUf9c|3|~ z;;Al$SzMA{XIi(~R^i{`f(j=*Tk44ecEt%KR^f&*TsK1@mdWyF11-{k*9CZV5^(3-d7+~1i`G~U&1c{I8XlVJxSQijVSf>-BT;RV*5Jt>&`lUYyAjeZZ+b^7U=z%Ks?xO z3LrJ7_xO{a;kc|sUy#qGOTkd(UeL2uh(l_Rs+@A9oP~H;qR{hCl`~2aH9Y;mBfOJp zOg}pFyJAo4u)gy2m&`oHQhT057b@SY>a7TNGBBcLq@?j`WqO7!m?6N>N5X8)jI`pR%WQ(#lt!@!Q)025ti>Tae-w_T@P`uFyq!2D zij{?9mi1Kw!Us+p84m8Bky1t!_udMdf-Sy~G+MtDd3o2(ri=AdOoF`Y7Kvg=@eIT& z`e8?*B&Z^>RMeyHfN#t`S!S$cKP@aha}|Gs!3>un4aOP8UaRaUEJBSGi*XLxMww%t z;B_~xWSuARhCA@B9fA!H>qg2EX&W1gNarQCNSLFXHrhP zP9j~>CI$I*-;8MizTMk$vn#1$tzdShoQvusWf)La1H@|(P+0bGGJcga7C+*^$qpw! z-xbbQBfBE+ZP5cTL$BcfDD>j?SBqw>ow*Y#x8ofB1J-p*5C_`VRM%5lSoK{2lLz;8CUThhz@Ir*yR0ErH1A@%QKzIJoR86quv)JmuWGEvAj7Oa7i= zYf@0GJm1mbGX8Z9zWwh7^__FNZE*IQR-Ph~ueozuZlz~YEM$~ z959Ll(}qswmq`PLGQk%+mt6EE!qsQ z9d9>^f5^$yzZIHGYefrN4M2%o3m1g5$s_CvWfQyQ-;$d_k)f@pw9$+)>Sv<|za_#b zN>$ZJR-GLIRz496dv7Ny>09-rDAsQd?aiSjB68BRw$>QT{@Fp)CGzZcxZ101rm=Hs z%eWBkcmryTJ74+s^WO`KY4U4FF+n-Gk_mJ67H-OWkHqXuRW}*}1f8YBIOe=(o=)2m zzAe8|%w14pC08G%amM3Euap-D>5;QncC=Z+EILfo} zZVjak?a=j}S;7wYxC!S}(ZrP2^}s@TFV%jm+uD(%u5PFkIz;Swh--ZoBr}4rVArou z`_$Hi`U9E|6A@vBrx)(`?`a6os~=^DsQ1_o_-t{zqm8mwvvZfnA2GNJ6N=foA3(_; zxr*Xl7JNaVO0=@jRGgT#vK8KGWX^FcyIy^O*R}CR=|&3%MuI)U9v1Ko zfM$_}pMC+mDQ-RZTLUl&2s$==XNF?499J{Wh#g#@PFGilibj5zF`-2$Oxq#wybp0; z+|sI}?naV}LuzV*d%cC4n3}VdK6^r1&&sxOu(aRoSpusN;9a9DWtvK33xIK;3PcI3 z5Ss$5SEW+V0kDE0R=PMDQCRzs8Dy;Bm+A7Z)RVL-E+ULH#1qyiv0HSz8Qs$?*q!FA z6x<-a)~aXZSl4mfM}lJ48xSUJ-^k+!JXx6;T8J}D(JsXucpvOM(Wv2-_(fRtIGTWM zN!50a+0jC8WeLVB%hcwH9re5Q;b)4-JVa~@W5!k$s<>IJb?{*BL5PuK&q4rnw>lBu zrxXEQ;CFMgcY6vEI~n-iUbLBeWfYKdSN2y*$>1J2dzwhMCSCe3FX+Ke))evb|D1k0 zKuz=%nfdpE*@>nAVLe2vjW$^}7iI0zyO(!(=C&Sqd71WEq4o+XyRjegG6_#gzq2l%<%WOw z4qimN&sFcY*nlhL8V{|n&Vd*FSkp9D8UrwIDdqIn)3_3n5poJ>BQ1X)%PLNjnLa(S zjib$hkr5=%lXe4G`uNjCm@KtS^}G_(t`IxQ%(q!aQ~)=n0UvSWiH#9&#E{%`M`=A# z7+{oHu1Jh0%+wIzQ((K9KgxYC>J?;yH??$?QEn-HnZtrI6etXkmuWIdtt<< zs2M;>d{c*vl{jBsjB-hn)knO3Ux%p&2 zHcTPnD5durJ;u?2)sb};LtXjP_gCho)&kt`UmL8*TprJ2SCd-H<;x6 zi~3`mZW`DnD7}G^@>Xt`{|IOSdPw8a-8hb9%Xt=bg%s`o_<=h_> zyzEzMN7RiSw{bgql6{lIp|Ebfs{MM->G9D0082e0TlcKDVMl-tV7$ zUz8E%W0-d!*KVL!Pr7aa*mnM;d)fjticX^%1vQ_gOvkcEO*!KMlq{?$Ug#o{1EIgH zr-x*})4(-o+-%iS;A$aMjKc3v+22>z(-fqcBRjPgsWyQ=7tya35 zzOOZV11Y}Yk5HMqu76oGdHB()G_$Dl4=X5f{L`_boEb`B1`236S33AXnc ze$BsH-)h-4tOJcl?}oi0-`h6NLR#ns{8sxGzKpmvtv0!Qg~R@Gg+S=-6wR@~pGdttpGSX2)-c+Hrj`mPEb01e#Nb?@*pQz&hkb!`%M_9)$H?3hC+hQNi2 z7?@*8Ydfef$M&Vc-1_pUYKInoFDmq5BCV=rBPkPsgR)orN>A|?&h$_hI}ak46)F!a z7q1c{j18l|*a(*{Ay<;=kKf8Jwn`}){Ip@JMqJK88#j?8>r;a6=z$vqPoL$6KdEe~ zDdEYm(u%E#869fPr-@H%xN;cvngPkZikyZQqccJ1J^?>H3L@?y{zm5pe^4Zxui_#Xjxx*@@?95hYIbX zC4BVs2{d$2uv{VK+B13sgWRE-r@S#8{QI+>v%#++>9KmK`kz?6v@KVf(g0$kYA0S* zsb0Zv(AY{b_KwGYQhwm0HWOcmUjC|yf3)O)9SEL)0TxeKMNc=X!UI*wXgCv?9g&dfRk$9jl*?3LMOaz89kzE3A1yy7O66e1bVAYx9d9s z`6FsEJeB6>$B0yX4W;1|LJv>Ws!}5pha76L3{8071|5*2m0XH-{0jqV%Oqs zTeqafuht=GRbY}NIGmXA(F~;Ej?RdmLylB*SO#6=^sjhrCCmB1<={9chF!YoejhT@ zRZPUBOs9yC!{d`2d@uQ>636JkYxJ{&`VH|{rb+{1Ublim_Qy#`lj|4BFwH4OMnYFL z)IM_8BZSr?BnyDbLozAL;Z;iUBhLF6a^J&411};lEQ?u5Uq}PcHjzY+F2aBYu@b8!zC+m64Xg*$jIy%w!a=L6I>X8VyiT4KyN%XUO15}N&FL+&K! z^hF(cQ1(D8m7Pd&Gy6U8ls5xODF`}*_i!{G>3jhr!>--=P;U+YvY2jTC$UVepw2R9 zvIo@sNybiG5AmDD4(1y`Lr+uGJ&cKzZ{%A;?Jd~sTXefzyBY<{XhQcW+SA1 z`Iqm`%?HdovWxkJ3;xeljxOKczsgPjb3A(#Ceq*ErX1@PHKy1#LC}A01czCz3+eOy zU);UtSCeV@w(HEOV?(7&mlBW?kkI=8p(OOs2}K|T0t5(6X*#1)LMPM&0#XucKw2Om zI4Ui4Na#(v6j7=I>TG^{z3;pB{tx!rpYkbRo~+#Kx}N(yj#Ee%e3ovQIb+`ByM#E< z5&iBjg=|fTp9t#e3X0PlF|V5d8ea{IsGI?CHLR-pNncM6#p|m(NVKmkugN%SsC7&m8 z6q$^zY$F1Odw=;P;X_1$g85EIBlicL|J@E&>IjHmH_(;3oU>}2ac{7!p^HQ{)!s3L zZY$3v!c?m>xY_5RudB=br8yaz)&Jh=H$njglO6vU=<~Pb=CI9tZ0*#$>m2`EpMd$% zTm##?rrf(`GIV~LOe^6!xD5aEK@@mP5L$h~!1L^c=g8nQ9iOJY*XRq-C}V7y1ia1$ z_RPcBsOg4H`6+w24dUw9)R1_X=4Y|1j?Zdttr1YUYiE!MF0DyP&KZy=!r^G6z}>8V zN__FZ zwgxajOQMkPlfP@Pq#MyPGI({^ z(pqdV9@haqqKFkqqP`;b=T!-nwYoCW=->5${(Hlg+-XMFK^4Zmg4R-q*<>+B_q0iK^rnHv$0lLS z0H`jBObD>C7mR@(U1wPQ0prjUy+NC}i?0=K`%4WhqZw>`i5QhFtB}J!|WCshcc13~JGL}~F75tFd2>{jw><`4<=qfZo z^ncnoNNLXWTI9nBC+m6aa!YoiS5oK7a_w--cIqAr{dQOmhYWEF-09Y#x#c3 z&Pgqm>_SYC`gF0LPt{e0@@XL|tS9T#^Lr%4A!{Ggi>r6bdkm*zw2mvV3E5WqCz_+d z19Y(42ry{Lnj7QAwRlWsDIMdUKTgZv5>+hR$dK@>RwK(-WT_6r$z#HvGq!U~#gnnX zMAPOz@Z&Y%K>bf*`6zTx5FHZHEity1m9t@gWc$s9XO#K3`!qMIWJp0h(fjMh{n2#` z$DUr9xmJ?{1np);=@`jTOTfhA{Xy<%4=|}eH z=d=|J`C7oFM--L_4?&9?5(1*>TFj2@fdRx`h^i8UUEx~d7BS`Dt0CmJqKtL50F&~W z9!_?FK1@aP$?cNP`Q7CXE>x!}!ByE2<_s_h&6>>zcCd?DNn=WHZIe;?&@q*%OP7Yj z;17o$4$Sk+^4L^Wbo8xSIdP)p^%eplggk0GQ+la< zQ@!~)YKUkiXzQiauCcM!?s%2Orv17CyAWw|ONpnPs}!O+n<56^Un>Eomp;gC*V~eb zTK9wnOD2zae>}jM!-n-OQ-fic8zwy?<|_6CSYcJhRzMMeHN)xQPTm_mg%UNVhqpp3 z+YMHqoVXeMQ+M*#;|Vu-Gkk0+*5GhhcD-{rI*d3UZ+6Z`AV5D9XSy z@*lvU(^-@is3`t`mn^E}0<5SbclKC6K+|rE%>)~TySF%_Zu9nKk-kEs6Jt0dXp;(C z9r<@}06MEDncjA-)n8jEJs8p&p5>xSgRmy)``~jZE|xD5bIT;?1f)pHI+NPwT8w-; zs%{hkL1lCe+|~-y^0bE)6}d@Wj}}~hFqbuBjJ^?({nZN4Lx))9bRnPWX$jbMB!BXG zq(=SLj5o4#2{fIzvGU}(DrC2^yDb6E8Q=cn5&hDo6$R;F;)R{I(s?SzGp#a`E-u;Y*_BcM%B#DMF(|=p zOpz}COKRs5mFGggm7yf!pdG6(QmM@gW7$TDlMiu#*>^9`` zwGAo0S5_YmOo7tn%Z1&DNx{|vS(kcFQ2KHt_g;a%(qJ2g#`d?p(=k8W{vzV^xJRpSB#6Ln#kO-{>* zZde>uO)0R03i)wfBH?OLfS3Sd6X&zRba;2X>$XX`3LbSyi6*H#BCW!g{ z1)rK{6K)mVpBVghkJRh#glg8u*SdsPIr%2wMTm7#$=+VF(2W8c$}Likpu*2o-vvn# z$bqP!vr&^8;-nRQD_JeY8KoC+X3Jo6rkB8e&mW zJEgE(e`&b!vqfK}f;vT+F%J?q$#j@9rbWvqK`qMMKy6*H`u`ZZ;onAxwFKb>OG zZW#S$NV3dey#DXrAQ!Z=i&(?NXU{j~wUxeSVIk7B)wt)0AHtLBQgclz4i+&1fUAB` zOWbx$C7YT4e%0&@NIP!Rsi^ei`0e$NLFHz|JHo{TsDvn3?N83>fnbGQAj(<_IzbC^ ze1I#ly7#uE>g0HaXUvak$&*^^#~x{$^)W~WzHXa2l7HJ|+Jg4e_ zPinoa`wBie|LxV<)BJB{zSnfPy_6X0p#pg2`wyRt!fgZYvZ0z-+ zuZf@fKgD&l4wtXx5PCDV%zm-+R#p-6zS*7t;oq{&6OVGIA(X;INNk`0bdK;N3%={& zeY@hv7?FkJ&tcQNudD5{@Xrk&u34wHu3#ng*x-cZk-mh476lF{@iF6OfzzCd$M$;w zi;NwaUgQPQ{zE$T;xG(*k>0P-&)xOCvJg?Km=WM>ew>I5*F$v?sZ*pLAk#2 znw5Wct4{H(oqd1k- z0F;}*VQY*1^bNYwummT^Jxb=M)1pD?e91Qwm+a*lXjwBzxs>|9j(R}cUeq5<8TwsF z4#&1li5Ol;LH0^p=Ek#U8$8)lsOObTo+oDyPXxQS8yr+qQpV;1Z29%s3%Km&YjX<0 zAVDH)uAmAG1@APr5*svA4YRFhw=tajs2!{m(*DEX*B-Y4;Pi zuF3xs#nSE-|CJ&Herh3i>xWlXOyosc?C(p;A%FevJ>ma+3Q6kFi2wIBkWs(Ip7;Ow zF|Run#oU5JeyxAcJrI-Ih5Aqig;*UKKJUw9(7+fpP@;mtiG`}O{q}n zTXmfM4OlhS_n@%;{_G!8vCTlpQkl8-uBeG0Na^CwE@u?{$*5g-#dM<=E0uKLhQn1Q zSuI0c!R<=Pf)7BPr@DG_16fvzS46WXit*B2_4N5Cuc-w%iVF2Ud{Y=^139QFq^EZU zSDE(xpl>8F_qA(fm8mIR4d~-*j1i1l(~`|;`#YlY^a_jIYkG)_Y!~Wl!6yH}f$A(E z0%CuMGu7X;7om1TvbdTv^GlcjYzAG%Q}9%l6|WezmojIg@RPIeTyj1AsDCjnmP_qQ zrRB<3{RvZc%Dx9wS=1e}LYZ^pWX-^*zb!&-e+oN`_e)2~K8D;eb#Is2pYgU?4rArP zLsIGTY9HW9?kDpQJbP3C)XByfyhqyEkS8m1DRDXcJTBYbJ{$PmgVwqcd?N;H|C-?& zvB4OrEl~>jpVat)!U3z9JgYiG$#l>c=~8l>W~RWRSWRPexV@9$E;jKINpE*|gtG<~`(yk&`TW5wt5I1T#*czgG3G_!obmzYCvmUuTm!p#pGEEu5MB>3qZ%ZaY36TcQwDpv0D5!5D(^e}pRHu+SBwJ|7-gw?Lt$-|USBL06B) zxPy2)^X9XU+}Gm9vV56unQQY#&ph&n%GwZTGyWr}J7P@*t4&t{|Ex9C@9>(O*4QXr zKc91upLLcpTyI0auDmE+3-RJLGc*(apr@sTgCRV2_uR1xi|R85`Z42CwlFm_pEd=T ziBE4jtDWIG#%BrBX&Hfq{;p`Ccx96-|37if0H@LqpbYOGIH1uyef2#L3@Xl#saBla+F_DYA=5t*bw$hd2=f4hfiG zVQNFr!uEZ9G!0lrC4p98%{=~^Prjl2e?JKx6S=aWW&K(PEw#7nNlj25yZ<0l^3%3{ zVi4{*$dSDQZDK@z4l^FK2Ngb3&m?LNKO!(3Qx)=u84fw=0X;X6{PCx`b-=$Ozkm;#R9?+Ka=?(d~#f<%jR`PYYx z6+@Y*FeycnF{Gm9to#C>?q-=t83trAovj?~ZQrJvr10+&GqlhpH{jxh@Ea02%K)iu z91P_sGxiQHLI__PZt$(f58XQ)p4uwZ67J_dI=t6?oFutD(1PSo2%TpHF(nq!_?v+w zF3*g;z->ywD{)vGd1RExf|JR zFZu^nDElO-+wE0xQ%UzhRJ(JW^LP3EPgV|QnR4%7V`_H*oB62D=c7JW`R%+Zms6t| z?V!B%!;0ZTZ&`h>d*PBV`-$ZuZB?t*rgUp*j)Z?)S*EHsloAt^0ZY?wJ`RY9%otc;DY0J6Pu=nZhT zA3LdSSb-QjK70K?iKC=t2CF*BX&avsltNTc3zU9wyM@2x880T&W`}#P5E*j&EDBJ8 zO{6YZWIn}}DPIgqLSP!Jf|@3+&)hQ$(oIh_n-SkyEi9+X{k8lJ=AYTQKJbQT7aSdZ zB00+I2$l`}6}$A`B|!hLO8sQnqw&*1`s?F(rGd&d;?qoBQgssCyh0Mm{V^Xa~m71V^iN7yVR@lj|PCwj{38i6L*T*psjMkQXR_T#tDdb3V z!~U;Uwn` z55FZwO%lkH4a8Z&&)adYdngAcw?b|apZa^*eMQo)V@k5rZo11t{plD-=d516MQFMg zO#%l&di)8aJltMTWhB?;18q}<&G2?Z;b&nb8_M(Ar9;OHdD~fjC~1u3HU;S%u3(j0 zrnGrD&#NqTk)ZJgPb!n?YxA<*S?`nwPXO#2wS_%T!O_(DCo*AyP}H{X7s{~)`wPd2 zx9>s_3x+7{9Qv(pw+wD?E_W9HV+|0E7ohJ`a-{_ME5|6yU{X)XZobc6L-K*;|ku0{*CosOn|#= zLrB{WQnOv{)i`y0*_v4+yE-+Cg0ooZ)IUeFK?k2PpCRNN~3@Ak4u! zNm5H6=4zjxAU{-S=nXkhlKa5+NwxZRM z<2Jssw*kg>>z^u8BUmhcrXVENhB4~` zQf-!-7zryl3Cpb3D4t5F6~1t@VrIfH@1x_@5;r5fNDE)>(_y&fXm%%+pi2SI054*4 zeQ6#_tbyP9B4omJILL!F4+koZoY!q{I&!pM_e1BIf#G`~XCiDM5o`8fBv>qiC(`Xn zHAlBP*=`s=b+`LKojxs#U7`I!JMmh$z=8IWj~g0zQfa)uzgaUzL4z2-NyD)wNskB@ zhZ%+OZo|pr5}lKGGehrRbRW45JDHmlEUP+xkm-F`R-(ZjdAEP~U0%oflALa5NfP1BKwpav zh&!=lHaoA-X8O{Qr=Mw(>DiYny-oQFt%3a1Gz(N1Pe1viVZ`*<+5sriFPP({k|gQD zXn)#gtXL^973sms8lLq#O$Bz*+k4-St$w7wuO*Fcs$4DD?9MhHW2`qm?x+-^L=ZrO zalpX>-Omk4&(_SK4E*VJkB1>Y0?fAZx1^{P2j@m*nIAhHz1Q1452ZO#1?)aE8cSn^>OveF=kfcyy0W-emD3wSvl^ zpdYJ39htwe&?E^1%3iYS&Z3HZShS%JfR*P$Ao+QX`}z3mOP8|#|6*qTJ-%8soa&{X zVy2zX_a1ah1j}NcVvWu;sew<@9V@9LFq6J-OPGB% ztz&rpPwn!!PNB5p?VqsIZ9_nfz52ilq(h-r9Q{>Ss<&gf8b?K=&=>zqbBtH3?5j3} zb7p%d{#58rL4?zI^nMD)Tca$^B6(bjzSVNaZY^Zq`>5&a=P@}y6^wm!Ed|vol9?=s2t&y3_kOtULR`>65dIN?SU$|y#*uc z*OvCmXe^l!o*-HCTo3@CB*<4xcmc`q;Fx68uYUN-U$XO{I-gg6yRnegR~Wb2*-sFk z5oaL2NwDq2ZBbu*OkwJ4q|9>Le%w%#l?=Q`o( zx0Ze^gy>czL`ffwnUN=d>3r)*QOlGops>*kM~RjUE;5rmr;?xD|MHvF`_$)+*?{Sv zUZJO~q&xR>yQQZ4`e>}I#PE^EUsWn$Q8C!{W+jDkqyDkmCNM!0O4V>b)dbK-&@Q6u z#Wc4TLeb{J#(>Z35^_y;p~FHI>WMu#`)b~Q#Sj?&u-$C4fx^#DOS+O|s7Sh58yS`f zD*#+rgI+a2B;9J4fM%St3cy0Fbz@^+ zQp_3VidumO+hvzc(3%DRv`+JGKFaW~Tvc$*wsFt^Vp$Z&@8%{ixMwSqcpeOOQ_Fz95E z$JZ?wHFlv}vVEB%IXCMxw}<>NzV25^l13Zr>tq00qbs;$Nm(&9vMdSEr(fDYZt z7@iStLQX7HRPTSvNs%!BNdG6n*+_MWkb3f744+^$?cs%;+ziqWplx;TZyR7B;7I`X@Q3Z~q) zVe$7T?2E0}X(^}jmE4MUSq<{y`Neg`sXyYz4`eb}-sb2EQ*gmV*hQV1<{)EvjKy^_ zy20Q|V??TFCYZ=|I{1oG&-G}6k$HJv-#J8e*D&oga z|COpRCq5nT`Dac+n$jE$7v1jHdB;y`)o$RqvSu`C4FCaQ4& z;_6=tK7B;6&p}Nl4FBu9^?Jw8elNiL8_!M{XRyZ*HA4&^2JwQ5?v0TCJTMV)3ZFp% zN5-DdZkv~$z`GjM{VA=Ele)3CPAa~;hBo6h#5hJzIq&kn`IrxiHR-q+Y5Uva5YR~V zvA2tooH;1Q@m=MvuLpbcQFfN(3qjLQJ02cR9R)Azew^>(`@M3^qPZ%ZTkER8lD<{= z8Q$Zc+5ycgBHGW38pC6ZU`d0KT%2k1Hk&d?MF(OuP3Y1)J-?|d-z%d_N$BJBAMs}B z9x+n4*qQH9imQyh@o|s(J`sjZ@-$ojjN}nFh%&;2?>^{LORnNGP7~KeiLbCROe=E8 z<9>3+<1^KX2>uBTdh4)U!pF1TXWoI?-YF}GvU2j%qX9l}sm*frf%GZP9Nc~j2mMC4 z_C;d0wFYPSZ2peDl8AZn3Lz>glQSMOco6L|iF)r&nv=i%8zgb(XDYH7RR}J1e7ca}N4Xr)J}laYiCKp}~4A z{HCK9f_*Eze^jt~PTPw5+Z@-A4M#gdA2vR8eIfJ}xh(2S{ZXf$aNzI>aO+(q{V!@=f3Ifwzb&^rFbV< zhfcwo<%H`hp2lCP>Ak;ZbNZWGv$B2*L{(1V{v7&6a?WUTWu`Y}N{iss@rT0nGpV4@ z{*Rs4FMN9{FP+AXBBt*hT~s|XLqr) zn}^|iS!9daA6vr>KZkiKG?`cF-%=UR+n#ZGuAa$n9X($5Dg`2oV$VH^%t-co3NW&- zY-mt!fL~9WS-m~+AwbKF_ui9!Cs%YsdaQ`9AtGz1+N3HBoF(C@i1IA~R(scdIiHw_ zI-5vQ-<`=@riH8ih&q?_AspV>se@*q@@* zX0?@(B0CT{EAxs#bPZaXoO^+OWJbr`64v^gv-ZGxs`k$}Y{0&J%TssIu; z8rKSC4yI|m_YahlS!epn4ET_2MY8k0`oZG7`dPR_0rEkb;>7H~g=$YS%FyoFYZ1xt7GLyDN=k~5eV3TSrw5zm zMPF?yzHBTaV!l0Ts5J9Mwyh!Z3+vE<|Ct@!ri&-DI^@P#-iV?50S4H?W1C!@>wxip zLmV=J*^FiE>xj-6<#U}LhBrCQ{-ZakDJMFJLWz0&>C|2j@QdZh_yoqE#%uMu6wt6b zL|q}Q#nyRc`ptlgzyp{V9>KO|ah4POlD&(IE-s?r#uOZjmP{BI)yy^PSxlXKInafC zd7%>JL9cny5qMh&xY zlVi#yfPjwgIo%a!#B7h5MTzn}uRT->be!nE=G+cE{{^C>9$+ z#c~C*d<~%gU21Z*PH|Z`b=pc+WIT#Bjo^F{@mpn2Vu+7*JWt^+_*8yy$T??9jFtDX z)mPx_pJILqYZG%F(8h@9?I$u#8nDcap4#$f+^({dWi3TCWz!ah`#Y#o$K5<}mQL_W z4(f6B#&amk^>g?A_lo*9RkD~*YKUY<)gI9TM8u#PCl1l>4h&gnP!^$(Ko`mQBtT^V zpqn>E(m%7FFY$2ub|W(P)a>#tH~z@0z?;?|J;noO+N^`Uw8ux@P+PJ7No@oyI#?wI z>ZCcl68!=kmami;78=p1enfE2)f%YpFTn1QjI(H_jY%N=&S#c9L$Q+}0B-c@ea6ICm3O>!nL65BP|D z)^#^Y8)zC1UHVAhTlsoG@VP5{!=qdYaAXAb<{~?ZyGbmi##6{Sj8|SZdy+ zd7u;@VDvL;Kwv0XP2#290)mH2APmmRz79=%FG&WSSHv)qXA6`vje3*3PP#HzTNJX| z#?g<8$dQda3HL(qaTMcp{KJVb$*TT}ohl^lDi*+E-iLXR8h&IPt2C z?B*KL2_NqCav3srFPHZipF{-cureWykBDB_8)!;?dhF4{+W56^jt{bMqINHc+fR!T z>IzlXzW&_h5j}Y&zBTO56(_r`)M|FM=Uy}NbZHb*`yC;x-_9_*++!}ixn5*WO=l{+ zE2>B*%9)WsL);Q6X;G$B{)4#z66qYWebMOYKNF;fJFIzFx`u-$r!=e}5r!qQMhI4i zf*HFf%}QBQkvPCqZ_N6>$Xc^*8f|u>h~jojN}OoFI3GHm6(9{pP%gLdtTb1LSfQy@`D9mym68suk!LiF3#pR! zh^D}C%UZuSZrlIN%7czuD;Giqlov%PkD5|yC3PPR8B!9zKW;h{sRduT`p>pN%!y9o z$m`xa7W{?xL|z~KPNMw4o&W#Pq<2Wbv;c(r`ync;Po zAt42XeUPDWS^M4Z4Xzs26zkW^Q>|&repq%4^9hr-;!%#i@JQJ70Lq;sI{v$~aE`zD zfr2lnwin>Nqu|cYl2yadM*fT&&y^5wlDM10!RPj~CJ-g<*`3neTo&n|!9_=0Yfk?> z(|+z6F9luBR$aIMS%=n#fTqtiHwfYg5tg_0&WxQV8A&GbLgj-)r1HuE55--DTKdco zH7eB_!9CktF{4FFRk{)VU1d7T);8HVjGD8u(r1A1i1Oma;Y{|*MoaDb4;-DE|CE{Z zLa|RZJ<%87ZKWLd=kB@q-vN}Gx3b-PByo0Nw9JB^Y?bsjL;{(EFq1?~aS-Tm zRx$ej%yZ3iZ;@{|Cu@+W=Q(rHDvncmmOBERvRspPi4ce5^~?D)(Ar>78R^qrz1BnQ zVkz>40<_e>wjq-i1gL7SbP}qL02eGIhksy43Q{~q>vt<>U}B{HayaTlDC5d?lz~{z zU@QRGMt)M+RKbiQKqrb^HhHw`CwzX1=9_#dQA#zY%TVs?C5d}(F2Ym0!V|5{8(szk zP`deit#)U`7__{RX1L|?)8d~N#1voQcYX#z(<-Tk>a9tEVg$?@Jsp?-NW_^6NyY8vI<8K1-C|BNsZZW@1^iMm ztV-1BT6R50R>lEnP4BIWU9;US%3l+Cv;P2hpRqD;_{XRg5T;!w`e_(nmJcRlR_JtVG>f3D)m5ACtMtpdM$N!nw|X{td^^N(!qVrF`ykD*!XIZwU3_ikG{m=8 zYZyFUa&QP7h#K@j4iCVmwts0s&-2@SF4@Z~D;T0dr1gt%FZ+~>X$%l|ZKie)ll1(1 zS@Ogp6xJbXwOevH=OM>$FsC*E>TQe;Y=|ucpi^sc8?w)?1QitQ-08vL)uPxuJ6-;? ztHqO!*l?XU7o=o$u0+~Au9;*DR0H_p{m{}t8%%;a*2^CV}pBLRa^fSg&_sU3d>jyLO_5vJ!P#1q}c7C|jh ztiVd?xV7V(LO$=Vo%s*gcCMGpcZKyz6qWNZBTc!KjpC+9a@8r18>VhpTf7ekK`LGP z3eZLqfWqA(ffVhcVa=3JPyH;)9H=_B+PHB0R4?eDbAbMEPhBSP^Z?M8oC}*vRUCWV zVmW)}3;Os%F?!AQCx(9b=9yJ!A=jUmcS%mOvkyP}O%Bw>0wgIhh+t>^0HMpg5xI2L zXsm|BGf?eX#Q5PE8o-<_58U4xSIGG!W!GB2Vk!*_O2~`*Yaw~-rc_u{hz+#S%uvZlsU}BrlV5 z+{8-#yvjz3$cUo73cuzamY8&#Rn^~vm4j+9HPzw~;UpH!t2Ub0tVn5DL&kl^4d|J# zHDu=csTmWjHN3rN5R-4*bu@7O#WUU#&gk=`>)#|?bMo_`aI+rWhg+RrAOiZ(p)ywB zm5~sTSs+%v(f-%7>)%wYi%%l@>lb|KN=w`g)q(fh=(A4KPvJio1C-mgKaGpL!Rcng z?*35fL-{~k6qREuaLqxl?N*$+rhLu?jPPmh72Oh6M!k@ph6@FniToNQhE3yQXrcjO zY%c=q8X)2&PQRG8uUY(aPjt6slX@`cbIo4sO7a^Hm1NYX{IYTte!mRqoC6^@E z))gi?gOD1jRaoYEb`FVescbm-q&(&?Q@8C_9XFu(&^V2z&PZ0yg^}KX>3~gs6}DVE zVjFaJ!h5%BPUFpCi9&?htRhmOGHo3LdeF+42vjv&9cl`c4Cqnq1CTK?tp_SY<1-xt z<-N~Lg72Xpduu_WZVbqKja2khZ>SibXr#$!IJi!}%Xd-e$b7j=B?N2S;ls?T4wgQ@ zJ41M^pphlzmAgWP1s#Jh~;c=qr_hOwGBWjmh^k5ukayVI|L*@;0>&b@t z*3=LFE@+}@S&jJPnGryoySd(KLy((8U2sklgr8L`@pCHg@*)&Tx4#yW)|x@e@G%jViQSGd{v zo@+sED79d`?pgynzg(~^VsmZZP1Q>lRL)8mFHw?}G=>Py9a!=32=+_X6fOZO!?W*g zh*UGpn|%R^47G}-KlNc;et8$v`?wzN<4$PsQgI z-)^~(8@umqOMhC#t#CZP-xuDTm1!9J@Th8fn0IImBWe{oiGn8ib$Tj>$aF6j69#Nd z+hxAs9q&&bu*9-o&Qv5Mc|XDLE1pK5=caEu7r7`&T`gT${P3pHPoTH4f6(M37}l^J znqXmEgB6}4=f~KXsZb(osJUz}d_gUG9RRL8d6%DEr*AhXA^f$J|05yz3+$=L&#m)EBaNe8QT-4}wDMcjYD}R5G&6I2AHshr*1(ckQ zszxl3CM4wEVyG!8=^Al#xj$RP6R|24T9&z1j?HFO^ihzFKGBM&{~A4X9B_756_&o` zDeL!V_ftY^TC2RKE20e!W-3if<&s z?CTbSat`acR%P4fMIMd&KaR^8d$0s3oNvV(awm|_NFxPXDM7s;O(XlpX#1wv-$UUd z<2nu;)NQuftKIOR!^;GgoRu{Q&Mn!c0&5guWmYNjut)Q&7jL#hbI1-KI~og#LL2 zek&QgPR~2Lm&TI1(3|Tqj%~X> z+W?*eQ479=2r+B*AQN;56GnYKI_K1wPa2Z!>>Avc0X7nlh z#I^e4U3Af_>=;I48k#G^GF5crjMNuc)o(c?iW?=L%2nZ%>A*H*iFkqHWEzRotL04qh24n_*==L;BPf z&s!nCDG{D|>GJ*xR}oMhOzQjM)JLhy*{wukUmm73oTr*ZmDJKCFOT2Kq3KiNw62{g`dwkhUpx5tP$ATZt8Ce(wQ2IT^}uct+tUVT z_4AzN66)GUt>5j+0QE5~R#`~IO_4RE*{d@X(djlfQ;ftz7Uiz5MkH~QymkH?v7N+| z(A8dj!-l01&*itjJSB=|N!AtnJ2H2EmyV!L@65=2QdYTEYa`AZinl>jlb5xC7VMPb zhLu=8X$1^`dTtKPxM=qoGITLsDX4A#N3dUnNz5XIbk9&irxOQBgN88=AFLI z;OQ+cZStdUm!&;qD`iR_*SZ=;@of+Xe+p1Vh`*7t+SpZcasPghLoR2#U00nko#}b5Q#^GsdD|wsk=asj;o}$y;QbQfM!bB9VHs#}_4+GHEwRA*euxQF?9Q-FPP zZ*%WnQ%^_o29+UD7f{gvkZQA5QD`r4G$vR@5*6EWu?P$nj26l`5Y_mF_tmUfcf6u> zqW1MFB+-FPU8I5f;Si^3^a;`H0IE&_mZu|1^*9Wnkg^mJQROff1tRYZzZQ@Qoqh96 z(e(ywfx@6_5N>Wp(dGD$E+=;liwmlRTQ_mV$v*9=__qT0xV75c80!XZc zA32k}Tgg@?lRQ2k1IlhYjDuKx)w7}CO6NQj?s7+NlJhA(Evk+%K5+Ny0dv%qpLfN z^w{6s8ALNamT1FYbp{%xzg~0t?^0P_O_@PU|3usNy*`%d>;uy41ji&Z| zu6}G&0yXipD$B~$G3adB%I329#e;f!ZkN9H9t)JL$ysF;$U{9S`|(oIzrnl`0_A8f zOL4^tt|3I2T~41m;Qsw$TgYN*Q08CB4|3V;$UVc1+;4pgVtW1rr_y|6=i#C%wc;gs zre)mjr{1A)`unEbhb7tN4TU{($?N7N8}`&azzK`-3oZLUc_w&m+se&Pj2gPp<}>2enxHHpXjFNCf5@~HGK1Luao_TuCFbZ~!x#H}p2*f-&ye)eA_ z)of~%Sz=$#Jup}AdJ%7?47OWBm4;!4tm{Q5|~)+mw2~Sq1h! zg`q3VTXGzAU#3*@*1q2r4YQnb{Kf;bOby_*Bo6kqSY>c8$>$+>B@37FRQvQTgkMtl zE=H=4wG6?%(;B+gf?UP%A!^**xY>COsn}=-6*JAW&v9uar!NLoXtyeHHJn{-;`{d^ ziMA|B#t{`Ge26B>^iX*fC1F^Nmk8N2G(>pLCHZbBx&Q6#tF;JWt%A1Syy}{@?%cL+ zfWqGN$46ix64)b**n`$wK*m={0GOR)N`=O5t6tMBr5I>aBU87r#=&(cI_@W4P$X zdh^@LD1fpv76qD5j?KS2xIeg8Z2x3%$}M?5zC=BDrGxWC2!)L24a>#F^E%nMqMVF> zdCqx?UMneth*!}rY$2q!e@tek?iHKg%KJ9Gh;)=(tw;9Euax|pL41%U&{JI&5Iz8) zM;;*_QONL z^C5?le^)&B!{i~=eW1(gR@+y zUT(DZbIz;ZUQvYaBFk_>`fK14(WNj}e}p%@S26CG*P!YirpqJsP_b=*WRPc~m)!mX zD~Xka`VaP<$%398;|wk;m;Gf2Ce3tYW-m%^Jw2se@i5N>xgL z00ANN=1LEt7&-)Gflva3-iz*v^d15sp(!O0Isrn5RcavgP(pKs08*7AMbv%s&U?;# z&bRaJoS8F|c_v@-A@j`qpLy>4_q#5?q_n1r8kajZl0F8e(S)&C_Q`-{d-gs=9SdK4 zT(MQu+pEtb)6-p`?@o$u_cLfsjxzZV|Nd1s^qfcG=)qI9B15oIx4iYHKEk8@AnHKo z4>7z8+6#B(irq6^+K)wvm&zdKBJ~gU_hJ zrDU&XyU5JCGnTJF-K7JO3>iM2hXSJk;Qx!`!RZ*B;ta3E8)RF%sd560skUV~zk-_f z{>kR?_vk2zl;Vz3U%<9?ONI~$hvaT{HmA2BVeax|&meSQF0k{T~@Ta2j%hd`C%vh19>c~}HO z9Fq5bj(|gqGV>;ew#arE}R>tE4 z7eBZM%8-MO<-5gRR(7Rl=HgwZt*g9N`Qu#|3U&PE5Q|;WKqIoS>Gv+`zkX42$Wz^o z(p)9w_|V#Aq4zp_SxVJBnVV|OS>m11hs(9<6u=BW9SykjH%s?WQ&g+L|hAR&^1T8X@oVTOxll76Y>X5mLucXZzASrkHKhFeL$|TH0@R>YQvoI?QWBDtmF3%EbN0CJzbl#E|5Nhkk;J7NMLb49 z2ri+ZD_7(^@3<_Hv{F`v+d3QHK-*1vv{Nypa=pZ`XXVO56?9#sK7~lm|3qGdZ5<&H zr7)s?lnR4?$SJRSKi|=At;V)RQOPQtzrgt5UYv23GrMk_nM8c|veJjZ+E@PQXu}Vn zpMiXT&Ny1AW|L&fgN)&-Gqy6<%qqU@znOvL9f#cyy+2VZ=7<1#jq{>>i@U|QtQ@$UHh}u{z2AumUYF0P z=FzVl>lnOIt14ME$E0DCZ&lg10m5 zw!rwGf+a)M#>R0GdSt!af^U~DD<`@VayFo|2LY_*O`9eL!aClv&@2|ocZFe0O=3tj z*BG=xUaY~L&T)cL}ib=*I*T!D}#V37ODXbHymBDq~~GbBx} zb~r?tq<@!fZ*HgO=jCsNm+QZQ`Blfp9k?BUl0jH;u30)1z?1#j%cvE=BN&l7Jm2j( zO)a)~MDMl8v>i>!=zCMw?hwkGV&gp3rX;v9O(UANXp+l#BZNq4o3Q{M8>K3Oy9ajS z(qv5-!~%@TmrLS2wkyVMppR5rvq(B4D_LAhX|@K4JhwT@9WhDsT*Jlf6E*jo8cen# zPXXwm+x(;T^Ih|=PNK0nuJbnc4bU@uH(leTr@>m^3ivrM1bnWvy}qm>dc*m^S(&Mx9t zf6#nE9`G|cRr7vOQT)_^yJQB~JiK85_$s|O#lw#9ZGr_!P)U>k5mD^*83y`8(DFF8 zs^jq{amn?xYF4`M!FiLM(lgg-;f6&ZQKy!m-MOqxE9dZQOV?=gK}3gv1}Z>f#0J9K z?toiAwLN-Zi%erhOclEKga2F7c5}w1_ge93>b#@yg7uDA7GoA?s&~@@%nbzmk>Ydt zSai^V=b*AEHIBo#a&GB`Hvsic-RE)N_;}A)SIVrnoijzjRLL3L=*M)+IlGz&4hoRu zun!ZHo3LCaZSQB_s~Vb{?;3^?{P^6Pv+$uuf<95&tAineyRhAsC}lW~dX~r z<8y6uq?au0S=6*EIh)Q>eze|^5kqUTjwsX}ZN9X3-^um6ElheZEzhgA)6PZzfpOo- z5t4gbT5TLBCLM1jH#X<)Wj)TDc1u`J*$<{9>#6aAwaA?we5pJBVo5Pg=13QxXE{C; zBR!;24QuEd&Gg|DT{Pdi?r4rdifoiuwbWOw)<4;Y51dMePk2b{a8uyJj}Q@3jl1b% zBWe}p^8S^DMNO6!!tD0qsuDDknbpwE=32;8A@PRGX-^BT&`>)8LdE78opU72v&7MM zI3A}Mz;AEeJOE|GBQV~ma{SyPtrW?hI!u0`m9qIQLs@+$x^8h^EJQ6!>lB0+Tj5XM z%8VBqM#(t`$-nlWXUp3xdwYG$mN}h0mYw7Cz2Ne&LwrHV=a$vHY;_XPh(G}!&N|IJr>H+ z!+~ANhlx{wrB!wQ4+o87eeP;(Jh^tLX5v{62`-|jQVOd)T|82qXad!lkB6bx=puHt zBB*stsPv?}vJtsK^|nKqQ4uXRgi)Sg!tw~W!}ivva*UvwB~|)+F}Ijx^VlLxPOg$G zM&}=?7H+hr@u;^X?wP4hgr(0!(W15X`-13HJ-;(sFR270L?tgC*8XuRrL7x92m$Dt~ z%N4Ae)mgayY@Hk}`e?E>6QhwWA_~m*Pn|BcR}yaC@kJ7!g{a@0iO~q+OSIEO(JsjM z?>S*tZn3{*XCd3i6jg{|%J3%0i|G=Z5~SmOsd*N7|G_bx9eOtlLLk1i%Dz#L#)Z0p z2(wZpJztO+Lue*OpY`bI=^nyc(|?`gCz$+X^{dy|#xXIRwD&pHc0(E=F+vLWU!dTr zx6t6jnP03cA?!t3#`#OKRN4od1OP9yx5UtTQ15Za``)mt)4-^FinWwXEy-xa8|Wi> zRUP`fDDdKj*aEckF!vH^lv@Unf}tbuxe>WNukS_YR!@3AA+bh1*+${K0^T;blO&`UMt)OE6cq50QLX;## zEw^nTJ^Nz1?i4K#mb1-XRJM?l4%y~GxyyQ^JmPKKI4LzQy0+9Ok`{96&9U%0;^5?7 zF4vqHtD(5&RlMQ2u$+e#xXuD>Cb{8Ef63bshgOWKRIN?@P8dfoqDs*viokX*Gob~- zguXnVXDjk*iIK)B7=&>zJ)PJ)w0mL}n0n*uLpitatI+8{=!s;vwKZZ7F#z}8VuLLi zJSFD~BQd<@i{W^)Agvb@*vctCMS+@E?#k+7OsH6(@JJHRr_6_Ti$h8nvh{AAf^Ipx zrK1%N9l?f`0ZxVd2B03ZaLiRnR|{ubVUU61yuNZb1FQJ*){yzdJpJhB@LD#`GWNxt z$}|(UBZbODxKj7?-TWUfZ**&{hO|aMyq^`fHYu0HV?x#r**}44&B|p128VxKtt^!v zeV!Z&;ZZUmL4&}{QvT~6@gw#X!la^F&KQjY1k8iV2MWV%Y`?q2`hE|7djq~5lEAN>#)RFvk1Q&_y$SGZm>j(S z+G>0jWJ%*s5T(lJCB(W-Z6kN2d>PvD)X+csFy2`L<>l@A-xzIPy8yQtg>sEh@cq7? z_6|vgtz@U#8kcHKbW2UFNaR&MA`b9Ef?T=_4#&P*hgOn)N1SDR;0$ zqnDe~#QNj4*D<|L{|5nHH6NaqBQ@tI)5wK_G^J8QKk}U512FI0R;$=efsi0HCWq(N zY5E8E2R$sM^FBMQj=rbm|5{IfwrlUnaT`R0vkdzN-O6mKQ%iF9R3Mj;V-Vk%Spq>^ zDfckcc}Dkjb6K6Vn4tM| z{5f0g02>N@0zDMIJg&KW83%0I=& z2#o!@QK`tgA5&)bhw~zt)YPhj&Kuk-j9aZ@dxFl(0ImV#0K*7~;x|!K-t*dP18zA1 zTId;#p7J$UrPG&O^d=rh^)y+9Q7&{lGgf)N7%2Q*I$vQx0&#d>r;|O37_8>ViZ!OT z`9>v1YEf^+CcE5wILn{hD$&1(LYaw_x$`x$(<(2P zzZro)?$+%0aAPvHtUT;`RQw?LmUg#W9A;~rUF@1|AW)k z<`P%8^1^)WiTX$MTXL8c*pTja75NN4vQ`*mEni|7Afqa9IwBq%(7oiH6c(tEkA(3^ z(8|Muw8m_U0zli`5utFNYOipNs;Fm^9W+8zVzUX=K1i^vov7Ld`Vh;BGC(o+!o)7# zzo_3Yoq)|HLt?|g!-WM*SJI&8ewnpl19p|3{>mY7S7$}ZycDc$i8e2KTAjsqZ%i>a z)JkCAofKP1ceLmL{XK8*3d!(4HtCg;o;o*NUUGAfpPl)p)r zN<4F2d??O2x_9IdWeX0sI#bSY8Xux`<;(nqtQ3@ z79mAq9=Qe`gCT0KiUicL=)#)79!qi0$# z+n>&F+FvPLpMgjn?UM1DDAnIqQv2MDPdfh zW3Eq7n%0}4rQXJIG5H;Wz5*DyZD;-_pTs#;?rm=GpP1|J?!b184$m0xhC?QzG(+Rw zebr{t?^(}&e7iWenCD27!O;9D6OG&31%+~9WtnqMU$z| znpaCrcJ>c;`AUjbns0}HZtlEZWB#q!vdLnswt_!kUcj7!BHr4mY1@n9f;(v`sa8dT zAKuJ|u`cQjbnxbQ^HAjIX7A)#jFjL3Y1XCzJWzgXF`?jT-uJWLGnx! z<{z3%X(;>-`xsUPieonm3qkk_VI5Ma*}ZrZz5}^2nm}d#M&B8`5G-(U)-`K`D3lE+n=a|^A~ycJ z!hcxPE7HF)oWjkL%~_{!93^pxDRpkmykz~Nkevcip^&$@0u+5P`zEt1E;JuM5o+}p zi9EXQOsZ4@aB)Kz;&VT(kWVsxsJ9u1{C5pt`KZ>Bdg0% zdC=e0^zIa!Tyv0g)gGJ}s8Lb)v|O|`>ze!+i}>FPMLok%Lo@u1>4?x`FI0;kvQ~H6 zD^XfUow{v}M%ApVRZcVvszOKZ=slXwp~8GWqH~&1LeRCXMa&+)Yv$U%V#=3}hPWg* zGrED|1XUXJ;y!j63t)pe9I0{r(8@jl4BC(~-@Q3iMC%do>&w(5rvtrpR688tE7-Ap zq47Bl$623RUw-6n++SPOBR4s51G0W3p(UUrvT>Ab-t+T)YE1rN{sWwR(49Wuh%_nh zv3D8VR-FwOfO~!PA!+g;i;j{)ZgK8dmjT^N%tT;p_h>cGvAjK&$>%W#7RI$c20QF( z+|U&Db$+eT96`U+Ahan z&*lNWEs<3kMlZQF`szmE)8D-;i@5rOvt#$CMa{hQEpRMV;*9--7k@3 zSKcIVM2l|s@mAJyrL=G_iP2cZ_U~3KQY_+EJO7^6*oe+kgrX`OnB~#@tjWyX&6!I3 zQ`1qfnE>Va3wzcM^BJd56YASl%lD1xtGAWRd2`KE&8trP%w-N&d;@XsPx-ta?R+Jfx)__%GV@GG_mjjjknAgIl!WK(AUx zvv|$ECDg&>as97Bnu`!#GopZ_3GLMabuxCDd>l&}s+W&Zcw03Tc%LXH#1uGUM&@#W ziASYcm+EhZPeXDG5Th}v1x!rbKc{&uVggHo(A~LsgmQN$LmPk3GWqe-dYKFq*P()Fl<*el; zj?_ZcUkH`B82&N-`H<_!ufK|Eh7Xq5Z}=f()y{X8jmoKPdiZ@ohSNu&ABH*PKpmEy`Yr zYLrLG#(w67tTy`6@4p?T!YaYvU;TH*DaiQw2kVgQ?aBOPfe=TKzGy!l>mm@$bIaHG zbF8CMns3*k#4NA{a0`-EI`yo2TfcWFXEvTE)s24JcVWqklfZf8{P@Gmp@O(zA0-5a z4PY`-W6okocd+xJy_6_XO(rIVi%I4^_fmLiLBfXQCRhR+)jC-0+{QkX4U10X-1M;F zZp)g9ynO@4`vAUtagf6cd#BgYUw-;aEk1z&{t~?rj<~HNZX;hZPDT(VaL&KN&GSQb zq}+NA7MIiMz-G{lusIpF2j6NN@Ac9wK-o1Hhfh7lr8>AphL`mt(iIHZ0eyDGrAPhl zG@z(&;1PCcQ&_0w`3^MC{qNyfczGC6v8@BOY{o8_J(oGFKp8Ki|1cm2*hIw}}%^d`8OwJe2gZG$fevpc7|i>(%ag5}K6BPUm) z9XS^G{Y?(&J+R$`-AhayQNf_R?n^pyJ4UCKGFLy-nu*DtW8<{sdX(2e^J(C~SMoS{ z`X_Bo)%N{5x%e+&jVgQ2@C4>OkhAzQ6Dj*ahb9S&EL=$Pv6lb- zbAX5E`{m89$Zx&kgp?TTq{ zP?w$$a#R-<9Qo`@jm(>_N^Z!!o1}H(r#ZbNX4kg1?3cuQ&jiqY8nKZq*5(~r z>eBuq$T@9=u2Vge=nse%;KdOi@6mj!97+m>Jk8h3jRTNH_$95nOZ<1VeIcRQc5bIy zO~Y1w;<>E`b!3a<@rV?y>!q6+yzogjT!yXL$EVbMIfIlWLp0gq$(HZ!AmwD722-kz z7faErk_Gk&pvF-bdL!-J%g!(`Xm4I4+cPuQHp>I>-pP(>%)(yf#gzU%bSdK4?)31+ zh{K?;YjgPixa#$haw)4ql9>L1v8JLV@+J!Q@qjK`5Qf9?0IxdeJYY(}3M(zcntW>t zhmJJXCOFTzg2$Adc=^oe1L?aQHwCPy;$9sYN)?=FU!zd%7!I{@&BY+|9nZI3j$h;A zn(t(Db`NL4WN?)Y|90~J{$7qRR_F#4%_nx#xXTyRx3k|^>S|c~<@=9O`yTzq@(xI) zZLW@6F+$PS!luR*Qd$=a1#Fhs8tnkjHb=wP(RhahNRe*tiV{Uq%M`cw2pMc(F!o_hBGO` zJ@;biobd?=JrG`=7%J6^ygIlM3Lgqz8xAuJ$la=yZdiPfS-Gpcgwmx0Oevs;?!S@cw(H}4NTc5}jKNPw&@VGNV=;a^&*!TYP zk*9UUwX|-%szR^AlO>3+Q9ctoFpCWhRnWp?CJ4s3Vq=;xr{a99ef&pZ@j%59^QANWQw>E zLg2wOa*TY(fkG8#`DWds(^H3FK*h#0Z(eF0*Ud0#aMLtZE4 zqDy7F+zmze|0xWct=YkH$HB6wL;JVcUogpZihLH?GGkzyo24Olh2Fc|RhdxP$sW+f z-qT%ETK?u_95@gDq11oeT0#Kh1o&@K96WUle8&B`RVS- z6b(tm;tX+-Nnfe?(xict%|}G>iLnKfE?AMxh}3jIixfrcMRKUf?Fv7cASL6`nZ|(h z1D~pI0sn~Z>9s#q?j%YLcguU`^lbHPI;(5Gt2$~^0pud}H~`HE##6$Jf!r-4&-Mdy zo_W$h1!MWx$hiC3i^EsoWgcof^_Q;cBs(4BFf5S6g)*U|&i0k{iOgGZSCNe&RBAjP zGk3~R$Rv5)miDDGJ$%mH*PlK*wK1Xbay9<^IU*MiM?UX#@-!hQE)}tU)E}Z(5ZinQ z?QQgL=%R(HfaTbLkVNE!^ z9}8l_NvLITF96>E^q9xGEq~IBw>jra&^;iE^`%~rsj`)+Myu0`+k>PhVh(1m;OUd% z-zbfp{GAHph&d^dF==|CjH3OrgQ0OFS0(lFQaV5?m;&OZeSFJD9Pq(p-K(7O7RXh9 zGovW5tZlrca)J0}<7Jas=K++Y2-Mjz%Sq;?p1GwKhDy^ApG74jqxcT{C+q}j>Winci3;w*^!#Nlol zU@#ERtE)U&A__1CdIem4Ei@p7D5FX8#x2h9{1eofsttYc4L2z8*8rVLu}gCC#>6lG zh`nj32o+n5XsH=P`3+B}dx;n(Q7bI7iq$@Tac%0Rvgj8g+U^}RE=jj^H5cXJmiryV zDVrbGzrc0<8N|EFl-uT+aDM>6AgnF1>O?mX zHmPMhm>1U z7J$f?u9XXkZ*2P`Fak9}(@48QLw8*uC#}nIyw}scW_$8B z9|)S=XCq?0(NA(_js6Z2d)IRkI^eP#al=9X4jv%HLPRR^Ceoszo9vNti|H9Qfu1V6 zauTjp2e_rh{y9dCJ;Y1h+fCBzUG8MjmI#uiG9RFpO@F`DaVMemx4G|2py{xd+$H1c z?N2sonlG=RhpmKNM^HU6;(P~09Zx&WSwrh(lop}Gpm_3?e^s{D$++|GL8V^RQ;QX2D`34ZMZEp!!wW5g;3v_dex>N%td&$PV=~Em=}-MIidPZ@r$&v3OlhVXIJz z1dSu;CM(Vm*?dw~LJTyiD>26qWjHhvhp|0BUFOmr{WF7atz>;i%JS{UiWvZHf{GJ( zwU;cQ5qZ8E0XY99`QzVkTMmLSci0R#_7pFE?}O*r-f)P>`@jI9CLw-*70;Brshz;CUMRGt$+C?Xwxy0H6DPi|aXG94!BC~l zK(U9ZAF-=DgO<%igA;3xv%!2qL@i_AVn8G}BIb#DA(oMOT;O)8T$pBOIPFnjpq`9D z>1tcgdfCO?fOMJh;0qY;&lkT`CHrxibY1MZa`lh51-o`QzUof+@UJQCvmQp>#~v(cxTg41ZJAW{2!*{ zk+&?SL~12XM^Gl%-wcY*0T{mZ$u$328$}LfjaP&h5rS{~5lwKH_IOb$GJY{V;bw96 znvpgw*8UC}0t4KHJnbK?JvsU;r~AEHqqD-dRRIaJL63xN!ejkx`>hl%N9~5Tg@e8` z?h{PD87T(Ow7A}?Kc5|4HGz$?CS|7}W#Xqdc&Js?v1vYifXq=lyAKr1sD%B!itMA$ zAPMa`{o2%|s}32-P;b_}wewK%`1$w$I5b*a&CSRD0`}Q7*=`_D&FC0_25JdOSBpUR zIYmB;w#tovgJdD>Le=ZqM5Pdp^Y6<4+_q23g^uv;jK&Jd2<7vb2yIcINUaP4MtTlY zy=4@p`TJ<2&k!UaX@Q{Q=-aPW>F*}PiP2nPv)g zLVSbhZ~C(K`DA$#rXS+Y_ zbANn`^VIz_r_1_@OLNIW`~2rc+jB|VfNL*Y6Wo*n0x)y>DGop4qdc6A6HQNM_}(nH z`|2B6I*O9ZM7uDMr+c*-AKa2XTf{`%%1@OZdr8`gA3++m9)^l0R=x;A7vTLkC@~oR z(h%K`ARr{GTuj#is=7Ck?-^ek>ie!4(bNSMEjTEg`=fJcZE5HQ9L0IvE;GSSdu;HW z=)l#^R<5tdaKXiX4B!9YzMS!Rw%a%okdtnd*CgM09qx}axi-~bBfbO+rVXhW2GFVz zz(9n=n8uj|UR*6`)9>Z?|E{>LExxV%XFy$Yd^*K$lH~K^1@{IXBu&zx3S^coD9(sN z0+$(lpJJ}&qOod>W-Mn`mSEp#nrH4`(Dw}mZUy5eqj$!dng(j$xE(#3z_&|=Fh_z8 zzb^*bcX-1{Qda+V`sGdOSVqEa6HE^kRS^{e z5Dt##iA(7m)hJ1%es_YQ>Ao?d1R%eOvh96TK6okl+YWada2a z!%V=YjS9`4p}j@%**!~{B+Y!do61*HSP8N($iDMF4O*~i+;(X4x^l9m;ly(qeUyYW zH_`grH(Lb8<0GF=Yd_6(jv!~MVmEDD1oBnOkQ0)F?=?K8p64muP{#O5V2nAcMUS57 zVoGEShKg_D2|e&#dP^ihx?$Do^tq;U5{LLW@qQ5@*bWx3%TM2s>M3tN3(GQR6k*{> zLWXwWi5QGQZ{Ck}6&nvj`%d3Y@q!M!nK@ZUxVa7udJSJLkysG&jg;rJ-;Z{%oM0C% zOH4%TT1xLqSG`jtxTRxvMYwSAYMCB;;4x+ zXge{cnefnhrke8<_1)qvdI=YtxcbMTY_=_J!~Ch2QYnH@go78g5o)ak>qqJA&k8B5 zJl^?MWfnDP@+Hfw3`b$g!X(0Z8eHc zrFL~y=BTa~jqO9L&R^UvSffyU9(&$Ma! zAVS)0K021Z`)?Kp!6k>pDZFCxr?ML}q}qpVNiUWUxJala%ENA(2b*B$c1WN1LoQ?{ps(J7-Ras4UrazY!rBG^=B)ODysF^Vw z{OEXHcgCUrthvO+^%;1@zOelC;V;{#rC--FLj;m zD9#Z9m4$I-yn?B|1Q`|_;SohBxv^-m{Cunv-a!H2Xz{O?V$pf9jk>tQ(B*2m1%Kn_ z*k|yJ$-U|kub^mhaW&!SS>#83ukW0rJBM84g<&q^{Y z*Q50Y)ppMI+4?%PIjmDSi;v&N;y0`bh^WE;SvOsOjYe;a`tnfo*0782c;jLXm`Mvp z8`HWLwan70#r;H2OVaJQVj?@Kv_ypfi zz?^Sp8c%xp+79+l7;kDr(RgxV}xe9tt=?dc?hamwsOYf#U`_@w~fp;%L}&Ktu<>pF4e>LnV7Hyzvjo0j^mNHjR#-#hTlAV!A#h=&w_! zT2qSz28=KqII_j#%ghJZ!Hu(o-%(6TiVNb}S~+8U(T0?0!z9toVOikvys`R|H8nf8 z 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 H?^FK+jj=-) literal 0 HcmV?d00001 diff --git a/docs/multitenant/images/KubectlGetSchema2.jpg b/docs/multitenant/images/KubectlGetSchema2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9df197c85b9375e25caf2cfb387c4c94814f6961 GIT binary patch literal 187365 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 *BK>+4 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

LejKVm?;e7+?MM_I}TgZT|Q@L3Z`$ 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 literal 0 HcmV?d00001 diff --git a/docs/multitenant/lrest-based/images/UsecaseSchema.jpg b/docs/multitenant/images/old_testcase_wf.jpg similarity index 100% rename from docs/multitenant/lrest-based/images/UsecaseSchema.jpg rename to docs/multitenant/images/old_testcase_wf.jpg diff --git a/docs/multitenant/images/plsqlmap.jpg b/docs/multitenant/images/plsqlmap.jpg new file mode 100644 index 0000000000000000000000000000000000000000..cef79daaf49fabd4e7cd1dda7287ad74ea482606 GIT binary patch literal 94000 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 bOrYnlOs3tW#6WU-*18o><53YD->3c;8?mX4 literal 0 HcmV?d00001 diff --git a/docs/multitenant/images/usecaseschema.jpg b/docs/multitenant/images/usecaseschema.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0c9b81102f3b07c5f3b6f6d2ee460b2acb0cf0e7 GIT binary patch literal 124714 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 z%0D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

qrX5I8PDvpztUaQ|W^7vr?7D;tTxF|< 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%0A4yEMXj{vq9FiaG{e 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 literal 0 HcmV?d00001 diff --git a/docs/multitenant/lrest-based/README.md b/docs/multitenant/lrest-based/README.md deleted file mode 100644 index d9b72a9d..00000000 --- a/docs/multitenant/lrest-based/README.md +++ /dev/null @@ -1,501 +0,0 @@ - - - -# LREST BASED MULTITENANT CONTROLLERS FOR PDB LIFE CYCLE MANAGEMENT - - -- [LREST BASED MULTITENANT CONTROLLERS FOR PDB LIFE CYCLE MANAGEMENT](#lrest-based-multitenant-controllers-for-pdb-life-cycle-management) - - [STEP BY STEP CONFIGURATION](#step-by-step-configuration) - - [Multiple namespace setup](#multiple-namespace-setup) - - [Create the operator](#create-the-operator) - - [Container database setup](#container-database-setup) - - [Apply rolebinding](#apply-rolebinding) - - [Certificate and credentials](#certificate-and-credentials) - - [Private key 🔑](#private-key-) - - [Public Key 🔑](#public-key-) - - [Certificates](#certificates) - - [Create secrets for certificate and keys](#create-secrets-for-certificate-and-keys) - - [Create secrets with encrypted password](#create-secrets-with-encrypted-password) - - [Create lrest pod](#create-lrest-pod) - - [Create PDB](#create-pdb) - - [pdb config map ](#pdb-config-map) - - [Open PDB](#open-pdb) - - [Close PDB](#close-pdb) - - [Clone PDB](#clone-pdb) - - [Unplug PDB](#unplug-pdb) - - [Plug PDB](#plug-pdb) - - [Delete PDB](#delete-pdb) - - [Map PDB](#map-pdb) - - - - - -**Lrpdb** and **lrest** are two controllers for PDB lifecycle management (**PDBLCM**). They rely on a dedicated REST server (Lite Rest Server) Container image to run. The `lrest` controller is available on the Oracle Container Registry (OCR). The container database can be anywhere (on-premises or in the Cloud). - -![generaleschema](./images/Generalschema2.jpg) - -## STEP BY STEP CONFIGURATION -Complete each of these steps in the order given. - -### Multiple namespace setup - -Before proceeding with controllers setup, ensure that the Oracle Database Operator (operator) is configured to work with multiple namespaces, as specified in the [README](../../../README.md). -In this document, each controller is running in a dedicated namespace: lrest controller is running in **cdbnamespace** , lrpdb controller is running in **pdbnamespace**. The [usecase directory](./usecase/README.md) contains all the files reported in this document. - -Configure the **WACTH_NAMESPACE** list of the operator `yaml` file - -```bash -sed -i 's/value: ""/value: "oracle-database-operator-system,pdbnamespace,cdbnamespace"/g' oracle-database-operator.yaml -``` - -### Create the operator -Run the following command: - -```bash -kubectl apply -f oracle-database-operator.yaml -``` -Check the controller: -```bash -kubectl get pods -n oracle-database-operator-system -NAME READY STATUS RESTARTS AGE -oracle-database-operator-controller-manager-796c9b87df-6xn7c 1/1 Running 0 22m -oracle-database-operator-controller-manager-796c9b87df-sckf2 1/1 Running 0 22m -oracle-database-operator-controller-manager-796c9b87df-t4qns 1/1 Running 0 22m -``` -### Container database setup - -On the container database, use the following commands to configure the account for PDB administration: - -```sql -alter session set "_oracle_script"=true; -create user identified by ; -grant create session to container=all; -grant sysdba to container=all; -``` - - -### Apply rolebinding - - -Apply the following files : [`pdbnamespace_binding.yaml`](./usecase/pdbnamespace_binding.yaml) [`cdbnamespace_binding.yaml`](./usecase/cdbnamespace_binding.yaml) -```bash -kubectl apply -f pdbnamespace_binding.yaml -kubectl apply -f cdbnamespace_binding.yaml -``` - -### Certificate and credentials -You must create the public key, private key, certificates and Kubernetes Secrets for the security configuration. - -#### Private key 🔑 -> Note: Only private key **PCKS8** format is supported by LREST controllers. Before you start configuration, ensure that you can use it. If you are using [`openssl3`](https://docs.openssl.org/master/) then `pcks8` is generated by default. If it is not already generated, then use the following command to create a `pcks8` private key - -```bash -openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_keygen_pubexp:65537 -out private.key -``` -#### Public Key 🔑 -Create the public key. - -```bash -/usr/bin/openssl rsa -in private.key -outform PEM -pubout -out public.pem -``` -#### Certificates -Create certificates. -```bash -openssl req -new -x509 -days 365 -key private.key -subj "/C=CN/ST=GD/L=SZ/O=oracle, Inc./CN=oracle Root CA" -out ca.crt -``` -```bash -openssl req -newkey rsa:2048 -nodes -keyout tls.key -subj "/C=CN/ST=GD/L=SZ/O=oracle, Inc./CN=cdb-dev-lrest.cdbnamespace" -out server.csr -``` -```bash -/usr/bin/echo "subjectAltName=DNS:cdb-dev-lrest.cdbnamespace,DNS:www.example.com" > extfile.txt -``` -```bash -/usr/bin/openssl x509 -req -extfile extfile.txt -days 365 -in server.csr -CA ca.crt -CAkey private.key -CAcreateserial -out tls.crt -``` - -### Create secrets for certificate and keys -Create the Kubernetes Secrets. - -```bash -kubectl create secret tls db-tls --key="tls.key" --cert="tls.crt" -n oracle-database-operator-system -kubectl create secret generic db-ca --from-file="ca.crt" -n oracle-database-operator-system -kubectl create secret tls db-tls --key="tls.key" --cert="tls.crt" -n cdbnamespace -kubectl create secret generic db-ca --from-file="ca.crt" -n cdbnamespace -kubectl create secret tls db-tls --key="tls.key" --cert="tls.crt" -n pdbnamespace -kubectl create secret generic db-ca --from-file="ca.crt" -n pdbnamespace -``` - -```bash -kubectl create secret tls prvkey --key="private.key" --cert=ca.crt -n cdbnamespace -kubectl create secret generic pubkey --from-file=publicKey=public.pem -n cdbnamespace -kubectl create secret generic prvkey --from-file=privateKey="private.key" -n pdbnamespace -``` - -### Create secrets with encrypted password - -In this example, we create the Secrets for each credential (username and password) - -| secret usr | secrets pwd | credential description | -| -----------|-------------|-----------------------------------------------------------| -| **dbuser** |**dbpass** | the administrative user created on the container database | -| **wbuser** |**wbpass** | the user for https authentication | -| **pdbusr** |**pdbpwd** | the administrative user of the pdbs | - - -```bash -echo "[ADMINUSERNAME]" > dbuser.txt -echo "[ADMINUSERNAME PASSWORD]" > dbpass.txt -echo "[WEBUSER]" > wbuser.txt -echo "[WEBUSER PASSWORD]" > wbpass.txt -echo "[PDBUSERNAME]" > pdbusr.txt -echo "[PDBUSERNAME PASSWORD]" > pdbpwd.txt - -## Encrypt the credentials -openssl rsautl -encrypt -pubin -inkey public.pem -in dbuser.txt |base64 > e_dbuser.txt -openssl rsautl -encrypt -pubin -inkey public.pem -in dbpass.txt |base64 > e_dbpass.txt -openssl rsautl -encrypt -pubin -inkey public.pem -in wbuser.txt |base64 > e_wbuser.txt -openssl rsautl -encrypt -pubin -inkey public.pem -in wbpass.txt |base64 > e_wbpass.txt -openssl rsautl -encrypt -pubin -inkey public.pem -in pdbusr.txt |base64 > e_pdbusr.txt -openssl rsautl -encrypt -pubin -inkey public.pem -in pdbpwd.txt |base64 > e_pdbpwd.txt - -kubectl create secret generic dbuser --from-file=e_dbuser.txt -n cdbnamespace -kubectl create secret generic dbpass --from-file=e_dbpass.txt -n cdbnamespace -kubectl create secret generic wbuser --from-file=e_wbuser.txt -n cdbnamespace -kubectl create secret generic wbpass --from-file=e_wbpass.txt -n cdbnamespace -kubectl create secret generic wbuser --from-file=e_wbuser.txt -n pdbnamespace -kubectl create secret generic wbpass --from-file=e_wbpass.txt -n pdbnamespace -kubectl create secret generic pdbusr --from-file=e_pdbusr.txt -n pdbnamespace -kubectl create secret generic pdbpwd --from-file=e_pdbpwd.txt -n pdbnamespace - -rm dbuser.txt dbpass.txt wbuser.txt wbpass.txt pdbusr.txt pdbpwd.txt \ - e_dbuser.txt e_dbpass.txt e_wbuser.txt e_wbpass.txt e_pdbusr.txt e_pdbpwd.txt -``` - -### Create lrest pod - -To create the REST pod and monitor its processing, use the `yaml` file [`create_lrest_pod.yaml`](./usecase/create_lrest_pod.yaml) - -Ensure that you update the **lrestImage** with the latest version available on the [Oracle Container Registry (OCR)](https://container-registry.oracle.com/ords/f?p=113:4:104288359787984:::4:P4_REPOSITORY,AI_REPOSITORY,AI_REPOSITORY_NAME,P4_REPOSITORY_NAME,P4_EULA_ID,P4_BUSINESS_AREA_ID:1283,1283,This%20image%20is%20part%20of%20and%20for%20use%20with%20the%20Oracle%20Database%20Operator%20for%20Kubernetes,This%20image%20is%20part%20of%20and%20for%20use%20with%20the%20Oracle%20Database%20Operator%20for%20Kubernetes,1,0&cs=3076h-hg1qX3eJANBcUHBNBCmYWjMvxLkZyTAhDn2e8VR8Gxb_a-I8jZLhf9j6gmnimHwlP_a0OQjX6vjBfSAqQ) - -```bash ---> for amd64 -lrestImage: container-registry.oracle.com/database/operator:lrest-241210-amd64 - ---> for arm64 -lrestImage: container-registry.oracle.com/database/operator:lrest-241210-arm64 -``` - -```bash -kubectl apply -f create_lrest_pod.yaml -``` - -monitor the file processing: - -```bash -kubectl get pods -n cdbnamespace --watch -NAME READY STATUS RESTARTS AGE -cdb-dev-lrest-rs-9gvx2 0/1 Pending 0 0s -cdb-dev-lrest-rs-9gvx2 0/1 Pending 0 0s -cdb-dev-lrest-rs-9gvx2 0/1 ContainerCreating 0 0s -cdb-dev-lrest-rs-9gvx2 1/1 Running 0 2s - -kubectl get lrest -n cdbnamespace -NAME CDB NAME DB SERVER DB PORT TNS STRING REPLICAS STATUS MESSAGE -cdb-dev DB12 (DESCRIPTION=(CONNECT_TIMEOUT=90)(RETRY_COUNT=30)(RETRY_DELAY=10)(TRANSPORT_CONNECT_TIMEOUT=70)(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL=TCP)(HOST=scan12.testrac.com)(PORT=1521)(IP=V4_ONLY))(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL=TCP)(HOST=scan34.testrac.com)(PORT=1521)(IP=V4_ONLY))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=TESTORDS))) 1 Ready -``` - -Check the Pod logs: - -```bash -/usr/local/go/bin/kubectl logs -f `/usr/local/go/bin/kubectl get pods -n cdbnamespace|grep lrest|cut -d ' ' -f 1` -n cdbnamespace -``` - -Output example: - -```text -... -... -2024/09/05 12:44:09 wallet file /opt/oracle/lrest/walletfile exists completed -2024/09/05 12:44:09 call: C.ReadWallet -LENCHECK: 7 11 7 8 -2024/09/05 12:44:09 ===== DUMP INFO ==== -00000000 28 44 45 53 43 52 49 50 54 49 4f 4e 3d 28 43 4f |(DESCRIPTION=(CO| -00000010 4e 4e 45 43 54 5f 54 49 4d 45 4f 55 54 3d 39 30 |NNECT_TIMEOUT=90| -00000020 29 28 52 45 54 52 59 5f 43 4f 55 4e 54 3d 33 30 |)(RETRY_COUNT=30| -00000030 29 28 52 45 54 52 59 5f 44 45 4c 41 59 3d 31 30 |)(RETRY_DELAY=10| -00000040 29 28 54 52 41 4e 53 50 4f 52 54 5f 43 4f 4e 4e |)(TRANSPORT_CONN| -00000050 45 43 54 5f 54 49 4d 45 4f 55 54 3d 37 30 29 28 |ECT_TIMEOUT=70)(| -00000060 4c 4f 41 44 5f 42 41 4c 4c 41 4e 43 45 3d 4f 4e |LOAD_BALLANCE=ON| -00000070 29 28 41 44 44 52 45 53 53 3d 28 50 52 4f 54 4f |)(ADDRESS=(PROTO| -00000080 43 4f 4c 3d 54 43 50 29 28 48 4f 53 54 3d 73 63 |COL=TCP)(HOST=sc| -00000090 61 6e 31 32 2e 74 65 73 74 72 61 63 2e 63 6f 6d |an12.testrac.com| -000000a0 29 28 50 4f 52 54 3d 31 35 32 31 29 28 49 50 3d |)(PORT=1521)(IP=| -000000b0 56 34 5f 4f 4e 4c 59 29 29 28 4c 4f 41 44 5f 42 |V4_ONLY))(LOAD_B| -000000c0 41 4c 4c 41 4e 43 45 3d 4f 4e 29 28 41 44 44 52 |ALLANCE=ON)(ADDR| -000000d0 45 53 53 3d 28 50 52 4f 54 4f 43 4f 4c 3d 54 43 |ESS=(PROTOCOL=TC| -000000e0 50 29 28 48 4f 53 54 3d 73 63 61 6e 33 34 2e 74 |P)(HOST=scan34.t| -000000f0 65 73 74 72 61 63 2e 63 6f 6d 29 28 50 4f 52 54 |estrac.com)(PORT| -00000100 3d 31 35 32 31 29 28 49 50 3d 56 34 5f 4f 4e 4c |=1521)(IP=V4_ONL| -00000110 59 29 29 28 43 4f 4e 4e 45 43 54 5f 44 41 54 41 |Y))(CONNECT_DATA| -00000120 3d 28 53 45 52 56 45 52 3d 44 45 44 49 43 41 54 |=(SERVER=DEDICAT| -00000130 45 44 29 28 53 45 52 56 49 43 45 5f 4e 41 4d 45 |ED)(SERVICE_NAME| -00000140 3d 54 45 53 54 4f 52 44 53 29 29 29 |=TESTORDS)))| -00000000 2f 6f 70 74 2f 6f 72 61 63 6c 65 2f 6c 72 65 73 |/opt/oracle/lres| -00000010 74 2f 77 61 6c 6c 65 74 66 69 6c 65 |t/walletfile| -2024/09/05 12:44:09 Get credential from wallet -7 -8 -2024/09/05 12:44:09 dbuser: restdba webuser :welcome -2024/09/05 12:44:09 Connections Handle -2024/09/05 12:44:09 Working Session Aarry dbhanlde=0x1944120 -2024/09/05 12:44:09 Monitor Session Array dbhanlde=0x1a4af70 -2024/09/05 12:44:09 Open cursors -Parsing sqltext=select inst_id,con_id,open_mode,nvl(restricted,'NONE'),total_size from gv$pdbs where inst_id = SYS_CONTEXT('USERENV','INSTANCE') and name =upper(:b1) -Parsing sqltext=select count(*) from pdb_plug_in_violations where name =:b1 -2024/09/05 12:44:11 Protocol=https -2024/09/05 12:44:11 starting HTTPS/SSL server -2024/09/05 12:44:11 ==== TLS CONFIGURATION === -2024/09/05 12:44:11 srv=0xc000106000 -2024/09/05 12:44:11 cfg=0xc0000a2058 -2024/09/05 12:44:11 mux=0xc0000a2050 -2024/09/05 12:44:11 tls.minversion=771 -2024/09/05 12:44:11 CipherSuites=[49200 49172 157 53] -2024/09/05 12:44:11 cer=/opt/oracle/lrest/certificates/tls.crt -2024/09/05 12:44:11 key=/opt/oracle/lrest/certificates/tls.key -2024/09/05 12:44:11 ========================== -2024/09/05 12:44:11 HTTPS: Listening port=8888 -2024/09/05 12:44:23 call BasicAuth Succeded -2024/09/05 12:44:23 HTTP: [1:0] Invalid credential <-- This message can be ignored - -``` - -**lrest Pod creation** - parameters list -| Name | Dcription | ---------------------------|-------------------------------------------------------------------------------| -|cdbName | Name of the container database (db) | -|lrestImage (DO NOT EDIT) | **container-registry.oracle.com/database/lrest-dboper:latest** use the latest label availble on OCR | -|dbTnsurl | The string of the tns alias to connect to cdb. Attention: remove all white space from string | -|deletePdbCascade | Delete all of the PDBs associated to a CDB resource when the CDB resource is dropped using [imperative approach](https://kubernetes.io/docs/tasks/manage-kubernetes-objects/imperative-command/) | -|cdbAdminUser | Secret: the administrative (admin) user | -|fileNameConversions | Use file name conversion if you are not using ASM | -|cdbAdminPwd | Secret: the admin user password | -|webServerUser | Secret: the HTTPS user | -|webServerPwd | Secret: the HTTPS user password | -|cdbTlsCrt | Secret: the `tls.crt ` | -|cdbPubKey | Secret: the public key | -|cdbPrvKey | Secret: the private key | - - - - -### Create PDB - -To create a pluggable database, apply the yaml file [`create_pdb1_resource.yaml`](./usecase/create_pdb1_resource.yaml) - -```bash -kubectl apply -f create_pdb1_resource.yaml -``` -Check the status of the resource and the PDB existence on the container db: - -```bash -kubectl get lrpdb -n pdbnamespace -NAME CONNECT_STRING CDB NAME LRPDB NAME LRPDB STATE LRPDB SIZE STATUS MESSAGE LAST SQLCODE -lrpdb1 (DESCRIPTION=(CONNECT_TIMEOUT=90)(RETRY_COUNT=30)(RETRY_DELAY=10)(TRANSPORT_CONNECT_TIMEOUT=70)(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL=TCP)(HOST=scan12.testrac.com)(PORT=1521)(IP=V4_ONLY))(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL=TCP)(HOST=scan34.testrac.com)(PORT=1521)(IP=V4_ONLY))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=pdbdev))) DB12 pdbdev MOUNTED 2G Ready Success -``` - -```bash -SQL> show pdbs - - CON_ID CON_NAME OPEN MODE RESTRICTED ----------- ------------------------------ ---------- ---------- - 2 PDB$SEED READ ONLY NO - 3 PDBDEV MOUNTED -SQL> -``` -``Note that after creation, the PDB is not open. You must explicitly open it using a dedicated `yaml` file. - -**pdb creation** - parameters list - -| Name | Dcription | -|-------------------------|-------------------------------------------------------------------------------| -|cdbResName | REST server resource name | -|cdbNamespace | Namespace of the REST server | -|cdbName | Name of the container database | -|pdbName | Name of the PDB that you want to create | -|assertiveLrpdbDeletion | Boolean: Turn on the imperative approach on PDB resource deletion | -|adminpdbUser | Secret: PDB admin user | -|adminpdbPass | Secret: password of PDB admin user | -|lrpdbTlsKey | Secret: `tls.key ` | -|lrpdbTlsCrt | Secret: `tls.crt` | -|lrpdbTlsCat | Secret: `ca.crt` | -|webServerUser | Secret: the HTTPS user | -|webServerPwd | Secret: the HTTPS user password | -|cdbPrvKey | Secret: private key | -|cdbPubKey | Secret: public key | -|pdbconfigmap | kubernetes config map that contains the PDB initialization (init) parameters | - -> NOTE: **assertiveLrpdbDeletion** must be specified for the following PDB actions **CLONE** **CREATE** **PLUG** **MAP**. - -🔥 **assertiveLrpdbDeletion** drops pluggable database using **INCLUDE DATAFILES** option - -All of the parameters **adminpdbUser** **adminpdbPass** **lrpdbTlsKey** **lrpdbTlsCrt** **lrpdbTlsCat** **webServerUser** **webServerPwd** **cdbPrvKey** **cdbPubKey** must be specified in all PDB lifecycle management `yaml` files. To simplify presentation of requirements, we will not include them in the subsequent tables. - - -#### pdb config map - -By using **pdbconfigmap** it is possible to specify a kubernetes `configmap` with init PDB parameters. The config map payload has the following format: - - -``` -;; -;; -;; -.... -.... -;; -``` - -Example of `configmap` creation: - -```bash -cat < parameters.txt -session_cached_cursors;100;spfile -open_cursors;100;spfile -db_file_multiblock_read_count;16;spfile -EOF - -kubectl create configmap config-map-pdb -n pdbnamespace --from-file=./parameters.txt - -kubectl describe configmap config-map-pdb -n pdbnamespace -Name: config-map-pdb -Namespace: pdbnamespace -Labels: -Annotations: - -Data -==== -parameters.txt: ----- -session_cached_cursors;100;spfile -open_cursors;100;spfile -db_file_multiblock_read_count;16;spfile -test_invalid_parameter;16;spfile -``` - -- If specified, the `configmap` is applied during PDB **cloning**, **opening** and **plugging** -- The `configmap` is not monitored by the reconciliation loop; this feature will be available in future releases. This means that if someone decides to manually alter an init parameter, then the operator does not take any actions to syncronize PDB configuration with the `configmap`. -- **Alter system parameter feature** will be available in future releases. -- An application error with the `configmap` (for whatever reason) does not stop processes from completing. A warning with the associated SQL code is reported in the log file. - - - -### Open PDB - -To open the PDB, use the file [`open_pdb1_resource.yaml`](./usecase/open_pdb1_resource.yaml): - -```bash -kubectl apply -f open_pdb1_resource.yaml -``` - - **pdb opening** - parameters list - -| Name | Description/Value | -|-------------------------|-------------------------------------------------------------------------------| -|cdbResName | REST server resource name | -|cdbNamespace | Namespace of the REST server | -|cdbName | Name of the container database (CDB) | -|pdbName | Name of the pluggable database (PDB) that you are creating | -|action | Use **Modify** to open the PDB | -|pdbState | Use **OPEN** to open the PDB | -|modifyOption | Use **READ WRITE** to open the PDB | - -### Close PDB - -To close the PDB, use the file [`close_pdb1_resource.yaml`](./usecase/close_pdb1_resource.yaml): - -```bash -kubectl apply -f close_pdb1_resource.yaml -``` -**pdb closing** - parameters list -| Name | Description/Value | -|-------------------------|-------------------------------------------------------------------------------| -|cdbResName | REST server resource name | -|cdbNamespace | Namespace of the REST server | -|cdbName | Name of the container database (CDB) | -|pdbName | Name of the pluggable database (PDB) that you want to create | -|action | Use **Modify** to close the PDB | -|pdbState | Use **CLOSE** to close the PDB | -|modifyOption | Use **IMMEDIATE** to close the PDB | - -### Clone PDB ### - -To clone the PDB, use the file [`clone_pdb1_resource.yaml`](./usecase/clone_pdb1_resource.yaml): - -```bash -kubeclt apply -f clone_pdb1_resource.yaml -``` -**pdb cloning** - parameters list -| Name | Description/Value | -|-------------------------|-------------------------------------------------------------------------------| -|cdbResName | REST server resource name | -|cdbNamespace | Namespace of the REST server | -|cdbName | Name of the container database (CDB) | -|pdbName | The name of the new pluggable database (PDB) | -|srcPdbName | The name of the source PDB | -|fileNameConversions | File name convert pattern **("path1","path2")** or **NONE** | -|totalSize | Set **unlimited** for cloning | -|tempSize | Set **unlimited** for cloning | -|pdbconfigmap | kubernetes `configmap` which contains the PDB init parameters | -|action | Use **clone** to clone the PDB | - -### Unplug PDB - -To unplug the PDB, use the file [`unplug_pdb1_resource.yaml`](./usecase/unplug_pdb1_resource.yaml): - -**pdb unplugging** -| Name | Description/Value | -|-------------------------|-------------------------------------------------------------------------------| -|cdbResName | REST server resource name | -|cdbNamespace | Namespace of the REST server | -|cdbName | Name of the container database (CDB) | -|pdbName | Name of the pluggable database (PDB)| -### Plug PDB - -To plug in the PDB, use the file [`plug_pdb1_resource.yaml`](./usecase/plug_pdb1_resource.yaml). In this example, we plug in the PDB that was unpluged in the previous step: - -**pdb plugging** -| Name | Description/Value | -|-------------------------|-------------------------------------------------------------------------------| -|cdbResName | REST server resource name | -|cdbNamespace | Namespace of the REST server | -|cdbName | Name of the container database (CDB)| | -|pdbName | Name of the pluggable database (PDB) | -|**xmlFileName** | Path of the XML file | -|action | **plug** | -|fileNameConversions | File name convert pattern **("path1","path2")** or **NONE** | -|sourceFileNameConversion | See parameter [SOURCE_FILE_NAME_CONVERT](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/CREATE-PLUGGABLE-DATABASE.html#GUID-F2DBA8DD-EEA8-4BB7-A07F-78DC04DB1FFC__CCHEJFID) documentation | -|pdbconfigmap | Kubernetes `configmap` that contains the PDB init parameters | - -### Delete PDB - -To delete the PDB, use the file [`delete_pdb1_resource.yaml`](./usecase/delete_pdb1_resource.yaml) - -**pdb deletion** - -| Name | Dcription/Value | -|-------------------------|-------------------------------------------------------------------------------| -|cdbResName | REST server resource name | -|cdbNamespace | Namespace of the REST server | -|cdbName | Name of the container database (CDB) | -|action | **Delete** | -|dropAction | **INCLUDING** - Including datafiles or **NONE** | - - -### Map PDB - -If you need to create a CRD for an existing PDB, then you can use the map option by applying the file [`map_pdb1_resource.yaml`](./usecase/map_pdb1_resource.yaml) -Map functionality can be used in a situation where you have a pdb which is not registered in the operator as a CRD. It's a temporary solution while waiting the autodiscovery to be available. - - - diff --git a/docs/multitenant/lrest-based/usecase/README.md b/docs/multitenant/lrest-based/usecase/README.md deleted file mode 100644 index 98897d7b..00000000 --- a/docs/multitenant/lrest-based/usecase/README.md +++ /dev/null @@ -1,139 +0,0 @@ - - - -# Use case directory - -The use case directory contains the `yaml` files to test the multitenant controller functionalities: create `lrest` pod, and create PDB operations *create / open / close / unplug / plug / delete / clone /map / parameter session* - -## Makefile helper - -Customizing `yaml` files (tns alias / credential / namespaces name, and so on) is a long procedure that is prone to human error. A simple [`makefile`](../usecase/makefile) is available to quickly and safely configure `yaml` files with your system environment information. Just edit the [parameter file](../usecase/parameters.txt) before proceding. - -```text -TNSALIAS...............:[Tnsalias do not use quotes and avoid space in the string --> (DESCRIPTION=(CONNECT_TIMEOUT=90)(RETRY_COUNT=30)(RETRY_DELA....] -DBUSER.................:[CDB admin user] -DBPASS.................:[CDB admin user password] -WBUSER.................:[HTTPS user] -WBPASS.................:[HTTPS user password] -PDBUSR.................:[PDB admin user] -PDBPWD.................:[PDB admin user password] -PDBNAMESPACE...........:[pdb namespace] -LRSNAMESPACE...........:[cdb namespace] -COMPANY................:[your company name] -APIVERSION.............:v4 --> do not edit -``` - -⚠ **WARNING: The makefile is only intended to speed up the usecase directory configuration. Use of this file for production purposes is not supported. The editing and configuration of yaml files for production system is left up to the end user** - -### Prerequisistes: - -- Ensure that **kubectl** is properly configured. -- Ensure that all requirements listed in the [operator installation page](../../../../docs/installation/OPERATOR_INSTALLATION_README.md) are implemented. (role binding,webcert,etc) -- Ensure that the administrative user (admin) on the container database is configured as documented. - -```bash -make operator -``` -This command creates the `operator-database-operator.yaml` file in the local directory, and set up the `watchnamespace` list. Note that the `yaml` file is not applied. - -```bash -make secrets -``` -This command creates all of the Secrets with the encrypted credentials. - -```bash -make genyaml -``` -*make genyaml* generates the required `yaml` files to work with multitenant controllers. - - -![image](../images/UsecaseSchema.jpg) - -## Diag commands and troubleshooting - -### Connect to rest server pod - -```bash -/usr/bin/kubectl exec -n -it -- /bin/bash -``` - - -```bash -## example ## - -kubectl get pods -n cdbnamespace -NAME READY STATUS RESTARTS AGE -cdb-dev-lrest-rs-fnw99 1/1 Running 1 (17h ago) 18h - -kubectl exec cdb-dev-lrest-rs-fnw99 -n cdbnamespace -it -- /bin/bash -[oracle@cdb-dev-lrest-rs-fnw99 ~]$ -``` - -### Monitor control plane - -```bash -kubectl logs -f -l control-plane=controller-manager -n oracle-database-operator-system -``` -```bash -## output example: ## -2024-10-28T23:54:25Z INFO lrpdb-webhook ValidateUpdate-Validating LRPDB spec for : lrpdb2 -2024-10-28T23:54:25Z INFO lrpdb-webhook validateCommon {"name": "lrpdb2"} -2024-10-28T23:54:25Z INFO lrpdb-webhook Valdiating LRPDB Resource Action : MODIFY -2024-10-29T10:07:34Z INFO lrpdb-webhook ValidateUpdate-Validating LRPDB spec for : lrpdb2 -2024-10-29T10:07:34Z INFO lrpdb-webhook ValidateUpdate-Validating LRPDB spec for : lrpdb1 -2024-10-29T16:49:15Z INFO lrpdb-webhook ValidateUpdate-Validating LRPDB spec for : lrpdb1 -2024-10-29T16:49:15Z INFO lrpdb-webhook validateCommon {"name": "lrpdb1"} -2024-10-29T16:49:15Z INFO lrpdb-webhook Valdiating LRPDB Resource Action : CREATE -2024-10-29T10:07:20Z INFO controller-runtime.certwatcher Updated current TLS certificate -2024-10-29T10:07:20Z INFO controller-runtime.webhook Serving webhook server {"host": "", "port": 9443} -2024-10-29T10:07:20Z INFO controller-runtime.certwatcher Starting certificate watcher -I1029 10:07:20.189724 1 leaderelection.go:250] attempting to acquire leader lease oracle-database-operator-system/a9d608ea.oracle.com... -2024-10-29T16:49:15Z INFO lrpdb-webhook Setting default values in LRPDB spec for : lrpdb1 - -``` - -### Error decrypting credential - -The following is an example of a resource creation failure due to decription error: - -```text -2024-10-30T10:09:08Z INFO controllers.LRPDB getEncriptedSecret :pdbusr {"getEncriptedSecret": {"name":"lrpdb1","namespace":"pdbnamespace"}} -2024-10-30T10:09:08Z ERROR controllers.LRPDB Failed to parse private key - x509: failed to parse private key (use ParsePKCS1PrivateKey instead for this key format) {"DecryptWithPrivKey": {"name":"lrpdb1","namespace":"pdbnamespace"}, "error": "x509: failed to parse private key (use ParsePKCS1PrivateKey instead for this key format)"} -``` - - -**Solution**: Ensure you use **PCKS8** format during private key generation. If you are not using `openssl3`, then run this command: - -```bash -openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_keygen_pubexp:65537 > mykey -``` - -### Crd details - -Use the **describe** option to obtain `crd` information - -```bash -kubectl describe lrpdb lrpdb1 -n pdbnamespace -[...] - Secret: - Key: e_wbuser.txt - Secret Name: wbuser -Status: - Action: CREATE - Bitstat: 25 - Bitstatstr: |MPAPPL|MPWARN|MPINIT| - Conn String: (DESCRIPTION=(CONNECT_TIMEOUT=90)(RETRY_COUNT=30)(RETRY_DELAY=10)(TRANSPORT_CONNECT_TIMEOUT=70)(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL=TCP)(HOST=scan12.testrac.com)(PORT=1521)(IP=V4_ONLY))(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL=TCP)(HOST=scan34.testrac.com)(PORT=1521)(IP=V4_ONLY))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=pdbdev))) - Msg: Success - Open Mode: MOUNTED - Phase: Ready - Status: true - Total Size: 2G -Events: - Type Reason Age From Message - ---- ------ ---- ---- ------- - Normal Created 108s LRPDB LRPDB 'pdbdev' created successfully - Normal Created 108s LRPDB PDB 'pdbdev' assertive pdb deletion turned on - Warning LRESTINFO 95s LRPDB pdb=pdbdev:test_invalid_parameter:16:spfile:2065 - Warning Done 15s (x12 over 2m25s) LRPDB cdb-dev - -``` diff --git a/docs/multitenant/lrest-based/usecase/config-map-pdb.yaml b/docs/multitenant/lrest-based/usecase/config-map-pdb.yaml deleted file mode 100644 index 2769b498..00000000 --- a/docs/multitenant/lrest-based/usecase/config-map-pdb.yaml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: config-map-pdb - namespace: pdbnamespace -data: - rdbmsparameters.txt: | - session_cached_cursors;100;spfile - open_cursors;100;spfile - db_file_multiblock_read_count;16;spfile - test_invalid_parameter;16;spfile diff --git a/docs/multitenant/lrest-based/usecase/makefile b/docs/multitenant/lrest-based/usecase/makefile deleted file mode 100644 index 1de320ad..00000000 --- a/docs/multitenant/lrest-based/usecase/makefile +++ /dev/null @@ -1,911 +0,0 @@ -# Copyright (c) 2022, Oracle and/or its affiliates. -# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. -# __ __ _ __ _ _ -# | \/ | __ _| | _____ / _(_) | ___ -# | |\/| |/ _` | |/ / _ \ |_| | |/ _ \ -# | | | | (_| | < __/ _| | | __/ -# |_| |_|\__,_|_|\_\___|_| |_|_|\___| -# | | | | ___| |_ __ ___ _ __ -# | |_| |/ _ \ | '_ \ / _ \ '__| -# | _ | __/ | |_) | __/ | -# |_| |_|\___|_| .__/ \___|_| -# |_| -# -# WARNING: Using this makefile helps you to customize yaml -# files. Edit parameters.txt with your enviroment -# informartion and execute the following steps -# -# 1) make operator -# it configures the operator yaml files with the -# watch namelist required by the multitenant controllers -# -# 2) make secrets -# It configure the required secrets necessary to operate -# with pdbs multitenant controllers -# -# 3) make genyaml -# It automatically creates all the yaml files based on the -# information available in the parameters file -# -# LIST OF GENERAED YAML FILE -# -# ----------------------------- ---------------------------------- -# oracle-database-operator.yaml : oracle database operator -# lrestnamespace_binding.yaml : role binding for lrestnamespace -# pdbnamespace_binding.yaml : role binding for pdbnamespace -# create_lrest_secret.yaml : create secrets for rest server pod -# create_lrpdb_secret.yaml : create secrets for pluggable database -# create_lrest_pod.yaml : create rest server pod -# create_pdb1_resource.yaml : create first pluggable database -# create_pdb2_resource.yaml : create second pluggable database -# open_pdb1_resource.yaml : open first pluggable database -# open_pdb2_resource.yaml : open second pluggable database -# close_pdb1_resource.yaml : close first pluggable database -# close_pdb2_resource.yaml : close second pluggable database -# clone_lrpdb_resource.yaml : clone thrid pluggable database -# clone_pdb2_resource.yaml : clone 4th pluggable database -# delete_pdb1_resource.yaml : delete first pluggable database -# delete_pdb2_resource.yaml : delete sencond pluggable database -# delete_pdb3_resource.yaml : delete thrid pluggable database -# unplug_pdb1_resource.yaml : unplug first pluggable database -# plug_pdb1_resource.yaml : plug first pluggable database -# map_pdb1_resource.yaml : map the first pluggable database -# config_map.yam : pdb parameters array -# altersystem_pdb1_resource.yaml : chage cpu_count count parameter for the first pdb -# -DATE := `date "+%y%m%d%H%M%S"` -###################### -# PARAMETER SECTIONS # -###################### - -export PARAMETERS=parameters.txt -export TNSALIAS=$(shell cat $(PARAMETERS) |grep -v ^\#|grep TNSALIAS|cut -d : -f 2) -export DBUSER=$(shell cat $(PARAMETERS)|grep -v ^\#|grep DBUSER|cut -d : -f 2) -export DBPASS=$(shell cat $(PARAMETERS)|grep -v ^\#|grep DBPASS|cut -d : -f 2) -export WBUSER=$(shell cat $(PARAMETERS)|grep -v ^\#|grep WBUSER|cut -d : -f 2) -export WBPASS=$(shell cat $(PARAMETERS)|grep -v ^\#|grep WBPASS|cut -d : -f 2) -export PDBUSR=$(shell cat $(PARAMETERS)|grep -v ^\#|grep PDBUSR|cut -d : -f 2) -export PDBPWD=$(shell cat $(PARAMETERS)|grep -v ^\#|grep PDBPWD|cut -d : -f 2) -export PDBNAMESPACE=$(shell cat $(PARAMETERS)|grep -v ^\#|grep PDBNAMESPACE|cut -d : -f 2) -export LRSNAMESPACE=$(shell cat $(PARAMETERS)|grep -v ^\#|grep LRSNAMESPACE|cut -d : -f 2) -export LRESTIMG=$(shell cat $(PARAMETERS)|grep -v ^\#|grep LRESTIMG|cut -d : -f 2,3) -export COMPANY=$(shell cat $(PARAMETERS)|grep -v ^\#|grep COMPANY|cut -d : -f 2) -export APIVERSION=$(shell cat $(PARAMETERS)|grep -v ^\#|grep APIVERSION|cut -d : -f 2) -export OPRNAMESPACE=oracle-database-operator-system -export ORACLE_OPERATOR_YAML=../../../../oracle-database-operator.yaml -export TEST_EXEC_TIMEOUT=3m - -REST_SERVER=lrest -SKEY=tls.key -SCRT=tls.crt -CART=ca.crt -PRVKEY=ca.key -PUBKEY=public.pem -COMPANY=oracle -DBUSERFILE=dbuser.txt -DBPASSFILE=dbpass.txt -WBUSERFILE=wbuser.txt -WBPASSFILE=wbpass.txt -PDBUSRFILE=pdbusr.txt -PDBPWDFILE=pdbpwd.txt - -################# -### FILE LIST ### -################# - -export LREST_POD=create_lrest_pod.yaml - -export LRPDBCRE1=create_pdb1_resource.yaml -export LRPDBCRE2=create_pdb2_resource.yaml - -export LRPDBCLOSE1=close_pdb1_resource.yaml -export LRPDBCLOSE2=close_pdb2_resource.yaml -export LRPDBCLOSE3=close_pdb3_resource.yaml - -export LRPDBOPEN1=open_pdb1_resource.yaml -export LRPDBOPEN2=open_pdb2_resource.yaml -export LRPDBOPEN3=open_pdb3_resource.yaml - -export LRPDBCLONE1=clone_pdb1_resource.yaml -export LRPDBCLONE2=clone_pdb2_resource.yaml - -export LRPDBDELETE1=delete_pdb1_resource.yaml -export LRPDBDELETE2=delete_pdb2_resource.yaml -export LRPDBDELETE3=delete_pdb3_resource.yaml - -export LRPDBUNPLUG1=unplug_pdb1_resource.yaml -export LRPDBPLUG1=plug_pdb1_resource.yaml - -export LRPDBMAP1=map_pdb1_resource.yaml -export LRPDBMAP2=map_pdb2_resource.yaml -export LRPDBMAP3=map_pdb3_resource.yaml - -export LRPDBMAP1=map_pdb1_resource.yaml -export LRPDBMAP2=map_pdb2_resource.yaml -export LRPDBMAP3=map_pdb3_resource.yaml - -export ALTERSYSTEMYAML=altersystem_pdb1_resource.yaml -export CONFIG_MAP=config_map_pdb.yaml - - - - -##BINARIES -export KUBECTL=/usr/bin/kubectl -OPENSSL=/usr/bin/openssl -ECHO=/usr/bin/echo -RM=/usr/bin/rm -CP=/usr/bin/cp -TAR=/usr/bin/tar -MKDIR=/usr/bin/mkdir -SED=/usr/bin/sed - -check: - @printf "TNSALIAS...............:%.60s....\n" $(TNSALIAS) - @printf "DBUSER.................:%s\n" $(DBUSER) - @printf "DBPASS.................:%s\n" $(DBPASS) - @printf "WBUSER.................:%s\n" $(WBUSER) - @printf "WBPASS.................:%s\n" $(WBPASS) - @printf "PDBUSR.................:%s\n" $(PDBUSR) - @printf "PDBPWD.................:%s\n" $(PDBPWD) - @printf "PDBNAMESPACE...........:%s\n" $(PDBNAMESPACE) - @printf "LRSNAMESPACE...........:%s\n" $(LRSNAMESPACE) - @printf "COMPANY................:%s\n" $(COMPANY) - @printf "APIVERSION.............:%s\n" $(APIVERSION) - -define msg -@printf "\033[31;7m%s\033[0m\r" "......................................]" -@printf "\033[31;7m[\xF0\x9F\x91\x89 %s\033[0m\n" $(1) -endef - -tls: - $(call msg,"TLS GENERATION") - #$(OPENSSL) genrsa -out $(PRVKEY) 2048 - $(OPENSSL) genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_keygen_pubexp:65537 > $(PRVKEY) - $(OPENSSL) req -new -x509 -days 365 -key $(PRVKEY) \ - -subj "/C=CN/ST=GD/L=SZ/O=$(COMPANY), Inc./CN=$(COMPANY) Root CA" -out ca.crt - $(OPENSSL) req -newkey rsa:2048 -nodes -keyout $(SKEY) -subj \ - "/C=CN/ST=GD/L=SZ/O=$(COMPANY), Inc./CN=cdb-dev-$(REST_SERVER).$(LRSNAMESPACE)" -out server.csr - $(ECHO) "subjectAltName=DNS:cdb-dev-$(REST_SERVER).$(LRSNAMESPACE)" > extfile.txt - $(OPENSSL) x509 -req -extfile extfile.txt -days 365 -in server.csr -CA ca.crt -CAkey $(PRVKEY) -CAcreateserial -out $(SCRT) - $(OPENSSL) rsa -in $(PRVKEY) -outform PEM -pubout -out $(PUBKEY) - -secrets: tls delsecrets - $(call msg,"CREATING NEW TLS/PRVKEY/PUBKEY SECRETS") - $(KUBECTL) create secret tls db-tls --key="$(SKEY)" --cert="$(SCRT)" -n $(LRSNAMESPACE) - $(KUBECTL) create secret generic db-ca --from-file="$(CART)" -n $(LRSNAMESPACE) - $(KUBECTL) create secret tls db-tls --key="$(SKEY)" --cert="$(SCRT)" -n $(PDBNAMESPACE) - $(KUBECTL) create secret generic db-ca --from-file="$(CART)" -n $(PDBNAMESPACE) - #$(KUBECTL) create secret tls prvkey --key="$(PRVKEY)" --cert=ca.crt -n $(LRSNAMESPACE) - $(KUBECTL) create secret generic pubkey --from-file=publicKey=$(PUBKEY) -n $(LRSNAMESPACE) - $(KUBECTL) create secret generic prvkey --from-file=privateKey=$(PRVKEY) -n $(LRSNAMESPACE) - $(KUBECTL) create secret generic prvkey --from-file=privateKey="$(PRVKEY)" -n $(PDBNAMESPACE) - $(call msg,"CREATING NEW CREDENTIAL SECRETS") - @$(ECHO) $(DBUSER) > $(DBUSERFILE) - @$(ECHO) $(DBPASS) > $(DBPASSFILE) - @$(ECHO) $(WBUSER) > $(WBUSERFILE) - @$(ECHO) $(WBPASS) > $(WBPASSFILE) - @$(ECHO) $(PDBUSR) > $(PDBUSRFILE) - @$(ECHO) $(PDBPWD) > $(PDBPWDFILE) - $(OPENSSL) rsautl -encrypt -pubin -inkey $(PUBKEY) -in $(DBUSERFILE) |base64 > e_$(DBUSERFILE) - $(OPENSSL) rsautl -encrypt -pubin -inkey $(PUBKEY) -in $(DBPASSFILE) |base64 > e_$(DBPASSFILE) - $(OPENSSL) rsautl -encrypt -pubin -inkey $(PUBKEY) -in $(WBUSERFILE) |base64 > e_$(WBUSERFILE) - $(OPENSSL) rsautl -encrypt -pubin -inkey $(PUBKEY) -in $(WBPASSFILE) |base64 > e_$(WBPASSFILE) - $(OPENSSL) rsautl -encrypt -pubin -inkey $(PUBKEY) -in $(PDBUSRFILE) |base64 > e_$(PDBUSRFILE) - $(OPENSSL) rsautl -encrypt -pubin -inkey $(PUBKEY) -in $(PDBPWDFILE) |base64 > e_$(PDBPWDFILE) - $(KUBECTL) create secret generic dbuser --from-file=e_$(DBUSERFILE) -n $(LRSNAMESPACE) - $(KUBECTL) create secret generic dbpass --from-file=e_$(DBPASSFILE) -n $(LRSNAMESPACE) - $(KUBECTL) create secret generic wbuser --from-file=e_$(WBUSERFILE) -n $(LRSNAMESPACE) - $(KUBECTL) create secret generic wbpass --from-file=e_$(WBPASSFILE) -n $(LRSNAMESPACE) - $(KUBECTL) create secret generic wbuser --from-file=e_$(WBUSERFILE) -n $(PDBNAMESPACE) - $(KUBECTL) create secret generic wbpass --from-file=e_$(WBPASSFILE) -n $(PDBNAMESPACE) - $(KUBECTL) create secret generic pdbusr --from-file=e_$(PDBUSRFILE) -n $(PDBNAMESPACE) - $(KUBECTL) create secret generic pdbpwd --from-file=e_$(PDBPWDFILE) -n $(PDBNAMESPACE) - $(RM) $(SKEY) $(SCRT) $(CART) $(PRVKEY) $(PUBKEY) server.csr extfile.txt ca.srl \ - $(DBUSERFILE) $(DBPASSFILE) $(WBUSERFILE) $(WBPASSFILE) $(PDBUSRFILE) $(PDBPWDFILE)\ - e_$(DBUSERFILE) e_$(DBPASSFILE) e_$(WBUSERFILE) e_$(WBPASSFILE) e_$(PDBUSRFILE) e_$(PDBPWDFILE) - $(KUBECTL) get secrets -n $(LRSNAMESPACE) - $(KUBECTL) get secrets -n $(PDBNAMESPACE) - -delsecrets: - $(call msg,"CLEAN OLD SECRETS") - $(eval SECRETSP:=$(shell kubectl get secrets -n $(PDBNAMESPACE) -o custom-columns=":metadata.name" --no-headers) ) - $(eval SECRETSL:=$(shell kubectl get secrets -n $(LRSNAMESPACE) -o custom-columns=":metadata.name" --no-headers) ) - @[ "${SECRETSP}" ] && ( \ - printf "Deleteing secrets in namespace -n $(PDBNAMESPACE)\n") &&\ - ($(KUBECTL) delete secret $(SECRETSP) -n $(PDBNAMESPACE))\ - || ( echo "No screts in namespace $(PDBNAMESPACE)") - @[ "${SECRETSL}" ] && ( \ - printf "Deleteing secrets in namespace -n $(LRSNAMESPACE)\n") &&\ - ($(KUBECTL) delete secret $(SECRETSL) -n $(LRSNAMESPACE))\ - || ( echo "No screts in namespace $(PDBNAMESPACE)") - -cleanCert: - $(RM) $(SKEY) $(SCRT) $(CART) $(PRVKEY) $(PUBKEY) server.csr extfile.txt ca.srl \ - $(DBUSERFILE) $(DBPASSFILE) $(WBUSERFILE) $(WBPASSFILE) $(PDBUSRFILE) $(PDBPWDFILE)\ - e_$(DBUSERFILE) e_$(DBPASSFILE) e_$(WBUSERFILE) e_$(WBPASSFILE) e_$(PDBUSRFILE) e_$(PDBPWDFILE) - -### YAML FILE SECTION ### -define _opr -cp ${ORACLE_OPERATOR_YAML} . -export OPBASENAME=`basename ${ORACLE_OPERATOR_YAML}` -#export PDBNAMESPACE=$(cat ${PARAMETERS}|grep -v ^\#|grep PDBNAMESPACE|cut -d : -f 2) - -cp ${OPBASENAME} ${OPBASENAME}.ORIGNINAL -printf "\n\t\xF0\x9F\x91\x89 ${OPBASENAME}\n\n" -printf "\n\t\xF0\x9F\x91\x89 ${PDBNAMESPACE}\n\n" -sed -i 's/value: ""/value: ${OPRNAMESPACE},$PDBNAMESPACE,${LRSNAMESPACE}/g' ${OPBASENAME} -endef - -export opr = $(value _opr) - -operator: -# @ eval "$$opr" - $(CP) ${ORACLE_OPERATOR_YAML} . - ${CP} `basename ${ORACLE_OPERATOR_YAML}` `basename ${ORACLE_OPERATOR_YAML}`.ORG - $(SED) -i 's/value: ""/value: $(OPRNAMESPACE),$(PDBNAMESPACE),$(LRSNAMESPACE)/g' `basename ${ORACLE_OPERATOR_YAML}` - - -define _script00 -cat < authsection.yaml - adminpdbUser: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminpdbPass: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - lrpdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - lrpdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - lrpdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - cdbPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" -EOF - - -cat < ${PDBNAMESPACE}_binding.yaml -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: oracle-database-operator-oracle-database-operator-manager-rolebinding1 - namespace: ${PDBNAMESPACE} -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: oracle-database-operator-manager-role -subjects: -- kind: ServiceAccount - name: default - namespace: oracle-database-operator-system -EOF - -cat < ${LRSNAMESPACE}_binding.yaml -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: oracle-database-operator-oracle-database-operator-manager-rolebinding2 - namespace: ${LRSNAMESPACE} -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: oracle-database-operator-manager-role -subjects: -- kind: ServiceAccount - name: default - namespace: oracle-database-operator-system -EOF - -endef -export script00 = $(value _script00) -secyaml: - @ eval "$$script00" - - -#echo lrest pod creation -define _script01 -cat < ${LREST_POD} -apiVersion: database.oracle.com/${APIVERSION} -kind: LREST -metadata: - name: cdb-dev - namespace: ${LRSNAMESPACE} -spec: - cdbName: "DB12" - lrestImage: ${LRESTIMG} - lrestImagePullPolicy: "Always" - dbTnsurl : ${TNSALIAS} - replicas: 1 - deletePdbCascade: true - cdbAdminUser: - secret: - secretName: "dbuser" - key: "e_dbuser.txt" - cdbAdminPwd: - secret: - secretName: "dbpass" - key: "e_dbpass.txt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - cdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - cdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - cdbPubKey: - secret: - secretName: "pubkey" - key: "publicKey" - cdbPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" -EOF - -endef -export script01 = $(value _script01) - - -define _script02 - -cat <${LRPDBCRE1} -apiVersion: database.oracle.com/${APIVERSION} -kind: LRPDB -metadata: - name: pdb1 - namespace: ${PDBNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${LRSNAMESPACE}" - cdbName: "DB12" - pdbName: "pdbdev" - assertiveLrpdbDeletion: true - fileNameConversions: "NONE" - unlimitedStorage: false - pdbconfigmap: "config-map-pdb" - tdeImport: false - totalSize: "2G" - tempSize: "800M" - action: "Create" -EOF - -cat < ${LRPDBCRE2} -apiVersion: database.oracle.com/${APIVERSION} -kind: LRPDB -metadata: - name: pdb2 - namespace: ${PDBNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${LRSNAMESPACE}" - cdbName: "DB12" - pdbName: "pdbprd" - assertiveLrpdbDeletion: true - fileNameConversions: "NONE" - unlimitedStorage: false - pdbconfigmap: "config-map-pdb" - tdeImport: false - totalSize: "2G" - tempSize: "800M" - action: "Create" -EOF - -cat <${LRPDBOPEN1} -apiVersion: database.oracle.com/${APIVERSION} -kind: LRPDB -metadata: - name: pdb1 - namespace: ${PDBNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${LRSNAMESPACE}" - cdbName: "DB12" - pdbName: "pdbdev" - action: "Modify" - pdbState: "OPEN" - modifyOption: "READ WRITE" -EOF - -cat <${LRPDBOPEN2} -apiVersion: database.oracle.com/${APIVERSION} -kind: LRPDB -metadata: - name: pdb2 - namespace: ${PDBNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${LRSNAMESPACE}" - cdbName: "DB12" - pdbName: "pdbprd" - action: "Modify" - pdbState: "OPEN" - modifyOption: "READ WRITE" -EOF - -cat <${LRPDBOPEN3} -apiVersion: database.oracle.com/${APIVERSION} -kind: LRPDB -metadata: - name: pdb3 - namespace: ${PDBNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${LRSNAMESPACE}" - cdbName: "DB12" - pdbName: "new_clone" - action: "Modify" - pdbState: "OPEN" - modifyOption: "READ WRITE" -EOF - -cat <${LRPDBCLOSE1} -apiVersion: database.oracle.com/${APIVERSION} -kind: LRPDB -metadata: - name: pdb1 - namespace: ${PDBNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${LRSNAMESPACE}" - cdbName: "DB12" - pdbName: "pdbdev" - pdbState: "CLOSE" - modifyOption: "IMMEDIATE" - action: "Modify" -EOF - -cat <${LRPDBCLOSE2} -apiVersion: database.oracle.com/${APIVERSION} -kind: LRPDB -metadata: - name: pdb2 - namespace: ${PDBNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${LRSNAMESPACE}" - cdbName: "DB12" - pdbName: "pdbprd" - pdbState: "CLOSE" - modifyOption: "IMMEDIATE" - action: "Modify" -EOF - -cat <${LRPDBCLOSE3} -apiVersion: database.oracle.com/${APIVERSION} -kind: LRPDB -metadata: - name: pdb3 - namespace: ${PDBNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${LRSNAMESPACE}" - cdbName: "DB12" - pdbName: "new_clone" - pdbState: "CLOSE" - modifyOption: "IMMEDIATE" - action: "Modify" -EOF - -cat < ${LRPDBCLONE1} -apiVersion: database.oracle.com/${APIVERSION} -kind: LRPDB -metadata: - name: pdb3 - namespace: ${PDBNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${LRSNAMESPACE}" - cdbName: "DB12" - pdbName: "new_clone" - srcPdbName: "pdbdev" - fileNameConversions: "NONE" - totalSize: "UNLIMITED" - tempSize: "UNLIMITED" - pdbconfigmap: "config-map-pdb" - assertiveLrpdbDeletion: true - action: "Clone" -EOF - -cat < ${LRPDBCLONE2} -apiVersion: database.oracle.com/${APIVERSION} -kind: LRPDB -metadata: - name: pdb4 - namespace: ${PDBNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${LRSNAMESPACE}" - cdbName: "DB12" - pdbName: "new_clone2" - srcPdbName: "pdbprd" - fileNameConversions: "NONE" - totalSize: "UNLIMITED" - tempSize: "UNLIMITED" - pdbconfigmap: "config-map-pdb" - assertiveLrpdbDeletion: true - action: "Clone" -EOF - -cat < ${LRPDBDELETE1} -apiVersion: database.oracle.com/${APIVERSION} -kind: LRPDB -metadata: - name: pdb1 - namespace: ${PDBNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${LRSNAMESPACE}" - pdbName: "pdbdev" - action: "Delete" - dropAction: "INCLUDING" -EOF - -cat < ${LRPDBDELETE2} -apiVersion: database.oracle.com/${APIVERSION} -kind: LRPDB -metadata: - name: pdb2 - namespace: ${PDBNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${LRSNAMESPACE}" - pdbName: "pdbprd" - action: "Delete" - dropAction: "INCLUDING" -EOF - -cat < ${LRPDBUNPLUG1} -apiVersion: database.oracle.com/${APIVERSION} -kind: LRPDB -metadata: - name: pdb1 - namespace: ${PDBNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${LRSNAMESPACE}" - cdbName: "DB12" - pdbName: "pdbdev" - xmlFileName: "/var/tmp/pdb.$$.xml" - action: "Unplug" -EOF - -cat <${LRPDBPLUG1} -apiVersion: database.oracle.com/${APIVERSION} -kind: LRPDB -metadata: - name: pdb1 - namespace: ${PDBNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${LRSNAMESPACE}" - cdbName: "DB12" - pdbName: "pdbdev" - xmlFileName: "/var/tmp/pdb.$$.xml" - action: "plug" - fileNameConversions: "NONE" - sourceFileNameConversions: "NONE" - copyAction: "MOVE" - totalSize: "1G" - tempSize: "100M" - assertiveLrpdbDeletion: true - pdbconfigmap: "config-map-pdb" - action: "Plug" -EOF - -cat <${LRPDBMAP1} -apiVersion: database.oracle.com/${APIVERSION} -kind: LRPDB -metadata: - name: pdb1 - namespace: ${PDBNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${LRSNAMESPACE}" - cdbName: "DB12" - pdbName: "pdbdev" - assertiveLrpdbDeletion: true - fileNameConversions: "NONE" - totalSize: "1G" - tempSize: "100M" - action: "Map" -EOF - -cat <${LRPDBMAP2} -apiVersion: database.oracle.com/${APIVERSION} -kind: LRPDB -metadata: - name: pdb2 - namespace: ${PDBNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${LRSNAMESPACE}" - cdbName: "DB12" - pdbName: "pdbprd" - assertiveLrpdbDeletion: true - fileNameConversions: "NONE" - totalSize: "1G" - tempSize: "100M" - action: "Map" -EOF - - -cat <${LRPDBMAP3} -apiVersion: database.oracle.com/${APIVERSION} -kind: LRPDB -metadata: - name: pdb3 - namespace: ${PDBNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${LRSNAMESPACE}" - cdbName: "DB12" - pdbName: "new_clone" - assertiveLrpdbDeletion: true - fileNameConversions: "NONE" - totalSize: "1G" - tempSize: "100M" - action: "Map" -EOF - -cat <${CONFIG_MAP} -apiVersion: v1 -kind: ConfigMap -metadata: - name: config-map-pdb - namespace: ${PDBNAMESPACE} -data: - rdbmsparameters.txt: | - session_cached_cursors;100;spfile - open_cursors;100;spfile - db_file_multiblock_read_count;16;spfile - test_invalid_parameter;16;spfile -EOF - - -cat < ${ALTERSYSTEMYAML} -apiVersion: database.oracle.com/${APIVERSION} -kind: LRPDB -metadata: - name: pdb1 - namespace: ${PDBNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${LRSNAMESPACE}" - cdbName: "DB12" - pdbName: "pdbdev" - action: "Alter" - alterSystemParameter : "cpu_count" - alterSystemValue : "3" - parameterScope : "memory" - - -EOF - -## Auth information -for _file in ${LRPDBCRE1} ${LRPDBCRE2} ${LRPDBOPEN1} ${LRPDBOPEN2} ${LRPDBOPEN3} ${LRPDBCLOSE1} ${LRPDBCLOSE2} ${LRPDBCLOSE3} ${LRPDBCLONE1} ${LRPDBCLONE2} ${LRPDBDELETE1} ${LRPDBDELETE2} ${LRPDBUNPLUG1} ${LRPDBPLUG1} ${LRPDBMAP1} ${LRPDBMAP2} ${LRPDBMAP3} ${ALTERSYSTEMYAML} -do -ls -ltr ${_file} - cat authsection.yaml >> ${_file} -done -rm authsection.yaml -endef - -export script02 = $(value _script02) - -genyaml: secyaml - @ eval "$$script01" - @ eval "$$script02" - -cleanyaml: - - $(RM) $(LRPDBMAP3) $(LRPDBMAP2) $(LRPDBMAP1) $(LRPDBPLUG1) $(LRPDBUNPLUG1) $(LRPDBDELETE2) $(LRPDBDELETE1) $(LRPDBCLONE2) $(LRPDBCLONE1) $(LRPDBCLOSE3) $(LRPDBCLOSE2) $(LRPDBCLOSE1) $(LRPDBOPEN3) $(LRPDBOPEN2) $(LRPDBOPEN1) $(LRPDBCRE2) $(LRPDBCRE1) $(LREST_POD) ${ALTERSYSTEMYAML} - - $(RM) ${CONFIG_MAP} ${PDBNAMESPACE}_binding.yaml ${LRSNAMESPACE}_binding.yaml - - - - -################# -### PACKAGING ### -################# - -pkg: - - $(RM) -rf /tmp/pkgtestplan - $(MKDIR) /tmp/pkgtestplan - $(CP) -R * /tmp/pkgtestplan - $(CP) ../../../../oracle-database-operator.yaml /tmp/pkgtestplan/ - $(TAR) -C /tmp -cvf ~/pkgtestplan_$(DATE).tar pkgtestplan - -################ -### diag ### -################ - -login: - $(KUBECTL) exec `$(KUBECTL) get pods -n $(LRSNAMESPACE)|grep rest|cut -d ' ' -f 1` -n $(LRSNAMESPACE) -it -- /bin/bash - - -reloadop: - echo "RESTARTING OPERATOR" - $(eval OP1 := $(shell $(KUBECTL) get pods -n $(OPRNAMESPACE)|grep oracle-database-operator-controller|head -1|cut -d ' ' -f 1 )) - $(eval OP2 := $(shell $(KUBECTL) get pods -n $(OPRNAMESPACE)|grep oracle-database-operator-controller|head -2|tail -1|cut -d ' ' -f 1 )) - $(eval OP3 := $(shell $(KUBECTL) get pods -n $(OPRNAMESPACE)|grep oracle-database-operator-controller|tail -1|cut -d ' ' -f 1 )) - $(KUBECTL) get pod $(OP1) -n $(OPRNAMESPACE) -o yaml | kubectl replace --force -f - - $(KUBECTL) get pod $(OP2) -n $(OPRNAMESPACE) -o yaml | kubectl replace --force -f - - $(KUBECTL) get pod $(OP3) -n $(OPRNAMESPACE) -o yaml | kubectl replace --force -f - - - -dump: - @$(eval TMPSP := $(shell date "+%y%m%d%H%M%S" )) - @$(eval DIAGFILE := ./opdmp.$(TMPSP)) - @>$(DIAGFILE) - @echo "OPERATOR DUMP" >> $(DIAGFILE) - @echo "~~~~~~~~~~~~~" >> $(DIAGFILE) - $(KUBECTL) logs pod/`$(KUBECTL) get pods -n $(OPRNAMESPACE)|grep oracle-database-operator-controller|head -1|cut -d ' ' -f 1` -n $(OPRNAMESPACE) >>$(DIAGFILE) - $(KUBECTL) logs pod/`$(KUBECTL) get pods -n $(OPRNAMESPACE)|grep oracle-database-operator-controller|head -2|tail -1 | cut -d ' ' -f 1` -n $(OPRNAMESPACE) >>$(DIAGFILE) - $(KUBECTL) logs pod/`$(KUBECTL) get pods -n $(OPRNAMESPACE)|grep oracle-database-operator-controller|tail -1|cut -d ' ' -f 1` -n $(OPRNAMESPACE) >>$(DIAGFILE) - -####################################################### -#### TEST SECTION #### -####################################################### - -run00: - @$(call msg,"lrest pod creation") - - $(KUBECTL) delete lrest cdb-dev -n $(LRSNAMESPACE) - $(KUBECTL) apply -f $(LREST_POD) - $(KUBECTL) wait --for jsonpath='{.status.phase'}="Ready" lrest cdb-dev -n $(LRSNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - @$(call msg,"lrest pod completed") - $(KUBECTL) get lrest -n $(LRSNAMESPACE) - $(KUBECTL) get pod -n $(LRSNAMESPACE) - -run01.1: - @$(call msg,"lrpdb pdb1 creation") - $(KUBECTL) apply -f $(LRPDBCRE1) - $(KUBECTL) wait --for jsonpath='{.status.phase'}="Ready" lrpdb pdb1 -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - @$(call msg, "lrpdb pdb1 creation completed") - $(KUBECTL) get lrpdb pdb1 -n $(PDBNAMESPACE) - -run01.2: - @$(call msg, "lrpdb pdb2 creation") - $(KUBECTL) apply -f $(LRPDBCRE2) - $(KUBECTL) wait --for jsonpath='{.status.phase'}="Ready" lrpdb pdb2 -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - @$(call msg, "lrpdb pdb2 creation completed") - $(KUBECTL) get lrpdb pdb2 -n $(PDBNAMESPACE) - -run02.1: - @$(call msg, "lrpdb pdb1 open") - $(KUBECTL) apply -f $(LRPDBOPEN1) - $(KUBECTL) wait --for jsonpath='{.status.openMode'}="READ WRITE" lrpdb pdb1 -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - @$(call msg, "lrpdb pdb1 open completed") - $(KUBECTL) get lrpdb pdb1 -n $(PDBNAMESPACE) - -run02.2: - @$(call msg,"lrpdb pdb2 open") - $(KUBECTL) apply -f $(LRPDBOPEN2) - $(KUBECTL) wait --for jsonpath='{.status.openMode'}="READ WRITE" lrpdb pdb2 -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - @$(call msg,"lrpdb pdb2 open completed") - $(KUBECTL) get lrpdb pdb2 -n $(PDBNAMESPACE) - - -run03.1: - @$(call msg,"clone pdb1-->pdb3") - $(KUBECTL) apply -f $(LRPDBCLONE1) - $(KUBECTL) wait --for jsonpath='{.status.phase'}="Ready" lrpdb pdb3 -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - @$(call msg,"clone pdb1-->pdb3 completed") - $(KUBECTL) get lrpdb pdb3 -n $(PDBNAMESPACE) - - -run03.2: - @$(call msg,"clone pdb2-->pdb4") - $(KUBECTL) apply -f $(LRPDBCLONE2) - $(KUBECTL) wait --for jsonpath='{.status.phase'}="Ready" lrpdb pdb4 -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - @$(call msg,"clone pdb2-->pdb4 completed") - $(KUBECTL) get lrpdb pdb3 -n $(PDBNAMESPACE) - - -run04.1: - @$(call msg,"lrpdb pdb1 close") - $(KUBECTL) apply -f $(LRPDBCLOSE1) - $(KUBECTL) wait --for jsonpath='{.status.openMode'}="MOUNTED" lrpdb pdb1 -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - @$(call msg, "lrpdb pdb1 close completed") - $(KUBECTL) get lrpdb pdb1 -n $(PDBNAMESPACE) - -run04.2: - @$(call msg,"lrpdb pdb2 close") - $(KUBECTL) apply -f $(LRPDBCLOSE2) - $(KUBECTL) wait --for jsonpath='{.status.openMode'}="MOUNTED" lrpdb pdb2 -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - @$(call msg,"lrpdb pdb2 close completed") - $(KUBECTL) get lrpdb pdb2 -n $(PDBNAMESPACE) - -run05.1: - @$(call msg,"lrpdb pdb1 unplug") - $(KUBECTL) apply -f $(LRPDBUNPLUG1) - $(KUBECTL) wait --for=delete lrpdb pdb1 -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - @$(call msg,"lrpdb pdb1 unplug completed") - -run06.1: - @$(call msg, "lrpdb pdb1 plug") - $(KUBECTL) apply -f $(LRPDBPLUG1) - $(KUBECTL) wait --for jsonpath='{.status.phase'}="Ready" lrpdb pdb1 -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - @$(call msg, "lrpdb pdb1 plug completed") - $(KUBECTL) get lrpdb pdb1 -n $(PDBNAMESPACE) - -run07.1: - @$(call msg,"lrpdb pdb1 delete ") - - $(KUBECTL) apply -f $(LRPDBCLOSE1) - $(KUBECTL) wait --for jsonpath='{.status.openMode'}="MOUNTED" lrpdb pdb1 -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - $(KUBECTL) apply -f $(LRPDBDELETE1) - $(KUBECTL) wait --for=delete lrpdb pdb1 -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - @$(call msg,"lrpdb pdb1 delete") - $(KUBECTL) get lrpdb -n $(PDBNAMESPACE) - -run99.1: - $(KUBECTL) delete lrest cdb-dev -n $(LRSNAMESPACE) - $(KUBECTL) wait --for=delete lrest cdb-dev -n $(LRSNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - $(KUBECTL) get lrest -n $(LRSNAMESPACE) - $(KUBECTL) get lrpdb -n $(PDBNAMESPACE) - -runall01: run00 run01.1 run01.2 run02.1 run02.2 run03.1 run03.2 run04.1 run04.2 run05.1 run06.1 run07.1 - - diff --git a/docs/multitenant/ords-based/NamespaceSeg.md b/docs/multitenant/ords-based/NamespaceSeg.md deleted file mode 100644 index 6738fe56..00000000 --- a/docs/multitenant/ords-based/NamespaceSeg.md +++ /dev/null @@ -1,14 +0,0 @@ - - -# Namespace segregation - -With the namespace segregation pdb controller and cdb controller run in different namespaces. The new functionality introduces a new parameter (the cdb namespace) in pdb crd definition. In case you don't need the namespace segregation you have to sepcify the namespace name that you are using for yours crd and pods anyway. Refer to usercase01 and usecase02 to see single namespace configuration. Refer to usecase03 to see examples of namespace segregation. - -# Secrets - -In order to use multiple namespace we need to create approriate secrets in each namespace. Tls certificate secrets must be created in all namespaces (db-ca db-tls). - -![general_schema](./images/K8S_NAMESPACE_SEG.png) - - - diff --git a/docs/multitenant/ords-based/README.md b/docs/multitenant/ords-based/README.md deleted file mode 100644 index edfd0208..00000000 --- a/docs/multitenant/ords-based/README.md +++ /dev/null @@ -1,411 +0,0 @@ - - -# Oracle Multitenant Database Controllers - -The Oracle Database Operator for Kubernetes uses two controllers to manage the [Pluggable Database lifecycle][oradocpdb] - -- CDB controller -- PDB controller - -By using CDB/PDB controllers, you can perform the following actions **CREATE**, **MODIFY(OPEN/COSE)**, **DELETE**, **CLONE**, **PLUG** and **UNPLUG** against pluggable database - -Examples are located under the following directories: - -- the directories [`Usecase`](./usecase/) and [`usecase01`](./usecase01/) contain a [configuration file](./usecase/parameters.txt) where you can specify all the details of your environment. A [`makefile`](./usecase/makefile) takes this file as input to generate all of the `yaml` files. There is no need to edit `yaml` files one by one. -- [Singlenamespace provisioning](./provisioning/singlenamespace/) This file contains base example files that you can use to manage the PDB and CDB within a single namespace. -- [Multinamespace provisioning](./provisioning/multinamespace/) This file contains base example files that you can use to manage the PDB and CDB in different namespaces. -- [Usecase01](./usecase01/README.md) [Usecase02](./usecase02/README.md) This file contains other step-by-step examples; - -Automatic `yaml` generation is not available for the directory `usecase02` and provisioning directories. - -**NOTE** the CDB controller is not intended to manage the container database. The CDB controller is meant to provide a pod with a REST server connected to the container database that you can use to manage PDBs. - - -## Macro steps for setup - -- Deploy the Oracle Database Operator (operator, or `OraOperator`) -- [Create Ords based image for CDB pod](./provisioning/ords_image.md) -- [Container RDBMB user creation](#prepare-the-container-database-for-pdb-lifecycle-management-pdb-lm) -- Create certificates for https connection -- Create secrets for credentials and certificates -- Create CDB pod using the Ords based image - -## Oracle DB Operator Multitenant Database Controller Deployment - -To deploy `OraOperator`, use this [Oracle Database Operator for Kubernetes](https://github.com/oracle/oracle-database-operator/blob/main/README.md) step-by-step procedure. - -After the **Oracle Database Operator** is deployed, you can see the Oracle Database (DB) Operator Pods running in the Kubernetes Cluster. The multitenant controllers are deployed as part of the `OraOperator` deployment. You can see the CRDs (Custom Resource Definition) for the CDB and PDBs in the list of CRDs. The following output is an example of such a deployment: - -```bash -[root@test-server oracle-database-operator]# kubectl get ns -NAME STATUS AGE -cert-manager Active 32h -default Active 245d -kube-node-lease Active 245d -kube-public Active 245d -kube-system Active 245d -oracle-database-operator-system Active 24h <---- namespace to deploy the Oracle Database Operator - -[root@test-server oracle-database-operator]# kubectl get all -n oracle-database-operator-system -NAME READY STATUS RESTARTS AGE -pod/oracle-database-operator-controller-manager-665874bd57-dlhls 1/1 Running 0 28s -pod/oracle-database-operator-controller-manager-665874bd57-g2cgw 1/1 Running 0 28s -pod/oracle-database-operator-controller-manager-665874bd57-q42f8 1/1 Running 0 28s - -NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE -service/oracle-database-operator-controller-manager-metrics-service ClusterIP 10.96.130.124 8443/TCP 29s -service/oracle-database-operator-webhook-service ClusterIP 10.96.4.104 443/TCP 29s - -NAME READY UP-TO-DATE AVAILABLE AGE -deployment.apps/oracle-database-operator-controller-manager 3/3 3 3 29s - -NAME DESIRED CURRENT READY AGE -replicaset.apps/oracle-database-operator-controller-manager-665874bd57 3 3 3 29s -[root@docker-test-server oracle-database-operator]# - -[root@test-server oracle-database-operator]# kubectl get crd -NAME CREATED AT -autonomouscontainerdatabases.database.oracle.com 2022-06-22T01:21:36Z -autonomousdatabasebackups.database.oracle.com 2022-06-22T01:21:36Z -autonomousdatabaserestores.database.oracle.com 2022-06-22T01:21:37Z -autonomousdatabases.database.oracle.com 2022-06-22T01:21:37Z -cdbs.database.oracle.com 2022-06-22T01:21:37Z <---- -certificaterequests.cert-manager.io 2022-06-21T17:03:46Z -certificates.cert-manager.io 2022-06-21T17:03:47Z -challenges.acme.cert-manager.io 2022-06-21T17:03:47Z -clusterissuers.cert-manager.io 2022-06-21T17:03:48Z -dbcssystems.database.oracle.com 2022-06-22T01:21:38Z -issuers.cert-manager.io 2022-06-21T17:03:49Z -oraclerestdataservices.database.oracle.com 2022-06-22T01:21:38Z -orders.acme.cert-manager.io 2022-06-21T17:03:49Z -pdbs.database.oracle.com 2022-06-22T01:21:39Z <--- -shardingdatabases.database.oracle.com 2022-06-22T01:21:39Z -singleinstancedatabases.database.oracle.com 2022-06-22T01:21:40Z -``` - - -## Prerequisites to manage PDB Life Cycle using Oracle DB Operator Multitenant Database Controller - -* [Prepare the container database (CDB) for PDB Lifecycle Management or PDB-LM](#prepare-cdb-for-pdb-lifecycle-management-pdb-lm) -* [Oracle REST Data Service or ORDS Image](#oracle-rest-data-service-ords-image) -* [Kubernetes Secrets](#kubernetes-secrets) -* [Kubernetes CRD for CDB](#cdb-crd) -* [Kubernetes CRD for PDB](#pdb-crd) - -## Prepare the container database for PDB Lifecycle Management (PDB-LM) - -Pluggable Database (PDB) management operations are performed in the Container Database (CDB). These operations include **create**, **clone**, **plug**, **unplug**, **delete**, **modify** and **map pdb**. - -To perform PDB lifecycle management operations, you must first use the following steps to define the default CDB administrator credentials on target CDBs: - -Create the CDB administrator user and grant the required privileges. In this example, the user is `C##DBAPI_CDB_ADMIN`. However, any suitable common username can be used. - -```SQL -SQL> conn /as sysdba - --- Create following users at the database level: - -ALTER SESSION SET "_oracle_script"=true; -DROP USER C##DBAPI_CDB_ADMIN cascade; -CREATE USER C##DBAPI_CDB_ADMIN IDENTIFIED BY CONTAINER=ALL ACCOUNT UNLOCK; -GRANT SYSOPER TO C##DBAPI_CDB_ADMIN CONTAINER = ALL; -GRANT SYSDBA TO C##DBAPI_CDB_ADMIN CONTAINER = ALL; -GRANT CREATE SESSION TO C##DBAPI_CDB_ADMIN CONTAINER = ALL; - - --- Verify the account status of the following usernames. They should not be in locked status: - -col username for a30 -col account_status for a30 -select username, account_status from dba_users where username in ('ORDS_PUBLIC_USER','C##DBAPI_CDB_ADMIN','APEX_PUBLIC_USER','APEX_REST_PUBLIC_USER'); -``` - -## OCI OKE (Kubernetes Cluster) - -You can use an [OKE in Oracle Cloud Infrastructure][okelink] to configure the controllers for PDB lifecycle management. **Note that there is no restriction about container database location; it can be anywhere (on Cloud or on-premises).** -To quickly create an OKE cluster in your OCI cloud environment you can use the following [link](./provisioning/quickOKEcreation.md). -In this setup example [provisioning example setup](./provisioning/example_setup_using_oci_oke_cluster.md), the Container Database is running on an OCI Exadata Database Cluster. - - -## Oracle REST Data Service (ORDS) Image - -The PDB Database controllers require a pod running a dedicated REST server image based on [ORDS][ordsdoc]. Read the following [document on ORDS images](./provisioning/ords_image.md) to build the ORDS images. - - -## Kubernetes Secrets - - Multitenant Controllers use Kubernetes Secrets to store the required credential and HTTPS certificates. - - **Note** In multi-namespace environments you must create specific Secrets for each namespaces. - -### Secrets for CERTIFICATES - -Create the certificates and key on your local host, and then use them to create the Kubernetes Secret. - -```bash -openssl genrsa -out ca.key 2048 -openssl req -new -x509 -days 365 -key ca.key -subj "/C=US/ST=California/L=SanFrancisco/O=oracle /CN=cdb-dev-ords /CN=localhost Root CA " -out ca.crt -openssl req -newkey rsa:2048 -nodes -keyout tls.key -subj "/C=US/ST=California/L=SanFrancisco/O=oracle /CN=cdb-dev-ords /CN=localhost" -out server.csr -echo "subjectAltName=DNS:cdb-dev-ords,DNS:www.example.com" > extfile.txt -openssl x509 -req -extfile extfile.txt -days 365 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out tls.crt -``` - -```bash -kubectl create secret tls db-tls --key="tls.key" --cert="tls.crt" -n oracle-database-operator-system -kubectl create secret generic db-ca --from-file=ca.crt -n oracle-database-operator-system -``` - -image_not_found - -**Note:** Remove temporary files after successfful Secret creation. - -### Secrets for CDB CRD - - **Note:** base64 encoded secrets are no longer supported; use OpenSSL secrets as documented in the following section. After successful creation of the CDB Resource, the CDB and PDB Secrets can be deleted from the Kubernetes system. Don't leave plaintext files containing sensitive data on disk. After loading the Secret, remove the plaintext file or move it to secure storage. - - ```bash - -export PRVKEY=ca.key -export PUBKEY=public.pem -WBUSERFILE=wbuser.txt -WBPASSFILE=wbpass.txt -CDBUSRFILE=cdbusr.txt -CDBPWDFILE=cdbpwd.txt -SYSPWDFILE=syspwd.txt -ORDPWDFILE=ordpwd.txt -PDBUSRFILE=pdbusr.txt -PDBPWDFILE=pdbpwd.txt - -# Webuser credential -echo [WBUSER] > ${WBUSERFILE} -echo [WBPASS] > ${WBPASSFILE} - -# CDB admin user credentioan -echo [CDBPWD] > ${CDBPWDFILE} -echo [CDBUSR] > ${CDBUSRFILE} - -# SYS Password -echo [SYSPWD] > ${SYSPWDFILE} - -# Ords Password -echo [ORDPWD] > ${ORDPWDFILE} - -## PDB admin credential -echo [PDBUSR] > ${PDBUSRFILE} -echo [PDBPWD] > ${PDBPWDFILE} - -#Secrets creation for pub and priv keys -openssl rsa -in ${PRVKEY} -outform PEM -pubout -out ${PUBKEY} -kubectl create secret generic pubkey --from-file=publicKey=${PUBKEY} -n ${CDBNAMESPACE} -kubectl create secret generic prvkey --from-file=privateKey=${PRVKEY} -n ${CDBNAMESPACE} -kubectl create secret generic prvkey --from-file=privateKey="${PRVKEY}" -n ${PDBNAMESPACE} - -#Password encryption -openssl rsautl -encrypt -pubin -inkey ${PUBKEY} -in ${WBUSERFILE} |base64 > e_${WBUSERFILE} -openssl rsautl -encrypt -pubin -inkey ${PUBKEY} -in ${WBPASSFILE} |base64 > e_${WBPASSFILE} -openssl rsautl -encrypt -pubin -inkey ${PUBKEY} -in ${CDBPWDFILE} |base64 > e_${CDBPWDFILE} -openssl rsautl -encrypt -pubin -inkey ${PUBKEY} -in ${CDBUSRFILE} |base64 > e_${CDBUSRFILE} -openssl rsautl -encrypt -pubin -inkey ${PUBKEY} -in ${SYSPWDFILE} |base64 > e_${SYSPWDFILE} -openssl rsautl -encrypt -pubin -inkey ${PUBKEY} -in ${ORDPWDFILE} |base64 > e_${ORDPWDFILE} -openssl rsautl -encrypt -pubin -inkey ${PUBKEY} -in ${PDBUSRFILE} |base64 > e_${PDBUSRFILE} -openssl rsautl -encrypt -pubin -inkey ${PUBKEY} -in ${PDBPWDFILE} |base64 > e_${PDBPWDFILE} - -#Ecrypted secrets creation -kubectl create secret generic wbuser --from-file=e_${WBUSERFILE} -n ${CDBNAMESPACE} -kubectl create secret generic wbpass --from-file=e_${WBPASSFILE} -n ${CDBNAMESPACE} -kubectl create secret generic wbuser --from-file=e_${WBUSERFILE} -n ${PDBNAMESPACE} -kubectl create secret generic wbpass --from-file=e_${WBPASSFILE} -n ${PDBNAMESPACE} -kubectl create secret generic cdbpwd --from-file=e_${CDBPWDFILE} -n ${CDBNAMESPACE} -kubectl create secret generic cdbusr --from-file=e_${CDBUSRFILE} -n ${CDBNAMESPACE} -kubectl create secret generic syspwd --from-file=e_${SYSPWDFILE} -n ${CDBNAMESPACE} -kubectl create secret generic ordpwd --from-file=e_${ORDPWDFILE} -n ${CDBNAMESPACE} -kubectl create secret generic pdbusr --from-file=e_${PDBUSRFILE} -n ${PDBNAMESPACE} -kubectl create secret generic pdbpwd --from-file=e_${PDBPWDFILE} -n ${PDBNAMESPACE} - -#Get rid of the swap files -rm ${WBUSERFILE} ${WBPASSFILE} ${CDBPWDFILE} ${CDBUSRFILE} \ - ${SYSPWDFILE} ${ORDPWDFILE} ${PDBUSRFILE} ${PDBPWDFILE} \ - e_${WBUSERFILE} e_${WBPASSFILE} e_${CDBPWDFILE} e_${CDBUSRFILE} \ - e_${SYSPWDFILE} e_${ORDPWDFILE} e_${PDBUSRFILE} e_${PDBPWDFILE} -``` - -Check Secrets details - -```bash -kubectl describe secrets syspwd -n cdbnamespace -Name: syspwd -Namespace: cdbnamespace -Labels: -Annotations: - -Type: Opaque - -Data -==== -e_syspwd.txt: 349 bytes -``` -Example of `yaml` file Secret section: - -```yaml -[...] - sysAdminPwd: - secret: - secretName: "syspwd" - key: "e_syspwd.txt" - ordsPwd: - secret: - secretName: "ordpwd" - key: "e_ordpwd.txt" -[...] -``` - -## CDB CRD - -The Oracle Database Operator Multitenant Controller creates the CDB as a custom resource object kind that models a target CDB as a native Kubernetes object. This object kind is used only to create Pods to connect to the target CDB to perform PDB-LM operations. Each CDB resource follows the CDB CRD as defined here: [`config/crd/bases/database.oracle.com_cdbs.yaml`](../../../config/crd/bases/database.oracle.com_cdbs.yaml) - -To create a CDB CRD, use this example`.yaml` file: [`cdb_create.yaml`](../multitenant/provisioning/singlenamespace/cdb_create.yaml) - -**Note:** The password and username fields in this *cdb.yaml* Yaml are the Kubernetes Secrets created earlier in this procedure. For more information, see the section [Kubernetes Secrets](https://kubernetes.io/docs/concepts/configuration/secret/). To understand more about creating secrets for pulling images from a Docker private registry, see [Kubernetes Private Registry Documenation]( https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/). - -Create a CDB CRD Resource example - -```bash -kubectl apply -f cdb_create.yaml -``` - -see [usecase01][uc01] and usecase02[uc02] for more information about file configuration - -## PDB CRD - -The Oracle Database Operator Multitenant Controller creates the PDB object kind as a custom resource that models a PDB as a native Kubernetes object. There is a one-to-one mapping between the actual PDB and the Kubernetes PDB Custom Resource. You cannot have more than one Kubernetes resource for a target PDB. This PDB resource can be used to perform PDB-LM operations by specifying the action attribute in the PDB Specs. Each PDB resource follows the PDB CRD as defined here: [config/crd/bases/database.oracle.com_pdbs.yaml](../../../config/crd/bases/database.oracle.com_pdbs.yaml) - -Yaml file [pdb_create.yaml](../multitenant/provisioning/singlenamespace/pdb_create.yaml) to create a pdb - -```bash -kubectl apply -f pdb_create.yaml -``` - -## CRD TABLE PARAMETERS - -| yaml file parameters | value | description /ords parameter | CRD | -|------------------ |--------------------------- |-------------------------------------------------------------------------------|-----------| -| dbserver | or | [--db-hostname][1] | CDB | -| dbTnsurl | | [--db-custom-url/db.customURL][dbtnsurl] | CDB | -| port | | [--db-port][2] | CDB | -| cdbName | | Container Name | CDB | -| name | | ORDS podname prefix in `cdb.yaml` | CDB | -| name | | Pdb resource in `pdb.yaml` | PDB | -| ordsImage | ords-dboper:latest | ORDS pod public container registry | CDB | -| pdbName | | Pluggable database (PDB) name | Container database (CDB) | -| servicename | | [--db-servicename][3] | CDB | -| sysadmin_user | | [--admin-user][adminuser] | CDB | -| sysadmin_pwd | | [--password-stdin][pwdstdin] | CDB | -| cdbadmin_user | | [db.cdb.adminUser][1] | CDB | -| cdbadmin_pwd | | [db.cdb.adminUser.password][cdbadminpwd] | CDB | -| webserver_user | | [https user][http] NOT A DB USER | CDB PDB | -| webserver_pwd | | [http user password][http] | CDB PDB | -| ords_pwd | | [ORDS_PUBLIC_USER password][public_user] | CDB | -| pdbTlsKey | | [standalone.https.cert.key][key] | PDB | -| pdbTlsCrt | | [standalone.https.cert][cr] | PDB | -| pdbTlsCat | | certificate authority | PDB | -| cdbTlsKey | | [standalone.https.cert.key][key] | CDB | -| cdbTlsCrt | | [standalone.https.cert][cr] | CDB | -| cdbTlsCat | | Certificate authority | CDB | -| cdbOrdsPrvKey | | Private key | CDB | -| pdbOrdsPrvKey | | Private key | PDB | -| xmlFileName | | Path for the unplug and plug operation | PDB | -| srcPdbName | | Name of the database that you want to be cloned | PDB | -| action | | Create open close delete clone plug unplug and map | PDB | -| deletePdbCascade | boolean | Delete PDBs cascade during CDB deletion | CDB | -| assertivePdbDeletion | boolean | Deleting the PDB crd means deleting the PDB as well | PDB | -| fileNameConversions | | Used for database cloning | PDB | -| totalSize | | `dbsize` | PDB | -| pdbState | | Change PDB state | PDB | -| modifyOption | | To be used along with `pdbState` | PDB | -| dropAction | | Delete datafiles during PDB deletion | PDB | -| sourceFileNameConversions | | [sourceFileNameConversions(optional): string][4] | PDB | -| tdeKeystorePath | | [tdeKeystorePath][tdeKeystorePath] | N/A | -| tdeExport | | [tdeExport] | N/A ] -| tdeSecret | | [tdeSecret][tdeSecret] | N/A | -| tdePassword | | [tdeSecret][tdeSecret] | N/A | - - - - -## Usecases files list - -### Single Namespace - -1. [Create CDB](./provisioning/singlenamespace/cdb_create.yaml) -2. [Create PDB](./provisioning/singlenamespace/pdb_create.yaml) -3. [Clone PDB](./provisioning/singlenamespace/pdb_clone.yaml) -4. [Open PDB](./provisioning/singlenamespace/pdb_open.yaml) -4. [Close PDB](./provisioning/singlenamespace/pdb_close.yaml) -5. [Delete PDB](./provisioning/singlenamespace/pdb_delete.yaml) -6. [Unplug PDB](./provisioning/singlenamespace/pdb_unplug.yaml) -7. [Plug PDB](./provisioning/singlenamespace/pdb_plug.yaml) - -### Multiple namespace (cdbnamespace,dbnamespace) - -1. [Create CDB](./provisioning/multinamespace/cdb_create.yaml) -2. [Create PDB](./provisioning/multinamespace/pdb_create.yaml) -3. [Clone PDB](./provisioning/multinamespace/pdb_clone.yaml) -4. [Open PDB](./provisioning/multinamespace/pdb_open.yaml) -4. [Close PDB](./provisioning/multinamespace/pdb_close.yaml) -5. [Delete PDB](./provisioning/multinamespace/pdb_delete.yaml) -6. [Unplug PDB](./provisioning/multinamespace/pdb_unplug.yaml) - -## Known issues - - - ORDS installation failure if pluaggable databases in the container db are not openedS - - - Version 1.1.0: encoded password for https authentication may include carriage return as consequence the https request fails with http 404 error. W/A generate encoded password using **printf** instead of **echo**. - - - pdb controller authentication suddenly fails without any system change. Check the certificate expiration date **openssl .... -days 365** - - - Nothing happens after applying cdb yaml files: Make sure to have properly configured the WHATCH_NAMESPACE list in the operator yaml file - - [okelink]:https://docs.oracle.com/en-us/iaas/Content/ContEng/Concepts/contengoverview.htm - - [ordsdoc]:https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/23.1/index.html - - [uc01]:../multitenant/usecase01/README.md - - [uc02]:../multitenant/usecase02/README.md - - [oradocpdb]:https://docs.oracle.com/en/database/oracle/oracle-database/21/multi/introduction-to-the-multitenant-architecture.html#GUID-AB84D6C9-4BBE-4D36-992F-2BB85739329F - - [1]:https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/22.2/ordig/installing-and-configuring-oracle-rest-data-services.html#GUID-E9625FAB-9BC8-468B-9FF9-443C88D76FA1:~:text=Table%202%2D2%20Command%20Options%20for%20Command%2DLine%20Interface%20Installation - - [2]:https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/22.2/ordig/installing-and-configuring-oracle-rest-data-services.html#GUID-E9625FAB-9BC8-468B-9FF9-443C88D76FA1:~:text=Table%202%2D2%20Command%20Options%20for%20Command%2DLine%20Interface%20Installation - - [3]:https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/22.2/ordig/installing-and-configuring-oracle-rest-data-services.html#GUID-DAA027FA-A4A6-43E1-B8DD-C92B330C2341:~:text=%2D%2Ddb%2Dservicename%20%3Cstring%3E - - [4]:https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/22.3/orrst/op-database-pdbs-post.html - -[adminuser]:https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/22.2/ordig/installing-and-configuring-oracle-rest-data-services.html#GUID-A9AED253-4EEC-4E13-A0C4-B7CE82EC1C22:~:text=Table%202%2D6%20Command%20Options%20for%20Uninstall%20CLI - -[public_user]:https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/22.2/ordig/using-multitenant-architecture-oracle-rest-data-services.html#GUID-E64A141A-A71F-4979-8D33-C5F8496D3C19:~:text=Preinstallation%20Tasks%20for%20Oracle%20REST%20Data%20Services%20CDB%20Installation - -[key]:https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/22.2/ordig/about-REST-configuration-files.html#GUID-006F916B-8594-4A78-B500-BB85F35C12A0:~:text=standalone.https.cert.key - -[cr]:https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/22.2/ordig/about-REST-configuration-files.html#GUID-006F916B-8594-4A78-B500-BB85F35C12A0 - -[cdbadminpwd]:https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/22.2/ordig/about-REST-configuration-files.html#GUID-006F916B-8594-4A78-B500-BB85F35C12A0:~:text=Table%20C%2D1%20Oracle%20REST%20Data%20Services%20Configuration%20Settings - - -[pwdstdin]:https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/22.2/ordig/installing-and-configuring-oracle-rest-data-services.html#GUID-88479C84-CAC1-4133-A33E-7995A645EC05:~:text=default%20database%20pool.-,2.1.4.1%20Understanding%20Command%20Options%20for%20Command%2DLine%20Interface%20Installation,-Table%202%2D2 - -[http]:https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/22.2/ordig/installing-and-configuring-oracle-rest-data-services.html#GUID-BEECC057-A8F5-4EAB-B88E-9828C2809CD8:~:text=Example%3A%20delete%20%5B%2D%2Dglobal%5D-,user%20add,-Add%20a%20user - -[dbtnsurl]:https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/22.2/ordig/installing-and-configuring-oracle-rest-data-services.html#GUID-A9AED253-4EEC-4E13-A0C4-B7CE82EC1C22 - -[tdeKeystorePath]:https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/21.4/orrst/op-database-pdbs-pdb_name-post.html - -[tdeSecret]:https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/ADMINISTER-KEY-MANAGEMENT.html#GUID-E5B2746F-19DC-4E94-83EC-A6A5C84A3EA9 -~ - - - - - diff --git a/docs/multitenant/ords-based/images/K8S_NAMESPACE_SEG.png b/docs/multitenant/ords-based/images/K8S_NAMESPACE_SEG.png deleted file mode 100644 index 594471ed54a6c980c9e63ab2f538c40a32a5dcb8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 269014 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

{$Kdx|9JL& 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 Z&B+Jld>=kvz=MAjuBcwlyJ#Hv{{cF13`_t3 diff --git a/docs/multitenant/ords-based/images/K8S_SECURE2.png b/docs/multitenant/ords-based/images/K8S_SECURE2.png deleted file mode 100644 index b9713d7cbbaa2cb1d7dc6634e8fef304aba79a99..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 130742 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

FcZ;+!m)=0*A!M^af8enjqQ3Ut|;QL7H6xvnR*~ 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 diff --git a/docs/multitenant/ords-based/images/K8S_SECURE3.png b/docs/multitenant/ords-based/images/K8S_SECURE3.png deleted file mode 100644 index b70123c12a2035af0533c17eaff662d2f11acb01..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 130498 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%0AV_A*$tYHe|B^myZN)Q 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 U`ptZk@GAJ(SlL+??IA|~8+Afp&;S4c diff --git a/docs/multitenant/ords-based/images/makerunall.png b/docs/multitenant/ords-based/images/makerunall.png deleted file mode 100644 index ab856f90ca3a29bbaece7f4d80b0056801243613..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 211874 zcmdSB2T+r17cLq_K|w^6CJG3sAfSk10fZ=31Zg52A|TR1sY*#yq)11kC?y~u(xijb zNLNvMM|zds2_?x{5%>P=5$3-}|ohtfzc0Rg~mtsadHJ2n4PC zO=&d*V$WFwg5v8wN_gcmv4tM~wZ~pkUVR_@&u!m*ANcvGgN&BL9oq*E&W3g-2vZwd zYm>9~#&#wqHuh$=4)YW^2?XKE~ueVPCp~pW2LVZXQ=XbW~iAM?&MEi|sWpe5AC9d;|Wf#5+x!;1?ehh5ZwD zSMbsm9pU1lq-VJyqiTllLmsxUDlSQVYjgd_d@hrvW9P@Rbz5X=YRQLGjYIS?8oZxb zj2jakzAHcYOsJN{*k7pQwnjK_b?q({-m|KQ;pg8k)^;hcDCxW~&)>%*Ifx>1z3FbC z#kcF;@<)#D#yS>=AC42MiV&1*@}#NUdV0}xu*k(Rv-ZS$OSeWpj7|v?F$hs&#W@s`Ec#070PQHH*S2=)G`we?K#H6-pk=}TBM?*pQ)|Gx1e^M zo#xf*!Me}a_b@YDpK*#GA2w2PbrqDYiM%$T6h-O%XylOHFEx~RCHdRevg95LC9#P( z1YR;wWT7^}5ht4ph77CNes1HORktu=aSoexE^WJ1#F~gruZD8UJAz{Zyk=&~4|@D{ zrGigqOm$Y4_ZVmy(#%&6sjN-YA>sm^c(;0oHJA0zqj48cA6GZ7+$E;?)6(OmYsUFU zSVpggR~-Jv56|yzh^?tVdp1(-jt4WTlXSS{H3ijhC(7+v1c$ifLqpCP)Z-h1IJ?2? zdkCwUk2jjR@mLAku&Ah+VhYj{Gk&JWGx!~sqNPP;roG)gF|*)co4!mAgH60K`8C_Q zK96}UanCf7aND;rdX3`g0FkiJpG_fX)8}g*5D@9Ke$K0G<22&Bp>+WLe7Rprp^afr zUkOXpo0L{`r-=QbXl&4c@D=Wr2DZ|b#kwqwv|R{7Y3U)Q7lY4tBMzO*COIlKAeZFi zXzFSE*tyz~aNnw2x19EDZ#3e6l)3KvUVqWtZD*N{{RvB7#LNx%`u7SU1gpysmSHhj zT{`A8N$yDA+1YsYGls^-w3e28)6*HV3MkX9N0D)@PE9B9SW!>7gL(%JRF7%r-ZNPb zDwZuLNxc5}@eAES*>=}?LoK&bgQKvU>H=naLpWIW@rP0Oi#dNw+cuRlGFCmz?49dD z8DCa(RM4jPbYfW+T3qk_?W&|-t*Q+4yOQ@GS*WZHI9+3^v?WFxi{xMn;uwjJU_KXk z%-efx4Dl3m>vm1t0Fg;^yg>%7awyMvzH0}mlAkw~>trw(22am;T&7@YOA7)) z$ItgrC;mFT5iyu2`K*QNn{TtJP>^0_|yBSGqwv;0gQyd7E(tEqU9Xb3EuFoN_ zU>K`*=k^r$&cR_Z#tG!u(5dnwJk{LpPMvUYaEQyur%0MFh@U<8^zz`4~#Xeh%-#EW!liL_I<&WqT=SxyjPM2*sME4**;0rNQ zpSyFj9@#x#o72}8YGV`kr7|%=JWXBim%3-b{*f3&dKM@bbbbXzVjM_L9GpXus1YB2 z=I!qna~j-gPcy0O>{~;otgcyaFCa-gh`8B#jGThWDTa;?eDVN^J%YG_ZC+WXP_Ku3 zVLR9F`Rxa7kZoUOP>J*O>pNw}ROnTEk?<28$e5`>wknU77OVk>ICY^X%~=iPpSqDf z5wsE1McQ~1K+VsurL8++c$;btO{EfLUbC{kD%0ACdR&roOyW3o(%~OS%+TJaRHsh4 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 D4UFOA> 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 sLIlI>es1C)7S}^Z@9@*K% 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 RK2h*>P)%1gNBQWb{{_V`YY_kd diff --git a/docs/multitenant/ords-based/images/makesecrets_1_1.png b/docs/multitenant/ords-based/images/makesecrets_1_1.png deleted file mode 100644 index f0f6f21569bfa17aab7bd37827d97aa041bb06ac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 117953 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 diff --git a/docs/multitenant/ords-based/openssl_schema.jpg b/docs/multitenant/ords-based/openssl_schema.jpg deleted file mode 100644 index 4453d52f27eaa83c6891a9ba6c8bef418b89ec8e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 54221 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 - -# Build ORDS Docker Image - -This file contains the steps to create an ORDS based image to be used solely by the PDB life cycle multitentant controllers. - -**NOTE:** It is assumed that before this step, you have followed the [prerequisite](./../README.md#prerequsites-to-manage-pdb-life-cycle-using-oracle-db-operator-on-prem-database-controller) steps. - -#### Clone the software using git: - -> Under directory ./oracle-database-operator/ords you will find the [Dockerfile](../../../ords/Dockerfile) and [runOrdsSSL.sh](../../../ords/runOrdsSSL.sh) required to build the image. - -```sh - git clone git@orahub.oci.oraclecorp.com:rac-docker-dev/oracle-database-operator.git - cd oracle-database-operator/ords/ -``` - -#### Login to the registry: container-registry.oracle.com - -**NOTE:** To login to this registry, you will need to the URL https://container-registry.oracle.com , Sign in, then click on "Java" and then accept the agreement. - -```bash -docker login container-registry.oracle.com -``` - -#### Login to the your container registry - -Login to a repo where you want to push your docker image (if needed) to pull during deployment in your environment. - -```bash -docker login -``` - -#### Build the image - -Build the docker image by using below command: - -```bash -docker build -t oracle/ords-dboper:latest . -``` -> If your are working behind a proxy mind to specify https_proxy and http_proxy during image creation - -Check the docker image details using: - -```bash -docker images -``` - -> OUTPUT EXAMPLE -```bash -REPOSITORY TAG IMAGE ID CREATED SIZE -oracle/ords-dboper latest fdb17aa242f8 4 hours ago 1.46GB - -``` - -#### Tag and push the image - -Tag and push the image to your image repository. - -NOTE: We have the repo as `phx.ocir.io//oracle/ords:latest`. Please change as per your environment. - -```bash -docker tag oracle/ords-dboper:ords-latest phx.ocir.io//oracle/ords:latest -docker push phx.ocir.io//oracle/ords:latest -``` - -#### In case of private image - -If you the image not be public then yuo need to create a secret containing the password of your image repository. -Create a Kubernetes Secret for your docker repository to pull the image during deployment using the below command: - -```bash -kubectl create secret generic container-registry-secret --from-file=.dockerconfigjson=./.docker/config.json --type=kubernetes.io/dockerconfigjson -n oracle-database-operator-system -``` - -Use the parameter `ordsImagePullSecret` to specify the container secrets in pod creation yaml file - -#### [Image createion example](../usecase01/logfiles/BuildImage.log) - - - diff --git a/docs/multitenant/ords-based/provisioning/quickOKEcreation.md b/docs/multitenant/ords-based/provisioning/quickOKEcreation.md deleted file mode 100644 index 19d9323e..00000000 --- a/docs/multitenant/ords-based/provisioning/quickOKEcreation.md +++ /dev/null @@ -1,136 +0,0 @@ - - -### Quick Oke creation script - -Use this script to create quickly an OKE cluster in your OCI. - -#### Prerequisties: -- ocicli is properly configured on your client -- make is installed on your client -- vnc is already configured -- ssh key is configured (public key available under directory ~/.ssh) -- edit make providing all the information about your compartment, vnc,subnet,lb subnet and nd subnet (exported variables in the header section) - - -#### Execution: - -```bash -make all -``` - -Monitor the OKE from OCI console - -#### Makefile -```makefile -.EXPORT_ALL_VARIABLES: - -export CMPID=[.... COMPARTMENT ID.............] -export VNCID=[.... VNC ID ....................] -export ENDID=[.... SUBNET END POINT ID .......] -export LBSID=[.....LB SUBNET ID...............] -export NDSID=[.....NODE SUBNET ID.............] - - -#ssh public key -export KEYFL=~/.ssh/id_rsa.pub - -#cluster version -export KSVER=v1.27.2 - -#cluster name -export CLUNM=myoke - -#pool name -export PLNAM=Pool1 - -#logfile -export LOGFILE=./clustoke.log - -#shape -export SHAPE=VM.Standard.E4.Flex - -OCI=/home/oracle/bin/oci -CUT=/usr/bin/cut -KUBECTL=/usr/bin/kubectl -CAT=/usr/bin/cat - -all: cluster waitcluster pool waitpool config desccluster - -cluster: - @echo " - CREATING CLUSTER " - @$(OCI) ce cluster create \ - --compartment-id $(CMPID) \ - --kubernetes-version $(KSVER) \ - --name $(CLUNM) \ - --vcn-id $(VNCID) \ - --endpoint-subnet-id $(ENDID) \ - --service-lb-subnet-ids '["'$(LBSID)'"]' \ - --endpoint-public-ip-enabled true \ - --persistent-volume-freeform-tags '{"$(CLUNM)" : "OKE"}' 1>$(LOGFILE) 2>&1 - -waitcluster: - @while [ `$(OCI) ce cluster list --compartment-id $(CMPID) \ - --name $(CLUNM) --lifecycle-state ACTIVE --query data[0].id \ - --raw-output |wc -l ` -eq 0 ] ; do sleep 5 ; done - @echo " - CLUSTER CREATED" - - -pool: - @echo " - CREATING POOL" - @$(eval PBKEY :=$(shell $(CAT) $(KEYFL)|grep -v " PUBLIC KEY")) - @$(OCI) ce node-pool create \ - --cluster-id `$(OCI) ce cluster list --compartment-id $(CMPID) \ - --name $(CLUNM) --lifecycle-state ACTIVE --query data[0].id --raw-output` \ - --compartment-id $(CMPID) \ - --kubernetes-version $(KSVER) \ - --name $(PLNAM) \ - --node-shape $(SHAPE) \ - --node-shape-config '{"memoryInGBs": 8.0, "ocpus": 1.0}' \ - --node-image-id `$(OCI) compute image list \ - --operating-system 'Oracle Linux' --operating-system-version 7.9 \ - --sort-by TIMECREATED --compartment-id $(CMPID) --shape $(SHAPE) \ - --query data[1].id --raw-output` \ - --node-boot-volume-size-in-gbs 50 \ - --ssh-public-key "$(PBKEY)" \ - --size 3 \ - --placement-configs '[{"availabilityDomain": "'`oci iam availability-domain list \ - --compartment-id $(CMPID) \ - --query data[0].name --raw-output`'", "subnetId": "'$(NDSID)'"}]' 1>>$(LOGFILE) 2>&1 - -waitpool: - $(eval CLSID :=$(shell $(OCI) ce cluster list --compartment-id $(CMPID) \ - --name $(CLUNM) --lifecycle-state ACTIVE --query data[0].id --raw-output)) - @while [ `$(OCI) ce node-pool list --compartment-id $(CMPID) \ - --lifecycle-state ACTIVE --cluster-id $(CLSID) \ - --query data[0].id --raw-output |wc -l ` -eq 0 ] ; do sleep 5 ; done - @sleep 10 - $(eval PLLID :=$(shell $(OCI) ce node-pool list --compartment-id $(CMPID) \ - --lifecycle-state ACTIVE --cluster-id $(CLSID) --query data[0].id --raw-output)) - @echo " - POOL CREATED" - -config: - @$(OCI) ce cluster create-kubeconfig --cluster-id \ - `$(OCI) ce cluster list \ - --compartment-id $(CMPID) --name $(CLUNM) --lifecycle-state ACTIVE \ - --query data[0].id --raw-output` \ - --file $(HOME)/.kube/config --region \ - `$(OCI) ce cluster list \ - --compartment-id $(CMPID) --name $(CLUNM) --lifecycle-state ACTIVE \ - --query data[0].id --raw-output|$(CUT) -f4 -d. ` \ - --token-version 2.0.0 --kube-endpoint PUBLIC_ENDPOINT - @echo " - KUBECTL PUBLIC ENDPOINT CONFIGURED" - - -desccluster: - @$(eval TMPSP := $(shell date "+%y/%m/%d:%H:%M" )) - $(KUBECTL) get nodes -o wide - $(KUBECTL) get storageclass - -checkvol: - $(OCI) bv volume list \ - --compartment-id $(CMPID) \ - --lifecycle-state AVAILABLE \ - --query 'data[?"freeform-tags".stackgres == '\''OKE'\''].id' -``` - - diff --git a/docs/multitenant/ords-based/provisioning/singlenamespace/cdb_create.yaml b/docs/multitenant/ords-based/provisioning/singlenamespace/cdb_create.yaml deleted file mode 100644 index 5e020de6..00000000 --- a/docs/multitenant/ords-based/provisioning/singlenamespace/cdb_create.yaml +++ /dev/null @@ -1,49 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: CDB -metadata: - name: cdb-dev - namespace: oracle-database-operator-system -spec: - cdbName: "DB12" - ordsImage: ".............your registry............./ords-dboper:latest" - ordsImagePullPolicy: "Always" - dbTnsurl : "...Container tns alias....." - replicas: 1 - sysAdminPwd: - secret: - secretName: "[...]" - key: "[...]" - ordsPwd: - secret: - secretName: "[...]" - key: "[...]" - cdbAdminUser: - secret: - secretName: "[...]" - key: "[...]" - cdbAdminPwd: - secret: - secretName: "[...]" - key: "[...]" - webServerUser: - secret: - secretName: "[...]" - key: "[...]" - webServerPwd: - secret: - secretName: "[...]" - key: "[...]" - cdbTlsKey: - secret: - secretName: "[...]" - key: "[...]" - cdbTlsCrt: - secret: - secretName: "[...]" - key: "[...]" - cdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" - - diff --git a/docs/multitenant/ords-based/provisioning/singlenamespace/cdb_secret.yaml b/docs/multitenant/ords-based/provisioning/singlenamespace/cdb_secret.yaml deleted file mode 100644 index 567b90a4..00000000 --- a/docs/multitenant/ords-based/provisioning/singlenamespace/cdb_secret.yaml +++ /dev/null @@ -1,17 +0,0 @@ -# -# Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. -# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. -# -apiVersion: v1 -kind: Secret -metadata: - name: cdb1-secret - namespace: oracle-database-operator-system -type: Opaque -data: - ords_pwd: ".....base64 encoded password...." - sysadmin_pwd: ".....base64 encoded password...." - cdbadmin_user: ".....base64 encoded password...." - cdbadmin_pwd: ".....base64 encoded password...." - webserver_user: ".....base64 encoded password...." - webserver_pwd: ".....base64 encoded password...." diff --git a/docs/multitenant/ords-based/provisioning/singlenamespace/pdb_clone.yaml b/docs/multitenant/ords-based/provisioning/singlenamespace/pdb_clone.yaml deleted file mode 100644 index 964d1e5e..00000000 --- a/docs/multitenant/ords-based/provisioning/singlenamespace/pdb_clone.yaml +++ /dev/null @@ -1,60 +0,0 @@ -# -# Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. -# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. -# -apiVersion: database.oracle.com/v1alpha1 -kind: PDB -metadata: - name: pdb2 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "oracle-database-operator-system" - cdbName: "DB12" - pdbName: "pdb2_clone" - srcPdbName: "pdbdev" - fileNameConversions: "NONE" - totalSize: "UNLIMITED" - tempSize: "UNLIMITED" - assertivePdbDeletion: true - adminName: - secret: - secretName: "[...]" - key: "[...]" - adminPwd: - secret: - secretName: "[...]" - key: "[...]" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "pdb1-secret" - key: "webserver_user" - webServerPwd: - secret: - secretName: "pdb1-secret" - key: "webserver_pwd" - secretName: "[...]" - key: "[...]" - webServerPwd: - secret: - secretName: "[...]" - key: "[...]" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" - action: "Clone" diff --git a/docs/multitenant/ords-based/provisioning/singlenamespace/pdb_close.yaml b/docs/multitenant/ords-based/provisioning/singlenamespace/pdb_close.yaml deleted file mode 100644 index 06d92469..00000000 --- a/docs/multitenant/ords-based/provisioning/singlenamespace/pdb_close.yaml +++ /dev/null @@ -1,48 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb1 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "oracle-database-operator-system" - cdbName: "DB12" - pdbName: "pdbdev" - adminName: - secret: - secretName: "[...]" - key: "[...]" - adminPwd: - secret: - secretName: "[...]" - key: "[...]" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "[...]" - key: "[...]" - webServerPwd: - secret: - secretName: "[...]" - key: "[...]" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" - pdbState: "CLOSE" - modifyOption: "IMMEDIATE" - action: "Modify" - diff --git a/docs/multitenant/ords-based/provisioning/singlenamespace/pdb_create.yaml b/docs/multitenant/ords-based/provisioning/singlenamespace/pdb_create.yaml deleted file mode 100644 index 2744223e..00000000 --- a/docs/multitenant/ords-based/provisioning/singlenamespace/pdb_create.yaml +++ /dev/null @@ -1,51 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb1 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "oracle-database-operator-system" - cdbName: "DB12" - pdbName: "pdbdev" - adminName: - secret: - secretName: "[...]" - key: "[...]" - adminPwd: - secret: - secretName: "[...]" - key: "[...]" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "[...]" - key: "[...]" - webServerPwd: - secret: - secretName: "[...]" - key: "[...]" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" - fileNameConversions: "NONE" - tdeImport: false - totalSize: "1G" - tempSize: "100M" - action: "Create" - assertivePdbDeletion: true - diff --git a/docs/multitenant/ords-based/provisioning/singlenamespace/pdb_delete.yaml b/docs/multitenant/ords-based/provisioning/singlenamespace/pdb_delete.yaml deleted file mode 100644 index 523ac1cb..00000000 --- a/docs/multitenant/ords-based/provisioning/singlenamespace/pdb_delete.yaml +++ /dev/null @@ -1,39 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb1 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "oracle-database-operator-system" - pdbName: "pdbdev" - action: "Delete" - dropAction: "INCLUDING" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "[...]" - key: "[...]" - webServerPwd: - secret: - secretName: "[...]" - key: "[...]" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" - - diff --git a/docs/multitenant/ords-based/provisioning/singlenamespace/pdb_open.yaml b/docs/multitenant/ords-based/provisioning/singlenamespace/pdb_open.yaml deleted file mode 100644 index 866db3e4..00000000 --- a/docs/multitenant/ords-based/provisioning/singlenamespace/pdb_open.yaml +++ /dev/null @@ -1,47 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb1 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "oracle-database-operator-system" - cdbName: "DB12" - pdbName: "pdbdev" - adminName: - secret: - secretName: "[...]" - key: "[...]" - adminPwd: - secret: - secretName: "[...]" - key: "[...]" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "[...]" - key: "[...]" - webServerPwd: - secret: - secretName: "[...]" - key: "[...]" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" - action: "Modify" - pdbState: "OPEN" - modifyOption: "READ WRITE" diff --git a/docs/multitenant/ords-based/provisioning/singlenamespace/pdb_plug.yaml b/docs/multitenant/ords-based/provisioning/singlenamespace/pdb_plug.yaml deleted file mode 100644 index e6605276..00000000 --- a/docs/multitenant/ords-based/provisioning/singlenamespace/pdb_plug.yaml +++ /dev/null @@ -1,55 +0,0 @@ -# -# Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. -# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. -# -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb1 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "oracle-database-operator-system" - cdbName: "DB12" - pdbName: "pdbdev" - xmlFileName: "/tmp/pdb.xml" - fileNameConversions: "NONE" - sourceFileNameConversions: "NONE" - copyAction: "MOVE" - totalSize: "1G" - tempSize: "100M" - action: "Plug" - assertivePdbDeletion: true - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "pdb1-secret" - key: "webserver_user" - webServerPwd: - secret: - secretName: "pdb1-secret" - key: "webserver_pwd" - secretName: "[...]" - key: "[...]" - webServerPwd: - secret: - secretName: "[...]" - key: "[...]" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" - diff --git a/docs/multitenant/ords-based/provisioning/singlenamespace/pdb_secret.yaml b/docs/multitenant/ords-based/provisioning/singlenamespace/pdb_secret.yaml deleted file mode 100644 index 60d95d76..00000000 --- a/docs/multitenant/ords-based/provisioning/singlenamespace/pdb_secret.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# -# Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. -# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. -# -apiVersion: v1 -kind: Secret -metadata: - name: pdb1-secret - namespace: oracle-database-operator-system -type: Opaque -data: - sysadmin_user: ".....base64 encoded password...." - sysadmin_pwd: ".....base64 encoded password...." - webserver_user: ".....base64 encoded password...." - webserver_pwd: ".....base64 encoded password...." - diff --git a/docs/multitenant/ords-based/provisioning/singlenamespace/pdb_unplug.yaml b/docs/multitenant/ords-based/provisioning/singlenamespace/pdb_unplug.yaml deleted file mode 100644 index 4e404efe..00000000 --- a/docs/multitenant/ords-based/provisioning/singlenamespace/pdb_unplug.yaml +++ /dev/null @@ -1,49 +0,0 @@ -# -# Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. -# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. -# -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb1 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "oracle-database-operator-system" - cdbName: "DB12" - pdbName: "pdbdev" - xmlFileName: "/tmp/pdb.xml" - action: "Unplug" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "pdb1-secret" - key: "webserver_user" - webServerPwd: - secret: - secretName: "pdb1-secret" - key: "webserver_pwd" - secretName: "[...]" - key: "[...]" - webServerPwd: - secret: - secretName: "[...]" - key: "[...]" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" - diff --git a/docs/multitenant/ords-based/usecase/README.md b/docs/multitenant/ords-based/usecase/README.md deleted file mode 100644 index b6f5e590..00000000 --- a/docs/multitenant/ords-based/usecase/README.md +++ /dev/null @@ -1,112 +0,0 @@ - - - -# Use case directory - -The use case directory contains the yaml files to test the multitenant controller functionalities: create ords pod and pdb operation *create / open / close / unplug / plug / delete / clone /map / parameter session* -In this exampl the cdb and pdbs resources are depolyed in different namespaces - -## Makefile helper - -Customizing yaml files (tns alias / credential / namespaces name etc...) is a long procedure prone to human error. A simple [makefile](../usecase/makefile) is available to quickly and safely configure yaml files with your system environment information. Just edit the [parameter file](../usecase/parameters.txt) before proceding. - -```text -[👉 CHECK PARAMETERS..................] -TNSALIAS...............:(DESCRIPTION=(CONNECT_TIMEOUT=90)(RETRY_COUNT=30)(RETRY_DELA.... -ORDPWD.................:[Password for ORDS_PUBLIC_USER ] -SYSPWD.................:[SYS password] -WBUSER.................:[username for https authentication] -WBPASS.................:[password for https authentication] -PDBUSR.................:[pdb admin user] -PDBPWD.................:[pdb admin password] -CDBUSR.................:[cdb admin user e.g. C##DBAPI_CDB_ADMIN] -CDBPWD.................:[cdb admin password] -PDBNAMESPACE...........:[namespace for pdb] -CDBNAMESPACE...........:[namespace for cdb] -COMPANY................:oracle -APIVERSION.............:v4 ---> do not edit -``` - -âš  **WARNING: The makefile is intended to speed up the usecase directory configuartion only, it is not supported, the editing and configuration of yaml files for production system is left up to the end user** - -### Pre requisistes: - -- Make sure that **kubectl** is properly configured. -- Make sure that all requirements listed in the [operator installation page](../../../../docs/installation/OPERATOR_INSTALLATION_README.md) are implemented. (role binding,webcert,etc) -- Make sure that administrative user on the container database is configured as documented. - -### Commands - -Review your configuraton running ```make check```; if all the parameters are correct then you can proceed with yaml files and certificates generation - -By excuting command ```make operator``` You will have in your directory an operator yaml file with the WATCH LIST required to operate with multiple namespaces. -Note that the yaml file is not applyed; you need to manually execute ```kubectl apply -f oracle-database-operator.yaml```. - -```bash -make operator -``` -You can generate all the other yaml files for pdb life cycle management using ```make genyaml``` - -```bash -make genyaml -``` - -list of generated yaml files - -```text --rw-r--r-- 1 mmalvezz g900 137142 Nov 13 09:35 oracle-database-operator.yaml --rw-r--r-- 1 mmalvezz g900 321 Nov 13 10:27 create_cdb_secrets.yaml --rw-r--r-- 1 mmalvezz g900 234 Nov 13 10:27 create_pdb_secrets.yaml --rw-r--r-- 1 mmalvezz g900 381 Nov 13 10:27 pdbnamespace_binding.yaml --rw-r--r-- 1 mmalvezz g900 381 Nov 13 10:27 cdbnamespace_binding.yaml --rw-r--r-- 1 mmalvezz g900 1267 Nov 13 10:27 create_ords_pod.yaml --rw-r--r-- 1 mmalvezz g900 935 Nov 13 10:27 create_pdb1_resource.yaml --rw-r--r-- 1 mmalvezz g900 935 Nov 13 10:27 create_pdb2_resource.yaml --rw-r--r-- 1 mmalvezz g900 842 Nov 13 10:27 open_pdb1_resource.yaml --rw-r--r-- 1 mmalvezz g900 842 Nov 13 10:27 open_pdb2_resource.yaml --rw-r--r-- 1 mmalvezz g900 845 Nov 13 10:27 open_pdb3_resource.yaml --rw-r--r-- 1 mmalvezz g900 842 Nov 13 10:27 close_pdb1_resource.yaml --rw-r--r-- 1 mmalvezz g900 842 Nov 13 10:27 close_pdb2_resource.yaml --rw-r--r-- 1 mmalvezz g900 846 Nov 13 10:27 close_pdb3_resource.yaml --rw-r--r-- 1 mmalvezz g900 927 Nov 13 10:27 clone_pdb1_resource.yaml --rw-r--r-- 1 mmalvezz g900 928 Nov 13 10:27 clone_pdb2_resource.yaml --rw-r--r-- 1 mmalvezz g900 802 Nov 13 10:27 delete_pdb1_resource.yaml --rw-r--r-- 1 mmalvezz g900 802 Nov 13 10:27 delete_pdb2_resource.yaml --rw-r--r-- 1 mmalvezz g900 824 Nov 13 10:27 unplug_pdb1_resource.yaml --rw-r--r-- 1 mmalvezz g900 992 Nov 13 10:27 plug_pdb1_resource.yaml --rw-r--r-- 1 mmalvezz g900 887 Nov 13 10:27 map_pdb1_resource.yaml --rw-r--r-- 1 mmalvezz g900 887 Nov 13 10:27 map_pdb2_resource.yaml --rw-r--r-- 1 mmalvezz g900 890 Nov 13 10:27 map_pdb3_resource.yaml -``` - -The command ```make secretes ``` will configure database secrets credential and certificates secretes - -```bash -make secrets -``` - - - -The makefile includes other different targets that can be used to test the various pdb operations available. E.g. - -```makefile -run03.2: - @$(call msg,"clone pdb2-->pdb4") - $(KUBECTL) apply -f $(PDBCLONE2) - $(KUBECTL) wait --for jsonpath='{.status.phase'}="Ready" pdb pdb4 -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - @$(call msg,"clone pdb2-->pdb4 completed") - $(KUBECTL) get pdb pdb3 -n $(PDBNAMESPACE) -``` -The target ```run03.2``` clones pdb2 into pdb4 and wait for ```$TEST_EXEC_TIMEOUT``` for the operation to complete. - -### Output executions:. - -```make secrets``` - -![image](../images/makesecrets_1_1.png) - - - -```make runall``` executes different pdb operations including the cdb controller creation - -![image](../images/makerunall.png) \ No newline at end of file diff --git a/docs/multitenant/ords-based/usecase/cdbnamespace_binding.yaml b/docs/multitenant/ords-based/usecase/cdbnamespace_binding.yaml deleted file mode 100644 index 5fd355f4..00000000 --- a/docs/multitenant/ords-based/usecase/cdbnamespace_binding.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: oracle-database-operator-oracle-database-operator-manager-rolebinding2 - namespace: cdbnamespace -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: oracle-database-operator-manager-role -subjects: -- kind: ServiceAccount - name: default - namespace: oracle-database-operator-system diff --git a/docs/multitenant/ords-based/usecase/clone_pdb1_resource.yaml b/docs/multitenant/ords-based/usecase/clone_pdb1_resource.yaml deleted file mode 100644 index 5723f7c6..00000000 --- a/docs/multitenant/ords-based/usecase/clone_pdb1_resource.yaml +++ /dev/null @@ -1,50 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb3 - namespace: pdbnamespace - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "cdbnamespace" - cdbName: "DB12" - pdbName: "new_clone" - srcPdbName: "pdbdev" - fileNameConversions: "NONE" - totalSize: "UNLIMITED" - tempSize: "UNLIMITED" - assertivePdbDeletion: true - action: "Clone" - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase/clone_pdb2_resource.yaml b/docs/multitenant/ords-based/usecase/clone_pdb2_resource.yaml deleted file mode 100644 index 2b9fc70a..00000000 --- a/docs/multitenant/ords-based/usecase/clone_pdb2_resource.yaml +++ /dev/null @@ -1,50 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb4 - namespace: pdbnamespace - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "cdbnamespace" - cdbName: "DB12" - pdbName: "new_clone2" - srcPdbName: "pdbprd" - fileNameConversions: "NONE" - totalSize: "UNLIMITED" - tempSize: "UNLIMITED" - assertivePdbDeletion: true - action: "Clone" - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase/close_pdb1_resource.yaml b/docs/multitenant/ords-based/usecase/close_pdb1_resource.yaml deleted file mode 100644 index ae837ce0..00000000 --- a/docs/multitenant/ords-based/usecase/close_pdb1_resource.yaml +++ /dev/null @@ -1,47 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb1 - namespace: pdbnamespace - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "cdbnamespace" - cdbName: "DB12" - pdbName: "pdbdev" - pdbState: "CLOSE" - modifyOption: "IMMEDIATE" - action: "Modify" - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase/close_pdb2_resource.yaml b/docs/multitenant/ords-based/usecase/close_pdb2_resource.yaml deleted file mode 100644 index 1b5d1324..00000000 --- a/docs/multitenant/ords-based/usecase/close_pdb2_resource.yaml +++ /dev/null @@ -1,47 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb2 - namespace: pdbnamespace - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "cdbnamespace" - cdbName: "DB12" - pdbName: "pdbprd" - pdbState: "CLOSE" - modifyOption: "IMMEDIATE" - action: "Modify" - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase/close_pdb3_resource.yaml b/docs/multitenant/ords-based/usecase/close_pdb3_resource.yaml deleted file mode 100644 index f4a32938..00000000 --- a/docs/multitenant/ords-based/usecase/close_pdb3_resource.yaml +++ /dev/null @@ -1,47 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb3 - namespace: pdbnamespace - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "cdbnamespace" - cdbName: "DB12" - pdbName: ""new_clone" - pdbState: "CLOSE" - modifyOption: "IMMEDIATE" - action: "Modify" - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase/create_ords_pod.yaml b/docs/multitenant/ords-based/usecase/create_ords_pod.yaml deleted file mode 100644 index ad196c9d..00000000 --- a/docs/multitenant/ords-based/usecase/create_ords_pod.yaml +++ /dev/null @@ -1,48 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: CDB -metadata: - name: cdb-dev - namespace: cdbnamespace -spec: - cdbName: "DB12" - ordsImage: _your_container_registry/ords-dboper:latest - ordsImagePullPolicy: "Always" - dbTnsurl : "T H I S I S J U S T A N E X A M P L E (DESCRIPTION=(CONNECT_TIMEOUT=90)(RETRY_COUNT=30)(RETRY_DELAY=10)(TRANSPORT_CONNECT_TIMEOUT=70)(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL=TCP)(HOST=scan12.testrac.com)(PORT=1521)(IP=V4_ONLY))(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL=TCP)(HOST=scan34.testrac.com)(PORT=1521)(IP=V4_ONLY))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=TESTORDS)))" - replicas: 1 - deletePdbCascade: true - sysAdminPwd: - secret: - secretName: "syspwd" - key: "e_syspwd.txt" - ordsPwd: - secret: - secretName: "ordpwd" - key: "e_ordpwd.txt" - cdbAdminUser: - secret: - secretName: "cdbusr" - key: "e_cdbusr.txt" - cdbAdminPwd: - secret: - secretName: "cdbpwd" - key: "e_cdbpwd.txt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - cdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - cdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - cdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase/create_pdb1_resource.yaml b/docs/multitenant/ords-based/usecase/create_pdb1_resource.yaml deleted file mode 100644 index 84e910e0..00000000 --- a/docs/multitenant/ords-based/usecase/create_pdb1_resource.yaml +++ /dev/null @@ -1,51 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb1 - namespace: pdbnamespace - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "cdbnamespace" - cdbName: "DB12" - pdbName: "pdbdev" - assertivePdbDeletion: true - fileNameConversions: "NONE" - unlimitedStorage: false - tdeImport: false - totalSize: "2G" - tempSize: "800M" - action: "Create" - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase/create_pdb2_resource.yaml b/docs/multitenant/ords-based/usecase/create_pdb2_resource.yaml deleted file mode 100644 index 0a71c7c3..00000000 --- a/docs/multitenant/ords-based/usecase/create_pdb2_resource.yaml +++ /dev/null @@ -1,51 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb2 - namespace: pdbnamespace - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "cdbnamespace" - cdbName: "DB12" - pdbName: "pdbprd" - assertivePdbDeletion: true - fileNameConversions: "NONE" - unlimitedStorage: false - tdeImport: false - totalSize: "2G" - tempSize: "800M" - action: "Create" - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase/delete_pdb1_resource.yaml b/docs/multitenant/ords-based/usecase/delete_pdb1_resource.yaml deleted file mode 100644 index 3aba580c..00000000 --- a/docs/multitenant/ords-based/usecase/delete_pdb1_resource.yaml +++ /dev/null @@ -1,45 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb1 - namespace: pdbnamespace - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "cdbnamespace" - pdbName: "pdbdev" - action: "Delete" - dropAction: "INCLUDING" - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase/delete_pdb2_resource.yaml b/docs/multitenant/ords-based/usecase/delete_pdb2_resource.yaml deleted file mode 100644 index 59b50a64..00000000 --- a/docs/multitenant/ords-based/usecase/delete_pdb2_resource.yaml +++ /dev/null @@ -1,45 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb2 - namespace: pdbnamespace - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "cdbnamespace" - pdbName: "pdbprd" - action: "Delete" - dropAction: "INCLUDING" - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase/makefile b/docs/multitenant/ords-based/usecase/makefile deleted file mode 100644 index dc881598..00000000 --- a/docs/multitenant/ords-based/usecase/makefile +++ /dev/null @@ -1,915 +0,0 @@ -# Copyright (c) 2022, Oracle and/or its affiliates. -# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. -# __ __ _ __ _ _ -# | \/ | __ _| | _____ / _(_) | ___ -# | |\/| |/ _` | |/ / _ \ |_| | |/ _ \ -# | | | | (_| | < __/ _| | | __/ -# |_| |_|\__,_|_|\_\___|_| |_|_|\___| -# | | | | ___| |_ __ ___ _ __ -# | |_| |/ _ \ | '_ \ / _ \ '__| -# | _ | __/ | |_) | __/ | -# |_| |_|\___|_| .__/ \___|_| -# |_| -# -# WARNING: Using this makefile helps you to customize yaml -# files. Edit parameters.txt with your enviroment -# informartion and execute the following steps -# -# 1) make operator -# it configures the operator yaml files with the -# watch namelist required by the multitenant controllers -# -# 2) make genyaml -# It automatically creates all the yaml files based on the -# information available in the parameters file -# -# 3) make secrets -# It configure the required secrets necessary to operate -# with pdbs multitenant controllers -# -# 4) make runall01 -# Start a series of operation create open close delete and so on -# -# LIST OF GENERAED YAML FILE -# -# ----------------------------- ---------------------------------- -# oracle-database-operator.yaml : oracle database operator -# cdbnamespace_binding.yaml : role binding for cdbnamespace -# pdbnamespace_binding.yaml : role binding for pdbnamespace -# create_ords_pod.yaml : create rest server pod -# create_pdb1_resource.yaml : create first pluggable database -# create_pdb2_resource.yaml : create second pluggable database -# open_pdb1_resource.yaml : open first pluggable database -# open_pdb2_resource.yaml : open second pluggable database -# close_pdb1_resource.yaml : close first pluggable database -# close_pdb2_resource.yaml : close second pluggable database -# clone_pdb_resource.yaml : clone thrid pluggable database -# clone_pdb2_resource.yaml : clone 4th pluggable database -# delete_pdb1_resource.yaml : delete first pluggable database -# delete_pdb2_resource.yaml : delete sencond pluggable database -# delete_pdb3_resource.yaml : delete thrid pluggable database -# unplug_pdb1_resource.yaml : unplug first pluggable database -# plug_pdb1_resource.yaml : plug first pluggable database -# map_pdb1_resource.yaml : map the first pluggable database -# config_map.yam : pdb parameters array -# -DATE := `date "+%y%m%d%H%M%S"` -###################### -# PARAMETER SECTIONS # -###################### - -export PARAMETERS=parameters.txt -export TNSALIAS=$(shell cat $(PARAMETERS) |grep -v ^\#|grep TNSALIAS|cut -d : -f 2) -export ORDPWD=$(shell cat $(PARAMETERS)|grep -v ^\#|grep ORDPWD|cut -d : -f 2) -export SYSPWD=$(shell cat $(PARAMETERS)|grep -v ^\#|grep SYSPWD|cut -d : -f 2) -export WBUSER=$(shell cat $(PARAMETERS)|grep -v ^\#|grep WBUSER|cut -d : -f 2) -export WBPASS=$(shell cat $(PARAMETERS)|grep -v ^\#|grep WBPASS|cut -d : -f 2) -export PDBUSR=$(shell cat $(PARAMETERS)|grep -v ^\#|grep PDBUSR|cut -d : -f 2) -export PDBPWD=$(shell cat $(PARAMETERS)|grep -v ^\#|grep PDBPWD|cut -d : -f 2) -export CDBUSR=$(shell cat $(PARAMETERS)|grep -v ^\#|grep CDBUSR|cut -d : -f 2) -export CDBPWD=$(shell cat $(PARAMETERS)|grep -v ^\#|grep CDBPWD|cut -d : -f 2) -export PDBNAMESPACE=$(shell cat $(PARAMETERS)|grep -v ^\#|grep PDBNAMESPACE|cut -d : -f 2) -export CDBNAMESPACE=$(shell cat $(PARAMETERS)|grep -v ^\#|grep CDBNAMESPACE|cut -d : -f 2) -export ORDSIMG=$(shell cat $(PARAMETERS)|grep -v ^\#|grep ORDSIMG|cut -d : -f 2,3) -export COMPANY=$(shell cat $(PARAMETERS)|grep -v ^\#|grep COMPANY|cut -d : -f 2) -export APIVERSION=$(shell cat $(PARAMETERS)|grep -v ^\#|grep APIVERSION|cut -d : -f 2) -export OPRNAMESPACE=oracle-database-operator-system -export ORACLE_OPERATOR_YAML=../../../../oracle-database-operator.yaml -export TEST_EXEC_TIMEOUT=3m -export IMAGE=oracle/ords-dboper:latest -export ORDSIMGDIR=../../../../ords - -REST_SERVER=ords -SKEY=tls.key -SCRT=tls.crt -CART=ca.crt -PRVKEY=ca.key -PUBKEY=public.pem -COMPANY=oracle -RUNTIME=/usr/bin/podman - -################# -### FILE LIST ### -################# - -export ORDS_POD=create_ords_pod.yaml - -export CDB_SECRETS=create_cdb_secrets.yaml -export PDB_SECRETS=create_pdb_secrets.yaml - -export PDBCRE1=create_pdb1_resource.yaml -export PDBCRE2=create_pdb2_resource.yaml - -export PDBCLOSE1=close_pdb1_resource.yaml -export PDBCLOSE2=close_pdb2_resource.yaml -export PDBCLOSE3=close_pdb3_resource.yaml - -export PDBOPEN1=open_pdb1_resource.yaml -export PDBOPEN2=open_pdb2_resource.yaml -export PDBOPEN3=open_pdb3_resource.yaml - -export PDBCLONE1=clone_pdb1_resource.yaml -export PDBCLONE2=clone_pdb2_resource.yaml - -export PDBDELETE1=delete_pdb1_resource.yaml -export PDBDELETE2=delete_pdb2_resource.yaml -export PDBDELETE3=delete_pdb3_resource.yaml - -export PDBUNPLUG1=unplug_pdb1_resource.yaml -export PDBPLUG1=plug_pdb1_resource.yaml - -export PDBMAP1=map_pdb1_resource.yaml -export PDBMAP2=map_pdb2_resource.yaml -export PDBMAP3=map_pdb3_resource.yaml - -export PDBMAP1=map_pdb1_resource.yaml -export PDBMAP2=map_pdb2_resource.yaml -export PDBMAP3=map_pdb3_resource.yaml - - -##BINARIES -export KUBECTL=/usr/bin/kubectl -OPENSSL=/usr/bin/openssl -ECHO=/usr/bin/echo -RM=/usr/bin/rm -CP=/usr/bin/cp -TAR=/usr/bin/tar -MKDIR=/usr/bin/mkdir -SED=/usr/bin/sed - -define msg -@printf "\033[31;7m%s\033[0m\r" "......................................]" -@printf "\033[31;7m[\xF0\x9F\x91\x89 %s\033[0m\n" $(1) -endef - -check: - $(call msg,"CHECK PARAMETERS") - @printf "TNSALIAS...............:%.60s....\n" $(TNSALIAS) - @printf "ORDPWD.................:%s\n" $(ORDPWD) - @printf "SYSPWD.................:%s\n" $(SYSPWD) - @printf "WBUSER.................:%s\n" $(WBUSER) - @printf "WBPASS.................:%s\n" $(WBPASS) - @printf "PDBUSR.................:%s\n" $(PDBUSR) - @printf "PDBPWD.................:%s\n" $(PDBPWD) - @printf "CDBUSR.................:%s\n" $(CDBUSR) - @printf "CDBPWD.................:%s\n" $(CDBPWD) - @printf "PDBNAMESPACE...........:%s\n" $(PDBNAMESPACE) - @printf "CDBNAMESPACE...........:%s\n" $(CDBNAMESPACE) - @printf "COMPANY................:%s\n" $(COMPANY) - @printf "APIVERSION.............:%s\n" $(APIVERSION) - - -tlscrt: - $(call msg,"TLS GENERATION") - #$(OPENSSL) genrsa -out $(PRVKEY) 2048 - $(OPENSSL) genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_keygen_pubexp:65537 > $(PRVKEY) - $(OPENSSL) req -new -x509 -days 365 -key $(PRVKEY) \ - -subj "/C=CN/ST=GD/L=SZ/O=$(COMPANY), Inc./CN=$(COMPANY) Root CA" -out ca.crt - $(OPENSSL) req -newkey rsa:2048 -nodes -keyout $(SKEY) -subj \ - "/C=CN/ST=GD/L=SZ/O=$(COMPANY), Inc./CN=cdb-dev-$(REST_SERVER).$(CDBNAMESPACE)" -out server.csr - $(ECHO) "subjectAltName=DNS:cdb-dev-$(REST_SERVER).$(CDBNAMESPACE)" > extfile.txt - $(OPENSSL) x509 -req -extfile extfile.txt -days 365 -in server.csr -CA ca.crt -CAkey $(PRVKEY) -CAcreateserial -out $(SCRT) - $(OPENSSL) rsa -in $(PRVKEY) -outform PEM -pubout -out $(PUBKEY) - -tlssec: - $(call msg,"GENERATE TLS SECRET") - $(KUBECTL) create secret tls db-tls --key="$(SKEY)" --cert="$(SCRT)" -n $(CDBNAMESPACE) - $(KUBECTL) create secret generic db-ca --from-file="$(CART)" -n $(CDBNAMESPACE) - $(KUBECTL) create secret tls db-tls --key="$(SKEY)" --cert="$(SCRT)" -n $(PDBNAMESPACE) - $(KUBECTL) create secret generic db-ca --from-file="$(CART)" -n $(PDBNAMESPACE) - - -delsec: - $(call msg,"CLEAN OLD SECRETS") - $(eval SECRETSP:=$(shell kubectl get secrets -n $(PDBNAMESPACE) -o custom-columns=":metadata.name" --no-headers) ) - $(eval SECRETSL:=$(shell kubectl get secrets -n $(CDBNAMESPACE) -o custom-columns=":metadata.name" --no-headers) ) - @[ "${SECRETSP}" ] && ( \ - printf "Deleteing secrets in namespace -n $(PDBNAMESPACE)\n") &&\ - ($(KUBECTL) delete secret $(SECRETSP) -n $(PDBNAMESPACE))\ - || ( echo "No screts in namespace $(PDBNAMESPACE)") - @[ "${SECRETSL}" ] && ( \ - printf "Deleteing secrets in namespace -n $(CDBNAMESPACE)\n") &&\ - ($(KUBECTL) delete secret $(SECRETSL) -n $(CDBNAMESPACE))\ - || ( echo "No screts in namespace $(PDBNAMESPACE)") - - -###### ENCRYPTED SECRETS ###### -export PRVKEY=ca.key -export PUBKEY=public.pem -WBUSERFILE=wbuser.txt -WBPASSFILE=wbpass.txt -CDBUSRFILE=cdbusr.txt -CDBPWDFILE=cdbpwd.txt -SYSPWDFILE=syspwd.txt -ORDPWDFILE=ordpwd.txt -PDBUSRFILE=pdbusr.txt -PDBPWDFILE=pdbpwd.txt - - - -secrets: delsec tlscrt tlssec - $(OPENSSL) rsa -in $(PRVKEY) -outform PEM -pubout -out $(PUBKEY) - $(KUBECTL) create secret generic pubkey --from-file=publicKey=$(PUBKEY) -n $(CDBNAMESPACE) - $(KUBECTL) create secret generic prvkey --from-file=privateKey=$(PRVKEY) -n $(CDBNAMESPACE) - $(KUBECTL) create secret generic prvkey --from-file=privateKey="$(PRVKEY)" -n $(PDBNAMESPACE) - @$(ECHO) $(WBUSER) > $(WBUSERFILE) - @$(ECHO) $(WBPASS) > $(WBPASSFILE) - @$(ECHO) $(CDBPWD) > $(CDBPWDFILE) - @$(ECHO) $(CDBUSR) > $(CDBUSRFILE) - @$(ECHO) $(SYSPWD) > $(SYSPWDFILE) - @$(ECHO) $(ORDPWD) > $(ORDPWDFILE) - @$(ECHO) $(PDBUSR) > $(PDBUSRFILE) - @$(ECHO) $(PDBPWD) > $(PDBPWDFILE) - $(OPENSSL) rsautl -encrypt -pubin -inkey $(PUBKEY) -in $(WBUSERFILE) |base64 > e_$(WBUSERFILE) - $(OPENSSL) rsautl -encrypt -pubin -inkey $(PUBKEY) -in $(WBPASSFILE) |base64 > e_$(WBPASSFILE) - $(OPENSSL) rsautl -encrypt -pubin -inkey $(PUBKEY) -in $(CDBPWDFILE) |base64 > e_$(CDBPWDFILE) - $(OPENSSL) rsautl -encrypt -pubin -inkey $(PUBKEY) -in $(CDBUSRFILE) |base64 > e_$(CDBUSRFILE) - $(OPENSSL) rsautl -encrypt -pubin -inkey $(PUBKEY) -in $(SYSPWDFILE) |base64 > e_$(SYSPWDFILE) - $(OPENSSL) rsautl -encrypt -pubin -inkey $(PUBKEY) -in $(ORDPWDFILE) |base64 > e_$(ORDPWDFILE) - $(OPENSSL) rsautl -encrypt -pubin -inkey $(PUBKEY) -in $(PDBUSRFILE) |base64 > e_$(PDBUSRFILE) - $(OPENSSL) rsautl -encrypt -pubin -inkey $(PUBKEY) -in $(PDBPWDFILE) |base64 > e_$(PDBPWDFILE) - $(KUBECTL) create secret generic wbuser --from-file=e_$(WBUSERFILE) -n $(CDBNAMESPACE) - $(KUBECTL) create secret generic wbpass --from-file=e_$(WBPASSFILE) -n $(CDBNAMESPACE) - $(KUBECTL) create secret generic wbuser --from-file=e_$(WBUSERFILE) -n $(PDBNAMESPACE) - $(KUBECTL) create secret generic wbpass --from-file=e_$(WBPASSFILE) -n $(PDBNAMESPACE) - $(KUBECTL) create secret generic cdbpwd --from-file=e_$(CDBPWDFILE) -n $(CDBNAMESPACE) - $(KUBECTL) create secret generic cdbusr --from-file=e_$(CDBUSRFILE) -n $(CDBNAMESPACE) - $(KUBECTL) create secret generic syspwd --from-file=e_$(SYSPWDFILE) -n $(CDBNAMESPACE) - $(KUBECTL) create secret generic ordpwd --from-file=e_$(ORDPWDFILE) -n $(CDBNAMESPACE) - $(KUBECTL) create secret generic pdbusr --from-file=e_$(PDBUSRFILE) -n $(PDBNAMESPACE) - $(KUBECTL) create secret generic pdbpwd --from-file=e_$(PDBPWDFILE) -n $(PDBNAMESPACE) - $(RM) $(WBUSERFILE) $(WBPASSFILE) $(CDBPWDFILE) $(CDBUSRFILE) $(SYSPWDFILE) $(ORDPWDFILE) $(PDBUSRFILE) $(PDBPWDFILE) - $(RM) e_$(WBUSERFILE) e_$(WBPASSFILE) e_$(CDBPWDFILE) e_$(CDBUSRFILE) e_$(SYSPWDFILE) e_$(ORDPWDFILE) e_$(PDBUSRFILE) e_$(PDBPWDFILE) - - -### YAML FILE SECTION ### -operator: - $(CP) ${ORACLE_OPERATOR_YAML} . - ${CP} `basename ${ORACLE_OPERATOR_YAML}` `basename ${ORACLE_OPERATOR_YAML}`.ORG - $(SED) -i 's/value: ""/value: $(OPRNAMESPACE),$(PDBNAMESPACE),$(CDBNAMESPACE)/g' `basename ${ORACLE_OPERATOR_YAML}` - - -define _script00 -cat < authsection01.yaml - sysAdminPwd: - secret: - secretName: "syspwd" - key: "e_syspwd.txt" - ordsPwd: - secret: - secretName: "ordpwd" - key: "e_ordpwd.txt" - cdbAdminUser: - secret: - secretName: "cdbusr" - key: "e_cdbusr.txt" - cdbAdminPwd: - secret: - secretName: "cdbpwd" - key: "e_cdbpwd.txt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - cdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - cdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - cdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" -EOF - -cat< authsection02.yaml - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" -EOF - - -cat < ${PDBNAMESPACE}_binding.yaml -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: oracle-database-operator-oracle-database-operator-manager-rolebinding1 - namespace: ${PDBNAMESPACE} -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: oracle-database-operator-manager-role -subjects: -- kind: ServiceAccount - name: default - namespace: oracle-database-operator-system -EOF - -cat < ${CDBNAMESPACE}_binding.yaml -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: oracle-database-operator-oracle-database-operator-manager-rolebinding2 - namespace: ${CDBNAMESPACE} -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: oracle-database-operator-manager-role -subjects: -- kind: ServiceAccount - name: default - namespace: oracle-database-operator-system -EOF - -endef -export script00 = $(value _script00) -secyaml: - @ eval "$$script00" - -#echo ords pod creation -define _script01 -cat < ${ORDS_POD} -apiVersion: database.oracle.com/${APIVERSION} -kind: CDB -metadata: - name: cdb-dev - namespace: ${CDBNAMESPACE} -spec: - cdbName: "DB12" - ordsImage: ${ORDSIMG} - ordsImagePullPolicy: "Always" - dbTnsurl : ${TNSALIAS} - replicas: 1 - deletePdbCascade: true -EOF - -cat authsection01.yaml >> ${ORDS_POD} - -endef -export script01 = $(value _script01) - - -define _script02 - -cat <${PDBCRE1} -apiVersion: database.oracle.com/${APIVERSION} -kind: PDB -metadata: - name: pdb1 - namespace: ${PDBNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${CDBNAMESPACE}" - cdbName: "DB12" - pdbName: "pdbdev" - assertivePdbDeletion: true - fileNameConversions: "NONE" - unlimitedStorage: false - tdeImport: false - totalSize: "2G" - tempSize: "800M" - action: "Create" -EOF - -cat < ${PDBCRE2} -apiVersion: database.oracle.com/${APIVERSION} -kind: PDB -metadata: - name: pdb2 - namespace: ${PDBNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${CDBNAMESPACE}" - cdbName: "DB12" - pdbName: "pdbprd" - assertivePdbDeletion: true - fileNameConversions: "NONE" - unlimitedStorage: false - tdeImport: false - totalSize: "2G" - tempSize: "800M" - action: "Create" -EOF - -cat <${PDBOPEN1} -apiVersion: database.oracle.com/${APIVERSION} -kind: PDB -metadata: - name: pdb1 - namespace: ${PDBNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${CDBNAMESPACE}" - cdbName: "DB12" - pdbName: "pdbdev" - action: "Modify" - pdbState: "OPEN" - modifyOption: "READ WRITE" -EOF - -cat <${PDBOPEN2} -apiVersion: database.oracle.com/${APIVERSION} -kind: PDB -metadata: - name: pdb2 - namespace: ${PDBNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${CDBNAMESPACE}" - cdbName: "DB12" - pdbName: "pdbprd" - action: "Modify" - pdbState: "OPEN" - modifyOption: "READ WRITE" -EOF - -cat <${PDBOPEN3} -apiVersion: database.oracle.com/${APIVERSION} -kind: PDB -metadata: - name: pdb3 - namespace: ${PDBNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${CDBNAMESPACE}" - cdbName: "DB12" - pdbName: "new_clone" - action: "Modify" - pdbState: "OPEN" - modifyOption: "READ WRITE" -EOF - -cat <${PDBCLOSE1} -apiVersion: database.oracle.com/${APIVERSION} -kind: PDB -metadata: - name: pdb1 - namespace: ${PDBNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${CDBNAMESPACE}" - cdbName: "DB12" - pdbName: "pdbdev" - pdbState: "CLOSE" - modifyOption: "IMMEDIATE" - action: "Modify" -EOF - -cat <${PDBCLOSE2} -apiVersion: database.oracle.com/${APIVERSION} -kind: PDB -metadata: - name: pdb2 - namespace: ${PDBNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${CDBNAMESPACE}" - cdbName: "DB12" - pdbName: "pdbprd" - pdbState: "CLOSE" - modifyOption: "IMMEDIATE" - action: "Modify" -EOF - -cat <${PDBCLOSE3} -apiVersion: database.oracle.com/${APIVERSION} -kind: PDB -metadata: - name: pdb3 - namespace: ${PDBNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${CDBNAMESPACE}" - cdbName: "DB12" - pdbName: ""new_clone" - pdbState: "CLOSE" - modifyOption: "IMMEDIATE" - action: "Modify" -EOF - -cat < ${PDBCLONE1} -apiVersion: database.oracle.com/${APIVERSION} -kind: PDB -metadata: - name: pdb3 - namespace: ${PDBNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${CDBNAMESPACE}" - cdbName: "DB12" - pdbName: "new_clone" - srcPdbName: "pdbdev" - fileNameConversions: "NONE" - totalSize: "UNLIMITED" - tempSize: "UNLIMITED" - assertivePdbDeletion: true - action: "Clone" -EOF - -cat < ${PDBCLONE2} -apiVersion: database.oracle.com/${APIVERSION} -kind: PDB -metadata: - name: pdb4 - namespace: ${PDBNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${CDBNAMESPACE}" - cdbName: "DB12" - pdbName: "new_clone2" - srcPdbName: "pdbprd" - fileNameConversions: "NONE" - totalSize: "UNLIMITED" - tempSize: "UNLIMITED" - assertivePdbDeletion: true - action: "Clone" -EOF - - -cat < ${PDBDELETE1} -apiVersion: database.oracle.com/${APIVERSION} -kind: PDB -metadata: - name: pdb1 - namespace: ${PDBNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${CDBNAMESPACE}" - pdbName: "pdbdev" - action: "Delete" - dropAction: "INCLUDING" -EOF - -cat < ${PDBDELETE2} -apiVersion: database.oracle.com/${APIVERSION} -kind: PDB -metadata: - name: pdb2 - namespace: ${PDBNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${CDBNAMESPACE}" - pdbName: "pdbprd" - action: "Delete" - dropAction: "INCLUDING" -EOF - -cat < ${PDBUNPLUG1} -apiVersion: database.oracle.com/${APIVERSION} -kind: PDB -metadata: - name: pdb1 - namespace: ${PDBNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${CDBNAMESPACE}" - cdbName: "DB12" - pdbName: "pdbdev" - xmlFileName: "/tmp/pdb.xml" - action: "Unplug" -EOF - -cat <${PDBPLUG1} -apiVersion: database.oracle.com/${APIVERSION} -kind: PDB -metadata: - name: pdb1 - namespace: ${PDBNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${CDBNAMESPACE}" - cdbName: "DB12" - pdbName: "pdbdev" - xmlFileName: "/tmp/pdb.xml" - action: "plug" - fileNameConversions: "NONE" - sourceFileNameConversions: "NONE" - copyAction: "MOVE" - totalSize: "1G" - tempSize: "100M" - assertivePdbDeletion: true - action: "Plug" -EOF - -cat <${PDBMAP1} -apiVersion: database.oracle.com/${APIVERSION} -kind: PDB -metadata: - name: pdb1 - namespace: ${PDBNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${CDBNAMESPACE}" - cdbName: "DB12" - pdbName: "pdbdev" - assertivePdbDeletion: true - fileNameConversions: "NONE" - totalSize: "1G" - tempSize: "100M" - action: "Map" -EOF - -cat <${PDBMAP2} -apiVersion: database.oracle.com/${APIVERSION} -kind: PDB -metadata: - name: pdb2 - namespace: ${PDBNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${CDBNAMESPACE}" - cdbName: "DB12" - pdbName: "pdbprd" - assertivePdbDeletion: true - fileNameConversions: "NONE" - totalSize: "1G" - tempSize: "100M" - action: "Map" -EOF - - -cat <${PDBMAP3} -apiVersion: database.oracle.com/${APIVERSION} -kind: PDB -metadata: - name: pdb3 - namespace: ${PDBNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${CDBNAMESPACE}" - cdbName: "DB12" - pdbName: "new_clone" - assertivePdbDeletion: true - fileNameConversions: "NONE" - totalSize: "1G" - tempSize: "100M" - action: "Map" -EOF - - -## Auth information -for _file in ${PDBCRE1} ${PDBCRE2} ${PDBOPEN1} ${PDBOPEN2} ${PDBOPEN3} ${PDBCLOSE1} ${PDBCLOSE2} ${PDBCLOSE3} ${PDBCLONE1} ${PDBCLONE2} ${PDBDELETE1} ${PDBDELETE2} ${PDBUNPLUG1} ${PDBPLUG1} ${PDBMAP1} ${PDBMAP2} ${PDBMAP3} -do -ls -ltr ${_file} - cat authsection02.yaml >> ${_file} -done -rm authsection02.yaml -rm authsection01.yaml -endef - -export script02 = $(value _script02) - -genyaml: secyaml - @ eval "$$script01" - @ eval "$$script02" - -cleanyaml: - - $(RM) $(PDBMAP3) $(PDBMAP2) $(PDBMAP1) $(PDBPLUG1) $(PDBUNPLUG1) $(PDBDELETE2) $(PDBDELETE1) $(PDBCLONE2) $(PDBCLONE1) $(PDBCLOSE3) $(PDBCLOSE2) $(PDBCLOSE1) $(PDBOPEN3) $(PDBOPEN2) $(PDBOPEN1) $(PDBCRE2) $(PDBCRE1) $(ORDS_POD) $(CDB_SECRETS) $(PDB_SECRETS) - - $(RM) ${PDBNAMESPACE}_binding.yaml ${CDBNAMESPACE}_binding.yaml - - -cleancrt: - - $(RM) $(SKEY) $(SCRT) $(CART) $(PRVKEY) $(PUBKEY) server.csr extfile.txt ca.srl - - -################# -### PACKAGING ### -################# - -pkg: - - $(RM) -rf /tmp/pkgtestplan - $(MKDIR) /tmp/pkgtestplan - $(CP) -R * /tmp/pkgtestplan - $(CP) ../../../../oracle-database-operator.yaml /tmp/pkgtestplan/ - $(TAR) -C /tmp -cvf ~/pkgtestplan_$(DATE).tar pkgtestplan - -################ -### diag ### -################ - -login: - $(KUBECTL) exec `$(KUBECTL) get pods -n $(CDBNAMESPACE)|grep ords|cut -d ' ' -f 1` -n $(CDBNAMESPACE) -it -- /bin/bash - - -reloadop: - echo "RESTARTING OPERATOR" - $(eval OP1 := $(shell $(KUBECTL) get pods -n $(OPRNAMESPACE)|grep oracle-database-operator-controller|head -1|cut -d ' ' -f 1 )) - $(eval OP2 := $(shell $(KUBECTL) get pods -n $(OPRNAMESPACE)|grep oracle-database-operator-controller|head -2|tail -1|cut -d ' ' -f 1 )) - $(eval OP3 := $(shell $(KUBECTL) get pods -n $(OPRNAMESPACE)|grep oracle-database-operator-controller|tail -1|cut -d ' ' -f 1 )) - $(KUBECTL) get pod $(OP1) -n $(OPRNAMESPACE) -o yaml | kubectl replace --force -f - - $(KUBECTL) get pod $(OP2) -n $(OPRNAMESPACE) -o yaml | kubectl replace --force -f - - $(KUBECTL) get pod $(OP3) -n $(OPRNAMESPACE) -o yaml | kubectl replace --force -f - - - -dump: - @$(eval TMPSP := $(shell date "+%y%m%d%H%M%S" )) - @$(eval DIAGFILE := ./opdmp.$(TMPSP)) - @>$(DIAGFILE) - @echo "OPERATOR DUMP" >> $(DIAGFILE) - @echo "~~~~~~~~~~~~~" >> $(DIAGFILE) - $(KUBECTL) logs pod/`$(KUBECTL) get pods -n $(OPRNAMESPACE)|grep oracle-database-operator-controller|head -1|cut -d ' ' -f 1` -n $(OPRNAMESPACE) >>$(DIAGFILE) - $(KUBECTL) logs pod/`$(KUBECTL) get pods -n $(OPRNAMESPACE)|grep oracle-database-operator-controller|head -2|tail -1 | cut -d ' ' -f 1` -n $(OPRNAMESPACE) >>$(DIAGFILE) - $(KUBECTL) logs pod/`$(KUBECTL) get pods -n $(OPRNAMESPACE)|grep oracle-database-operator-controller|tail -1|cut -d ' ' -f 1` -n $(OPRNAMESPACE) >>$(DIAGFILE) - -####################################################### -#### TEST SECTION #### -####################################################### - -run00: - @$(call msg,"cdb pod creation") - - $(KUBECTL) delete cdb cdb-dev -n $(CDBNAMESPACE) - $(KUBECTL) apply -f $(ORDS_POD) - time $(KUBECTL) wait --for jsonpath='{.status.phase'}="Ready" cdb cdb-dev -n $(CDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - @$(call msg,"cdb pod completed") - $(KUBECTL) get cdb -n $(CDBNAMESPACE) - $(KUBECTL) get pod -n $(CDBNAMESPACE) - -run01.1: - @$(call msg,"pdb pdb1 creation") - $(KUBECTL) apply -f $(PDBCRE1) - time $(KUBECTL) wait --for jsonpath='{.status.phase'}="Ready" pdb pdb1 -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - @$(call msg, "pdb pdb1 creation completed") - $(KUBECTL) get pdb pdb1 -n $(PDBNAMESPACE) - -run01.2: - @$(call msg, "pdb pdb2 creation") - $(KUBECTL) apply -f $(PDBCRE2) - $(KUBECTL) wait --for jsonpath='{.status.phase'}="Ready" pdb pdb2 -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - @$(call msg, "pdb pdb2 creation completed") - $(KUBECTL) get pdb pdb2 -n $(PDBNAMESPACE) - -run02.1: - @$(call msg, "pdb pdb1 open") - $(KUBECTL) apply -f $(PDBOPEN1) - $(KUBECTL) wait --for jsonpath='{.status.openMode'}="READ WRITE" pdb pdb1 -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - @$(call msg, "pdb pdb1 open completed") - $(KUBECTL) get pdb pdb1 -n $(PDBNAMESPACE) - -run02.2: - @$(call msg,"pdb pdb2 open") - $(KUBECTL) apply -f $(PDBOPEN2) - $(KUBECTL) wait --for jsonpath='{.status.openMode'}="READ WRITE" pdb pdb2 -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - @$(call msg,"pdb pdb2 open completed") - $(KUBECTL) get pdb pdb2 -n $(PDBNAMESPACE) - - -run03.1: - @$(call msg,"clone pdb1-->pdb3") - $(KUBECTL) apply -f $(PDBCLONE1) - $(KUBECTL) wait --for jsonpath='{.status.phase'}="Ready" pdb pdb3 -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - @$(call msg,"clone pdb1-->pdb3 completed") - $(KUBECTL) get pdb pdb3 -n $(PDBNAMESPACE) - - -run03.2: - @$(call msg,"clone pdb2-->pdb4") - $(KUBECTL) apply -f $(PDBCLONE2) - $(KUBECTL) wait --for jsonpath='{.status.phase'}="Ready" pdb pdb4 -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - @$(call msg,"clone pdb2-->pdb4 completed") - $(KUBECTL) get pdb pdb3 -n $(PDBNAMESPACE) - - -run04.1: - @$(call msg,"pdb pdb1 close") - $(KUBECTL) apply -f $(PDBCLOSE1) - $(KUBECTL) wait --for jsonpath='{.status.openMode'}="MOUNTED" pdb pdb1 -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - @$(call msg, "pdb pdb1 close completed") - $(KUBECTL) get pdb pdb1 -n $(PDBNAMESPACE) - -run04.2: - @$(call msg,"pdb pdb2 close") - $(KUBECTL) apply -f $(PDBCLOSE2) - $(KUBECTL) wait --for jsonpath='{.status.openMode'}="MOUNTED" pdb pdb2 -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - @$(call msg,"pdb pdb2 close completed") - $(KUBECTL) get pdb pdb2 -n $(PDBNAMESPACE) - -run05.1: - @$(call msg,"pdb pdb1 unplug") - $(KUBECTL) apply -f $(PDBUNPLUG1) - $(KUBECTL) wait --for=delete pdb pdb1 -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - @$(call msg,"pdb pdb1 unplug completed") - -run06.1: - @$(call msg, "pdb pdb1 plug") - $(KUBECTL) apply -f $(PDBPLUG1) - $(KUBECTL) wait --for jsonpath='{.status.phase'}="Ready" pdb pdb1 -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - @$(call msg, "pdb pdb1 plug completed") - $(KUBECTL) get pdb pdb1 -n $(PDBNAMESPACE) - -run07.1: - @$(call msg,"pdb pdb1 delete ") - - $(KUBECTL) apply -f $(PDBCLOSE1) - $(KUBECTL) wait --for jsonpath='{.status.openMode'}="MOUNTED" pdb pdb1 -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - $(KUBECTL) apply -f $(PDBDELETE1) - $(KUBECTL) wait --for=delete pdb pdb1 -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - @$(call msg,"pdb pdb1 delete") - $(KUBECTL) get pdb -n $(PDBNAMESPACE) - -run99.1: - $(KUBECTL) delete cdb cdb-dev -n cdbnamespace - $(KUBECTL) wait --for=delete cdb cdb-dev -n $(CDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - $(KUBECTL) get cdb -n cdbnamespaace - $(KUBECTL) get pdb -n pdbnamespaace - - -## SEQ | ACTION -## ----+---------------- -## 00 | create ords pod -## 01 | create pdb -## 02 | open pdb -## 03 | clone pdb -## 04 | close pdb -## 05 | unpug pdb -## 06 | plug pdb -## 07 | delete pdb (declarative) - - -runall01: run00 run01.1 run01.2 run03.1 run03.2 run04.1 run05.1 run06.1 run02.1 run07.1 - - -###### BUILD ORDS IMAGE ###### - -createimage: - $(RUNTIME) build -t $(IMAGE) $(ORDSIMGDIR) - -createimageproxy: - $(RUNTIME) build -t $(IMAGE) $(ORDSIMGDIR) --build-arg https_proxy=$(HTTPS_PROXY) --build-arg http_proxy=$(HTTP_PROXY) - -tagimage: - @echo "TAG IMAGE" - $(RUNTIME) tag $(IMAGE) $(ORDSIMG) - -push: - $(RUNTIME) push $(ORDSIMG) - - diff --git a/docs/multitenant/ords-based/usecase/map_pdb1_resource.yaml b/docs/multitenant/ords-based/usecase/map_pdb1_resource.yaml deleted file mode 100644 index b71b59d5..00000000 --- a/docs/multitenant/ords-based/usecase/map_pdb1_resource.yaml +++ /dev/null @@ -1,49 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb1 - namespace: pdbnamespace - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "cdbnamespace" - cdbName: "DB12" - pdbName: "pdbdev" - assertivePdbDeletion: true - fileNameConversions: "NONE" - totalSize: "1G" - tempSize: "100M" - action: "Map" - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase/map_pdb2_resource.yaml b/docs/multitenant/ords-based/usecase/map_pdb2_resource.yaml deleted file mode 100644 index 75d056d0..00000000 --- a/docs/multitenant/ords-based/usecase/map_pdb2_resource.yaml +++ /dev/null @@ -1,49 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb2 - namespace: pdbnamespace - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "cdbnamespace" - cdbName: "DB12" - pdbName: "pdbprd" - assertivePdbDeletion: true - fileNameConversions: "NONE" - totalSize: "1G" - tempSize: "100M" - action: "Map" - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase/map_pdb3_resource.yaml b/docs/multitenant/ords-based/usecase/map_pdb3_resource.yaml deleted file mode 100644 index 3523aa68..00000000 --- a/docs/multitenant/ords-based/usecase/map_pdb3_resource.yaml +++ /dev/null @@ -1,49 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb3 - namespace: pdbnamespace - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "cdbnamespace" - cdbName: "DB12" - pdbName: "new_clone" - assertivePdbDeletion: true - fileNameConversions: "NONE" - totalSize: "1G" - tempSize: "100M" - action: "Map" - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase/open_pdb1_resource.yaml b/docs/multitenant/ords-based/usecase/open_pdb1_resource.yaml deleted file mode 100644 index 93a1d43a..00000000 --- a/docs/multitenant/ords-based/usecase/open_pdb1_resource.yaml +++ /dev/null @@ -1,47 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb1 - namespace: pdbnamespace - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "cdbnamespace" - cdbName: "DB12" - pdbName: "pdbdev" - action: "Modify" - pdbState: "OPEN" - modifyOption: "READ WRITE" - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase/open_pdb2_resource.yaml b/docs/multitenant/ords-based/usecase/open_pdb2_resource.yaml deleted file mode 100644 index deb27f9a..00000000 --- a/docs/multitenant/ords-based/usecase/open_pdb2_resource.yaml +++ /dev/null @@ -1,47 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb2 - namespace: pdbnamespace - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "cdbnamespace" - cdbName: "DB12" - pdbName: "pdbprd" - action: "Modify" - pdbState: "OPEN" - modifyOption: "READ WRITE" - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase/open_pdb3_resource.yaml b/docs/multitenant/ords-based/usecase/open_pdb3_resource.yaml deleted file mode 100644 index 586f2f57..00000000 --- a/docs/multitenant/ords-based/usecase/open_pdb3_resource.yaml +++ /dev/null @@ -1,47 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb3 - namespace: pdbnamespace - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "cdbnamespace" - cdbName: "DB12" - pdbName: "new_clone" - action: "Modify" - pdbState: "OPEN" - modifyOption: "READ WRITE" - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase/parameters.txt b/docs/multitenant/ords-based/usecase/parameters.txt deleted file mode 100644 index 64dc3759..00000000 --- a/docs/multitenant/ords-based/usecase/parameters.txt +++ /dev/null @@ -1,61 +0,0 @@ - -######################## -## REST SERVER IMAGE ### -######################## - -ORDSIMG:_your_container_registry/ords-dboper:latest - -############################## -## TNS URL FOR CDB CREATION ## -############################## -TNSALIAS:"T H I S I S J U S T A N E X A M P L E (DESCRIPTION=(CONNECT_TIMEOUT=90)(RETRY_COUNT=30)(RETRY_DELAY=10)(TRANSPORT_CONNECT_TIMEOUT=70)(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL=TCP)(HOST=scan12.testrac.com)(PORT=1521)(IP=V4_ONLY))(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL=TCP)(HOST=scan34.testrac.com)(PORT=1521)(IP=V4_ONLY))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=TESTORDS)))" - -########################################### -## ORDS PUBLIC USER ## -########################################### -ORDPWD:change_me_please - -########################################### -## SYSPASSWORD ## -########################################### -SYSPWD:change_me_please - -####################### -## HTTPS CREDENTIAL ### -####################### - -WBUSER:change_me_please -WBPASS:change_me_please - -##################### -## PDB ADMIN USER ### -##################### - -PDBUSR:change_me_please -PDBPWD:change_me_please - -##################### -## CDB ADMIN USER ### -##################### - -CDBUSR:C##DBAPI_CDB_ADMIN -CDBPWD:change_me_please - -################### -### NAMESPACES #### -################### - -PDBNAMESPACE:pdbnamespace -CDBNAMESPACE:cdbnamespace - -#################### -### COMPANY NAME ### -#################### - -COMPANY:oracle - -#################### -### APIVERSION ### -#################### - -APIVERSION:v4 diff --git a/docs/multitenant/ords-based/usecase/pdbnamespace_binding.yaml b/docs/multitenant/ords-based/usecase/pdbnamespace_binding.yaml deleted file mode 100644 index 5af79ed6..00000000 --- a/docs/multitenant/ords-based/usecase/pdbnamespace_binding.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: oracle-database-operator-oracle-database-operator-manager-rolebinding1 - namespace: pdbnamespace -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: oracle-database-operator-manager-role -subjects: -- kind: ServiceAccount - name: default - namespace: oracle-database-operator-system diff --git a/docs/multitenant/ords-based/usecase/plug_pdb1_resource.yaml b/docs/multitenant/ords-based/usecase/plug_pdb1_resource.yaml deleted file mode 100644 index 9eb5ed77..00000000 --- a/docs/multitenant/ords-based/usecase/plug_pdb1_resource.yaml +++ /dev/null @@ -1,53 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb1 - namespace: pdbnamespace - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "cdbnamespace" - cdbName: "DB12" - pdbName: "pdbdev" - xmlFileName: "/tmp/pdb.xml" - action: "plug" - fileNameConversions: "NONE" - sourceFileNameConversions: "NONE" - copyAction: "MOVE" - totalSize: "1G" - tempSize: "100M" - assertivePdbDeletion: true - action: "Plug" - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase/unplug_pdb1_resource.yaml b/docs/multitenant/ords-based/usecase/unplug_pdb1_resource.yaml deleted file mode 100644 index 0036d5f7..00000000 --- a/docs/multitenant/ords-based/usecase/unplug_pdb1_resource.yaml +++ /dev/null @@ -1,46 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb1 - namespace: pdbnamespace - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "cdbnamespace" - cdbName: "DB12" - pdbName: "pdbdev" - xmlFileName: "/tmp/pdb.xml" - action: "Unplug" - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase01/README.md b/docs/multitenant/ords-based/usecase01/README.md deleted file mode 100644 index 0020541c..00000000 --- a/docs/multitenant/ords-based/usecase01/README.md +++ /dev/null @@ -1,516 +0,0 @@ - - - -# STEP BY STEP USE CASE - -- [STEP BY STEP USE CASE](#step-by-step-use-case) - - [INTRODUCTION](#introduction) - - [OPERATIONAL STEPS](#operational-steps) - - [Download latest version from github ](#download-latest-version-from-github-) - - [Upload webhook certificates ](#upload-webhook-certificates-) - - [Create the dboperator ](#create-the-dboperator-) - - [Create secret for container registry](#create-secret-for-container-registry) - - [Build ords immage ](#build-ords-immage-) - - [Database Configuration](#database-configuration) - - [Create CDB secret](#create-cdb-secret) - - [Create Certificates](#create-certificates) - - [Apply cdb.yaml](#apply-cdbyaml) - - [CDB - Logs and throuble shutting](#cdb---logs-and-throuble-shutting) - - [Create PDB secret](#create-pdb-secret) - - [Apply pdb yaml file to create pdb](#apply-pdb-yaml-file-to-create-pdb) - - [Other actions](#other-actions) - - [Imperative approach on pdb deletion - will be avilable in 1.2.0 ](#imperative-approach-on-pdb-deletion) - - - -##### INTRODUCTION - -This readme is a step by step guide used to implement database multi tenant operator. It assumes that a kubernets cluster and a database server are already available (no matter if single instance or RAC). kubectl must be configured in order to reach k8s cluster. - -The following table reports the parameters required to configure and use oracle multi tenant controller for pluggable database lifecycle management. - -| yaml file parameters | value | description /ords parameter | -|-------------- |--------------------------- |-------------------------------------------------| -| dbserver | or | [--db-hostname][1] | -| dbTnsurl | | [--db-custom-url/db.customURL][dbtnsurl] | -| port | | [--db-port][2] | -| cdbName | | Container Name | -| name | | Ords podname prefix in cdb.yaml | -| name | | pdb resource in pdb.yaml | -| ordsImage | /ords-dboper:latest|My public container registry | -| pdbName | | Pluggable database name | -| servicename | | [--db-servicename][3] | -| sysadmin_user | | [--admin-user][adminuser] | -| sysadmin_pwd | | [--password-stdin][pwdstdin] | -| cdbadmin_user | | [db.cdb.adminUser][1] | -| cdbadmin_pwd | | [db.cdb.adminUser.password][cdbadminpwd] | -| webserver_user| | [https user][http] NOT A DB USER | -| webserver_pwd | | [http user password][http] | -| ords_pwd | | [ORDS_PUBLIC_USER password][public_user] | -| pdbTlsKey | | [standalone.https.cert.key][key] | -| pdbTlsCrt | | [standalone.https.cert][cr] | -| pdbTlsCat | | certificate authority | -| cdbOrdsPrvKey | | private key (cdb crd) | -| pdbOrdsPrvKey | | private key (pdb crd) | -| assertivePdbDeletion | boolean | [turn on imperative approach on crd deleteion][imperative] | - -> A [makfile](./makefile) is available to sped up the command execution for the multitenant setup and test. See the comments in the header of file - -### OPERATIONAL STEPS ----- - - -#### Download latest version from github - - -```bash -git clone https://github.com/oracle/oracle-database-operator.git -``` - -If golang compiler is installed on your environment and you've got a public container registry then you can compile the operator, upload to the registry and use it - -```bash - -cd oracle-database-operator -make generate -make manifests -make install -make docker-build IMG=/operator:latest - -make operator-yaml IMG=operator:latest -``` - -> **NOTE:** The last make executions recreates the **oracle-database-operator.yaml** with the **image:** parameter pointing to your public container registry. If you don't have a golang compilation environment you can use the **oracle-database-operator.yaml** provided in the github distribution. Check [operator installation documentation](../installation/OPERATOR_INSTALLATION_README.md ) for more details. - -> **NOTE:** If you are using oracle-container-registry make sure to accept the license agreement otherwise the operator image pull fails. ----- - -#### Upload webhook certificates - -```bash -kubectl apply -f https://github.com/jetstack/cert-manager/releases/latest/download/cert-manager.yaml -``` - -#### Create the dboperator - -```bash -cd oracle-database-operator -/usr/bin/kubectl apply -f oracle-database-operator.yaml -``` -+ Check the status of the operator - -```bash -/usr/bin/kubectl get pods -n oracle-database-operator-system -NAME READY STATUS RESTARTS AGE -oracle-database-operator-controller-manager-557ff6c659-g7t66 1/1 Running 0 10s -oracle-database-operator-controller-manager-557ff6c659-rssmj 1/1 Running 0 10s -oracle-database-operator-controller-manager-557ff6c659-xpswv 1/1 Running 0 10s - -``` ----- - -#### Create secret for container registry - -+ Make sure to login to your container registry and then create the secret for you container registry. - -```bash -docker login **** -/usr/bin/kubectl create secret generic container-registry-secret --from-file=.dockerconfigjson=/home/oracle/.docker/config.json --type=kubernetes.io/dockerconfigjson -n oracle-database-operator-system -``` - -+ Check secret - -```bash -kubectl get secret -n oracle-database-operator-system -NAME TYPE DATA AGE -container-registry-secret kubernetes.io/dockerconfigjson 1 19s -webhook-server-cert kubernetes.io/tls -``` ----- - -#### Build ords immage - -+ Build the ords image, downloading ords software is no longer needed; just build the image and push it to your repository - -```bash -cd oracle-database-operator/ords -docker build -t oracle/ords-dboper:latest . -``` - -[Example of execution](./logfiles/BuildImage.log) -+ Login to your container registry and push the ords image. - -```bash -docker tag /ords-dboper:latest -docker push /ords-dboper:latest -``` -[Example of execution](./logfiles/tagandpush.log) - ----- - -#### Database Configuration - -+ Configure Database - -Connect as sysdba and execute the following script in order to create the required ords accounts. - -```sql -ALTER SESSION SET "_oracle_script"=true; -DROP USER cascade; -CREATE USER IDENTIFIED BY CONTAINER=ALL ACCOUNT UNLOCK; -GRANT SYSOPER TO CONTAINER = ALL; -GRANT SYSDBA TO CONTAINER = ALL; -GRANT CREATE SESSION TO CONTAINER = ALL; -``` ----- -#### Create Certificates - -+ Create certificates: At this stage we need to create certificates on our local machine and upload into kubernetes cluster by creating new secrets. - - - -```text - - +-----------+ - | openssl | - +-----------+ - | - | - +-----------+ - | tls.key | - | tls.crt +------------+ - | ca.crt | | - +-----------+ | - | - +------------------------|---------------------------+ - |KUBERNETES +------+--------+ | - |CLUSTER +---|kubernet secret|---+ | - | | +---------------+ | | - | | | | - | +----------+---+ https +--+----------+ | - | |ORDS CONTAINER|<-------------->| PDB/POD | | - | +----------+---+ +-------------+ | - | cdb.yaml | pdb.yaml | - +-------------|--------------------------------------+ - | - | - +-----------+ - | DB SERVER | - +-----------+ - -``` - -```bash - -openssl genrsa -out 2048 -openssl req -new -x509 -days 365 -key -subj "/C=CN/ST=GD/L=SZ/O=oracle, Inc./CN=oracle Root CA" -out -openssl req -newkey rsa:2048 -nodes -keyout -subj "/C=CN/ST=GD/L=SZ/O=oracle, Inc./CN=-ords" -out server.csr -/usr/bin/echo "subjectAltName=DNS:-ords,DNS:www.example.com" > extfile.txt -openssl x509 -req -extfile extfile.txt -days 365 -in server.csr -CA -CAkey -CAcreateserial -out - -kubectl create secret tls db-tls --key="" --cert="" -n oracle-database-operator-system -kubectl create secret generic db-ca --from-file= -n oracle-database-operator-system - -``` - -[Example of execution:](./logfiles/openssl_execution.log) - -#### CDB and PDB credential - -Refer to the [landing page](../README.md) to implement openssl encrpted secrets. - ----- - -#### Apply cdb.yaml - - -**note:** - Before creating the CDB pod make sure that all the pluggable databases in the container DB are open. - - -+ Create ords container - -```bash -/usr/bin/kubectl apply -f create_ords_pod.yaml -n oracle-database-operator-system -``` -Example: **create_ords_pod.yaml** - -```yaml -apiVersion: database.oracle.com/v1alpha1 -kind: CDB -metadata: - name: cdb-dev - namespace: oracle-database-operator-system -spec: - cdbName: "DB12" - ordsImage: ".............your registry............./ords-dboper:latest" - ordsImagePullPolicy: "Always" - dbTnsurl : "...Container tns alias....." - replicas: 1 - sysAdminPwd: - secret: - secretName: "syspwd" - key: "e_syspwd.txt" - ordsPwd: - secret: - secretName: "ordpwd" - key: "e_ordpwd.txt" - cdbAdminUser: - secret: - secretName: "cdbusr" - key: "e_cdbusr.txt" - cdbAdminPwd: - secret: - secretName: "cdbpwd" - key: "e_cdbpwd.txt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - cdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - cdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - cdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" - - -``` -> **Note** if you are working in dataguard environment with multiple sites (AC/DR) specifying the host name (dbServer/dbPort/serviceName) may not be the suitable solution for this kind of configuration, use **dbTnsurl** instead. Specify the whole tns string which includes the hosts/scan list. - -``` - +----------+ - ____| standbyB | - | | scanB | (DESCRIPTION= - +----------+ | +----------+ (CONNECT_TIMEOUT=90) - | primary |_______| (RETRY_COUNT=30)(RETRY_DELAY=10)(TRANSPORT_CONNECT_TIMEOUT=70) - | scanA | | +----------+ (TRANSPORT_CONNECT_TIMEOUT=10)(LOAD_BALLANCE=ON) - +----------+ |___| stanbyC | (ADDRESS=(PROTOCOL=TCP)(HOST=scanA.testrac.com)(PORT=1521)(IP=V4_ONLY)) - | scanC | (ADDRESS=(PROTOCOL=TCP)(HOST=scanB.testrac.com)(PORT=1521)(IP=V4_ONLY)) - +----------+ (ADDRESS=(PROTOCOL=TCP)(HOST=scanC.testrac.com)(PORT=1521)(IP=V4_ONLY)) - (CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=TESTORDS))) - - - dbtnsurl:((DESCRIPTION=(CONNECT_TIMEOUT=90)(RETRY_COUNT=30)(RETRY_DELAY=10)(TRANSPORT_CONNECT_TIMEOUT=70)(TRANS...... -``` - -[create_ords_pod.yaml example](./create_ords_pod.yaml) - - ----- - -#### CDB - Logs and throuble shutting - -+ Check the status of ords container - -```bash -/usr/bin/kubectl get pods -n oracle-database-operator-system -NAME READY STATUS RESTARTS AGE -cdb-dev-ords-rs-m9ggp 0/1 ContainerCreating 0 67s <----- -oracle-database-operator-controller-manager-557ff6c659-g7t66 1/1 Running 0 11m -oracle-database-operator-controller-manager-557ff6c659-rssmj 1/1 Running 0 11m -oracle-database-operator-controller-manager-557ff6c659-xpswv 1/1 Running 0 11m -``` -+ Make sure that the cdb container is running - -```bash -/usr/bin/kubectl get pods -n oracle-database-operator-system -NAME READY STATUS RESTARTS AGE -cdb-dev-ords-rs-dnshz 1/1 Running 0 31s -oracle-database-operator-controller-manager-557ff6c659-9bjfl 1/1 Running 0 2m42s -oracle-database-operator-controller-manager-557ff6c659-cx8hd 1/1 Running 0 2m42s -oracle-database-operator-controller-manager-557ff6c659-rq9xs 1/1 Running 0 2m42s -``` -+ Check the status of the services - -```bash -kubectl get cdb -n oracle-database-operator-system -NAME CDB NAME DB SERVER DB PORT REPLICAS STATUS MESSAGE -[.....................................................] Ready -``` -+ Use log file to trouble shutting - -```bash -/usr/bin/kubectl logs `/usr/bin/kubectl get pods -n oracle-database-operator-system|grep ords|cut -d ' ' -f 1` -n oracle-database-operator-system -``` -[Example of cdb creation log](./logfiles/cdb_creation.log) - -+ Test REST API from the pod. By querying the metadata catalog you can verify the status of https setting - -```bash - /usr/bin/kubectl exec -it `/usr/bin/kubectl get pods -n oracle-database-operator-system|grep ords|cut -d ' ' -f 1` -n oracle-database-operator-system -i -t -- /usr/bin/curl -sSkv -k -X GET https://localhost:8888/ords/_/db-api/stable/metadata-catalog/ -``` -[Example of execution](./logfiles/testapi.log) - -+ Verify the pod environment varaibles - ```bash - kubectl set env pods --all --list -n oracle-database-operator-system - ``` - -+ Connect to cdb pod - -```bash - kubectl exec -it `kubectl get pods -n oracle-database-operator-system|grep ords|cut -d ' ' -f 1` -n oracle-database-operator-system bash -``` -+ Dump ords server configuration - -```bash -/usr/bin/kubectl exec -it `/usr/bin/kubectl get pods -n oracle-database-operator-system|grep ords|cut -d ' ' -f 1` -n oracle-database-operator-system -i -t -- /usr/local/bin/ords --config /etc/ords/config config list -``` -[Example of executions](./logfiles/ordsconfig.log) - ------ -#### Apply pdb yaml file to create pdb - -```bash -/usr/bin/kubectl apply -f create_pdb1_resource.yaml -n oracle-database-operator-system -``` - -Example: **create_pdb1_resource.yaml** - -```yaml -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb1 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "cdbnamespace" - cdbName: "DB12" - pdbName: "pdbdev" - assertivePdbDeletion: true - fileNameConversions: "NONE" - unlimitedStorage: false - tdeImport: false - totalSize: "2G" - tempSize: "800M" - action: "Create" - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" -``` - -+ Monitor the pdb creation status until message is success - -```bash -kubectl get pdbs --all-namespaces=true - - +-----------------------------------------+ +-----------------------------------------+ - | STATUS MESSAGE |______\ | STATUS MESSAGE | - | Creating Waiting for PDB to be created | / | Ready Success | - +-----------------------------------------+ +-----------------------------------------+ - -NAMESPACE NAME DBSERVER CDB NAME PDB NAME PDB STATE PDB SIZE STATUS MESSAGE -oracle-database-operator-system 1G Creating Waiting for PDB to be created - -[wait sometimes] - -kubectl get pdbs --all-namespaces=true -NAMESPACE NAME DBSERVER CDB NAME PDB NAME PDB STATE PDB SIZE STATUS MESSAGE -oracle-database-operator-system pdb1 READ WRITE 1G Ready Success -``` - -Connect to the hosts and verify the PDB creation. - -```text -[oracle@racnode1 ~]$ sqlplus '/as sysdba' -[...] -Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production -Version 19.15.0.0.0 - - -SQL> show pdbs - - CON_ID CON_NAME OPEN MODE RESTRICTED ----------- ------------------------------ ---------- ---------- - 2 PDB$SEED READ ONLY NO - 3 PDBDEV READ WRITE NO - -``` -Check controller log to debug pluggable database life cycle actions in case of problem - -```bash -kubectl logs -f $(kubectl get pods -n oracle-database-operator-system|grep oracle-database-operator-controller|head -1|cut -d ' ' -f 1) -n oracle-database-operator-system -``` - ---- - -#### Other actions - -Configure and use other yaml files to perform pluggable database life cycle managment action **pdb_open.yaml** **pdb_close.yaml** - -> **Note** sql command *"alter pluggable database open instances=all;"* acts only on closed databases, so you don't get any oracle error in case of execution against an pluggable database already opened - -#### Imperative approach on pdb deletion - -If **assertivePdbDeletion** is true then the command execution **kubectl delete pdbs crd_pdb_name** automatically deletes the pluggable database on the container database. By default this option is disabled. You can use this option during **create**,**map**,**plug** and **clone** operation. If the option is disabled then **kubectl delete** only deletes the crd but not the pluggable on the container db. Database deletion uses the option **including datafiles**. -If you drop the CRD without dropping the pluggable database and you need to recreate the CRD then you can use the [pdb_map.yaml](./pdb_map.yaml) - - -[1]:https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/22.2/ordig/installing-and-configuring-oracle-rest-data-services.html#GUID-E9625FAB-9BC8-468B-9FF9-443C88D76FA1:~:text=Table%202%2D2%20Command%20Options%20for%20Command%2DLine%20Interface%20Installation - -[2]:https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/22.2/ordig/installing-and-configuring-oracle-rest-data-services.html#GUID-E9625FAB-9BC8-468B-9FF9-443C88D76FA1:~:text=Table%202%2D2%20Command%20Options%20for%20Command%2DLine%20Interface%20Installation - -[3]:https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/22.2/ordig/installing-and-configuring-oracle-rest-data-services.html#GUID-DAA027FA-A4A6-43E1-B8DD-C92B330C2341:~:text=%2D%2Ddb%2Dservicename%20%3Cstring%3E - -[adminuser]:https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/22.2/ordig/installing-and-configuring-oracle-rest-data-services.html#GUID-A9AED253-4EEC-4E13-A0C4-B7CE82EC1C22:~:text=Table%202%2D6%20Command%20Options%20for%20Uninstall%20CLI - -[public_user]:https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/22.2/ordig/using-multitenant-architecture-oracle-rest-data-services.html#GUID-E64A141A-A71F-4979-8D33-C5F8496D3C19:~:text=Preinstallation%20Tasks%20for%20Oracle%20REST%20Data%20Services%20CDB%20Installation - -[key]:https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/22.2/ordig/about-REST-configuration-files.html#GUID-006F916B-8594-4A78-B500-BB85F35C12A0:~:text=standalone.https.cert.key - -[cr]:https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/22.2/ordig/about-REST-configuration-files.html#GUID-006F916B-8594-4A78-B500-BB85F35C12A0 - -[cdbadminpwd]:https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/22.2/ordig/about-REST-configuration-files.html#GUID-006F916B-8594-4A78-B500-BB85F35C12A0:~:text=Table%20C%2D1%20Oracle%20REST%20Data%20Services%20Configuration%20Settings - -[pwdstdin]:https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/22.2/ordig/installing-and-configuring-oracle-rest-data-services.html#GUID-88479C84-CAC1-4133-A33E-7995A645EC05:~:text=default%20database%20pool.-,2.1.4.1%20Understanding%20Command%20Options%20for%20Command%2DLine%20Interface%20Installation,-Table%202%2D2 - -[http]:https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/22.2/ordig/installing-and-configuring-oracle-rest-data-services.html#GUID-BEECC057-A8F5-4EAB-B88E-9828C2809CD8:~:text=Example%3A%20delete%20%5B%2D%2Dglobal%5D-,user%20add,-Add%20a%20user - -[dbtnsurl]:https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/22.2/ordig/installing-and-configuring-oracle-rest-data-services.html#GUID-A9AED253-4EEC-4E13-A0C4-B7CE82EC1C22 - -[imperative]:https://kubernetes.io/docs/concepts/overview/working-with-objects/object-management/ - - diff --git a/docs/multitenant/ords-based/usecase01/ca.crt b/docs/multitenant/ords-based/usecase01/ca.crt deleted file mode 100644 index cc9aa8bb..00000000 --- a/docs/multitenant/ords-based/usecase01/ca.crt +++ /dev/null @@ -1,25 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEJTCCAw2gAwIBAgIUNXPtpnNEFBCMcnxRP5kJsBDpafcwDQYJKoZIhvcNAQEL -BQAwgaExCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRUwEwYDVQQH -DAxTYW5GcmFuY2lzY28xEDAOBgNVBAoMB29yYWNsZSAxNjA0BgNVBAMMLWNkYi1k -ZXYtb3Jkcy5vcmFjbGUtZGF0YWJhc2Utb3BlcmF0b3Itc3lzdGVtIDEcMBoGA1UE -AwwTbG9jYWxob3N0ICBSb290IENBIDAeFw0yNDA4MTIxNTMyMzVaFw0yNTA4MTIx -NTMyMzVaMIGhMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMG -A1UEBwwMU2FuRnJhbmNpc2NvMRAwDgYDVQQKDAdvcmFjbGUgMTYwNAYDVQQDDC1j -ZGItZGV2LW9yZHMub3JhY2xlLWRhdGFiYXNlLW9wZXJhdG9yLXN5c3RlbSAxHDAa -BgNVBAMME2xvY2FsaG9zdCAgUm9vdCBDQSAwggEiMA0GCSqGSIb3DQEBAQUAA4IB -DwAwggEKAoIBAQCmnGVApwUBF1kpqcyr2nYeED0VKvefpoHLtxHSP+vP0lWhW7NU -NJlb1YuUagjJ4/rpGRQmPxcVU51n3aAW3a5qHazIpNxNa3fvgB1rMOPFxGmdel2d -8lIt+u19q19DknX/GNgH9Mog8RcyZyPeA7d2icT8TBo74ognr+8p68O3CjBHQ8EM -SnRQR7/bh1c10Uia317ilKvs+I7oErTq5JFLeIuPDdAJ6UncaeblTf1XJ/1FrpHG -fSS7xmR8x0/MblBQlku4eImYmN35g+eRgf8bLDDwC+GPzDnAqqMLjx6h2N+btDxr -tnn05qyqmN9G08uUlP4d4BXi9ISb/toYypklAgMBAAGjUzBRMB0GA1UdDgQWBBS+ -a4X2XTmdPivdQtqDWNpfOtHypDAfBgNVHSMEGDAWgBS+a4X2XTmdPivdQtqDWNpf -OtHypDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAZIrGBNdSw -pe+1agefHfaR8hjZQiXBxdwHM1gR2LWOaFzMS8Q/eRETHTO6+VwQ0/FNaXbAqgqk -G317gZMXS5ZmXuOi28fTpAQtuzokkEKpoK0puTnbXOKGA2QSbBlpSFPqb3aJXvVt -afXFQb5P/0mhr4kuVt7Ech82WM/o5ryFgObygDayDmLatTp+VaRmBZPksnSMhslq -3zPyS7bx2YhbPTLkDxq8Mfr/Msxme8LvSXUpFf4PpQ5zwp1RE32gekct6eRQLmqU -5LXY2aPtqpMF0fBpcwPWbqA9gOYCRKcvXXIr+u1x8hf6Er6grZegHkM9TQ8s0hJd -sxi5tK0lPMHJ ------END CERTIFICATE----- diff --git a/docs/multitenant/ords-based/usecase01/ca.key b/docs/multitenant/ords-based/usecase01/ca.key deleted file mode 100644 index 1a0ef89d..00000000 --- a/docs/multitenant/ords-based/usecase01/ca.key +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEAppxlQKcFARdZKanMq9p2HhA9FSr3n6aBy7cR0j/rz9JVoVuz -VDSZW9WLlGoIyeP66RkUJj8XFVOdZ92gFt2uah2syKTcTWt374AdazDjxcRpnXpd -nfJSLfrtfatfQ5J1/xjYB/TKIPEXMmcj3gO3donE/EwaO+KIJ6/vKevDtwowR0PB -DEp0UEe/24dXNdFImt9e4pSr7PiO6BK06uSRS3iLjw3QCelJ3Gnm5U39Vyf9Ra6R -xn0ku8ZkfMdPzG5QUJZLuHiJmJjd+YPnkYH/Gyww8Avhj8w5wKqjC48eodjfm7Q8 -a7Z59OasqpjfRtPLlJT+HeAV4vSEm/7aGMqZJQIDAQABAoIBAGXRGYdjCgnarOBr -Jeq3vIsuvUVcVqs35AYMQFXOPltoXHAZTAPfiQC4BW6TRf+q1MDyVH/y+jZMPNsm -cxjGLDopHFgZd4/QZyDzmAbTf75yA2D7UI6fcV0sBUpRGgx/SqC0HADwtT1gWB6z -LRYWC13jX4AXOcjy7OXj/DIQJDCMivedt3dv0rDWJUcBCnVot5tr6zjycefxGKa8 -mG9LZQb3x71FxwpFUau3WLDSwOjtXCeMytaGXnGmIiofJmXnFi0KA4ApzKL7QV6I -cCBS1WBLLXeVM9vOfrtzKVLWGe0qADyLm35p5Fnl3j+vimkk8h/2DEvCZ75c987m -O3PEgdkCgYEA0Scg+KINTA78sdZL5v2+8fT4b+EfoCgUqfr10ReUPKrz3HfrVHcj -7Vf00RT52TkfmkL3mIdLyBUzQ9vzPgweo1o4yKCKNCpR9G3ydNW+KI5jSYnq2efz -Gpe3wTt+8YoyCgm9eUxNWjfO9fipS91sSotY0PovkBohj9aezfcWp1sCgYEAy+3n -MIvW/9PoYxCvQ9fDGLvx3B4/uy0ZYPh7j5edDuaRzwFd2YXUysXhJVuqTp0KT2tv -dRPFRE9Oq5N8e5ITIUiKLQ5PIRNBZm8CiAof+XS1fIuU+MTDaTfXwyGQo0xSg8MB -ITnJulmUlkcTWEtGyBi9sIjor5ve8kqvyrdAKX8CgYA9ZUUSd0978jJPad6iEf6J -PCXpgaYs91cJhre+BzPmkzA+mZ0lEEwlkdo1vfiRwWj7eYkA50Zhl4eS9e/zWM9t -mEBu9GFdasbf/55amZvWf+W5YpjkGmiMd9jjCjn7YVvLAozyHGngf91q6vGXaYou -X7VUsvxfSqxrcs7vGwc1XQKBgB0qaD80MMqj5v+MGlTsndWCw8OEe/7sI04QG7Pc -rjS8Wyws+NwsXNOnW1z5cDEQGrJjHiyzaCot4YV+cXZG3P+MnV52RnDnjRn2VHla -YVpPC8nFOMgfdAcvWmdo/IOuXbrEf/vdhPFm8G5Ruf2NvpDNoQuHeSfsdgVXEy89 -6CpHAoGBAMZInYD0XjcnZNqiQnQdcIJN3CqDIU76Z45OOpcUrYrvTos2xhGLrRI5 -qrk5Od/sovJfse+oUIIbgsABieqtyfxM03iu8fvbahIY6Un1iw2KN9t+mcPrSZJK -jTXKf7XxZ1+yN9kvohdLc65ySyXFSm++glDq8WGrmnOtLUlr0oMm ------END RSA PRIVATE KEY----- diff --git a/docs/multitenant/ords-based/usecase01/ca.srl b/docs/multitenant/ords-based/usecase01/ca.srl deleted file mode 100644 index 7c9868bb..00000000 --- a/docs/multitenant/ords-based/usecase01/ca.srl +++ /dev/null @@ -1 +0,0 @@ -77D97AB4C4B6D5A9377B84B455D3E16348C6DE04 diff --git a/docs/multitenant/ords-based/usecase01/cdb_create.yaml b/docs/multitenant/ords-based/usecase01/cdb_create.yaml deleted file mode 100644 index 01fc0a18..00000000 --- a/docs/multitenant/ords-based/usecase01/cdb_create.yaml +++ /dev/null @@ -1,44 +0,0 @@ -apiVersion: database.oracle.com/v1alpha1 -kind: CDB -metadata: - name: cdb-dev - namespace: oracle-database-operator-system -spec: - cdbName: "DB12" - ordsImage: ".............your registry............./ords-dboper:latest" - ordsImagePullPolicy: "Always" - dbTnsurl : "...Container tns alias....." - replicas: 1 - sysAdminPwd: - secret: - secretName: "cdb1-secret" - key: "sysadmin_pwd" - ordsPwd: - secret: - secretName: "cdb1-secret" - key: "ords_pwd" - cdbAdminUser: - secret: - secretName: "cdb1-secret" - key: "cdbadmin_user" - cdbAdminPwd: - secret: - secretName: "cdb1-secret" - key: "cdbadmin_pwd" - webServerUser: - secret: - secretName: "cdb1-secret" - key: "webserver_user" - webServerPwd: - secret: - secretName: "cdb1-secret" - key: "webserver_pwd" - cdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - cdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - diff --git a/docs/multitenant/ords-based/usecase01/cdb_secret.yaml b/docs/multitenant/ords-based/usecase01/cdb_secret.yaml deleted file mode 100644 index 567b90a4..00000000 --- a/docs/multitenant/ords-based/usecase01/cdb_secret.yaml +++ /dev/null @@ -1,17 +0,0 @@ -# -# Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. -# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. -# -apiVersion: v1 -kind: Secret -metadata: - name: cdb1-secret - namespace: oracle-database-operator-system -type: Opaque -data: - ords_pwd: ".....base64 encoded password...." - sysadmin_pwd: ".....base64 encoded password...." - cdbadmin_user: ".....base64 encoded password...." - cdbadmin_pwd: ".....base64 encoded password...." - webserver_user: ".....base64 encoded password...." - webserver_pwd: ".....base64 encoded password...." diff --git a/docs/multitenant/ords-based/usecase01/clone_pdb1_resource.yaml b/docs/multitenant/ords-based/usecase01/clone_pdb1_resource.yaml deleted file mode 100644 index 3cc2c3dd..00000000 --- a/docs/multitenant/ords-based/usecase01/clone_pdb1_resource.yaml +++ /dev/null @@ -1,50 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb3 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "oracle-database-operator-system" - cdbName: "DB12" - pdbName: "new_clone" - srcPdbName: "pdbdev" - fileNameConversions: "NONE" - totalSize: "UNLIMITED" - tempSize: "UNLIMITED" - assertivePdbDeletion: true - action: "Clone" - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase01/clone_pdb2_resource.yaml b/docs/multitenant/ords-based/usecase01/clone_pdb2_resource.yaml deleted file mode 100644 index 28a4eab6..00000000 --- a/docs/multitenant/ords-based/usecase01/clone_pdb2_resource.yaml +++ /dev/null @@ -1,50 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb4 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "oracle-database-operator-system" - cdbName: "DB12" - pdbName: "new_clone2" - srcPdbName: "pdbprd" - fileNameConversions: "NONE" - totalSize: "UNLIMITED" - tempSize: "UNLIMITED" - assertivePdbDeletion: true - action: "Clone" - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase01/close_pdb1_resource.yaml b/docs/multitenant/ords-based/usecase01/close_pdb1_resource.yaml deleted file mode 100644 index a5c3cf59..00000000 --- a/docs/multitenant/ords-based/usecase01/close_pdb1_resource.yaml +++ /dev/null @@ -1,47 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb1 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "oracle-database-operator-system" - cdbName: "DB12" - pdbName: "pdbdev" - pdbState: "CLOSE" - modifyOption: "IMMEDIATE" - action: "Modify" - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase01/close_pdb2_resource.yaml b/docs/multitenant/ords-based/usecase01/close_pdb2_resource.yaml deleted file mode 100644 index 7fa15111..00000000 --- a/docs/multitenant/ords-based/usecase01/close_pdb2_resource.yaml +++ /dev/null @@ -1,47 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb2 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "oracle-database-operator-system" - cdbName: "DB12" - pdbName: "pdbprd" - pdbState: "CLOSE" - modifyOption: "IMMEDIATE" - action: "Modify" - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase01/close_pdb3_resource.yaml b/docs/multitenant/ords-based/usecase01/close_pdb3_resource.yaml deleted file mode 100644 index fa7cf009..00000000 --- a/docs/multitenant/ords-based/usecase01/close_pdb3_resource.yaml +++ /dev/null @@ -1,47 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb3 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "oracle-database-operator-system" - cdbName: "DB12" - pdbName: ""new_clone" - pdbState: "CLOSE" - modifyOption: "IMMEDIATE" - action: "Modify" - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase01/create_ords_pod.yaml b/docs/multitenant/ords-based/usecase01/create_ords_pod.yaml deleted file mode 100644 index e39c4c56..00000000 --- a/docs/multitenant/ords-based/usecase01/create_ords_pod.yaml +++ /dev/null @@ -1,48 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: CDB -metadata: - name: cdb-dev - namespace: oracle-database-operator-system -spec: - cdbName: "DB12" - ordsImage: _your_container_registry/ords-dboper:latest - ordsImagePullPolicy: "Always" - dbTnsurl : "T H I S I S J U S T A N E X A M P L E ....(DESCRIPTION=(CONNECT_TIMEOUT=90)(RETRY_COUNT=30)(RETRY_DELAY=10)(TRANSPORT_CONNECT_TIMEOUT=70)(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL=TCP)(HOST=scan12.testrac.com)(PORT=1521)(IP=V4_ONLY))(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL=TCP)(HOST=scan34.testrac.com)(PORT=1521)(IP=V4_ONLY))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=TESTORDS)))" - replicas: 1 - deletePdbCascade: true - sysAdminPwd: - secret: - secretName: "syspwd" - key: "e_syspwd.txt" - ordsPwd: - secret: - secretName: "ordpwd" - key: "e_ordpwd.txt" - cdbAdminUser: - secret: - secretName: "cdbusr" - key: "e_cdbusr.txt" - cdbAdminPwd: - secret: - secretName: "cdbpwd" - key: "e_cdbpwd.txt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - cdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - cdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - cdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase01/create_pdb1_resource.yaml b/docs/multitenant/ords-based/usecase01/create_pdb1_resource.yaml deleted file mode 100644 index 044d466b..00000000 --- a/docs/multitenant/ords-based/usecase01/create_pdb1_resource.yaml +++ /dev/null @@ -1,51 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb1 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "oracle-database-operator-system" - cdbName: "DB12" - pdbName: "pdbdev" - assertivePdbDeletion: true - fileNameConversions: "NONE" - unlimitedStorage: false - tdeImport: false - totalSize: "2G" - tempSize: "800M" - action: "Create" - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase01/create_pdb2_resource.yaml b/docs/multitenant/ords-based/usecase01/create_pdb2_resource.yaml deleted file mode 100644 index eb36aaa2..00000000 --- a/docs/multitenant/ords-based/usecase01/create_pdb2_resource.yaml +++ /dev/null @@ -1,51 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb2 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "oracle-database-operator-system" - cdbName: "DB12" - pdbName: "pdbprd" - assertivePdbDeletion: true - fileNameConversions: "NONE" - unlimitedStorage: false - tdeImport: false - totalSize: "2G" - tempSize: "800M" - action: "Create" - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase01/delete_pdb1_resource.yaml b/docs/multitenant/ords-based/usecase01/delete_pdb1_resource.yaml deleted file mode 100644 index b0816929..00000000 --- a/docs/multitenant/ords-based/usecase01/delete_pdb1_resource.yaml +++ /dev/null @@ -1,45 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb1 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "oracle-database-operator-system" - pdbName: "pdbdev" - action: "Delete" - dropAction: "INCLUDING" - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase01/delete_pdb2_resource.yaml b/docs/multitenant/ords-based/usecase01/delete_pdb2_resource.yaml deleted file mode 100644 index d2ad95cc..00000000 --- a/docs/multitenant/ords-based/usecase01/delete_pdb2_resource.yaml +++ /dev/null @@ -1,45 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb2 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "oracle-database-operator-system" - pdbName: "pdbprd" - action: "Delete" - dropAction: "INCLUDING" - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase01/extfile.txt b/docs/multitenant/ords-based/usecase01/extfile.txt deleted file mode 100644 index c51d22a3..00000000 --- a/docs/multitenant/ords-based/usecase01/extfile.txt +++ /dev/null @@ -1 +0,0 @@ -subjectAltName=DNS:cdb-dev-ords.oracle-database-operator-system,DNS:www.example.com diff --git a/docs/multitenant/ords-based/usecase01/logfiles/BuildImage.log b/docs/multitenant/ords-based/usecase01/logfiles/BuildImage.log deleted file mode 100644 index f35c66d8..00000000 --- a/docs/multitenant/ords-based/usecase01/logfiles/BuildImage.log +++ /dev/null @@ -1,896 +0,0 @@ -/usr/bin/docker build -t oracle/ords-dboper:latest ../../../ords -Sending build context to Docker daemon 13.82kB -Step 1/12 : FROM container-registry.oracle.com/java/jdk:latest - ---> b8457e2f0b73 -Step 2/12 : ENV ORDS_HOME=/opt/oracle/ords/ RUN_FILE="runOrdsSSL.sh" ORDSVERSION=23.4.0-8 - ---> Using cache - ---> 3317a16cd6f8 -Step 3/12 : COPY $RUN_FILE $ORDS_HOME - ---> 7995edec33cc -Step 4/12 : RUN yum -y install yum-utils bind-utils tree hostname openssl net-tools zip unzip tar wget vim-minimal which sudo expect procps curl lsof && yum-config-manager --add-repo=http://yum.oracle.com/repo/OracleLinux/OL8/oracle/software/x86_64 && yum -y install java-11-openjdk-devel && yum -y install iproute && yum clean all - ---> Running in fe168b01f3ad -Oracle Linux 8 BaseOS Latest (x86_64) 91 MB/s | 79 MB 00:00 -Oracle Linux 8 Application Stream (x86_64) 69 MB/s | 62 MB 00:00 -Last metadata expiration check: 0:00:12 ago on Tue 20 Aug 2024 08:54:50 AM UTC. -Package yum-utils-4.0.21-23.0.1.el8.noarch is already installed. -Package tar-2:1.30-9.el8.x86_64 is already installed. -Package vim-minimal-2:8.0.1763-19.0.1.el8_6.4.x86_64 is already installed. -Package procps-ng-3.3.15-14.0.1.el8.x86_64 is already installed. -Package curl-7.61.1-33.el8_9.5.x86_64 is already installed. -Dependencies resolved. -================================================================================ - Package Arch Version Repository Size -================================================================================ -Installing: - bind-utils x86_64 32:9.11.36-16.el8_10.2 ol8_appstream 453 k - expect x86_64 5.45.4-5.el8 ol8_baseos_latest 266 k - hostname x86_64 3.20-6.el8 ol8_baseos_latest 32 k - lsof x86_64 4.93.2-1.el8 ol8_baseos_latest 253 k - net-tools x86_64 2.0-0.52.20160912git.el8 ol8_baseos_latest 322 k - openssl x86_64 1:1.1.1k-12.el8_9 ol8_baseos_latest 710 k - sudo x86_64 1.9.5p2-1.el8_9 ol8_baseos_latest 1.0 M - tree x86_64 1.7.0-15.el8 ol8_baseos_latest 59 k - unzip x86_64 6.0-46.0.1.el8 ol8_baseos_latest 196 k - wget x86_64 1.19.5-12.0.1.el8_10 ol8_appstream 733 k - which x86_64 2.21-20.el8 ol8_baseos_latest 50 k - zip x86_64 3.0-23.el8 ol8_baseos_latest 270 k -Upgrading: - curl x86_64 7.61.1-34.el8 ol8_baseos_latest 352 k - dnf-plugins-core noarch 4.0.21-25.0.1.el8 ol8_baseos_latest 76 k - libcurl x86_64 7.61.1-34.el8 ol8_baseos_latest 303 k - python3-dnf-plugins-core - noarch 4.0.21-25.0.1.el8 ol8_baseos_latest 263 k - yum-utils noarch 4.0.21-25.0.1.el8 ol8_baseos_latest 75 k -Installing dependencies: - bind-libs x86_64 32:9.11.36-16.el8_10.2 ol8_appstream 176 k - bind-libs-lite x86_64 32:9.11.36-16.el8_10.2 ol8_appstream 1.2 M - bind-license noarch 32:9.11.36-16.el8_10.2 ol8_appstream 104 k - fstrm x86_64 0.6.1-3.el8 ol8_appstream 29 k - libmaxminddb x86_64 1.2.0-10.el8_9.1 ol8_appstream 32 k - libmetalink x86_64 0.1.3-7.el8 ol8_baseos_latest 32 k - protobuf-c x86_64 1.3.0-8.el8 ol8_appstream 37 k - python3-bind noarch 32:9.11.36-16.el8_10.2 ol8_appstream 151 k - python3-ply noarch 3.9-9.el8 ol8_baseos_latest 111 k - tcl x86_64 1:8.6.8-2.el8 ol8_baseos_latest 1.1 M -Installing weak dependencies: - geolite2-city noarch 20180605-1.el8 ol8_appstream 19 M - geolite2-country noarch 20180605-1.el8 ol8_appstream 1.0 M - -Transaction Summary -================================================================================ -Install 24 Packages -Upgrade 5 Packages - -Total download size: 28 M -Downloading Packages: -(1/29): hostname-3.20-6.el8.x86_64.rpm 268 kB/s | 32 kB 00:00 -(2/29): libmetalink-0.1.3-7.el8.x86_64.rpm 257 kB/s | 32 kB 00:00 -(3/29): expect-5.45.4-5.el8.x86_64.rpm 1.4 MB/s | 266 kB 00:00 -(4/29): lsof-4.93.2-1.el8.x86_64.rpm 3.2 MB/s | 253 kB 00:00 -(5/29): net-tools-2.0-0.52.20160912git.el8.x86_ 3.6 MB/s | 322 kB 00:00 -(6/29): python3-ply-3.9-9.el8.noarch.rpm 2.7 MB/s | 111 kB 00:00 -(7/29): openssl-1.1.1k-12.el8_9.x86_64.rpm 10 MB/s | 710 kB 00:00 -(8/29): tree-1.7.0-15.el8.x86_64.rpm 2.2 MB/s | 59 kB 00:00 -(9/29): sudo-1.9.5p2-1.el8_9.x86_64.rpm 14 MB/s | 1.0 MB 00:00 -(10/29): unzip-6.0-46.0.1.el8.x86_64.rpm 6.8 MB/s | 196 kB 00:00 -(11/29): which-2.21-20.el8.x86_64.rpm 2.0 MB/s | 50 kB 00:00 -(12/29): tcl-8.6.8-2.el8.x86_64.rpm 13 MB/s | 1.1 MB 00:00 -(13/29): bind-libs-9.11.36-16.el8_10.2.x86_64.r 6.7 MB/s | 176 kB 00:00 -(14/29): zip-3.0-23.el8.x86_64.rpm 8.4 MB/s | 270 kB 00:00 -(15/29): bind-libs-lite-9.11.36-16.el8_10.2.x86 29 MB/s | 1.2 MB 00:00 -(16/29): bind-license-9.11.36-16.el8_10.2.noarc 3.3 MB/s | 104 kB 00:00 -(17/29): bind-utils-9.11.36-16.el8_10.2.x86_64. 13 MB/s | 453 kB 00:00 -(18/29): fstrm-0.6.1-3.el8.x86_64.rpm 1.2 MB/s | 29 kB 00:00 -(19/29): libmaxminddb-1.2.0-10.el8_9.1.x86_64.r 1.3 MB/s | 32 kB 00:00 -(20/29): geolite2-country-20180605-1.el8.noarch 17 MB/s | 1.0 MB 00:00 -(21/29): protobuf-c-1.3.0-8.el8.x86_64.rpm 1.5 MB/s | 37 kB 00:00 -(22/29): python3-bind-9.11.36-16.el8_10.2.noarc 5.8 MB/s | 151 kB 00:00 -(23/29): wget-1.19.5-12.0.1.el8_10.x86_64.rpm 17 MB/s | 733 kB 00:00 -(24/29): curl-7.61.1-34.el8.x86_64.rpm 12 MB/s | 352 kB 00:00 -(25/29): dnf-plugins-core-4.0.21-25.0.1.el8.noa 2.4 MB/s | 76 kB 00:00 -(26/29): libcurl-7.61.1-34.el8.x86_64.rpm 8.6 MB/s | 303 kB 00:00 -(27/29): python3-dnf-plugins-core-4.0.21-25.0.1 9.8 MB/s | 263 kB 00:00 -(28/29): yum-utils-4.0.21-25.0.1.el8.noarch.rpm 3.0 MB/s | 75 kB 00:00 -(29/29): geolite2-city-20180605-1.el8.noarch.rp 66 MB/s | 19 MB 00:00 --------------------------------------------------------------------------------- -Total 43 MB/s | 28 MB 00:00 -Running transaction check -Transaction check succeeded. -Running transaction test -Transaction test succeeded. -Running transaction - Preparing : 1/1 - Running scriptlet: protobuf-c-1.3.0-8.el8.x86_64 1/1 - Installing : protobuf-c-1.3.0-8.el8.x86_64 1/34 - Installing : fstrm-0.6.1-3.el8.x86_64 2/34 - Installing : bind-license-32:9.11.36-16.el8_10.2.noarch 3/34 - Upgrading : python3-dnf-plugins-core-4.0.21-25.0.1.el8.noarch 4/34 - Upgrading : dnf-plugins-core-4.0.21-25.0.1.el8.noarch 5/34 - Upgrading : libcurl-7.61.1-34.el8.x86_64 6/34 - Installing : geolite2-country-20180605-1.el8.noarch 7/34 - Installing : geolite2-city-20180605-1.el8.noarch 8/34 - Installing : libmaxminddb-1.2.0-10.el8_9.1.x86_64 9/34 - Running scriptlet: libmaxminddb-1.2.0-10.el8_9.1.x86_64 9/34 - Installing : bind-libs-lite-32:9.11.36-16.el8_10.2.x86_64 10/34 - Installing : bind-libs-32:9.11.36-16.el8_10.2.x86_64 11/34 - Installing : unzip-6.0-46.0.1.el8.x86_64 12/34 - Installing : tcl-1:8.6.8-2.el8.x86_64 13/34 - Running scriptlet: tcl-1:8.6.8-2.el8.x86_64 13/34 - Installing : python3-ply-3.9-9.el8.noarch 14/34 - Installing : python3-bind-32:9.11.36-16.el8_10.2.noarch 15/34 - Installing : libmetalink-0.1.3-7.el8.x86_64 16/34 - Installing : wget-1.19.5-12.0.1.el8_10.x86_64 17/34 - Running scriptlet: wget-1.19.5-12.0.1.el8_10.x86_64 17/34 - Installing : bind-utils-32:9.11.36-16.el8_10.2.x86_64 18/34 - Installing : expect-5.45.4-5.el8.x86_64 19/34 - Installing : zip-3.0-23.el8.x86_64 20/34 - Upgrading : curl-7.61.1-34.el8.x86_64 21/34 - Upgrading : yum-utils-4.0.21-25.0.1.el8.noarch 22/34 - Installing : which-2.21-20.el8.x86_64 23/34 - Installing : tree-1.7.0-15.el8.x86_64 24/34 - Installing : sudo-1.9.5p2-1.el8_9.x86_64 25/34 - Running scriptlet: sudo-1.9.5p2-1.el8_9.x86_64 25/34 - Installing : openssl-1:1.1.1k-12.el8_9.x86_64 26/34 - Installing : net-tools-2.0-0.52.20160912git.el8.x86_64 27/34 - Running scriptlet: net-tools-2.0-0.52.20160912git.el8.x86_64 27/34 - Installing : lsof-4.93.2-1.el8.x86_64 28/34 - Installing : hostname-3.20-6.el8.x86_64 29/34 - Running scriptlet: hostname-3.20-6.el8.x86_64 29/34 - Cleanup : curl-7.61.1-33.el8_9.5.x86_64 30/34 - Cleanup : yum-utils-4.0.21-23.0.1.el8.noarch 31/34 - Cleanup : dnf-plugins-core-4.0.21-23.0.1.el8.noarch 32/34 - Cleanup : python3-dnf-plugins-core-4.0.21-23.0.1.el8.noarch 33/34 - Cleanup : libcurl-7.61.1-33.el8_9.5.x86_64 34/34 - Running scriptlet: libcurl-7.61.1-33.el8_9.5.x86_64 34/34 - Verifying : expect-5.45.4-5.el8.x86_64 1/34 - Verifying : hostname-3.20-6.el8.x86_64 2/34 - Verifying : libmetalink-0.1.3-7.el8.x86_64 3/34 - Verifying : lsof-4.93.2-1.el8.x86_64 4/34 - Verifying : net-tools-2.0-0.52.20160912git.el8.x86_64 5/34 - Verifying : openssl-1:1.1.1k-12.el8_9.x86_64 6/34 - Verifying : python3-ply-3.9-9.el8.noarch 7/34 - Verifying : sudo-1.9.5p2-1.el8_9.x86_64 8/34 - Verifying : tcl-1:8.6.8-2.el8.x86_64 9/34 - Verifying : tree-1.7.0-15.el8.x86_64 10/34 - Verifying : unzip-6.0-46.0.1.el8.x86_64 11/34 - Verifying : which-2.21-20.el8.x86_64 12/34 - Verifying : zip-3.0-23.el8.x86_64 13/34 - Verifying : bind-libs-32:9.11.36-16.el8_10.2.x86_64 14/34 - Verifying : bind-libs-lite-32:9.11.36-16.el8_10.2.x86_64 15/34 - Verifying : bind-license-32:9.11.36-16.el8_10.2.noarch 16/34 - Verifying : bind-utils-32:9.11.36-16.el8_10.2.x86_64 17/34 - Verifying : fstrm-0.6.1-3.el8.x86_64 18/34 - Verifying : geolite2-city-20180605-1.el8.noarch 19/34 - Verifying : geolite2-country-20180605-1.el8.noarch 20/34 - Verifying : libmaxminddb-1.2.0-10.el8_9.1.x86_64 21/34 - Verifying : protobuf-c-1.3.0-8.el8.x86_64 22/34 - Verifying : python3-bind-32:9.11.36-16.el8_10.2.noarch 23/34 - Verifying : wget-1.19.5-12.0.1.el8_10.x86_64 24/34 - Verifying : curl-7.61.1-34.el8.x86_64 25/34 - Verifying : curl-7.61.1-33.el8_9.5.x86_64 26/34 - Verifying : dnf-plugins-core-4.0.21-25.0.1.el8.noarch 27/34 - Verifying : dnf-plugins-core-4.0.21-23.0.1.el8.noarch 28/34 - Verifying : libcurl-7.61.1-34.el8.x86_64 29/34 - Verifying : libcurl-7.61.1-33.el8_9.5.x86_64 30/34 - Verifying : python3-dnf-plugins-core-4.0.21-25.0.1.el8.noarch 31/34 - Verifying : python3-dnf-plugins-core-4.0.21-23.0.1.el8.noarch 32/34 - Verifying : yum-utils-4.0.21-25.0.1.el8.noarch 33/34 - Verifying : yum-utils-4.0.21-23.0.1.el8.noarch 34/34 - -Upgraded: - curl-7.61.1-34.el8.x86_64 - dnf-plugins-core-4.0.21-25.0.1.el8.noarch - libcurl-7.61.1-34.el8.x86_64 - python3-dnf-plugins-core-4.0.21-25.0.1.el8.noarch - yum-utils-4.0.21-25.0.1.el8.noarch -Installed: - bind-libs-32:9.11.36-16.el8_10.2.x86_64 - bind-libs-lite-32:9.11.36-16.el8_10.2.x86_64 - bind-license-32:9.11.36-16.el8_10.2.noarch - bind-utils-32:9.11.36-16.el8_10.2.x86_64 - expect-5.45.4-5.el8.x86_64 - fstrm-0.6.1-3.el8.x86_64 - geolite2-city-20180605-1.el8.noarch - geolite2-country-20180605-1.el8.noarch - hostname-3.20-6.el8.x86_64 - libmaxminddb-1.2.0-10.el8_9.1.x86_64 - libmetalink-0.1.3-7.el8.x86_64 - lsof-4.93.2-1.el8.x86_64 - net-tools-2.0-0.52.20160912git.el8.x86_64 - openssl-1:1.1.1k-12.el8_9.x86_64 - protobuf-c-1.3.0-8.el8.x86_64 - python3-bind-32:9.11.36-16.el8_10.2.noarch - python3-ply-3.9-9.el8.noarch - sudo-1.9.5p2-1.el8_9.x86_64 - tcl-1:8.6.8-2.el8.x86_64 - tree-1.7.0-15.el8.x86_64 - unzip-6.0-46.0.1.el8.x86_64 - wget-1.19.5-12.0.1.el8_10.x86_64 - which-2.21-20.el8.x86_64 - zip-3.0-23.el8.x86_64 - -Complete! -Adding repo from: http://yum.oracle.com/repo/OracleLinux/OL8/oracle/software/x86_64 -created by dnf config-manager from http://yum.o 496 kB/s | 139 kB 00:00 -Last metadata expiration check: 0:00:01 ago on Tue 20 Aug 2024 08:55:14 AM UTC. -Dependencies resolved. -============================================================================================== - Package Arch Version Repository Size -============================================================================================== -Installing: - java-11-openjdk-devel x86_64 1:11.0.24.0.8-3.0.1.el8 ol8_appstream 3.4 M -Installing dependencies: - adwaita-cursor-theme noarch 3.28.0-3.el8 ol8_appstream 647 k - adwaita-icon-theme noarch 3.28.0-3.el8 ol8_appstream 11 M - alsa-lib x86_64 1.2.10-2.el8 ol8_appstream 500 k - at-spi2-atk x86_64 2.26.2-1.el8 ol8_appstream 89 k - at-spi2-core x86_64 2.28.0-1.el8 ol8_appstream 169 k - atk x86_64 2.28.1-1.el8 ol8_appstream 272 k - avahi-libs x86_64 0.7-27.el8 ol8_baseos_latest 61 k - cairo x86_64 1.15.12-6.el8 ol8_appstream 719 k - cairo-gobject x86_64 1.15.12-6.el8 ol8_appstream 33 k - colord-libs x86_64 1.4.2-1.el8 ol8_appstream 236 k - copy-jdk-configs noarch 4.0-2.el8 ol8_appstream 30 k - cpio x86_64 2.12-11.el8 ol8_baseos_latest 266 k - crypto-policies-scripts noarch 20230731-1.git3177e06.el8 ol8_baseos_latest 84 k - cups-libs x86_64 1:2.2.6-60.el8_10 ol8_baseos_latest 435 k - dracut x86_64 049-233.git20240115.0.1.el8 ol8_baseos_latest 382 k - file x86_64 5.33-25.el8 ol8_baseos_latest 77 k - fribidi x86_64 1.0.4-9.el8 ol8_appstream 89 k - gdk-pixbuf2 x86_64 2.36.12-6.el8_10 ol8_baseos_latest 465 k - gdk-pixbuf2-modules x86_64 2.36.12-6.el8_10 ol8_appstream 108 k - gettext x86_64 0.19.8.1-17.el8 ol8_baseos_latest 1.1 M - gettext-libs x86_64 0.19.8.1-17.el8 ol8_baseos_latest 312 k - glib-networking x86_64 2.56.1-1.1.el8 ol8_baseos_latest 155 k - graphite2 x86_64 1.3.10-10.el8 ol8_appstream 122 k - grub2-common noarch 1:2.02-156.0.2.el8 ol8_baseos_latest 897 k - grub2-tools x86_64 1:2.02-156.0.2.el8 ol8_baseos_latest 2.0 M - grub2-tools-minimal x86_64 1:2.02-156.0.2.el8 ol8_baseos_latest 215 k - gsettings-desktop-schemas x86_64 3.32.0-6.el8 ol8_baseos_latest 633 k - gtk-update-icon-cache x86_64 3.22.30-11.el8 ol8_appstream 32 k - harfbuzz x86_64 1.7.5-4.el8 ol8_appstream 295 k - hicolor-icon-theme noarch 0.17-2.el8 ol8_appstream 48 k - jasper-libs x86_64 2.0.14-5.el8 ol8_appstream 167 k - java-11-openjdk x86_64 1:11.0.24.0.8-3.0.1.el8 ol8_appstream 475 k - java-11-openjdk-headless x86_64 1:11.0.24.0.8-3.0.1.el8 ol8_appstream 42 M - javapackages-filesystem noarch 5.3.0-1.module+el8+5136+7ff78f74 ol8_appstream 30 k - jbigkit-libs x86_64 2.1-14.el8 ol8_appstream 55 k - json-glib x86_64 1.4.4-1.el8 ol8_baseos_latest 144 k - kbd-legacy noarch 2.0.4-11.el8 ol8_baseos_latest 481 k - kbd-misc noarch 2.0.4-11.el8 ol8_baseos_latest 1.5 M - lcms2 x86_64 2.9-2.el8 ol8_appstream 164 k - libX11 x86_64 1.6.8-8.el8 ol8_appstream 611 k - libX11-common noarch 1.6.8-8.el8 ol8_appstream 157 k - libXau x86_64 1.0.9-3.el8 ol8_appstream 37 k - libXcomposite x86_64 0.4.4-14.el8 ol8_appstream 28 k - libXcursor x86_64 1.1.15-3.el8 ol8_appstream 36 k - libXdamage x86_64 1.1.4-14.el8 ol8_appstream 27 k - libXext x86_64 1.3.4-1.el8 ol8_appstream 45 k - libXfixes x86_64 5.0.3-7.el8 ol8_appstream 25 k - libXft x86_64 2.3.3-1.el8 ol8_appstream 67 k - libXi x86_64 1.7.10-1.el8 ol8_appstream 49 k - libXinerama x86_64 1.1.4-1.el8 ol8_appstream 15 k - libXrandr x86_64 1.5.2-1.el8 ol8_appstream 34 k - libXrender x86_64 0.9.10-7.el8 ol8_appstream 33 k - libXtst x86_64 1.2.3-7.el8 ol8_appstream 22 k - libcroco x86_64 0.6.12-4.el8_2.1 ol8_baseos_latest 113 k - libdatrie x86_64 0.2.9-7.el8 ol8_appstream 33 k - libepoxy x86_64 1.5.8-1.el8 ol8_appstream 225 k - libfontenc x86_64 1.1.3-8.el8 ol8_appstream 37 k - libgomp x86_64 8.5.0-22.0.1.el8_10 ol8_baseos_latest 218 k - libgusb x86_64 0.3.0-1.el8 ol8_baseos_latest 49 k - libjpeg-turbo x86_64 1.5.3-12.el8 ol8_appstream 157 k - libkcapi x86_64 1.4.0-2.0.1.el8 ol8_baseos_latest 52 k - libkcapi-hmaccalc x86_64 1.4.0-2.0.1.el8 ol8_baseos_latest 31 k - libmodman x86_64 2.0.1-17.el8 ol8_baseos_latest 36 k - libpkgconf x86_64 1.4.2-1.el8 ol8_baseos_latest 35 k - libproxy x86_64 0.4.15-5.2.el8 ol8_baseos_latest 75 k - libsoup x86_64 2.62.3-5.el8 ol8_baseos_latest 424 k - libthai x86_64 0.1.27-2.el8 ol8_appstream 203 k - libtiff x86_64 4.0.9-32.el8_10 ol8_appstream 189 k - libwayland-client x86_64 1.21.0-1.el8 ol8_appstream 41 k - libwayland-cursor x86_64 1.21.0-1.el8 ol8_appstream 26 k - libwayland-egl x86_64 1.21.0-1.el8 ol8_appstream 19 k - libxcb x86_64 1.13.1-1.el8 ol8_appstream 231 k - libxkbcommon x86_64 0.9.1-1.el8 ol8_appstream 116 k - lksctp-tools x86_64 1.0.18-3.el8 ol8_baseos_latest 100 k - lua x86_64 5.3.4-12.el8 ol8_appstream 192 k - nspr x86_64 4.35.0-1.el8_8 ol8_appstream 143 k - nss x86_64 3.90.0-7.el8_10 ol8_appstream 750 k - nss-softokn x86_64 3.90.0-7.el8_10 ol8_appstream 1.2 M - nss-softokn-freebl x86_64 3.90.0-7.el8_10 ol8_appstream 375 k - nss-sysinit x86_64 3.90.0-7.el8_10 ol8_appstream 74 k - nss-util x86_64 3.90.0-7.el8_10 ol8_appstream 139 k - os-prober x86_64 1.74-9.0.1.el8 ol8_baseos_latest 51 k - pango x86_64 1.42.4-8.el8 ol8_appstream 297 k - pixman x86_64 0.38.4-4.el8 ol8_appstream 256 k - pkgconf x86_64 1.4.2-1.el8 ol8_baseos_latest 38 k - pkgconf-m4 noarch 1.4.2-1.el8 ol8_baseos_latest 17 k - pkgconf-pkg-config x86_64 1.4.2-1.el8 ol8_baseos_latest 15 k - rest x86_64 0.8.1-2.el8 ol8_appstream 70 k - shared-mime-info x86_64 1.9-4.el8 ol8_baseos_latest 328 k - systemd-udev x86_64 239-78.0.4.el8 ol8_baseos_latest 1.6 M - ttmkfdir x86_64 3.0.9-54.el8 ol8_appstream 62 k - tzdata-java noarch 2024a-1.0.1.el8 ol8_appstream 186 k - xkeyboard-config noarch 2.28-1.el8 ol8_appstream 782 k - xorg-x11-font-utils x86_64 1:7.5-41.el8 ol8_appstream 104 k - xorg-x11-fonts-Type1 noarch 7.5-19.el8 ol8_appstream 522 k - xz x86_64 5.2.4-4.el8_6 ol8_baseos_latest 153 k -Installing weak dependencies: - abattis-cantarell-fonts noarch 0.0.25-6.el8 ol8_appstream 155 k - dconf x86_64 0.28.0-4.0.1.el8 ol8_appstream 108 k - dejavu-sans-mono-fonts noarch 2.35-7.el8 ol8_baseos_latest 447 k - grubby x86_64 8.40-49.0.2.el8 ol8_baseos_latest 50 k - gtk3 x86_64 3.22.30-11.el8 ol8_appstream 4.5 M - hardlink x86_64 1:1.3-6.el8 ol8_baseos_latest 29 k - kbd x86_64 2.0.4-11.el8 ol8_baseos_latest 390 k - memstrack x86_64 0.2.5-2.el8 ol8_baseos_latest 51 k - pigz x86_64 2.4-4.el8 ol8_baseos_latest 80 k -Enabling module streams: - javapackages-runtime 201801 - -Transaction Summary -============================================================================================== -Install 106 Packages - -Total download size: 86 M -Installed size: 312 M -Downloading Packages: -(1/106): crypto-policies-scripts-20230731-1.git 862 kB/s | 84 kB 00:00 -(2/106): avahi-libs-0.7-27.el8.x86_64.rpm 602 kB/s | 61 kB 00:00 -(3/106): cpio-2.12-11.el8.x86_64.rpm 1.8 MB/s | 266 kB 00:00 -(4/106): cups-libs-2.2.6-60.el8_10.x86_64.rpm 5.7 MB/s | 435 kB 00:00 -(5/106): dejavu-sans-mono-fonts-2.35-7.el8.noar 5.1 MB/s | 447 kB 00:00 -(6/106): dracut-049-233.git20240115.0.1.el8.x86 7.0 MB/s | 382 kB 00:00 -(7/106): gdk-pixbuf2-2.36.12-6.el8_10.x86_64.rp 12 MB/s | 465 kB 00:00 -(8/106): gettext-libs-0.19.8.1-17.el8.x86_64.rp 9.3 MB/s | 312 kB 00:00 -(9/106): gettext-0.19.8.1-17.el8.x86_64.rpm 16 MB/s | 1.1 MB 00:00 -(10/106): glib-networking-2.56.1-1.1.el8.x86_64 6.0 MB/s | 155 kB 00:00 -(11/106): grub2-common-2.02-156.0.2.el8.noarch. 26 MB/s | 897 kB 00:00 -(12/106): grub2-tools-minimal-2.02-156.0.2.el8. 8.2 MB/s | 215 kB 00:00 -(13/106): grubby-8.40-49.0.2.el8.x86_64.rpm 2.1 MB/s | 50 kB 00:00 -(14/106): grub2-tools-2.02-156.0.2.el8.x86_64.r 26 MB/s | 2.0 MB 00:00 -(15/106): gsettings-desktop-schemas-3.32.0-6.el 19 MB/s | 633 kB 00:00 -(16/106): hardlink-1.3-6.el8.x86_64.rpm 1.1 MB/s | 29 kB 00:00 -(17/106): json-glib-1.4.4-1.el8.x86_64.rpm 5.9 MB/s | 144 kB 00:00 -(18/106): kbd-2.0.4-11.el8.x86_64.rpm 14 MB/s | 390 kB 00:00 -(19/106): kbd-legacy-2.0.4-11.el8.noarch.rpm 17 MB/s | 481 kB 00:00 -(20/106): kbd-misc-2.0.4-11.el8.noarch.rpm 41 MB/s | 1.5 MB 00:00 -(21/106): libcroco-0.6.12-4.el8_2.1.x86_64.rpm 4.7 MB/s | 113 kB 00:00 -(22/106): libgomp-8.5.0-22.0.1.el8_10.x86_64.rp 9.1 MB/s | 218 kB 00:00 -(23/106): libgusb-0.3.0-1.el8.x86_64.rpm 2.1 MB/s | 49 kB 00:00 -(24/106): libkcapi-1.4.0-2.0.1.el8.x86_64.rpm 1.6 MB/s | 52 kB 00:00 -(25/106): libkcapi-hmaccalc-1.4.0-2.0.1.el8.x86 822 kB/s | 31 kB 00:00 -(26/106): libmodman-2.0.1-17.el8.x86_64.rpm 1.6 MB/s | 36 kB 00:00 -(27/106): libpkgconf-1.4.2-1.el8.x86_64.rpm 1.2 MB/s | 35 kB 00:00 -(28/106): libproxy-0.4.15-5.2.el8.x86_64.rpm 3.0 MB/s | 75 kB 00:00 -(29/106): libsoup-2.62.3-5.el8.x86_64.rpm 15 MB/s | 424 kB 00:00 -(30/106): lksctp-tools-1.0.18-3.el8.x86_64.rpm 3.5 MB/s | 100 kB 00:00 -(31/106): memstrack-0.2.5-2.el8.x86_64.rpm 2.2 MB/s | 51 kB 00:00 -(32/106): os-prober-1.74-9.0.1.el8.x86_64.rpm 2.2 MB/s | 51 kB 00:00 -(33/106): pigz-2.4-4.el8.x86_64.rpm 3.5 MB/s | 80 kB 00:00 -(34/106): pkgconf-1.4.2-1.el8.x86_64.rpm 1.7 MB/s | 38 kB 00:00 -(35/106): pkgconf-m4-1.4.2-1.el8.noarch.rpm 761 kB/s | 17 kB 00:00 -(36/106): pkgconf-pkg-config-1.4.2-1.el8.x86_64 691 kB/s | 15 kB 00:00 -(37/106): shared-mime-info-1.9-4.el8.x86_64.rpm 13 MB/s | 328 kB 00:00 -(38/106): systemd-udev-239-78.0.4.el8.x86_64.rp 32 MB/s | 1.6 MB 00:00 -(39/106): xz-5.2.4-4.el8_6.x86_64.rpm 5.2 MB/s | 153 kB 00:00 -(40/106): abattis-cantarell-fonts-0.0.25-6.el8. 6.4 MB/s | 155 kB 00:00 -(41/106): adwaita-cursor-theme-3.28.0-3.el8.noa 22 MB/s | 647 kB 00:00 -(42/106): alsa-lib-1.2.10-2.el8.x86_64.rpm 18 MB/s | 500 kB 00:00 -(43/106): at-spi2-atk-2.26.2-1.el8.x86_64.rpm 3.8 MB/s | 89 kB 00:00 -(44/106): at-spi2-core-2.28.0-1.el8.x86_64.rpm 6.9 MB/s | 169 kB 00:00 -(45/106): atk-2.28.1-1.el8.x86_64.rpm 9.2 MB/s | 272 kB 00:00 -(46/106): cairo-1.15.12-6.el8.x86_64.rpm 24 MB/s | 719 kB 00:00 -(47/106): adwaita-icon-theme-3.28.0-3.el8.noarc 65 MB/s | 11 MB 00:00 -(48/106): cairo-gobject-1.15.12-6.el8.x86_64.rp 914 kB/s | 33 kB 00:00 -(49/106): colord-libs-1.4.2-1.el8.x86_64.rpm 9.5 MB/s | 236 kB 00:00 -(50/106): copy-jdk-configs-4.0-2.el8.noarch.rpm 1.1 MB/s | 30 kB 00:00 -(51/106): dconf-0.28.0-4.0.1.el8.x86_64.rpm 4.4 MB/s | 108 kB 00:00 -(52/106): fribidi-1.0.4-9.el8.x86_64.rpm 3.9 MB/s | 89 kB 00:00 -(53/106): graphite2-1.3.10-10.el8.x86_64.rpm 5.1 MB/s | 122 kB 00:00 -(54/106): gdk-pixbuf2-modules-2.36.12-6.el8_10. 3.6 MB/s | 108 kB 00:00 -(55/106): gtk-update-icon-cache-3.22.30-11.el8. 1.4 MB/s | 32 kB 00:00 -(56/106): harfbuzz-1.7.5-4.el8.x86_64.rpm 11 MB/s | 295 kB 00:00 -(57/106): gtk3-3.22.30-11.el8.x86_64.rpm 68 MB/s | 4.5 MB 00:00 -(58/106): hicolor-icon-theme-0.17-2.el8.noarch. 2.1 MB/s | 48 kB 00:00 -(59/106): java-11-openjdk-11.0.24.0.8-3.0.1.el8 17 MB/s | 475 kB 00:00 -(60/106): jasper-libs-2.0.14-5.el8.x86_64.rpm 5.0 MB/s | 167 kB 00:00 -(61/106): java-11-openjdk-devel-11.0.24.0.8-3.0 61 MB/s | 3.4 MB 00:00 -(62/106): javapackages-filesystem-5.3.0-1.modul 1.2 MB/s | 30 kB 00:00 -(63/106): jbigkit-libs-2.1-14.el8.x86_64.rpm 2.1 MB/s | 55 kB 00:00 -(64/106): lcms2-2.9-2.el8.x86_64.rpm 3.8 MB/s | 164 kB 00:00 -(65/106): libX11-1.6.8-8.el8.x86_64.rpm 20 MB/s | 611 kB 00:00 -(66/106): libX11-common-1.6.8-8.el8.noarch.rpm 6.8 MB/s | 157 kB 00:00 -(67/106): libXau-1.0.9-3.el8.x86_64.rpm 1.6 MB/s | 37 kB 00:00 -(68/106): libXcomposite-0.4.4-14.el8.x86_64.rpm 1.3 MB/s | 28 kB 00:00 -(69/106): libXcursor-1.1.15-3.el8.x86_64.rpm 1.6 MB/s | 36 kB 00:00 -(70/106): libXdamage-1.1.4-14.el8.x86_64.rpm 1.2 MB/s | 27 kB 00:00 -(71/106): libXext-1.3.4-1.el8.x86_64.rpm 2.0 MB/s | 45 kB 00:00 -(72/106): libXfixes-5.0.3-7.el8.x86_64.rpm 1.1 MB/s | 25 kB 00:00 -(73/106): libXft-2.3.3-1.el8.x86_64.rpm 2.9 MB/s | 67 kB 00:00 -(74/106): libXi-1.7.10-1.el8.x86_64.rpm 2.2 MB/s | 49 kB 00:00 -(75/106): libXinerama-1.1.4-1.el8.x86_64.rpm 717 kB/s | 15 kB 00:00 -(76/106): libXrandr-1.5.2-1.el8.x86_64.rpm 1.5 MB/s | 34 kB 00:00 -(77/106): libXrender-0.9.10-7.el8.x86_64.rpm 1.4 MB/s | 33 kB 00:00 -(78/106): libXtst-1.2.3-7.el8.x86_64.rpm 957 kB/s | 22 kB 00:00 -(79/106): java-11-openjdk-headless-11.0.24.0.8- 71 MB/s | 42 MB 00:00 -(80/106): libdatrie-0.2.9-7.el8.x86_64.rpm 274 kB/s | 33 kB 00:00 -(81/106): libepoxy-1.5.8-1.el8.x86_64.rpm 9.1 MB/s | 225 kB 00:00 -(82/106): libfontenc-1.1.3-8.el8.x86_64.rpm 1.5 MB/s | 37 kB 00:00 -(83/106): libthai-0.1.27-2.el8.x86_64.rpm 8.2 MB/s | 203 kB 00:00 -(84/106): libjpeg-turbo-1.5.3-12.el8.x86_64.rpm 5.1 MB/s | 157 kB 00:00 -(85/106): libtiff-4.0.9-32.el8_10.x86_64.rpm 7.8 MB/s | 189 kB 00:00 -(86/106): libwayland-client-1.21.0-1.el8.x86_64 1.7 MB/s | 41 kB 00:00 -(87/106): libwayland-cursor-1.21.0-1.el8.x86_64 1.2 MB/s | 26 kB 00:00 -(88/106): libwayland-egl-1.21.0-1.el8.x86_64.rp 801 kB/s | 19 kB 00:00 -(89/106): libxcb-1.13.1-1.el8.x86_64.rpm 9.7 MB/s | 231 kB 00:00 -(90/106): libxkbcommon-0.9.1-1.el8.x86_64.rpm 5.0 MB/s | 116 kB 00:00 -(91/106): nspr-4.35.0-1.el8_8.x86_64.rpm 6.0 MB/s | 143 kB 00:00 -(92/106): lua-5.3.4-12.el8.x86_64.rpm 5.9 MB/s | 192 kB 00:00 -(93/106): nss-softokn-3.90.0-7.el8_10.x86_64.rp 38 MB/s | 1.2 MB 00:00 -(94/106): nss-3.90.0-7.el8_10.x86_64.rpm 17 MB/s | 750 kB 00:00 -(95/106): nss-softokn-freebl-3.90.0-7.el8_10.x8 14 MB/s | 375 kB 00:00 -(96/106): nss-sysinit-3.90.0-7.el8_10.x86_64.rp 3.2 MB/s | 74 kB 00:00 -(97/106): nss-util-3.90.0-7.el8_10.x86_64.rpm 5.8 MB/s | 139 kB 00:00 -(98/106): pango-1.42.4-8.el8.x86_64.rpm 11 MB/s | 297 kB 00:00 -(99/106): pixman-0.38.4-4.el8.x86_64.rpm 10 MB/s | 256 kB 00:00 -(100/106): rest-0.8.1-2.el8.x86_64.rpm 3.1 MB/s | 70 kB 00:00 -(101/106): ttmkfdir-3.0.9-54.el8.x86_64.rpm 2.5 MB/s | 62 kB 00:00 -(102/106): tzdata-java-2024a-1.0.1.el8.noarch.r 7.4 MB/s | 186 kB 00:00 -(103/106): xkeyboard-config-2.28-1.el8.noarch.r 27 MB/s | 782 kB 00:00 -(104/106): xorg-x11-font-utils-7.5-41.el8.x86_6 3.9 MB/s | 104 kB 00:00 -(105/106): xorg-x11-fonts-Type1-7.5-19.el8.noar 1.3 MB/s | 522 kB 00:00 -(106/106): file-5.33-25.el8.x86_64.rpm 26 kB/s | 77 kB 00:02 --------------------------------------------------------------------------------- -Total 27 MB/s | 86 MB 00:03 -Running transaction check -Transaction check succeeded. -Running transaction test -Transaction test succeeded. -Running transaction - Running scriptlet: copy-jdk-configs-4.0-2.el8.noarch 1/1 - Running scriptlet: java-11-openjdk-headless-1:11.0.24.0.8-3.0.1.el8.x86 1/1 - Preparing : 1/1 - Installing : nspr-4.35.0-1.el8_8.x86_64 1/106 - Running scriptlet: nspr-4.35.0-1.el8_8.x86_64 1/106 - Installing : nss-util-3.90.0-7.el8_10.x86_64 2/106 - Installing : libjpeg-turbo-1.5.3-12.el8.x86_64 3/106 - Installing : pixman-0.38.4-4.el8.x86_64 4/106 - Installing : libwayland-client-1.21.0-1.el8.x86_64 5/106 - Installing : atk-2.28.1-1.el8.x86_64 6/106 - Installing : libgomp-8.5.0-22.0.1.el8_10.x86_64 7/106 - Running scriptlet: libgomp-8.5.0-22.0.1.el8_10.x86_64 7/106 - Installing : libcroco-0.6.12-4.el8_2.1.x86_64 8/106 - Running scriptlet: libcroco-0.6.12-4.el8_2.1.x86_64 8/106 - Installing : grub2-common-1:2.02-156.0.2.el8.noarch 9/106 - Installing : gettext-libs-0.19.8.1-17.el8.x86_64 10/106 - Installing : gettext-0.19.8.1-17.el8.x86_64 11/106 - Running scriptlet: gettext-0.19.8.1-17.el8.x86_64 11/106 - Installing : grub2-tools-minimal-1:2.02-156.0.2.el8.x86_64 12/106 - Installing : libwayland-cursor-1.21.0-1.el8.x86_64 13/106 - Installing : jasper-libs-2.0.14-5.el8.x86_64 14/106 - Installing : nss-softokn-freebl-3.90.0-7.el8_10.x86_64 15/106 - Installing : nss-softokn-3.90.0-7.el8_10.x86_64 16/106 - Installing : xkeyboard-config-2.28-1.el8.noarch 17/106 - Installing : libxkbcommon-0.9.1-1.el8.x86_64 18/106 - Installing : tzdata-java-2024a-1.0.1.el8.noarch 19/106 - Installing : ttmkfdir-3.0.9-54.el8.x86_64 20/106 - Installing : lua-5.3.4-12.el8.x86_64 21/106 - Installing : copy-jdk-configs-4.0-2.el8.noarch 22/106 - Installing : libwayland-egl-1.21.0-1.el8.x86_64 23/106 - Installing : libfontenc-1.1.3-8.el8.x86_64 24/106 - Installing : libepoxy-1.5.8-1.el8.x86_64 25/106 - Installing : libdatrie-0.2.9-7.el8.x86_64 26/106 - Running scriptlet: libdatrie-0.2.9-7.el8.x86_64 26/106 - Installing : libthai-0.1.27-2.el8.x86_64 27/106 - Running scriptlet: libthai-0.1.27-2.el8.x86_64 27/106 - Installing : libXau-1.0.9-3.el8.x86_64 28/106 - Installing : libxcb-1.13.1-1.el8.x86_64 29/106 - Installing : libX11-common-1.6.8-8.el8.noarch 30/106 - Installing : libX11-1.6.8-8.el8.x86_64 31/106 - Installing : libXext-1.3.4-1.el8.x86_64 32/106 - Installing : libXrender-0.9.10-7.el8.x86_64 33/106 - Installing : cairo-1.15.12-6.el8.x86_64 34/106 - Installing : libXi-1.7.10-1.el8.x86_64 35/106 - Installing : libXfixes-5.0.3-7.el8.x86_64 36/106 - Installing : libXtst-1.2.3-7.el8.x86_64 37/106 - Installing : libXcomposite-0.4.4-14.el8.x86_64 38/106 - Installing : at-spi2-core-2.28.0-1.el8.x86_64 39/106 - Running scriptlet: at-spi2-core-2.28.0-1.el8.x86_64 39/106 - Installing : at-spi2-atk-2.26.2-1.el8.x86_64 40/106 - Running scriptlet: at-spi2-atk-2.26.2-1.el8.x86_64 40/106 - Installing : libXcursor-1.1.15-3.el8.x86_64 41/106 - Installing : libXdamage-1.1.4-14.el8.x86_64 42/106 - Installing : cairo-gobject-1.15.12-6.el8.x86_64 43/106 - Installing : libXft-2.3.3-1.el8.x86_64 44/106 - Installing : libXrandr-1.5.2-1.el8.x86_64 45/106 - Installing : libXinerama-1.1.4-1.el8.x86_64 46/106 - Installing : lcms2-2.9-2.el8.x86_64 47/106 - Running scriptlet: lcms2-2.9-2.el8.x86_64 47/106 - Installing : jbigkit-libs-2.1-14.el8.x86_64 48/106 - Running scriptlet: jbigkit-libs-2.1-14.el8.x86_64 48/106 - Installing : libtiff-4.0.9-32.el8_10.x86_64 49/106 - Installing : javapackages-filesystem-5.3.0-1.module+el8+5136+ 50/106 - Installing : hicolor-icon-theme-0.17-2.el8.noarch 51/106 - Installing : graphite2-1.3.10-10.el8.x86_64 52/106 - Installing : harfbuzz-1.7.5-4.el8.x86_64 53/106 - Running scriptlet: harfbuzz-1.7.5-4.el8.x86_64 53/106 - Installing : fribidi-1.0.4-9.el8.x86_64 54/106 - Installing : pango-1.42.4-8.el8.x86_64 55/106 - Running scriptlet: pango-1.42.4-8.el8.x86_64 55/106 - Installing : dconf-0.28.0-4.0.1.el8.x86_64 56/106 - Installing : alsa-lib-1.2.10-2.el8.x86_64 57/106 - Running scriptlet: alsa-lib-1.2.10-2.el8.x86_64 57/106 - Installing : adwaita-cursor-theme-3.28.0-3.el8.noarch 58/106 - Installing : adwaita-icon-theme-3.28.0-3.el8.noarch 59/106 - Installing : abattis-cantarell-fonts-0.0.25-6.el8.noarch 60/106 - Installing : xz-5.2.4-4.el8_6.x86_64 61/106 - Installing : shared-mime-info-1.9-4.el8.x86_64 62/106 - Running scriptlet: shared-mime-info-1.9-4.el8.x86_64 62/106 - Installing : gdk-pixbuf2-2.36.12-6.el8_10.x86_64 63/106 - Running scriptlet: gdk-pixbuf2-2.36.12-6.el8_10.x86_64 63/106 - Installing : gdk-pixbuf2-modules-2.36.12-6.el8_10.x86_64 64/106 - Installing : gtk-update-icon-cache-3.22.30-11.el8.x86_64 65/106 - Installing : pkgconf-m4-1.4.2-1.el8.noarch 66/106 - Installing : pigz-2.4-4.el8.x86_64 67/106 - Installing : memstrack-0.2.5-2.el8.x86_64 68/106 - Installing : lksctp-tools-1.0.18-3.el8.x86_64 69/106 - Running scriptlet: lksctp-tools-1.0.18-3.el8.x86_64 69/106 - Installing : libpkgconf-1.4.2-1.el8.x86_64 70/106 - Installing : pkgconf-1.4.2-1.el8.x86_64 71/106 - Installing : pkgconf-pkg-config-1.4.2-1.el8.x86_64 72/106 - Installing : xorg-x11-font-utils-1:7.5-41.el8.x86_64 73/106 - Installing : xorg-x11-fonts-Type1-7.5-19.el8.noarch 74/106 - Running scriptlet: xorg-x11-fonts-Type1-7.5-19.el8.noarch 74/106 - Installing : libmodman-2.0.1-17.el8.x86_64 75/106 - Running scriptlet: libmodman-2.0.1-17.el8.x86_64 75/106 - Installing : libproxy-0.4.15-5.2.el8.x86_64 76/106 - Running scriptlet: libproxy-0.4.15-5.2.el8.x86_64 76/106 - Installing : libkcapi-1.4.0-2.0.1.el8.x86_64 77/106 - Installing : libkcapi-hmaccalc-1.4.0-2.0.1.el8.x86_64 78/106 - Installing : libgusb-0.3.0-1.el8.x86_64 79/106 - Installing : colord-libs-1.4.2-1.el8.x86_64 80/106 - Installing : kbd-misc-2.0.4-11.el8.noarch 81/106 - Installing : kbd-legacy-2.0.4-11.el8.noarch 82/106 - Installing : kbd-2.0.4-11.el8.x86_64 83/106 - Installing : systemd-udev-239-78.0.4.el8.x86_64 84/106 - Running scriptlet: systemd-udev-239-78.0.4.el8.x86_64 84/106 - Installing : os-prober-1.74-9.0.1.el8.x86_64 85/106 - Installing : json-glib-1.4.4-1.el8.x86_64 86/106 - Installing : hardlink-1:1.3-6.el8.x86_64 87/106 - Installing : file-5.33-25.el8.x86_64 88/106 - Installing : dejavu-sans-mono-fonts-2.35-7.el8.noarch 89/106 - Installing : gsettings-desktop-schemas-3.32.0-6.el8.x86_64 90/106 - Installing : glib-networking-2.56.1-1.1.el8.x86_64 91/106 - Installing : libsoup-2.62.3-5.el8.x86_64 92/106 - Installing : rest-0.8.1-2.el8.x86_64 93/106 - Running scriptlet: rest-0.8.1-2.el8.x86_64 93/106 - Installing : cpio-2.12-11.el8.x86_64 94/106 - Installing : dracut-049-233.git20240115.0.1.el8.x86_64 95/106 - Running scriptlet: grub2-tools-1:2.02-156.0.2.el8.x86_64 96/106 - Installing : grub2-tools-1:2.02-156.0.2.el8.x86_64 96/106 - Running scriptlet: grub2-tools-1:2.02-156.0.2.el8.x86_64 96/106 - Installing : grubby-8.40-49.0.2.el8.x86_64 97/106 - Installing : crypto-policies-scripts-20230731-1.git3177e06.el 98/106 - Installing : nss-sysinit-3.90.0-7.el8_10.x86_64 99/106 - Installing : nss-3.90.0-7.el8_10.x86_64 100/106 - Installing : avahi-libs-0.7-27.el8.x86_64 101/106 - Installing : cups-libs-1:2.2.6-60.el8_10.x86_64 102/106 - Installing : java-11-openjdk-headless-1:11.0.24.0.8-3.0.1.el8 103/106 - Running scriptlet: java-11-openjdk-headless-1:11.0.24.0.8-3.0.1.el8 103/106 - Installing : gtk3-3.22.30-11.el8.x86_64 104/106 - Installing : java-11-openjdk-1:11.0.24.0.8-3.0.1.el8.x86_64 105/106 - Running scriptlet: java-11-openjdk-1:11.0.24.0.8-3.0.1.el8.x86_64 105/106 - Installing : java-11-openjdk-devel-1:11.0.24.0.8-3.0.1.el8.x8 106/106 - Running scriptlet: java-11-openjdk-devel-1:11.0.24.0.8-3.0.1.el8.x8 106/106 - Running scriptlet: copy-jdk-configs-4.0-2.el8.noarch 106/106 - Running scriptlet: dconf-0.28.0-4.0.1.el8.x86_64 106/106 - Running scriptlet: crypto-policies-scripts-20230731-1.git3177e06.el 106/106 - Running scriptlet: nss-3.90.0-7.el8_10.x86_64 106/106 - Running scriptlet: java-11-openjdk-headless-1:11.0.24.0.8-3.0.1.el8 106/106 - Running scriptlet: java-11-openjdk-1:11.0.24.0.8-3.0.1.el8.x86_64 106/106 - Running scriptlet: java-11-openjdk-devel-1:11.0.24.0.8-3.0.1.el8.x8 106/106 - Running scriptlet: hicolor-icon-theme-0.17-2.el8.noarch 106/106 - Running scriptlet: adwaita-icon-theme-3.28.0-3.el8.noarch 106/106 - Running scriptlet: shared-mime-info-1.9-4.el8.x86_64 106/106 - Running scriptlet: gdk-pixbuf2-2.36.12-6.el8_10.x86_64 106/106 - Running scriptlet: systemd-udev-239-78.0.4.el8.x86_64 106/106 - Verifying : avahi-libs-0.7-27.el8.x86_64 1/106 - Verifying : cpio-2.12-11.el8.x86_64 2/106 - Verifying : crypto-policies-scripts-20230731-1.git3177e06.el 3/106 - Verifying : cups-libs-1:2.2.6-60.el8_10.x86_64 4/106 - Verifying : dejavu-sans-mono-fonts-2.35-7.el8.noarch 5/106 - Verifying : dracut-049-233.git20240115.0.1.el8.x86_64 6/106 - Verifying : file-5.33-25.el8.x86_64 7/106 - Verifying : gdk-pixbuf2-2.36.12-6.el8_10.x86_64 8/106 - Verifying : gettext-0.19.8.1-17.el8.x86_64 9/106 - Verifying : gettext-libs-0.19.8.1-17.el8.x86_64 10/106 - Verifying : glib-networking-2.56.1-1.1.el8.x86_64 11/106 - Verifying : grub2-common-1:2.02-156.0.2.el8.noarch 12/106 - Verifying : grub2-tools-1:2.02-156.0.2.el8.x86_64 13/106 - Verifying : grub2-tools-minimal-1:2.02-156.0.2.el8.x86_64 14/106 - Verifying : grubby-8.40-49.0.2.el8.x86_64 15/106 - Verifying : gsettings-desktop-schemas-3.32.0-6.el8.x86_64 16/106 - Verifying : hardlink-1:1.3-6.el8.x86_64 17/106 - Verifying : json-glib-1.4.4-1.el8.x86_64 18/106 - Verifying : kbd-2.0.4-11.el8.x86_64 19/106 - Verifying : kbd-legacy-2.0.4-11.el8.noarch 20/106 - Verifying : kbd-misc-2.0.4-11.el8.noarch 21/106 - Verifying : libcroco-0.6.12-4.el8_2.1.x86_64 22/106 - Verifying : libgomp-8.5.0-22.0.1.el8_10.x86_64 23/106 - Verifying : libgusb-0.3.0-1.el8.x86_64 24/106 - Verifying : libkcapi-1.4.0-2.0.1.el8.x86_64 25/106 - Verifying : libkcapi-hmaccalc-1.4.0-2.0.1.el8.x86_64 26/106 - Verifying : libmodman-2.0.1-17.el8.x86_64 27/106 - Verifying : libpkgconf-1.4.2-1.el8.x86_64 28/106 - Verifying : libproxy-0.4.15-5.2.el8.x86_64 29/106 - Verifying : libsoup-2.62.3-5.el8.x86_64 30/106 - Verifying : lksctp-tools-1.0.18-3.el8.x86_64 31/106 - Verifying : memstrack-0.2.5-2.el8.x86_64 32/106 - Verifying : os-prober-1.74-9.0.1.el8.x86_64 33/106 - Verifying : pigz-2.4-4.el8.x86_64 34/106 - Verifying : pkgconf-1.4.2-1.el8.x86_64 35/106 - Verifying : pkgconf-m4-1.4.2-1.el8.noarch 36/106 - Verifying : pkgconf-pkg-config-1.4.2-1.el8.x86_64 37/106 - Verifying : shared-mime-info-1.9-4.el8.x86_64 38/106 - Verifying : systemd-udev-239-78.0.4.el8.x86_64 39/106 - Verifying : xz-5.2.4-4.el8_6.x86_64 40/106 - Verifying : abattis-cantarell-fonts-0.0.25-6.el8.noarch 41/106 - Verifying : adwaita-cursor-theme-3.28.0-3.el8.noarch 42/106 - Verifying : adwaita-icon-theme-3.28.0-3.el8.noarch 43/106 - Verifying : alsa-lib-1.2.10-2.el8.x86_64 44/106 - Verifying : at-spi2-atk-2.26.2-1.el8.x86_64 45/106 - Verifying : at-spi2-core-2.28.0-1.el8.x86_64 46/106 - Verifying : atk-2.28.1-1.el8.x86_64 47/106 - Verifying : cairo-1.15.12-6.el8.x86_64 48/106 - Verifying : cairo-gobject-1.15.12-6.el8.x86_64 49/106 - Verifying : colord-libs-1.4.2-1.el8.x86_64 50/106 - Verifying : copy-jdk-configs-4.0-2.el8.noarch 51/106 - Verifying : dconf-0.28.0-4.0.1.el8.x86_64 52/106 - Verifying : fribidi-1.0.4-9.el8.x86_64 53/106 - Verifying : gdk-pixbuf2-modules-2.36.12-6.el8_10.x86_64 54/106 - Verifying : graphite2-1.3.10-10.el8.x86_64 55/106 - Verifying : gtk-update-icon-cache-3.22.30-11.el8.x86_64 56/106 - Verifying : gtk3-3.22.30-11.el8.x86_64 57/106 - Verifying : harfbuzz-1.7.5-4.el8.x86_64 58/106 - Verifying : hicolor-icon-theme-0.17-2.el8.noarch 59/106 - Verifying : jasper-libs-2.0.14-5.el8.x86_64 60/106 - Verifying : java-11-openjdk-1:11.0.24.0.8-3.0.1.el8.x86_64 61/106 - Verifying : java-11-openjdk-devel-1:11.0.24.0.8-3.0.1.el8.x8 62/106 - Verifying : java-11-openjdk-headless-1:11.0.24.0.8-3.0.1.el8 63/106 - Verifying : javapackages-filesystem-5.3.0-1.module+el8+5136+ 64/106 - Verifying : jbigkit-libs-2.1-14.el8.x86_64 65/106 - Verifying : lcms2-2.9-2.el8.x86_64 66/106 - Verifying : libX11-1.6.8-8.el8.x86_64 67/106 - Verifying : libX11-common-1.6.8-8.el8.noarch 68/106 - Verifying : libXau-1.0.9-3.el8.x86_64 69/106 - Verifying : libXcomposite-0.4.4-14.el8.x86_64 70/106 - Verifying : libXcursor-1.1.15-3.el8.x86_64 71/106 - Verifying : libXdamage-1.1.4-14.el8.x86_64 72/106 - Verifying : libXext-1.3.4-1.el8.x86_64 73/106 - Verifying : libXfixes-5.0.3-7.el8.x86_64 74/106 - Verifying : libXft-2.3.3-1.el8.x86_64 75/106 - Verifying : libXi-1.7.10-1.el8.x86_64 76/106 - Verifying : libXinerama-1.1.4-1.el8.x86_64 77/106 - Verifying : libXrandr-1.5.2-1.el8.x86_64 78/106 - Verifying : libXrender-0.9.10-7.el8.x86_64 79/106 - Verifying : libXtst-1.2.3-7.el8.x86_64 80/106 - Verifying : libdatrie-0.2.9-7.el8.x86_64 81/106 - Verifying : libepoxy-1.5.8-1.el8.x86_64 82/106 - Verifying : libfontenc-1.1.3-8.el8.x86_64 83/106 - Verifying : libjpeg-turbo-1.5.3-12.el8.x86_64 84/106 - Verifying : libthai-0.1.27-2.el8.x86_64 85/106 - Verifying : libtiff-4.0.9-32.el8_10.x86_64 86/106 - Verifying : libwayland-client-1.21.0-1.el8.x86_64 87/106 - Verifying : libwayland-cursor-1.21.0-1.el8.x86_64 88/106 - Verifying : libwayland-egl-1.21.0-1.el8.x86_64 89/106 - Verifying : libxcb-1.13.1-1.el8.x86_64 90/106 - Verifying : libxkbcommon-0.9.1-1.el8.x86_64 91/106 - Verifying : lua-5.3.4-12.el8.x86_64 92/106 - Verifying : nspr-4.35.0-1.el8_8.x86_64 93/106 - Verifying : nss-3.90.0-7.el8_10.x86_64 94/106 - Verifying : nss-softokn-3.90.0-7.el8_10.x86_64 95/106 - Verifying : nss-softokn-freebl-3.90.0-7.el8_10.x86_64 96/106 - Verifying : nss-sysinit-3.90.0-7.el8_10.x86_64 97/106 - Verifying : nss-util-3.90.0-7.el8_10.x86_64 98/106 - Verifying : pango-1.42.4-8.el8.x86_64 99/106 - Verifying : pixman-0.38.4-4.el8.x86_64 100/106 - Verifying : rest-0.8.1-2.el8.x86_64 101/106 - Verifying : ttmkfdir-3.0.9-54.el8.x86_64 102/106 - Verifying : tzdata-java-2024a-1.0.1.el8.noarch 103/106 - Verifying : xkeyboard-config-2.28-1.el8.noarch 104/106 - Verifying : xorg-x11-font-utils-1:7.5-41.el8.x86_64 105/106 - Verifying : xorg-x11-fonts-Type1-7.5-19.el8.noarch 106/106 - -Installed: - abattis-cantarell-fonts-0.0.25-6.el8.noarch - adwaita-cursor-theme-3.28.0-3.el8.noarch - adwaita-icon-theme-3.28.0-3.el8.noarch - alsa-lib-1.2.10-2.el8.x86_64 - at-spi2-atk-2.26.2-1.el8.x86_64 - at-spi2-core-2.28.0-1.el8.x86_64 - atk-2.28.1-1.el8.x86_64 - avahi-libs-0.7-27.el8.x86_64 - cairo-1.15.12-6.el8.x86_64 - cairo-gobject-1.15.12-6.el8.x86_64 - colord-libs-1.4.2-1.el8.x86_64 - copy-jdk-configs-4.0-2.el8.noarch - cpio-2.12-11.el8.x86_64 - crypto-policies-scripts-20230731-1.git3177e06.el8.noarch - cups-libs-1:2.2.6-60.el8_10.x86_64 - dconf-0.28.0-4.0.1.el8.x86_64 - dejavu-sans-mono-fonts-2.35-7.el8.noarch - dracut-049-233.git20240115.0.1.el8.x86_64 - file-5.33-25.el8.x86_64 - fribidi-1.0.4-9.el8.x86_64 - gdk-pixbuf2-2.36.12-6.el8_10.x86_64 - gdk-pixbuf2-modules-2.36.12-6.el8_10.x86_64 - gettext-0.19.8.1-17.el8.x86_64 - gettext-libs-0.19.8.1-17.el8.x86_64 - glib-networking-2.56.1-1.1.el8.x86_64 - graphite2-1.3.10-10.el8.x86_64 - grub2-common-1:2.02-156.0.2.el8.noarch - grub2-tools-1:2.02-156.0.2.el8.x86_64 - grub2-tools-minimal-1:2.02-156.0.2.el8.x86_64 - grubby-8.40-49.0.2.el8.x86_64 - gsettings-desktop-schemas-3.32.0-6.el8.x86_64 - gtk-update-icon-cache-3.22.30-11.el8.x86_64 - gtk3-3.22.30-11.el8.x86_64 - hardlink-1:1.3-6.el8.x86_64 - harfbuzz-1.7.5-4.el8.x86_64 - hicolor-icon-theme-0.17-2.el8.noarch - jasper-libs-2.0.14-5.el8.x86_64 - java-11-openjdk-1:11.0.24.0.8-3.0.1.el8.x86_64 - java-11-openjdk-devel-1:11.0.24.0.8-3.0.1.el8.x86_64 - java-11-openjdk-headless-1:11.0.24.0.8-3.0.1.el8.x86_64 - javapackages-filesystem-5.3.0-1.module+el8+5136+7ff78f74.noarch - jbigkit-libs-2.1-14.el8.x86_64 - json-glib-1.4.4-1.el8.x86_64 - kbd-2.0.4-11.el8.x86_64 - kbd-legacy-2.0.4-11.el8.noarch - kbd-misc-2.0.4-11.el8.noarch - lcms2-2.9-2.el8.x86_64 - libX11-1.6.8-8.el8.x86_64 - libX11-common-1.6.8-8.el8.noarch - libXau-1.0.9-3.el8.x86_64 - libXcomposite-0.4.4-14.el8.x86_64 - libXcursor-1.1.15-3.el8.x86_64 - libXdamage-1.1.4-14.el8.x86_64 - libXext-1.3.4-1.el8.x86_64 - libXfixes-5.0.3-7.el8.x86_64 - libXft-2.3.3-1.el8.x86_64 - libXi-1.7.10-1.el8.x86_64 - libXinerama-1.1.4-1.el8.x86_64 - libXrandr-1.5.2-1.el8.x86_64 - libXrender-0.9.10-7.el8.x86_64 - libXtst-1.2.3-7.el8.x86_64 - libcroco-0.6.12-4.el8_2.1.x86_64 - libdatrie-0.2.9-7.el8.x86_64 - libepoxy-1.5.8-1.el8.x86_64 - libfontenc-1.1.3-8.el8.x86_64 - libgomp-8.5.0-22.0.1.el8_10.x86_64 - libgusb-0.3.0-1.el8.x86_64 - libjpeg-turbo-1.5.3-12.el8.x86_64 - libkcapi-1.4.0-2.0.1.el8.x86_64 - libkcapi-hmaccalc-1.4.0-2.0.1.el8.x86_64 - libmodman-2.0.1-17.el8.x86_64 - libpkgconf-1.4.2-1.el8.x86_64 - libproxy-0.4.15-5.2.el8.x86_64 - libsoup-2.62.3-5.el8.x86_64 - libthai-0.1.27-2.el8.x86_64 - libtiff-4.0.9-32.el8_10.x86_64 - libwayland-client-1.21.0-1.el8.x86_64 - libwayland-cursor-1.21.0-1.el8.x86_64 - libwayland-egl-1.21.0-1.el8.x86_64 - libxcb-1.13.1-1.el8.x86_64 - libxkbcommon-0.9.1-1.el8.x86_64 - lksctp-tools-1.0.18-3.el8.x86_64 - lua-5.3.4-12.el8.x86_64 - memstrack-0.2.5-2.el8.x86_64 - nspr-4.35.0-1.el8_8.x86_64 - nss-3.90.0-7.el8_10.x86_64 - nss-softokn-3.90.0-7.el8_10.x86_64 - nss-softokn-freebl-3.90.0-7.el8_10.x86_64 - nss-sysinit-3.90.0-7.el8_10.x86_64 - nss-util-3.90.0-7.el8_10.x86_64 - os-prober-1.74-9.0.1.el8.x86_64 - pango-1.42.4-8.el8.x86_64 - pigz-2.4-4.el8.x86_64 - pixman-0.38.4-4.el8.x86_64 - pkgconf-1.4.2-1.el8.x86_64 - pkgconf-m4-1.4.2-1.el8.noarch - pkgconf-pkg-config-1.4.2-1.el8.x86_64 - rest-0.8.1-2.el8.x86_64 - shared-mime-info-1.9-4.el8.x86_64 - systemd-udev-239-78.0.4.el8.x86_64 - ttmkfdir-3.0.9-54.el8.x86_64 - tzdata-java-2024a-1.0.1.el8.noarch - xkeyboard-config-2.28-1.el8.noarch - xorg-x11-font-utils-1:7.5-41.el8.x86_64 - xorg-x11-fonts-Type1-7.5-19.el8.noarch - xz-5.2.4-4.el8_6.x86_64 - -Complete! -Last metadata expiration check: 0:00:23 ago on Tue 20 Aug 2024 08:55:14 AM UTC. -Package iproute-6.2.0-5.el8_9.x86_64 is already installed. -Dependencies resolved. -================================================================================ - Package Architecture Version Repository Size -================================================================================ -Upgrading: - iproute x86_64 6.2.0-6.el8_10 ol8_baseos_latest 853 k - -Transaction Summary -================================================================================ -Upgrade 1 Package - -Total download size: 853 k -Downloading Packages: -iproute-6.2.0-6.el8_10.x86_64.rpm 4.2 MB/s | 853 kB 00:00 --------------------------------------------------------------------------------- -Total 4.2 MB/s | 853 kB 00:00 -Running transaction check -Transaction check succeeded. -Running transaction test -Transaction test succeeded. -Running transaction - Preparing : 1/1 - Upgrading : iproute-6.2.0-6.el8_10.x86_64 1/2 - Cleanup : iproute-6.2.0-5.el8_9.x86_64 2/2 - Running scriptlet: iproute-6.2.0-5.el8_9.x86_64 2/2 - Verifying : iproute-6.2.0-6.el8_10.x86_64 1/2 - Verifying : iproute-6.2.0-5.el8_9.x86_64 2/2 - -Upgraded: - iproute-6.2.0-6.el8_10.x86_64 - -Complete! -24 files removed -Removing intermediate container fe168b01f3ad - ---> 791878694a50 -Step 5/12 : RUN curl -o /tmp/ords-$ORDSVERSION.el8.noarch.rpm https://yum.oracle.com/repo/OracleLinux/OL8/oracle/software/x86_64/getPackage/ords-$ORDSVERSION.el8.noarch.rpm - ---> Running in 59d7143da358 - % Total % Received % Xferd Average Speed Time Time Time Current - Dload Upload Total Spent Left Speed -100 108M 100 108M 0 0 1440k 0 0:01:16 0:01:16 --:--:-- 1578k -Removing intermediate container 59d7143da358 - ---> 17c4534293e5 -Step 6/12 : RUN rpm -ivh /tmp/ords-$ORDSVERSION.el8.noarch.rpm - ---> Running in 84b1cbffdc51 -Verifying... ######################################## -Preparing... ######################################## -Updating / installing... -ords-23.4.0-8.el8 ######################################## -INFO: Before starting ORDS service, run the below command as user oracle: - ords --config /etc/ords/config install -Removing intermediate container 84b1cbffdc51 - ---> 6e7151b79588 -Step 7/12 : RUN mkdir -p $ORDS_HOME/doc_root && mkdir -p $ORDS_HOME/error && mkdir -p $ORDS_HOME/secrets && chmod ug+x $ORDS_HOME/*.sh && groupadd -g 54322 dba && usermod -u 54321 -d /home/oracle -g dba -m -s /bin/bash oracle && chown -R oracle:dba $ORDS_HOME && echo "oracle ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers - ---> Running in 66e5db5f343f -Removing intermediate container 66e5db5f343f - ---> 0523dc897bf4 -Step 8/12 : USER oracle - ---> Running in ffda8495ac77 -Removing intermediate container ffda8495ac77 - ---> 162acd4d0b93 -Step 9/12 : WORKDIR /home/oracle - ---> Running in 8c14310ffbc7 -Removing intermediate container 8c14310ffbc7 - ---> c8dae809e772 -Step 10/12 : VOLUME ["$ORDS_HOME/config/ords"] - ---> Running in ed64548fd997 -Removing intermediate container ed64548fd997 - ---> 22e2c99247b0 -Step 11/12 : EXPOSE 8888 - ---> Running in 921f7c85d61d -Removing intermediate container 921f7c85d61d - ---> e5d503c92224 -Step 12/12 : CMD $ORDS_HOME/$RUN_FILE - ---> Running in cad487298d63 -Removing intermediate container cad487298d63 - ---> fdb17aa242f8 -Successfully built fdb17aa242f8 -Successfully tagged oracle/ords-dboper:latest -08:57:18 oracle@mitk01:# - diff --git a/docs/multitenant/ords-based/usecase01/logfiles/ImagePush.log b/docs/multitenant/ords-based/usecase01/logfiles/ImagePush.log deleted file mode 100644 index 9b8df426..00000000 --- a/docs/multitenant/ords-based/usecase01/logfiles/ImagePush.log +++ /dev/null @@ -1,11 +0,0 @@ -/usr/bin/docker tag oracle/ords-dboper:latest /ords-dboper:latest -/usr/bin/docker push /ords-dboper:latest -The push refers to repository [/ords-dboper] -aef18205865c: Pushing [=============================> ] 56.55MB/95.45MB -2564d855e579: Pushing [=======> ] 57.08MB/357.6MB -a70a4f9a73c3: Pushed -f283c83ba6ac: Pushed -8c6709989678: Pushing [=======> ] 52.58MB/332.7MB -5bfd57d8f58a: Pushing [========> ] 37.47MB/229.2MB - - diff --git a/docs/multitenant/ords-based/usecase01/logfiles/cdb.log b/docs/multitenant/ords-based/usecase01/logfiles/cdb.log deleted file mode 100644 index c75e9bf8..00000000 --- a/docs/multitenant/ords-based/usecase01/logfiles/cdb.log +++ /dev/null @@ -1,372 +0,0 @@ -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M -NOT_INSTALLED=2 - SETUP -==================================================== -CONFIG=/etc/ords/config -+ export ORDS_LOGS=/tmp -+ ORDS_LOGS=/tmp -+ '[' -f /opt/oracle/ords//secrets/webserver_user ']' -++ cat /opt/oracle/ords//secrets/webserver_user -+ WEBSERVER_USER=.... -+ '[' -f /opt/oracle/ords//secrets/webserver_pwd ']' -++ cat /opt/oracle/ords//secrets/webserver_pwd -+ WEBSERVER_PASSWORD=.... -+ '[' -f /opt/oracle/ords//secrets/cdbadmin_user ']' -++ cat /opt/oracle/ords//secrets/cdbadmin_user -+ CDBADMIN_USER=.... -+ '[' -f /opt/oracle/ords//secrets/cdbadmin_pwd ']' -++ cat /opt/oracle/ords//secrets/cdbadmin_pwd -+ CDBADMIN_PWD=.... -+ '[' -f /opt/oracle/ords//secrets/sysadmin_pwd ']' -++ cat /opt/oracle/ords//secrets/sysadmin_pwd -+ SYSDBA_PASSWORD=..... -+ '[' -f /opt/oracle/ords//secrets/sysadmin_pwd ']' -++ cat /opt/oracle/ords//secrets/ords_pwd -+ ORDS_PASSWORD=.... -+ setupHTTPS -+ rm -rf /home/oracle/keystore -+ '[' '!' -d /home/oracle/keystore ']' -+ mkdir /home/oracle/keystore -+ cd /home/oracle/keystore -+ cat -+ rm /home/oracle/keystore/PASSWORD -+ ls -ltr /home/oracle/keystore -total 0 -+ SetParameter -+ /usr/local/bin/ords --config /etc/ords/config config set security.requestValidationFunction false -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 22.3 Production on Tue Oct 11 07:45:22 2022 - -Copyright (c) 2010, 2022, Oracle. - -Configuration: - /etc/ords/config/ - -The setting named: security.requestValidationFunction was set to: false in configuration: default -+ /usr/local/bin/ords --config /etc/ords/config config set jdbc.MaxLimit 100 -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 22.3 Production on Tue Oct 11 07:45:23 2022 - -Copyright (c) 2010, 2022, Oracle. - -Configuration: - /etc/ords/config/ - -The setting named: jdbc.MaxLimit was set to: 100 in configuration: default -+ /usr/local/bin/ords --config /etc/ords/config config set jdbc.InitialLimit 50 -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 22.3 Production on Tue Oct 11 07:45:24 2022 - -Copyright (c) 2010, 2022, Oracle. - -Configuration: - /etc/ords/config/ - -The setting named: jdbc.InitialLimit was set to: 50 in configuration: default -+ /usr/local/bin/ords --config /etc/ords/config config set error.externalPath /opt/oracle/ords/error -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 22.3 Production on Tue Oct 11 07:45:26 2022 - -Copyright (c) 2010, 2022, Oracle. - -Configuration: - /etc/ords/config/ - -The global setting named: error.externalPath was set to: /opt/oracle/ords/error -+ /usr/local/bin/ords --config /etc/ords/config config set standalone.access.log /home/oracle -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 22.3 Production on Tue Oct 11 07:45:27 2022 - -Copyright (c) 2010, 2022, Oracle. - -Configuration: - /etc/ords/config/ - -The global setting named: standalone.access.log was set to: /home/oracle -+ /usr/local/bin/ords --config /etc/ords/config config set standalone.https.port 8888 -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 22.3 Production on Tue Oct 11 07:45:28 2022 - -Copyright (c) 2010, 2022, Oracle. - -Configuration: - /etc/ords/config/ - -The global setting named: standalone.https.port was set to: 8888 -+ /usr/local/bin/ords --config /etc/ords/config config set standalone.https.cert /opt/oracle/ords//secrets/tls.crt -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 22.3 Production on Tue Oct 11 07:45:29 2022 - -Copyright (c) 2010, 2022, Oracle. - -Configuration: - /etc/ords/config/ - -The global setting named: standalone.https.cert was set to: /opt/oracle/ords//secrets/tls.crt -+ /usr/local/bin/ords --config /etc/ords/config config set standalone.https.cert.key /opt/oracle/ords//secrets/tls.key -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 22.3 Production on Tue Oct 11 07:45:31 2022 - -Copyright (c) 2010, 2022, Oracle. - -Configuration: - /etc/ords/config/ - -The global setting named: standalone.https.cert.key was set to: /opt/oracle/ords//secrets/tls.key -+ /usr/local/bin/ords --config /etc/ords/config config set restEnabledSql.active true -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 22.3 Production on Tue Oct 11 07:45:32 2022 - -Copyright (c) 2010, 2022, Oracle. - -Configuration: - /etc/ords/config/ - -The setting named: restEnabledSql.active was set to: true in configuration: default -+ /usr/local/bin/ords --config /etc/ords/config config set security.verifySSL true -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 22.3 Production on Tue Oct 11 07:45:33 2022 - -Copyright (c) 2010, 2022, Oracle. - -Configuration: - /etc/ords/config/ - -The global setting named: security.verifySSL was set to: true -+ /usr/local/bin/ords --config /etc/ords/config config set database.api.enabled true -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 22.3 Production on Tue Oct 11 07:45:34 2022 - -Copyright (c) 2010, 2022, Oracle. - -Configuration: - /etc/ords/config/ - -The global setting named: database.api.enabled was set to: true -+ /usr/local/bin/ords --config /etc/ords/config config set plsql.gateway.mode false -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 22.3 Production on Tue Oct 11 07:45:35 2022 - -Copyright (c) 2010, 2022, Oracle. - -Configuration: - /etc/ords/config/ - -Invalid VALUE argument false for KEY plsql.gateway.mode. -+ /usr/local/bin/ords --config /etc/ords/config config set database.api.management.services.disabled false -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 22.3 Production on Tue Oct 11 07:45:37 2022 - -Copyright (c) 2010, 2022, Oracle. - -Configuration: - /etc/ords/config/ - -The global setting named: database.api.management.services.disabled was set to: false -+ /usr/local/bin/ords --config /etc/ords/config config set misc.pagination.maxRows 1000 -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 22.3 Production on Tue Oct 11 07:45:38 2022 - -Copyright (c) 2010, 2022, Oracle. - -Configuration: - /etc/ords/config/ - -The setting named: misc.pagination.maxRows was set to: 1000 in configuration: default -+ /usr/local/bin/ords --config /etc/ords/config config set db.cdb.adminUser 'C##DBAPI_CDB_ADMIN AS SYSDBA' -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 22.3 Production on Tue Oct 11 07:45:39 2022 - -Copyright (c) 2010, 2022, Oracle. - -Configuration: - /etc/ords/config/ - -The setting named: db.cdb.adminUser was set to: C##DBAPI_CDB_ADMIN AS SYSDBA in configuration: default -+ /usr/local/bin/ords --config /etc/ords/config config secret --password-stdin db.cdb.adminUser.password -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 22.3 Production on Tue Oct 11 07:45:40 2022 - -Copyright (c) 2010, 2022, Oracle. - -Configuration: - /etc/ords/config/ - -The setting named: db.cdb.adminUser.password was set to: ****** in configuration: default -+ /usr/local/bin/ords --config /etc/ords/config config user add --password-stdin sql_admin 'SQL Administrator, System Administrator' -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 22.3 Production on Tue Oct 11 07:45:42 2022 - -Copyright (c) 2010, 2022, Oracle. - -Configuration: - /etc/ords/config/ - -Created user sql_admin in file /etc/ords/config/global/credentials -+ /usr/local/bin/ords --config /etc/ords/config install --admin-user 'SYS AS SYSDBA' --db-hostname racnode1.testrac.com --db-port 1521 --db-servicename TESTORDS --feature-db-api true --feature-rest-enabled-sql true --log-folder /tmp --proxy-user --password-stdin -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 22.3 Production on Tue Oct 11 07:45:43 2022 - -Copyright (c) 2010, 2022, Oracle. - -Configuration: - /etc/ords/config/ - -Oracle REST Data Services - Non-Interactive Install -Connecting to database user: SYS AS SYSDBA url: jdbc:oracle:thin:@//racnode1.testrac.com:1521/TESTORDS - -Retrieving information.. -Your database connection is to a CDB. ORDS common user ORDS_PUBLIC_USER will be created in the CDB. ORDS schema will be installed in the PDBs. -Root CDB$ROOT - create ORDS common user -PDB PDB$SEED - install ORDS 22.3.0.r2781755 -PDB PDB$SEED - configure PL/SQL gateway user APEX_PUBLIC_USER in ORDS version 22.3.0.r2781755 - -The setting named: db.connectionType was set to: basic in configuration: default -The setting named: db.hostname was set to: racnode1.testrac.com in configuration: default -The setting named: db.port was set to: 1521 in configuration: default -The setting named: db.servicename was set to: TESTORDS in configuration: default -The setting named: db.serviceNameSuffix was set to: in configuration: default -The setting named: plsql.gateway.mode was set to: proxied in configuration: default -The setting named: db.username was set to: ORDS_PUBLIC_USER in configuration: default -The setting named: db.password was set to: ****** in configuration: default -The setting named: security.requestValidationFunction was set to: wwv_flow_epg_include_modules.authorize in configuration: default -2022-10-11T07:45:45.885Z INFO Installing Oracle REST Data Services version 22.3.0.r2781755 in CDB$ROOT -2022-10-11T07:45:46.703Z INFO ... Verified database prerequisites -2022-10-11T07:45:46.946Z INFO ... Created Oracle REST Data Services proxy user -2022-10-11T07:45:46.979Z INFO Completed installation for Oracle REST Data Services version 22.3.0.r2781755. Elapsed time: 00:00:01.71 - -2022-10-11T07:45:46.986Z INFO Installing Oracle REST Data Services version 22.3.0.r2781755 in PDB$SEED -2022-10-11T07:45:47.078Z INFO ... Verified database prerequisites -2022-10-11T07:45:47.290Z INFO ... Created Oracle REST Data Services proxy user -2022-10-11T07:45:47.741Z INFO ... Created Oracle REST Data Services schema -2022-10-11T07:45:48.097Z INFO ... Granted privileges to Oracle REST Data Services -2022-10-11T07:45:51.848Z INFO ... Created Oracle REST Data Services database objects -2022-10-11T07:46:00.829Z INFO Completed installation for Oracle REST Data Services version 22.3.0.r2781755. Elapsed time: 00:00:13.841 - -2022-10-11T07:46:00.898Z INFO Completed configuring PL/SQL gateway user for Oracle REST Data Services version 22.3.0.r2781755. Elapsed time: 00:00:00.68 - -2022-10-11T07:46:00.898Z INFO Completed CDB installation for Oracle REST Data Services version 22.3.0.r2781755. Total elapsed time: 00:00:15.17 - -2022-10-11T07:46:00.898Z INFO Log file written to /tmp/ords_cdb_install_2022-10-11_074545_78000.log -2022-10-11T07:46:00.901Z INFO To run in standalone mode, use the ords serve command: -2022-10-11T07:46:00.901Z INFO ords --config /etc/ords/config serve -2022-10-11T07:46:00.901Z INFO Visit the ORDS Documentation to access tutorials, developer guides and more to help you get started with the new ORDS Command Line Interface (http://oracle.com/rest). -+ '[' 0 -ne 0 ']' -+ StartUp -+ /usr/local/bin/ords --config /etc/ords/config serve --port 8888 --secure -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 22.3 Production on Tue Oct 11 07:46:02 2022 - -Copyright (c) 2010, 2022, Oracle. - -Configuration: - /etc/ords/config/ - -2022-10-11T07:46:02.286Z INFO HTTPS and HTTPS/2 listening on host: 0.0.0.0 port: 8888 -2022-10-11T07:46:02.302Z INFO Disabling document root because the specified folder does not exist: /etc/ords/config/global/doc_root -2022-10-11T07:46:04.636Z INFO Configuration properties for: |default|lo| -db.servicename=TESTORDS -db.serviceNameSuffix= -java.specification.version=19 -conf.use.wallet=true -database.api.management.services.disabled=false -sun.jnu.encoding=UTF-8 -user.region=US -java.class.path=/opt/oracle/ords/ords.war -java.vm.vendor=Oracle Corporation -standalone.https.cert.key=/opt/oracle/ords//secrets/tls.key -sun.arch.data.model=64 -nashorn.args=--no-deprecation-warning -java.vendor.url=https://java.oracle.com/ -resource.templates.enabled=false -user.timezone=UTC -db.port=1521 -java.vm.specification.version=19 -os.name=Linux -sun.java.launcher=SUN_STANDARD -user.country=US -sun.boot.library.path=/usr/java/jdk-19/lib -sun.java.command=/opt/oracle/ords/ords.war --config /etc/ords/config serve --port 8888 --secure -jdk.debug=release -sun.cpu.endian=little -user.home=/home/oracle -oracle.dbtools.launcher.executable.jar.path=/opt/oracle/ords/ords.war -user.language=en -db.cdb.adminUser.password=****** -java.specification.vendor=Oracle Corporation -java.version.date=2022-09-20 -database.api.enabled=true -java.home=/usr/java/jdk-19 -db.username=ORDS_PUBLIC_USER -file.separator=/ -java.vm.compressedOopsMode=32-bit -line.separator= - -restEnabledSql.active=true -java.specification.name=Java Platform API Specification -java.vm.specification.vendor=Oracle Corporation -java.awt.headless=true -standalone.https.cert=/opt/oracle/ords//secrets/tls.crt -db.hostname=racnode1.testrac.com -db.password=****** -sun.management.compiler=HotSpot 64-Bit Tiered Compilers -security.requestValidationFunction=wwv_flow_epg_include_modules.authorize -misc.pagination.maxRows=1000 -java.runtime.version=19+36-2238 -user.name=oracle -error.externalPath=/opt/oracle/ords/error -stdout.encoding=UTF-8 -path.separator=: -db.cdb.adminUser=C##DBAPI_CDB_ADMIN AS SYSDBA -os.version=5.4.17-2136.308.9.el7uek.x86_64 -java.runtime.name=Java(TM) SE Runtime Environment -file.encoding=UTF-8 -plsql.gateway.mode=proxied -security.verifySSL=true -standalone.https.port=8888 -java.vm.name=Java HotSpot(TM) 64-Bit Server VM -java.vendor.url.bug=https://bugreport.java.com/bugreport/ -java.io.tmpdir=/tmp -oracle.dbtools.cmdline.ShellCommand=ords -java.version=19 -user.dir=/home/oracle/keystore -os.arch=amd64 -java.vm.specification.name=Java Virtual Machine Specification -jdbc.MaxLimit=100 -oracle.dbtools.cmdline.home=/opt/oracle/ords -native.encoding=UTF-8 -java.library.path=/usr/java/packages/lib:/usr/lib64:/lib64:/lib:/usr/lib -java.vendor=Oracle Corporation -java.vm.info=mixed mode, sharing -stderr.encoding=UTF-8 -java.vm.version=19+36-2238 -sun.io.unicode.encoding=UnicodeLittle -jdbc.InitialLimit=50 -db.connectionType=basic -java.class.version=63.0 -standalone.access.log=/home/oracle - -2022-10-11T07:46:06.669Z INFO Oracle REST Data Services initialized -Oracle REST Data Services version : 22.3.0.r2781755 -Oracle REST Data Services server info: jetty/10.0.11 -Oracle REST Data Services java info: Java HotSpot(TM) 64-Bit Server VM 19+36-2238 - diff --git a/docs/multitenant/ords-based/usecase01/logfiles/cdb_creation.log b/docs/multitenant/ords-based/usecase01/logfiles/cdb_creation.log deleted file mode 100644 index b4602f54..00000000 --- a/docs/multitenant/ords-based/usecase01/logfiles/cdb_creation.log +++ /dev/null @@ -1,357 +0,0 @@ -/usr/local/go/bin/kubectl logs -f `/usr/local/go/bin/kubectl get pods -n oracle-database-operator-system|grep ords|cut -d ' ' -f 1` -n oracle-database-operator-system -** Copyright (c) 2022 Oracle and/or its affiliates. -** -** The Universal Permissive License (UPL), Version 1.0 -** -** Subject to the condition set forth below, permission is hereby granted to any -** person obtaining a copy of this software, associated documentation and/or data -** (collectively the "Software"), free of charge and under any and all copyright -** rights in the Software, and any and all patent rights owned or freely -** licensable by each licensor hereunder covering either (i) the unmodified -** Software as contributed to or provided by such licensor, or (ii) the Larger -** Works (as defined below), to deal in both -** -** (a) the Software, and -** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if -** one is included with the Software (each a "Larger Work" to which the Software -** is contributed by such licensors), -** -** without restriction, including without limitation the rights to copy, create -** derivative works of, display, perform, and distribute the Software and make, -** use, sell, offer for sale, import, export, have made, and have sold the -** Software and the Larger Work(s), and to sublicense the foregoing rights on -** either these or other terms. -** -** This license is subject to the following condition: -** The above copyright notice and either this complete permission notice or at -** a minimum a reference to the UPL must be included in all copies or -** substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -** SOFTWARE. -ORDSVERSIN:23.4.0-8 -NOT_INSTALLED=2 - SETUP -==================================================== -CONFIG=/etc/ords/config -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.4 Production on Tue Aug 20 07:21:16 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -The setting named: db.connectionType was set to: customurl in configuration: default -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.4 Production on Tue Aug 20 07:21:18 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -The setting named: db.customURL was set to: jdbc:oracle:thin:@(DESCRIPTION=(CONNECT_TIMEOUT=90)(RETRY_COUNT=30)(RETRY_DELAY=10)(TRANSPORT_CONNECT_TIMEOUT=70)(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL=TCP)(HOST=scan12.testrac.com)(PORT=1521)(IP=V4_ONLY))(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL=TCP)(HOST=scan34.testrac.com)(PORT=1521)(IP=V4_ONLY))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=TESTORDS))) in configuration: default -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.4 Production on Tue Aug 20 07:21:20 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -The setting named: security.requestValidationFunction was set to: false in configuration: default -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.4 Production on Tue Aug 20 07:21:22 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -The setting named: jdbc.MaxLimit was set to: 100 in configuration: default -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.4 Production on Tue Aug 20 07:21:24 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -The setting named: jdbc.InitialLimit was set to: 50 in configuration: default -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.4 Production on Tue Aug 20 07:21:25 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -The global setting named: error.externalPath was set to: /opt/oracle/ords/error -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.4 Production on Tue Aug 20 07:21:27 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -The global setting named: standalone.access.log was set to: /home/oracle -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.4 Production on Tue Aug 20 07:21:29 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -The global setting named: standalone.https.port was set to: 8888 -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.4 Production on Tue Aug 20 07:21:31 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -The global setting named: standalone.https.cert was set to: /opt/oracle/ords//secrets/tls.crt -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.4 Production on Tue Aug 20 07:21:33 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -The global setting named: standalone.https.cert.key was set to: /opt/oracle/ords//secrets/tls.key -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.4 Production on Tue Aug 20 07:21:35 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -The setting named: restEnabledSql.active was set to: true in configuration: default -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.4 Production on Tue Aug 20 07:21:37 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -The global setting named: security.verifySSL was set to: true -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.4 Production on Tue Aug 20 07:21:39 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -The global setting named: database.api.enabled was set to: true -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.4 Production on Tue Aug 20 07:21:41 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -The setting named: plsql.gateway.mode was set to: disabled in configuration: default -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.4 Production on Tue Aug 20 07:21:43 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -The global setting named: database.api.management.services.disabled was set to: false -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.4 Production on Tue Aug 20 07:21:45 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -The setting named: misc.pagination.maxRows was set to: 1000 in configuration: default -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.4 Production on Tue Aug 20 07:21:47 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -The setting named: db.cdb.adminUser was set to: C##DBAPI_CDB_ADMIN AS SYSDBA in configuration: default -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.4 Production on Tue Aug 20 07:21:49 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -The setting named: db.cdb.adminUser.password was set to: ****** in configuration: default -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.4 Production on Tue Aug 20 07:21:51 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -Created user welcome in file /etc/ords/config/global/credentials -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.4 Production on Tue Aug 20 07:21:53 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -Oracle REST Data Services - Non-Interactive Install - -Retrieving information... -Completed verifying Oracle REST Data Services schema version 23.4.0.r3461619. -Connecting to database user: ORDS_PUBLIC_USER url: jdbc:oracle:thin:@(DESCRIPTION=(CONNECT_TIMEOUT=90)(RETRY_COUNT=30)(RETRY_DELAY=10)(TRANSPORT_CONNECT_TIMEOUT=70)(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL=TCP)(HOST=scan12.testrac.com)(PORT=1521)(IP=V4_ONLY))(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL=TCP)(HOST=scan34.testrac.com)(PORT=1521)(IP=V4_ONLY))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=TESTORDS))) -The setting named: db.serviceNameSuffix was set to: in configuration: default -The setting named: db.username was set to: ORDS_PUBLIC_USER in configuration: default -The setting named: db.password was set to: ****** in configuration: default -The setting named: security.requestValidationFunction was set to: ords_util.authorize_plsql_gateway in configuration: default -2024-08-20T07:21:57.563Z INFO Oracle REST Data Services schema version 23.4.0.r3461619 is installed. -2024-08-20T07:21:57.565Z INFO To run in standalone mode, use the ords serve command: -2024-08-20T07:21:57.565Z INFO ords --config /etc/ords/config serve -2024-08-20T07:21:57.565Z INFO Visit the ORDS Documentation to access tutorials, developer guides and more to help you get started with the new ORDS Command Line Interface (http://oracle.com/rest). -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.4 Production on Tue Aug 20 07:21:59 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -2024-08-20T07:21:59.739Z INFO HTTP and HTTP/2 cleartext listening on host: 0.0.0.0 port: 8080 -2024-08-20T07:21:59.741Z INFO HTTPS and HTTPS/2 listening on host: 0.0.0.0 port: 8888 -2024-08-20T07:21:59.765Z INFO Disabling document root because the specified folder does not exist: /etc/ords/config/global/doc_root -2024-08-20T07:21:59.765Z INFO Default forwarding from / to contextRoot configured. -2024-08-20T07:22:05.313Z INFO Configuration properties for: |default|lo| -db.serviceNameSuffix= -java.specification.version=22 -conf.use.wallet=true -database.api.management.services.disabled=false -sun.jnu.encoding=UTF-8 -user.region=US -java.class.path=/opt/oracle/ords/ords.war -java.vm.vendor=Oracle Corporation -standalone.https.cert.key=/opt/oracle/ords//secrets/tls.key -sun.arch.data.model=64 -nashorn.args=--no-deprecation-warning -java.vendor.url=https://java.oracle.com/ -resource.templates.enabled=false -user.timezone=UTC -java.vm.specification.version=22 -os.name=Linux -sun.java.launcher=SUN_STANDARD -user.country=US -sun.boot.library.path=/usr/java/jdk-22/lib -sun.java.command=/opt/oracle/ords/ords.war --config /etc/ords/config serve --port 8888 --secure -jdk.debug=release -sun.cpu.endian=little -user.home=/home/oracle -oracle.dbtools.launcher.executable.jar.path=/opt/oracle/ords/ords.war -user.language=en -db.cdb.adminUser.password=****** -java.specification.vendor=Oracle Corporation -java.version.date=2024-07-16 -database.api.enabled=true -java.home=/usr/java/jdk-22 -db.username=ORDS_PUBLIC_USER -file.separator=/ -java.vm.compressedOopsMode=32-bit -line.separator= - -restEnabledSql.active=true -java.specification.name=Java Platform API Specification -java.vm.specification.vendor=Oracle Corporation -java.awt.headless=true -standalone.https.cert=/opt/oracle/ords//secrets/tls.crt -db.password=****** -sun.management.compiler=HotSpot 64-Bit Tiered Compilers -security.requestValidationFunction=ords_util.authorize_plsql_gateway -misc.pagination.maxRows=1000 -java.runtime.version=22.0.2+9-70 -user.name=oracle -error.externalPath=/opt/oracle/ords/error -stdout.encoding=UTF-8 -path.separator=: -db.cdb.adminUser=C##DBAPI_CDB_ADMIN AS SYSDBA -os.version=5.4.17-2136.329.3.1.el7uek.x86_64 -java.runtime.name=Java(TM) SE Runtime Environment -file.encoding=UTF-8 -plsql.gateway.mode=disabled -security.verifySSL=true -standalone.https.port=8888 -java.vm.name=Java HotSpot(TM) 64-Bit Server VM -java.vendor.url.bug=https://bugreport.java.com/bugreport/ -java.io.tmpdir=/tmp -oracle.dbtools.cmdline.ShellCommand=ords -java.version=22.0.2 -user.dir=/home/oracle -os.arch=amd64 -java.vm.specification.name=Java Virtual Machine Specification -jdbc.MaxLimit=100 -oracle.dbtools.cmdline.home=/opt/oracle/ords -native.encoding=UTF-8 -java.library.path=/usr/java/packages/lib:/usr/lib64:/lib64:/lib:/usr/lib -java.vendor=Oracle Corporation -java.vm.info=mixed mode, sharing -stderr.encoding=UTF-8 -java.vm.version=22.0.2+9-70 -sun.io.unicode.encoding=UnicodeLittle -jdbc.InitialLimit=50 -db.connectionType=customurl -java.class.version=66.0 -db.customURL=jdbc:oracle:thin:@(DESCRIPTION=(CONNECT_TIMEOUT=90)(RETRY_COUNT=30)(RETRY_DELAY=10)(TRANSPORT_CONNECT_TIMEOUT=70)(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL=TCP)(HOST=scan12.testrac.com)(PORT=1521)(IP=V4_ONLY))(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL=TCP)(HOST=scan34.testrac.com)(PORT=1521)(IP=V4_ONLY))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=TESTORDS))) -standalone.access.log=/home/oracle - -2024-08-20T07:22:09.268Z INFO - -Mapped local pools from /etc/ords/config/databases: - /ords/ => default => VALID - - -2024-08-20T07:22:09.414Z INFO Oracle REST Data Services initialized -Oracle REST Data Services version : 23.4.0.r3461619 -Oracle REST Data Services server info: jetty/10.0.18 -Oracle REST Data Services java info: Java HotSpot(TM) 64-Bit Server VM 22.0.2+9-70 - diff --git a/docs/multitenant/ords-based/usecase01/logfiles/openssl_execution.log b/docs/multitenant/ords-based/usecase01/logfiles/openssl_execution.log deleted file mode 100644 index e3915a21..00000000 --- a/docs/multitenant/ords-based/usecase01/logfiles/openssl_execution.log +++ /dev/null @@ -1,19 +0,0 @@ -CREATING TLS CERTIFICATES -/usr/bin/openssl genrsa -out ca.key 2048 -Generating RSA private key, 2048 bit long modulus (2 primes) -......................+++++ -..................................................+++++ -e is 65537 (0x010001) -/usr/bin/openssl req -new -x509 -days 365 -key ca.key -subj "/C=US/ST=California/L=SanFrancisco/O=oracle /CN=cdb-dev-ords.oracle-database-operator-system /CN=localhost Root CA " -out ca.crt -/usr/bin/openssl req -newkey rsa:2048 -nodes -keyout tls.key -subj "/C=US/ST=California/L=SanFrancisco/O=oracle /CN=cdb-dev-ords.oracle-database-operator-system /CN=localhost" -out server.csr -Generating a RSA private key -...........+++++ -...........................................+++++ -writing new private key to 'tls.key' ------ -/usr/bin/echo "subjectAltName=DNS:cdb-dev-ords.oracle-database-operator-system,DNS:www.example.com" > extfile.txt -/usr/bin/openssl x509 -req -extfile extfile.txt -days 365 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out tls.crt -Signature ok -subject=C = US, ST = California, L = SanFrancisco, O = "oracle ", CN = "cdb-dev-ords.oracle-database-operator-system ", CN = localhost -Getting CA Private Key - diff --git a/docs/multitenant/ords-based/usecase01/logfiles/ordsconfig.log b/docs/multitenant/ords-based/usecase01/logfiles/ordsconfig.log deleted file mode 100644 index b787b752..00000000 --- a/docs/multitenant/ords-based/usecase01/logfiles/ordsconfig.log +++ /dev/null @@ -1,39 +0,0 @@ -ORDS: Release 23.4 Production on Tue Aug 20 07:48:44 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -Database pool: default - -Setting Value Source ------------------------------------------ -------------------------------------------------- ----------- -database.api.enabled true Global -database.api.management.services.disabled false Global -db.cdb.adminUser C##DBAPI_CDB_ADMIN AS SYSDBA Pool -db.cdb.adminUser.password ****** Pool Wallet -db.connectionType customurl Pool -db.customURL jdbc:oracle:thin:@(DESCRIPTION=(CONNECT_TIMEOUT=90 Pool - )(RETRY_COUNT=30)(RETRY_DELAY=10)(TRANSPORT_CONNEC - T_TIMEOUT=70)(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL= - TCP)(HOST=scan12.testrac.com)(PORT=1521)(IP=V4_ONL - Y))(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL=TCP)(HOST= - scan34.testrac.com)(PORT=1521)(IP=V4_ONLY))(CONNEC - T_DATA=(SERVER=DEDICATED)(SERVICE_NAME=TESTORDS))) -db.password ****** Pool Wallet -db.serviceNameSuffix Pool -db.username ORDS_PUBLIC_USER Pool -error.externalPath /opt/oracle/ords/error Global -jdbc.InitialLimit 50 Pool -jdbc.MaxLimit 100 Pool -misc.pagination.maxRows 1000 Pool -plsql.gateway.mode disabled Pool -restEnabledSql.active true Pool -security.requestValidationFunction ords_util.authorize_plsql_gateway Pool -security.verifySSL true Global -standalone.access.log /home/oracle Global -standalone.https.cert /opt/oracle/ords//secrets/tls.crt Global -standalone.https.cert.key /opt/oracle/ords//secrets/tls.key Global -standalone.https.port 8888 Global - diff --git a/docs/multitenant/ords-based/usecase01/logfiles/tagandpush.log b/docs/multitenant/ords-based/usecase01/logfiles/tagandpush.log deleted file mode 100644 index 232d5bb2..00000000 --- a/docs/multitenant/ords-based/usecase01/logfiles/tagandpush.log +++ /dev/null @@ -1,14 +0,0 @@ -/usr/bin/docker tag oracle/ords-dboper:latest [.......]/ords-dboper:latest - -/usr/bin/docker push [your container registry]/ords-dboper:latest -The push refers to repository [your container registry] -0405aac3af1c: Pushed -6be46e8e1e21: Pushed -c9884830a66d: Pushed -a46244557bb9: Pushing [===========================> ] 261.8MB/469.9MB -f988845e261e: Pushed -fe07ec0b1f5a: Layer already exists -2ac63de5f950: Layer already exists -386cd7a64c01: Layer already exists -826c69252b8b: Layer already exists - diff --git a/docs/multitenant/ords-based/usecase01/logfiles/testapi.log b/docs/multitenant/ords-based/usecase01/logfiles/testapi.log deleted file mode 100644 index cb42ecc3..00000000 --- a/docs/multitenant/ords-based/usecase01/logfiles/testapi.log +++ /dev/null @@ -1,62 +0,0 @@ -kubectl exec -it `kubectl get pods -n oracle-database-operator-system|grep ords|cut -d ' ' -f 1` -n oracle-database-operator-system -i -t -- /usr/bin/curl -sSkv -k -X GET https://localhost:8888/ords/_/db-api/stable/metadata-catalog/ -* Trying ::1... -* TCP_NODELAY set -* Connected to localhost (::1) port 8888 (#0) -* ALPN, offering h2 -* ALPN, offering http/1.1 -* successfully set certificate verify locations: -* CAfile: /etc/pki/tls/certs/ca-bundle.crt - CApath: none -* TLSv1.3 (OUT), TLS handshake, Client hello (1): -* TLSv1.3 (IN), TLS handshake, Server hello (2): -* TLSv1.3 (IN), TLS handshake, [no content] (0): -* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8): -* TLSv1.3 (IN), TLS handshake, Certificate (11): -* TLSv1.3 (IN), TLS handshake, CERT verify (15): -* TLSv1.3 (IN), TLS handshake, Finished (20): -* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1): -* TLSv1.3 (OUT), TLS handshake, [no content] (0): -* TLSv1.3 (OUT), TLS handshake, Finished (20): -* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 -* ALPN, server accepted to use h2 -* Server certificate: -* subject: C=US; ST=California; L=SanFrancisco; O=oracle ; CN=cdb-dev-ords.oracle-database-operator-system ; CN=localhost -* start date: Aug 20 07:14:04 2024 GMT -* expire date: Aug 20 07:14:04 2025 GMT -* issuer: C=US; ST=California; L=SanFrancisco; O=oracle ; CN=cdb-dev-ords.oracle-database-operator-system ; CN=localhost Root CA -* SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway. -* Using HTTP2, server supports multi-use -* Connection state changed (HTTP/2 confirmed) -* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0 -* TLSv1.3 (OUT), TLS app data, [no content] (0): -* TLSv1.3 (OUT), TLS app data, [no content] (0): -* TLSv1.3 (OUT), TLS app data, [no content] (0): -* Using Stream ID: 1 (easy handle 0x55d14a7dea90) -* TLSv1.3 (OUT), TLS app data, [no content] (0): -> GET /ords/_/db-api/stable/metadata-catalog/ HTTP/2 -> Host: localhost:8888 -> User-Agent: curl/7.61.1 -> Accept: */* -> -* TLSv1.3 (IN), TLS handshake, [no content] (0): -* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): -* TLSv1.3 (IN), TLS handshake, [no content] (0): -* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): -* TLSv1.3 (IN), TLS app data, [no content] (0): -* Connection state changed (MAX_CONCURRENT_STREAMS == 128)! -* TLSv1.3 (OUT), TLS app data, [no content] (0): -* TLSv1.3 (IN), TLS app data, [no content] (0): -* TLSv1.3 (IN), TLS app data, [no content] (0): -< HTTP/2 200 -< content-type: application/json -< -* TLSv1.3 (IN), TLS handshake, [no content] (0): -* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): -* TLSv1.3 (IN), TLS app data, [no content] (0): -* TLSv1.3 (IN), TLS app data, [no content] (0): -* TLSv1.3 (IN), TLS app data, [no content] (0): -* TLSv1.3 (IN), TLS app data, [no content] (0): -* TLSv1.3 (IN), TLS app data, [no content] (0): -* TLSv1.3 (IN), TLS app data, [no content] (0): -* Connection #0 to host localhost left intact -{"items":[{"name":"default","links":[{"rel":"canonical","href":"https://localhost:8888/ords/_/db-api/stable/metadata-catalog/openapi.json","mediaType":"application/vnd.oai.openapi+json;version=3.0"}]}],"links":[{"rel":"self","href":"https://localhost:8888/ords/_/db-api/stable/metadata-catalog/"},{"rel":"describes","href":"https://localhost:8888/ords/_/db-api/stable/"}]} diff --git a/docs/multitenant/ords-based/usecase01/makefile b/docs/multitenant/ords-based/usecase01/makefile deleted file mode 100644 index ec454e28..00000000 --- a/docs/multitenant/ords-based/usecase01/makefile +++ /dev/null @@ -1,906 +0,0 @@ -# Copyright (c) 2022, Oracle and/or its affiliates. -# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. -# __ __ _ __ _ _ -# | \/ | __ _| | _____ / _(_) | ___ -# | |\/| |/ _` | |/ / _ \ |_| | |/ _ \ -# | | | | (_| | < __/ _| | | __/ -# |_| |_|\__,_|_|\_\___|_| |_|_|\___| -# | | | | ___| |_ __ ___ _ __ -# | |_| |/ _ \ | '_ \ / _ \ '__| -# | _ | __/ | |_) | __/ | -# |_| |_|\___|_| .__/ \___|_| -# |_| -# -# WARNING: Using this makefile helps you to customize yaml -# files. Edit parameters.txt with your enviroment -# informartion and execute the following steps -# -# 1) make operator -# it configures the operator yaml files with the -# watch namelist required by the multitenant controllers -# -# 2) make genyaml -# It automatically creates all the yaml files based on the -# information available in the parameters file -# -# 3) make secrets -# It configure the required secrets necessary to operate -# with pdbs multitenant controllers -# -# 4) make runall01 -# Start a series of operation create open close delete and so on -# -# LIST OF GENERAED YAML FILE -# -# ----------------------------- ---------------------------------- -# oracle-database-operator.yaml : oracle database operator -# cdbnamespace_binding.yaml : role binding for cdbnamespace -# pdbnamespace_binding.yaml : role binding for pdbnamespace -# create_cdb_secret.yaml : create secrets for ords server pod -# create_pdb_secret.yaml : create secrets for pluggable database -# create_ords_pod.yaml : create rest server pod -# create_pdb1_resource.yaml : create first pluggable database -# create_pdb2_resource.yaml : create second pluggable database -# open_pdb1_resource.yaml : open first pluggable database -# open_pdb2_resource.yaml : open second pluggable database -# close_pdb1_resource.yaml : close first pluggable database -# close_pdb2_resource.yaml : close second pluggable database -# clone_pdb_resource.yaml : clone thrid pluggable database -# clone_pdb2_resource.yaml : clone 4th pluggable database -# delete_pdb1_resource.yaml : delete first pluggable database -# delete_pdb2_resource.yaml : delete sencond pluggable database -# delete_pdb3_resource.yaml : delete thrid pluggable database -# unplug_pdb1_resource.yaml : unplug first pluggable database -# plug_pdb1_resource.yaml : plug first pluggable database -# map_pdb1_resource.yaml : map the first pluggable database -# config_map.yam : pdb parameters array -# -DATE := `date "+%y%m%d%H%M%S"` -###################### -# PARAMETER SECTIONS # -###################### - -export PARAMETERS=parameters.txt -export TNSALIAS=$(shell cat $(PARAMETERS) |grep -v ^\#|grep TNSALIAS|cut -d : -f 2) -export ORDPWD=$(shell cat $(PARAMETERS)|grep -v ^\#|grep ORDPWD|cut -d : -f 2) -export SYSPWD=$(shell cat $(PARAMETERS)|grep -v ^\#|grep SYSPWD|cut -d : -f 2) -export WBUSER=$(shell cat $(PARAMETERS)|grep -v ^\#|grep WBUSER|cut -d : -f 2) -export WBPASS=$(shell cat $(PARAMETERS)|grep -v ^\#|grep WBPASS|cut -d : -f 2) -export PDBUSR=$(shell cat $(PARAMETERS)|grep -v ^\#|grep PDBUSR|cut -d : -f 2) -export PDBPWD=$(shell cat $(PARAMETERS)|grep -v ^\#|grep PDBPWD|cut -d : -f 2) -export CDBUSR=$(shell cat $(PARAMETERS)|grep -v ^\#|grep CDBUSR|cut -d : -f 2) -export CDBPWD=$(shell cat $(PARAMETERS)|grep -v ^\#|grep CDBPWD|cut -d : -f 2) -export OPRNAMESPACE=$(shell cat $(PARAMETERS)|grep -v ^\#|grep OPRNAMESPACE|cut -d : -f 2) -export OPRNAMESPACE=$(shell cat $(PARAMETERS)|grep -v ^\#|grep OPRNAMESPACE|cut -d : -f 2) -export ORDSIMG=$(shell cat $(PARAMETERS)|grep -v ^\#|grep ORDSIMG|cut -d : -f 2,3) -export COMPANY=$(shell cat $(PARAMETERS)|grep -v ^\#|grep COMPANY|cut -d : -f 2) -export APIVERSION=$(shell cat $(PARAMETERS)|grep -v ^\#|grep APIVERSION|cut -d : -f 2) -export OPRNAMESPACE=oracle-database-operator-system -export ORACLE_OPERATOR_YAML=../../../../oracle-database-operator.yaml -export TEST_EXEC_TIMEOUT=3m -export IMAGE=oracle/ords-dboper:latest -export ORDSIMGDIR=../../../../ords - -REST_SERVER=ords -SKEY=tls.key -SCRT=tls.crt -CART=ca.crt -PRVKEY=ca.key -PUBKEY=public.pem -COMPANY=oracle -RUNTIME=/usr/bin/podman - -################# -### FILE LIST ### -################# - -export ORDS_POD=create_ords_pod.yaml - -export CDB_SECRETS=create_cdb_secrets.yaml -export PDB_SECRETS=create_pdb_secrets.yaml - -export PDBCRE1=create_pdb1_resource.yaml -export PDBCRE2=create_pdb2_resource.yaml - -export PDBCLOSE1=close_pdb1_resource.yaml -export PDBCLOSE2=close_pdb2_resource.yaml -export PDBCLOSE3=close_pdb3_resource.yaml - -export PDBOPEN1=open_pdb1_resource.yaml -export PDBOPEN2=open_pdb2_resource.yaml -export PDBOPEN3=open_pdb3_resource.yaml - -export PDBCLONE1=clone_pdb1_resource.yaml -export PDBCLONE2=clone_pdb2_resource.yaml - -export PDBDELETE1=delete_pdb1_resource.yaml -export PDBDELETE2=delete_pdb2_resource.yaml -export PDBDELETE3=delete_pdb3_resource.yaml - -export PDBUNPLUG1=unplug_pdb1_resource.yaml -export PDBPLUG1=plug_pdb1_resource.yaml - -export PDBMAP1=map_pdb1_resource.yaml -export PDBMAP2=map_pdb2_resource.yaml -export PDBMAP3=map_pdb3_resource.yaml - -export PDBMAP1=map_pdb1_resource.yaml -export PDBMAP2=map_pdb2_resource.yaml -export PDBMAP3=map_pdb3_resource.yaml - - -##BINARIES -export KUBECTL=/usr/bin/kubectl -OPENSSL=/usr/bin/openssl -ECHO=/usr/bin/echo -RM=/usr/bin/rm -CP=/usr/bin/cp -TAR=/usr/bin/tar -MKDIR=/usr/bin/mkdir -SED=/usr/bin/sed - -define msg -@printf "\033[31;7m%s\033[0m\r" "......................................]" -@printf "\033[31;7m[\xF0\x9F\x91\x89 %s\033[0m\n" $(1) -endef - -check: - $(call msg,"CHECK PARAMETERS") - @printf "TNSALIAS...............:%.60s....\n" $(TNSALIAS) - @printf "ORDPWD.................:%s\n" $(ORDPWD) - @printf "SYSPWD.................:%s\n" $(SYSPWD) - @printf "WBUSER.................:%s\n" $(WBUSER) - @printf "WBPASS.................:%s\n" $(WBPASS) - @printf "PDBUSR.................:%s\n" $(PDBUSR) - @printf "PDBPWD.................:%s\n" $(PDBPWD) - @printf "CDBUSR.................:%s\n" $(CDBUSR) - @printf "CDBPWD.................:%s\n" $(CDBPWD) - @printf "OPRNAMESPACE...........:%s\n" $(OPRNAMESPACE) - @printf "COMPANY................:%s\n" $(COMPANY) - @printf "APIVERSION.............:%s\n" $(APIVERSION) - - -tlscrt: - $(call msg,"TLS GENERATION") - #$(OPENSSL) genrsa -out $(PRVKEY) 2048 - $(OPENSSL) genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_keygen_pubexp:65537 > $(PRVKEY) - $(OPENSSL) req -new -x509 -days 365 -key $(PRVKEY) \ - -subj "/C=CN/ST=GD/L=SZ/O=$(COMPANY), Inc./CN=$(COMPANY) Root CA" -out ca.crt - $(OPENSSL) req -newkey rsa:2048 -nodes -keyout $(SKEY) -subj \ - "/C=CN/ST=GD/L=SZ/O=$(COMPANY), Inc./CN=cdb-dev-$(REST_SERVER).$(OPRNAMESPACE)" -out server.csr - $(ECHO) "subjectAltName=DNS:cdb-dev-$(REST_SERVER).$(OPRNAMESPACE)" > extfile.txt - $(OPENSSL) x509 -req -extfile extfile.txt -days 365 -in server.csr -CA ca.crt -CAkey $(PRVKEY) -CAcreateserial -out $(SCRT) - $(OPENSSL) rsa -in $(PRVKEY) -outform PEM -pubout -out $(PUBKEY) - -tlssec: - $(call msg,"GENERATE TLS SECRET") - $(KUBECTL) create secret tls db-tls --key="$(SKEY)" --cert="$(SCRT)" -n $(OPRNAMESPACE) - $(KUBECTL) create secret generic db-ca --from-file="$(CART)" -n $(OPRNAMESPACE) - - -delsec: - $(call msg,"CLEAN OLD SECRETS") - $(eval SECRETSP:=$(shell kubectl get secrets -n $(OPRNAMESPACE) -o custom-columns=":metadata.name" --no-headers|grep -v webhook-server-cert) ) - @[ "${SECRETSP}" ] && ( \ - printf "Deleteing secrets in namespace -n $(OPRNAMESPACE)\n") &&\ - ($(KUBECTL) delete secret $(SECRETSP) -n $(OPRNAMESPACE))\ - || ( echo "No screts in namespace $(OPRNAMESPACE)") - - -###### ENCRYPTED SECRETS ###### -export PRVKEY=ca.key -export PUBKEY=public.pem -WBUSERFILE=wbuser.txt -WBPASSFILE=wbpass.txt -CDBUSRFILE=cdbusr.txt -CDBPWDFILE=cdbpwd.txt -SYSPWDFILE=syspwd.txt -ORDPWDFILE=ordpwd.txt -PDBUSRFILE=pdbusr.txt -PDBPWDFILE=pdbpwd.txt - - - -secrets: delsec tlscrt tlssec - $(OPENSSL) rsa -in $(PRVKEY) -outform PEM -pubout -out $(PUBKEY) - $(KUBECTL) create secret generic pubkey --from-file=publicKey=$(PUBKEY) -n $(OPRNAMESPACE) - $(KUBECTL) create secret generic prvkey --from-file=privateKey=$(PRVKEY) -n $(OPRNAMESPACE) - @$(ECHO) $(WBUSER) > $(WBUSERFILE) - @$(ECHO) $(WBPASS) > $(WBPASSFILE) - @$(ECHO) $(CDBPWD) > $(CDBPWDFILE) - @$(ECHO) $(CDBUSR) > $(CDBUSRFILE) - @$(ECHO) $(SYSPWD) > $(SYSPWDFILE) - @$(ECHO) $(ORDPWD) > $(ORDPWDFILE) - @$(ECHO) $(PDBUSR) > $(PDBUSRFILE) - @$(ECHO) $(PDBPWD) > $(PDBPWDFILE) - $(OPENSSL) rsautl -encrypt -pubin -inkey $(PUBKEY) -in $(WBUSERFILE) |base64 > e_$(WBUSERFILE) - $(OPENSSL) rsautl -encrypt -pubin -inkey $(PUBKEY) -in $(WBPASSFILE) |base64 > e_$(WBPASSFILE) - $(OPENSSL) rsautl -encrypt -pubin -inkey $(PUBKEY) -in $(CDBPWDFILE) |base64 > e_$(CDBPWDFILE) - $(OPENSSL) rsautl -encrypt -pubin -inkey $(PUBKEY) -in $(CDBUSRFILE) |base64 > e_$(CDBUSRFILE) - $(OPENSSL) rsautl -encrypt -pubin -inkey $(PUBKEY) -in $(SYSPWDFILE) |base64 > e_$(SYSPWDFILE) - $(OPENSSL) rsautl -encrypt -pubin -inkey $(PUBKEY) -in $(ORDPWDFILE) |base64 > e_$(ORDPWDFILE) - $(OPENSSL) rsautl -encrypt -pubin -inkey $(PUBKEY) -in $(PDBUSRFILE) |base64 > e_$(PDBUSRFILE) - $(OPENSSL) rsautl -encrypt -pubin -inkey $(PUBKEY) -in $(PDBPWDFILE) |base64 > e_$(PDBPWDFILE) - $(KUBECTL) create secret generic wbuser --from-file=e_$(WBUSERFILE) -n $(OPRNAMESPACE) - $(KUBECTL) create secret generic wbpass --from-file=e_$(WBPASSFILE) -n $(OPRNAMESPACE) - $(KUBECTL) create secret generic cdbpwd --from-file=e_$(CDBPWDFILE) -n $(OPRNAMESPACE) - $(KUBECTL) create secret generic cdbusr --from-file=e_$(CDBUSRFILE) -n $(OPRNAMESPACE) - $(KUBECTL) create secret generic syspwd --from-file=e_$(SYSPWDFILE) -n $(OPRNAMESPACE) - $(KUBECTL) create secret generic ordpwd --from-file=e_$(ORDPWDFILE) -n $(OPRNAMESPACE) - $(KUBECTL) create secret generic pdbusr --from-file=e_$(PDBUSRFILE) -n $(OPRNAMESPACE) - $(KUBECTL) create secret generic pdbpwd --from-file=e_$(PDBPWDFILE) -n $(OPRNAMESPACE) - $(RM) $(WBUSERFILE) $(WBPASSFILE) $(CDBPWDFILE) $(CDBUSRFILE) $(SYSPWDFILE) $(ORDPWDFILE) $(PDBUSRFILE) $(PDBPWDFILE) - $(RM) e_$(WBUSERFILE) e_$(WBPASSFILE) e_$(CDBPWDFILE) e_$(CDBUSRFILE) e_$(SYSPWDFILE) e_$(ORDPWDFILE) e_$(PDBUSRFILE) e_$(PDBPWDFILE) - - -### YAML FILE SECTION ### -operator: - $(CP) ${ORACLE_OPERATOR_YAML} . - ${CP} `basename ${ORACLE_OPERATOR_YAML}` `basename ${ORACLE_OPERATOR_YAML}`.ORG - $(SED) -i 's/value: ""/value: $(OPRNAMESPACE)/g' `basename ${ORACLE_OPERATOR_YAML}` - - -define _script00 -cat < authsection01.yaml - sysAdminPwd: - secret: - secretName: "syspwd" - key: "e_syspwd.txt" - ordsPwd: - secret: - secretName: "ordpwd" - key: "e_ordpwd.txt" - cdbAdminUser: - secret: - secretName: "cdbusr" - key: "e_cdbusr.txt" - cdbAdminPwd: - secret: - secretName: "cdbpwd" - key: "e_cdbpwd.txt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - cdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - cdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - cdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" -EOF - -cat< authsection02.yaml - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" -EOF - - -cat < ${OPRNAMESPACE}_binding.yaml -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: oracle-database-operator-oracle-database-operator-manager-rolebinding1 - namespace: ${OPRNAMESPACE} -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: oracle-database-operator-manager-role -subjects: -- kind: ServiceAccount - name: default - namespace: oracle-database-operator-system -EOF - -cat < ${OPRNAMESPACE}_binding.yaml -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: oracle-database-operator-oracle-database-operator-manager-rolebinding2 - namespace: ${OPRNAMESPACE} -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: oracle-database-operator-manager-role -subjects: -- kind: ServiceAccount - name: default - namespace: oracle-database-operator-system -EOF - -endef -export script00 = $(value _script00) -secyaml: - @ eval "$$script00" - -#echo ords pod creation -define _script01 -cat < ${ORDS_POD} -apiVersion: database.oracle.com/${APIVERSION} -kind: CDB -metadata: - name: cdb-dev - namespace: oracle-database-operator-system -spec: - cdbName: "DB12" - ordsImage: ${ORDSIMG} - ordsImagePullPolicy: "Always" - dbTnsurl : ${TNSALIAS} - replicas: 1 - deletePdbCascade: true -EOF - -cat authsection01.yaml >> ${ORDS_POD} - -endef -export script01 = $(value _script01) - - -define _script02 - -cat <${PDBCRE1} -apiVersion: database.oracle.com/${APIVERSION} -kind: PDB -metadata: - name: pdb1 - namespace: ${OPRNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${OPRNAMESPACE}" - cdbName: "DB12" - pdbName: "pdbdev" - assertivePdbDeletion: true - fileNameConversions: "NONE" - unlimitedStorage: false - tdeImport: false - totalSize: "2G" - tempSize: "800M" - action: "Create" -EOF - -cat < ${PDBCRE2} -apiVersion: database.oracle.com/${APIVERSION} -kind: PDB -metadata: - name: pdb2 - namespace: ${OPRNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${OPRNAMESPACE}" - cdbName: "DB12" - pdbName: "pdbprd" - assertivePdbDeletion: true - fileNameConversions: "NONE" - unlimitedStorage: false - tdeImport: false - totalSize: "2G" - tempSize: "800M" - action: "Create" -EOF - -cat <${PDBOPEN1} -apiVersion: database.oracle.com/${APIVERSION} -kind: PDB -metadata: - name: pdb1 - namespace: ${OPRNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${OPRNAMESPACE}" - cdbName: "DB12" - pdbName: "pdbdev" - action: "Modify" - pdbState: "OPEN" - modifyOption: "READ WRITE" -EOF - -cat <${PDBOPEN2} -apiVersion: database.oracle.com/${APIVERSION} -kind: PDB -metadata: - name: pdb2 - namespace: ${OPRNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${OPRNAMESPACE}" - cdbName: "DB12" - pdbName: "pdbprd" - action: "Modify" - pdbState: "OPEN" - modifyOption: "READ WRITE" -EOF - -cat <${PDBOPEN3} -apiVersion: database.oracle.com/${APIVERSION} -kind: PDB -metadata: - name: pdb3 - namespace: ${OPRNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${OPRNAMESPACE}" - cdbName: "DB12" - pdbName: "new_clone" - action: "Modify" - pdbState: "OPEN" - modifyOption: "READ WRITE" -EOF - -cat <${PDBCLOSE1} -apiVersion: database.oracle.com/${APIVERSION} -kind: PDB -metadata: - name: pdb1 - namespace: ${OPRNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${OPRNAMESPACE}" - cdbName: "DB12" - pdbName: "pdbdev" - pdbState: "CLOSE" - modifyOption: "IMMEDIATE" - action: "Modify" -EOF - -cat <${PDBCLOSE2} -apiVersion: database.oracle.com/${APIVERSION} -kind: PDB -metadata: - name: pdb2 - namespace: ${OPRNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${OPRNAMESPACE}" - cdbName: "DB12" - pdbName: "pdbprd" - pdbState: "CLOSE" - modifyOption: "IMMEDIATE" - action: "Modify" -EOF - -cat <${PDBCLOSE3} -apiVersion: database.oracle.com/${APIVERSION} -kind: PDB -metadata: - name: pdb3 - namespace: ${OPRNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${OPRNAMESPACE}" - cdbName: "DB12" - pdbName: ""new_clone" - pdbState: "CLOSE" - modifyOption: "IMMEDIATE" - action: "Modify" -EOF - -cat < ${PDBCLONE1} -apiVersion: database.oracle.com/${APIVERSION} -kind: PDB -metadata: - name: pdb3 - namespace: ${OPRNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${OPRNAMESPACE}" - cdbName: "DB12" - pdbName: "new_clone" - srcPdbName: "pdbdev" - fileNameConversions: "NONE" - totalSize: "UNLIMITED" - tempSize: "UNLIMITED" - assertivePdbDeletion: true - action: "Clone" -EOF - -cat < ${PDBCLONE2} -apiVersion: database.oracle.com/${APIVERSION} -kind: PDB -metadata: - name: pdb4 - namespace: ${OPRNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${OPRNAMESPACE}" - cdbName: "DB12" - pdbName: "new_clone2" - srcPdbName: "pdbprd" - fileNameConversions: "NONE" - totalSize: "UNLIMITED" - tempSize: "UNLIMITED" - assertivePdbDeletion: true - action: "Clone" -EOF - - -cat < ${PDBDELETE1} -apiVersion: database.oracle.com/${APIVERSION} -kind: PDB -metadata: - name: pdb1 - namespace: ${OPRNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${OPRNAMESPACE}" - pdbName: "pdbdev" - action: "Delete" - dropAction: "INCLUDING" -EOF - -cat < ${PDBDELETE2} -apiVersion: database.oracle.com/${APIVERSION} -kind: PDB -metadata: - name: pdb2 - namespace: ${OPRNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${OPRNAMESPACE}" - pdbName: "pdbprd" - action: "Delete" - dropAction: "INCLUDING" -EOF - -cat < ${PDBUNPLUG1} -apiVersion: database.oracle.com/${APIVERSION} -kind: PDB -metadata: - name: pdb1 - namespace: ${OPRNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${OPRNAMESPACE}" - cdbName: "DB12" - pdbName: "pdbdev" - xmlFileName: "/tmp/pdb.xml" - action: "Unplug" -EOF - -cat <${PDBPLUG1} -apiVersion: database.oracle.com/${APIVERSION} -kind: PDB -metadata: - name: pdb1 - namespace: ${OPRNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${OPRNAMESPACE}" - cdbName: "DB12" - pdbName: "pdbdev" - xmlFileName: "/tmp/pdb.xml" - action: "plug" - fileNameConversions: "NONE" - sourceFileNameConversions: "NONE" - copyAction: "MOVE" - totalSize: "1G" - tempSize: "100M" - assertivePdbDeletion: true - action: "Plug" -EOF - -cat <${PDBMAP1} -apiVersion: database.oracle.com/${APIVERSION} -kind: PDB -metadata: - name: pdb1 - namespace: ${OPRNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${OPRNAMESPACE}" - cdbName: "DB12" - pdbName: "pdbdev" - assertivePdbDeletion: true - fileNameConversions: "NONE" - totalSize: "1G" - tempSize: "100M" - action: "Map" -EOF - -cat <${PDBMAP2} -apiVersion: database.oracle.com/${APIVERSION} -kind: PDB -metadata: - name: pdb2 - namespace: ${OPRNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${OPRNAMESPACE}" - cdbName: "DB12" - pdbName: "pdbprd" - assertivePdbDeletion: true - fileNameConversions: "NONE" - totalSize: "1G" - tempSize: "100M" - action: "Map" -EOF - - -cat <${PDBMAP3} -apiVersion: database.oracle.com/${APIVERSION} -kind: PDB -metadata: - name: pdb3 - namespace: ${OPRNAMESPACE} - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "${OPRNAMESPACE}" - cdbName: "DB12" - pdbName: "new_clone" - assertivePdbDeletion: true - fileNameConversions: "NONE" - totalSize: "1G" - tempSize: "100M" - action: "Map" -EOF - - -## Auth information -for _file in ${PDBCRE1} ${PDBCRE2} ${PDBOPEN1} ${PDBOPEN2} ${PDBOPEN3} ${PDBCLOSE1} ${PDBCLOSE2} ${PDBCLOSE3} ${PDBCLONE1} ${PDBCLONE2} ${PDBDELETE1} ${PDBDELETE2} ${PDBUNPLUG1} ${PDBPLUG1} ${PDBMAP1} ${PDBMAP2} ${PDBMAP3} -do -ls -ltr ${_file} - cat authsection02.yaml >> ${_file} -done -rm authsection02.yaml -rm authsection01.yaml -endef - -export script02 = $(value _script02) - -genyaml: secyaml - @ eval "$$script01" - @ eval "$$script02" - -cleanyaml: - - $(RM) $(PDBMAP3) $(PDBMAP2) $(PDBMAP1) $(PDBPLUG1) $(PDBUNPLUG1) $(PDBDELETE2) $(PDBDELETE1) $(PDBCLONE2) $(PDBCLONE1) $(PDBCLOSE3) $(PDBCLOSE2) $(PDBCLOSE1) $(PDBOPEN3) $(PDBOPEN2) $(PDBOPEN1) $(PDBCRE2) $(PDBCRE1) $(ORDS_POD) $(CDB_SECRETS) $(PDB_SECRETS) - - $(RM) ${OPRNAMESPACE}_binding.yaml ${OPRNAMESPACE}_binding.yaml - - -cleancrt: - - $(RM) $(SKEY) $(SCRT) $(CART) $(PRVKEY) $(PUBKEY) server.csr extfile.txt ca.srl - - -################# -### PACKAGING ### -################# - -pkg: - - $(RM) -rf /tmp/pkgtestplan - $(MKDIR) /tmp/pkgtestplan - $(CP) -R * /tmp/pkgtestplan - $(CP) ../../../../oracle-database-operator.yaml /tmp/pkgtestplan/ - $(TAR) -C /tmp -cvf ~/pkgtestplan_$(DATE).tar pkgtestplan - -################ -### diag ### -################ - -login: - $(KUBECTL) exec `$(KUBECTL) get pods -n $(OPRNAMESPACE)|grep ords|cut -d ' ' -f 1` -n $(OPRNAMESPACE) -it -- /bin/bash - - -reloadop: - echo "RESTARTING OPERATOR" - $(eval OP1 := $(shell $(KUBECTL) get pods -n $(OPRNAMESPACE)|grep oracle-database-operator-controller|head -1|cut -d ' ' -f 1 )) - $(eval OP2 := $(shell $(KUBECTL) get pods -n $(OPRNAMESPACE)|grep oracle-database-operator-controller|head -2|tail -1|cut -d ' ' -f 1 )) - $(eval OP3 := $(shell $(KUBECTL) get pods -n $(OPRNAMESPACE)|grep oracle-database-operator-controller|tail -1|cut -d ' ' -f 1 )) - $(KUBECTL) get pod $(OP1) -n $(OPRNAMESPACE) -o yaml | kubectl replace --force -f - - $(KUBECTL) get pod $(OP2) -n $(OPRNAMESPACE) -o yaml | kubectl replace --force -f - - $(KUBECTL) get pod $(OP3) -n $(OPRNAMESPACE) -o yaml | kubectl replace --force -f - - - -dump: - @$(eval TMPSP := $(shell date "+%y%m%d%H%M%S" )) - @$(eval DIAGFILE := ./opdmp.$(TMPSP)) - @>$(DIAGFILE) - @echo "OPERATOR DUMP" >> $(DIAGFILE) - @echo "~~~~~~~~~~~~~" >> $(DIAGFILE) - $(KUBECTL) logs pod/`$(KUBECTL) get pods -n $(OPRNAMESPACE)|grep oracle-database-operator-controller|head -1|cut -d ' ' -f 1` -n $(OPRNAMESPACE) >>$(DIAGFILE) - $(KUBECTL) logs pod/`$(KUBECTL) get pods -n $(OPRNAMESPACE)|grep oracle-database-operator-controller|head -2|tail -1 | cut -d ' ' -f 1` -n $(OPRNAMESPACE) >>$(DIAGFILE) - $(KUBECTL) logs pod/`$(KUBECTL) get pods -n $(OPRNAMESPACE)|grep oracle-database-operator-controller|tail -1|cut -d ' ' -f 1` -n $(OPRNAMESPACE) >>$(DIAGFILE) - -####################################################### -#### TEST SECTION #### -####################################################### - -run00: - @$(call msg,"cdb pod creation") - - $(KUBECTL) delete cdb cdb-dev -n $(OPRNAMESPACE) - $(KUBECTL) apply -f $(ORDS_POD) - time $(KUBECTL) wait --for jsonpath='{.status.phase'}="Ready" cdb cdb-dev -n $(OPRNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - @$(call msg,"cdb pod completed") - $(KUBECTL) get cdb -n $(OPRNAMESPACE) - $(KUBECTL) get pod -n $(OPRNAMESPACE) - -run01.1: - @$(call msg,"pdb pdb1 creation") - $(KUBECTL) apply -f $(PDBCRE1) - time $(KUBECTL) wait --for jsonpath='{.status.phase'}="Ready" pdb pdb1 -n $(OPRNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - @$(call msg, "pdb pdb1 creation completed") - $(KUBECTL) get pdb pdb1 -n $(OPRNAMESPACE) - -run01.2: - @$(call msg, "pdb pdb2 creation") - $(KUBECTL) apply -f $(PDBCRE2) - $(KUBECTL) wait --for jsonpath='{.status.phase'}="Ready" pdb pdb2 -n $(OPRNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - @$(call msg, "pdb pdb2 creation completed") - $(KUBECTL) get pdb pdb2 -n $(OPRNAMESPACE) - -run02.1: - @$(call msg, "pdb pdb1 open") - $(KUBECTL) apply -f $(PDBOPEN1) - $(KUBECTL) wait --for jsonpath='{.status.openMode'}="READ WRITE" pdb pdb1 -n $(OPRNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - @$(call msg, "pdb pdb1 open completed") - $(KUBECTL) get pdb pdb1 -n $(OPRNAMESPACE) - -run02.2: - @$(call msg,"pdb pdb2 open") - $(KUBECTL) apply -f $(PDBOPEN2) - $(KUBECTL) wait --for jsonpath='{.status.openMode'}="READ WRITE" pdb pdb2 -n $(OPRNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - @$(call msg,"pdb pdb2 open completed") - $(KUBECTL) get pdb pdb2 -n $(OPRNAMESPACE) - - -run03.1: - @$(call msg,"clone pdb1-->pdb3") - $(KUBECTL) apply -f $(PDBCLONE1) - $(KUBECTL) wait --for jsonpath='{.status.phase'}="Ready" pdb pdb3 -n $(OPRNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - @$(call msg,"clone pdb1-->pdb3 completed") - $(KUBECTL) get pdb pdb3 -n $(OPRNAMESPACE) - - -run03.2: - @$(call msg,"clone pdb2-->pdb4") - $(KUBECTL) apply -f $(PDBCLONE2) - $(KUBECTL) wait --for jsonpath='{.status.phase'}="Ready" pdb pdb4 -n $(OPRNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - @$(call msg,"clone pdb2-->pdb4 completed") - $(KUBECTL) get pdb pdb3 -n $(OPRNAMESPACE) - - -run04.1: - @$(call msg,"pdb pdb1 close") - $(KUBECTL) apply -f $(PDBCLOSE1) - $(KUBECTL) wait --for jsonpath='{.status.openMode'}="MOUNTED" pdb pdb1 -n $(OPRNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - @$(call msg, "pdb pdb1 close completed") - $(KUBECTL) get pdb pdb1 -n $(OPRNAMESPACE) - -run04.2: - @$(call msg,"pdb pdb2 close") - $(KUBECTL) apply -f $(PDBCLOSE2) - $(KUBECTL) wait --for jsonpath='{.status.openMode'}="MOUNTED" pdb pdb2 -n $(OPRNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - @$(call msg,"pdb pdb2 close completed") - $(KUBECTL) get pdb pdb2 -n $(OPRNAMESPACE) - -run05.1: - @$(call msg,"pdb pdb1 unplug") - $(KUBECTL) apply -f $(PDBUNPLUG1) - $(KUBECTL) wait --for=delete pdb pdb1 -n $(OPRNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - @$(call msg,"pdb pdb1 unplug completed") - -run06.1: - @$(call msg, "pdb pdb1 plug") - $(KUBECTL) apply -f $(PDBPLUG1) - $(KUBECTL) wait --for jsonpath='{.status.phase'}="Ready" pdb pdb1 -n $(OPRNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - @$(call msg, "pdb pdb1 plug completed") - $(KUBECTL) get pdb pdb1 -n $(OPRNAMESPACE) - -run07.1: - @$(call msg,"pdb pdb1 delete ") - - $(KUBECTL) apply -f $(PDBCLOSE1) - $(KUBECTL) wait --for jsonpath='{.status.openMode'}="MOUNTED" pdb pdb1 -n $(OPRNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - $(KUBECTL) apply -f $(PDBDELETE1) - $(KUBECTL) wait --for=delete pdb pdb1 -n $(OPRNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - @$(call msg,"pdb pdb1 delete") - $(KUBECTL) get pdb -n $(OPRNAMESPACE) - -run99.1: - $(KUBECTL) delete cdb cdb-dev -n cdbnamespace - $(KUBECTL) wait --for=delete cdb cdb-dev -n $(OPRNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) - $(KUBECTL) get cdb -n cdbnamespaace - $(KUBECTL) get pdb -n pdbnamespaace - - -## SEQ | ACTION -## ----+---------------- -## 00 | create ords pod -## 01 | create pdb -## 02 | open pdb -## 03 | clone pdb -## 04 | close pdb -## 05 | unpug pdb -## 06 | plug pdb -## 07 | delete pdb (declarative) - - -runall01: run00 run01.1 run01.2 run03.1 run03.2 run04.1 run05.1 run06.1 run02.1 run07.1 - - -###### BUILD ORDS IMAGE ###### - -createimage: - $(RUNTIME) build -t $(IMAGE) $(ORDSIMGDIR) - -createimageproxy: - $(RUNTIME) build -t $(IMAGE) $(ORDSIMGDIR) --build-arg https_proxy=$(HTTPS_PROXY) --build-arg http_proxy=$(HTTP_PROXY) - -tagimage: - @echo "TAG IMAGE" - $(RUNTIME) tag $(IMAGE) $(ORDSIMG) - -push: - $(RUNTIME) push $(ORDSIMG) - - diff --git a/docs/multitenant/ords-based/usecase01/map_pdb1_resource.yaml b/docs/multitenant/ords-based/usecase01/map_pdb1_resource.yaml deleted file mode 100644 index 18cb35b1..00000000 --- a/docs/multitenant/ords-based/usecase01/map_pdb1_resource.yaml +++ /dev/null @@ -1,49 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb1 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "oracle-database-operator-system" - cdbName: "DB12" - pdbName: "pdbdev" - assertivePdbDeletion: true - fileNameConversions: "NONE" - totalSize: "1G" - tempSize: "100M" - action: "Map" - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase01/map_pdb2_resource.yaml b/docs/multitenant/ords-based/usecase01/map_pdb2_resource.yaml deleted file mode 100644 index 85899597..00000000 --- a/docs/multitenant/ords-based/usecase01/map_pdb2_resource.yaml +++ /dev/null @@ -1,49 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb2 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "oracle-database-operator-system" - cdbName: "DB12" - pdbName: "pdbprd" - assertivePdbDeletion: true - fileNameConversions: "NONE" - totalSize: "1G" - tempSize: "100M" - action: "Map" - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase01/map_pdb3_resource.yaml b/docs/multitenant/ords-based/usecase01/map_pdb3_resource.yaml deleted file mode 100644 index 9c2c1cd3..00000000 --- a/docs/multitenant/ords-based/usecase01/map_pdb3_resource.yaml +++ /dev/null @@ -1,49 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb3 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "oracle-database-operator-system" - cdbName: "DB12" - pdbName: "new_clone" - assertivePdbDeletion: true - fileNameConversions: "NONE" - totalSize: "1G" - tempSize: "100M" - action: "Map" - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase01/open_pdb1_resource.yaml b/docs/multitenant/ords-based/usecase01/open_pdb1_resource.yaml deleted file mode 100644 index 63a0a49c..00000000 --- a/docs/multitenant/ords-based/usecase01/open_pdb1_resource.yaml +++ /dev/null @@ -1,47 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb1 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "oracle-database-operator-system" - cdbName: "DB12" - pdbName: "pdbdev" - action: "Modify" - pdbState: "OPEN" - modifyOption: "READ WRITE" - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase01/open_pdb2_resource.yaml b/docs/multitenant/ords-based/usecase01/open_pdb2_resource.yaml deleted file mode 100644 index 8c4eed0d..00000000 --- a/docs/multitenant/ords-based/usecase01/open_pdb2_resource.yaml +++ /dev/null @@ -1,47 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb2 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "oracle-database-operator-system" - cdbName: "DB12" - pdbName: "pdbprd" - action: "Modify" - pdbState: "OPEN" - modifyOption: "READ WRITE" - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase01/open_pdb3_resource.yaml b/docs/multitenant/ords-based/usecase01/open_pdb3_resource.yaml deleted file mode 100644 index 5f0e4b77..00000000 --- a/docs/multitenant/ords-based/usecase01/open_pdb3_resource.yaml +++ /dev/null @@ -1,47 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb3 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "oracle-database-operator-system" - cdbName: "DB12" - pdbName: "new_clone" - action: "Modify" - pdbState: "OPEN" - modifyOption: "READ WRITE" - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase01/oracle-database-operator-system_binding.yaml b/docs/multitenant/ords-based/usecase01/oracle-database-operator-system_binding.yaml deleted file mode 100644 index 79e44269..00000000 --- a/docs/multitenant/ords-based/usecase01/oracle-database-operator-system_binding.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: oracle-database-operator-oracle-database-operator-manager-rolebinding2 - namespace: oracle-database-operator-system -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: oracle-database-operator-manager-role -subjects: -- kind: ServiceAccount - name: default - namespace: oracle-database-operator-system diff --git a/docs/multitenant/ords-based/usecase01/oracle-database-operator.yaml b/docs/multitenant/ords-based/usecase01/oracle-database-operator.yaml deleted file mode 120000 index d5bae7bc..00000000 --- a/docs/multitenant/ords-based/usecase01/oracle-database-operator.yaml +++ /dev/null @@ -1 +0,0 @@ -../../../oracle-database-operator.yaml \ No newline at end of file diff --git a/docs/multitenant/ords-based/usecase01/parameters.txt b/docs/multitenant/ords-based/usecase01/parameters.txt deleted file mode 100644 index 0a7b394a..00000000 --- a/docs/multitenant/ords-based/usecase01/parameters.txt +++ /dev/null @@ -1,61 +0,0 @@ - -######################## -## REST SERVER IMAGE ### -######################## - -ORDSIMG:_your_container_registry/ords-dboper:latest - -############################## -## TNS URL FOR CDB CREATION ## -############################## -TNSALIAS:"T H I S I S J U S T A N E X A M P L E ....(DESCRIPTION=(CONNECT_TIMEOUT=90)(RETRY_COUNT=30)(RETRY_DELAY=10)(TRANSPORT_CONNECT_TIMEOUT=70)(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL=TCP)(HOST=scan12.testrac.com)(PORT=1521)(IP=V4_ONLY))(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL=TCP)(HOST=scan34.testrac.com)(PORT=1521)(IP=V4_ONLY))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=TESTORDS)))" - -########################################### -## ORDS PUBLIC USER ## -########################################### -ORDPWD:Change_me_please - -########################################### -## SYSPASSWORD ## -########################################### -SYSPWD:Change_me_please - -####################### -## HTTPS CREDENTIAL ### -####################### - -WBUSER:Change_me_please -WBPASS:Change_me_please - -##################### -## PDB ADMIN USER ### -##################### - -PDBUSR:Change_me_please -PDBPWD:Change_me_please - -##################### -## CDB ADMIN USER ### -##################### - -CDBUSR:C##DBAPI_CDB_ADMIN -CDBPWD:Change_me_please - -################### -### NAMESPACES #### -################### - -PDBNAMESPACE:pdbnamespace -CDBNAMESPACE:cdbnamespace - -#################### -### COMPANY NAME ### -#################### - -COMPANY:oracle - -#################### -### APIVERSION ### -#################### - -APIVERSION:v4 diff --git a/docs/multitenant/ords-based/usecase01/pdb_close.yaml b/docs/multitenant/ords-based/usecase01/pdb_close.yaml deleted file mode 100644 index 5917d33a..00000000 --- a/docs/multitenant/ords-based/usecase01/pdb_close.yaml +++ /dev/null @@ -1,44 +0,0 @@ -apiVersion: database.oracle.com/v1alpha1 -kind: PDB -metadata: - name: pdb1 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "oracle-database-operator-system" - cdbName: "DB12" - pdbName: "pdbdev" - adminName: - secret: - secretName: "pdb1-secret" - key: "sysadmin_user" - adminPwd: - secret: - secretName: "pdb1-secret" - key: "sysadmin_pwd" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "pdb1-secret" - key: "webserver_user" - webServerPwd: - secret: - secretName: "pdb1-secret" - key: "webserver_pwd" - pdbState: "CLOSE" - modifyOption: "IMMEDIATE" - action: "Modify" - diff --git a/docs/multitenant/ords-based/usecase01/pdb_create.yaml b/docs/multitenant/ords-based/usecase01/pdb_create.yaml deleted file mode 100644 index be3581ad..00000000 --- a/docs/multitenant/ords-based/usecase01/pdb_create.yaml +++ /dev/null @@ -1,47 +0,0 @@ -apiVersion: database.oracle.com/v1alpha1 -kind: PDB -metadata: - name: pdb1 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "oracle-database-operator-system" - cdbName: "DB12" - pdbName: "pdbdev" - adminName: - secret: - secretName: "pdb1-secret" - key: "sysadmin_user" - adminPwd: - secret: - secretName: "pdb1-secret" - key: "sysadmin_pwd" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "pdb1-secret" - key: "webserver_user" - webServerPwd: - secret: - secretName: "pdb1-secret" - key: "webserver_pwd" - fileNameConversions: "NONE" - tdeImport: false - totalSize: "1G" - tempSize: "100M" - action: "Create" - assertivePdbDeletion: true - diff --git a/docs/multitenant/ords-based/usecase01/pdb_delete.yaml b/docs/multitenant/ords-based/usecase01/pdb_delete.yaml deleted file mode 100644 index c22b546a..00000000 --- a/docs/multitenant/ords-based/usecase01/pdb_delete.yaml +++ /dev/null @@ -1,34 +0,0 @@ -apiVersion: database.oracle.com/v1alpha1 -kind: PDB -metadata: - name: pdb1 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "oracle-database-operator-system" - pdbName: "pdbdev" - action: "Delete" - dropAction: "INCLUDING" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "pdb1-secret" - key: "webserver_user" - webServerPwd: - secret: - secretName: "pdb1-secret" - key: "webserver_pwd" - diff --git a/docs/multitenant/ords-based/usecase01/pdb_map.yaml b/docs/multitenant/ords-based/usecase01/pdb_map.yaml deleted file mode 100644 index 3300a7fa..00000000 --- a/docs/multitenant/ords-based/usecase01/pdb_map.yaml +++ /dev/null @@ -1,45 +0,0 @@ -apiVersion: database.oracle.com/v1alpha1 -kind: PDB -metadata: - name: pdb1 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "oracle-database-operator-system" - cdbName: "DB12" - pdbName: "pdbdev" - adminName: - secret: - secretName: "pdb1-secret" - key: "sysadmin_user" - adminPwd: - secret: - secretName: "pdb1-secret" - key: "sysadmin_pwd" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "pdb1-secret" - key: "webserver_user" - webServerPwd: - secret: - secretName: "pdb1-secret" - key: "webserver_pwd" - fileNameConversions: "NONE" - totalSize: "1G" - tempSize: "100M" - action: "Map" - assertivePdbDeletion: true diff --git a/docs/multitenant/ords-based/usecase01/pdb_open.yaml b/docs/multitenant/ords-based/usecase01/pdb_open.yaml deleted file mode 100644 index 25fdccc4..00000000 --- a/docs/multitenant/ords-based/usecase01/pdb_open.yaml +++ /dev/null @@ -1,43 +0,0 @@ -apiVersion: database.oracle.com/v1alpha1 -kind: PDB -metadata: - name: pdb1 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "oracle-database-operator-system" - cdbName: "DB12" - pdbName: "pdbdev" - adminName: - secret: - secretName: "pdb1-secret" - key: "sysadmin_user" - adminPwd: - secret: - secretName: "pdb1-secret" - key: "sysadmin_pwd" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "pdb1-secret" - key: "webserver_user" - webServerPwd: - secret: - secretName: "pdb1-secret" - key: "webserver_pwd" - action: "Modify" - pdbState: "OPEN" - modifyOption: "READ WRITE" diff --git a/docs/multitenant/ords-based/usecase01/pdb_secret.yaml b/docs/multitenant/ords-based/usecase01/pdb_secret.yaml deleted file mode 100644 index 60d95d76..00000000 --- a/docs/multitenant/ords-based/usecase01/pdb_secret.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# -# Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. -# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. -# -apiVersion: v1 -kind: Secret -metadata: - name: pdb1-secret - namespace: oracle-database-operator-system -type: Opaque -data: - sysadmin_user: ".....base64 encoded password...." - sysadmin_pwd: ".....base64 encoded password...." - webserver_user: ".....base64 encoded password...." - webserver_pwd: ".....base64 encoded password...." - diff --git a/docs/multitenant/ords-based/usecase01/plug_pdb1_resource.yaml b/docs/multitenant/ords-based/usecase01/plug_pdb1_resource.yaml deleted file mode 100644 index 0e86e10c..00000000 --- a/docs/multitenant/ords-based/usecase01/plug_pdb1_resource.yaml +++ /dev/null @@ -1,53 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb1 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "oracle-database-operator-system" - cdbName: "DB12" - pdbName: "pdbdev" - xmlFileName: "/tmp/pdb.xml" - action: "plug" - fileNameConversions: "NONE" - sourceFileNameConversions: "NONE" - copyAction: "MOVE" - totalSize: "1G" - tempSize: "100M" - assertivePdbDeletion: true - action: "Plug" - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase01/server.csr b/docs/multitenant/ords-based/usecase01/server.csr deleted file mode 100644 index e308d301..00000000 --- a/docs/multitenant/ords-based/usecase01/server.csr +++ /dev/null @@ -1,18 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIC3TCCAcUCAQAwgZcxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlh -MRUwEwYDVQQHDAxTYW5GcmFuY2lzY28xEDAOBgNVBAoMB29yYWNsZSAxNjA0BgNV -BAMMLWNkYi1kZXYtb3Jkcy5vcmFjbGUtZGF0YWJhc2Utb3BlcmF0b3Itc3lzdGVt -IDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB -CgKCAQEAm9nlNSQNsPTVqH57MkWKZEyaVtzVKQ8Z3oDK6hWXfB24p0jVj6sTOJkf -NVAxnqmU8DpW3odpbU6qWe/n+B5vJpqdXUGdsq9NKyus2fGb/xf1UnskpA2FUuWZ -o3upyCFxDAOvE4eZUzlxIn+54XXaNAdQiU9E8VXPr5YxrvZ15T/xCXLtJPs/RCOF -cJ8+gvZGcjMbdP16auJDVWZzBaur3eKbiHN7LXNCCRzGO++dv0kGY8vH7MyFfgp3 -qYBiSHS3WDiFUJjYIvfa8lLfP1hnlCyHn8TnU9gjGjmd1YcccSKqWIAT24wPUKVU -Lme4n91jxDPp7g8nRtDw0Smj9gYCtQIDAQABoAAwDQYJKoZIhvcNAQELBQADggEB -AGOG/9IJJRvT2JLcuzE5Arai1XHc6Jh65iuDRqXQav47Bz38FFF2gZNO69gzDmhq -6k7tie+5bPcAHuuJZ0dAa71a9SLjKl+XNkkI0vS6te6OK3DCVUoMqNCk5VdwrJw0 -RORbKUwgLEG6mu80Gc/6wCdeR/36hoYTMeNPjm6M9e+X5ppsXqxCNsgDxasJFT82 -FejuJE2sZ6RCradlDToUHNS1dMLoW0WAIISqOmrDvEI6snm9ZZr3Sxo1auEtpI6v -NllBM4AgEghy/2mAtke+By4WHCfXBpxEGv9S7ATqJHYrR5Qa3nwx0eojWW1vmn0/ -aEzslX1tAH6oz2jA6QZ0sNo= ------END CERTIFICATE REQUEST----- diff --git a/docs/multitenant/ords-based/usecase01/tde_secret.yaml b/docs/multitenant/ords-based/usecase01/tde_secret.yaml deleted file mode 100644 index 7cf66c03..00000000 --- a/docs/multitenant/ords-based/usecase01/tde_secret.yaml +++ /dev/null @@ -1,17 +0,0 @@ -# -# Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. -# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. -# -apiVersion: v1 -kind: Secret -metadata: - name: tde1-secret - namespace: oracle-database-operator-system -type: Opaque -data: - tdepassword: "bW1hbHZlenoK" - tdesecret: "bW1hbHZlenoK" - - - - diff --git a/docs/multitenant/ords-based/usecase01/tls.crt b/docs/multitenant/ords-based/usecase01/tls.crt deleted file mode 100644 index 6bf8aef4..00000000 --- a/docs/multitenant/ords-based/usecase01/tls.crt +++ /dev/null @@ -1,24 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEFDCCAvygAwIBAgIUd9l6tMS21ak3e4S0VdPhY0jG3gQwDQYJKoZIhvcNAQEL -BQAwgaExCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRUwEwYDVQQH -DAxTYW5GcmFuY2lzY28xEDAOBgNVBAoMB29yYWNsZSAxNjA0BgNVBAMMLWNkYi1k -ZXYtb3Jkcy5vcmFjbGUtZGF0YWJhc2Utb3BlcmF0b3Itc3lzdGVtIDEcMBoGA1UE -AwwTbG9jYWxob3N0ICBSb290IENBIDAeFw0yNDA4MTIxNTMyMzVaFw0yNTA4MTIx -NTMyMzVaMIGXMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMG -A1UEBwwMU2FuRnJhbmNpc2NvMRAwDgYDVQQKDAdvcmFjbGUgMTYwNAYDVQQDDC1j -ZGItZGV2LW9yZHMub3JhY2xlLWRhdGFiYXNlLW9wZXJhdG9yLXN5c3RlbSAxEjAQ -BgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB -AJvZ5TUkDbD01ah+ezJFimRMmlbc1SkPGd6AyuoVl3wduKdI1Y+rEziZHzVQMZ6p -lPA6Vt6HaW1Oqlnv5/gebyaanV1BnbKvTSsrrNnxm/8X9VJ7JKQNhVLlmaN7qcgh -cQwDrxOHmVM5cSJ/ueF12jQHUIlPRPFVz6+WMa72deU/8Qly7ST7P0QjhXCfPoL2 -RnIzG3T9emriQ1VmcwWrq93im4hzey1zQgkcxjvvnb9JBmPLx+zMhX4Kd6mAYkh0 -t1g4hVCY2CL32vJS3z9YZ5Qsh5/E51PYIxo5ndWHHHEiqliAE9uMD1ClVC5nuJ/d -Y8Qz6e4PJ0bQ8NEpo/YGArUCAwEAAaNMMEowSAYDVR0RBEEwP4IsY2RiLWRldi1v -cmRzLm9yYWNsZS1kYXRhYmFzZS1vcGVyYXRvci1zeXN0ZW2CD3d3dy5leGFtcGxl -LmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAh7Lsu2ITS6Bc2q/Ef4No5Us0Vo9BWKoL -AlrfQPjsv1erMGsyEEyZ0Cg8l3QrXlscQ1ESvx0BnRGjoqZGE4+PoVZTEYSkokXP -aAr69epPzXQRyyAGCg5GeL6IFAj1AzqJGNnKOrPaLpcTri4MboiWmW+MHmgLdyPK -iwl8bNa8841nK/L/m6QET15BI+MIAvn7pgcpztum5jmkB+eceXzXnKUGg77TaFiX -bXqVBR4EvexC4DgUfQJI4zJLFdcH/GHxCpaaXNjbXeVz1ZK/qo2TCrXp2UXVrznU -9VTUuCaQA2VYZCitvAbupt+1OvMFYhWiIAroJSmzrvH4oK+IXgY6GA== ------END CERTIFICATE----- diff --git a/docs/multitenant/ords-based/usecase01/tls.key b/docs/multitenant/ords-based/usecase01/tls.key deleted file mode 100644 index 666c5639..00000000 --- a/docs/multitenant/ords-based/usecase01/tls.key +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCb2eU1JA2w9NWo -fnsyRYpkTJpW3NUpDxnegMrqFZd8HbinSNWPqxM4mR81UDGeqZTwOlbeh2ltTqpZ -7+f4Hm8mmp1dQZ2yr00rK6zZ8Zv/F/VSeySkDYVS5Zmje6nIIXEMA68Th5lTOXEi -f7nhddo0B1CJT0TxVc+vljGu9nXlP/EJcu0k+z9EI4Vwnz6C9kZyMxt0/Xpq4kNV -ZnMFq6vd4puIc3stc0IJHMY7752/SQZjy8fszIV+CnepgGJIdLdYOIVQmNgi99ry -Ut8/WGeULIefxOdT2CMaOZ3VhxxxIqpYgBPbjA9QpVQuZ7if3WPEM+nuDydG0PDR -KaP2BgK1AgMBAAECggEAKUwl1l0FW7yk2Q8a6glPUKCTzSybN1QPEMyj+D9ccsEV -aw57uKQmZbr9cA0d+OMK2lU7K6BKKXLM5SQTHcZCwcH6rPl0JiMZmbTrCp1hLslU -clS7MtV6XKsGeTGNncBuyjY3sD8gO9NezTt3L+0gsuS1TI06wZBxhh+QbsJUHzjW -bC3mNjD4SqXree4Snp05nlFaT2s2isIjj25mKDwBu8IX0BN2VjsaSiQcjb8Dmzmu -42Xh7bcWBebns8Ehuq9TIl6ZjQht+pmVOMlB862baVpW/9CxkknzM+UQhIkXTSJk -Jt/mGeO89V4/Zh2N4ixIOE1hw87EvRFBoYh2VF58QQKBgQDMujXYblh+eEdsB1LG -kY0LerFHuQgdzifYmjPl0jtBsWDmh5i6q9PRUs2JZ/Fsq4QMQ8SLinGzaIBq5FKr -CL067X5blrFA9H0D6exJI3iHBTQpeMFwtqvu3j+zpCmgzonaUDQrczUpc0hxU7YI -/jhDe9LSWknPrzzMoWWKuy0sTQKBgQDC4g8F2krqm9Q5ug8bRKTAvMrY0skFIwrP -5LXBq9C8YCnLnT4S4tYQfbnWaBeG7YpkkmkZe30c9MUjsr1OHZbo+jlxHBU+oRYZ -e1j0UorVGt7FfNe/zjW0fLd72CBO741EDvV6pVeItkAwH6P5/cbRu085dwvyFbxv -JmOaYddECQKBgQCuid6YG1NE10SE3CV89uAZtktny18ZEgY0ixrNx5MPaaskPtw9 -4Xofjol+qOhR7lQQpMHu+WQAQYqiFvBHspapo4pDiVCrAQWIDamNnTkHW69h3/qD -HqmsZzxF6iI3X351akVf+cOMCCXtwCGEvz+2gN12ytT8w/iAuOS6BuP3TQKBgBlf -v57+diSn13EQtajSPjVOH4ctorjFgEHjQHsP+OSeDLMTLSLeYArTo9+zu+R4hz1j -BsYnmvmrMQPd4OIL3jtFYTdF9coqxSraMZHWMXdfwUOrZpf1rG5skqNQV5yPejAz -Vmj6oDQPrrnVVM9W6I0kO0N7KZYCmH9MW0mdlZ6pAoGAB60f2sk35VUBpvh7qzTY -70WDbNnCCU3I3KZ7LCUwUPWzGLQwMXRlAb5ZMheT/SGPChX4QXCNUCjXkR3Am3NO -yURHqZIRy0bwZRVjYnlCtc9YQ8pB0isZ1z2a9FXRD75o2WboFZ+VsG0FU81IE2ZO -gW802gT76NRnz851B7/nFNs= ------END PRIVATE KEY----- diff --git a/docs/multitenant/ords-based/usecase01/unplug_pdb1_resource.yaml b/docs/multitenant/ords-based/usecase01/unplug_pdb1_resource.yaml deleted file mode 100644 index 61fe915d..00000000 --- a/docs/multitenant/ords-based/usecase01/unplug_pdb1_resource.yaml +++ /dev/null @@ -1,46 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb1 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "oracle-database-operator-system" - cdbName: "DB12" - pdbName: "pdbdev" - xmlFileName: "/tmp/pdb.xml" - action: "Unplug" - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase02/README.md b/docs/multitenant/ords-based/usecase02/README.md deleted file mode 100644 index 39978747..00000000 --- a/docs/multitenant/ords-based/usecase02/README.md +++ /dev/null @@ -1,523 +0,0 @@ - - - -# UNPLUG - PLUG - CLONE - -- [UNPLUG - PLUG - CLONE](#unplug---plug---clone) - - [INTRODUCTION](#introduction) - - [UNPLUG DATABASE](#unplug-database) - - [PLUG DATABASE](#plug-database) - - [CLONE PDB](#clone-pdb) - -### INTRODUCTION - -> ☞ The examples of this folder are based on single namespace **oracle-database-operator-system** - -This page explains how to plug and unplug database a pdb; it assumes that you have already configured a pluggable database (see [usecase01](../usecase01/README.md)). Check yaml parameters in the CRD tables in the main [README](../README.md) file. - -```text - - - +--------------------------------+ - UNPLUG PDB PLUG PDB | CLONE PDB | - | | - +-----------+ +-----------+ | +-----------+ +----------+ | - | PDB | | PDB | | | PDB | |CLONED PDB| | - +----+------+ +----+------+ | +----+------+ +----------+ | - | | | | | | -+----> UNPLUG -----+ +--> PLUG | CLONE ---------+ | -| | | | | | | | -| +----+------+ | | +----+------+ | +----+------+ | -| | Container | | | | Container | | | Container | | -| | | | | | | | | | | -| +-----------+ | | +-----------+ | +-----------+ | -| | | | | -| +------+----+ | | kubectk apply -f pdb_clone.yaml| -| | | | | | -| +------|-----------|--------+ | +--------------------------------+ -| | +----+----+ +--+------+ | | -| | |xml file | |DB FILES | |--+ -| | +---------+ +---------+ | | -| +---------------------------+ | -| | -| | -+- kubectl apply -f pdb_unplug.yaml | - | - kubectl apply -f pdb_plug.yaml-----+ -``` - -### UNPLUG DATABASE - -Use the following command to check kubernets pdb resources. Note that the output of the commands can be tailored to meet your needs. Just check the structure of pdb resource **kubectl get pdbs -n oracle-database-operator-system -o=json** and modify the script accordingly. For the sake of simplicity put this command in a single script **checkpdbs.sh**. - -```bash -kubectl get pdbs -n oracle-database-operator-system -o=jsonpath='{range .items[*]} -{"\n==================================================================\n"} -{"CDB="}{.metadata.labels.cdb} -{"K8SNAME="}{.metadata.name} -{"PDBNAME="}{.spec.pdbName} -{"OPENMODE="}{.status.openMode} -{"ACTION="}{.status.action} -{"MSG="}{.status.msg} -{"\n"}{end}' -``` - -We assume that the pluggable database pdbdev is already configured and opened in read write mode - -```bash -./checkpdbs.sh -================================================================== -CDB=cdb-dev -K8SNAME=pdb1 -PDBNAME=pdbdev -OPENMODE=READ WRITE -ACTION=CREATE -MSG=Success - -``` - -Prepare a new yaml file **pdb_unplug.yaml** to unplug the pdbdev database. Make sure that the path of the xml file is correct and check the existence of all the required secrets. Do not reuse an existing xml files. - -```yaml -# Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. -# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. -# -#pdb_unplug.yaml -apiVersion: database.oracle.com/v1alpha1 -kind: PDB -metadata: - name: pdb1 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "oracle-database-operator-system" - cdbName: "DB12" - pdbName: "pdbdev" - xmlFileName: "/tmp/pdbunplug.xml" - action: "Unplug" - [ secret sections ] -``` - -Close the pluggable database by applying the following yaml file **pdb_close.yaml** - -```yaml -# Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. -# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. -# -#pdb_close.yaml -apiVersion: database.oracle.com/v1alpha1 -kind: PDB -metadata: - name: pdb1 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "oracle-database-operator-system" - cdbName: "DB12" - pdbName: "pdbdev" - pdbState: "CLOSE" - modifyOption: "IMMEDIATE" - action: "Modify" - [secret section] -``` - -```bash -kubectl apply -f pdb_close.yaml -pdb.database.oracle.com/pdb1 configured - -sh checkpdbs.sh -================================================================== -CDB=cdb-dev -K8SNAME=pdb1 -PDBNAME=pdbdev -OPENMODE=MOUNTED -ACTION=MODIFY -MSG=Success -``` -After that apply the unplug file **pdb_unplug.yaml** ; The resource is no longer available once the unplug operation is completed. - -```bash -kubectl apply -f pdb_unplug.yaml -pdb.database.oracle.com/pdb1 configured - -sh checkpdbs.sh -================================================================== -CDB=cdb-dev -K8SNAME=pdb1 -PDBNAME=pdbdev -OPENMODE=MOUNTED -ACTION=MODIFY -MSG=Waiting for PDB to be unplugged -``` - -Check kubernets log files and the database alert log - -```text -/usr/bin/kubectl logs -f pod/`/usr/bin/kubectl get pods -n oracle-database-operator-system|grep oracle-database-operator-controller|head -1|cut -d ' ' -f 1` -n oracle-database-operator-system -[...] -base-oracle-com-v1alpha1-pdb", "UID": "6f469423-85e5-4287-94d5-3d91a04b621e", "kind": "database.oracle.com/v1alpha1, Kind=PDB", "resource": {"group":"database.oracle.com","version":"v1alpha1","resource":"pdbs"}} -2023-01-03T14:04:05Z INFO pdb-webhook ValidateUpdate-Validating PDB spec for : pdb1 -2023-01-03T14:04:05Z INFO pdb-webhook validateCommon {"name": "pdb1"} -2023-01-03T14:04:05Z INFO pdb-webhook Valdiating PDB Resource Action : UNPLUG -2023-01-03T14:04:05Z DEBUG controller-runtime.webhook.webhooks wrote response {"webhook": "/validate-database-oracle-com-v1alpha1-pdb", "code": 200, "reason": "", "UID": "6f469423-85e5-4287-94d5-3d91a04b621e", "allowed": true} - - -[database alert log] -Domain Action Reconfiguration complete (total time 0.0 secs) -Completed: ALTER PLUGGABLE DATABASE "pdbdev" UNPLUG INTO '/tmp/pdbunplug.xml' -DROP PLUGGABLE DATABASE "pdbdev" KEEP DATAFILES -2023-01-03T14:04:05.518845+00:00 -Deleted Oracle managed file +DATA/DB12/F146D9482AA0260FE0531514000AB1BC/TEMPFILE/temp.266.1125061101 -2023-01-03T14:04:05.547820+00:00 -Stopped service pdbdev -Completed: DROP PLUGGABLE DATABASE "pdbdev" KEEP DATAFILES - -``` - - -login to the server and check xml file existence. Verify the datafile path on the ASM filesystem. - -```bash -ls -ltr /tmp/pdbunplug.xml --rw-r--r--. 1 oracle asmadmin 8007 Jan 3 14:04 /tmp/pdbunplug.xml -[..] -cat /tmp/pdbunplug.xml |grep path - +DATA/DB12/F146D9482AA0260FE0531514000AB1BC/DATAFILE/system.353.1125061021 - +DATA/DB12/F146D9482AA0260FE0531514000AB1BC/DATAFILE/sysaux.328.1125061021 - +DATA/DB12/F146D9482AA0260FE0531514000AB1BC/DATAFILE/undotbs1.347.1125061021 - +DATA/DB12/F146D9482AA0260FE0531514000AB1BC/TEMPFILE/temp.266.1125061101 - +DATA/DB12/F146D9482AA0260FE0531514000AB1BC/DATAFILE/undo_2.318.1125061021 -[..] -asmcmd ls -l +DATA/DB12/F146D9482AA0260FE0531514000AB1BC/DATAFILE/system.353.1125061021 -Type Redund Striped Time Sys Name -DATAFILE UNPROT COARSE JAN 03 14:00:00 Y system.353.1125061021 -``` - -### PLUG DATABASE - -Prepare a new yaml file **pdb_plug.yaml** to plug the database back into the container. - -```yaml -# Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. -# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. -# -# pdb_plug.yaml -apiVersion: database.oracle.com/v1alpha1 -kind: PDB -metadata: - name: pdb1 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "oracle-database-operator-system" - cdbName: "DB12" - pdbName: "pdbdev" - xmlFileName: "/tmp/pdbunplug.xml" - fileNameConversions: "NONE" - sourceFileNameConversions: "NONE" - copyAction: "MOVE" - totalSize: "1G" - tempSize: "100M" - action: "Plug" - [secrets section] -``` -Apply **pdb_plug.yaml** - -```bash -kubectl apply -f pdb_plug.yaml -[...] -sh checkpdbs.sh -================================================================== -CDB=cdb-dev -K8SNAME=pdb1 -PDBNAME=pdbdev -OPENMODE= -ACTION= -MSG=Waiting for PDB to be plugged -[...] -sh checkpdbs.sh -================================================================== -CDB=cdb-dev -K8SNAME=pdb1 -PDBNAME=pdbdev -OPENMODE=READ WRITE -ACTION=PLUG -MSG=Success -``` - -Check kubernets log files and the database alert log - -```text -/usr/bin/kubectl logs -f pod/`/usr/bin/kubectl get pods -n oracle-database-operator-system|grep oracle-database-operator-controller|head -1|cut -d ' ' -f 1` -n oracle-database-operator-system - -2023-01-03T14:33:51Z INFO pdb-webhook ValidateCreate-Validating PDB spec for : pdb1 -2023-01-03T14:33:51Z INFO pdb-webhook validateCommon {"name": "pdb1"} -2023-01-03T14:33:51Z INFO pdb-webhook Valdiating PDB Resource Action : PLUG -2023-01-03T14:33:51Z INFO pdb-webhook PDB Resource : pdb1 successfully validated for Action : PLUG -2023-01-03T14:33:51Z DEBUG controller-runtime.webhook.webhooks wrote response {"webhook": "/validate-database-oracle-com-v1alpha1-pdb", "code": 200, "reason": "", "UID": "fccac7ba-7540-42ff-93b2-46675506a098", "allowed": true} -2023-01-03T14:34:16Z DEBUG controller-runtime.webhook.webhooks received request {"webhook": "/mutate-database-oracle-com-v1alpha1-pdb", "UID": "766dadcc-aeea-4a80-bc17-e957b4a44d3c", "kind": "database.oracle.com/v1alpha1, Kind=PDB", "resource": {"group":"database.oracle.com","version":"v1alpha1","resource":"pdbs"}} -2023-01-03T14:34:16Z INFO pdb-webhook Setting default values in PDB spec for : pdb1 -2023-01-03T14:34:16Z DEBUG controller-runtime.webhook.webhooks wrote response {"webhook": "/mutate-database-oracle-com-v1alpha1-pdb", "code": 200, "reason": "", "UID": "766dadcc-aeea-4a80-bc17-e957b4a44d3c", "allowed": true} - -[database alert log] -... -All grantable enqueues granted -freeing rdom 3 -freeing the fusion rht of pdb 3 -freeing the pdb enqueue rht -Domain Action Reconfiguration complete (total time 0.0 secs) -Completed: CREATE PLUGGABLE DATABASE "pdbdev" - USING '/tmp/pdbunplug.xml' - SOURCE_FILE_NAME_CONVERT=NONE - MOVE - FILE_NAME_CONVERT=NONE - STORAGE UNLIMITED TEMPFILE REUSE - -2023-01-03T14:35:41.500186+00:00 -ALTER PLUGGABLE DATABASE "pdbdev" OPEN READ WRITE INSTANCES=ALL -2023-01-03T14:35:41.503482+00:00 -PDBDEV(3):Pluggable database PDBDEV opening in read write -PDBDEV(3):SUPLOG: Initialize PDB SUPLOG SGA, old value 0x0, new value 0x18 -PDBDEV(3):Autotune of undo retention is turned on -... -``` -### CLONE PDB - -Prepare and apply a new yaml file **pdb_clone.yaml** to clone the existing pluggable database. - -```yaml -#pdb_clone.yaml -apiVersion: database.oracle.com/v1alpha1 -kind: PDB -metadata: - name: pdb2 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "oracle-database-operator-system" - cdbName: "DB12" - pdbName: "pdb2-clone" - srcPdbName: "pdbdev" - fileNameConversions: "NONE" - totalSize: "UNLIMITED" - tempSize: "UNLIMITED" - action: "Clone" - [secret section] - -``` -```bash -kubectl apply -f pdb_clone.yaml -pdb.database.oracle.com/pdb2 created -[oracle@mitk01 https.ords.22]$ sh checkpdbs.sh -================================================================== -CDB=cdb-dev -K8SNAME=pdb1 -PDBNAME=pdbdev -OPENMODE=READ WRITE -ACTION=PLUG -MSG=Success -================================================================== -CDB=cdb-dev -K8SNAME=pdb2 -PDBNAME=pdb2-clone -OPENMODE= -ACTION= -MSG=Waiting for PDB to be cloned -[...] -[.wait sometimes..] - sh checkpdbs.sh -================================================================== -CDB=cdb-dev -K8SNAME=pdb1 -PDBNAME=pdbdev -OPENMODE=READ WRITE -ACTION=PLUG -MSG=Success -================================================================== -CDB=cdb-dev -K8SNAME=pdb2 -PDBNAME=pdb2-clone -OPENMODE=READ WRITE -ACTION=CLONE -MSG=Success -``` -log info - -```text -[kubernets log] -2023-01-03T15:13:31Z INFO pdb-webhook - asClone : false -2023-01-03T15:13:31Z INFO pdb-webhook - getScript : false -2023-01-03T15:13:31Z DEBUG controller-runtime.webhook.webhooks wrote response {"webhook": "/mutate-database-oracle-com-v1alpha1-pdb", "code": 200, "reason": "", "UID": "7c17a715-7e4e-47d4-ad42-dcb37526bb3e", "allowed": true} -2023-01-03T15:13:31Z DEBUG controller-runtime.webhook.webhooks received request {"webhook": "/validate-database-oracle-com-v1alpha1-pdb", "UID": "11e0d49c-afaa-47ac-a301-f1fdd1e70173", "kind": "database.oracle.com/v1alpha1, Kind=PDB", "resource": {"group":"database.oracle.com","version":"v1alpha1","resource":"pdbs"}} -2023-01-03T15:13:31Z INFO pdb-webhook ValidateCreate-Validating PDB spec for : pdb2 -2023-01-03T15:13:31Z INFO pdb-webhook validateCommon {"name": "pdb2"} -2023-01-03T15:13:31Z INFO pdb-webhook Valdiating PDB Resource Action : CLONE -2023-01-03T15:13:31Z INFO pdb-webhook PDB Resource : pdb2 successfully validated for Action : CLONE -2023-01-03T15:13:31Z DEBUG controller-runtime.webhook.webhooks wrote response {"webhook": "/validate-database-oracle-com-v1alpha1-pdb", "code": 200, "reason": "", "UID": "11e0d49c-afaa-47ac-a301-f1fdd1e70173", "allowed": true} - -[database alert log] -Domain Action Reconfiguration complete (total time 0.0 secs) -2023-01-03T15:15:00.670436+00:00 -Completed: CREATE PLUGGABLE DATABASE "pdb2-clone" FROM "pdbdev" - STORAGE UNLIMITED - TEMPFILE REUSE - FILE_NAME_CONVERT=NONE -ALTER PLUGGABLE DATABASE "pdbdev" CLOSE IMMEDIATE INSTANCES=ALL -2023-01-03T15:15:00.684271+00:00 -PDBDEV(3):Pluggable database PDBDEV closing -PDBDEV(3):JIT: pid 8235 requesting stop -PDBDEV(3):Buffer Cache flush started: 3 -PDBDEV(3):Buffer Cache flush finished: 3 - -``` -### UNPLUG AND PLUG WITH TDE - - - - -> ⚠ __WARNING FOR THE TDE USERS__ ⚠ According to the [ords documentation](https://docs.oracle.com/en/database/oracle/oracle-database/21/dbrst/op-database-pdbs-pdb_name-post.html) the plug and unplug operation with tde is supported only if ords runs on the same host of the database which is not the case of operator where ords runs on an isolated pods. Do not use pdb controller for unplug and plug operation with tde in production environments. - - - -You can use unplug and plug database with TDE; in order to do that you have to specify a key store path and create new kubernets secret for TDE using the following yaml file. **tde_secrete.yaml**. - -```yaml -#tde_secret -apiVersion: v1 -kind: Secret -metadata: - name: tde1-secret - namespace: oracle-database-operator-system -type: Opaque -data: - tdepassword: "...." - tdesecret: "...." -``` - -```bash -kubectl apply -f tde_secret.yaml -``` - -The file to unplug and plug database with TDE are the following - - -```yaml -#pdb_unplugtde.yaml -apiVersion: database.oracle.com/v1alpha1 -kind: PDB -metadata: - name: pdb1 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "oracle-database-operator-system" - cdbName: "DB12" - pdbName: "pdbdev" - adminName: - secret: - secretName: - key: "sysadmin_user" - adminPwd: - secret: - secretName: pdb1-secret - key: "sysadmin_pwd" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - tdePassword: - secret: - secretName: "tde1-secret" - key: "tdepassword" - tdeSecret: - secret: - secretName: "tde1-secret" - key: "tdesecret" - totalSize: 1G - tempSize: 1G - unlimitedStorage: true - reuseTempFile: true - fileNameConversions: NONE - action: "Unplug" - xmlFileName: "/home/oracle/unplugpdb.xml" - tdeExport: true -``` - -```yaml -#pdb_plugtde.ymal -kind: PDB -metadata: - name: pdb1 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "oracle-database-operator-system" - cdbName: "DB12" - pdbName: "pdbdev" - adminName: - secret: - secretName: pdb1-secret - key: "sysadmin_user" - adminPwd: - secret: - secretName: pdb1-secret - key: "sysadmin_pwd" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - tdePassword: - secret: - secretName: "tde1-secret" - key: "tdepassword" - tdeSecret: - secret: - secretName: "tde1-secret" - key: "tdesecret" - totalSize: 1G - tempSize: "100M" - unlimitedStorage: true - reuseTempFile: true - fileNameConversions: NONE - sourceFileNameConversions: "NONE" - copyAction: "MOVE" - action: "Plug" - xmlFileName: /home/oracle/unplugpdb.xml - tdeImport: true - tdeKeystorePath: /home/oracle/keystore - -``` - - - - - - diff --git a/docs/multitenant/ords-based/usecase02/pdb_clone.yaml b/docs/multitenant/ords-based/usecase02/pdb_clone.yaml deleted file mode 100644 index 5723f7c6..00000000 --- a/docs/multitenant/ords-based/usecase02/pdb_clone.yaml +++ /dev/null @@ -1,50 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb3 - namespace: pdbnamespace - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "cdbnamespace" - cdbName: "DB12" - pdbName: "new_clone" - srcPdbName: "pdbdev" - fileNameConversions: "NONE" - totalSize: "UNLIMITED" - tempSize: "UNLIMITED" - assertivePdbDeletion: true - action: "Clone" - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase02/pdb_plug.yaml b/docs/multitenant/ords-based/usecase02/pdb_plug.yaml deleted file mode 100644 index 9eb5ed77..00000000 --- a/docs/multitenant/ords-based/usecase02/pdb_plug.yaml +++ /dev/null @@ -1,53 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb1 - namespace: pdbnamespace - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "cdbnamespace" - cdbName: "DB12" - pdbName: "pdbdev" - xmlFileName: "/tmp/pdb.xml" - action: "plug" - fileNameConversions: "NONE" - sourceFileNameConversions: "NONE" - copyAction: "MOVE" - totalSize: "1G" - tempSize: "100M" - assertivePdbDeletion: true - action: "Plug" - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase02/pdb_plugtde.yaml b/docs/multitenant/ords-based/usecase02/pdb_plugtde.yaml deleted file mode 100644 index 995be538..00000000 --- a/docs/multitenant/ords-based/usecase02/pdb_plugtde.yaml +++ /dev/null @@ -1,56 +0,0 @@ -# -# Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. -# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. -# - -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb1 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbName: "DB12" - pdbName: "pdbdev" - adminName: - secret: - secretName: pdb1-secret - key: "sysadmin_user" - adminPwd: - secret: - secretName: pdb1-secret - key: "sysadmin_pwd" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - tdePassword: - secret: - secretName: "tde1-secret" - key: "tdepassword" - tdeSecret: - secret: - secretName: "tde1-secret" - key: "tdesecret" - totalSize: 1G - tempSize: "100M" - unlimitedStorage: true - reuseTempFile: true - fileNameConversions: NONE - sourceFileNameConversions: "NONE" - copyAction: "MOVE" - action: "Plug" - xmlFileName: /home/oracle/unplugpdb.xml - tdeImport: true - tdeKeystorePath: /home/oracle/keystore - diff --git a/docs/multitenant/ords-based/usecase02/pdb_unplug.yaml b/docs/multitenant/ords-based/usecase02/pdb_unplug.yaml deleted file mode 100644 index 0036d5f7..00000000 --- a/docs/multitenant/ords-based/usecase02/pdb_unplug.yaml +++ /dev/null @@ -1,46 +0,0 @@ -apiVersion: database.oracle.com/v4 -kind: PDB -metadata: - name: pdb1 - namespace: pdbnamespace - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "cdbnamespace" - cdbName: "DB12" - pdbName: "pdbdev" - xmlFileName: "/tmp/pdb.xml" - action: "Unplug" - adminName: - secret: - secretName: "pdbusr" - key: "e_pdbusr.txt" - adminPwd: - secret: - secretName: "pdbpwd" - key: "e_pdbpwd.txt" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "wbuser" - key: "e_wbuser.txt" - webServerPwd: - secret: - secretName: "wbpass" - key: "e_wbpass.txt" - pdbOrdsPrvKey: - secret: - secretName: "prvkey" - key: "privateKey" diff --git a/docs/multitenant/ords-based/usecase02/pdb_unplugtde.yaml b/docs/multitenant/ords-based/usecase02/pdb_unplugtde.yaml deleted file mode 100644 index 2eacc5b7..00000000 --- a/docs/multitenant/ords-based/usecase02/pdb_unplugtde.yaml +++ /dev/null @@ -1,54 +0,0 @@ -# -# Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. -# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. -# - -apiVersion: database.oracle.com/v4 -Kind: PDB -metadata: - name: pdb1 - namespace: oracle-database-operator-system - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbName: "DB12" - pdbName: "pdbdev" - adminName: - secret: - secretName: pdb1-secret - key: "sysadmin_user" - adminPwd: - secret: - secretName: pdb1-secret - key: "sysadmin_pwd" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - tdePassword: - secret: - secretName: "tde1-secret" - key: "tdepassword" - tdeSecret: - secret: - secretName: "tde1-secret" - key: "tdesecret" - totalSize: 1G - tempSize: 1G - unlimitedStorage: true - reuseTempFile: true - fileNameConversions: NONE - action: "Unplug" - xmlFileName: "/home/oracle/unplugpdb.xml" - tdeExport: true - tdeKeystorePath: "/home/oracle/keystore" - diff --git a/docs/multitenant/provisioning/ords_image.md b/docs/multitenant/provisioning/ords_image.md deleted file mode 100644 index e2d1dcef..00000000 --- a/docs/multitenant/provisioning/ords_image.md +++ /dev/null @@ -1,81 +0,0 @@ - - -# Build ORDS Docker Image - -This file contains the steps to create an ORDS based image to be used solely by the PDB life cycle multitentant controllers. - -**NOTE:** It is assumed that before this step, you have followed the [prerequisite](./../README.md#prerequsites-to-manage-pdb-life-cycle-using-oracle-db-operator-on-prem-database-controller) steps. - -#### Clone the software using git: - -> Under directory ./oracle-database-operator/ords you will find the [Dockerfile](../../../ords/Dockerfile) and [runOrdsSSL.sh](../../../ords/runOrdsSSL.sh) required to build the image. - -```sh - git clone git@orahub.oci.oraclecorp.com:rac-docker-dev/oracle-database-operator.git - cd oracle-database-operator/ords/ -``` - -#### Login to the registry: container-registry.oracle.com - -**NOTE:** To login to this registry, you will need to the URL https://container-registry.oracle.com , Sign in, then click on "Java" and then accept the agreement. - -```bash -docker login container-registry.oracle.com -``` - -#### Login to the your container registry - -Login to a repo where you want to push your docker image (if needed) to pull during deployment in your environment. - -```bash -docker login -``` - -#### Build the image - -Build the docker image by using below command: - -```bash -docker build -t oracle/ords-dboper:latest . -``` -> If your are working behind a proxy mind to specify https_proxy and http_proxy during image creation - -Check the docker image details using: - -```bash -docker images -``` - -> OUTPUT EXAMPLE -```bash -REPOSITORY TAG IMAGE ID CREATED SIZE -oracle/ords-dboper latest fdb17aa242f8 4 hours ago 1.46GB - -``` - -#### Tag and push the image - -Tag and push the image to your image repository. - -NOTE: We have the repo as `phx.ocir.io//oracle/ords:latest`. Please change as per your environment. - -```bash -docker tag oracle/ords-dboper:ords-latest phx.ocir.io//oracle/ords:latest -docker push phx.ocir.io//oracle/ords:latest -``` - -#### In case of private image - -If you the image not be public then yuo need to create a secret containing the password of your image repository. -Create a Kubernetes Secret for your docker repository to pull the image during deployment using the below command: - -```bash -kubectl create secret generic container-registry-secret --from-file=.dockerconfigjson=./.docker/config.json --type=kubernetes.io/dockerconfigjson -n oracle-database-operator-system -``` - -Use the parameter `ordsImagePullSecret` to specify the container secrets in pod creation yaml file - -#### [Image createion example](../usecase01/logfiles/BuildImage.log) - - - diff --git a/docs/multitenant/usecase/README.md b/docs/multitenant/usecase/README.md new file mode 100644 index 00000000..062200ee --- /dev/null +++ b/docs/multitenant/usecase/README.md @@ -0,0 +1,248 @@ + +* 1. [Prerequisites](#Prerequisites) +* 2. [Operator setup](#Operatorsetup) +* 3. [Secrets creation](#Secretscreation) +* 4. [Yaml file creation](#Yamlfilecreation) +* 5. [Run testcase](#Runtestcase) +* 6. [Makefile targets table](#Makefiletargetstable) +* 7. [Diag commands and troubleshooting](#Diagcommandsandtroubleshooting) + * 7.1. [Connect to rest server pod](#Connecttorestserverpod) + * 7.2. [Lrest pod log](#Lrestpodlog) + * 7.3. [Monitor control plane](#Monitorcontrolplane) + * 7.4. [Error decrypting credential](#Errordecryptingcredential) + * 7.5. [Crd details](#Crddetails) + + + + + + + + + +# Use case directory + +The use case directory contains a makefile to automatically install the Oracle Database Operator (namespace scope configuration) and generate the yaml files to test pdb life cycle management in two different namespaces (one for lrest pod the other one for pdb crd). To simplify and speed up the execution you just need to edit a [parameter file](../usecase/parameters.txt) with all the information about your environment. The makefile script uses parameter file to generate all the yaml file required to test the controllers. +After parameters setup there is the operator installation (**make opsetup**) , the secrets installation (**make secrets**), the yaml file generation (**make genyaml**). + +![generalschema](../images/usecaseschema.jpg) + +**parameter file table of contents** +```text + Check the latest version available<--------------+ + | + +-----+ +LRESTIMG...............:container-registry.oracle.com/database/operator:lrest-241210-amd64 +TNSALIAS...............:[Tnsalias do not use quotes and avoid space in the string --> (DESCRIPTION=(CONNECT_TIMEOUT=90)(RETRY_COUNT=30)(RETRY_DELA....] +DBUSER.................:[CDB admin user] +DBPASS.................:[CDB admin user password] +WBUSER.................:[HTTPS user] +WBPASS.................:[HTTPS user password] +PDBUSR.................:[PDB admin user] +PDBPWD.................:[PDB admin user password] +PDBNAMESPACE...........:[pdb namespace] +LRSNAMESPACE...........:[cdb namespace] +COMPANY................:[your company name] +APIVERSION.............:[v4 --> do not edit] +SERVICENAMEACCOUNT.....:[service account - for openshift ] +AUTODISCOVER...........:[boolean: check for pdb with no crd ] +CODE_TREE..............:[Is the path of the directory with the original operator yaml file] +CERT_MANAGER...........:https://github.com/cert-manager/cert-manager/releases/download/v1.16.2/cert-manager.yaml +OPENSHIFT..............:[boolean] +``` + +Verify parameters using ``make check`` command. + +## 1. Prerequisites + +- Ensure that **kubectl** is properly installed on your client. +- Even if the makefile automation, read carefully the [operator installation page](../../../../docs/installation/OPERATOR_INSTALLATION_README.md). (role binding,webcert,etc) +- Ensure that the administrative user (admin) on the container database is configured as documented. + +eg + +```sql +-- Connect to the container and creates the administrative user +alter session set "_oracle_script"=true; +create user [DBUSER] identified by [DBPASS]; +grant create session to restdba container=all; +grant sysdba to restdba container=all; +``` + +## 2. Operator setup + +```bash +make opsetup +``` +The make target **make opsetup** does the following actions: +- Creates a copy the original **oracle-database-operator.yaml** and updates the WATCH_NAMESPACE list with the pdbnamespace and cdbnamespace values. +- [Applies the certmaneger](../../../README.md#install-cert-manager) +- Creates two namespaces: one for the lrest pod and the other one for the pdb controller. +- [Namespace Scoped Deployment](../../../README.md#2-namespace-scoped-deployment) +- Applies the oracle-database-operator.yaml +- [ClusterRole and ClusterRoleBinding for NodePort services](../../../README.md#clusterrole-and-clusterrolebinding-for-nodeport-services) + +👉 **If your are running on Openshift you need to manually apply the [service context file](./security_context.yaml)** + +## 3. Secrets creation + +```bash +make secrets +``` +**make secrets** creates secrets encrypting the credential specified in the parameters + +## 4. Yaml file creation + +```bash +make genyaml +``` +**make genyaml** generates the required `yaml` files to work with multitenant controllers. + +## 5. Run testcase + +```bash +make runall00 +``` + +You can run **make runall00** to test all the functionality the multitenant controller + +## 6. Makefile targets table + + | target | action | additional info | + |-----------------|---------------------|-----------------| + |tkapplyinit | config map creation | | + |run00 | lrest pod creation | | + |run01.1 | pdb1 creation | | + |run01.2 | pdb2 creation | | + |run02.1 | pdb1 open | declarative | + |run02.2 | pdb2 open | declarative | + |run03.1 | pdb1 clone | declarative | + |run04.1 | pdb1 close | declarative | + |run04.2 | pdb2 close | declatative | + |run05.1 | pdb1 unplug | declarative | + |run06.1 | pdb1 plug | declarative | + |openpdb1 | pdb1 open | imperative | + |openpdb2 | pdb2 open | imperative | + |closepdb1 | pdb1 close | imperative | + |closepdb2 | pdb2 close | imperative | + |openpdb1rs | pdb1 open restrict | imperative | + |openpdb2rs | pdb2 open restrict | imperative | + |tkaudosicov | test autodiscovery | | + |tkplsqlexec | test sql/plsql | | + |tkapplyinit | apply init map | | + |altercpu | alter cpu_count | make altercpu LRPDBNAME=_lrpdb resource_ CPU_COUNT=_cpu count value_ | + |open | open pdb [imperative]| make open LRPDBNAME=_lrpdb resource_ | + |close | close pdb [imperative]| make close LRPDBNAME=_lrpdb resource_ | + |listimage | images available on the cluster| | + |dumpoperator | dump operator log | | + |dumplrest | dump lrest log | | + |login | connect to lrest pod| | + |reloadod | reload operator img | | + |mgrrestart | manager restart | | + |**opsetup** | install the operator| | + |**secrets** | create secrets | | + |**genyaml** | generate the yaml files| | + |opclean | deintall the operator| | + |cleanlrest | drop lrest resource | **lrest name hard coded** | + |rest | rest status bitmask | make rest LRPDBNAME=_resname_ RESETVALUE=_new bitmask value_ | + |tkautd | Turn on/off autodiscover | make tkautd AUTOD=true/false | + |tkdelcs | Turn on/off pdb delete cascade | make tkdelcs DELETECS=true/false | + |tkdelcrd |Turn ON/OFF lrest pdb imperativeLrpdb deletion | make tkdelcrd DELETECRD=true/false LRPDBNAME=_lrpdb resource name_ | + | checkimpdel | report of imperative delete setting || + + +## 7. Diag commands and troubleshooting + +### 7.1. Connect to rest server pod + +```bash +/usr/bin/kubectl exec -n -it -- /bin/bash +``` + +### 7.2. Lrest pod log + +```bash +kubectl logs `kubectl get pods -o custom-columns=:metadata.name -n cdbnamespace --no-headers ` -n cdbnamespace +``` + +```bash +## example ## + +kubectl get pods -n cdbnamespace +NAME READY STATUS RESTARTS AGE +cdb-dev-lrest-rs-fnw99 1/1 Running 1 (17h ago) 18h + +kubectl exec cdb-dev-lrest-rs-fnw99 -n cdbnamespace -it -- /bin/bash +[oracle@cdb-dev-lrest-rs-fnw99 ~]$ +``` + +### 7.3. Monitor control plane + +```bash +kubectl logs -f -l control-plane=controller-manager -n oracle-database-operator-system +``` +```bash +## output example: ## +2024-10-28T23:54:25Z INFO lrpdb-webhook ValidateUpdate-Validating LRPDB spec for : lrpdb2 +2024-10-28T23:54:25Z INFO lrpdb-webhook validateCommon {"name": "lrpdb2"} +2024-10-28T23:54:25Z INFO lrpdb-webhook Valdiating LRPDB Resource Action : MODIFY +2024-10-29T10:07:34Z INFO lrpdb-webhook ValidateUpdate-Validating LRPDB spec for : lrpdb2 +2024-10-29T10:07:34Z INFO lrpdb-webhook ValidateUpdate-Validating LRPDB spec for : lrpdb1 +2024-10-29T16:49:15Z INFO lrpdb-webhook ValidateUpdate-Validating LRPDB spec for : lrpdb1 +2024-10-29T16:49:15Z INFO lrpdb-webhook validateCommon {"name": "lrpdb1"} +2024-10-29T16:49:15Z INFO lrpdb-webhook Valdiating LRPDB Resource Action : CREATE +2024-10-29T10:07:20Z INFO controller-runtime.certwatcher Updated current TLS certificate +2024-10-29T10:07:20Z INFO controller-runtime.webhook Serving webhook server {"host": "", "port": 9443} +2024-10-29T10:07:20Z INFO controller-runtime.certwatcher Starting certificate watcher +I1029 10:07:20.189724 1 leaderelection.go:250] attempting to acquire leader lease oracle-database-operator-system/a9d608ea.oracle.com... +2024-10-29T16:49:15Z INFO lrpdb-webhook Setting default values in LRPDB spec for : lrpdb1 + +``` + +### 7.4. Error decrypting credential + +In the following example you can see a resource creation failure due to a decryption issue + +```text +2024-10-30T10:09:08Z INFO controllers.LRPDB getEncriptedSecret :pdbusr {"getEncriptedSecret": {"name":"lrpdb1","namespace":"pdbnamespace"}} +2024-10-30T10:09:08Z ERROR controllers.LRPDB Failed to parse private key - x509: failed to parse private key (use ParsePKCS1PrivateKey instead for this key format) {"DecryptWithPrivKey": {"name":"lrpdb1","namespace":"pdbnamespace"}, "error": "x509: failed to parse private key (use ParsePKCS1PrivateKey instead for this key format)"} +``` + +**Solution**: Ensure you use **PCKS8** format during private key generation. If you are not using `openssl3`, then run this command: + +```bash +openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_keygen_pubexp:65537 > mykey +``` +### 7.5. Crd details +Use the **describe** option to obtain `crd` information + +```bash +kubectl describe lrpdb lrpdb1 -n pdbnamespace +[...] + Secret: + Key: e_wbuser.txt + Secret Name: wbuser +Status: + Action: CREATE + Bitstat: 25 + Bitstatstr: |MPAPPL|MPWARN|MPINIT| + Conn String: (DESCRIPTION=(CONNECT_TIMEOUT=90)(RETRY_COUNT=30)(RETRY_DELAY=10)(TRANSPORT_CONNECT_TIMEOUT=70)(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL=TCP)(HOST=scan12.testrac.com)(PORT=1521)(IP=V4_ONLY))(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL=TCP)(HOST=scan34.testrac.com)(PORT=1521)(IP=V4_ONLY))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=pdbdev))) + Msg: Success + Open Mode: MOUNTED + Phase: Ready + Status: true + Total Size: 2G +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Created 108s LRPDB LRPDB 'pdbdev' created successfully + Normal Created 108s LRPDB PDB 'pdbdev' assertive pdb deletion turned on + Warning LRESTINFO 95s LRPDB pdb=pdbdev:test_invalid_parameter:16:spfile:2065 + Warning Done 15s (x12 over 2m25s) LRPDB cdb-dev + +``` + + \ No newline at end of file diff --git a/docs/multitenant/lrest-based/usecase/altersystem_pdb1_resource.yaml b/docs/multitenant/usecase/altersystem_pdb1_resource.yaml similarity index 97% rename from docs/multitenant/lrest-based/usecase/altersystem_pdb1_resource.yaml rename to docs/multitenant/usecase/altersystem_pdb1_resource.yaml index 0467a948..7e4deaa5 100644 --- a/docs/multitenant/lrest-based/usecase/altersystem_pdb1_resource.yaml +++ b/docs/multitenant/usecase/altersystem_pdb1_resource.yaml @@ -10,12 +10,9 @@ spec: cdbNamespace: "cdbnamespace" cdbName: "DB12" pdbName: "pdbdev" - action: "Alter" alterSystemParameter : "cpu_count" alterSystemValue : "3" parameterScope : "memory" - - adminpdbUser: secret: secretName: "pdbusr" diff --git a/docs/multitenant/lrest-based/usecase/cdbnamespace_binding.yaml b/docs/multitenant/usecase/cdbnamespace_binding.yaml similarity index 100% rename from docs/multitenant/lrest-based/usecase/cdbnamespace_binding.yaml rename to docs/multitenant/usecase/cdbnamespace_binding.yaml diff --git a/docs/multitenant/lrest-based/usecase/clone_pdb1_resource.yaml b/docs/multitenant/usecase/clone_pdb1_resource.yaml similarity index 95% rename from docs/multitenant/lrest-based/usecase/clone_pdb1_resource.yaml rename to docs/multitenant/usecase/clone_pdb1_resource.yaml index 2c4afe13..dfeac7ab 100644 --- a/docs/multitenant/lrest-based/usecase/clone_pdb1_resource.yaml +++ b/docs/multitenant/usecase/clone_pdb1_resource.yaml @@ -15,8 +15,7 @@ spec: totalSize: "UNLIMITED" tempSize: "UNLIMITED" pdbconfigmap: "config-map-pdb" - assertiveLrpdbDeletion: true - action: "Clone" + imperativeLrpdbDeletion: true adminpdbUser: secret: secretName: "pdbusr" diff --git a/docs/multitenant/lrest-based/usecase/clone_pdb2_resource.yaml b/docs/multitenant/usecase/clone_pdb2_resource.yaml similarity index 95% rename from docs/multitenant/lrest-based/usecase/clone_pdb2_resource.yaml rename to docs/multitenant/usecase/clone_pdb2_resource.yaml index 16255a87..aeabc9d0 100644 --- a/docs/multitenant/lrest-based/usecase/clone_pdb2_resource.yaml +++ b/docs/multitenant/usecase/clone_pdb2_resource.yaml @@ -15,8 +15,7 @@ spec: totalSize: "UNLIMITED" tempSize: "UNLIMITED" pdbconfigmap: "config-map-pdb" - assertiveLrpdbDeletion: true - action: "Clone" + imperativeLrpdbDeletion: true adminpdbUser: secret: secretName: "pdbusr" diff --git a/docs/multitenant/lrest-based/usecase/close_pdb1_resource.yaml b/docs/multitenant/usecase/close_pdb1_resource.yaml similarity index 97% rename from docs/multitenant/lrest-based/usecase/close_pdb1_resource.yaml rename to docs/multitenant/usecase/close_pdb1_resource.yaml index 87f7383d..4f5c9cb4 100644 --- a/docs/multitenant/lrest-based/usecase/close_pdb1_resource.yaml +++ b/docs/multitenant/usecase/close_pdb1_resource.yaml @@ -12,7 +12,6 @@ spec: pdbName: "pdbdev" pdbState: "CLOSE" modifyOption: "IMMEDIATE" - action: "Modify" adminpdbUser: secret: secretName: "pdbusr" diff --git a/docs/multitenant/lrest-based/usecase/close_pdb2_resource.yaml b/docs/multitenant/usecase/close_pdb2_resource.yaml similarity index 97% rename from docs/multitenant/lrest-based/usecase/close_pdb2_resource.yaml rename to docs/multitenant/usecase/close_pdb2_resource.yaml index 0743bd8c..c37c8184 100644 --- a/docs/multitenant/lrest-based/usecase/close_pdb2_resource.yaml +++ b/docs/multitenant/usecase/close_pdb2_resource.yaml @@ -12,7 +12,6 @@ spec: pdbName: "pdbprd" pdbState: "CLOSE" modifyOption: "IMMEDIATE" - action: "Modify" adminpdbUser: secret: secretName: "pdbusr" diff --git a/docs/multitenant/lrest-based/usecase/close_pdb3_resource.yaml b/docs/multitenant/usecase/close_pdb3_resource.yaml similarity index 95% rename from docs/multitenant/lrest-based/usecase/close_pdb3_resource.yaml rename to docs/multitenant/usecase/close_pdb3_resource.yaml index 6c6ca519..6b201d95 100644 --- a/docs/multitenant/lrest-based/usecase/close_pdb3_resource.yaml +++ b/docs/multitenant/usecase/close_pdb3_resource.yaml @@ -9,10 +9,9 @@ spec: cdbResName: "cdb-dev" cdbNamespace: "cdbnamespace" cdbName: "DB12" - pdbName: ""new_clone" + pdbName: "new_clone" pdbState: "CLOSE" modifyOption: "IMMEDIATE" - action: "Modify" adminpdbUser: secret: secretName: "pdbusr" diff --git a/docs/multitenant/lrest-based/usecase/config_map_pdb.yaml b/docs/multitenant/usecase/config_map_pdb.yaml similarity index 100% rename from docs/multitenant/lrest-based/usecase/config_map_pdb.yaml rename to docs/multitenant/usecase/config_map_pdb.yaml diff --git a/docs/multitenant/usecase/config_map_plsql.yaml b/docs/multitenant/usecase/config_map_plsql.yaml new file mode 100644 index 00000000..1a4f998e --- /dev/null +++ b/docs/multitenant/usecase/config_map_plsql.yaml @@ -0,0 +1,29 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: sql-map-example1 + namespace: pdbnamespace +data: + plblock1.sql: | + create user k8stestuser identified by tkstestuser + plblock2.sql: | + grant dba to k8stestuser + plblock3.sql: | + create table k8stestuser.rndnum (c1 number ,c2 number) + plblock4.sql: | + alter session set events '942 trace name errorstack level 3' + plblock5.sql: | + create or replace procedure k8stestuser.gennum + as + begin + for cnt in 1..100 + loop + insert into k8stestuser.rndnum values (dbms_random.value(1,100), + dbms_random.value(1,100)); + end loop; + commit; + end; + plblock6.sql: | + begin + k8stestuser.gennum; + end; diff --git a/docs/multitenant/lrest-based/usecase/create_lrest_pod.yaml b/docs/multitenant/usecase/create_lrest_pod.yaml similarity index 86% rename from docs/multitenant/lrest-based/usecase/create_lrest_pod.yaml rename to docs/multitenant/usecase/create_lrest_pod.yaml index b80c1c56..926a4462 100644 --- a/docs/multitenant/lrest-based/usecase/create_lrest_pod.yaml +++ b/docs/multitenant/usecase/create_lrest_pod.yaml @@ -10,6 +10,11 @@ spec: dbTnsurl : "(DESCRIPTION=(CONNECT_TIMEOUT=90)(RETRY_COUNT=30)(RETRY_DELAY=10)(TRANSPORT_CONNECT_TIMEOUT=70)(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL=TCP)(HOST=scan12.testrac.com)(PORT=1521)(IP=V4_ONLY))(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL=TCP)(HOST=scan34.testrac.com)(PORT=1521)(IP=V4_ONLY))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=TESTORDS)))" replicas: 1 deletePdbCascade: true + autodiscover: false + namespaceAutoDiscover: pdbnamespace + clusterIp: false + loadBalancer: false + trace_level_client : 16 cdbAdminUser: secret: secretName: "dbuser" @@ -34,6 +39,10 @@ spec: secret: secretName: "db-tls" key: "tls.crt" + cdbTlsCat: + secret: + secretName: "db-ca" + key: "ca.crt" cdbPubKey: secret: secretName: "pubkey" diff --git a/docs/multitenant/lrest-based/usecase/create_pdb1_resource.yaml b/docs/multitenant/usecase/create_pdb1_resource.yaml similarity index 95% rename from docs/multitenant/lrest-based/usecase/create_pdb1_resource.yaml rename to docs/multitenant/usecase/create_pdb1_resource.yaml index fa58d36a..1ce6476c 100644 --- a/docs/multitenant/lrest-based/usecase/create_pdb1_resource.yaml +++ b/docs/multitenant/usecase/create_pdb1_resource.yaml @@ -10,14 +10,13 @@ spec: cdbNamespace: "cdbnamespace" cdbName: "DB12" pdbName: "pdbdev" - assertiveLrpdbDeletion: true + imperativeLrpdbDeletion: true fileNameConversions: "NONE" unlimitedStorage: false pdbconfigmap: "config-map-pdb" tdeImport: false totalSize: "2G" tempSize: "800M" - action: "Create" adminpdbUser: secret: secretName: "pdbusr" diff --git a/docs/multitenant/lrest-based/usecase/create_pdb2_resource.yaml b/docs/multitenant/usecase/create_pdb2_resource.yaml similarity index 95% rename from docs/multitenant/lrest-based/usecase/create_pdb2_resource.yaml rename to docs/multitenant/usecase/create_pdb2_resource.yaml index 02d5763b..a966de91 100644 --- a/docs/multitenant/lrest-based/usecase/create_pdb2_resource.yaml +++ b/docs/multitenant/usecase/create_pdb2_resource.yaml @@ -10,14 +10,13 @@ spec: cdbNamespace: "cdbnamespace" cdbName: "DB12" pdbName: "pdbprd" - assertiveLrpdbDeletion: true + imperativeLrpdbDeletion: true fileNameConversions: "NONE" unlimitedStorage: false pdbconfigmap: "config-map-pdb" tdeImport: false totalSize: "2G" tempSize: "800M" - action: "Create" adminpdbUser: secret: secretName: "pdbusr" diff --git a/docs/multitenant/lrest-based/usecase/delete_pdb1_resource.yaml b/docs/multitenant/usecase/delete_pdb1_resource.yaml similarity index 94% rename from docs/multitenant/lrest-based/usecase/delete_pdb1_resource.yaml rename to docs/multitenant/usecase/delete_pdb1_resource.yaml index 1a3c328a..751de900 100644 --- a/docs/multitenant/lrest-based/usecase/delete_pdb1_resource.yaml +++ b/docs/multitenant/usecase/delete_pdb1_resource.yaml @@ -9,8 +9,9 @@ spec: cdbResName: "cdb-dev" cdbNamespace: "cdbnamespace" pdbName: "pdbdev" - action: "Delete" + pdbState: "DELETE" dropAction: "INCLUDING" + imperativeLrpdbDeletion: true adminpdbUser: secret: secretName: "pdbusr" diff --git a/docs/multitenant/lrest-based/usecase/delete_pdb2_resource.yaml b/docs/multitenant/usecase/delete_pdb2_resource.yaml similarity index 94% rename from docs/multitenant/lrest-based/usecase/delete_pdb2_resource.yaml rename to docs/multitenant/usecase/delete_pdb2_resource.yaml index 747641d4..01118ea0 100644 --- a/docs/multitenant/lrest-based/usecase/delete_pdb2_resource.yaml +++ b/docs/multitenant/usecase/delete_pdb2_resource.yaml @@ -9,8 +9,9 @@ spec: cdbResName: "cdb-dev" cdbNamespace: "cdbnamespace" pdbName: "pdbprd" - action: "Delete" + pdbState: "DELETE" dropAction: "INCLUDING" + imperativeLrpdbDeletion: true adminpdbUser: secret: secretName: "pdbusr" diff --git a/docs/multitenant/usecase/delete_pdb3_resource.yaml b/docs/multitenant/usecase/delete_pdb3_resource.yaml new file mode 100644 index 00000000..9c57c5c1 --- /dev/null +++ b/docs/multitenant/usecase/delete_pdb3_resource.yaml @@ -0,0 +1,46 @@ +apiVersion: database.oracle.com/v4 +kind: LRPDB +metadata: + name: pdb3 + namespace: pdbnamespace + labels: + cdb: cdb-dev +spec: + cdbResName: "cdb-dev" + cdbNamespace: "cdbnamespace" + pdbName: "new_clone" + pdbState: "DELETE" + dropAction: "INCLUDING" + imperativeLrpdbDeletion: true + adminpdbUser: + secret: + secretName: "pdbusr" + key: "e_pdbusr.txt" + adminpdbPass: + secret: + secretName: "pdbpwd" + key: "e_pdbpwd.txt" + lrpdbTlsKey: + secret: + secretName: "db-tls" + key: "tls.key" + lrpdbTlsCrt: + secret: + secretName: "db-tls" + key: "tls.crt" + lrpdbTlsCat: + secret: + secretName: "db-ca" + key: "ca.crt" + webServerUser: + secret: + secretName: "wbuser" + key: "e_wbuser.txt" + webServerPwd: + secret: + secretName: "wbpass" + key: "e_wbpass.txt" + cdbPrvKey: + secret: + secretName: "prvkey" + key: "privateKey" diff --git a/docs/multitenant/usecase/makefile b/docs/multitenant/usecase/makefile new file mode 100644 index 00000000..a05f3e47 --- /dev/null +++ b/docs/multitenant/usecase/makefile @@ -0,0 +1,1545 @@ +# Copyright (c) 2022, Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. +# __ __ _ __ _ _ +# | \/ | __ _| | _____ / _(_) | ___ +# | |\/| |/ _` | |/ / _ \ |_| | |/ _ \ +# | | | | (_| | < __/ _| | | __/ +# |_| |_|\__,_|_|\_\___|_| |_|_|\___| +# | | | | ___| |_ __ ___ _ __ +# | |_| |/ _ \ | '_ \ / _ \ '__| +# | _ | __/ | |_) | __/ | +# |_| |_|\___|_| .__/ \___|_| +# |_| +# +# WARNING: Using this makefile helps you to customize yaml +# files. Edit parameters.txt with your enviroment +# informartion and execute the following steps +# +# 1) make opsetup +# it configures the operator yaml files with the +# watch namelist required by the multitenant controllers +# +# 2) make secrets +# It configure the required secrets necessary to operate +# with pdbs multitenant controllers +# +# 3) make genyaml +# It automatically creates all the yaml files based on the +# information available in the parameters file +# +# LIST OF GENERAED YAML FILE +# +# ----------------------------- ---------------------------------- +# oracle-database-operator.yaml : oracle database operator +# lrestnamespace_binding.yaml : role binding for lrestnamespace +# pdbnamespace_binding.yaml : role binding for pdbnamespace +# create_lrest_secret.yaml : create secrets for rest server pod +# create_lrpdb_secret.yaml : create secrets for pluggable database +# create_lrest_pod.yaml : create rest server pod +# create_pdb1_resource.yaml : create first pluggable database +# create_pdb2_resource.yaml : create second pluggable database +# open_pdb1_resource.yaml : open first pluggable database +# open_pdb2_resource.yaml : open second pluggable database +# close_pdb1_resource.yaml : close first pluggable database +# close_pdb2_resource.yaml : close second pluggable database +# clone_lrpdb_resource.yaml : clone thrid pluggable database +# clone_pdb2_resource.yaml : clone 4th pluggable database +# delete_pdb1_resource.yaml : delete first pluggable database +# delete_pdb2_resource.yaml : delete sencond pluggable database +# delete_pdb3_resource.yaml : delete thrid pluggable database +# unplug_pdb1_resource.yaml : unplug first pluggable database +# plug_pdb1_resource.yaml : plug first pluggable database +# map_pdb1_resource.yaml : map the first pluggable database +# config_map.yam : pdb parameters array +# altersystem_pdb1_resource.yaml : chage cpu_count count parameter for the first pdb +# config_map_plsql.yaml : plsql code +# +#[BEGIN TABLE] +# +# TARGETS DEPENDENCIES +# +#|level 1 |level 2 |level 3 |description | +#|----------------|----------------|---------------|-------------------------------------| +#|**opsetup** | | |install the operator | +#| | prsyaml | |configure the watch list | +#| | crtmgrappl | |apply the certmanager | +#| | nscrt | |namespace creation | +#| | bndappl | |role binding | +#| | opapply | |apply operator yaml file | +#| | rbacap | |apply rbac | +#+----------------+----------------+---------------+-------------------------------------+ +#|**secrets** | | |create certificates and secrets | +#| | tkl | |tls cert for https | +#| | delsecres | |cleanup existing secrets | +#+----------------+----------------+---------------+-------------------------------------+ +#|**genyaml** | | |generate yaml files | +#| | secyaml | |not documented | +#+----------------+----------------+---------------+-------------------------------------+ +# +# OTHER TARGETS +# +#|target | description | +#+----------------+----------------------------------------------------------------------+ +#|dumplrest | dump lrest | +#+----------------+----------------------------------------------------------------------+ +#|dumpoperator | dump operator logs | +#+----------------+----------------------------------------------------------------------+ +#|check | print parameters value | +#+----------------+----------------------------------------------------------------------+ +#|tklrestnew | online rest server recreation | +#+----------------+----------------------------------------------------------------------+ +#|mgrrestart | restart manager | +#+----------------+----------------------------------------------------------------------+ +#|listimage | list images available on the cluster | +#+----------------+----------------------------------------------------------------------+ +#|loglrest | tail -f log lrest | +#+----------------+----------------------------------------------------------------------+ +#|cleanyaml | clean yaml files | +#+----------------+----------------------------------------------------------------------+ +#|pkg | tar this directory | +#+----------------+----------------------------------------------------------------------+ +#|run00 | delete and recreate the lrest pod | +#+----------------+----------------------------------------------------------------------+ +#|tkautd | autodiscovery turn on/off *make tkautd AUTOD=false/true* | +#+----------------+----------------------------------------------------------------------+ +#|tkdelcs | lrest pdb delete cascade (on/off) *make tkdelcs DELETECS=true/false* | +#+----------------+----------------------------------------------------------------------+ +#|tkdelcrd | imperative deletion *make tkdelcrd DELETECRD=false LRPDNNAME=* | +#+----------------+----------------------------------------------------------------------+ + +#[END TABLE] + + +DATE := `date "+%y%m%d%H%M%S"` +###################### +# PARAMETER SECTIONS # +###################### + +export PARAMETERS=parameters.txt +#export TNSALIAS=$(shell cat $(PARAMETERS) |grep -v ^\#|grep TNSALIAS|cut -d : -f 2) +export TNSALIAS=$(shell cat $(PARAMETERS) |grep -v ^\#|grep TNSALIAS|awk -F ':' '{$$1="" ;printf("%s",$$2) }') +export DBUSER=$(shell cat $(PARAMETERS)|grep -v ^\#|grep DBUSER|cut -d : -f 2) +export DBPASS=$(shell cat $(PARAMETERS)|grep -v ^\#|grep DBPASS|cut -d : -f 2) +export WBUSER=$(shell cat $(PARAMETERS)|grep -v ^\#|grep WBUSER|cut -d : -f 2) +export WBPASS=$(shell cat $(PARAMETERS)|grep -v ^\#|grep WBPASS|cut -d : -f 2) +export PDBUSR=$(shell cat $(PARAMETERS)|grep -v ^\#|grep PDBUSR|cut -d : -f 2) +export PDBPWD=$(shell cat $(PARAMETERS)|grep -v ^\#|grep PDBPWD|cut -d : -f 2) +export PDBNAMESPACE=$(shell cat $(PARAMETERS)|grep -v ^\#|grep PDBNAMESPACE|cut -d : -f 2) +export LRSNAMESPACE=$(shell cat $(PARAMETERS)|grep -v ^\#|grep LRSNAMESPACE|cut -d : -f 2) +export LRESTIMG=$(shell cat $(PARAMETERS)|grep -v ^\#|grep LRESTIMG|cut -d : -f 2,3) +export OPRIMG=$(shell cat $(PARAMETERS)|grep -v ^\#|grep OPRIMG|cut -d : -f 2,3) +export COMPANY=$(shell cat $(PARAMETERS)|grep -v ^\#|grep COMPANY|cut -d : -f 2) +export APIVERSION=$(shell cat $(PARAMETERS)|grep -v ^\#|grep APIVERSION|cut -d : -f 2) +export SERVICENAM=$(shell cat $(PARAMETERS)|grep -v ^\#|grep SERVICENAMEACCOUNT|cut -d : -f 2) +export OPENSHIFT=$(shell cat $(PARAMETERS)|grep -v ^\#|grep OPENSHIFT|cut -d : -f 2) +export PLSQLMAP=$(shell cat $(PARAMETERS)|grep -v ^\#|grep PLSQLMAP|cut -d : -f 2) +export OPRNAMESPACE=oracle-database-operator-system +export CODE_TREE=$(shell cat $(PARAMETERS)|grep -v ^\#|grep CODE_TREE|cut -d : -f 2) +export ORACLE_OPERATOR_YAML=$(CODE_TREE)/oracle-database-operator.yaml +export AUTODISCOVER=$(shell cat $(PARAMETERS)|grep -v ^\#|grep AUTODISCOVER|cut -d : -f 2) +export CERT_MANAGER=$(shell cat $(PARAMETERS)|grep -v ^\#|grep CERT_MANAGER| awk -F ':' '{$$1="" ;\ + if ($$2 == "https" ) {printf("%s:%s",$$2,$$3) } else {printf ("%s",$$2)}}') +export TEST_EXEC_TIMEOUT=3m + +REST_SERVER=lrest +SKEY=tls.key +SCRT=tls.crt +CART=ca.crt +PRVKEY=ca.key +PUBKEY=public.pem +COMPANY=oracle +DBUSERFILE=dbuser.txt +DBPASSFILE=dbpass.txt +WBUSERFILE=wbuser.txt +WBPASSFILE=wbpass.txt +PDBUSRFILE=pdbusr.txt +PDBPWDFILE=pdbpwd.txt + +################# +## PARAMETERS ## +################# + +export ASSERTDELETION=true + +####################### +### BEGIN FILE LIST ### +####################### + +export LREST_POD=create_lrest_pod.yaml + +export LRPDBCRE1=create_pdb1_resource.yaml +export LRPDBCRE2=create_pdb2_resource.yaml + +export LRPDBCLOSE1=close_pdb1_resource.yaml +export LRPDBCLOSE2=close_pdb2_resource.yaml +export LRPDBCLOSE3=close_pdb3_resource.yaml + +export LRPDBOPEN1=open_pdb1_resource.yaml +export LRPDBOPEN2=open_pdb2_resource.yaml +export LRPDBOPEN3=open_pdb3_resource.yaml + +export LRPDBCLONE1=clone_pdb1_resource.yaml +export LRPDBCLONE2=clone_pdb2_resource.yaml + +export LRPDBDELETE1=delete_pdb1_resource.yaml +export LRPDBDELETE2=delete_pdb2_resource.yaml +export LRPDBDELETE3=delete_pdb3_resource.yaml + +export LRPDBUNPLUG1=unplug_pdb1_resource.yaml +export LRPDBPLUG1=plug_pdb1_resource.yaml + +export LRPDBMAP1=map_pdb1_resource.yaml +export LRPDBMAP2=map_pdb2_resource.yaml +export LRPDBMAP3=map_pdb3_resource.yaml + +export LRPDBMAP1=map_pdb1_resource.yaml +export LRPDBMAP2=map_pdb2_resource.yaml +export LRPDBMAP3=map_pdb3_resource.yaml + +export SECURITYCTX=security_context.yaml +export NODE_RBAC=node_rbac.yaml + +export ALTERSYSTEMYAML=altersystem_pdb1_resource.yaml +export CONFIG_MAP=config_map_pdb.yaml +export CONFIG_MAP_SQL=config_map_plsql.yaml + + + + +##BINARIES +export KUBECTL=/usr/bin/kubectl +OPENSSL=/usr/bin/openssl +ECHO=/usr/bin/echo +RM=/usr/bin/rm +CP=/usr/bin/cp +TAR=/usr/bin/tar +MKDIR=/usr/bin/mkdir +SED=/usr/bin/sed +DIFF=/usr/bin/diff +MAKE=/usr/bin/make +MAKEFILE=./makefile + +check: + @printf "LRESTIMG...............:%s\n" $(LRESTIMG) + @printf "OPRIMG.................:%s\n" $(OPRIMG) + @printf "TNSALIAS...............:%.60s....\n" $(TNSALIAS) + @printf "DBUSER.................:%s\n" $(DBUSER) + @printf "DBPASS.................:%s\n" $(DBPASS) + @printf "WBUSER.................:%s\n" $(WBUSER) + @printf "WBPASS.................:%s\n" $(WBPASS) + @printf "PDBUSR.................:%s\n" $(PDBUSR) + @printf "PDBPWD.................:%s\n" $(PDBPWD) + @printf "PDBNAMESPACE...........:%s\n" $(PDBNAMESPACE) + @printf "LRSNAMESPACE...........:%s\n" $(LRSNAMESPACE) + @printf "COMPANY................:%s\n" $(COMPANY) + @printf "SERVICENAMEACCOUNT.....:%s\n" $(SERVICENAM) + @printf "APIVERSION.............:%s\n" $(APIVERSION) + @printf "PLSQLMAP...............:%s\n" $(PLSQLMAP) + @printf "AUTODISCOVER...........:%s\n" $(AUTODISCOVER) + @printf "ORACLE_OPERATOR_YAML...:%s\n" $(ORACLE_OPERATOR_YAML) + @printf "CODE_TREE..............:%s\n" $(CODE_TREE) + @printf "CERT_MANAGER...........:%s\n" $(CERT_MANAGER) + @printf "OPENSHIFT..............:%s\n" $(OPENSHIFT) + +define msg +@printf "\033[31;7m%s\033[0m\r" "......................................]" +@printf "\033[31;7m[\xF0\x9F\x91\x89 %s\033[0m\n" $(1) +endef + +define _gendoc +echo producing markdown tables +cat ./makefile |awk 'BEGIN { PRINTLINE=0; } +{ +if (( PRINTLINE==1 ) && ( $0 !~ /+-----/ ) && ( $0 !~ / TABLE/)) { print; } +if ( $0 ~ /BEGIN TABLE/ ) { PRINTLINE=1; } +if ( $0 ~ /END TABLE/ ) { PRINTLINE=0; } +} ' |sed 's/#//g' > toc_targets.md +cat toc_targets.md +endef + +export gendoc = $(value _gendoc) +docs: + eval "$$gendoc" + +tls: + $(call msg,"TLS GENERATION") + #$(OPENSSL) genrsa -out $(PRVKEY) 2048 + $(OPENSSL) genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_keygen_pubexp:65537 > $(PRVKEY) + $(OPENSSL) req -new -x509 -days 365 -key $(PRVKEY) \ + -subj "/C=CN/ST=GD/L=SZ/O=$(COMPANY), Inc./CN=$(COMPANY) Root CA" -out ca.crt + $(OPENSSL) req -newkey rsa:2048 -nodes -keyout $(SKEY) -subj \ + "/C=CN/ST=GD/L=SZ/O=$(COMPANY), Inc./CN=cdb-dev-$(REST_SERVER).$(LRSNAMESPACE)" -out server.csr + $(ECHO) "subjectAltName=DNS:cdb-dev-$(REST_SERVER).$(LRSNAMESPACE)" > extfile.txt + $(OPENSSL) x509 -req -extfile extfile.txt -days 365 -in server.csr -CA ca.crt -CAkey $(PRVKEY) -CAcreateserial -out $(SCRT) + $(OPENSSL) rsa -in $(PRVKEY) -outform PEM -pubout -out $(PUBKEY) + +secrets: tls delsecrets + $(call msg,"CREATING NEW TLS/PRVKEY/PUBKEY SECRETS") + $(KUBECTL) create secret tls db-tls --key="$(SKEY)" --cert="$(SCRT)" -n $(LRSNAMESPACE) + $(KUBECTL) create secret generic db-ca --from-file="$(CART)" -n $(LRSNAMESPACE) + $(KUBECTL) create secret tls db-tls --key="$(SKEY)" --cert="$(SCRT)" -n $(PDBNAMESPACE) + $(KUBECTL) create secret generic db-ca --from-file="$(CART)" -n $(PDBNAMESPACE) + #$(KUBECTL) create secret tls prvkey --key="$(PRVKEY)" --cert=ca.crt -n $(LRSNAMESPACE) + $(KUBECTL) create secret generic pubkey --from-file=publicKey=$(PUBKEY) -n $(LRSNAMESPACE) + $(KUBECTL) create secret generic prvkey --from-file=privateKey=$(PRVKEY) -n $(LRSNAMESPACE) + $(KUBECTL) create secret generic prvkey --from-file=privateKey="$(PRVKEY)" -n $(PDBNAMESPACE) + $(call msg,"CREATING NEW CREDENTIAL SECRETS") + @$(ECHO) $(DBUSER) > $(DBUSERFILE) + @$(ECHO) $(DBPASS) > $(DBPASSFILE) + @$(ECHO) $(WBUSER) > $(WBUSERFILE) + @$(ECHO) $(WBPASS) > $(WBPASSFILE) + @$(ECHO) $(PDBUSR) > $(PDBUSRFILE) + @$(ECHO) $(PDBPWD) > $(PDBPWDFILE) + $(OPENSSL) pkeyutl -encrypt -pubin -inkey $(PUBKEY) -in $(DBUSERFILE) -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 |base64 > e_$(DBUSERFILE) + $(OPENSSL) pkeyutl -encrypt -pubin -inkey $(PUBKEY) -in $(DBPASSFILE) -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 |base64 > e_$(DBPASSFILE) + $(OPENSSL) pkeyutl -encrypt -pubin -inkey $(PUBKEY) -in $(WBUSERFILE) -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 |base64 > e_$(WBUSERFILE) + $(OPENSSL) pkeyutl -encrypt -pubin -inkey $(PUBKEY) -in $(WBPASSFILE) -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 |base64 > e_$(WBPASSFILE) + $(OPENSSL) pkeyutl -encrypt -pubin -inkey $(PUBKEY) -in $(PDBUSRFILE) -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 |base64 > e_$(PDBUSRFILE) + $(OPENSSL) pkeyutl -encrypt -pubin -inkey $(PUBKEY) -in $(PDBPWDFILE) -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 |base64 > e_$(PDBPWDFILE) + $(KUBECTL) create secret generic dbuser --from-file=e_$(DBUSERFILE) -n $(LRSNAMESPACE) + $(KUBECTL) create secret generic dbpass --from-file=e_$(DBPASSFILE) -n $(LRSNAMESPACE) + $(KUBECTL) create secret generic wbuser --from-file=e_$(WBUSERFILE) -n $(LRSNAMESPACE) + $(KUBECTL) create secret generic wbpass --from-file=e_$(WBPASSFILE) -n $(LRSNAMESPACE) + $(KUBECTL) create secret generic wbuser --from-file=e_$(WBUSERFILE) -n $(PDBNAMESPACE) + $(KUBECTL) create secret generic wbpass --from-file=e_$(WBPASSFILE) -n $(PDBNAMESPACE) + $(KUBECTL) create secret generic pdbusr --from-file=e_$(PDBUSRFILE) -n $(PDBNAMESPACE) + $(KUBECTL) create secret generic pdbpwd --from-file=e_$(PDBPWDFILE) -n $(PDBNAMESPACE) + $(RM) $(SKEY) $(SCRT) $(CART) $(PRVKEY) $(PUBKEY) server.csr extfile.txt ca.srl \ + $(DBUSERFILE) $(DBPASSFILE) $(WBUSERFILE) $(WBPASSFILE) $(PDBUSRFILE) $(PDBPWDFILE)\ + e_$(DBUSERFILE) e_$(DBPASSFILE) e_$(WBUSERFILE) e_$(WBPASSFILE) e_$(PDBUSRFILE) e_$(PDBPWDFILE) + $(KUBECTL) get secrets -n $(LRSNAMESPACE) + $(KUBECTL) get secrets -n $(PDBNAMESPACE) + +delsecrets: + $(call msg,"CLEAN OLD SECRETS") + $(eval SECRETSP:=$(shell kubectl get secrets -n $(PDBNAMESPACE) -o custom-columns=":metadata.name" --no-headers) ) + $(eval SECRETSL:=$(shell kubectl get secrets -n $(LRSNAMESPACE) -o custom-columns=":metadata.name" --no-headers) ) + @[ "${SECRETSP}" ] && ( \ + printf "Deleteing secrets in namespace -n $(PDBNAMESPACE)\n") &&\ + ($(KUBECTL) delete secret $(SECRETSP) -n $(PDBNAMESPACE))\ + || ( echo "No screts in namespace $(PDBNAMESPACE)") + @[ "${SECRETSL}" ] && ( \ + printf "Deleteing secrets in namespace -n $(LRSNAMESPACE)\n") &&\ + ($(KUBECTL) delete secret $(SECRETSL) -n $(LRSNAMESPACE))\ + || ( echo "No screts in namespace $(PDBNAMESPACE)") + +cleanCert: + $(RM) $(SKEY) $(SCRT) $(CART) $(PRVKEY) $(PUBKEY) server.csr extfile.txt ca.srl \ + $(DBUSERFILE) $(DBPASSFILE) $(WBUSERFILE) $(WBPASSFILE) $(PDBUSRFILE) $(PDBPWDFILE)\ + e_$(DBUSERFILE) e_$(DBPASSFILE) e_$(WBUSERFILE) e_$(WBPASSFILE) e_$(PDBUSRFILE) e_$(PDBPWDFILE) + +### YAML FILE SECTION ### +define _opr + + +export OPBASENAME=`basename ${ORACLE_OPERATOR_YAML}` +echo "code path..........: ${ORACLE_OPERATOR_YAML}" +echo "basename...........: ${OPBASENAME}" + +if [[ ${CODE_TREE:-"OFF"} == "OFF" ]] ; then +echo "code tree not available" +if [[ ! -f ${OPBASENAME} ]]; then + echo "ERROR: oracle-database-operator.yaml not available" + exit 1 + fi +cp ${OPBASENAME} ${OPBASENAME}.ORIG +else +echo "code tree..........: ${CODE_TREE}" +cp ${ORACLE_OPERATOR_YAML} . +cp ${OPBASENAME} ${OPBASENAME}.ORIG +fi + +sed -i 's/value: ""/value: '${OPRNAMESPACE}','${PDBNAMESPACE}','${LRSNAMESPACE}'/g' `basename ${ORACLE_OPERATOR_YAML}` + +if [[ ${OPRIMG:-"OFF"} != "OFF" ]] ; then + export IMG=`cat ${OPBASENAME}| awk '( $1 == "image:" ) && ( NF == 2) { printf("%s",$2); }'` + echo "ORIGINALIMG........: ${IMG}" + echo "NEWIMAG............: ${OPRIMG}" + sed -i 's!image: '${IMG}'!image: '${OPRIMG}'!g' ${OPBASENAME} +fi + +diff ${OPBASENAME} ${OPBASENAME}.ORIG||true +endef + +export opr = $(value _opr) + +opsetup: prsyaml crtmgrappl nscrt bndappl opapply rbacap + +#opclean: rbacdl opdelete bnddel crtmgrdel +opclean: rbacdl bnddel crtmgrdel opdelete + +opapply: + @$(call msg,"Applying operator yaml file") + $(KUBECTL) apply -f `basename ${ORACLE_OPERATOR_YAML}` + +opdelete: + - $(KUBECTL) delete -f `basename ${ORACLE_OPERATOR_YAML}` + +prsyaml: + @ eval "$$opr" + +### Certmanager ### +crtmgrappl: + @$(call msg,"Install cert-manager") + $(KUBECTL) apply -f $(CERT_MANAGER) + $(KUBECTL) wait --for jsonpath='{.webhooks[0].clientConfig.caBundle'} \ + validatingwebhookconfiguration cert-manager-webhook --timeout=$(TEST_EXEC_TIMEOUT) + @$(call msg,"Cert manager application completed") + +crtmgrdel: + @$(call msg,"Deleting cert manager $(CERT_MANAGER)") + - $(KUBECTL) delete -f $(CERT_MANAGER) + +### Namespaces creation ### +nscrt: + @$(call msg,"Namespace scope: creating namespace") + - $(KUBECTL) create namespace $(LRSNAMESPACE) + - $(KUBECTL) create namespace $(PDBNAMESPACE) + +### Namespace deletion ### +nsdel: + @$(call msg,"Deleting namespaces $(LRSNAMESPACE) $(PDBNAMESPACE)") + $(KUBECTL) delete namespace $(LRSNAMESPACE) + $(KUBECTL) delete namespace $(PDBNAMESPACE) + +### Namespace Scoped Deployment ### +bndappl: + @$(call msg," Namespace Scoped Deployment: Binding $(PDBNAMESPACE) $(LRSNAMESPACE)") + - $(KUBECTL) apply -f $(PDBNAMESPACE)_binding.yaml + - $(KUBECTL) apply -f $(LRSNAMESPACE)_binding.yaml + +### Namespace Scope Remove ### +bnddel: + @$(call msg," Namespace Scoped remove $(PDBNAMESPACE) $(LRSNAMESPACE)") + - $(KUBECTL) delete -f $(PDBNAMESPACE)_binding.yaml + - $(KUBECTL) delete -f $(LRSNAMESPACE)_binding.yaml + +### Cluster Role Binfing ### +rbacap: + @$(call msg," ClusterRole and ClusterRoleBinding for NodePort services") + - $(KUBECTL) apply -f ${NODE_RBAC} + +### Remove Cluster Role Binding ### +rbacdl: + @$(call msg," Clean $(NODE_RBAC)") + - $(KUBECTL) delete -f ${NODE_RBAC} + + +define _script00 +cat < authsection.yaml + adminpdbUser: + secret: + secretName: "pdbusr" + key: "e_pdbusr.txt" + adminpdbPass: + secret: + secretName: "pdbpwd" + key: "e_pdbpwd.txt" + lrpdbTlsKey: + secret: + secretName: "db-tls" + key: "tls.key" + lrpdbTlsCrt: + secret: + secretName: "db-tls" + key: "tls.crt" + lrpdbTlsCat: + secret: + secretName: "db-ca" + key: "ca.crt" + webServerUser: + secret: + secretName: "wbuser" + key: "e_wbuser.txt" + webServerPwd: + secret: + secretName: "wbpass" + key: "e_wbpass.txt" + cdbPrvKey: + secret: + secretName: "prvkey" + key: "privateKey" +EOF + + +cat < ${PDBNAMESPACE}_binding.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: oracle-database-operator-oracle-database-operator-manager-rolebinding1 + namespace: ${PDBNAMESPACE} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: oracle-database-operator-manager-role +subjects: +- kind: ServiceAccount + name: default + namespace: oracle-database-operator-system +EOF + +cat < ${LRSNAMESPACE}_binding.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: oracle-database-operator-oracle-database-operator-manager-rolebinding2 + namespace: ${LRSNAMESPACE} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: oracle-database-operator-manager-role +subjects: +- kind: ServiceAccount + name: default + namespace: oracle-database-operator-system +EOF + +cat < ${NODE_RBAC} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: oracle-database-operator-manager-role-node +rules: +- apiGroups: + - "" + resources: + - nodes + verbs: + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: oracle-database-operator-manager-role-node-cluster-role-binding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: oracle-database-operator-manager-role-node +subjects: +- kind: ServiceAccount + name: default + namespace: oracle-database-operator-system +--- +EOF + +endef +export script00 = $(value _script00) +secyaml: + @ eval "$$script00" + + +#echo lrest pod creation +define _script01 +cat < ${LREST_POD} +apiVersion: database.oracle.com/${APIVERSION} +kind: LREST +metadata: + name: cdb-dev + namespace: ${LRSNAMESPACE} +spec: + cdbName: "DB12" + lrestImage: ${LRESTIMG} + lrestImagePullPolicy: "Always" + dbTnsurl : ${TNSALIAS} + replicas: 1 + deletePdbCascade: true + autodiscover: ${AUTODISCOVER} + namespaceAutoDiscover: ${PDBNAMESPACE} + clusterIp: false + loadBalancer: false + trace_level_client : 0 +EOF + +if [[ ${OPENSHIFT} == "true" ]];then +cat <> ${LREST_POD} + serviceAccountName: ${SERVICENAM} +EOF +fi + +cat <> ${LREST_POD} + cdbAdminUser: + secret: + secretName: "dbuser" + key: "e_dbuser.txt" + cdbAdminPwd: + secret: + secretName: "dbpass" + key: "e_dbpass.txt" + webServerUser: + secret: + secretName: "wbuser" + key: "e_wbuser.txt" + webServerPwd: + secret: + secretName: "wbpass" + key: "e_wbpass.txt" + cdbTlsKey: + secret: + secretName: "db-tls" + key: "tls.key" + cdbTlsCrt: + secret: + secretName: "db-tls" + key: "tls.crt" + cdbTlsCat: + secret: + secretName: "db-ca" + key: "ca.crt" + cdbPubKey: + secret: + secretName: "pubkey" + key: "publicKey" + cdbPrvKey: + secret: + secretName: "prvkey" + key: "privateKey" +EOF + +endef +export script01 = $(value _script01) + + +define _script02 + +cat <${LRPDBCRE1} +apiVersion: database.oracle.com/${APIVERSION} +kind: LRPDB +metadata: + name: pdb1 + namespace: ${PDBNAMESPACE} + labels: + cdb: cdb-dev +spec: + cdbResName: "cdb-dev" + cdbNamespace: "${LRSNAMESPACE}" + cdbName: "DB12" + pdbName: "pdbdev" + imperativeLrpdbDeletion: ${ASSERTDELETION} + fileNameConversions: "NONE" + unlimitedStorage: false + pdbconfigmap: "config-map-pdb" + tdeImport: false + totalSize: "2G" + tempSize: "800M" +EOF + +cat < ${LRPDBCRE2} +apiVersion: database.oracle.com/${APIVERSION} +kind: LRPDB +metadata: + name: pdb2 + namespace: ${PDBNAMESPACE} + labels: + cdb: cdb-dev +spec: + cdbResName: "cdb-dev" + cdbNamespace: "${LRSNAMESPACE}" + cdbName: "DB12" + pdbName: "pdbprd" + imperativeLrpdbDeletion: ${ASSERTDELETION} + fileNameConversions: "NONE" + unlimitedStorage: false + pdbconfigmap: "config-map-pdb" + tdeImport: false + totalSize: "2G" + tempSize: "800M" +EOF + +cat <${LRPDBOPEN1} +apiVersion: database.oracle.com/${APIVERSION} +kind: LRPDB +metadata: + name: pdb1 + namespace: ${PDBNAMESPACE} + labels: + cdb: cdb-dev +spec: + cdbResName: "cdb-dev" + cdbNamespace: "${LRSNAMESPACE}" + cdbName: "DB12" + pdbName: "pdbdev" + pdbState: "OPEN" + modifyOption: "READ WRITE" +EOF + +cat <${LRPDBOPEN2} +apiVersion: database.oracle.com/${APIVERSION} +kind: LRPDB +metadata: + name: pdb2 + namespace: ${PDBNAMESPACE} + labels: + cdb: cdb-dev +spec: + cdbResName: "cdb-dev" + cdbNamespace: "${LRSNAMESPACE}" + cdbName: "DB12" + pdbName: "pdbprd" + pdbState: "OPEN" + modifyOption: "READ WRITE" +EOF + +cat <${LRPDBOPEN3} +apiVersion: database.oracle.com/${APIVERSION} +kind: LRPDB +metadata: + name: pdb3 + namespace: ${PDBNAMESPACE} + labels: + cdb: cdb-dev +spec: + cdbResName: "cdb-dev" + cdbNamespace: "${LRSNAMESPACE}" + cdbName: "DB12" + pdbName: "new_clone" + pdbState: "OPEN" + modifyOption: "READ WRITE" +EOF + +cat <${LRPDBCLOSE1} +apiVersion: database.oracle.com/${APIVERSION} +kind: LRPDB +metadata: + name: pdb1 + namespace: ${PDBNAMESPACE} + labels: + cdb: cdb-dev +spec: + cdbResName: "cdb-dev" + cdbNamespace: "${LRSNAMESPACE}" + cdbName: "DB12" + pdbName: "pdbdev" + pdbState: "CLOSE" + modifyOption: "IMMEDIATE" +EOF + +cat <${LRPDBCLOSE2} +apiVersion: database.oracle.com/${APIVERSION} +kind: LRPDB +metadata: + name: pdb2 + namespace: ${PDBNAMESPACE} + labels: + cdb: cdb-dev +spec: + cdbResName: "cdb-dev" + cdbNamespace: "${LRSNAMESPACE}" + cdbName: "DB12" + pdbName: "pdbprd" + pdbState: "CLOSE" + modifyOption: "IMMEDIATE" +EOF + +cat <${LRPDBCLOSE3} +apiVersion: database.oracle.com/${APIVERSION} +kind: LRPDB +metadata: + name: pdb3 + namespace: ${PDBNAMESPACE} + labels: + cdb: cdb-dev +spec: + cdbResName: "cdb-dev" + cdbNamespace: "${LRSNAMESPACE}" + cdbName: "DB12" + pdbName: "new_clone" + pdbState: "CLOSE" + modifyOption: "IMMEDIATE" +EOF + +cat < ${LRPDBCLONE1} +apiVersion: database.oracle.com/${APIVERSION} +kind: LRPDB +metadata: + name: pdb3 + namespace: ${PDBNAMESPACE} + labels: + cdb: cdb-dev +spec: + cdbResName: "cdb-dev" + cdbNamespace: "${LRSNAMESPACE}" + cdbName: "DB12" + pdbName: "new_clone" + srcPdbName: "pdbdev" + fileNameConversions: "NONE" + totalSize: "UNLIMITED" + tempSize: "UNLIMITED" + pdbconfigmap: "config-map-pdb" + imperativeLrpdbDeletion: true +EOF + +cat < ${LRPDBCLONE2} +apiVersion: database.oracle.com/${APIVERSION} +kind: LRPDB +metadata: + name: pdb4 + namespace: ${PDBNAMESPACE} + labels: + cdb: cdb-dev +spec: + cdbResName: "cdb-dev" + cdbNamespace: "${LRSNAMESPACE}" + cdbName: "DB12" + pdbName: "new_clone2" + srcPdbName: "pdbprd" + fileNameConversions: "NONE" + totalSize: "UNLIMITED" + tempSize: "UNLIMITED" + pdbconfigmap: "config-map-pdb" + imperativeLrpdbDeletion: ${ASSERTDELETION} +EOF + +cat < ${LRPDBDELETE1} +apiVersion: database.oracle.com/${APIVERSION} +kind: LRPDB +metadata: + name: pdb1 + namespace: ${PDBNAMESPACE} + labels: + cdb: cdb-dev +spec: + cdbResName: "cdb-dev" + cdbNamespace: "${LRSNAMESPACE}" + pdbName: "pdbdev" + pdbState: "DELETE" + dropAction: "INCLUDING" + imperativeLrpdbDeletion: ${ASSERTDELETION} +EOF + +cat < ${LRPDBDELETE2} +apiVersion: database.oracle.com/${APIVERSION} +kind: LRPDB +metadata: + name: pdb2 + namespace: ${PDBNAMESPACE} + labels: + cdb: cdb-dev +spec: + cdbResName: "cdb-dev" + cdbNamespace: "${LRSNAMESPACE}" + pdbName: "pdbprd" + pdbState: "DELETE" + dropAction: "INCLUDING" + imperativeLrpdbDeletion: ${ASSERTDELETION} +EOF + +cat < ${LRPDBDELETE3} +apiVersion: database.oracle.com/${APIVERSION} +kind: LRPDB +metadata: + name: pdb3 + namespace: ${PDBNAMESPACE} + labels: + cdb: cdb-dev +spec: + cdbResName: "cdb-dev" + cdbNamespace: "${LRSNAMESPACE}" + pdbName: "new_clone" + pdbState: "DELETE" + dropAction: "INCLUDING" + imperativeLrpdbDeletion: ${ASSERTDELETION} +EOF + +cat < ${LRPDBUNPLUG1} +apiVersion: database.oracle.com/${APIVERSION} +kind: LRPDB +metadata: + name: pdb1 + namespace: ${PDBNAMESPACE} + labels: + cdb: cdb-dev +spec: + cdbResName: "cdb-dev" + cdbNamespace: "${LRSNAMESPACE}" + cdbName: "DB12" + pdbName: "pdbdev" + pdbState: "UNPLUG" + xmlFileName: "/var/tmp/pdb.$$.xml" +EOF + +cat <${LRPDBPLUG1} +apiVersion: database.oracle.com/${APIVERSION} +kind: LRPDB +metadata: + name: pdb1 + namespace: ${PDBNAMESPACE} + labels: + cdb: cdb-dev +spec: + cdbResName: "cdb-dev" + cdbNamespace: "${LRSNAMESPACE}" + cdbName: "DB12" + pdbName: "pdbdev" + pdbState: "PLUG" + xmlFileName: "/var/tmp/pdb.$$.xml" + fileNameConversions: "NONE" + sourceFileNameConversions: "NONE" + copyAction: "MOVE" + totalSize: "1G" + tempSize: "100M" + imperativeLrpdbDeletion: true + pdbconfigmap: "config-map-pdb" +EOF + +cat <${LRPDBMAP1} +apiVersion: database.oracle.com/${APIVERSION} +kind: LRPDB +metadata: + name: pdb1 + namespace: ${PDBNAMESPACE} + labels: + cdb: cdb-dev +spec: + cdbResName: "cdb-dev" + cdbNamespace: "${LRSNAMESPACE}" + cdbName: "DB12" + pdbName: "pdbdev" + imperativeLrpdbDeletion: true + fileNameConversions: "NONE" + totalSize: "1G" + tempSize: "100M" +EOF + +cat <${LRPDBMAP2} +apiVersion: database.oracle.com/${APIVERSION} +kind: LRPDB +metadata: + name: pdb2 + namespace: ${PDBNAMESPACE} + labels: + cdb: cdb-dev +spec: + cdbResName: "cdb-dev" + cdbNamespace: "${LRSNAMESPACE}" + cdbName: "DB12" + pdbName: "pdbprd" + imperativeLrpdbDeletion: true + fileNameConversions: "NONE" + totalSize: "1G" + tempSize: "100M" +EOF + + +cat <${LRPDBMAP3} +apiVersion: database.oracle.com/${APIVERSION} +kind: LRPDB +metadata: + name: pdb3 + namespace: ${PDBNAMESPACE} + labels: + cdb: cdb-dev +spec: + cdbResName: "cdb-dev" + cdbNamespace: "${LRSNAMESPACE}" + cdbName: "DB12" + pdbName: "new_clone" + imperativeLrpdbDeletion: true + fileNameConversions: "NONE" + totalSize: "1G" + tempSize: "100M" +EOF + +cat <${CONFIG_MAP} +apiVersion: v1 +kind: ConfigMap +metadata: + name: config-map-pdb + namespace: ${PDBNAMESPACE} +data: + rdbmsparameters.txt: | + session_cached_cursors;100;spfile + open_cursors;100;spfile + db_file_multiblock_read_count;16;spfile + test_invalid_parameter;16;spfile +EOF + + +cat < ${ALTERSYSTEMYAML} +apiVersion: database.oracle.com/${APIVERSION} +kind: LRPDB +metadata: + name: pdb1 + namespace: ${PDBNAMESPACE} + labels: + cdb: cdb-dev +spec: + cdbResName: "cdb-dev" + cdbNamespace: "${LRSNAMESPACE}" + cdbName: "DB12" + pdbName: "pdbdev" + alterSystemParameter : "cpu_count" + alterSystemValue : "3" + parameterScope : "memory" +EOF + + +cat < ${CONFIG_MAP_SQL} +apiVersion: v1 +kind: ConfigMap +metadata: + name: ${PLSQLMAP} + namespace: ${PDBNAMESPACE} +data: + plblock1.sql: | + create user k8stestuser identified by tkstestuser + plblock2.sql: | + grant dba to k8stestuser + plblock3.sql: | + create table k8stestuser.rndnum (c1 number ,c2 number) + plblock4.sql: | + alter session set events '942 trace name errorstack level 3' + plblock5.sql: | + create or replace procedure k8stestuser.gennum + as + begin + for cnt in 1..100 + loop + insert into k8stestuser.rndnum values (dbms_random.value(1,100), + dbms_random.value(1,100)); + end loop; + commit; + end; + plblock6.sql: | + begin + k8stestuser.gennum; + end; +EOF + + +## Auth information +for _file in ${LRPDBCRE1} ${LRPDBCRE2} ${LRPDBOPEN1} ${LRPDBOPEN2} ${LRPDBOPEN3} ${LRPDBCLOSE1} ${LRPDBCLOSE2} ${LRPDBCLOSE3} ${LRPDBCLONE1} ${LRPDBCLONE2} ${LRPDBDELETE1} ${LRPDBDELETE2} ${LRPDBDELETE3} ${LRPDBUNPLUG1} ${LRPDBPLUG1} ${LRPDBMAP1} ${LRPDBMAP2} ${LRPDBMAP3} ${ALTERSYSTEMYAML} +do +ls -ltr ${_file} + cat authsection.yaml >> ${_file} +done +rm authsection.yaml +endef + +define _script03 +cat <${SECURITYCTX} +# +# Copyright (c) 2024, Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. +# +--- + +# Create a Security Context Contraint +kind: SecurityContextConstraints +apiVersion: security.openshift.io/v1 +metadata: + name: lrest-scc +allowPrivilegedContainer: false +allowedCapabilities: + - SYS_NICE +runAsUser: + type: MustRunAs + uid: 54321 +seLinuxContext: + type: RunAsAny +fsGroup: + type: MustRunAs + ranges: + - min: 54321 + max: 54321 +supplementalGroups: + type: MustRunAs + ranges: + - min: 54321 + max: 54321 +--- + +# Create a Security Context Contraint +kind: SecurityContextConstraints +apiVersion: security.openshift.io/v1 +metadata: + name: lrest-root-user-scc +allowPrivilegedContainer: false +allowedCapabilities: + - SYS_NICE +runAsUser: + type: MustRunAsRange + uidRangeMin: 0 + uidRangeMax: 54321 +seLinuxContext: + type: RunAsAny +fsGroup: + type: MustRunAs + ranges: + - min: 0 + max: 54325 +supplementalGroups: + type: MustRunAs + ranges: + - min: 0 + max: 54321 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: lrest-sa + namespace: ${LRSNAMESPACE} +--- + +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: use-lrest-scc + namespace: ${LRSNAMESPACE} +rules: + - apiGroups: + - security.openshift.io + verbs: + - use + resources: + - securitycontextconstraints + resourceNames: + - lrest-scc + - lrest-root-user-scc +--- + +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: use-lrest-scc + namespace: ${LRSNAMESPACE} +subjects: + - kind: ServiceAccount + name: lrest-sa + namespace: ${LRSNAMESPACE} +roleRef: + kind: Role + name: use-lrest-scc + apiGroup: rbac.authorization.k8s.io +EOF + +endef + +define _envfile +cat <tkpc_mmk8slenv.sh +export TNSALIAS=${TNSALIAS} +export DBUSER=${DBUSER} +export DBPASS=${DBPASS} +export WBUSER=${WBUSER} +export WBPASS=${WBPASS} +export PDBUSR=${PDBUSR} +export PDBPWD=${PDBPWD} +export PDBNAMESPACE=${PDBNAMESPACE} +export LRSNAMESPACE=${LRSNAMESPACE} +export LRESTIMG=${LRESTIMG} +export COMPANY=${COMPANY} +export APIVERSION=${APIVERSION} +export SERVICENAM=${SERVICENAM} +export OPENSHIFT=${OPENSHIFT} +export PLSQLMAP=${PLSQLMAP} +export OPRNAMESPACE=oracle-database-operator-system +export CODE_TREE=${CODE_TREE} +export ORACLE_OPERATOR_YAML=${ORACLE_OPERATOR_YAML} +export AUTODISCOVER=${AUTODISCOVER} +export CERT_MANAGER=${CERT_MANAGER} +export TEST_EXEC_TIMEOUT=3m +EOF +endef + +export script02 = $(value _script02) +export script03 = $(value _script03) +export script04 = $(value _envfile) + + + +genyaml: secyaml + @ eval "$$script01" + @ eval "$$script02" + @ eval "$$script03" + @ eval "$$script04" + +cleanyaml: + - $(RM) $(LRPDBMAP3) $(LRPDBMAP2) $(LRPDBMAP1) $(LRPDBPLUG1) $(LRPDBUNPLUG1) $(LRPDBDELETE2) $(LRPDBDELETE1) $(LRPDBDELETE3) $(LRPDBCLONE2) $(LRPDBCLONE1) $(LRPDBCLOSE3) $(LRPDBCLOSE2) $(LRPDBCLOSE1) $(LRPDBOPEN3) $(LRPDBOPEN2) $(LRPDBOPEN1) $(LRPDBCRE2) $(LRPDBCRE1) $(LREST_POD) ${ALTERSYSTEMYAML} + - $(RM) ${CONFIG_MAP} ${PDBNAMESPACE}_binding.yaml ${LRSNAMESPACE}_binding.yaml + + + + +################# +### PACKAGING ### +################# + +pkg: + - $(RM) -rf /tmp/pkgtestplan + $(MKDIR) /tmp/pkgtestplan + $(CP) -R * /tmp/pkgtestplan + $(CP) $(ORACLE_OPERATOR_YAML) /tmp/pkgtestplan/ + $(TAR) -C /tmp -cvf ~/pkgtestplan_$(DATE).tar pkgtestplan + +################ +### diag ### +################ + +login: + $(KUBECTL) exec `$(KUBECTL) get pods -n $(LRSNAMESPACE)|grep rest|cut -d ' ' -f 1` -n $(LRSNAMESPACE) -it -- /bin/bash + + +reloadop: + echo "RESTARTING OPERATOR" + $(eval OP1 := $(shell $(KUBECTL) get pods -n $(OPRNAMESPACE)|grep oracle-database-operator-controller|head -1|cut -d ' ' -f 1 )) + $(eval OP2 := $(shell $(KUBECTL) get pods -n $(OPRNAMESPACE)|grep oracle-database-operator-controller|head -2|tail -1|cut -d ' ' -f 1 )) + $(eval OP3 := $(shell $(KUBECTL) get pods -n $(OPRNAMESPACE)|grep oracle-database-operator-controller|tail -1|cut -d ' ' -f 1 )) + $(KUBECTL) get pod $(OP1) -n $(OPRNAMESPACE) -o yaml | kubectl replace --force -f - + $(KUBECTL) get pod $(OP2) -n $(OPRNAMESPACE) -o yaml | kubectl replace --force -f - + $(KUBECTL) get pod $(OP3) -n $(OPRNAMESPACE) -o yaml | kubectl replace --force -f - + + +dumpoperator: + @$(eval TMPSP := $(shell date "+%y%m%d%H%M%S" )) + @$(eval DIAGFILE := ./opdmp.$(TMPSP)) + @>$(DIAGFILE) + @echo "OPERATOR DUMP" >> $(DIAGFILE) + @echo "~~~~~~~~~~~~~" >> $(DIAGFILE) + $(KUBECTL) logs pod/`$(KUBECTL) get pods -n $(OPRNAMESPACE)|grep oracle-database-operator-controller|head -1|cut -d ' ' -f 1` -n $(OPRNAMESPACE) >>$(DIAGFILE) + $(KUBECTL) logs pod/`$(KUBECTL) get pods -n $(OPRNAMESPACE)|grep oracle-database-operator-controller|head -2|tail -1 | cut -d ' ' -f 1` -n $(OPRNAMESPACE) >>$(DIAGFILE) + $(KUBECTL) logs pod/`$(KUBECTL) get pods -n $(OPRNAMESPACE)|grep oracle-database-operator-controller|tail -1|cut -d ' ' -f 1` -n $(OPRNAMESPACE) >>$(DIAGFILE) + +dumplrest: + @$(eval TMPSP := $(shell date "+%y%m%d%H%M%S" )) + @$(eval DIAGFILE := ./lrestdump.$(TMPSP)) + @>$(DIAGFILE) + $(KUBECTL) logs `$(KUBECTL) get pods -o custom-columns=:metadata.name -n $(LRSNAMESPACE) --no-headers ` -n $(LRSNAMESPACE) |strings > $(DIAGFILE) + +loglrest: + $(KUBECTL) logs -f `$(KUBECTL) get pods -o custom-columns=:metadata.name -n $(LRSNAMESPACE) --no-headers ` -n $(LRSNAMESPACE) + + +listimage: + $(KUBECTL) get pods --all-namespaces -o jsonpath="{.items[*].spec['initContainers', 'containers'][*].image}" | tr -s '[[:space:]]' '\n' | sort | uniq -c + +getvaliduid: + $(KUBECTL) get namespace $(LRSNAMESPACE) -ojson | jq '.metadata.annotations | with_entries(select(.key | contains("sa.scc")))' + +mgrrestart: + $(KUBECTL) rollout restart -n $(OPRNAMESPACE) deployment oracle-database-operator-controller-manager + + +####################################################### +#### TEST SECTION #### +####################################################### +CREATEMSG="create:[op completed]" +OPENMSG="open:[op. completed]" +CLOSEMSG="close:[op. completed]" +CLONEMSG="clone:[op. completed]" +PLUGMSG="plug:[op. completed]" + +cleanlrest: + @$(call msg,"lrest deletion") + - $(KUBECTL) delete lrest cdb-dev -n $(LRSNAMESPACE) + - $(KUBECTL) delete replicaset.apps/cdb-dev-lrest-rs -n $(LRSNAMESPACE) + $(KUBECTL) wait --for=delete lrest cdb-dev -n $(LRSNAMESPACE) --timeout=10m + $(KUBECTL) wait --for=delete pod/* -n $(LRSNAMESPACE) --timeout=10m + +toplrest: + $(KUBECTL) top pod `$(KUBECTL) get pods -o custom-columns=:metadata.name -n $(LRSNAMESPACE) --no-headers ` -n $(LRSNAMESPACE) + + +run00: cleanlrest + @$(call msg,"lrest pod creation") + $(KUBECTL) apply -f $(LREST_POD) + $(KUBECTL) wait --for jsonpath='{.status.phase'}="Ready" lrest cdb-dev -n $(LRSNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) + @$(call msg,"lrest pod completed") + $(KUBECTL) get lrest -n $(LRSNAMESPACE) + $(KUBECTL) get pod -n $(LRSNAMESPACE) + $(KUBECTL) logs `$(KUBECTL) get pods -o custom-columns=:metadata.name -n $(LRSNAMESPACE) --no-headers ` -n $(LRSNAMESPACE) + +run01.1: + @$(call msg,"lrpdb pdb1 creation") + $(KUBECTL) apply -f $(LRPDBCRE1) + $(KUBECTL) wait --for jsonpath='{.status.msg'}=$(CREATEMSG) lrpdb pdb1 -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) + @$(call msg, "lrpdb pdb1 creation completed") + $(KUBECTL) get lrpdb pdb1 -n $(PDBNAMESPACE) + +run01.2: + @$(call msg, "lrpdb pdb2 creation") + $(KUBECTL) apply -f $(LRPDBCRE2) + $(KUBECTL) wait --for jsonpath='{.status.msg'}=$(CREATEMSG) lrpdb pdb2 -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) + @$(call msg, "lrpdb pdb2 creation completed") + $(KUBECTL) get lrpdb pdb2 -n $(PDBNAMESPACE) + +open: + @$(call msg, "lrpdb $(LRPDBNAME) open") + $(KUBECTL) patch lrpdb $(LRPDBNAME) -n $(PDBNAMESPACE) -p \ + '{"spec":{"pdbState":"OPEN","modifyOption":"READ WRITE","modifyOption2":"NONE"}}' --type=merge + $(KUBECTL) wait --for jsonpath='{.status.msg'}=$(OPENMSG) lrpdb $(LRPDBNAME) -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) + @$(call msg, "lrpdb $(LRPDBNAME) completed") + $(KUBECTL) get lrpdb $(LRPDBNAME) -n $(PDBNAMESPACE) + +openrestricted: + @$(call msg, "lrpdb $(LRPDBNAME) open") + $(KUBECTL) patch lrpdb $(LRPDBNAME) -n $(PDBNAMESPACE) -p \ + '{"spec":{"pdbState":"OPEN","modifyOption":"READ WRITE","modifyOption2":"RESTRICTED"}}' --type=merge + $(KUBECTL) wait --for jsonpath='{.status.msg'}=$(OPENMSG) lrpdb $(LRPDBNAME) -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) + @$(call msg, "lrpdb $(LRPDBNAME) completed") + $(KUBECTL) get lrpdb $(LRPDBNAME) -n $(PDBNAMESPACE) + + +close: + @$(call msg, "lrpdb $(LRPDBNAME) close") + $(KUBECTL) patch lrpdb $(LRPDBNAME) -n $(PDBNAMESPACE) -p \ + '{"spec":{"pdbState":"CLOSE","modifyOption":"IMMEDIATE"}}' --type=merge + $(KUBECTL) wait --for jsonpath='{.status.msg'}=$(CLOSEMSG) lrpdb $(LRPDBNAME) -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) + @$(call msg, "lrpdb $(LRPDBNAME) completed") + $(KUBECTL) get lrpdb $(LRPDBNAME) -n $(PDBNAMESPACE) + +altercpu: + @$(call msg, "lrpdb $(LRPDBNAME) alter cpu") + $(KUBECTL) patch lrpdb $(LRPDBNAME) -n $(PDBNAMESPACE) -p \ + '{"spec":{"alterSystemParameter":"cpu_count","alterSystemValue":"3","parameterScope":"memory"}}' --type=merge + +openpdb1: + $(MAKE) -f $(MAKEFILE) open LRPDBNAME=pdb1 + +openpdb2: + $(MAKE) -f $(MAKEFILE) open LRPDBNAME=pdb2 + +openpdb3: + $(MAKE) -f $(MAKEFILE) open LRPDBNAME=pdb3 + +openpdb1rs: + $(MAKE) -f $(MAKEFILE) openrestricted LRPDBNAME=pdb1 + +openpdb2rs: + $(MAKE) -f $(MAKEFILE) openrestricted LRPDBNAME=pdb2 + +closepdb1: + $(MAKE) -f $(MAKEFILE) close LRPDBNAME=pdb1 + +closepdb2: + $(MAKE) -f $(MAKEFILE) close LRPDBNAME=pdb2 + +closepdb3: + $(MAKE) -f $(MAKEFILE) close LRPDBNAME=pdb3 + + +run02.1: + @$(call msg, "lrpdb pdb1 open") + $(KUBECTL) apply -f $(LRPDBOPEN1) + $(KUBECTL) wait --for jsonpath='{.status.msg'}=$(OPENMSG) lrpdb pdb1 -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) + @$(call msg, "lrpdb pdb1 open completed") + $(KUBECTL) get lrpdb pdb1 -n $(PDBNAMESPACE) + + +run02.2: + @$(call msg,"lrpdb pdb2 open") + $(KUBECTL) apply -f $(LRPDBOPEN2) + $(KUBECTL) wait --for jsonpath='{.status.msg'}=$(OPENMSG) lrpdb pdb2 -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) + @$(call msg,"lrpdb pdb2 open completed") + $(KUBECTL) get lrpdb pdb2 -n $(PDBNAMESPACE) + + +run03.1: + @$(call msg,"clone pdb1-->pdb3") + $(KUBECTL) apply -f $(LRPDBCLONE1) + $(KUBECTL) wait --for jsonpath='{.status.msg'}=$(CLONEMSG) lrpdb pdb3 -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) + @$(call msg,"clone pdb1-->pdb3 completed") + $(KUBECTL) get lrpdb pdb3 -n $(PDBNAMESPACE) + + +run03.2: + @$(call msg,"clone pdb2-->pdb4") + $(KUBECTL) apply -f $(LRPDBCLONE2) + $(KUBECTL) wait --for jsonpath='{.status.msg'}=$(CLONEMSG) lrpdb pdb4 -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) + @$(call msg,"clone pdb2-->pdb4 completed") + $(KUBECTL) get lrpdb pdb3 -n $(PDBNAMESPACE) + + +run04.1: + @$(call msg,"lrpdb pdb1 close") + $(KUBECTL) apply -f $(LRPDBCLOSE1) + $(KUBECTL) wait --for jsonpath='{.status.msg'}=$(CLOSEMSG) lrpdb pdb1 -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) + @$(call msg, "lrpdb pdb1 close completed") + $(KUBECTL) get lrpdb pdb1 -n $(PDBNAMESPACE) + +run04.2: + @$(call msg,"lrpdb pdb2 close") + $(KUBECTL) apply -f $(LRPDBCLOSE2) + $(KUBECTL) wait --for jsonpath='{.status.msg'}=$(CLOSEMSG) lrpdb pdb2 -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) + @$(call msg,"lrpdb pdb2 close completed") + $(KUBECTL) get lrpdb pdb2 -n $(PDBNAMESPACE) + +run05.1: + @$(call msg,"lrpdb pdb1 unplug") + $(KUBECTL) apply -f $(LRPDBUNPLUG1) + $(KUBECTL) wait --for=delete lrpdb pdb1 -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) + @$(call msg,"lrpdb pdb1 unplug completed") + +run06.1: + @$(call msg, "lrpdb pdb1 plug") + $(KUBECTL) apply -f $(LRPDBPLUG1) + $(KUBECTL) wait --for jsonpath='{.status.msg'}=$(PLUGMSG) lrpdb pdb1 -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) + @$(call msg, "lrpdb pdb1 plug completed") + $(KUBECTL) get lrpdb pdb1 -n $(PDBNAMESPACE) + +run07.1: + @$(call msg,"lrpdb pdb1 delete ") + - $(KUBECTL) apply -f $(LRPDBCLOSE1) + $(KUBECTL) wait --for jsonpath='{.status.msg'}=$(CLOSEMSG) lrpdb pdb1 -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) + $(KUBECTL) apply -f $(LRPDBDELETE1) + $(KUBECTL) wait --for=delete lrpdb pdb1 -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) + @$(call msg,"lrpdb pdb1 delete") + $(KUBECTL) get lrpdb -n $(PDBNAMESPACE) + +run99.1: + $(KUBECTL) delete lrest cdb-dev -n $(LRSNAMESPACE) + $(KUBECTL) wait --for=delete lrest cdb-dev -n $(LRSNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) + $(KUBECTL) get lrest -n $(LRSNAMESPACE) + $(KUBECTL) get lrpdb -n $(PDBNAMESPACE) + + +tklifecycle: runall01 + +tkonepdbcre: runall02 + +tkapplyinit: + @$(call msg," init config map creation") + -- $(KUBECTL) delete -f $(CONFIG_MAP) -n $(PDBNAMESPACE) + $(KUBECTL) apply -f $(CONFIG_MAP) -n $(PDBNAMESPACE) + +tkplsqlexec: + @$(call msg,"==>PLSQL TESTING<==") + $(MAKE) -f $(MAKEFILE) run00 run01.1 run02.1 + @$(call msg,"delete config map ") + -- $(KUBECTL) delete configmap $(PLSQLMAP) -n $(PDBNAMESPACE) + @$(call msg,"recreating config sql map") + $(KUBECTL) apply -f $(CONFIG_MAP_SQL) + $(KUBECTL) describe configmap $(PLSQLMAP) -n $(PDBNAMESPACE) + @$(call msg,"applying config sql map") + $(KUBECTL) patch lrpdb pdb1 -n $(PDBNAMESPACE) -p \ + '{"spec":{"codeconfigmap":"$(PLSQLMAP)"}}' --type=merge + sleep 10 + $(KUBECTL) get events --sort-by='.lastTimestamp' -n $(PDBNAMESPACE) + $(KUBECTL) get events -n $(PDBNAMESPACE) --sort-by='.lastTimestamp' --field-selector involvedObject.name=pdb1|grep APPLYSQL + $(KUBECTL) get events -n $(PDBNAMESPACE) --sort-by='.lastTimestamp' --field-selector involvedObject.name=pdb1|grep APPLYSQL + + +tkplsqlexec01: + @$(call msg,"delete config map ") + -- $(KUBECTL) delete configmap $(PLSQLMAP) -n $(PDBNAMESPACE) + @$(call msg,"recreating config sql map") + $(KUBECTL) apply -f $(CONFIG_MAP_SQL) + $(KUBECTL) describe configmap $(PLSQLMAP) -n $(PDBNAMESPACE) + @$(call msg,"applying config sql map") + $(KUBECTL) patch lrpdb $(LRPDBNAME) -n $(PDBNAMESPACE) -p \ + '{"spec":{"codeconfigmap":"$(PLSQLMAP)"}}' --type=merge + sleep 10 + $(KUBECTL) get events --sort-by='.lastTimestamp' -n $(PDBNAMESPACE) + $(KUBECTL) get events -n $(PDBNAMESPACE) --sort-by='.lastTimestamp' --field-selector involvedObject.name=$(LRPDBNAME)|grep APPLYSQL + +tkaudosicov: + @$(call msg,"==>AUTODISCOVER TESTING<==") + $(MAKE) -f $(MAKEFILE) genyaml ASSERTDELETION=false + $(MAKE) -f $(MAKEFILE) run00 run01.1 run02.1 + @$(call msg,"deleting pdb1 (imperativedeletion=false)") + $(KUBECTL) delete lrpdb pdb1 -n $(PDBNAMESPACE) + $(KUBECTL) wait --for=delete lrpdb pdb1 -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) + @$(call msg,"checking lrpdb pdb1 recreation") + $(KUBECTL) wait --for jsonpath='{.status.phase'}="Ready" \ + lrpdb atd-pdbdev -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) + $(MAKE) -f $(MAKEFILE) genyaml ASSERTDELETION=true + +# Proceudure to recreate restserver +tklrestnew: + @$(call msg,"Recrete resetserver") + @$(call msg,"disable autodiscover and delete cascade") + $(KUBECTL) patch lrest cdb-dev -n $(LRSNAMESPACE) -p '{"spec":{"autodiscover":false,"deletePdbCascade":false}}' --type=merge + $(KUBECTL) wait --for jsonpath='{.spec.autodiscover'}=false lrest cdb-dev -n $(LRSNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) + $(KUBECTL) wait --for jsonpath='{.spec.deletePdbCascade'}=false lrest cdb-dev -n $(LRSNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) + sleep 10 + $(MAKE) -f $(MAKEFILE) run00 + +# Turn ON/OFF autodiscovery +# ON: make tkautd AUTOD=true +# OFF: make tkautd AUTOD=false +tkautd: + @$(call msg,"autodiscover $(AUTOD)") + $(KUBECTL) patch lrest cdb-dev -n $(LRSNAMESPACE) -p '{"spec":{"autodiscover":$(AUTOD)}}' --type=merge + $(KUBECTL) wait --for jsonpath='{.spec.autodiscover'}=$(AUTOD) lrest cdb-dev -n $(LRSNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) + +# Turn ON/OFF lrest pdb delete cascade +# ON: make tkdelcs DELETECS=true +# OFF: make tkdelcs DELETECS=false +tkdelcs: + @$(call msg,"delete pdb cascade $(DELETECS)") + $(KUBECTL) patch lrest cdb-dev -n $(LRSNAMESPACE) -p '{"spec":{"deletePdbCascade":$(DELETECS)}}' --type=merge + $(KUBECTL) wait --for jsonpath='{.spec.deletePdbCascade'}=$(DELETECS) lrest cdb-dev -n $(LRSNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) + +# Turn ON/OFF lrest pdb imperativeLrpdb deletion (delete CRD & PDB) +# ON: make tkdelcrd DELETECRD=true LRPDBNAME= +# OFF: make tkdelcrd DELETECRD=false LRPDNNAME= +tkdelcrd: + @$(call msg,"imperativeLrpdbDeletion $(DELETECRD)") + $(KUBECTL) patch lrpdb $(LRPDBNAME) -n $(PDBNAMESPACE) \ + -p '{"spec":{"imperativeLrpdbDeletion":$(DELETECRD)}}' --type=merge + $(KUBECTL) wait --for jsonpath='{.spec.imperativeLrpdbDeletion'}=$(DELETECRD) lrpdb $(LRPDBNAME) -n $(PDBNAMESPACE) --timeout=$(TEST_EXEC_TIMEOUT) + +checkpdbsize: + $(KUBECTL) get lrpdb -n $(PDBNAMESPACE) -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.pdbName}{"\t"}{.spec.totalSize}{"\t"}{.status.totalSize}{"\n"}{end}' + +checkimpdel: + $(KUBECTL) get lrpdb -n $(PDBNAMESPACE) \ + -o jsonpath='{range .items[*]}{.metadata.name}{" "}{.spec.pdbName}{" "}{.status.openMode}{" "}{.spec.imperativeLrpdbDeletion}{" "}{"\t\t"}{"\n"}{end}'| sed 's/READ WRITE/READ_WRITE/g' |awk ' BEGIN { printf( "%-20s %-10s %-10s %10s\n","CRD","PDB NAME","OPEN MODE","IMPERATIVELRPDBDELETION"); \ + printf( "%-20s %-10s %-10s %-23s\n","--------------------","----------","----------","-----------------------");\ + } { printf("%-20s %-10s %-10s %-23s\n",$$1,$$2,$$3,$$4) }' + +reset: + @$(call msg,"Reset state $(LRPDBNAME):$(RESETVALUE)") + $(KUBECTL) patch lrpdb $(LRPDBNAME) -n $(PDBNAMESPACE) \ + -p '{"spec":{"pdbState":"RESET","reststate":$(RESETVALUE)}}' \ + --type=merge + $(KUBECTL) get lrpdb $(LRPDBNAME) -n $(PDBNAMESPACE) + + + + +# tkapplyinit - config map creation +# run00 - lrest pod creation +# run01.1 - pdb1 creation +# run01.2 - pdb2 creation +# run02.1 - pdb1 open - declarative +# run02.2 - pdb2 open - declarative +# run03.1 - pdb1 clone - declarative +# run04.1 - pdb1 close - declarative +# run04.2 - pdb2 close - declatative +# run05.1 - pdb1 unplug - declarative +# run06.1 - pdb1 plug - declarative +# openpdb1 - pdb1 open - imperative +# openpdb2 - pdb2 open - imperative +# closepdb1 - pdb1 close - imperative +# closepdb2 - pdb2 close - imperative +# openpdb1rs - pdb1 open restrict - imperative +# openpdb2rs - pdb2 open restrict - imperative +# + +runall01: genyaml tkapplyinit run00 run01.1 run01.2 run02.1 run02.2 \ + run03.1 run03.2 run04.1 run04.2 run05.1 run06.1 run07.1 +runall02: genyaml tkapplyinit run00 run01.1 openpdb1 +runall03: genyaml tkapplyinit run00 run01.1 run01.2 run02.1 run02.2 \ + run04.1 run04.2 openpdb1 openpdb2 closepdb1 closepdb2 openpdb1rs + $(MAKE) -f $(MAKEFILE) close LRPDBNAME=pdb1 + $(MAKE) -f $(MAKEFILE) open LRPDBNAME=pdb1 + $(MAKE) -f $(MAKEFILE) close LRPDBNAME=pdb1 + $(MAKE) -f $(MAKEFILE) run05.1 run06.1 +runall04: genyaml tkapplyinit run00 run01.1 run01.2 run02.1 run02.2 run04.1 \ + run04.2 openpdb1 \ + openpdb2 closepdb1 closepdb2 run05.1 run06.1 closepdb1 +runall05: genyaml tkapplyinit run00 run01.1 run02.1 run03.1 +## Delete cloned pdb +## setup:create/open/clone +runall06: run01.1 run02.1 run03.1 + $(KUBECTL) apply -f $(LRPDBDELETE3) + $(KUBECTL) apply -f $(LRPDBDELETE1) + + + + + + + + diff --git a/docs/multitenant/lrest-based/usecase/map_pdb1_resource.yaml b/docs/multitenant/usecase/map_pdb1_resource.yaml similarity index 95% rename from docs/multitenant/lrest-based/usecase/map_pdb1_resource.yaml rename to docs/multitenant/usecase/map_pdb1_resource.yaml index 2cd57b87..177b6915 100644 --- a/docs/multitenant/lrest-based/usecase/map_pdb1_resource.yaml +++ b/docs/multitenant/usecase/map_pdb1_resource.yaml @@ -10,11 +10,10 @@ spec: cdbNamespace: "cdbnamespace" cdbName: "DB12" pdbName: "pdbdev" - assertiveLrpdbDeletion: true + imperativeLrpdbDeletion: true fileNameConversions: "NONE" totalSize: "1G" tempSize: "100M" - action: "Map" adminpdbUser: secret: secretName: "pdbusr" diff --git a/docs/multitenant/lrest-based/usecase/map_pdb2_resource.yaml b/docs/multitenant/usecase/map_pdb2_resource.yaml similarity index 95% rename from docs/multitenant/lrest-based/usecase/map_pdb2_resource.yaml rename to docs/multitenant/usecase/map_pdb2_resource.yaml index bab614cf..3e8028e7 100644 --- a/docs/multitenant/lrest-based/usecase/map_pdb2_resource.yaml +++ b/docs/multitenant/usecase/map_pdb2_resource.yaml @@ -10,11 +10,10 @@ spec: cdbNamespace: "cdbnamespace" cdbName: "DB12" pdbName: "pdbprd" - assertiveLrpdbDeletion: true + imperativeLrpdbDeletion: true fileNameConversions: "NONE" totalSize: "1G" tempSize: "100M" - action: "Map" adminpdbUser: secret: secretName: "pdbusr" diff --git a/docs/multitenant/lrest-based/usecase/map_pdb3_resource.yaml b/docs/multitenant/usecase/map_pdb3_resource.yaml similarity index 95% rename from docs/multitenant/lrest-based/usecase/map_pdb3_resource.yaml rename to docs/multitenant/usecase/map_pdb3_resource.yaml index 7bbae48d..df69929a 100644 --- a/docs/multitenant/lrest-based/usecase/map_pdb3_resource.yaml +++ b/docs/multitenant/usecase/map_pdb3_resource.yaml @@ -10,11 +10,10 @@ spec: cdbNamespace: "cdbnamespace" cdbName: "DB12" pdbName: "new_clone" - assertiveLrpdbDeletion: true + imperativeLrpdbDeletion: true fileNameConversions: "NONE" totalSize: "1G" tempSize: "100M" - action: "Map" adminpdbUser: secret: secretName: "pdbusr" diff --git a/docs/multitenant/usecase/node_rbac.yaml b/docs/multitenant/usecase/node_rbac.yaml new file mode 100644 index 00000000..ac474873 --- /dev/null +++ b/docs/multitenant/usecase/node_rbac.yaml @@ -0,0 +1,27 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: oracle-database-operator-manager-role-node +rules: +- apiGroups: + - "" + resources: + - nodes + verbs: + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: oracle-database-operator-manager-role-node-cluster-role-binding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: oracle-database-operator-manager-role-node +subjects: +- kind: ServiceAccount + name: default + namespace: oracle-database-operator-system +--- diff --git a/docs/multitenant/lrest-based/usecase/open_pdb1_resource.yaml b/docs/multitenant/usecase/open_pdb1_resource.yaml similarity index 97% rename from docs/multitenant/lrest-based/usecase/open_pdb1_resource.yaml rename to docs/multitenant/usecase/open_pdb1_resource.yaml index a845a0bd..8b6dac1f 100644 --- a/docs/multitenant/lrest-based/usecase/open_pdb1_resource.yaml +++ b/docs/multitenant/usecase/open_pdb1_resource.yaml @@ -10,7 +10,6 @@ spec: cdbNamespace: "cdbnamespace" cdbName: "DB12" pdbName: "pdbdev" - action: "Modify" pdbState: "OPEN" modifyOption: "READ WRITE" adminpdbUser: diff --git a/docs/multitenant/lrest-based/usecase/open_pdb2_resource.yaml b/docs/multitenant/usecase/open_pdb2_resource.yaml similarity index 97% rename from docs/multitenant/lrest-based/usecase/open_pdb2_resource.yaml rename to docs/multitenant/usecase/open_pdb2_resource.yaml index 9356184f..c2d24d50 100644 --- a/docs/multitenant/lrest-based/usecase/open_pdb2_resource.yaml +++ b/docs/multitenant/usecase/open_pdb2_resource.yaml @@ -10,7 +10,6 @@ spec: cdbNamespace: "cdbnamespace" cdbName: "DB12" pdbName: "pdbprd" - action: "Modify" pdbState: "OPEN" modifyOption: "READ WRITE" adminpdbUser: diff --git a/docs/multitenant/lrest-based/usecase/open_pdb3_resource.yaml b/docs/multitenant/usecase/open_pdb3_resource.yaml similarity index 97% rename from docs/multitenant/lrest-based/usecase/open_pdb3_resource.yaml rename to docs/multitenant/usecase/open_pdb3_resource.yaml index 1b8024ba..393a7a6f 100644 --- a/docs/multitenant/lrest-based/usecase/open_pdb3_resource.yaml +++ b/docs/multitenant/usecase/open_pdb3_resource.yaml @@ -10,7 +10,6 @@ spec: cdbNamespace: "cdbnamespace" cdbName: "DB12" pdbName: "new_clone" - action: "Modify" pdbState: "OPEN" modifyOption: "READ WRITE" adminpdbUser: diff --git a/docs/multitenant/lrest-based/usecase/parameters.txt b/docs/multitenant/usecase/parameters.txt similarity index 52% rename from docs/multitenant/lrest-based/usecase/parameters.txt rename to docs/multitenant/usecase/parameters.txt index 1f21ed38..34a64c6f 100644 --- a/docs/multitenant/lrest-based/usecase/parameters.txt +++ b/docs/multitenant/usecase/parameters.txt @@ -2,7 +2,6 @@ ######################## ## REST SERVER IMAGE ### ######################## - LRESTIMG:container-registry.oracle.com/database/operator:lrest-241210-amd64 ############################## @@ -14,39 +13,75 @@ TNSALIAS:"(DESCRIPTION=(CONNECT_TIMEOUT=90)(RETRY_COUNT=30)(RETRY_DELAY=10)(TRAN ## CDB USER FOR PDB LIFECYCLE MANAGMENT ### ########################################### -DBUSER:restdba -DBPASS:CLWKO655321 +DBUSER:_username_ +DBPASS:_password_ + +######################### +## SERVICENAME ACCOUNT ## +######################### +SERVICENAMEACCOUNT:_your_service_account_ + + +######################### +## OPENSHIFT ## +######################### +OPENSHIFT:false + +######################### +## PLSQL CONFIGMAP ## +######################### +PLSQLMAP:sql-map-example1 ####################### ## HTTPS CREDENTIAL ### ####################### -WBUSER:welcome -WBPASS:welcome1 +WBUSER:_username_ +WBPASS:_password_ ##################### ## PDB ADMIN USER ### ##################### -PDBUSR:Citizenkane -PDBPWD:Rosebud +PDBUSR:_username_ +PDBPWD:_password_ ################### ### NAMESPACES #### ################### - PDBNAMESPACE:pdbnamespace LRSNAMESPACE:cdbnamespace - #################### ### COMPANY NAME ### #################### - COMPANY:oracle +########################## +### CODE TREE ### +########################## +CODE_TREE:../../../../oracle-database-operator + +######################## +## OPERATOR IMAGE ### +######################## +#Use this parameter if you need to change the image +#OPRIMG:lin.ocir.io/intsanjaysingh/operator/dboperator_master_orahub:test + + +########################## +### PDB AUTODISCOVER ### +########################## +AUTODISCOVER:false + #################### ### APIVERSION ### #################### - APIVERSION:v4 + +########################## +### CERT_MANAGER ### +########################## +CERT_MANAGER:https://github.com/cert-manager/cert-manager/releases/download/v1.16.2/cert-manager.yaml + + diff --git a/docs/multitenant/lrest-based/usecase/pdbnamespace_binding.yaml b/docs/multitenant/usecase/pdbnamespace_binding.yaml similarity index 100% rename from docs/multitenant/lrest-based/usecase/pdbnamespace_binding.yaml rename to docs/multitenant/usecase/pdbnamespace_binding.yaml diff --git a/docs/multitenant/lrest-based/usecase/plug_pdb1_resource.yaml b/docs/multitenant/usecase/plug_pdb1_resource.yaml similarity index 91% rename from docs/multitenant/lrest-based/usecase/plug_pdb1_resource.yaml rename to docs/multitenant/usecase/plug_pdb1_resource.yaml index d7d310db..7ce0d60e 100644 --- a/docs/multitenant/lrest-based/usecase/plug_pdb1_resource.yaml +++ b/docs/multitenant/usecase/plug_pdb1_resource.yaml @@ -10,16 +10,15 @@ spec: cdbNamespace: "cdbnamespace" cdbName: "DB12" pdbName: "pdbdev" - xmlFileName: "/tmp/pdb.xml" - action: "plug" + pdbState: "PLUG" + xmlFileName: "/var/tmp/pdb.4095290.xml" fileNameConversions: "NONE" sourceFileNameConversions: "NONE" copyAction: "MOVE" totalSize: "1G" tempSize: "100M" - assertiveLrpdbDeletion: true + imperativeLrpdbDeletion: true pdbconfigmap: "config-map-pdb" - action: "Plug" adminpdbUser: secret: secretName: "pdbusr" diff --git a/docs/multitenant/usecase/security_context.yaml b/docs/multitenant/usecase/security_context.yaml new file mode 100644 index 00000000..61d7637d --- /dev/null +++ b/docs/multitenant/usecase/security_context.yaml @@ -0,0 +1,93 @@ +# +# Copyright (c) 2024, Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. +# +--- + +# Create a Security Context Contraint +kind: SecurityContextConstraints +apiVersion: security.openshift.io/v1 +metadata: + name: lrest-scc +allowPrivilegedContainer: false +allowedCapabilities: + - SYS_NICE +runAsUser: + type: MustRunAs + uid: 54321 +seLinuxContext: + type: RunAsAny +fsGroup: + type: MustRunAs + ranges: + - min: 54321 + max: 54321 +supplementalGroups: + type: MustRunAs + ranges: + - min: 54321 + max: 54321 +--- + +# Create a Security Context Contraint +kind: SecurityContextConstraints +apiVersion: security.openshift.io/v1 +metadata: + name: lrest-root-user-scc +allowPrivilegedContainer: false +allowedCapabilities: + - SYS_NICE +runAsUser: + type: MustRunAsRange + uidRangeMin: 0 + uidRangeMax: 54321 +seLinuxContext: + type: RunAsAny +fsGroup: + type: MustRunAs + ranges: + - min: 0 + max: 54325 +supplementalGroups: + type: MustRunAs + ranges: + - min: 0 + max: 54321 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: lrest-sa + namespace: cdbnamespace +--- + +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: use-lrest-scc + namespace: cdbnamespace +rules: + - apiGroups: + - security.openshift.io + verbs: + - use + resources: + - securitycontextconstraints + resourceNames: + - lrest-scc + - lrest-root-user-scc +--- + +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: use-lrest-scc + namespace: cdbnamespace +subjects: + - kind: ServiceAccount + name: lrest-sa + namespace: cdbnamespace +roleRef: + kind: Role + name: use-lrest-scc + apiGroup: rbac.authorization.k8s.io diff --git a/docs/multitenant/lrest-based/usecase/unplug_pdb1_resource.yaml b/docs/multitenant/usecase/unplug_pdb1_resource.yaml similarity index 93% rename from docs/multitenant/lrest-based/usecase/unplug_pdb1_resource.yaml rename to docs/multitenant/usecase/unplug_pdb1_resource.yaml index a5da5a57..f48fe74b 100644 --- a/docs/multitenant/lrest-based/usecase/unplug_pdb1_resource.yaml +++ b/docs/multitenant/usecase/unplug_pdb1_resource.yaml @@ -10,8 +10,8 @@ spec: cdbNamespace: "cdbnamespace" cdbName: "DB12" pdbName: "pdbdev" - xmlFileName: "/tmp/pdb.xml" - action: "Unplug" + pdbState: "UNPLUG" + xmlFileName: "/var/tmp/pdb.4095290.xml" adminpdbUser: secret: secretName: "pdbusr" diff --git a/docs/multitenant/usecase01/logfiles/BuildImage.log b/docs/multitenant/usecase01/logfiles/BuildImage.log deleted file mode 100644 index f35c66d8..00000000 --- a/docs/multitenant/usecase01/logfiles/BuildImage.log +++ /dev/null @@ -1,896 +0,0 @@ -/usr/bin/docker build -t oracle/ords-dboper:latest ../../../ords -Sending build context to Docker daemon 13.82kB -Step 1/12 : FROM container-registry.oracle.com/java/jdk:latest - ---> b8457e2f0b73 -Step 2/12 : ENV ORDS_HOME=/opt/oracle/ords/ RUN_FILE="runOrdsSSL.sh" ORDSVERSION=23.4.0-8 - ---> Using cache - ---> 3317a16cd6f8 -Step 3/12 : COPY $RUN_FILE $ORDS_HOME - ---> 7995edec33cc -Step 4/12 : RUN yum -y install yum-utils bind-utils tree hostname openssl net-tools zip unzip tar wget vim-minimal which sudo expect procps curl lsof && yum-config-manager --add-repo=http://yum.oracle.com/repo/OracleLinux/OL8/oracle/software/x86_64 && yum -y install java-11-openjdk-devel && yum -y install iproute && yum clean all - ---> Running in fe168b01f3ad -Oracle Linux 8 BaseOS Latest (x86_64) 91 MB/s | 79 MB 00:00 -Oracle Linux 8 Application Stream (x86_64) 69 MB/s | 62 MB 00:00 -Last metadata expiration check: 0:00:12 ago on Tue 20 Aug 2024 08:54:50 AM UTC. -Package yum-utils-4.0.21-23.0.1.el8.noarch is already installed. -Package tar-2:1.30-9.el8.x86_64 is already installed. -Package vim-minimal-2:8.0.1763-19.0.1.el8_6.4.x86_64 is already installed. -Package procps-ng-3.3.15-14.0.1.el8.x86_64 is already installed. -Package curl-7.61.1-33.el8_9.5.x86_64 is already installed. -Dependencies resolved. -================================================================================ - Package Arch Version Repository Size -================================================================================ -Installing: - bind-utils x86_64 32:9.11.36-16.el8_10.2 ol8_appstream 453 k - expect x86_64 5.45.4-5.el8 ol8_baseos_latest 266 k - hostname x86_64 3.20-6.el8 ol8_baseos_latest 32 k - lsof x86_64 4.93.2-1.el8 ol8_baseos_latest 253 k - net-tools x86_64 2.0-0.52.20160912git.el8 ol8_baseos_latest 322 k - openssl x86_64 1:1.1.1k-12.el8_9 ol8_baseos_latest 710 k - sudo x86_64 1.9.5p2-1.el8_9 ol8_baseos_latest 1.0 M - tree x86_64 1.7.0-15.el8 ol8_baseos_latest 59 k - unzip x86_64 6.0-46.0.1.el8 ol8_baseos_latest 196 k - wget x86_64 1.19.5-12.0.1.el8_10 ol8_appstream 733 k - which x86_64 2.21-20.el8 ol8_baseos_latest 50 k - zip x86_64 3.0-23.el8 ol8_baseos_latest 270 k -Upgrading: - curl x86_64 7.61.1-34.el8 ol8_baseos_latest 352 k - dnf-plugins-core noarch 4.0.21-25.0.1.el8 ol8_baseos_latest 76 k - libcurl x86_64 7.61.1-34.el8 ol8_baseos_latest 303 k - python3-dnf-plugins-core - noarch 4.0.21-25.0.1.el8 ol8_baseos_latest 263 k - yum-utils noarch 4.0.21-25.0.1.el8 ol8_baseos_latest 75 k -Installing dependencies: - bind-libs x86_64 32:9.11.36-16.el8_10.2 ol8_appstream 176 k - bind-libs-lite x86_64 32:9.11.36-16.el8_10.2 ol8_appstream 1.2 M - bind-license noarch 32:9.11.36-16.el8_10.2 ol8_appstream 104 k - fstrm x86_64 0.6.1-3.el8 ol8_appstream 29 k - libmaxminddb x86_64 1.2.0-10.el8_9.1 ol8_appstream 32 k - libmetalink x86_64 0.1.3-7.el8 ol8_baseos_latest 32 k - protobuf-c x86_64 1.3.0-8.el8 ol8_appstream 37 k - python3-bind noarch 32:9.11.36-16.el8_10.2 ol8_appstream 151 k - python3-ply noarch 3.9-9.el8 ol8_baseos_latest 111 k - tcl x86_64 1:8.6.8-2.el8 ol8_baseos_latest 1.1 M -Installing weak dependencies: - geolite2-city noarch 20180605-1.el8 ol8_appstream 19 M - geolite2-country noarch 20180605-1.el8 ol8_appstream 1.0 M - -Transaction Summary -================================================================================ -Install 24 Packages -Upgrade 5 Packages - -Total download size: 28 M -Downloading Packages: -(1/29): hostname-3.20-6.el8.x86_64.rpm 268 kB/s | 32 kB 00:00 -(2/29): libmetalink-0.1.3-7.el8.x86_64.rpm 257 kB/s | 32 kB 00:00 -(3/29): expect-5.45.4-5.el8.x86_64.rpm 1.4 MB/s | 266 kB 00:00 -(4/29): lsof-4.93.2-1.el8.x86_64.rpm 3.2 MB/s | 253 kB 00:00 -(5/29): net-tools-2.0-0.52.20160912git.el8.x86_ 3.6 MB/s | 322 kB 00:00 -(6/29): python3-ply-3.9-9.el8.noarch.rpm 2.7 MB/s | 111 kB 00:00 -(7/29): openssl-1.1.1k-12.el8_9.x86_64.rpm 10 MB/s | 710 kB 00:00 -(8/29): tree-1.7.0-15.el8.x86_64.rpm 2.2 MB/s | 59 kB 00:00 -(9/29): sudo-1.9.5p2-1.el8_9.x86_64.rpm 14 MB/s | 1.0 MB 00:00 -(10/29): unzip-6.0-46.0.1.el8.x86_64.rpm 6.8 MB/s | 196 kB 00:00 -(11/29): which-2.21-20.el8.x86_64.rpm 2.0 MB/s | 50 kB 00:00 -(12/29): tcl-8.6.8-2.el8.x86_64.rpm 13 MB/s | 1.1 MB 00:00 -(13/29): bind-libs-9.11.36-16.el8_10.2.x86_64.r 6.7 MB/s | 176 kB 00:00 -(14/29): zip-3.0-23.el8.x86_64.rpm 8.4 MB/s | 270 kB 00:00 -(15/29): bind-libs-lite-9.11.36-16.el8_10.2.x86 29 MB/s | 1.2 MB 00:00 -(16/29): bind-license-9.11.36-16.el8_10.2.noarc 3.3 MB/s | 104 kB 00:00 -(17/29): bind-utils-9.11.36-16.el8_10.2.x86_64. 13 MB/s | 453 kB 00:00 -(18/29): fstrm-0.6.1-3.el8.x86_64.rpm 1.2 MB/s | 29 kB 00:00 -(19/29): libmaxminddb-1.2.0-10.el8_9.1.x86_64.r 1.3 MB/s | 32 kB 00:00 -(20/29): geolite2-country-20180605-1.el8.noarch 17 MB/s | 1.0 MB 00:00 -(21/29): protobuf-c-1.3.0-8.el8.x86_64.rpm 1.5 MB/s | 37 kB 00:00 -(22/29): python3-bind-9.11.36-16.el8_10.2.noarc 5.8 MB/s | 151 kB 00:00 -(23/29): wget-1.19.5-12.0.1.el8_10.x86_64.rpm 17 MB/s | 733 kB 00:00 -(24/29): curl-7.61.1-34.el8.x86_64.rpm 12 MB/s | 352 kB 00:00 -(25/29): dnf-plugins-core-4.0.21-25.0.1.el8.noa 2.4 MB/s | 76 kB 00:00 -(26/29): libcurl-7.61.1-34.el8.x86_64.rpm 8.6 MB/s | 303 kB 00:00 -(27/29): python3-dnf-plugins-core-4.0.21-25.0.1 9.8 MB/s | 263 kB 00:00 -(28/29): yum-utils-4.0.21-25.0.1.el8.noarch.rpm 3.0 MB/s | 75 kB 00:00 -(29/29): geolite2-city-20180605-1.el8.noarch.rp 66 MB/s | 19 MB 00:00 --------------------------------------------------------------------------------- -Total 43 MB/s | 28 MB 00:00 -Running transaction check -Transaction check succeeded. -Running transaction test -Transaction test succeeded. -Running transaction - Preparing : 1/1 - Running scriptlet: protobuf-c-1.3.0-8.el8.x86_64 1/1 - Installing : protobuf-c-1.3.0-8.el8.x86_64 1/34 - Installing : fstrm-0.6.1-3.el8.x86_64 2/34 - Installing : bind-license-32:9.11.36-16.el8_10.2.noarch 3/34 - Upgrading : python3-dnf-plugins-core-4.0.21-25.0.1.el8.noarch 4/34 - Upgrading : dnf-plugins-core-4.0.21-25.0.1.el8.noarch 5/34 - Upgrading : libcurl-7.61.1-34.el8.x86_64 6/34 - Installing : geolite2-country-20180605-1.el8.noarch 7/34 - Installing : geolite2-city-20180605-1.el8.noarch 8/34 - Installing : libmaxminddb-1.2.0-10.el8_9.1.x86_64 9/34 - Running scriptlet: libmaxminddb-1.2.0-10.el8_9.1.x86_64 9/34 - Installing : bind-libs-lite-32:9.11.36-16.el8_10.2.x86_64 10/34 - Installing : bind-libs-32:9.11.36-16.el8_10.2.x86_64 11/34 - Installing : unzip-6.0-46.0.1.el8.x86_64 12/34 - Installing : tcl-1:8.6.8-2.el8.x86_64 13/34 - Running scriptlet: tcl-1:8.6.8-2.el8.x86_64 13/34 - Installing : python3-ply-3.9-9.el8.noarch 14/34 - Installing : python3-bind-32:9.11.36-16.el8_10.2.noarch 15/34 - Installing : libmetalink-0.1.3-7.el8.x86_64 16/34 - Installing : wget-1.19.5-12.0.1.el8_10.x86_64 17/34 - Running scriptlet: wget-1.19.5-12.0.1.el8_10.x86_64 17/34 - Installing : bind-utils-32:9.11.36-16.el8_10.2.x86_64 18/34 - Installing : expect-5.45.4-5.el8.x86_64 19/34 - Installing : zip-3.0-23.el8.x86_64 20/34 - Upgrading : curl-7.61.1-34.el8.x86_64 21/34 - Upgrading : yum-utils-4.0.21-25.0.1.el8.noarch 22/34 - Installing : which-2.21-20.el8.x86_64 23/34 - Installing : tree-1.7.0-15.el8.x86_64 24/34 - Installing : sudo-1.9.5p2-1.el8_9.x86_64 25/34 - Running scriptlet: sudo-1.9.5p2-1.el8_9.x86_64 25/34 - Installing : openssl-1:1.1.1k-12.el8_9.x86_64 26/34 - Installing : net-tools-2.0-0.52.20160912git.el8.x86_64 27/34 - Running scriptlet: net-tools-2.0-0.52.20160912git.el8.x86_64 27/34 - Installing : lsof-4.93.2-1.el8.x86_64 28/34 - Installing : hostname-3.20-6.el8.x86_64 29/34 - Running scriptlet: hostname-3.20-6.el8.x86_64 29/34 - Cleanup : curl-7.61.1-33.el8_9.5.x86_64 30/34 - Cleanup : yum-utils-4.0.21-23.0.1.el8.noarch 31/34 - Cleanup : dnf-plugins-core-4.0.21-23.0.1.el8.noarch 32/34 - Cleanup : python3-dnf-plugins-core-4.0.21-23.0.1.el8.noarch 33/34 - Cleanup : libcurl-7.61.1-33.el8_9.5.x86_64 34/34 - Running scriptlet: libcurl-7.61.1-33.el8_9.5.x86_64 34/34 - Verifying : expect-5.45.4-5.el8.x86_64 1/34 - Verifying : hostname-3.20-6.el8.x86_64 2/34 - Verifying : libmetalink-0.1.3-7.el8.x86_64 3/34 - Verifying : lsof-4.93.2-1.el8.x86_64 4/34 - Verifying : net-tools-2.0-0.52.20160912git.el8.x86_64 5/34 - Verifying : openssl-1:1.1.1k-12.el8_9.x86_64 6/34 - Verifying : python3-ply-3.9-9.el8.noarch 7/34 - Verifying : sudo-1.9.5p2-1.el8_9.x86_64 8/34 - Verifying : tcl-1:8.6.8-2.el8.x86_64 9/34 - Verifying : tree-1.7.0-15.el8.x86_64 10/34 - Verifying : unzip-6.0-46.0.1.el8.x86_64 11/34 - Verifying : which-2.21-20.el8.x86_64 12/34 - Verifying : zip-3.0-23.el8.x86_64 13/34 - Verifying : bind-libs-32:9.11.36-16.el8_10.2.x86_64 14/34 - Verifying : bind-libs-lite-32:9.11.36-16.el8_10.2.x86_64 15/34 - Verifying : bind-license-32:9.11.36-16.el8_10.2.noarch 16/34 - Verifying : bind-utils-32:9.11.36-16.el8_10.2.x86_64 17/34 - Verifying : fstrm-0.6.1-3.el8.x86_64 18/34 - Verifying : geolite2-city-20180605-1.el8.noarch 19/34 - Verifying : geolite2-country-20180605-1.el8.noarch 20/34 - Verifying : libmaxminddb-1.2.0-10.el8_9.1.x86_64 21/34 - Verifying : protobuf-c-1.3.0-8.el8.x86_64 22/34 - Verifying : python3-bind-32:9.11.36-16.el8_10.2.noarch 23/34 - Verifying : wget-1.19.5-12.0.1.el8_10.x86_64 24/34 - Verifying : curl-7.61.1-34.el8.x86_64 25/34 - Verifying : curl-7.61.1-33.el8_9.5.x86_64 26/34 - Verifying : dnf-plugins-core-4.0.21-25.0.1.el8.noarch 27/34 - Verifying : dnf-plugins-core-4.0.21-23.0.1.el8.noarch 28/34 - Verifying : libcurl-7.61.1-34.el8.x86_64 29/34 - Verifying : libcurl-7.61.1-33.el8_9.5.x86_64 30/34 - Verifying : python3-dnf-plugins-core-4.0.21-25.0.1.el8.noarch 31/34 - Verifying : python3-dnf-plugins-core-4.0.21-23.0.1.el8.noarch 32/34 - Verifying : yum-utils-4.0.21-25.0.1.el8.noarch 33/34 - Verifying : yum-utils-4.0.21-23.0.1.el8.noarch 34/34 - -Upgraded: - curl-7.61.1-34.el8.x86_64 - dnf-plugins-core-4.0.21-25.0.1.el8.noarch - libcurl-7.61.1-34.el8.x86_64 - python3-dnf-plugins-core-4.0.21-25.0.1.el8.noarch - yum-utils-4.0.21-25.0.1.el8.noarch -Installed: - bind-libs-32:9.11.36-16.el8_10.2.x86_64 - bind-libs-lite-32:9.11.36-16.el8_10.2.x86_64 - bind-license-32:9.11.36-16.el8_10.2.noarch - bind-utils-32:9.11.36-16.el8_10.2.x86_64 - expect-5.45.4-5.el8.x86_64 - fstrm-0.6.1-3.el8.x86_64 - geolite2-city-20180605-1.el8.noarch - geolite2-country-20180605-1.el8.noarch - hostname-3.20-6.el8.x86_64 - libmaxminddb-1.2.0-10.el8_9.1.x86_64 - libmetalink-0.1.3-7.el8.x86_64 - lsof-4.93.2-1.el8.x86_64 - net-tools-2.0-0.52.20160912git.el8.x86_64 - openssl-1:1.1.1k-12.el8_9.x86_64 - protobuf-c-1.3.0-8.el8.x86_64 - python3-bind-32:9.11.36-16.el8_10.2.noarch - python3-ply-3.9-9.el8.noarch - sudo-1.9.5p2-1.el8_9.x86_64 - tcl-1:8.6.8-2.el8.x86_64 - tree-1.7.0-15.el8.x86_64 - unzip-6.0-46.0.1.el8.x86_64 - wget-1.19.5-12.0.1.el8_10.x86_64 - which-2.21-20.el8.x86_64 - zip-3.0-23.el8.x86_64 - -Complete! -Adding repo from: http://yum.oracle.com/repo/OracleLinux/OL8/oracle/software/x86_64 -created by dnf config-manager from http://yum.o 496 kB/s | 139 kB 00:00 -Last metadata expiration check: 0:00:01 ago on Tue 20 Aug 2024 08:55:14 AM UTC. -Dependencies resolved. -============================================================================================== - Package Arch Version Repository Size -============================================================================================== -Installing: - java-11-openjdk-devel x86_64 1:11.0.24.0.8-3.0.1.el8 ol8_appstream 3.4 M -Installing dependencies: - adwaita-cursor-theme noarch 3.28.0-3.el8 ol8_appstream 647 k - adwaita-icon-theme noarch 3.28.0-3.el8 ol8_appstream 11 M - alsa-lib x86_64 1.2.10-2.el8 ol8_appstream 500 k - at-spi2-atk x86_64 2.26.2-1.el8 ol8_appstream 89 k - at-spi2-core x86_64 2.28.0-1.el8 ol8_appstream 169 k - atk x86_64 2.28.1-1.el8 ol8_appstream 272 k - avahi-libs x86_64 0.7-27.el8 ol8_baseos_latest 61 k - cairo x86_64 1.15.12-6.el8 ol8_appstream 719 k - cairo-gobject x86_64 1.15.12-6.el8 ol8_appstream 33 k - colord-libs x86_64 1.4.2-1.el8 ol8_appstream 236 k - copy-jdk-configs noarch 4.0-2.el8 ol8_appstream 30 k - cpio x86_64 2.12-11.el8 ol8_baseos_latest 266 k - crypto-policies-scripts noarch 20230731-1.git3177e06.el8 ol8_baseos_latest 84 k - cups-libs x86_64 1:2.2.6-60.el8_10 ol8_baseos_latest 435 k - dracut x86_64 049-233.git20240115.0.1.el8 ol8_baseos_latest 382 k - file x86_64 5.33-25.el8 ol8_baseos_latest 77 k - fribidi x86_64 1.0.4-9.el8 ol8_appstream 89 k - gdk-pixbuf2 x86_64 2.36.12-6.el8_10 ol8_baseos_latest 465 k - gdk-pixbuf2-modules x86_64 2.36.12-6.el8_10 ol8_appstream 108 k - gettext x86_64 0.19.8.1-17.el8 ol8_baseos_latest 1.1 M - gettext-libs x86_64 0.19.8.1-17.el8 ol8_baseos_latest 312 k - glib-networking x86_64 2.56.1-1.1.el8 ol8_baseos_latest 155 k - graphite2 x86_64 1.3.10-10.el8 ol8_appstream 122 k - grub2-common noarch 1:2.02-156.0.2.el8 ol8_baseos_latest 897 k - grub2-tools x86_64 1:2.02-156.0.2.el8 ol8_baseos_latest 2.0 M - grub2-tools-minimal x86_64 1:2.02-156.0.2.el8 ol8_baseos_latest 215 k - gsettings-desktop-schemas x86_64 3.32.0-6.el8 ol8_baseos_latest 633 k - gtk-update-icon-cache x86_64 3.22.30-11.el8 ol8_appstream 32 k - harfbuzz x86_64 1.7.5-4.el8 ol8_appstream 295 k - hicolor-icon-theme noarch 0.17-2.el8 ol8_appstream 48 k - jasper-libs x86_64 2.0.14-5.el8 ol8_appstream 167 k - java-11-openjdk x86_64 1:11.0.24.0.8-3.0.1.el8 ol8_appstream 475 k - java-11-openjdk-headless x86_64 1:11.0.24.0.8-3.0.1.el8 ol8_appstream 42 M - javapackages-filesystem noarch 5.3.0-1.module+el8+5136+7ff78f74 ol8_appstream 30 k - jbigkit-libs x86_64 2.1-14.el8 ol8_appstream 55 k - json-glib x86_64 1.4.4-1.el8 ol8_baseos_latest 144 k - kbd-legacy noarch 2.0.4-11.el8 ol8_baseos_latest 481 k - kbd-misc noarch 2.0.4-11.el8 ol8_baseos_latest 1.5 M - lcms2 x86_64 2.9-2.el8 ol8_appstream 164 k - libX11 x86_64 1.6.8-8.el8 ol8_appstream 611 k - libX11-common noarch 1.6.8-8.el8 ol8_appstream 157 k - libXau x86_64 1.0.9-3.el8 ol8_appstream 37 k - libXcomposite x86_64 0.4.4-14.el8 ol8_appstream 28 k - libXcursor x86_64 1.1.15-3.el8 ol8_appstream 36 k - libXdamage x86_64 1.1.4-14.el8 ol8_appstream 27 k - libXext x86_64 1.3.4-1.el8 ol8_appstream 45 k - libXfixes x86_64 5.0.3-7.el8 ol8_appstream 25 k - libXft x86_64 2.3.3-1.el8 ol8_appstream 67 k - libXi x86_64 1.7.10-1.el8 ol8_appstream 49 k - libXinerama x86_64 1.1.4-1.el8 ol8_appstream 15 k - libXrandr x86_64 1.5.2-1.el8 ol8_appstream 34 k - libXrender x86_64 0.9.10-7.el8 ol8_appstream 33 k - libXtst x86_64 1.2.3-7.el8 ol8_appstream 22 k - libcroco x86_64 0.6.12-4.el8_2.1 ol8_baseos_latest 113 k - libdatrie x86_64 0.2.9-7.el8 ol8_appstream 33 k - libepoxy x86_64 1.5.8-1.el8 ol8_appstream 225 k - libfontenc x86_64 1.1.3-8.el8 ol8_appstream 37 k - libgomp x86_64 8.5.0-22.0.1.el8_10 ol8_baseos_latest 218 k - libgusb x86_64 0.3.0-1.el8 ol8_baseos_latest 49 k - libjpeg-turbo x86_64 1.5.3-12.el8 ol8_appstream 157 k - libkcapi x86_64 1.4.0-2.0.1.el8 ol8_baseos_latest 52 k - libkcapi-hmaccalc x86_64 1.4.0-2.0.1.el8 ol8_baseos_latest 31 k - libmodman x86_64 2.0.1-17.el8 ol8_baseos_latest 36 k - libpkgconf x86_64 1.4.2-1.el8 ol8_baseos_latest 35 k - libproxy x86_64 0.4.15-5.2.el8 ol8_baseos_latest 75 k - libsoup x86_64 2.62.3-5.el8 ol8_baseos_latest 424 k - libthai x86_64 0.1.27-2.el8 ol8_appstream 203 k - libtiff x86_64 4.0.9-32.el8_10 ol8_appstream 189 k - libwayland-client x86_64 1.21.0-1.el8 ol8_appstream 41 k - libwayland-cursor x86_64 1.21.0-1.el8 ol8_appstream 26 k - libwayland-egl x86_64 1.21.0-1.el8 ol8_appstream 19 k - libxcb x86_64 1.13.1-1.el8 ol8_appstream 231 k - libxkbcommon x86_64 0.9.1-1.el8 ol8_appstream 116 k - lksctp-tools x86_64 1.0.18-3.el8 ol8_baseos_latest 100 k - lua x86_64 5.3.4-12.el8 ol8_appstream 192 k - nspr x86_64 4.35.0-1.el8_8 ol8_appstream 143 k - nss x86_64 3.90.0-7.el8_10 ol8_appstream 750 k - nss-softokn x86_64 3.90.0-7.el8_10 ol8_appstream 1.2 M - nss-softokn-freebl x86_64 3.90.0-7.el8_10 ol8_appstream 375 k - nss-sysinit x86_64 3.90.0-7.el8_10 ol8_appstream 74 k - nss-util x86_64 3.90.0-7.el8_10 ol8_appstream 139 k - os-prober x86_64 1.74-9.0.1.el8 ol8_baseos_latest 51 k - pango x86_64 1.42.4-8.el8 ol8_appstream 297 k - pixman x86_64 0.38.4-4.el8 ol8_appstream 256 k - pkgconf x86_64 1.4.2-1.el8 ol8_baseos_latest 38 k - pkgconf-m4 noarch 1.4.2-1.el8 ol8_baseos_latest 17 k - pkgconf-pkg-config x86_64 1.4.2-1.el8 ol8_baseos_latest 15 k - rest x86_64 0.8.1-2.el8 ol8_appstream 70 k - shared-mime-info x86_64 1.9-4.el8 ol8_baseos_latest 328 k - systemd-udev x86_64 239-78.0.4.el8 ol8_baseos_latest 1.6 M - ttmkfdir x86_64 3.0.9-54.el8 ol8_appstream 62 k - tzdata-java noarch 2024a-1.0.1.el8 ol8_appstream 186 k - xkeyboard-config noarch 2.28-1.el8 ol8_appstream 782 k - xorg-x11-font-utils x86_64 1:7.5-41.el8 ol8_appstream 104 k - xorg-x11-fonts-Type1 noarch 7.5-19.el8 ol8_appstream 522 k - xz x86_64 5.2.4-4.el8_6 ol8_baseos_latest 153 k -Installing weak dependencies: - abattis-cantarell-fonts noarch 0.0.25-6.el8 ol8_appstream 155 k - dconf x86_64 0.28.0-4.0.1.el8 ol8_appstream 108 k - dejavu-sans-mono-fonts noarch 2.35-7.el8 ol8_baseos_latest 447 k - grubby x86_64 8.40-49.0.2.el8 ol8_baseos_latest 50 k - gtk3 x86_64 3.22.30-11.el8 ol8_appstream 4.5 M - hardlink x86_64 1:1.3-6.el8 ol8_baseos_latest 29 k - kbd x86_64 2.0.4-11.el8 ol8_baseos_latest 390 k - memstrack x86_64 0.2.5-2.el8 ol8_baseos_latest 51 k - pigz x86_64 2.4-4.el8 ol8_baseos_latest 80 k -Enabling module streams: - javapackages-runtime 201801 - -Transaction Summary -============================================================================================== -Install 106 Packages - -Total download size: 86 M -Installed size: 312 M -Downloading Packages: -(1/106): crypto-policies-scripts-20230731-1.git 862 kB/s | 84 kB 00:00 -(2/106): avahi-libs-0.7-27.el8.x86_64.rpm 602 kB/s | 61 kB 00:00 -(3/106): cpio-2.12-11.el8.x86_64.rpm 1.8 MB/s | 266 kB 00:00 -(4/106): cups-libs-2.2.6-60.el8_10.x86_64.rpm 5.7 MB/s | 435 kB 00:00 -(5/106): dejavu-sans-mono-fonts-2.35-7.el8.noar 5.1 MB/s | 447 kB 00:00 -(6/106): dracut-049-233.git20240115.0.1.el8.x86 7.0 MB/s | 382 kB 00:00 -(7/106): gdk-pixbuf2-2.36.12-6.el8_10.x86_64.rp 12 MB/s | 465 kB 00:00 -(8/106): gettext-libs-0.19.8.1-17.el8.x86_64.rp 9.3 MB/s | 312 kB 00:00 -(9/106): gettext-0.19.8.1-17.el8.x86_64.rpm 16 MB/s | 1.1 MB 00:00 -(10/106): glib-networking-2.56.1-1.1.el8.x86_64 6.0 MB/s | 155 kB 00:00 -(11/106): grub2-common-2.02-156.0.2.el8.noarch. 26 MB/s | 897 kB 00:00 -(12/106): grub2-tools-minimal-2.02-156.0.2.el8. 8.2 MB/s | 215 kB 00:00 -(13/106): grubby-8.40-49.0.2.el8.x86_64.rpm 2.1 MB/s | 50 kB 00:00 -(14/106): grub2-tools-2.02-156.0.2.el8.x86_64.r 26 MB/s | 2.0 MB 00:00 -(15/106): gsettings-desktop-schemas-3.32.0-6.el 19 MB/s | 633 kB 00:00 -(16/106): hardlink-1.3-6.el8.x86_64.rpm 1.1 MB/s | 29 kB 00:00 -(17/106): json-glib-1.4.4-1.el8.x86_64.rpm 5.9 MB/s | 144 kB 00:00 -(18/106): kbd-2.0.4-11.el8.x86_64.rpm 14 MB/s | 390 kB 00:00 -(19/106): kbd-legacy-2.0.4-11.el8.noarch.rpm 17 MB/s | 481 kB 00:00 -(20/106): kbd-misc-2.0.4-11.el8.noarch.rpm 41 MB/s | 1.5 MB 00:00 -(21/106): libcroco-0.6.12-4.el8_2.1.x86_64.rpm 4.7 MB/s | 113 kB 00:00 -(22/106): libgomp-8.5.0-22.0.1.el8_10.x86_64.rp 9.1 MB/s | 218 kB 00:00 -(23/106): libgusb-0.3.0-1.el8.x86_64.rpm 2.1 MB/s | 49 kB 00:00 -(24/106): libkcapi-1.4.0-2.0.1.el8.x86_64.rpm 1.6 MB/s | 52 kB 00:00 -(25/106): libkcapi-hmaccalc-1.4.0-2.0.1.el8.x86 822 kB/s | 31 kB 00:00 -(26/106): libmodman-2.0.1-17.el8.x86_64.rpm 1.6 MB/s | 36 kB 00:00 -(27/106): libpkgconf-1.4.2-1.el8.x86_64.rpm 1.2 MB/s | 35 kB 00:00 -(28/106): libproxy-0.4.15-5.2.el8.x86_64.rpm 3.0 MB/s | 75 kB 00:00 -(29/106): libsoup-2.62.3-5.el8.x86_64.rpm 15 MB/s | 424 kB 00:00 -(30/106): lksctp-tools-1.0.18-3.el8.x86_64.rpm 3.5 MB/s | 100 kB 00:00 -(31/106): memstrack-0.2.5-2.el8.x86_64.rpm 2.2 MB/s | 51 kB 00:00 -(32/106): os-prober-1.74-9.0.1.el8.x86_64.rpm 2.2 MB/s | 51 kB 00:00 -(33/106): pigz-2.4-4.el8.x86_64.rpm 3.5 MB/s | 80 kB 00:00 -(34/106): pkgconf-1.4.2-1.el8.x86_64.rpm 1.7 MB/s | 38 kB 00:00 -(35/106): pkgconf-m4-1.4.2-1.el8.noarch.rpm 761 kB/s | 17 kB 00:00 -(36/106): pkgconf-pkg-config-1.4.2-1.el8.x86_64 691 kB/s | 15 kB 00:00 -(37/106): shared-mime-info-1.9-4.el8.x86_64.rpm 13 MB/s | 328 kB 00:00 -(38/106): systemd-udev-239-78.0.4.el8.x86_64.rp 32 MB/s | 1.6 MB 00:00 -(39/106): xz-5.2.4-4.el8_6.x86_64.rpm 5.2 MB/s | 153 kB 00:00 -(40/106): abattis-cantarell-fonts-0.0.25-6.el8. 6.4 MB/s | 155 kB 00:00 -(41/106): adwaita-cursor-theme-3.28.0-3.el8.noa 22 MB/s | 647 kB 00:00 -(42/106): alsa-lib-1.2.10-2.el8.x86_64.rpm 18 MB/s | 500 kB 00:00 -(43/106): at-spi2-atk-2.26.2-1.el8.x86_64.rpm 3.8 MB/s | 89 kB 00:00 -(44/106): at-spi2-core-2.28.0-1.el8.x86_64.rpm 6.9 MB/s | 169 kB 00:00 -(45/106): atk-2.28.1-1.el8.x86_64.rpm 9.2 MB/s | 272 kB 00:00 -(46/106): cairo-1.15.12-6.el8.x86_64.rpm 24 MB/s | 719 kB 00:00 -(47/106): adwaita-icon-theme-3.28.0-3.el8.noarc 65 MB/s | 11 MB 00:00 -(48/106): cairo-gobject-1.15.12-6.el8.x86_64.rp 914 kB/s | 33 kB 00:00 -(49/106): colord-libs-1.4.2-1.el8.x86_64.rpm 9.5 MB/s | 236 kB 00:00 -(50/106): copy-jdk-configs-4.0-2.el8.noarch.rpm 1.1 MB/s | 30 kB 00:00 -(51/106): dconf-0.28.0-4.0.1.el8.x86_64.rpm 4.4 MB/s | 108 kB 00:00 -(52/106): fribidi-1.0.4-9.el8.x86_64.rpm 3.9 MB/s | 89 kB 00:00 -(53/106): graphite2-1.3.10-10.el8.x86_64.rpm 5.1 MB/s | 122 kB 00:00 -(54/106): gdk-pixbuf2-modules-2.36.12-6.el8_10. 3.6 MB/s | 108 kB 00:00 -(55/106): gtk-update-icon-cache-3.22.30-11.el8. 1.4 MB/s | 32 kB 00:00 -(56/106): harfbuzz-1.7.5-4.el8.x86_64.rpm 11 MB/s | 295 kB 00:00 -(57/106): gtk3-3.22.30-11.el8.x86_64.rpm 68 MB/s | 4.5 MB 00:00 -(58/106): hicolor-icon-theme-0.17-2.el8.noarch. 2.1 MB/s | 48 kB 00:00 -(59/106): java-11-openjdk-11.0.24.0.8-3.0.1.el8 17 MB/s | 475 kB 00:00 -(60/106): jasper-libs-2.0.14-5.el8.x86_64.rpm 5.0 MB/s | 167 kB 00:00 -(61/106): java-11-openjdk-devel-11.0.24.0.8-3.0 61 MB/s | 3.4 MB 00:00 -(62/106): javapackages-filesystem-5.3.0-1.modul 1.2 MB/s | 30 kB 00:00 -(63/106): jbigkit-libs-2.1-14.el8.x86_64.rpm 2.1 MB/s | 55 kB 00:00 -(64/106): lcms2-2.9-2.el8.x86_64.rpm 3.8 MB/s | 164 kB 00:00 -(65/106): libX11-1.6.8-8.el8.x86_64.rpm 20 MB/s | 611 kB 00:00 -(66/106): libX11-common-1.6.8-8.el8.noarch.rpm 6.8 MB/s | 157 kB 00:00 -(67/106): libXau-1.0.9-3.el8.x86_64.rpm 1.6 MB/s | 37 kB 00:00 -(68/106): libXcomposite-0.4.4-14.el8.x86_64.rpm 1.3 MB/s | 28 kB 00:00 -(69/106): libXcursor-1.1.15-3.el8.x86_64.rpm 1.6 MB/s | 36 kB 00:00 -(70/106): libXdamage-1.1.4-14.el8.x86_64.rpm 1.2 MB/s | 27 kB 00:00 -(71/106): libXext-1.3.4-1.el8.x86_64.rpm 2.0 MB/s | 45 kB 00:00 -(72/106): libXfixes-5.0.3-7.el8.x86_64.rpm 1.1 MB/s | 25 kB 00:00 -(73/106): libXft-2.3.3-1.el8.x86_64.rpm 2.9 MB/s | 67 kB 00:00 -(74/106): libXi-1.7.10-1.el8.x86_64.rpm 2.2 MB/s | 49 kB 00:00 -(75/106): libXinerama-1.1.4-1.el8.x86_64.rpm 717 kB/s | 15 kB 00:00 -(76/106): libXrandr-1.5.2-1.el8.x86_64.rpm 1.5 MB/s | 34 kB 00:00 -(77/106): libXrender-0.9.10-7.el8.x86_64.rpm 1.4 MB/s | 33 kB 00:00 -(78/106): libXtst-1.2.3-7.el8.x86_64.rpm 957 kB/s | 22 kB 00:00 -(79/106): java-11-openjdk-headless-11.0.24.0.8- 71 MB/s | 42 MB 00:00 -(80/106): libdatrie-0.2.9-7.el8.x86_64.rpm 274 kB/s | 33 kB 00:00 -(81/106): libepoxy-1.5.8-1.el8.x86_64.rpm 9.1 MB/s | 225 kB 00:00 -(82/106): libfontenc-1.1.3-8.el8.x86_64.rpm 1.5 MB/s | 37 kB 00:00 -(83/106): libthai-0.1.27-2.el8.x86_64.rpm 8.2 MB/s | 203 kB 00:00 -(84/106): libjpeg-turbo-1.5.3-12.el8.x86_64.rpm 5.1 MB/s | 157 kB 00:00 -(85/106): libtiff-4.0.9-32.el8_10.x86_64.rpm 7.8 MB/s | 189 kB 00:00 -(86/106): libwayland-client-1.21.0-1.el8.x86_64 1.7 MB/s | 41 kB 00:00 -(87/106): libwayland-cursor-1.21.0-1.el8.x86_64 1.2 MB/s | 26 kB 00:00 -(88/106): libwayland-egl-1.21.0-1.el8.x86_64.rp 801 kB/s | 19 kB 00:00 -(89/106): libxcb-1.13.1-1.el8.x86_64.rpm 9.7 MB/s | 231 kB 00:00 -(90/106): libxkbcommon-0.9.1-1.el8.x86_64.rpm 5.0 MB/s | 116 kB 00:00 -(91/106): nspr-4.35.0-1.el8_8.x86_64.rpm 6.0 MB/s | 143 kB 00:00 -(92/106): lua-5.3.4-12.el8.x86_64.rpm 5.9 MB/s | 192 kB 00:00 -(93/106): nss-softokn-3.90.0-7.el8_10.x86_64.rp 38 MB/s | 1.2 MB 00:00 -(94/106): nss-3.90.0-7.el8_10.x86_64.rpm 17 MB/s | 750 kB 00:00 -(95/106): nss-softokn-freebl-3.90.0-7.el8_10.x8 14 MB/s | 375 kB 00:00 -(96/106): nss-sysinit-3.90.0-7.el8_10.x86_64.rp 3.2 MB/s | 74 kB 00:00 -(97/106): nss-util-3.90.0-7.el8_10.x86_64.rpm 5.8 MB/s | 139 kB 00:00 -(98/106): pango-1.42.4-8.el8.x86_64.rpm 11 MB/s | 297 kB 00:00 -(99/106): pixman-0.38.4-4.el8.x86_64.rpm 10 MB/s | 256 kB 00:00 -(100/106): rest-0.8.1-2.el8.x86_64.rpm 3.1 MB/s | 70 kB 00:00 -(101/106): ttmkfdir-3.0.9-54.el8.x86_64.rpm 2.5 MB/s | 62 kB 00:00 -(102/106): tzdata-java-2024a-1.0.1.el8.noarch.r 7.4 MB/s | 186 kB 00:00 -(103/106): xkeyboard-config-2.28-1.el8.noarch.r 27 MB/s | 782 kB 00:00 -(104/106): xorg-x11-font-utils-7.5-41.el8.x86_6 3.9 MB/s | 104 kB 00:00 -(105/106): xorg-x11-fonts-Type1-7.5-19.el8.noar 1.3 MB/s | 522 kB 00:00 -(106/106): file-5.33-25.el8.x86_64.rpm 26 kB/s | 77 kB 00:02 --------------------------------------------------------------------------------- -Total 27 MB/s | 86 MB 00:03 -Running transaction check -Transaction check succeeded. -Running transaction test -Transaction test succeeded. -Running transaction - Running scriptlet: copy-jdk-configs-4.0-2.el8.noarch 1/1 - Running scriptlet: java-11-openjdk-headless-1:11.0.24.0.8-3.0.1.el8.x86 1/1 - Preparing : 1/1 - Installing : nspr-4.35.0-1.el8_8.x86_64 1/106 - Running scriptlet: nspr-4.35.0-1.el8_8.x86_64 1/106 - Installing : nss-util-3.90.0-7.el8_10.x86_64 2/106 - Installing : libjpeg-turbo-1.5.3-12.el8.x86_64 3/106 - Installing : pixman-0.38.4-4.el8.x86_64 4/106 - Installing : libwayland-client-1.21.0-1.el8.x86_64 5/106 - Installing : atk-2.28.1-1.el8.x86_64 6/106 - Installing : libgomp-8.5.0-22.0.1.el8_10.x86_64 7/106 - Running scriptlet: libgomp-8.5.0-22.0.1.el8_10.x86_64 7/106 - Installing : libcroco-0.6.12-4.el8_2.1.x86_64 8/106 - Running scriptlet: libcroco-0.6.12-4.el8_2.1.x86_64 8/106 - Installing : grub2-common-1:2.02-156.0.2.el8.noarch 9/106 - Installing : gettext-libs-0.19.8.1-17.el8.x86_64 10/106 - Installing : gettext-0.19.8.1-17.el8.x86_64 11/106 - Running scriptlet: gettext-0.19.8.1-17.el8.x86_64 11/106 - Installing : grub2-tools-minimal-1:2.02-156.0.2.el8.x86_64 12/106 - Installing : libwayland-cursor-1.21.0-1.el8.x86_64 13/106 - Installing : jasper-libs-2.0.14-5.el8.x86_64 14/106 - Installing : nss-softokn-freebl-3.90.0-7.el8_10.x86_64 15/106 - Installing : nss-softokn-3.90.0-7.el8_10.x86_64 16/106 - Installing : xkeyboard-config-2.28-1.el8.noarch 17/106 - Installing : libxkbcommon-0.9.1-1.el8.x86_64 18/106 - Installing : tzdata-java-2024a-1.0.1.el8.noarch 19/106 - Installing : ttmkfdir-3.0.9-54.el8.x86_64 20/106 - Installing : lua-5.3.4-12.el8.x86_64 21/106 - Installing : copy-jdk-configs-4.0-2.el8.noarch 22/106 - Installing : libwayland-egl-1.21.0-1.el8.x86_64 23/106 - Installing : libfontenc-1.1.3-8.el8.x86_64 24/106 - Installing : libepoxy-1.5.8-1.el8.x86_64 25/106 - Installing : libdatrie-0.2.9-7.el8.x86_64 26/106 - Running scriptlet: libdatrie-0.2.9-7.el8.x86_64 26/106 - Installing : libthai-0.1.27-2.el8.x86_64 27/106 - Running scriptlet: libthai-0.1.27-2.el8.x86_64 27/106 - Installing : libXau-1.0.9-3.el8.x86_64 28/106 - Installing : libxcb-1.13.1-1.el8.x86_64 29/106 - Installing : libX11-common-1.6.8-8.el8.noarch 30/106 - Installing : libX11-1.6.8-8.el8.x86_64 31/106 - Installing : libXext-1.3.4-1.el8.x86_64 32/106 - Installing : libXrender-0.9.10-7.el8.x86_64 33/106 - Installing : cairo-1.15.12-6.el8.x86_64 34/106 - Installing : libXi-1.7.10-1.el8.x86_64 35/106 - Installing : libXfixes-5.0.3-7.el8.x86_64 36/106 - Installing : libXtst-1.2.3-7.el8.x86_64 37/106 - Installing : libXcomposite-0.4.4-14.el8.x86_64 38/106 - Installing : at-spi2-core-2.28.0-1.el8.x86_64 39/106 - Running scriptlet: at-spi2-core-2.28.0-1.el8.x86_64 39/106 - Installing : at-spi2-atk-2.26.2-1.el8.x86_64 40/106 - Running scriptlet: at-spi2-atk-2.26.2-1.el8.x86_64 40/106 - Installing : libXcursor-1.1.15-3.el8.x86_64 41/106 - Installing : libXdamage-1.1.4-14.el8.x86_64 42/106 - Installing : cairo-gobject-1.15.12-6.el8.x86_64 43/106 - Installing : libXft-2.3.3-1.el8.x86_64 44/106 - Installing : libXrandr-1.5.2-1.el8.x86_64 45/106 - Installing : libXinerama-1.1.4-1.el8.x86_64 46/106 - Installing : lcms2-2.9-2.el8.x86_64 47/106 - Running scriptlet: lcms2-2.9-2.el8.x86_64 47/106 - Installing : jbigkit-libs-2.1-14.el8.x86_64 48/106 - Running scriptlet: jbigkit-libs-2.1-14.el8.x86_64 48/106 - Installing : libtiff-4.0.9-32.el8_10.x86_64 49/106 - Installing : javapackages-filesystem-5.3.0-1.module+el8+5136+ 50/106 - Installing : hicolor-icon-theme-0.17-2.el8.noarch 51/106 - Installing : graphite2-1.3.10-10.el8.x86_64 52/106 - Installing : harfbuzz-1.7.5-4.el8.x86_64 53/106 - Running scriptlet: harfbuzz-1.7.5-4.el8.x86_64 53/106 - Installing : fribidi-1.0.4-9.el8.x86_64 54/106 - Installing : pango-1.42.4-8.el8.x86_64 55/106 - Running scriptlet: pango-1.42.4-8.el8.x86_64 55/106 - Installing : dconf-0.28.0-4.0.1.el8.x86_64 56/106 - Installing : alsa-lib-1.2.10-2.el8.x86_64 57/106 - Running scriptlet: alsa-lib-1.2.10-2.el8.x86_64 57/106 - Installing : adwaita-cursor-theme-3.28.0-3.el8.noarch 58/106 - Installing : adwaita-icon-theme-3.28.0-3.el8.noarch 59/106 - Installing : abattis-cantarell-fonts-0.0.25-6.el8.noarch 60/106 - Installing : xz-5.2.4-4.el8_6.x86_64 61/106 - Installing : shared-mime-info-1.9-4.el8.x86_64 62/106 - Running scriptlet: shared-mime-info-1.9-4.el8.x86_64 62/106 - Installing : gdk-pixbuf2-2.36.12-6.el8_10.x86_64 63/106 - Running scriptlet: gdk-pixbuf2-2.36.12-6.el8_10.x86_64 63/106 - Installing : gdk-pixbuf2-modules-2.36.12-6.el8_10.x86_64 64/106 - Installing : gtk-update-icon-cache-3.22.30-11.el8.x86_64 65/106 - Installing : pkgconf-m4-1.4.2-1.el8.noarch 66/106 - Installing : pigz-2.4-4.el8.x86_64 67/106 - Installing : memstrack-0.2.5-2.el8.x86_64 68/106 - Installing : lksctp-tools-1.0.18-3.el8.x86_64 69/106 - Running scriptlet: lksctp-tools-1.0.18-3.el8.x86_64 69/106 - Installing : libpkgconf-1.4.2-1.el8.x86_64 70/106 - Installing : pkgconf-1.4.2-1.el8.x86_64 71/106 - Installing : pkgconf-pkg-config-1.4.2-1.el8.x86_64 72/106 - Installing : xorg-x11-font-utils-1:7.5-41.el8.x86_64 73/106 - Installing : xorg-x11-fonts-Type1-7.5-19.el8.noarch 74/106 - Running scriptlet: xorg-x11-fonts-Type1-7.5-19.el8.noarch 74/106 - Installing : libmodman-2.0.1-17.el8.x86_64 75/106 - Running scriptlet: libmodman-2.0.1-17.el8.x86_64 75/106 - Installing : libproxy-0.4.15-5.2.el8.x86_64 76/106 - Running scriptlet: libproxy-0.4.15-5.2.el8.x86_64 76/106 - Installing : libkcapi-1.4.0-2.0.1.el8.x86_64 77/106 - Installing : libkcapi-hmaccalc-1.4.0-2.0.1.el8.x86_64 78/106 - Installing : libgusb-0.3.0-1.el8.x86_64 79/106 - Installing : colord-libs-1.4.2-1.el8.x86_64 80/106 - Installing : kbd-misc-2.0.4-11.el8.noarch 81/106 - Installing : kbd-legacy-2.0.4-11.el8.noarch 82/106 - Installing : kbd-2.0.4-11.el8.x86_64 83/106 - Installing : systemd-udev-239-78.0.4.el8.x86_64 84/106 - Running scriptlet: systemd-udev-239-78.0.4.el8.x86_64 84/106 - Installing : os-prober-1.74-9.0.1.el8.x86_64 85/106 - Installing : json-glib-1.4.4-1.el8.x86_64 86/106 - Installing : hardlink-1:1.3-6.el8.x86_64 87/106 - Installing : file-5.33-25.el8.x86_64 88/106 - Installing : dejavu-sans-mono-fonts-2.35-7.el8.noarch 89/106 - Installing : gsettings-desktop-schemas-3.32.0-6.el8.x86_64 90/106 - Installing : glib-networking-2.56.1-1.1.el8.x86_64 91/106 - Installing : libsoup-2.62.3-5.el8.x86_64 92/106 - Installing : rest-0.8.1-2.el8.x86_64 93/106 - Running scriptlet: rest-0.8.1-2.el8.x86_64 93/106 - Installing : cpio-2.12-11.el8.x86_64 94/106 - Installing : dracut-049-233.git20240115.0.1.el8.x86_64 95/106 - Running scriptlet: grub2-tools-1:2.02-156.0.2.el8.x86_64 96/106 - Installing : grub2-tools-1:2.02-156.0.2.el8.x86_64 96/106 - Running scriptlet: grub2-tools-1:2.02-156.0.2.el8.x86_64 96/106 - Installing : grubby-8.40-49.0.2.el8.x86_64 97/106 - Installing : crypto-policies-scripts-20230731-1.git3177e06.el 98/106 - Installing : nss-sysinit-3.90.0-7.el8_10.x86_64 99/106 - Installing : nss-3.90.0-7.el8_10.x86_64 100/106 - Installing : avahi-libs-0.7-27.el8.x86_64 101/106 - Installing : cups-libs-1:2.2.6-60.el8_10.x86_64 102/106 - Installing : java-11-openjdk-headless-1:11.0.24.0.8-3.0.1.el8 103/106 - Running scriptlet: java-11-openjdk-headless-1:11.0.24.0.8-3.0.1.el8 103/106 - Installing : gtk3-3.22.30-11.el8.x86_64 104/106 - Installing : java-11-openjdk-1:11.0.24.0.8-3.0.1.el8.x86_64 105/106 - Running scriptlet: java-11-openjdk-1:11.0.24.0.8-3.0.1.el8.x86_64 105/106 - Installing : java-11-openjdk-devel-1:11.0.24.0.8-3.0.1.el8.x8 106/106 - Running scriptlet: java-11-openjdk-devel-1:11.0.24.0.8-3.0.1.el8.x8 106/106 - Running scriptlet: copy-jdk-configs-4.0-2.el8.noarch 106/106 - Running scriptlet: dconf-0.28.0-4.0.1.el8.x86_64 106/106 - Running scriptlet: crypto-policies-scripts-20230731-1.git3177e06.el 106/106 - Running scriptlet: nss-3.90.0-7.el8_10.x86_64 106/106 - Running scriptlet: java-11-openjdk-headless-1:11.0.24.0.8-3.0.1.el8 106/106 - Running scriptlet: java-11-openjdk-1:11.0.24.0.8-3.0.1.el8.x86_64 106/106 - Running scriptlet: java-11-openjdk-devel-1:11.0.24.0.8-3.0.1.el8.x8 106/106 - Running scriptlet: hicolor-icon-theme-0.17-2.el8.noarch 106/106 - Running scriptlet: adwaita-icon-theme-3.28.0-3.el8.noarch 106/106 - Running scriptlet: shared-mime-info-1.9-4.el8.x86_64 106/106 - Running scriptlet: gdk-pixbuf2-2.36.12-6.el8_10.x86_64 106/106 - Running scriptlet: systemd-udev-239-78.0.4.el8.x86_64 106/106 - Verifying : avahi-libs-0.7-27.el8.x86_64 1/106 - Verifying : cpio-2.12-11.el8.x86_64 2/106 - Verifying : crypto-policies-scripts-20230731-1.git3177e06.el 3/106 - Verifying : cups-libs-1:2.2.6-60.el8_10.x86_64 4/106 - Verifying : dejavu-sans-mono-fonts-2.35-7.el8.noarch 5/106 - Verifying : dracut-049-233.git20240115.0.1.el8.x86_64 6/106 - Verifying : file-5.33-25.el8.x86_64 7/106 - Verifying : gdk-pixbuf2-2.36.12-6.el8_10.x86_64 8/106 - Verifying : gettext-0.19.8.1-17.el8.x86_64 9/106 - Verifying : gettext-libs-0.19.8.1-17.el8.x86_64 10/106 - Verifying : glib-networking-2.56.1-1.1.el8.x86_64 11/106 - Verifying : grub2-common-1:2.02-156.0.2.el8.noarch 12/106 - Verifying : grub2-tools-1:2.02-156.0.2.el8.x86_64 13/106 - Verifying : grub2-tools-minimal-1:2.02-156.0.2.el8.x86_64 14/106 - Verifying : grubby-8.40-49.0.2.el8.x86_64 15/106 - Verifying : gsettings-desktop-schemas-3.32.0-6.el8.x86_64 16/106 - Verifying : hardlink-1:1.3-6.el8.x86_64 17/106 - Verifying : json-glib-1.4.4-1.el8.x86_64 18/106 - Verifying : kbd-2.0.4-11.el8.x86_64 19/106 - Verifying : kbd-legacy-2.0.4-11.el8.noarch 20/106 - Verifying : kbd-misc-2.0.4-11.el8.noarch 21/106 - Verifying : libcroco-0.6.12-4.el8_2.1.x86_64 22/106 - Verifying : libgomp-8.5.0-22.0.1.el8_10.x86_64 23/106 - Verifying : libgusb-0.3.0-1.el8.x86_64 24/106 - Verifying : libkcapi-1.4.0-2.0.1.el8.x86_64 25/106 - Verifying : libkcapi-hmaccalc-1.4.0-2.0.1.el8.x86_64 26/106 - Verifying : libmodman-2.0.1-17.el8.x86_64 27/106 - Verifying : libpkgconf-1.4.2-1.el8.x86_64 28/106 - Verifying : libproxy-0.4.15-5.2.el8.x86_64 29/106 - Verifying : libsoup-2.62.3-5.el8.x86_64 30/106 - Verifying : lksctp-tools-1.0.18-3.el8.x86_64 31/106 - Verifying : memstrack-0.2.5-2.el8.x86_64 32/106 - Verifying : os-prober-1.74-9.0.1.el8.x86_64 33/106 - Verifying : pigz-2.4-4.el8.x86_64 34/106 - Verifying : pkgconf-1.4.2-1.el8.x86_64 35/106 - Verifying : pkgconf-m4-1.4.2-1.el8.noarch 36/106 - Verifying : pkgconf-pkg-config-1.4.2-1.el8.x86_64 37/106 - Verifying : shared-mime-info-1.9-4.el8.x86_64 38/106 - Verifying : systemd-udev-239-78.0.4.el8.x86_64 39/106 - Verifying : xz-5.2.4-4.el8_6.x86_64 40/106 - Verifying : abattis-cantarell-fonts-0.0.25-6.el8.noarch 41/106 - Verifying : adwaita-cursor-theme-3.28.0-3.el8.noarch 42/106 - Verifying : adwaita-icon-theme-3.28.0-3.el8.noarch 43/106 - Verifying : alsa-lib-1.2.10-2.el8.x86_64 44/106 - Verifying : at-spi2-atk-2.26.2-1.el8.x86_64 45/106 - Verifying : at-spi2-core-2.28.0-1.el8.x86_64 46/106 - Verifying : atk-2.28.1-1.el8.x86_64 47/106 - Verifying : cairo-1.15.12-6.el8.x86_64 48/106 - Verifying : cairo-gobject-1.15.12-6.el8.x86_64 49/106 - Verifying : colord-libs-1.4.2-1.el8.x86_64 50/106 - Verifying : copy-jdk-configs-4.0-2.el8.noarch 51/106 - Verifying : dconf-0.28.0-4.0.1.el8.x86_64 52/106 - Verifying : fribidi-1.0.4-9.el8.x86_64 53/106 - Verifying : gdk-pixbuf2-modules-2.36.12-6.el8_10.x86_64 54/106 - Verifying : graphite2-1.3.10-10.el8.x86_64 55/106 - Verifying : gtk-update-icon-cache-3.22.30-11.el8.x86_64 56/106 - Verifying : gtk3-3.22.30-11.el8.x86_64 57/106 - Verifying : harfbuzz-1.7.5-4.el8.x86_64 58/106 - Verifying : hicolor-icon-theme-0.17-2.el8.noarch 59/106 - Verifying : jasper-libs-2.0.14-5.el8.x86_64 60/106 - Verifying : java-11-openjdk-1:11.0.24.0.8-3.0.1.el8.x86_64 61/106 - Verifying : java-11-openjdk-devel-1:11.0.24.0.8-3.0.1.el8.x8 62/106 - Verifying : java-11-openjdk-headless-1:11.0.24.0.8-3.0.1.el8 63/106 - Verifying : javapackages-filesystem-5.3.0-1.module+el8+5136+ 64/106 - Verifying : jbigkit-libs-2.1-14.el8.x86_64 65/106 - Verifying : lcms2-2.9-2.el8.x86_64 66/106 - Verifying : libX11-1.6.8-8.el8.x86_64 67/106 - Verifying : libX11-common-1.6.8-8.el8.noarch 68/106 - Verifying : libXau-1.0.9-3.el8.x86_64 69/106 - Verifying : libXcomposite-0.4.4-14.el8.x86_64 70/106 - Verifying : libXcursor-1.1.15-3.el8.x86_64 71/106 - Verifying : libXdamage-1.1.4-14.el8.x86_64 72/106 - Verifying : libXext-1.3.4-1.el8.x86_64 73/106 - Verifying : libXfixes-5.0.3-7.el8.x86_64 74/106 - Verifying : libXft-2.3.3-1.el8.x86_64 75/106 - Verifying : libXi-1.7.10-1.el8.x86_64 76/106 - Verifying : libXinerama-1.1.4-1.el8.x86_64 77/106 - Verifying : libXrandr-1.5.2-1.el8.x86_64 78/106 - Verifying : libXrender-0.9.10-7.el8.x86_64 79/106 - Verifying : libXtst-1.2.3-7.el8.x86_64 80/106 - Verifying : libdatrie-0.2.9-7.el8.x86_64 81/106 - Verifying : libepoxy-1.5.8-1.el8.x86_64 82/106 - Verifying : libfontenc-1.1.3-8.el8.x86_64 83/106 - Verifying : libjpeg-turbo-1.5.3-12.el8.x86_64 84/106 - Verifying : libthai-0.1.27-2.el8.x86_64 85/106 - Verifying : libtiff-4.0.9-32.el8_10.x86_64 86/106 - Verifying : libwayland-client-1.21.0-1.el8.x86_64 87/106 - Verifying : libwayland-cursor-1.21.0-1.el8.x86_64 88/106 - Verifying : libwayland-egl-1.21.0-1.el8.x86_64 89/106 - Verifying : libxcb-1.13.1-1.el8.x86_64 90/106 - Verifying : libxkbcommon-0.9.1-1.el8.x86_64 91/106 - Verifying : lua-5.3.4-12.el8.x86_64 92/106 - Verifying : nspr-4.35.0-1.el8_8.x86_64 93/106 - Verifying : nss-3.90.0-7.el8_10.x86_64 94/106 - Verifying : nss-softokn-3.90.0-7.el8_10.x86_64 95/106 - Verifying : nss-softokn-freebl-3.90.0-7.el8_10.x86_64 96/106 - Verifying : nss-sysinit-3.90.0-7.el8_10.x86_64 97/106 - Verifying : nss-util-3.90.0-7.el8_10.x86_64 98/106 - Verifying : pango-1.42.4-8.el8.x86_64 99/106 - Verifying : pixman-0.38.4-4.el8.x86_64 100/106 - Verifying : rest-0.8.1-2.el8.x86_64 101/106 - Verifying : ttmkfdir-3.0.9-54.el8.x86_64 102/106 - Verifying : tzdata-java-2024a-1.0.1.el8.noarch 103/106 - Verifying : xkeyboard-config-2.28-1.el8.noarch 104/106 - Verifying : xorg-x11-font-utils-1:7.5-41.el8.x86_64 105/106 - Verifying : xorg-x11-fonts-Type1-7.5-19.el8.noarch 106/106 - -Installed: - abattis-cantarell-fonts-0.0.25-6.el8.noarch - adwaita-cursor-theme-3.28.0-3.el8.noarch - adwaita-icon-theme-3.28.0-3.el8.noarch - alsa-lib-1.2.10-2.el8.x86_64 - at-spi2-atk-2.26.2-1.el8.x86_64 - at-spi2-core-2.28.0-1.el8.x86_64 - atk-2.28.1-1.el8.x86_64 - avahi-libs-0.7-27.el8.x86_64 - cairo-1.15.12-6.el8.x86_64 - cairo-gobject-1.15.12-6.el8.x86_64 - colord-libs-1.4.2-1.el8.x86_64 - copy-jdk-configs-4.0-2.el8.noarch - cpio-2.12-11.el8.x86_64 - crypto-policies-scripts-20230731-1.git3177e06.el8.noarch - cups-libs-1:2.2.6-60.el8_10.x86_64 - dconf-0.28.0-4.0.1.el8.x86_64 - dejavu-sans-mono-fonts-2.35-7.el8.noarch - dracut-049-233.git20240115.0.1.el8.x86_64 - file-5.33-25.el8.x86_64 - fribidi-1.0.4-9.el8.x86_64 - gdk-pixbuf2-2.36.12-6.el8_10.x86_64 - gdk-pixbuf2-modules-2.36.12-6.el8_10.x86_64 - gettext-0.19.8.1-17.el8.x86_64 - gettext-libs-0.19.8.1-17.el8.x86_64 - glib-networking-2.56.1-1.1.el8.x86_64 - graphite2-1.3.10-10.el8.x86_64 - grub2-common-1:2.02-156.0.2.el8.noarch - grub2-tools-1:2.02-156.0.2.el8.x86_64 - grub2-tools-minimal-1:2.02-156.0.2.el8.x86_64 - grubby-8.40-49.0.2.el8.x86_64 - gsettings-desktop-schemas-3.32.0-6.el8.x86_64 - gtk-update-icon-cache-3.22.30-11.el8.x86_64 - gtk3-3.22.30-11.el8.x86_64 - hardlink-1:1.3-6.el8.x86_64 - harfbuzz-1.7.5-4.el8.x86_64 - hicolor-icon-theme-0.17-2.el8.noarch - jasper-libs-2.0.14-5.el8.x86_64 - java-11-openjdk-1:11.0.24.0.8-3.0.1.el8.x86_64 - java-11-openjdk-devel-1:11.0.24.0.8-3.0.1.el8.x86_64 - java-11-openjdk-headless-1:11.0.24.0.8-3.0.1.el8.x86_64 - javapackages-filesystem-5.3.0-1.module+el8+5136+7ff78f74.noarch - jbigkit-libs-2.1-14.el8.x86_64 - json-glib-1.4.4-1.el8.x86_64 - kbd-2.0.4-11.el8.x86_64 - kbd-legacy-2.0.4-11.el8.noarch - kbd-misc-2.0.4-11.el8.noarch - lcms2-2.9-2.el8.x86_64 - libX11-1.6.8-8.el8.x86_64 - libX11-common-1.6.8-8.el8.noarch - libXau-1.0.9-3.el8.x86_64 - libXcomposite-0.4.4-14.el8.x86_64 - libXcursor-1.1.15-3.el8.x86_64 - libXdamage-1.1.4-14.el8.x86_64 - libXext-1.3.4-1.el8.x86_64 - libXfixes-5.0.3-7.el8.x86_64 - libXft-2.3.3-1.el8.x86_64 - libXi-1.7.10-1.el8.x86_64 - libXinerama-1.1.4-1.el8.x86_64 - libXrandr-1.5.2-1.el8.x86_64 - libXrender-0.9.10-7.el8.x86_64 - libXtst-1.2.3-7.el8.x86_64 - libcroco-0.6.12-4.el8_2.1.x86_64 - libdatrie-0.2.9-7.el8.x86_64 - libepoxy-1.5.8-1.el8.x86_64 - libfontenc-1.1.3-8.el8.x86_64 - libgomp-8.5.0-22.0.1.el8_10.x86_64 - libgusb-0.3.0-1.el8.x86_64 - libjpeg-turbo-1.5.3-12.el8.x86_64 - libkcapi-1.4.0-2.0.1.el8.x86_64 - libkcapi-hmaccalc-1.4.0-2.0.1.el8.x86_64 - libmodman-2.0.1-17.el8.x86_64 - libpkgconf-1.4.2-1.el8.x86_64 - libproxy-0.4.15-5.2.el8.x86_64 - libsoup-2.62.3-5.el8.x86_64 - libthai-0.1.27-2.el8.x86_64 - libtiff-4.0.9-32.el8_10.x86_64 - libwayland-client-1.21.0-1.el8.x86_64 - libwayland-cursor-1.21.0-1.el8.x86_64 - libwayland-egl-1.21.0-1.el8.x86_64 - libxcb-1.13.1-1.el8.x86_64 - libxkbcommon-0.9.1-1.el8.x86_64 - lksctp-tools-1.0.18-3.el8.x86_64 - lua-5.3.4-12.el8.x86_64 - memstrack-0.2.5-2.el8.x86_64 - nspr-4.35.0-1.el8_8.x86_64 - nss-3.90.0-7.el8_10.x86_64 - nss-softokn-3.90.0-7.el8_10.x86_64 - nss-softokn-freebl-3.90.0-7.el8_10.x86_64 - nss-sysinit-3.90.0-7.el8_10.x86_64 - nss-util-3.90.0-7.el8_10.x86_64 - os-prober-1.74-9.0.1.el8.x86_64 - pango-1.42.4-8.el8.x86_64 - pigz-2.4-4.el8.x86_64 - pixman-0.38.4-4.el8.x86_64 - pkgconf-1.4.2-1.el8.x86_64 - pkgconf-m4-1.4.2-1.el8.noarch - pkgconf-pkg-config-1.4.2-1.el8.x86_64 - rest-0.8.1-2.el8.x86_64 - shared-mime-info-1.9-4.el8.x86_64 - systemd-udev-239-78.0.4.el8.x86_64 - ttmkfdir-3.0.9-54.el8.x86_64 - tzdata-java-2024a-1.0.1.el8.noarch - xkeyboard-config-2.28-1.el8.noarch - xorg-x11-font-utils-1:7.5-41.el8.x86_64 - xorg-x11-fonts-Type1-7.5-19.el8.noarch - xz-5.2.4-4.el8_6.x86_64 - -Complete! -Last metadata expiration check: 0:00:23 ago on Tue 20 Aug 2024 08:55:14 AM UTC. -Package iproute-6.2.0-5.el8_9.x86_64 is already installed. -Dependencies resolved. -================================================================================ - Package Architecture Version Repository Size -================================================================================ -Upgrading: - iproute x86_64 6.2.0-6.el8_10 ol8_baseos_latest 853 k - -Transaction Summary -================================================================================ -Upgrade 1 Package - -Total download size: 853 k -Downloading Packages: -iproute-6.2.0-6.el8_10.x86_64.rpm 4.2 MB/s | 853 kB 00:00 --------------------------------------------------------------------------------- -Total 4.2 MB/s | 853 kB 00:00 -Running transaction check -Transaction check succeeded. -Running transaction test -Transaction test succeeded. -Running transaction - Preparing : 1/1 - Upgrading : iproute-6.2.0-6.el8_10.x86_64 1/2 - Cleanup : iproute-6.2.0-5.el8_9.x86_64 2/2 - Running scriptlet: iproute-6.2.0-5.el8_9.x86_64 2/2 - Verifying : iproute-6.2.0-6.el8_10.x86_64 1/2 - Verifying : iproute-6.2.0-5.el8_9.x86_64 2/2 - -Upgraded: - iproute-6.2.0-6.el8_10.x86_64 - -Complete! -24 files removed -Removing intermediate container fe168b01f3ad - ---> 791878694a50 -Step 5/12 : RUN curl -o /tmp/ords-$ORDSVERSION.el8.noarch.rpm https://yum.oracle.com/repo/OracleLinux/OL8/oracle/software/x86_64/getPackage/ords-$ORDSVERSION.el8.noarch.rpm - ---> Running in 59d7143da358 - % Total % Received % Xferd Average Speed Time Time Time Current - Dload Upload Total Spent Left Speed -100 108M 100 108M 0 0 1440k 0 0:01:16 0:01:16 --:--:-- 1578k -Removing intermediate container 59d7143da358 - ---> 17c4534293e5 -Step 6/12 : RUN rpm -ivh /tmp/ords-$ORDSVERSION.el8.noarch.rpm - ---> Running in 84b1cbffdc51 -Verifying... ######################################## -Preparing... ######################################## -Updating / installing... -ords-23.4.0-8.el8 ######################################## -INFO: Before starting ORDS service, run the below command as user oracle: - ords --config /etc/ords/config install -Removing intermediate container 84b1cbffdc51 - ---> 6e7151b79588 -Step 7/12 : RUN mkdir -p $ORDS_HOME/doc_root && mkdir -p $ORDS_HOME/error && mkdir -p $ORDS_HOME/secrets && chmod ug+x $ORDS_HOME/*.sh && groupadd -g 54322 dba && usermod -u 54321 -d /home/oracle -g dba -m -s /bin/bash oracle && chown -R oracle:dba $ORDS_HOME && echo "oracle ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers - ---> Running in 66e5db5f343f -Removing intermediate container 66e5db5f343f - ---> 0523dc897bf4 -Step 8/12 : USER oracle - ---> Running in ffda8495ac77 -Removing intermediate container ffda8495ac77 - ---> 162acd4d0b93 -Step 9/12 : WORKDIR /home/oracle - ---> Running in 8c14310ffbc7 -Removing intermediate container 8c14310ffbc7 - ---> c8dae809e772 -Step 10/12 : VOLUME ["$ORDS_HOME/config/ords"] - ---> Running in ed64548fd997 -Removing intermediate container ed64548fd997 - ---> 22e2c99247b0 -Step 11/12 : EXPOSE 8888 - ---> Running in 921f7c85d61d -Removing intermediate container 921f7c85d61d - ---> e5d503c92224 -Step 12/12 : CMD $ORDS_HOME/$RUN_FILE - ---> Running in cad487298d63 -Removing intermediate container cad487298d63 - ---> fdb17aa242f8 -Successfully built fdb17aa242f8 -Successfully tagged oracle/ords-dboper:latest -08:57:18 oracle@mitk01:# - diff --git a/docs/multitenant/usecase01/logfiles/openssl_execution.log b/docs/multitenant/usecase01/logfiles/openssl_execution.log deleted file mode 100644 index e3915a21..00000000 --- a/docs/multitenant/usecase01/logfiles/openssl_execution.log +++ /dev/null @@ -1,19 +0,0 @@ -CREATING TLS CERTIFICATES -/usr/bin/openssl genrsa -out ca.key 2048 -Generating RSA private key, 2048 bit long modulus (2 primes) -......................+++++ -..................................................+++++ -e is 65537 (0x010001) -/usr/bin/openssl req -new -x509 -days 365 -key ca.key -subj "/C=US/ST=California/L=SanFrancisco/O=oracle /CN=cdb-dev-ords.oracle-database-operator-system /CN=localhost Root CA " -out ca.crt -/usr/bin/openssl req -newkey rsa:2048 -nodes -keyout tls.key -subj "/C=US/ST=California/L=SanFrancisco/O=oracle /CN=cdb-dev-ords.oracle-database-operator-system /CN=localhost" -out server.csr -Generating a RSA private key -...........+++++ -...........................................+++++ -writing new private key to 'tls.key' ------ -/usr/bin/echo "subjectAltName=DNS:cdb-dev-ords.oracle-database-operator-system,DNS:www.example.com" > extfile.txt -/usr/bin/openssl x509 -req -extfile extfile.txt -days 365 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out tls.crt -Signature ok -subject=C = US, ST = California, L = SanFrancisco, O = "oracle ", CN = "cdb-dev-ords.oracle-database-operator-system ", CN = localhost -Getting CA Private Key - diff --git a/docs/multitenant/usecase01/logfiles/ordsconfig.log b/docs/multitenant/usecase01/logfiles/ordsconfig.log deleted file mode 100644 index b787b752..00000000 --- a/docs/multitenant/usecase01/logfiles/ordsconfig.log +++ /dev/null @@ -1,39 +0,0 @@ -ORDS: Release 23.4 Production on Tue Aug 20 07:48:44 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -Database pool: default - -Setting Value Source ------------------------------------------ -------------------------------------------------- ----------- -database.api.enabled true Global -database.api.management.services.disabled false Global -db.cdb.adminUser C##DBAPI_CDB_ADMIN AS SYSDBA Pool -db.cdb.adminUser.password ****** Pool Wallet -db.connectionType customurl Pool -db.customURL jdbc:oracle:thin:@(DESCRIPTION=(CONNECT_TIMEOUT=90 Pool - )(RETRY_COUNT=30)(RETRY_DELAY=10)(TRANSPORT_CONNEC - T_TIMEOUT=70)(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL= - TCP)(HOST=scan12.testrac.com)(PORT=1521)(IP=V4_ONL - Y))(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL=TCP)(HOST= - scan34.testrac.com)(PORT=1521)(IP=V4_ONLY))(CONNEC - T_DATA=(SERVER=DEDICATED)(SERVICE_NAME=TESTORDS))) -db.password ****** Pool Wallet -db.serviceNameSuffix Pool -db.username ORDS_PUBLIC_USER Pool -error.externalPath /opt/oracle/ords/error Global -jdbc.InitialLimit 50 Pool -jdbc.MaxLimit 100 Pool -misc.pagination.maxRows 1000 Pool -plsql.gateway.mode disabled Pool -restEnabledSql.active true Pool -security.requestValidationFunction ords_util.authorize_plsql_gateway Pool -security.verifySSL true Global -standalone.access.log /home/oracle Global -standalone.https.cert /opt/oracle/ords//secrets/tls.crt Global -standalone.https.cert.key /opt/oracle/ords//secrets/tls.key Global -standalone.https.port 8888 Global - diff --git a/docs/multitenant/usecase01/makefile b/docs/multitenant/usecase01/makefile deleted file mode 100644 index d4176c75..00000000 --- a/docs/multitenant/usecase01/makefile +++ /dev/null @@ -1,284 +0,0 @@ -# __ __ _ __ _ _ -# | \/ | __ _| | _____ / _(_) | ___ -# | |\/| |/ _` | |/ / _ \ |_| | |/ _ \ -# | | | | (_| | < __/ _| | | __/ -# |_| |_|\__,_|_|\_\___|_| |_|_|\___| -# -# ___ -# / _ \ _ __ _ __ _ __ ___ _ __ ___ -# | | | | '_ \| '_ \| '__/ _ \ '_ ` _ \ -# | |_| | | | | |_) | | | __/ | | | | | -# \___/|_| |_| .__/|_| \___|_| |_| |_| -# |_| -# ____ _ _ _ -# / ___|___ _ __ | |_ _ __ ___ | | | ___ _ __ -# | | / _ \| '_ \| __| '__/ _ \| | |/ _ \ '__| -# | |__| (_) | | | | |_| | | (_) | | | __/ | -# \____\___/|_| |_|\__|_| \___/|_|_|\___|_| -# -# -# This makefile helps to speed up the kubectl commands executions to deploy and test -# the OnPremises operator. Although it has few functionality you can adapt to your needs -# by adding much more targets. -# -# Quick start: -# ~~~~~~~~~~~ -# -# - Copy files of tab.1 in the makefile directory. -# - Edit the secret files and other yaml files with the correct credential as -# specified in the documentation. -# - Edit makefile updating variables of tab.2 -# - Execute commands of tab.3 "make step1" "make step2" "make step3".... -# -# Tab.1 - List of required files -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# -# +-----------------------------+---------------------------------------------+ -# |oracle-database-operator.yaml| Opertaor yaml file | -# +-----------------------------+---------------------------------------------+ -# |cdb_secret.yaml | Secret file for the rest server pod | -# +-----------------------------+---------------------------------------------+ -# |pdb_secret.yaml | Secret file for the pdb creation | -# +-----------------------------+---------------------------------------------+ -# |tde_secret.yaml | Secret file for the tablepsace enc. | -# +-----------------------------+---------------------------------------------+ -# |cdb_create.yaml | Rest server pod creation | -# +-----------------------------+---------------------------------------------+ -# |pdb_create.yaml | Pluggable database creation | -# +-----------------------------+---------------------------------------------+ -# |pdb_close.yaml | Close pluggable database | -# +-----------------------------+---------------------------------------------+ -# |pdb_open.yaml | Open pluggable database | -# +-----------------------------+---------------------------------------------+ -# |pdb_map.yaml | Map an existing pdb | -# +-----------------------------+---------------------------------------------+ -# |oracle-database-operator.yaml| Database operator | -# +-----------------------------+---------------------------------------------+ -# |Dockerfiles | Dockerfile for CBD | -# +-----------------------------+---------------------------------------------+ -# |runOrdsSSL.sh | Init script executed by Dockerfile | -# +-----------------------------+---------------------------------------------+ -# -# Tab.2 - List of variables -# ~~~~~~~~~~~~~~~~~~~~~~~~~ -# -# +-----------------------------+---------------------------------------------+ -# |OCIR | Your image registry | -# +-----------------------------+---------------------------------------------+ -# |OCIRPATH | Path of the image in your registry | -# +-----------------------------+---------------------------------------------+ -# -# Tab.3 - Execution steps -# ~~~~~~~~~~~~~~~~~~~~~~~ -# -# +-----------------------------+---------------------------------------------+ -# | MAKEFILE TARGETS LIST | -# | ----- ooo ----- | -# | - TARGET - - DESCRIPTION - | -# +-----------------------------+-------------------------------------+-------+ -# |step1 | Build rest server images | | -# +-----------------------------+-------------------------------------+ REST | -# |step2 | Tag the immages | SRV | -# +-----------------------------+-------------------------------------+ IMG | -# |step3 | Push the image into the repository | | -# +-----------------------------+-------------------------------------+-------+ -# |step4 | Load webhook certmanager | DB | -# +-----------------------------+-------------------------------------+ OPER | -# |step5 | Create the db operator | | -# +-----------------------------+-------------------------------------+-------+ -# |step6 | Create tls certificates | T | -# +-----------------------------+-------------------------------------+ L | -# |step7 | Create tls secret | S | -# +-----------------------------+---------------------------------------------+ -# |step8 | Create database secrets | -# +-----------------------------+---------------------------------------------+ -# |step9 | Create restserver pod | -# | | +---------------------------------------------+ -# | +---> checkstep9 | Monitor the executions | -# +-----------------------------+---------------------------------------------+ -# |step10 | Create pluggable database | -# | | +---------------------------------------------+ -# | +---> checkpdb | Monitor PDB status | -# +-----------------------------+---------------------------------------------+ -# |step11 | Close pluggable database | -# +-----------------------------+---------------------------------------------+ -# |step12 | Open pluggable database | -# +-----------------------------+---------------------------------------------+ -# |step13 | Map pluggable database | -# +-----------------------------+---------------------------------------------+ -# | Before testing step13 delete the crd: | -# | kubectl delete pdb pdb1 -n oracle-database-operator-system | -# +---------------------------------------------------------------------------+ -# |step14 | delete pdb | -# +-----------------------------+---------------------------------------------+ -# | DIAGNOSTIC TARGETS | -# +-----------------------------+---------------------------------------------+ -# | dump | Dump pods info into a file | -# +-----------------------------+---------------------------------------------+ -# | reloadop | Reload the db operator | -# +-----------------------------+---------------------------------------------+ -# | login | Login into cdb pod | -# +-----------------------------+---------------------------------------------+ - - -################ TAB 2 VARIABLES ############ -OCIR=[...........YOUR REGISTRY...........] -OCIRPATH=[...PATH IN YOUR REGISTRY.....]/$(REST_SERVER)-dboper:$(ORDSVERSION) -############################################# -REST_SERVER=ords -ORDSVERSION=latest -DOCKER=/usr/bin/docker -KUBECTL=/usr/bin/kubectl -ORDS=/usr/local/bin/ords -CONFIG=/etc/ords/config -IMAGE=oracle/$(REST_SERVER)-dboper:$(ORDSVERSION) -DBOPERATOR=oracle-database-operator.yaml -URLPATH=/_/db-api/stable/database/pdbs/ -OPENSSL=/usr/bin/openssl -ORDSPORT=8888 -MAKE=/usr/bin/make -DOCKERFILE=../../../ords/Dockerfile -RUNSCRIPT=../../../ords/runOrdsSSL.sh -ORDSIMGDIR=../../../ords -RM=/usr/bin/rm -CP=/usr/bin/cp -ECHO=/usr/bin/echo -NAMESPACE=oracle-database-operator-system -CERTMANAGER=https://github.com/jetstack/cert-manager/releases/latest/download/cert-manager.yaml -CDB_SECRET=cdb_secret.yaml -PDB_SECRET=pdb_secret.yaml -TDE_SECRET=tde_secret.yaml -CDB=cdb_create.yaml -PDB=pdb_create.yaml -PDB_CLOSE=pdb_close.yaml -PDB_OPEN=pdb_open.yaml -PDB_MAP=pdb_map.yaml -SKEY=tls.key -SCRT=tls.crt -CART=ca.crt -COMPANY=oracle -LOCALHOST=localhost -RESTPREFIX=cdb-dev - -step1: createimage -step2: tagimage -step3: push -step4: certmanager -step5: dboperator -step6: tlscert -step7: tlssecret -step8: dbsecret -step9: cdb -step10: pdb -step11: close -step12: open -step13: map -step14: delete - -checkstep9: checkcdb - - -createimage: - $(DOCKER) build -t $(IMAGE) $(ORDSIMGDIR) - -tagimage: - @echo "TAG IMAGE" - $(DOCKER) tag $(IMAGE) $(OCIR)$(OCIRPATH) - -push: - @echo "PUSH IMAGE INTO THE REGISTRY" - $(DOCKER) push $(OCIR)$(OCIRPATH) - -certmanager: - @echo "WEBHOOK CERT MANAGER" - $(KUBECTL) apply -f $(CERTMANAGER) - -dboperator: - @echo "ORACLE DATABASE OPERATOR" - $(KUBECTL) apply -f $(DBOPERATOR) - - -#C: Country -#ST: State -#L: locality (city) -#O: Organization Name Organization Unit -#CN: Common Name - -tlscert: - @echo "CREATING TLS CERTIFICATES" - $(OPENSSL) genrsa -out ca.key 2048 - $(OPENSSL) req -new -x509 -days 365 -key ca.key -subj "/C=US/ST=California/L=SanFrancisco/O=$(COMPANY) /CN=$(RESTPREFIX)-$(REST_SERVER).$(NAMESPACE) /CN=$(LOCALHOST) Root CA " -out ca.crt - $(OPENSSL) req -newkey rsa:2048 -nodes -keyout $(SKEY) -subj "/C=US/ST=California/L=SanFrancisco/O=$(COMPANY) /CN=$(RESTPREFIX)-$(REST_SERVER).$(NAMESPACE) /CN=$(LOCALHOST)" -out server.csr - $(ECHO) "subjectAltName=DNS:$(RESTPREFIX)-$(REST_SERVER).$(NAMESPACE),DNS:www.example.com" > extfile.txt - $(OPENSSL) x509 -req -extfile extfile.txt -days 365 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out $(SCRT) - -tlssecret: - @echo "CREATING TLS SECRETS" - $(KUBECTL) create secret tls db-tls --key="$(SKEY)" --cert="$(SCRT)" -n $(NAMESPACE) - $(KUBECTL) create secret generic db-ca --from-file="$(CART)" -n $(NAMESPACE) - -dbsecret: - @echo "CREATING DB SECRETS" - $(KUBECTL) apply -f $(CDB_SECRET) -n $(NAMESPACE) - $(KUBECTL) apply -f $(PDB_SECRET) -n $(NAMESPACE) - $(KUBECTL) apply -f $(TDE_SECRET) -n $(NAMESPACE) - -cdb: - @echo "CREATING REST SRV POD" - $(KUBECTL) apply -f $(CDB) - -checkcdb: - $(KUBECTL) logs -f `$(KUBECTL) get pods -n $(NAMESPACE)|grep $(REST_SERVER)|cut -d ' ' -f 1` -n $(NAMESPACE) - -pdb: - $(KUBECTL) apply -f $(PDB) - -close: - $(KUBECTL) apply -f $(PDB_CLOSE) - -open: - $(KUBECTL) apply -f $(PDB_OPEN) - -map: - $(KUBECTL) apply -f $(PDB_MAP) - -checkpdb: - $(KUBECTL) get pdbs -n $(NAMESPACE) - -delete: - $(KUBECTL) apply -f pdb_delete.yaml - -dump: - @$(eval TMPSP := $(shell date "+%y%m%d%H%M%S" )) - @$(eval DIAGFILE := ./opdmp.$(TMPSP)) - @>$(DIAGFILE) - @echo "OPERATOR DUMP" >> $(DIAGFILE) - @echo "~~~~~~~~~~~~~" >> $(DIAGFILE) - $(KUBECTL) logs pod/`$(KUBECTL) get pods -n $(NAMESPACE)|grep oracle-database-operator-controller|head -1|cut -d ' ' -f 1` -n $(NAMESPACE) >>$(DIAGFILE) - $(KUBECTL) logs pod/`$(KUBECTL) get pods -n $(NAMESPACE)|grep oracle-database-operator-controller|head -2|tail -1 | cut -d ' ' -f 1` -n $(NAMESPACE) >>$(DIAGFILE) - $(KUBECTL) logs pod/`$(KUBECTL) get pods -n $(NAMESPACE)|grep oracle-database-operator-controller|tail -1|cut -d ' ' -f 1` -n $(NAMESPACE) >>$(DIAGFILE) - @echo "CDB LOG DUMP" >> $(DIAGFILE) - @echo "~~~~~~~~" >> $(DIAGFILE) - $(KUBECTL) logs `$(KUBECTL) get pods -n $(NAMESPACE)|grep $(REST_SERVER)| cut -d ' ' -f 1` -n $(NAMESPACE) >>$(DIAGFILE) - @echo "SECRET DMP" >>$(DIAGFILE) - @echo "~~~~~~~~" >> $(DIAGFILE) - $(KUBECTL) get secrets -o yaml -n $(NAMESPACE) >> $(DIAGFILE) - @echo "CDB/PDB DMP" >> $(DIAGFILE) - $(KUBECTL) get pdbs -o yaml -n $(NAMESPACE) >> $(DIAGFILE) - $(KUBECTL) get cdb -o yaml -n $(NAMESPACE) >> $(DIAGFILE) - @echo "CLUSTER INFO" >> $(DIAGFILE) - $(KUBECTL) get nodes -o wide - $(KUBECTL) get svc --namespace=kube-system - -reloadop: - echo "RESTARTING OPERATOR" - $(eval OP1 := $(shell $(KUBECTL) get pods -n $(NAMESPACE)|grep oracle-database-operator-controller|head -1|cut -d ' ' -f 1 )) - $(eval OP2 := $(shell $(KUBECTL) get pods -n $(NAMESPACE)|grep oracle-database-operator-controller|head -2|tail -1|cut -d ' ' -f 1 )) - $(eval OP3 := $(shell $(KUBECTL) get pods -n $(NAMESPACE)|grep oracle-database-operator-controller|tail -1|cut -d ' ' -f 1 )) - $(KUBECTL) get pod $(OP1) -n $(NAMESPACE) -o yaml | kubectl replace --force -f - - $(KUBECTL) get pod $(OP2) -n $(NAMESPACE) -o yaml | kubectl replace --force -f - - $(KUBECTL) get pod $(OP3) -n $(NAMESPACE) -o yaml | kubectl replace --force -f - - -login: - $(KUBECTL) exec -it `$(KUBECTL) get pods -n $(NAMESPACE)|grep $(REST_SERVER)|cut -d ' ' -f 1` -n $(NAMESPACE) bash - diff --git a/docs/multitenant/usecase03/Dockerfile b/docs/multitenant/usecase03/Dockerfile deleted file mode 100644 index 772a7e6d..00000000 --- a/docs/multitenant/usecase03/Dockerfile +++ /dev/null @@ -1,80 +0,0 @@ -## Copyright (c) 2022 Oracle and/or its affiliates. -## -## The Universal Permissive License (UPL), Version 1.0 -## -## Subject to the condition set forth below, permission is hereby granted to any -## person obtaining a copy of this software, associated documentation and/or data -## (collectively the "Software"), free of charge and under any and all copyright -## rights in the Software, and any and all patent rights owned or freely -## licensable by each licensor hereunder covering either (i) the unmodified -## Software as contributed to or provided by such licensor, or (ii) the Larger -## Works (as defined below), to deal in both -## -## (a) the Software, and -## (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if -## one is included with the Software (each a "Larger Work" to which the Software -## is contributed by such licensors), -## -## without restriction, including without limitation the rights to copy, create -## derivative works of, display, perform, and distribute the Software and make, -## use, sell, offer for sale, import, export, have made, and have sold the -## Software and the Larger Work(s), and to sublicense the foregoing rights on -## either these or other terms. -## -## This license is subject to the following condition: -## The above copyright notice and either this complete permission notice or at -## a minimum a reference to the UPL must be included in all copies or -## substantial portions of the Software. -## -## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -## SOFTWARE. - -FROM container-registry.oracle.com/java/jdk:latest - -# Environment variables required for this build (do NOT change) -# ------------------------------------------------------------- -ENV ORDS_HOME=/opt/oracle/ords/ \ - RUN_FILE="runOrdsSSL.sh" \ - ORDSVERSION=23.4.0-8 - -# Copy binaries -# ------------- -COPY $RUN_FILE $ORDS_HOME - -RUN yum -y install yum-utils bind-utils tree hostname openssl net-tools zip unzip tar wget vim-minimal which sudo expect procps curl lsof && \ - yum-config-manager --add-repo=http://yum.oracle.com/repo/OracleLinux/OL8/oracle/software/x86_64 && \ - yum -y install java-11-openjdk-devel && \ - yum -y install iproute && \ - yum clean all - -RUN curl -o /tmp/ords-$ORDSVERSION.el8.noarch.rpm https://yum.oracle.com/repo/OracleLinux/OL8/oracle/software/x86_64/getPackage/ords-$ORDSVERSION.el8.noarch.rpm - -RUN rpm -ivh /tmp/ords-$ORDSVERSION.el8.noarch.rpm - -# Setup filesystem and oracle user -# -------------------------------- -RUN mkdir -p $ORDS_HOME/doc_root && \ - mkdir -p $ORDS_HOME/error && \ - mkdir -p $ORDS_HOME/secrets && \ - chmod ug+x $ORDS_HOME/*.sh && \ - groupadd -g 54322 dba && \ - usermod -u 54321 -d /home/oracle -g dba -m -s /bin/bash oracle && \ - chown -R oracle:dba $ORDS_HOME && \ - echo "oracle ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers - -# Finalize setup -# ------------------- -USER oracle -WORKDIR /home/oracle - -VOLUME ["$ORDS_HOME/config/ords"] -EXPOSE 8888 - -# Define default command to start Ords Services -CMD $ORDS_HOME/$RUN_FILE - diff --git a/docs/multitenant/usecase03/NamespaceSegregation.png b/docs/multitenant/usecase03/NamespaceSegregation.png deleted file mode 100644 index bcb0ae77818e87ed64bf248bc0c173a4aac28c73..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 270813 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≪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 S*V+lde;zJA&et8oss9JAh>QII diff --git a/docs/multitenant/usecase03/README.md b/docs/multitenant/usecase03/README.md deleted file mode 100644 index c06368cd..00000000 --- a/docs/multitenant/usecase03/README.md +++ /dev/null @@ -1,268 +0,0 @@ - - - -# STEP BY STEP (NAMESPACE SEGREGATION) - -- [STEP BY STEP (NAMESPACE SEGREGATION)](#step-by-step-namespace-segregation) - - [INTRODUCTION](#introduction) - - [GIT CLONE ORACLE DATABASE OPERATOR PROJECT](#git-clone-oracle-database-operator-project) - - [NAMESPACE CREATION](#namespace-creation) - - [WEBHOOK CERTIFICATES](#webhook-certificates) - - [ORACLE DATABASE OPERATOR](#oracle-database-operator) - - [CREATE PDB AND CDB SECRETS](#create-pdb-and-cdb-secrets) - - [CREATE TLS CERTIFICATE](#create-tls-certificate) - - [REST SERVER IMAGE CREATION](#rest-server-image-creation) - - [CDB POD CREATION](#cdb-pod-creation) - - [PDB CREATION](#pdb-creation) - - [MAKEFILE](#makefile) - - -### INTRODUCTION - -> ☞ This folder contains the yaml files required to configure and manage cdb and pdb in different namespaces. The main change here is the possibility to specify the namespace where CDB will be created, this implies the introduction of new parameter at PDB level in order to specify the CDB namespace. - -Tasks performed in the usecase03 are the same ones of the other usecase01 with the exception that controller pods cdb pods and pdb crd are running in different namespaces. You must be aware of the fact that secrets must be created in the proper namespaces; cdb secrets go into cdb namespace , pdb secrets go into pdbnamespace while certificate secrets need to be created in every namespace. - - -| yaml file parameters | value | description /ords parameter | -|-------------- |--------------------------- |-------------------------------------------------| -| ☞ cdbNamespace | | Cdb namespace | -| dbserver | or | [--db-hostname][1] | -| dbTnsurl | | [--db-custom-url/db.customURL][dbtnsurl] | -| port | | [--db-port][2] | -| cdbName | | Container Name | -| name | | Ords podname prefix in cdb.yaml | -| name | | pdb resource in pdb.yaml | -| ordsImage | /ords-dboper:latest|My public container registry | -| pdbName | | Pluggable database name | -| servicename | | [--db-servicename][3] | -| sysadmin_user | | [--admin-user][adminuser] | -| sysadmin_pwd | | [--password-stdin][pwdstdin] | -| cdbadmin_user | | [db.cdb.adminUser][1] | -| cdbadmin_pwd | | [db.cdb.adminUser.password][cdbadminpwd] | -| webserver_user| | [https user][http] NOT A DB USER | -| webserver_pwd | | [http user password][http] | -| ords_pwd | | [ORDS_PUBLIC_USER password][public_user] | -| pdbTlsKey | | [standalone.https.cert.key][key] | -| pdbTlsCrt | | [standalone.https.cert][cr] | -| pdbTlsCat | | certificate authority | -| xmlFileName | | path for the unplug and plug operation | -| srcPdbName | | name of the database to be cloned | -| fileNameConversions | | used for database cloning | -| tdeKeystorePath | | [tdeKeystorePath][tdeKeystorePath] | -| tdeExport | | [tdeExport] | -| tdeSecret | | [tdeSecret][tdeSecret] | -| tdePassword | | [tdeSecret][tdeSecret] | -| assertivePdbDeletion | boolean | [turn on imperative approach on crd deleteion][imperative] | - -![generla schema](./NamespaceSegregation.png) - -### GIT CLONE ORACLE DATABASE OPERATOR PROJECT - -```bash -git clone https://github.com/oracle/oracle-database-operator.git -cd oracle-database-operator/docs/multitenant/usecase03 -``` -### NAMESPACE CREATION - -We need first to create two different namespaces (**cdbnamespace**,**pdbnamespace**) using ns_pdb_namespace.yaml and ns_cdb_namespace.yaml - -```bash -kubectl apply -f ns_pdb_namespace.yaml -kubectl apply -f ns_cdb_namespace.yaml -``` - -### WEBHOOK CERTIFICATES -Create cert manager and verify the status - -```bash -kubectl apply -f https://github.com/jetstack/cert-manager/releases/latest/download/cert-manager.yaml -``` - -```bash -kubectl get pods --namespace cert-manager -NAME READY STATUS RESTARTS AGE -cert-manager-75997f4b44-4nf5c 1/1 Running 1 9d -cert-manager-cainjector-769785cd7b-mzfq5 1/1 Running 1 9d -cert-manager-webhook-6bc9944d78-tslrp 1/1 Running 1 9d -``` - -### ORACLE DATABASE OPERATOR - -Create the oracle database operator using oracle-database-operator.yaml -```bash -cd oracle-database-operator -kubectl apply -f oracle-database-operator.yaml -cd - -``` - -[operator creation log](operator_creation_log.txt) -### CREATE PDB AND CDB SECRETS - -Update secrets files with your base64 encodede password. - -```bash -echo ImAdemoPassword | base64 -SW1BZGVtb1Bhc3N3b3JkCg== -``` -Apply the cdb_secret and pdb_secret yaml file to generate credential information in each namespace. - -``` -kubectl apply -f cdb_secret.yaml -kubectl apply -f pdb_secret.yaml -``` -> ☞ Note that https credential needs to be replicated in any secret file. It is possible to improve configuration by creating a dedicated namespace for https credential in order to specify this information only once. - -Namespace segregation enables the capability of deploying and manages pluggable database without the cdb administrative passwords. - -### CREATE TLS CERTIFICATE - -Here follow an example of script shell that can be used to create secret certificates in each namespace involved in the kubernets multi tenant architecture - -```bash -#!/bin/bash -export CDB_NAMESPACE=cdbnamespace -export PDB_NAMESPACE=pdbnamespace -export OPR_NAMESPACE=oracle-database-operator-system -export SKEY=tls.key -export SCRT=tls.crt -export CART=ca.crt -export COMPANY=oracle -export REST_SERVER=ords - -openssl genrsa -out ca.key 2048 -openssl req -new -x509 -days 365 -key ca.key -subj "/C=CN/ST=GD/L=SZ/O=${COMPANY}, Inc./CN=${COMPANY} Root CA" -out ca.crt -openssl req -newkey rsa:2048 -nodes -keyout ${SKEY} -subj "/C=CN/ST=GD/L=SZ/O=${COMPANY}, Inc./CN=cdb-dev-${REST_SERVER}.${CDB_NAMESPACE}" -out server.csr -echo "subjectAltName=DNS:cdb-dev-${REST_SERVER}.${CDB_NAMESPACE},DNS:www.example.com" > extfile.txt -openssl x509 -req -extfile extfile.txt -days 365 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out ${SCRT} - -kubectl create secret tls db-tls --key="${SKEY}" --cert="${SCRT}" -n ${CDB_NAMESPACE} -kubectl create secret generic db-ca --from-file="${CART}" -n ${CDB_NAMESPACE} -kubectl create secret tls db-tls --key="${SKEY}" --cert="${SCRT}" -n ${PDB_NAMESPACE} -kubectl create secret generic db-ca --from-file="${CART}" -n ${PDB_NAMESPACE} -kubectl create secret tls db-tls --key="${SKEY}" --cert="${SCRT}" -n ${OPR_NAMESPACE} -kubectl create secret generic db-ca --from-file="${CART}" -n ${OPR_NAMESPACE} -``` -after all secrets creation you shoud have the following pattern - -```bash -kubectl get secrets -n oracle-database-operator-system -NAME TYPE DATA AGE -db-ca Opaque 1 6d5h -db-tls kubernetes.io/tls 2 6d5h -webhook-server-cert kubernetes.io/tls 3 6d15h - - -kubectl get secrets -n cdbnamespace -NAME TYPE DATA AGE -cdb1-secret Opaque 6 6d15h -db-ca Opaque 1 6d6h -db-tls kubernetes.io/tls 2 6d6h - - -kubectl get secrets -n pdbnamespace -NAME TYPE DATA AGE -db-ca Opaque 1 6d6h -db-tls kubernetes.io/tls 2 6d6h -pdb1-secret Opaque 4 2d16h -tde1-secret Opaque 2 22h -``` -### REST SERVER IMAGE CREATION - -```bash -cd oracle-database-operator/ords -docker build -t oracle/ords-dboper:latest . -docker tag oracle/ords-dboper:latest [path_of_your_registry]/ords-dboper:latest -docker push [path_of_your_registry]/ords-dboper.latest -cd - -``` - -### CDB POD CREATION - -**note:** - Before creating the CDB pod make sure that all the pluggable databases in the container DB are open. - - - -Update the cdb_create.yaml with the path of the image generated before to create CDB pod - -```bash -kubectl apply -f cdb_create.yaml -``` - -Verify the status of the operation and cdb pod existence using the following commands - -```bash -## check the pod creation -kubectl get pods -n cdbnamespace - -## check the rest server log after pod creation -kubectl logs -f `/usr/bin/kubectl get pods -n cdbnamespace|grep ords|cut -d ' ' -f 1` -n cdbnamespace - -##login to the pod for further debug and information gathering -kubectl exec -it `kubectl get pods -n cdbnamespace |grep ords|cut -d ' ' -f 1` -n cdbnamespace bash -``` - -[log cdb creation](./cdb_creation_log.txt) - -### PDB CREATION - -Apply the the pdb_create.yaml file to create a new pdb , after pdb creation you should be able to get pdb details using **kubectl get** command - -```bash -kubectl apply -f pdb_create.yaml -``` - -```bash -#!/bin/bash -#checkpdbs.sh -kubectl get pdbs -n pdbnamespace -o=jsonpath='{range .items[*]} -{"\n==================================================================\n"} -{"CDB="}{.metadata.labels.cdb} -{"K8SNAME="}{.metadata.name} -{"PDBNAME="}{.spec.pdbName} -{"OPENMODE="}{.status.openMode} -{"ACTION="}{.status.action} -{"MSG="}{.status.msg} -{"\n"}{end}' -``` - -```bash -./checkpdbs.sh -================================================================== -CDB=cdb-dev -K8SNAME=pdb1 -PDBNAME=pdbdev -OPENMODE=READ WRITE -ACTION=CREATE -MSG=Success - -``` -[pdb creation log](./pdb_creation_log.txt) - -### MAKEFILE - -In order to facilitate the command execution use the [makefile](./makefile) available target details are exposed in the following tables. - -|target |Action | -|-----------------------------|-------------------------------------| -|step1 | Build rest server images | -|step2 | Tag the immages | -|step3 | Push the image into the repository | -|step4 | Load webhook certmanager | -|step5 | Create the db operator | -|step6 | Create tls certificates | -|step7 | Create tls secret | -|step8 | Create database secrets | -|step9 | Create restserver pod | -|checkstep9 | Monitor the executions | -|step10 | Create pluggable database | -|checkpdb | Monitor PDB status | -|dump | Dump pods info into a file | -|reloadop | Reload the db operator | -|login | Login into cdb pod | - -[imperative]:https://kubernetes.io/docs/concepts/overview/working-with-objects/object-management/ - - - diff --git a/docs/multitenant/usecase03/cdb_create.yaml b/docs/multitenant/usecase03/cdb_create.yaml deleted file mode 100644 index d3b5e04f..00000000 --- a/docs/multitenant/usecase03/cdb_create.yaml +++ /dev/null @@ -1,44 +0,0 @@ -apiVersion: database.oracle.com/v1alpha1 -kind: CDB -metadata: - name: cdb-dev - namespace: cdbnamespace -spec: - cdbName: "DB12" - ordsImage: ".............your registry............./ords-dboper:latest" - ordsImagePullPolicy: "Always" - dbTnsurl : "...Container tns alias....." - replicas: 1 - sysAdminPwd: - secret: - secretName: "cdb1-secret" - key: "sysadmin_pwd" - ordsPwd: - secret: - secretName: "cdb1-secret" - key: "ords_pwd" - cdbAdminUser: - secret: - secretName: "cdb1-secret" - key: "cdbadmin_user" - cdbAdminPwd: - secret: - secretName: "cdb1-secret" - key: "cdbadmin_pwd" - webServerUser: - secret: - secretName: "cdb1-secret" - key: "webserver_user" - webServerPwd: - secret: - secretName: "cdb1-secret" - key: "webserver_pwd" - cdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - cdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - diff --git a/docs/multitenant/usecase03/cdb_creation_log.txt b/docs/multitenant/usecase03/cdb_creation_log.txt deleted file mode 100644 index 8c7dc161..00000000 --- a/docs/multitenant/usecase03/cdb_creation_log.txt +++ /dev/null @@ -1,336 +0,0 @@ -kubectl get pods -n cdbnamespace -NAME READY STATUS RESTARTS AGE -cdb-dev-ords-rs-pgqqh 0/1 ContainerCreating 0 1s - -kubectl get pods -n cdbnamespace -NAME READY STATUS RESTARTS AGE -cdb-dev-ords-rs-pgqqh 1/1 Running 0 6s - -kubectl logs -f `/usr/bin/kubectl get pods -n cdbnamespace|grep ords|cut -d ' ' -f 1` -n cdbnamespace -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M -NOT_INSTALLED=2 - SETUP -==================================================== -CONFIG=/etc/ords/config -total 0 -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.3 Production on Thu Jan 25 17:17:20 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -The setting named: db.connectionType was set to: customurl in configuration: default -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.3 Production on Thu Jan 25 17:17:21 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -The setting named: db.customURL was set to: jdbc:oracle:thin:@(DESCRIPTION=(CONNECT_TIMEOUT=90)(RETRY_COUNT=30)(RETRY_DELAY=10)(TRANSPORT_CONNECT_TIMEOUT=70)(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL=TCP)(HOST=scan12.testrac.com)(PORT=1521)(IP=V4_ONLY))(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL=TCP)(HOST=scan34.testrac.com)(PORT=1521)(IP=V4_ONLY))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=TESTORDS))) in configuration: default -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.3 Production on Thu Jan 25 17:17:23 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -The setting named: security.requestValidationFunction was set to: false in configuration: default -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.3 Production on Thu Jan 25 17:17:25 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -The setting named: jdbc.MaxLimit was set to: 100 in configuration: default -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.3 Production on Thu Jan 25 17:17:27 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -The setting named: jdbc.InitialLimit was set to: 50 in configuration: default -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.3 Production on Thu Jan 25 17:17:29 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -The global setting named: error.externalPath was set to: /opt/oracle/ords/error -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.3 Production on Thu Jan 25 17:17:31 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -The global setting named: standalone.access.log was set to: /home/oracle -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.3 Production on Thu Jan 25 17:17:32 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -The global setting named: standalone.https.port was set to: 8888 -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.3 Production on Thu Jan 25 17:17:34 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -The global setting named: standalone.https.cert was set to: /opt/oracle/ords//secrets/tls.crt -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.3 Production on Thu Jan 25 17:17:36 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -The global setting named: standalone.https.cert.key was set to: /opt/oracle/ords//secrets/tls.key -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.3 Production on Thu Jan 25 17:17:38 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -The setting named: restEnabledSql.active was set to: true in configuration: default -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.3 Production on Thu Jan 25 17:17:40 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -The global setting named: security.verifySSL was set to: true -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.3 Production on Thu Jan 25 17:17:42 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -The global setting named: database.api.enabled was set to: true -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.3 Production on Thu Jan 25 17:17:43 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -The setting named: plsql.gateway.mode was set to: disabled in configuration: default -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.3 Production on Thu Jan 25 17:17:45 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -The global setting named: database.api.management.services.disabled was set to: false -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.3 Production on Thu Jan 25 17:17:47 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -The setting named: misc.pagination.maxRows was set to: 1000 in configuration: default -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.3 Production on Thu Jan 25 17:17:49 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -The setting named: db.cdb.adminUser was set to: C##DBAPI_CDB_ADMIN AS SYSDBA in configuration: default -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.3 Production on Thu Jan 25 17:17:51 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -The setting named: db.cdb.adminUser.password was set to: ****** in configuration: default -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.3 Production on Thu Jan 25 17:17:53 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -Created user welcome in file /etc/ords/config/global/credentials -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.3 Production on Thu Jan 25 17:17:55 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -Oracle REST Data Services - Non-Interactive Install - -Retrieving information.. -Completed verifying Oracle REST Data Services schema version 23.3.0.r2891830. -Connecting to database user: ORDS_PUBLIC_USER url: jdbc:oracle:thin:@(DESCRIPTION=(CONNECT_TIMEOUT=90)(RETRY_COUNT=30)(RETRY_DELAY=10)(TRANSPORT_CONNECT_TIMEOUT=70)(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL=TCP)(HOST=scan12.testrac.com)(PORT=1521)(IP=V4_ONLY))(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL=TCP)(HOST=scan34.testrac.com)(PORT=1521)(IP=V4_ONLY))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=TESTORDS))) -The setting named: db.serviceNameSuffix was set to: in configuration: default -The setting named: db.username was set to: ORDS_PUBLIC_USER in configuration: default -The setting named: db.password was set to: ****** in configuration: default -The setting named: security.requestValidationFunction was set to: ords_util.authorize_plsql_gateway in configuration: default -2024-01-25T17:17:58.898Z INFO Oracle REST Data Services schema version 23.3.0.r2891830 is installed. -2024-01-25T17:17:58.900Z INFO To run in standalone mode, use the ords serve command: -2024-01-25T17:17:58.900Z INFO ords --config /etc/ords/config serve -2024-01-25T17:17:58.900Z INFO Visit the ORDS Documentation to access tutorials, developer guides and more to help you get started with the new ORDS Command Line Interface (http://oracle.com/rest). -Picked up _JAVA_OPTIONS: -Xms1126M -Xmx1126M - -ORDS: Release 23.3 Production on Thu Jan 25 17:18:00 2024 - -Copyright (c) 2010, 2024, Oracle. - -Configuration: - /etc/ords/config/ - -2024-01-25T17:18:00.960Z INFO HTTP and HTTP/2 cleartext listening on host: 0.0.0.0 port: 8080 -2024-01-25T17:18:00.963Z INFO HTTPS and HTTPS/2 listening on host: 0.0.0.0 port: 8888 -2024-01-25T17:18:00.980Z INFO Disabling document root because the specified folder does not exist: /etc/ords/config/global/doc_root -2024-01-25T17:18:00.981Z INFO Default forwarding from / to contextRoot configured. -2024-01-25T17:18:06.634Z INFO Configuration properties for: |default|lo| -db.serviceNameSuffix= -java.specification.version=21 -conf.use.wallet=true -database.api.management.services.disabled=false -sun.jnu.encoding=UTF-8 -user.region=US -java.class.path=/opt/oracle/ords/ords.war -java.vm.vendor=Oracle Corporation -standalone.https.cert.key=/opt/oracle/ords//secrets/tls.key -sun.arch.data.model=64 -nashorn.args=--no-deprecation-warning -java.vendor.url=https://java.oracle.com/ -resource.templates.enabled=false -user.timezone=UTC -java.vm.specification.version=21 -os.name=Linux -sun.java.launcher=SUN_STANDARD -user.country=US -sun.boot.library.path=/usr/java/jdk-21/lib -sun.java.command=/opt/oracle/ords/ords.war --config /etc/ords/config serve --port 8888 --secure -jdk.debug=release -sun.cpu.endian=little -user.home=/home/oracle -oracle.dbtools.launcher.executable.jar.path=/opt/oracle/ords/ords.war -user.language=en -db.cdb.adminUser.password=****** -java.specification.vendor=Oracle Corporation -java.version.date=2023-10-17 -database.api.enabled=true -java.home=/usr/java/jdk-21 -db.username=ORDS_PUBLIC_USER -file.separator=/ -java.vm.compressedOopsMode=32-bit -line.separator= - -restEnabledSql.active=true -java.specification.name=Java Platform API Specification -java.vm.specification.vendor=Oracle Corporation -java.awt.headless=true -standalone.https.cert=/opt/oracle/ords//secrets/tls.crt -db.password=****** -sun.management.compiler=HotSpot 64-Bit Tiered Compilers -security.requestValidationFunction=ords_util.authorize_plsql_gateway -misc.pagination.maxRows=1000 -java.runtime.version=21.0.1+12-LTS-29 -user.name=oracle -error.externalPath=/opt/oracle/ords/error -stdout.encoding=UTF-8 -path.separator=: -db.cdb.adminUser=C##DBAPI_CDB_ADMIN AS SYSDBA -os.version=5.4.17-2136.323.8.1.el7uek.x86_64 -java.runtime.name=Java(TM) SE Runtime Environment -file.encoding=UTF-8 -plsql.gateway.mode=disabled -security.verifySSL=true -standalone.https.port=8888 -java.vm.name=Java HotSpot(TM) 64-Bit Server VM -java.vendor.url.bug=https://bugreport.java.com/bugreport/ -java.io.tmpdir=/tmp -oracle.dbtools.cmdline.ShellCommand=ords -java.version=21.0.1 -user.dir=/home/oracle/keystore -os.arch=amd64 -java.vm.specification.name=Java Virtual Machine Specification -jdbc.MaxLimit=100 -oracle.dbtools.cmdline.home=/opt/oracle/ords -native.encoding=UTF-8 -java.library.path=/usr/java/packages/lib:/usr/lib64:/lib64:/lib:/usr/lib -java.vendor=Oracle Corporation -java.vm.info=mixed mode, sharing -stderr.encoding=UTF-8 -java.vm.version=21.0.1+12-LTS-29 -sun.io.unicode.encoding=UnicodeLittle -jdbc.InitialLimit=50 -db.connectionType=customurl -java.class.version=65.0 -db.customURL=jdbc:oracle:thin:@(DESCRIPTION=(CONNECT_TIMEOUT=90)(RETRY_COUNT=30)(RETRY_DELAY=10)(TRANSPORT_CONNECT_TIMEOUT=70)(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL=TCP)(HOST=scan12.testrac.com)(PORT=1521)(IP=V4_ONLY))(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL=TCP)(HOST=scan34.testrac.com)(PORT=1521)(IP=V4_ONLY))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=TESTORDS))) -standalone.access.log=/home/oracle - -2024-01-25T17:18:10.381Z INFO - -Mapped local pools from /etc/ords/config/databases: - /ords/ => default => VALID - - -2024-01-25T17:18:10.532Z INFO Oracle REST Data Services initialized -Oracle REST Data Services version : 23.3.0.r2891830 -Oracle REST Data Services server info: jetty/10.0.17 -Oracle REST Data Services java info: Java HotSpot(TM) 64-Bit Server VM 21.0.1+12-LTS-29 - - -exec -it `kubectl get pods -n cdbnamespace |grep ords|cut -d ' ' -f 1` -n cdbnamespace bash -[oracle@cdb-dev-ords-rs-pgqqh ~]$ ps -ef|grep java -oracle 1147 1116 10 17:17 ? 00:00:21 /usr/java/jdk-21/bin/java -Doracle.dbtools.cmdline.home=/opt/oracle/ords -Duser.language=en -Duser.region=US -Dfile.encoding=UTF-8 -Djava.awt.headless=true -Dnashorn.args=--no-deprecation-warning -Doracle.dbtools.cmdline.ShellCommand=ords -Duser.timezone=UTC -jar /opt/oracle/ords/ords.war --config /etc/ords/config serve --port 8888 --secure -oracle 1227 1200 0 17:21 pts/0 00:00:00 grep --color=auto java diff --git a/docs/multitenant/usecase03/cdb_secret.yaml b/docs/multitenant/usecase03/cdb_secret.yaml deleted file mode 100644 index 8f1b6fc9..00000000 --- a/docs/multitenant/usecase03/cdb_secret.yaml +++ /dev/null @@ -1,17 +0,0 @@ -# -# Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. -# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. -# -apiVersion: v1 -kind: Secret -metadata: - name: cdb1-secret - namespace: cdbnamespace -type: Opaque -data: - ords_pwd: "[...base64 encoded password...]" - sysadmin_pwd: "[...base64 encoded password...]" - cdbadmin_user: "[...base64 encoded password...]" - cdbadmin_pwd: "[...base64 encoded password...]" - webserver_user: "[...base64 encoded password...]" - webserver_pwd: "[...base64 encoded password...]" diff --git a/docs/multitenant/usecase03/gentlscert.sh b/docs/multitenant/usecase03/gentlscert.sh deleted file mode 100644 index 49e29147..00000000 --- a/docs/multitenant/usecase03/gentlscert.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash -export CDB_NAMESPACE=cdbnamespace -export PDB_NAMESPACE=pdbnamespace -export OPR_NAMESPACE=oracle-database-operator-system -export SKEY=tls.key -export SCRT=tls.crt -export CART=ca.crt -export COMPANY=oracle -export REST_SERVER=ords - -openssl genrsa -out ca.key 2048 -openssl req -new -x509 -days 365 -key ca.key -subj "/C=CN/ST=GD/L=SZ/O=${COMPANY}, Inc./CN=${COMPANY} Root CA" -out ca.crt -openssl req -newkey rsa:2048 -nodes -keyout ${SKEY} -subj "/C=CN/ST=GD/L=SZ/O=${COMPANY}, Inc./CN=cdb-dev-${REST_SERVER}.${CDB_NAMESPACE}" -out server.csr -echo "subjectAltName=DNS:cdb-dev-${REST_SERVER}.${CDB_NAMESPACE},DNS:www.example.com" > extfile.txt -openssl x509 -req -extfile extfile.txt -days 365 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out ${SCRT} - -kubectl create secret tls db-tls --key="${SKEY}" --cert="${SCRT}" -n ${CDB_NAMESPACE} -kubectl create secret generic db-ca --from-file="${CART}" -n ${CDB_NAMESPACE} -kubectl create secret tls db-tls --key="${SKEY}" --cert="${SCRT}" -n ${PDB_NAMESPACE} -kubectl create secret generic db-ca --from-file="${CART}" -n ${PDB_NAMESPACE} -kubectl create secret tls db-tls --key="${SKEY}" --cert="${SCRT}" -n ${OPR_NAMESPACE} -kubectl create secret generic db-ca --from-file="${CART}" -n ${OPR_NAMESPACE} - diff --git a/docs/multitenant/usecase03/makefile b/docs/multitenant/usecase03/makefile deleted file mode 100644 index 7270a5e0..00000000 --- a/docs/multitenant/usecase03/makefile +++ /dev/null @@ -1,285 +0,0 @@ -# __ __ _ __ _ _ -# | \/ | __ _| | _____ / _(_) | ___ -# | |\/| |/ _` | |/ / _ \ |_| | |/ _ \ -# | | | | (_| | < __/ _| | | __/ -# |_| |_|\__,_|_|\_\___|_| |_|_|\___| -# -# ___ -# / _ \ _ __ _ __ _ __ ___ _ __ ___ -# | | | | '_ \| '_ \| '__/ _ \ '_ ` _ \ -# | |_| | | | | |_) | | | __/ | | | | | -# \___/|_| |_| .__/|_| \___|_| |_| |_| -# |_| -# ____ _ _ _ -# / ___|___ _ __ | |_ _ __ ___ | | | ___ _ __ -# | | / _ \| '_ \| __| '__/ _ \| | |/ _ \ '__| -# | |__| (_) | | | | |_| | | (_) | | | __/ | -# \____\___/|_| |_|\__|_| \___/|_|_|\___|_| -# -# -# This makefile helps to speed up the kubectl commands executions to deploy and test -# the mutlitenant operator. Although it has few functionality you can adapt to your needs -# by adding much more targets. -# -# Quick start: -# ~~~~~~~~~~~ -# -# - Copy files of tab.1 in the makefile directory. -# - Edit the secret files and other yaml files with the correct credential as -# specified in the documentation. -# - Edit makefile updating variables of tab.2 -# - Execute commands of tab.3 "make step1" "make step2" "make step3".... -# -# Tab.1 - List of required files -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# -# +-----------------------------+---------------------------------------------+ -# |oracle-database-operator.yaml| Opertaor yaml file | -# +-----------------------------+---------------------------------------------+ -# |cdb_secret.yaml | Secret file for the rest server pod | -# +-----------------------------+---------------------------------------------+ -# |pdb_secret.yaml | Secret file for the pdb creation | -# +-----------------------------+---------------------------------------------+ -# |cdb_create.yaml | Rest server pod creation | -# +-----------------------------+---------------------------------------------+ -# |pdb_create.yaml | Pluggable database creation | -# +-----------------------------+---------------------------------------------+ -# |oracle-database-operator.yaml| Database operator | -# +-----------------------------+---------------------------------------------+ -# |Dockerfiles | Dockerfile for CBD | -# +-----------------------------+---------------------------------------------+ -# |runOrdsSSL.sh | Init script executed by Dockerfile | -# +-----------------------------+---------------------------------------------+ -# -# Tab.2 - List of variables -# ~~~~~~~~~~~~~~~~~~~~~~~~~ -# -# +-----------------------------+---------------------------------------------+ -# |OCIR | Your image registry | -# +-----------------------------+---------------------------------------------+ -# |OCIRPATH | Path of the image in your registry | -# +-----------------------------+---------------------------------------------+ -# -# Tab.3 - Execution steps -# ~~~~~~~~~~~~~~~~~~~~~~~ -# -# +-----------------------------+---------------------------------------------+ -# | MAKEFILE TARGETS LIST | -# | ----- ooo ----- | -# | - TARGET - - DESCRIPTION - | -# +-----------------------------+-------------------------------------+-------+ -# |step1 | Build rest server images | | -# +-----------------------------+-------------------------------------+ REST | -# |step2 | Tag the immages | SRV | -# +-----------------------------+-------------------------------------+ IMG | -# |step3 | Push the image into the repository | | -# +-----------------------------+-------------------------------------+-------+ -# |step4 | Load webhook certmanager | DB | -# +-----------------------------+-------------------------------------+ OPER | -# |step5 | Create the db operator | | -# +-----------------------------+-------------------------------------+-------+ -# |step6 | Create tls certificates | T | -# +-----------------------------+-------------------------------------+ L | -# |step7 | Create tls secret | S | -# +-----------------------------+---------------------------------------------+ -# |step8 | Create database secrets | -# +-----------------------------+---------------------------------------------+ -# |step9 | Create restserver pod | -# | | +---------------------------------------------+ -# | +---> checkstep9 | Monitor the executions | -# +-----------------------------+---------------------------------------------+ -# |step10 | Create pluggable database | -# | | +---------------------------------------------+ -# | +---> checkpdb | Monitor PDB status | -# +-----------------------------+---------------------------------------------+ -# | DIAGNOSTIC TARGETS | -# +-----------------------------+---------------------------------------------+ -# | dump | Dump pods info into a file | -# +-----------------------------+---------------------------------------------+ -# | reloadop | Reload the db operator | -# +-----------------------------+---------------------------------------------+ -# | login | Login into cdb pod | -# +-----------------------------+---------------------------------------------+ - - -################ TAB 2 VARIABLES ############ -REST_SERVER=ords -ORDSVERSION=latest - -OCIR=[container registry] -OCIRPATH=$(REST_SERVER)-dboper:$(ORDSVERSION) - -#examples: -#OCIR=lin.ocir.io -#OCIRPATH=/sampletenancy/samplepath/sampledir/$(REST_SERVER)-dboper:$(ORDSVERSION) -############################################# -DOCKER=/usr/bin/docker -KUBECTL=/usr/bin/kubectl -ORDS=/usr/local/bin/ords -CONFIG=/etc/ords/config -IMAGE=oracle/$(REST_SERVER)-dboper:$(ORDSVERSION) -DBOPERATOR=oracle-database-operator.yaml -URLPATH=/_/db-api/stable/database/pdbs/ -OPENSSL=/usr/bin/openssl -ORDSPORT=8888 -MAKE=/usr/bin/make -DOCKERFILE=../../../ords/Dockerfile -RUNSCRIPT=../../../ords/runOrdsSSL.sh -RM=/usr/bin/rm -CP=/bin/cp -ECHO=/usr/bin/echo -CERTMANAGER=https://github.com/jetstack/cert-manager/releases/latest/download/cert-manager.yaml -CDB_SECRET_YAML=cdb_secret.yaml -PDB_SECRET_YAML=pdb_secret.yaml -TDE_SECRET_YAML=tde_secret.yaml -CDB_NAMESPACE_YAML=ns_namespace_cdb.yaml -PDB_NAMESPACE_YAML=ns_namespace_pdb.yaml -OPR_NAMESPACE=oracle-database-operator-system -PDB_NAMESPACE=$(shell grep namespace $(PDB_NAMESPACE_YAML) |cut -d: -f 2| tr -d ' ') -CDB_NAMESPACE=$(shell grep namespace $(CDB_NAMESPACE_YAML) |cut -d: -f 2| tr -d ' ') -CDB=cdb_create.yaml -PDB=pdb_create.yaml -SKEY=tls.key -SCRT=tls.crt -CART=ca.crt -COMPANY=oracle -LOCALHOST=localhost -RESTPREFIX=cdb-dev - - -step1: createimage -step2: tagimage -step3: push -step4: certmanager -step5: dboperator -step6: tlscert -step7: tlssecret -step8: dbsecret -step9: cdb -step10: pdb - -checkstep9: checkcdb - - -createimage: - @echo "BUILDING CDB IMAGES" - $(CP) $(DOCKERFILE) . - $(CP) $(RUNSCRIPT) . - $(DOCKER) build -t $(IMAGE) . - -tagimage: - @echo "TAG IMAGE" - $(DOCKER) tag $(IMAGE) $(OCIR)$(OCIRPATH) - -push: - @echo "PUSH IMAGE INTO THE REGISTRY" - $(DOCKER) push $(OCIR)$(OCIRPATH) - -certmanager: - @echo "WEBHOOK CERT MANAGER" - $(KUBECTL) apply -f $(CERTMANAGER) - -dboperator: - @echo "ORACLE DATABASE OPERATOR" - $(KUBECTL) apply -f $(DBOPERATOR) - -namespace: - $(KUBECTL) get namespaces - $(KUBECTL) apply -f $(CDB_NAMESPACE_YAML) - $(KUBECTL) apply -f $(PDB_NAMESPACE_YAML) - $(KUBECTL) get namespaces - - -tlscert: - @echo "CREATING TLS CERTIFICATES" - $(OPENSSL) genrsa -out ca.key 2048 - $(OPENSSL) req -new -x509 -days 365 -key ca.key -subj "/C=US/ST=California/L=SanFrancisco/O=$(COMPANY) /CN=$(RESTPREFIX)-$(REST_SERVER).$(CDB_NAMESPACE) /CN=$(LOCALHOST) Root CA " -out ca.crt - $(OPENSSL) req -newkey rsa:2048 -nodes -keyout $(SKEY) -subj "/C=US/ST=California/L=SanFrancisco/O=$(COMPANY) /CN=$(RESTPREFIX)-$(REST_SERVER).$(CDB_NAMESPACE) /CN=$(LOCALHOST)" -out server.csr - $(ECHO) "subjectAltName=DNS:$(RESTPREFIX)-$(REST_SERVER).$(CDB_NAMESPACE),DNS:www.example.com" > extfile.txt - $(OPENSSL) x509 -req -extfile extfile.txt -days 365 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out $(SCRT) - - -tlssecret: - $(KUBECTL) create secret tls db-tls --key="$(SKEY)" --cert="$(SCRT)" -n $(CDB_NAMESPACE) - $(KUBECTL) create secret generic db-ca --from-file="$(CART)" -n $(CDB_NAMESPACE) - $(KUBECTL) create secret tls db-tls --key="$(SKEY)" --cert="$(SCRT)" -n $(PDB_NAMESPACE) - $(KUBECTL) create secret generic db-ca --from-file="$(CART)" -n $(PDB_NAMESPACE) - $(KUBECTL) create secret tls db-tls --key="$(SKEY)" --cert="$(SCRT)" -n $(OPR_NAMESPACE) - $(KUBECTL) create secret generic db-ca --from-file="$(CART)" -n $(OPR_NAMESPACE) - - -dbsecret: - @echo "CREATING DB SECRETS" - $(KUBECTL) apply -f $(CDB_SECRET_YAML) - $(KUBECTL) apply -f $(PDB_SECRET_YAML) - $(KUBECTL) apply -f $(TDE_SECRET_YAML) - - -cdb: - @echo "CREATING REST SRV POD" - $(KUBECTL) apply -f $(CDB) - -checkcdb: - $(KUBECTL) logs -f `$(KUBECTL) get pods -n $(CDB_NAMESPACE)|grep $(REST_SERVER)|cut -d ' ' -f 1` -n $(CDB_NAMESPACE) - -pdb: - $(KUBECTL) apply -f $(PDB) - -checkpdb: - $(KUBECTL) get pdbs -n $(OPR_NAMESPACE) - -dump: - @$(eval TMPSP := $(shell date "+%y%m%d%H%M%S" )) - @$(eval DIAGFILE := ./opdmp.$(TMPSP)) - @>$(DIAGFILE) - @echo "OPERATOR DUMP" >> $(DIAGFILE) - @echo "~~~~~~~~~~~~~" >> $(DIAGFILE) - $(KUBECTL) logs pod/`$(KUBECTL) get pods -n $(OPR_NAMESPACE)|grep oracle-database-operator-controller|head -1|cut -d ' ' -f 1` -n $(OPR_NAMESPACE) >>$(DIAGFILE) - $(KUBECTL) logs pod/`$(KUBECTL) get pods -n $(OPR_NAMESPACE)|grep oracle-database-operator-controller|head -2|tail -1 | cut -d ' ' -f 1` -n $(OPR_NAMESPACE) >>$(DIAGFILE) - $(KUBECTL) logs pod/`$(KUBECTL) get pods -n $(OPR_NAMESPACE)|grep oracle-database-operator-controller|tail -1|cut -d ' ' -f 1` -n $(OPR_NAMESPACE) >>$(DIAGFILE) - @echo "CDB LOG DUMP" >> $(DIAGFILE) - @echo "~~~~~~~~" >> $(DIAGFILE) - $(KUBECTL) logs `$(KUBECTL) get pods -n $(OPR_NAMESPACE)|grep $(REST_SERVER)| cut -d ' ' -f 1` -n $(OPR_NAMESPACE) >>$(DIAGFILE) - @echo "SECRET DMP" >>$(DIAGFILE) - @echo "~~~~~~~~" >> $(DIAGFILE) - $(KUBECTL) get secrets -o yaml -n $(OPR_NAMESPACE) >> $(DIAGFILE) - @echo "CDB/PDB DMP" >> $(DIAGFILE) - $(KUBECTL) get pdbs -o yaml -n $(OPR_NAMESPACE) >> $(DIAGFILE) - $(KUBECTL) get cdb -o yaml -n $(OPR_NAMESPACE) >> $(DIAGFILE) - @echo "CLUSTER INFO" >> $(DIAGFILE) - $(KUBECTL) get nodes -o wide - $(KUBECTL) get svc --namespace=kube-system - -reloadop: - echo "RESTARTING OPERATOR" - $(eval OP1 := $(shell $(KUBECTL) get pods -n $(OPR_NAMESPACE)|grep oracle-database-operator-controller|head -1|cut -d ' ' -f 1 )) - $(eval OP2 := $(shell $(KUBECTL) get pods -n $(OPR_NAMESPACE)|grep oracle-database-operator-controller|head -2|tail -1|cut -d ' ' -f 1 )) - $(eval OP3 := $(shell $(KUBECTL) get pods -n $(OPR_NAMESPACE)|grep oracle-database-operator-controller|tail -1|cut -d ' ' -f 1 )) - $(KUBECTL) get pod $(OP1) -n $(OPR_NAMESPACE) -o yaml | kubectl replace --force -f - - $(KUBECTL) get pod $(OP2) -n $(OPR_NAMESPACE) -o yaml | kubectl replace --force -f - - $(KUBECTL) get pod $(OP3) -n $(OPR_NAMESPACE) -o yaml | kubectl replace --force -f - - -login: - $(KUBECTL) exec -it `$(KUBECTL) get pods -n $(CDB_NAMESPACE) |grep $(REST_SERVER)|cut -d ' ' -f 1` -n $(CDB_NAMESPACE) bash - -cdblog: - $(KUBECTL) logs -f `$(KUBECTL) get pods -n $(CDB_NAMESPACE)|grep $(REST_SERVER)|cut -d ' ' -f 1` -n $(CDB_NAMESPACE) - - - -xlog1: - $(KUBECTL) logs -f pod/`$(KUBECTL) get pods -n $(OPR_NAMESPACE)|grep oracle-database-operator-controller|head -1|cut -d ' ' -f 1` -n $(OPR_NAMESPACE) - -xlog2: - $(KUBECTL) logs -f pod/`$(KUBECTL) get pods -n $(OPR_NAMESPACE)|grep oracle-database-operator-controller|head -2|tail -1|cut -d ' ' -f 1` -n $(OPR_NAMESPACE) - -xlog3: - $(KUBECTL) logs -f pod/`$(KUBECTL) get pods -n $(OPR_NAMESPACE)|grep oracle-database-operator-controller|tail -1|cut -d ' ' -f 1` -n $(OPR_NAMESPACE) - -checkdep: - $(KUBECTL) api-resources --verbs=list --namespaced -o name | xargs -n 1 $(KUBECTL) get -n $(OPR_NAMESPACE) - $(KUBECTL) api-resources --verbs=list --namespaced -o name | xargs -n 1 $(KUBECTL) get -n $(CBD_NAMESPACE) - $(KUBECTL) api-resources --verbs=list --namespaced -o name | xargs -n 1 $(KUBECTL) get -n $(PDB_NAMESPACE) - - - diff --git a/docs/multitenant/usecase03/ns_namespace_cdb.yaml b/docs/multitenant/usecase03/ns_namespace_cdb.yaml deleted file mode 100644 index f4c6d77b..00000000 --- a/docs/multitenant/usecase03/ns_namespace_cdb.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - labels: - control-plane: controller-manager - name: cdbnamespace - diff --git a/docs/multitenant/usecase03/ns_namespace_pdb.yaml b/docs/multitenant/usecase03/ns_namespace_pdb.yaml deleted file mode 100644 index b22245f9..00000000 --- a/docs/multitenant/usecase03/ns_namespace_pdb.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - labels: - control-plane: controller-manager - name: pdbnamespace - diff --git a/docs/multitenant/usecase03/operator_creation_log.txt b/docs/multitenant/usecase03/operator_creation_log.txt deleted file mode 100644 index 36ed02ac..00000000 --- a/docs/multitenant/usecase03/operator_creation_log.txt +++ /dev/null @@ -1,27 +0,0 @@ -kubectl apply -f oracle-database-operator.yaml -namespace/oracle-database-operator-system created -customresourcedefinition.apiextensions.k8s.io/autonomouscontainerdatabases.database.oracle.com configured -customresourcedefinition.apiextensions.k8s.io/autonomousdatabasebackups.database.oracle.com configured -customresourcedefinition.apiextensions.k8s.io/autonomousdatabaserestores.database.oracle.com configured -customresourcedefinition.apiextensions.k8s.io/autonomousdatabases.database.oracle.com configured -customresourcedefinition.apiextensions.k8s.io/cdbs.database.oracle.com configured -customresourcedefinition.apiextensions.k8s.io/dataguardbrokers.database.oracle.com configured -customresourcedefinition.apiextensions.k8s.io/dbcssystems.database.oracle.com configured -customresourcedefinition.apiextensions.k8s.io/oraclerestdataservices.database.oracle.com configured -customresourcedefinition.apiextensions.k8s.io/pdbs.database.oracle.com configured -customresourcedefinition.apiextensions.k8s.io/shardingdatabases.database.oracle.com configured -customresourcedefinition.apiextensions.k8s.io/singleinstancedatabases.database.oracle.com configured -role.rbac.authorization.k8s.io/oracle-database-operator-leader-election-role created -clusterrole.rbac.authorization.k8s.io/oracle-database-operator-manager-role created -clusterrole.rbac.authorization.k8s.io/oracle-database-operator-metrics-reader created -clusterrole.rbac.authorization.k8s.io/oracle-database-operator-oracle-database-operator-proxy-role created -rolebinding.rbac.authorization.k8s.io/oracle-database-operator-oracle-database-operator-leader-election-rolebinding created -clusterrolebinding.rbac.authorization.k8s.io/oracle-database-operator-oracle-database-operator-manager-rolebinding created -clusterrolebinding.rbac.authorization.k8s.io/oracle-database-operator-oracle-database-operator-proxy-rolebinding created -service/oracle-database-operator-controller-manager-metrics-service created -service/oracle-database-operator-webhook-service created -certificate.cert-manager.io/oracle-database-operator-serving-cert created -issuer.cert-manager.io/oracle-database-operator-selfsigned-issuer created -mutatingwebhookconfiguration.admissionregistration.k8s.io/oracle-database-operator-mutating-webhook-configuration created -validatingwebhookconfiguration.admissionregistration.k8s.io/oracle-database-operator-validating-webhook-configuration created -deployment.apps/oracle-database-operator-controller-manager created diff --git a/docs/multitenant/usecase03/pdb_create.yaml b/docs/multitenant/usecase03/pdb_create.yaml deleted file mode 100644 index 200f3712..00000000 --- a/docs/multitenant/usecase03/pdb_create.yaml +++ /dev/null @@ -1,46 +0,0 @@ -apiVersion: database.oracle.com/v1alpha1 -kind: PDB -metadata: - name: pdb1 - namespace: pdbnamespace - labels: - cdb: cdb-dev -spec: - cdbResName: "cdb-dev" - cdbNamespace: "cdbnamespace" - cdbName: "DB12" - pdbName: "pdbdev" - adminName: - secret: - secretName: "pdb1-secret" - key: "sysadmin_user" - adminPwd: - secret: - secretName: "pdb1-secret" - key: "sysadmin_pwd" - pdbTlsKey: - secret: - secretName: "db-tls" - key: "tls.key" - pdbTlsCrt: - secret: - secretName: "db-tls" - key: "tls.crt" - pdbTlsCat: - secret: - secretName: "db-ca" - key: "ca.crt" - webServerUser: - secret: - secretName: "pdb1-secret" - key: "webserver_user" - webServerPwd: - secret: - secretName: "pdb1-secret" - key: "webserver_pwd" - fileNameConversions: "NONE" - tdeImport: false - totalSize: "1G" - tempSize: "100M" - action: "Create" - diff --git a/docs/multitenant/usecase03/pdb_creation_log.txt b/docs/multitenant/usecase03/pdb_creation_log.txt deleted file mode 100644 index 71d0eb4f..00000000 --- a/docs/multitenant/usecase03/pdb_creation_log.txt +++ /dev/null @@ -1,6 +0,0 @@ -kubectl apply -f pdb_create.yaml -pdb.database.oracle.com/pdb1 created - -kubectl get pdbs -n pdbnamespace -NAME CONNECT_STRING CDB NAME PDB NAME PDB STATE PDB SIZE STATUS MESSAGE -pdb1 (DESCRIPTION=(CONNECT_TIMEOUT=90)(RETRY_COUNT=30)(RETRY_DELAY=10)(TRANSPORT_CONNECT_TIMEOUT=70)(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL=TCP)(HOST=scan12.testrac.com)(PORT=1521)(IP=V4_ONLY))(LOAD_BALLANCE=ON)(ADDRESS=(PROTOCOL=TCP)(HOST=scan34.testrac.com)(PORT=1521)(IP=V4_ONLY))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=pdbdev))) DB12 pdbdev READ WRITE 0.78G Ready Success diff --git a/docs/multitenant/usecase03/pdb_secret.yaml b/docs/multitenant/usecase03/pdb_secret.yaml deleted file mode 100644 index f1dfdac6..00000000 --- a/docs/multitenant/usecase03/pdb_secret.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# -# Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. -# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. -# -apiVersion: v1 -kind: Secret -metadata: - name: pdb1-secret - namespace: pdbnamespace -type: Opaque -data: - sysadmin_user: "[...base64 encoded password...]" - sysadmin_pwd: "[...base64 encoded password...]" - webserver_user: "[...base64 encoded password...]" - webserver_pwd: "[...base64 encoded password...]" - diff --git a/docs/multitenant/usecase03/runOrdsSSL.sh b/docs/multitenant/usecase03/runOrdsSSL.sh deleted file mode 100644 index 35f1b77b..00000000 --- a/docs/multitenant/usecase03/runOrdsSSL.sh +++ /dev/null @@ -1,190 +0,0 @@ -#!/bin/bash - -cat <$TNSNAME - - -function SetParameter() { - ##ords config info <--- Use this command to get the list - -[[ ! -z "${ORACLE_HOST}" && -z "${DBTNSURL}" ]] && { - $ORDS --config ${CONFIG} config set db.hostname ${ORACLE_HOST:-racnode1} - $ORDS --config ${CONFIG} config set db.port ${ORACLE_PORT:-1521} - $ORDS --config ${CONFIG} config set db.servicename ${ORACLE_SERVICE:-TESTORDS} -} - -[[ -z "${ORACLE_HOST}" && ! -z "${DBTNSURL}" ]] && { - #$ORDS --config ${CONFIG} config set db.tnsAliasName ${TNSALIAS} - #$ORDS --config ${CONFIG} config set db.tnsDirectory ${TNS_ADMIN} - #$ORDS --config ${CONFIG} config set db.connectionType tns - - $ORDS --config ${CONFIG} config set db.connectionType customurl - $ORDS --config ${CONFIG} config set db.customURL jdbc:oracle:thin:@${DBTNSURL} -} - - $ORDS --config ${CONFIG} config set security.requestValidationFunction false - $ORDS --config ${CONFIG} config set jdbc.MaxLimit 100 - $ORDS --config ${CONFIG} config set jdbc.InitialLimit 50 - $ORDS --config ${CONFIG} config set error.externalPath ${ERRORFOLDER} - $ORDS --config ${CONFIG} config set standalone.access.log /home/oracle - $ORDS --config ${CONFIG} config set standalone.https.port 8888 - $ORDS --config ${CONFIG} config set standalone.https.cert ${CERTIFICATE} - $ORDS --config ${CONFIG} config set standalone.https.cert.key ${KEY} - $ORDS --config ${CONFIG} config set restEnabledSql.active true - $ORDS --config ${CONFIG} config set security.verifySSL true - $ORDS --config ${CONFIG} config set database.api.enabled true - $ORDS --config ${CONFIG} config set plsql.gateway.mode disabled - $ORDS --config ${CONFIG} config set database.api.management.services.disabled false - $ORDS --config ${CONFIG} config set misc.pagination.maxRows 1000 - $ORDS --config ${CONFIG} config set db.cdb.adminUser "${CDBADMIN_USER:-C##DBAPI_CDB_ADMIN} AS SYSDBA" - $ORDS --config ${CONFIG} config secret --password-stdin db.cdb.adminUser.password << EOF -${CDBADMIN_PWD:-PROVIDE_A_PASSWORD} -EOF - -$ORDS --config ${CONFIG} config user add --password-stdin ${WEBSERVER_USER:-ordspdbadmin} "SQL Administrator, System Administrator" <${CKF} 2>&1 -echo "checkfile" >> ${CKF} -NOT_INSTALLED=`cat ${CKF} | grep "INFO: The" |wc -l ` -echo NOT_INSTALLED=$NOT_INSTALLED - - -function StartUp () { - $ORDS --config $CONFIG serve --port 8888 --secure -} - -# Check whether ords is already setup -if [ $NOT_INSTALLED -ne 0 ] -then - echo " SETUP " - setupOrds; - StartUp; -fi - -if [ $NOT_INSTALLED -eq 0 ] -then - echo " STARTUP " - StartUp; -fi - - diff --git a/docs/observability/README.md b/docs/observability/README.md index 5a281c9c..a087c892 100644 --- a/docs/observability/README.md +++ b/docs/observability/README.md @@ -2,155 +2,304 @@ Oracle Database Operator for Kubernetes (`OraOperator`) includes the Observability controller for Oracle Databases and adds the `DatabaseObserver` CRD, which enables users to observe -Oracle Databases by scraping database metrics using SQL queries and observe logs in the Database _alert.log_. The controller -automates the deployment and maintenance of the metrics exporter container image, -metrics exporter service and Prometheus servicemonitor. +Oracle Databases by scraping database metrics using SQL queries and observe logs in the Database _alert.log_. +The controller automates the deployment and maintenance of the [database observability exporter](https://github.com/oracle/oracle-db-appdev-monitoring), +metrics exporter service and Prometheus ServiceMonitor. The following sections explains the configuration and functionality of the controller. * [Prerequisites](#prerequisites) * [The DatabaseObserver Custom Resource Definition](#the-databaseobserver-custom-resource) - * [Configuration Options](#configuration-options) - * [Resources Managed by the Controller](#resources-managed-by-the-controller) + * [Configuring Database Credentials](#configuration-fields-related-to-managing-database-credentials) + * [Configuring Cloud Provider Vaults for Database credentials](#configuration-fields-related-to-vault-usage) + * [Configuring the Managed Resources](#configuration-fields-related-to-the-deployment-pod-service-and-servicemonitor-resources) + * [Configuring Export of Database Metrics](#configuration-fields-related-to-metrics-export) + * [Configuring Export of Database Logs](#configuration-fields-related-to-logs-export) + * [Configuring the Exporter Config File](#configuration-fields-related-to-the-exporter-config-file) + * [DatabaseObserver Operations](#databaseobserver-operations) * [Create](#create-resource) * [List](#list-resource) * [Get Status](#get-detailed-status) * [Update](#patch-resource) * [Delete](#delete-resource) -* [Configuration Options for Scraping Metrics](#scraping-metrics) + +* [Connecting to the Database](#connecting-to-the-database) + * [Default DB Configuration](#default-database-configuration) + * [Multiple DB Configuration](#multiple-database-configuration) + +* [Vault Configuration](#database-authentication-with-vaults-in-the-cloud) + * [Using OCI Vault](#oci-vault-configuration) + * [Using Azure Vault](#azure-vault-configuration) + +* [Setting the Exporter Config File](#defining-an-exporter-config-file) + +* [Scraping Metrics](#scraping-metrics) * [Custom Metrics Config](#custom-metrics-config) * [Prometheus Release](#prometheus-release) -* [Configuration Options for Scraping Logs](#scraping-logs) + +* [Scraping Logs](#scraping-logs) * [Custom Log Location with PersistentVolumes](#custom-log-location-with-persistentvolumes) * [Example Working with Sidecars and Promtail](#working-with-sidecars-to-deploy-promtail) - * [Promtail Config Example](#Promtail-Config-Example) -* [Other Configuration Options](#other-configuration-options) - * [Labels](#labels) - * [Custom Exporter Image or Version](#custom-exporter-image-or-version) + + +* [Customizing Resources](#customizing-resources-and-available-configuration-options) + * [Environment Variables and Default Values](#environment-variables-and-default-values) + * [Managing Labels](#managing-labels) + * [Custom Exporter Image or Version](#custom-environment-variables-arguments-and-commands) + * [Security Contexts](#security-contexts) + * [Custom Service Ports](#custom-service-ports) + * [Custom ServiceMonitor](#custom-servicemonitor-endpoints) + * [Mandatory Roles and Privileges](#mandatory-roles-and-privileges-requirements-for-observability-controller) + * [Debugging and troubleshooting](#debugging-and-troubleshooting) * [Known Issues](#known-issues) +* [Resources](#resources) ## Prerequisites The `DatabaseObserver` custom resource has the following prerequisites: -1. Prometheus and its `servicemonitor` custom resource definition must be installed on the cluster. +1. Installation of Prometheus `servicemonitor` custom resource definition (CRD) on the cluster. -- The Observability controller creates multiple Kubernetes resources that include - a Prometheus `servicemonitor`. For the controller - to create ServiceMonitors, the ServiceMonitor custom resource must exist. + - The Observability controller creates multiple Kubernetes resources that include + a Prometheus `servicemonitor`. For the controller + to create ServiceMonitors, the ServiceMonitor custom resource must exist. For __example__, to install + Prometheus CRDs using the [Kube Prometheus Stack helm chart](https://prometheus-community.github.io/helm-charts/), run the following helm commands: + ```bash + helm repo add prometheus https://prometheus-community.github.io/helm-charts + helm repo update + helm upgrade --install prometheus prometheus/kube-prometheus-stack -n prometheus --create-namespace + ``` + - You can check if the ServiceMonitor API exists in your cluster by running the following command: + ```bash + kubectl api-resources | grep smon + ``` -2. A preexisting Oracle Database and the proper database grants and privileges. +2. A preexisting Oracle Database, and the proper database grants and privileges. -- The controller exports metrics through SQL queries that the user can control - and specify through a _toml_ file. The necessary access privileges to the tables used in the queries - are not provided and applied automatically. + - The controller exports metrics through SQL queries that the user can control + and specify through the _toml_ files. The necessary access privileges to the tables used in the queries + are not provided and applied automatically. ## The DatabaseObserver Custom Resource -The Oracle Database Operator (__v1.2.0__ or later) includes the Oracle Database Observability controller, which automates -the deployment and setting up of the Oracle Database exporter and the related resources to make Oracle Databases observable. - -In the example YAML file found in -[`./config/samples/observability/v4/databaseobserver.yaml`](../../config/samples/observability/v4/databaseobserver.yaml), -the databaseObserver custom resource provides the following configurable properties: - -| Attribute | Type | Default | Required? | Example | -|--------------------------------------------------------|--------|---------------------------------------------------------------------|:------------|-----------------------------------------------------------------------| -| `spec.database.dbUser.key` | string | user | Optional | _username_ | -| `spec.database.dbUser.secret` | string | - | Yes | _db-secret_ | -| `spec.database.dbPassword.key` | string | password | Optional | _admin-password_ | -| `spec.database.dbPassword.secret` | string | - | Conditional | _db-secret_ | -| `spec.database.dbPassword.vaultOCID` | string | - | Conditional | _ocid1.vault.oc1..._ | -| `spec.database.dbPassword.vaultSecretName` | string | - | Conditional | _db-vault_ | -| `spec.database.dbWallet.secret` | string | - | Conditional | _devsec-oradevdb-wallet_ | -| `spec.database.dbConnectionString.key` | string | connection | Optional | _connection_ | -| `spec.database.dbConnectionString.secret` | string | - | Yes | _db-secretg_ | -| `spec.sidecars` | array | - | Optional | - | -| `spec.sidecarVolumes` | array | - | Optional | - | -| `spec.exporter.deployment.securityContext` | object | | Optional | _ | -| `spec.exporter.deployment.env` | map | - | Optional | _DB_ROLE: "SYSDBA"_ | -| `spec.exporter.deployment.image` | string | container-registry.oracle.com/database/observability-exporter:1.5.1 | Optional | _container-registry.oracle.com/database/observability-exporter:1.3.0_ | -| `spec.exporter.deployment.args` | array | - | Optional | _[ "--log.level=info" ]_ | -| `spec.exporter.deployment.commands` | array | - | Optional | _[ "/oracledb_exporter" ]_ | -| `spec.exporter.deployment.labels` | map | - | Optional | _environment: dev_ | -| `spec.exporter.deployment.podTemplate.labels` | map | - | Optional | _environment: dev_ | -| `spec.exporter.deployment.podTemplate.securityContext` | object | - | Optional | _ | -| `spec.exporter.service.ports` | array | - | Optional | - | -| `spec.exporter.service.labels` | map | - | Optional | _environment: dev_ | | -| `spec.configuration.configMap.key` | string | config.toml | Optional | _config.toml_ | -| `spec.configuration.configMap.name` | string | - | Optional | _devcm-oradevdb-config_ | -| `spec.prometheus.serviceMonitor.labels` | map | - | Yes | _release: prometheus_ | -| `spec.prometheus.serviceMonitor.namespaceSelector` | - | - | Yes | - | -| `spec.prometheus.serviceMonitor.endpoints` | array | - | Optional | - | -| `spec.log.filename` | string | alert.log | Optional | _alert.log_ | -| `spec.log.path` | string | /log | Optional | _/log_ | -| `spec.log.volume.name` | string | log-volume | Optional | _my-persistent-volume_ | -| `spec.log.volume.persistentVolumeClaim.claimName` | string | - | Optional | _my-pvc_ | -| `spec.replicas` | number | 1 | Optional | _1_ | -| `spec.inheritLabels` | array | - | Optional | _- environment: dev_
- app.kubernetes.io/name: observer | -| `spec.ociConfig.configMapName` | string | - | Conditional | _oci-cred_ | -| `spec.ociConfig.secretName` | string | - | Conditional | _oci-privatekey_ | - - -### Configuration Options -The `databaseObserver` Custom resource has the following fields for all configurations that are required: -* `spec.database.dbUser.secret` - Secret containing the database username. The corresponding key can be any value but must match the key in the secret provided. -* `spec.database.dbPassword.secret` - Secret containing the database password (if `vault` is NOT used). The corresponding key field can be any value, but must match the key in the Secret provided -* `spec.database.dbConnectionString.secret` - Secret containing the database connection string. The corresponding key field can be any value but must match the key in the Secret provided -* `spec.prometheus.serviceMonitor.labels` - Custom labels to add to the service monitors labels. A label is required for your serviceMonitor to be discovered. This label must match what is set in the serviceMonitorSelector of your Prometheus configuration - -If a database wallet is required to connect, then the following field containing the wallet secret is required: -* `spec.database.dbWallet.secret` - Secret containing the database wallet. The filenames inside the wallet must be used as keys - -If vault is used to store the database password instead, then the following fields are required: -* `spec.database.dbPassword.vaultOCID` - OCID of the vault used -* `spec.database.dbPassword.vaultSecretName` - Name of the secret inside the desired vault -* `spec.ociConfig.configMapName` - Holds the rest of the information of the OCI API signing key. The following keys must be used: `fingerprint`, `region`, `tenancy` and `user` -* `spec.ociConfig.secretName` - Holds the private key of the OCI API signing key. The key to the file containing the user private key must be: `privatekey` - -The `databaseObserver` Resource provides the remaining multiple fields that are optional: -* `spec.prometheus.serviceMonitor.endpoints` - ServiceMonitor endpoints -* `spec.prometheus.serviceMonitor.namespaceSelector` - ServiceMonitor namespace selector -* `spec.sidecars` - List of containers to run as a sidecar container with the observability exporter container image -* `spec.sidecarVolumes` - Volumes of any sidecar containers -* `spec.log.path` - Custom path to create -* `spec.log.filename` - Custom filename for the log file -* `spec.log.volume.name` - Custom name for the log volume -* `spec.log.volume.persistentVolumeClaim.claimName` - A volume in which to place the log to be shared by the containers. If not specified, an EmptyDir is used by default. -* `spec.configuration.configMap.key` - Configuration filename inside the container and the configmap -* `spec.configuration.configMap.name` - Name of the `configMap` that holds the custom metrics configuration -* `spec.replicas` - Number of replicas to deploy -* `spec.exporter.service.ports` - Port number for the generated service to use -* `spec.exporter.service.labels` - Custom labels to add to service labels -* `spec.exporter.deployment.image` - Image version of observability exporter to use -* `spec.exporter.deployment.env` - Custom environment variables for the observability exporter -* `spec.exporter.deployment.labels` - Custom labels to add to deployment labels -* `spec.exporter.deployment.podTemplate.labels` - Custom labels to add to pod labels -* `spec.exporter.deployment.podTemplate.securityContext` - Configures pod securityContext -* `spec.exporter.deployment.args` - Additional arguments to provide the observability-exporter -* `spec.exporter.deployment.commands` - Commands to supply to the observability-exporter -* `spec.exporter.deployment.securityContext` - Configures container securityContext -* `spec.inheritLabels` - Keys of inherited labels from the databaseObserver resource. These labels are applied to generated resources. - -### Resources Managed by the Controller +Oracle Database Operator (__v1.0.0__ or later) includes the Oracle Database Observability controller, which automates +the deployment and configuration of the Oracle Database exporter and the related resources to make Oracle Databases observable. +The Observability Controller introduces the `databaseobserver` APIs. + +To list the available APIs included in the +Database Operator, you can run the following command: + +```bash +kubectl api-resources | grep oracle +``` + +Learn about the different and configurable fields available in this release of the DatabaseObserver APIs in the following sections. + +> In this release, the controller deploys the Database Observability Exporter ([v2.0.2](https://github.com/oracle/oracle-db-appdev-monitoring/releases/tag/2.0.2)). + + + +### Configuration Fields Related to Managing Database Credentials +The following fields are available for configuring the exporter to successfully connect to databases: + +| Attribute | Type | Required? | Example | +|---------------------------------------------------|--------|:------------|----------------------| +| `spec.database.dbUser.key` | string | No | _username_ | +| `spec.database.dbPassword.key` | string | No | _password_ | +| `spec.database.dbConnectionString.key` | string | No | _connection_ | +| `spec.database.dbUser.secret` | string | Yes | _db-secret_ | +| `spec.database.dbPassword.secret` | string | Yes | _db-secret_ | +| `spec.database.dbConnectionString.secret` | string | Yes | _db-secret_ | +| `spec.database.dbUser.envName` | string | No | _DB_USERNAME_ | +| `spec.database.dbPassword.envName` | string | No | _DB_PASSWORD_ | +| `spec.database.dbConnectionString.envName` | string | No | _DB_CONN_STRING_ | +| `spec.databases..dbUser.key` | string | No | _username_ | +| `spec.databases..dbPassword.key` | string | No | _password_ | +| `spec.databases..dbConnectionString.key` | string | No | _connection_ | +| `spec.databases..dbUser.secret` | string | Yes | _db02-secret_ | +| `spec.databases..dbPassword.secret` | string | Yes | _db02-secret_ | +| `spec.databases..dbConnectionString.secret` | string | Yes | _db02-secret_ | +| `spec.databases..dbUser.envName` | string | No | _DB2_USERNAME_ | +| `spec.databases..dbPassword.envName` | string | No | _DB2_PASSWORD_ | +| `spec.databases..dbConnectionString.envName` | string | No | _DB2_CONN_STRING_ | +| `spec.wallet.secret` | string | Conditional | _combined-wallet_ | +| `spec.wallet.mountPath` | string | No | _"/wallet/combined"_ | +| `spec.wallet.additional[].name` | string | Conditional | _db02_ | +| `spec.wallet.additional[].secret` | string | Conditional | _db02-wallet_ | +| `spec.wallet.additional[].mountPath` | string | Conditional | _"/wallet/db02"_ | + +These fields enable you to define connection details for a single database, or for multiple databases. For default values, environment variables and default behavior, see [defaults](#environment-variables-and-default-values). + +1. `spec.database` - Use to configure the database username, password and connection string. +The environment variables set by the controller can be customized through the `envName` field. +Both `envName` and `key` fields are optional. + + +2. `spec.databases` - Use to configure multiple database credentials. The keys are used as a prefix for environment +variables. For example, a key of `MYDB` will produce the environment variables `MYDB_USERNAME` and `MYDB_PASSWORD`. + + +3. `spec.wallet` - Use to configure the wallet with which you want to connect to the Oracle Database, if applicable. The field `mountPath` +enables you to control where the wallet is to be mounted. Meanwhile, additional wallets can be mounted in the array `additional`, used for multi +database configuration. + +To learn more about configuring the database connection, see [Connecting to the Database](#connecting-to-the-database). + +### Configuration Fields Related to Vault Usage +The following fields are available for configuring the exporter to use the vault when connecting to databases. + +| Attribute | Type | Required? | Example | +|-------------------------------------------|--------|:------------|-----------------------------------------| +| `spec.database.oci.vaultID` | string | Conditional | _ocid1.vault.oc1.._ | +| `spec.database.oci.vaultPasswordSecret` | string | Conditional | _sample_secret_ | +| `spec.database.azure.vaultID` | string | Conditional | - | +| `spec.database.azure.vaultUsernameSecret` | string | Conditional | _sample_usn_secret | +| `spec.database.azure.vaultPasswordSecret` | string | Conditional | _sample_pwd_secret_ | +| `spec.ociConfig.configMap.key` | string | No | _config_ | +| `spec.ociConfig.configMap.name` | string | Conditional | _oci-config-file_ | +| `spec.ociConfig.privateKey.key` | string | No | _private.pem_ | +| `spec.ociConfig.privateKey.secret` | string | Conditional | _oci-privatekey_ | +| `spec.ociConfig.mountPath` | string | No | _"/.oci"_ | +| `spec.azureConfig.configMap.name` | string | Conditional | _azure-configmap_ | + + +These fields enable you to define vault details for OCI or Azure. For default values, environment variables and default behavior, see [defaults](#environment-variables-and-default-values). +1. About `spec.database.oci` - for configuring the OCI Vault details for the default database. + +> Note: For multiple database configuration with Vault integration, use the exporter config file. See instructions on the [Exporter Config File](#defining-an-exporter-config-file). + +2. `spec.database.azure.` - Use to configure the Azure Vault details for the default database. + + +3. `spec.ociConfig` - Use to configure the authentication of requests made to the Oracle Cloud performed by the exporter. The configMap should contain +the actual _config_ file use by the OCI CLI (usually found in ~/.oci/config). See the Oracle Cloud Infrastructure documentation on [configuring the OCI CLI (external link)](https://docs.oracle.com/en-us/iaas/Content/API/SDKDocs/cliconfigure.htm#Configuring_the_CLI). +> Note: The OCI profile used is [DEFAULT]. Under the profile DEFAULT, set `key_file=/.oci/.pem`. + +4. About `spec.azureConfig` - for configuring the authentication of requests made to the Azure Cloud done by the exporter. +The configMap should contain the keys `tenantId`, `clientId` and `clientSecret`, which are used to create the related environment variables. + +To learn more about configuring an integration with a vault for database authentication, see [Authenticating with Vaults](#database-authentication-with-vaults-in-the-cloud). + +### Configuration Fields Related to the Deployment, Pod, Service and ServiceMonitor Resources When you create a `DatabaseObserver` resource, the controller creates and manages the following resources: +1. Deployment +2. Service +3. Prometheus ServiceMonitor +The following fields below are available for configuring the controller-managed resources for a custom deployment, pod, service and servicemonitor. + +| Attribute | Type | Required? | Example | +|-----------------------------------------|--------|:----------|-----------------------------------------------------------------------| +| `spec.deployment.securityContext` | object | No | - | +| `spec.deployment.podSecurityContext` | object | No | - | +| `spec.deployment.env` | map | No | _DB_ROLE: "SYSDBA"_ | +| `spec.deployment.image` | string | No | _container-registry.oracle.com/database/observability-exporter:1.3.0_ | +| `spec.deployment.args` | array | No | _[ "--log.level=info" ]_ | +| `spec.deployment.commands` | array | No | _[ "/oracledb_exporter" ]_ | +| `spec.deployment.labels` | map | No | _environment: dev_ | +| `spec.deployment.podTemplate.labels` | map | No | _environment: dev_ | +| `spec.service.ports` | array | No | - | +| `spec.service.labels` | map | No | _environment: dev_ | +| `spec.serviceMonitor.labels` | map | Yes | _release: prometheus_ | +| `spec.serviceMonitor.endpoints` | array | No | - | +| `spec.serviceMonitor.namespaceSelector` | - | No | - | +| `spec.replicas` | number | No | _1_ | +| `spec.inheritLabels` | array | No | _- environment: dev_
- app.kubernetes.io/name: observer | + +These fields enable you to define deployment, service and serviceMonitor details. For default values, environment variables and default behavior, see [defaults](#environment-variables-and-default-values). + +1. `spec.deployment` - Use to configure deployment details such as custom environment variables through `env` and arguments through `args`. +The container image can also be replaced with a later or older version of the exporter using the `image` field. For security related configurations, +the `securityContext` and `podSecurityContext` are available. + +2. `spec.service` - Use to configure a customized service resource. + + +3. `spec.serviceMonitor` - Use to configure the serviceMonitor resource. + +> Note: It is an essential requirement that you set the Prometheus label inside `serviceMonitor.labels`. Ensure that the label set is included in the serviceMonitorSelector field of your Prometheus CR. + + +5. `spec.replicas` - Use to configure the number of replicas to deploy + + +6. `spec.inheritLabels` - Use to configure all resources created and managed so that they inherit the labels from databaseobserver CR. + + +To learn more about configuring managed resources, such as the Deployment, Service and other services, see [Customizing Resources and Available Configuration Options](#customizing-resources-and-available-configuration-options) + +### Configuration Fields Related to Metrics Export +The following fields are available for configuring the metrics exported from the database. + +| Attribute | Type | Required? | Example | +|---------------------------------|--------|:------------|-------------------------| +| `spec.metrics.configMap[].key` | string | No | _config.toml_ || | | | | +| `spec.metrics.configMap[].name` | string | Conditional | _custom-metrics-config_ | + +These fields enable you to define configMap sources for metrics. For default values, environment variables and default behavior, see [defaults](#environment-variables-and-default-values). +1. `metrics.configMap[]` - Use to configure an array of configs that will contain the TOML files. This field creates a volume with multiple source files mounted in the same directory. + +To learn more about configuring the metrics export, see [Defining an Exporter Config File](#defining-an-exporter-config-file). + +### Configuration Fields Related to Logs Export +The following fields are available for configuring how the `alert.log` is exported from the database. + +| Attribute | Type | Required? | Example | +|--------------------------------------------------|--------|:------------|--------------| +| `spc.log.destination` | string | No | _alert.log_ | +| `spc.log.filename` | string | No | _/log_ | +| `spc.log.disable` | bool | No | true | +| `spc.log.volume.name` | string | No | _log-volume_ | | +| `spc.log.volume.persistentVolumeClaim.claimName` | string | Conditional | _my-pvc_ | +| `spc.sidecar.containers[]` | array | Conditional | - | +| `spc.sidecar.volumes[]` | array | Conditional | - | + +These fields enable you to define log details and sidecar resources. For default values, environment variables and default behavior, see [defaults](#environment-variables-and-default-values). +1. `spec.sidecar.containers` - Use to configure an array of containers as a sidecar to the observability +exporter container, such as promtail. The field `sidecar.containers` enables you to list containers as you would normally for deployments. + +2. `spec.sidecar.volumes` - Use to configure extra volumes related to your sidecar containers. + + +3. `spec.log.disable` - Use to disable the log volume creation + + +4. `spec.log.filename` - Use to specify a custom filename for the log file + + +5. `spec.log.destination` - Use to configure a custom destination for the log volume. + + +6. `spec.log.volume` - Use to configure the log volume into which the exporter extracts the logs, and from which the logs are read. +If a persistentVolumeClaim is not provided, then an emptyDir is created instead. Meanwhile, the field `log.volume.name` can be used to define the name of the volume +to reference. -1. __Deployment__ - The deployment will have the same name as the `databaseObserver` resource - - Deploys a container named `observability-exporter` - - The default container image version of the `container-registry.oracle.com/database/observability-exporter` supported is __[v1.5.1](https://github.com/oracle/oracle-db-appdev-monitoring/releases/tag/1.5.1)__ +To learn more about configuring the log export, see [Scraping Logs](#scraping-logs). -2. __Service__ - The service will have the same name as the databaseObserver - - The service is of type `ClusterIP` +### Configuration Fields Related to the Exporter Config File +The following fields are available for configuring the exporter through a config-file: -3. __Prometheus ServiceMonitor__ - The serviceMonitor will have the same name as the `databaseObserver` +| Attribute | Type | Required? | Example | +|-------------------------------------|--------|:------------|-------------------| +| `spec.exporterConfig.configMapName` | string | Conditional | _exporter-config_ | +| `spec.exporterConfig.mountPath` | string | No | _/config_ | + +These fields enable you to define the exporter config-file. For default values, environment variables and default behavior, see [defaults](#environment-variables-and-default-values). + +1. `spec.exporterConfig` - Use to configure the exporter with a config file containing database, log and metrics details. You can use +the `mountPath` field to define a custom location for the config file. + +> Note: The CONFIG_FILE environment variable or the --config.file args must be set to the desired location of the config file. + +To learn more about configuring the config-file for the exporter, see [Defining an Exporter Config File](#defining-an-exporter-config-file). ## DatabaseObserver Operations ### Create Resource -Follow the steps below to create a new `databaseObserver` resource object. +Use the following steps to create a new `databaseObserver` resource object. -1. To begin, creating a `databaseObserver` requires you to create and provide Kubernetes Secrets to provide connection details: +1. When you create a `databaseObserver`, you are required to create and to provide Kubernetes Secrets containing your database connection details. Replace the values and +create a single secret by running the following command: ```bash kubectl create secret generic db-secret \ --from-literal=username='username' \ @@ -158,17 +307,18 @@ kubectl create secret generic db-secret \ --from-literal=connection='dbsample_tp' ``` -2. (Conditional) Create a Kubernetes Secret for the wallet (if a wallet is required to connect to the database). +2. (Conditional) Create a Kubernetes Secret for the wallet (if a wallet is required to connect to the database). -You can create this Secret by using a command similar to the example that follows. -If you are connecting to an Autunomous Database, and the operator is used to manage the Oracle Autonomous Database, then a client wallet can also be downloaded as a Secret through `kubectl` commands. See the ADB README section on [Download Wallets](../../docs/adb/README.md#download-wallets). +If you are connecting to an Autonomous Database, and the operator is used to manage the Oracle Autonomous Database, +then a client wallet can also be downloaded as a Secret through `kubectl` commands. +See the ADB README section on [Download Wallets](../../docs/adb/README.md#download-wallets). You can also choose to create the wallet secret from a local directory containing the wallet files: ```bash -kubectl create secret generic db-wallet --from-file=wallet_dir +kubectl create secret generic db-wallet --from-file= ``` -3. Finally, update the `databaseObserver` manifest with the resources you have created. You can use the example _minimal_ manifest +3. Update the `databaseObserver` manifest with the resources that you have created. You can use the example _minimal_ manifest inside [config/samples/observability/v4](../../config/samples/observability/v4/databaseobserver_minimal.yaml) to specify and create your databaseObserver object with a YAML file. @@ -192,15 +342,15 @@ spec: key: "connection" secret: db-secret - dbWallet: - secret: db-wallet + wallet: + secret: db-wallet - prometheus: - serviceMonitor: - labels: - release: prometheus + serviceMonitor: + labels: + release: prometheus ``` +To create the resource: ```bash kubectl apply -f databaseobserver.yaml ``` @@ -208,19 +358,19 @@ spec: ### List Resource To list the Observability custom resources, use the following command as an example: ```bash -kubectl get databaseobserver -A +kubectl get dbobserver -A ``` ### Get Detailed Status -To obtain a quick status, use the following command as an example: +To obtain a quick status, use the following command as an example: > Note: The databaseobserver custom resource is named `obs-sample` in the next following sections. > We will use this name as an example. ```sh $ kubectl get databaseobserver obs-sample -NAME EXPORTERCONFIG STATUS VERSION -obs-sample DEFAULT READY 1.5.1 +NAME METRICSCONFIG STATUS VERSION +obs-sample DEFAULT READY 2.0.2 ``` @@ -231,13 +381,16 @@ kubectl describe databaseobserver obs-sample ``` This command displays details of the current state of your `databaseObserver` resource object. A successful -deployment of the `databaseObserver` resource object should display `READY` as the status, and all conditions should display with a `True` value for every ConditionType. +deployment of the `databaseObserver` resource object should display `READY` as the status, and all conditions should +display with a `True` value for every ConditionType. ### Patch Resource -The Observability controller currently supports updates for most of the fields in the manifest. The following is an example of patching the `databaseObserver` resource: +The Observability controller currently supports updates for most of the fields in the manifest. The following is an example +of patching the `databaseObserver` resource: + ```bash -kubectl --type=merge -p '{"spec":{"exporter":{"image":"container-registry.oracle.com/database/observability-exporter:1.5.0"}}}' patch databaseobserver obs-sample +kubectl --type=merge -p '{"spec":{"exporter":{"image":"container-registry.oracle.com/database/observability-exporter:2.0.1"}}}' patch databaseobserver obs-sample ``` ### Delete Resource @@ -248,16 +401,368 @@ To delete the `databaseObserver` custom resource and all related resources, use kubectl delete databaseobserver obs-sample ``` +## Connecting to the Database + + +### Default Database Configuration +To configure the observability exporter to export from a single Oracle Database, use the field `spec.database` +to define the details of the database. If the wallet is applicable, `spec.wallet` allows you to define a secret containing the wallet +and where the wallet is to be mounted as a volume. + +```yaml +spec: + database: + dbUser: + key: "username" + secret: db-secret + + dbPassword: + key: "password" + secret: db-secret + + dbConnectionString: + key: "connection" + secret: db-secret + + wallet: + secret: db-wallet + mountPath: /oracle/wallet + #... +``` +Alternatively, the default database can be defined and configured through the exporter config-file. In this case, the config-file must be defined, and the +credentials are set only through the exporter YAML file as kubernetes secrets. As an example, using the same secret with the same keys, you can define the relevant secrets +in the following YAML file: + +```yaml + +spec: + database: + dbUser: + secret: db-secret + dbPassword: + secret: db-secret + + wallet: + secret: db-wallet + mountPath: /oracle/wallet + + deployment: + env: + CONFIG_FILE: "/oracle/exporter/config.yaml" + + exporterConfig: + configMap: + name: config-file +``` + +In the config-file, we reference the environment variables DB_USERNAME and DB_PASSWORD which are set by default by the observability controller +when `spec.database.dbUser` and `spec.database.dbPassword` are defined. The TNS_ADMIN is also set through the config-file, and the +connection string provided through the config file. + +```yaml +# config.yaml + +# Environment variables of the form ${VAR_NAME} will be expanded. +databases: + default: + username: ${DB_USERNAME} + password: ${DB_PASSWORD} + url: oracledb_tp + + ## Path to Oracle Database wallet, if using wallet + tnsAdmin: /oracle/wallet +``` +To create the configmap: +```bash +kubectl create cm config-file --from-file=config.yaml +``` + +### Multiple Database Configuration +To configure the observability exporter to export metrics and logs from multiple Oracle Databases, __instead__ of `spec.database`, you must use an exporter config file, +configure the _databaseobserver_ YAML file with a combined wallet (if applicable), and then use the +`spec.databases` field . The field `spec.databases` is a map with keys used for naming environment variables +and identifying groups of credentials. + +For example, to define two databases, in the _databaseobserver_ YAML file, you can have the following configuration: + +```yaml +spec: + databases: + db01: + dbUser: + secret: adb01-secret + dbPassword: + secret: adb01-secret + db02: + dbUser: + secret: adb02-secret + envName: "DB2_USN" + dbPassword: + secret: adb02-secret + envName: "DB2_PWD" + + wallet: + secret: combined + mountPath: "/example_dbwallet/combined" + additional: + - name: db01 + secret: db01-wallet + mountPath: "/example_dbwallet/db01" + - name: db02 + secret: db02-wallet + mountPath: "/example_dbwallet/db02" + + deployment: + env: + TNS_ADMIN: /example_dbwallet/combined + CONFIG_FILE: "/config/config.yaml" + + exporterConfig: + configMap: + name: config-file +``` +Each database is configured under `spec.databases`, and multiple wallets are defined in the shared directory `/dbwallet` +as an example. To configure a combined wallet for multiple databases, see [configuring a combined wallet](#configuring-wallets-for-multiple-databases). + +In the configuration file _config-file_, db1 and db2 are configured with the credentials provided as environment variables through the +__databaseobserver__ YAML file as secrets. + +```yaml +# config.yaml +# Environment variables of the form ${VAR_NAME} will be expanded. +databases: + db1: + username: ${db01_USERNAME} + password: ${db01_PASSWORD} + url: db1_tp + + db2: + username: ${DB2_USN} + password: ${DB2_PWD} + url: db2_tp +``` + +To create the configMap, run the following command: + +```bash +kubectl create cm config-file --from-file=config.yaml +``` + +To learn more about the config file, you can consult the [official documentations of the exporter](https://github.com/oracle/oracle-db-appdev-monitoring?tab=readme-ov-file#standalone-binary). + +#### Configuring Wallets for Multiple Databases +In configuring multiple databases where each connection requires a database wallet, a combined wallet is required and can be configured through the databaseobserver YAML file. To +create a combined wallet: + +1. Copy TNS aliases from every tnsnames.ora and combined them into one tnsnames.ora file. + + +2. Set the wallet directory for the aliases inside security, with the following snippet pointing to each database wallet location: `(MY_WALLET_DIRECTORY=/example_dbwallet/db01)`, for example: + +``` +...)(security=(MY_WALLET_DIRECTORY=)(ssl_server_dn_match=...))) +``` + + +3. Place the combined `tnsnames.ora` file and one of the `sqlnet.ora` files inside a combined directory. + + +4. Take wallet files (`.sso`, `.p12`, `.pem`) and place them in separate directories. + + +The resulting wallet directory structure should look similar to the following, where wallet files for each database are in separate directories: +```bash +# +example_dbwallet +├── combined +│ ├── sqlnet.ora +│ └── tnsnames.ora # Combined tnsnames.ora +├── db01 +│ ├── cwallet.sso +│ ├── ewallet.p12 +│ └── ewallet.pem +└── db02 + ├── cwallet.sso + ├── ewallet.p12 + └── ewallet.pem +``` +Return to the YAML file for the next example. + +5. Set the combined _tnsnames.ora_ under `spec.wallet.secret` and set the +specific database wallet files (.sso, .p12, .pem) under `.spec.wallet.additional`. + +6. Finally, set the TNS_ADMIN to the location of the `tnsnames.ora`. + +> Note: When setting the name under `spec.wallets.additional[].name`, you must provide a unique name other than `creds`, because this is the default volume name. + +To learn more about this requirement, you can consult the [official documentation of the exporter](https://github.com/oracle/oracle-db-appdev-monitoring?tab=readme-ov-file#configuring-connections-for-multiple-databases-using-oracle-database-wallets). + + +## Database Authentication with Vaults in the Cloud +You can use Cloud Vault resources to store sensitive database credentials. In this release, the following vaults are supported: +- OCI Vault +- Azure Vault + +### OCI Vault Configuration +The OCI Vault can be used to store the database credentials. This release supports storing the Oracle Database password in the OCI Vault. + +When you configure the Vault, you must provide the following: +- Vault details: + - The string OCID of the Vault used + - The string name of the OCI Vault Secret containing the password +- Authentication details (if applicable): + - Kubernetes Secret containing the [OCI CLI Config file](https://docs.oracle.com/en-us/iaas/Content/API/SDKDocs/cliconfigure.htm) + - Kubernetes Secret containing the user's OCI CLI Private Key + +The observability exporter needs to authenticate requests to retrieve the database password from the OCI Vault. When configuring API Key authentication, +the OCI CLI config file and the __DEFAULT profile is used__. +> Note: The exporter uses the DEFAULT profile. + +For example, the OCI CLI config file can appear as follows: +```bash +[DEFAULT] +user= +fingerprint= +key_file=/.oci/private.pem +tenancy= +region= +``` + +> Note: Ensure that the key_file is in `/.oci` and that the private key file matches. + +You can create the configmap with the following command: +```bash +kubectl create cm oci-cred --from-file=config +``` + +You can create the secret with the following command: +```bash +kubectl create secret generic oci-privatekey --from-file=private.pem +``` + +Finally, to configure the exporter to use an OCI Vault for retrieving the password, you can configure the following fields in the YAML file: +```yaml +spec: + database: + oci: + vaultID: ocid1.vault.oc1.. + vaultPasswordSecret: sample_secret + + # ... + + ociConfig: + configMap: + name: oci-cred + privateKey: + name: oci-privatekey +``` + +### Azure Vault Configuration +The Azure Vault can be used to store the database credentials. This release supports storing the database username and password in the Azure Vault. + +To configure the exporter to use Azure Vault for retrieving database credentials, you can configure the following fields: +```yaml +spec: + database: + azure: + vaultID: "..." + vaultUsernameSecret: sample_usn_secret + vaultPasswordSecret: sample_pwd_secret + + # ... + + azureConfig: + configMap: + name: azure-cred +``` + +The `spec.azureConfig` field allows you to provide the environment variables through a configMap: +- AZURE_TENANT_ID +- AZURE_CLIENT_ID +- AZURE_CLIENT_SECRET + +You can then create the following configmap referenced in the YAML file above with your desired values: +```bash +kubectl create configmap azure-cred \ +--from-literal=tenantId= \ +--from-literal=clientId= \ +--from-literal=clientSecret= +``` + +## Defining an Exporter Config File +A YAML configuration file for the exporter can be provided by setting the `--config.file=` +command-line argument. It is recommended to use the configuration file from the 2.0.0 release of the exporter +and onwards. + +To configure the exporter config file, set the path to the YAML file +under `spec.deployment.args` from which to read the config from. The configMap +containing the exporter settings and configurations is set through `spec.exporterConfig`. +Specifying `spec.exporterConfig.mountPath` allows you to control the location where +the volume will be mounted. +```yaml +spec: + # ... + + deployment: + args: "--config.file=/config/exporter-config.yaml" + + exporterConfig: + mountPath: "/config" + configMap: + key: exporter-config.yaml + name: exporter-config-file +``` + +Create the _exporter-config-file_ configMap. For example, using the following example configuration. +```yaml +# exporter-config.yaml + +# Example Oracle Database Metrics Exporter Configuration file. +# Environment variables of the form ${VAR_NAME} will be expanded. +databases: + default: + ## Database username + username: ${DB_USERNAME} + ## Database password + password: ${DB_PASSWORD} + ## Database connection url + url: localhost:1521/freepdb1 + + ## Metrics query timeout for this database, in seconds + queryTimeout: 5 + + ### Connection pooling settings for the go-sql connection pool + ## Max open connections for this database using go-sql connection pool + maxOpenConns: 10 + ## Max idle connections for this database using go-sql connection pool + maxIdleConns: 10 +``` +For more information on the configuration fields, see the following [examples](https://github.com/oracle/oracle-db-appdev-monitoring/blob/main/example-config.yaml). You can then create the configMap that is referenced in the _databaseobserver_ YAML file with the following command: +```bash +kubectl create cm exporter-config-file --from-file=exporter-config.yaml +``` + +Note that in the above configuration file, the environment variables `DB_USERNAME` and `DB_PASSWORD` will be expanded +by the exporter. These environment variables +are one of the default environment variables set by the DatabaseObserver controller. In the DatabaseObserver YAML file, you +can set the following details: + +```yaml +spec: + database: + dbUser: + secret: db-secret + dbPassword: + secret: db-secret + + # ... +``` + ## Scraping Metrics -The `databaseObserve`r resource deploys the Observability exporter container. This container connects to an Oracle Database and +The `databaseObserver` resource deploys the Observability exporter container. This container connects to an Oracle Database and scrapes metrics using SQL queries. By default, the exporter provides standard metrics, which are listed in the [official GitHub page of the Observability Exporter](https://github.com/oracle/oracle-db-appdev-monitoring?tab=readme-ov-file#standard-metrics). To define custom metrics in Oracle Database for scraping, a TOML file that lists your custom queries and properties is required. -The file will have metric sections with the following parts: -- a context -- a request, which contains the SQL query -- a map between the field(s) in the request and comment(s) - For example, the code snippet that follows shows how you can define custom metrics: ```toml [[metric]] @@ -277,35 +782,46 @@ oracledb_test_value_2 2 You can find more information in the [__Custom Metrics__](https://github.com/oracle/oracle-db-appdev-monitoring?tab=readme-ov-file#custom-metrics) section of the Official GitHub page. - - ### Custom Metrics Config -When configuring a `databaseObserver` resource, you can use the field `spec.configuration.configMap` to provide a -custom metrics file as a `configMap`. +When configuring a `databaseObserver` resource, you can use the field `spec.metrics.configMap[]` to provide one or more +custom metrics files as a `configMap`. You can create the `configMap` by running the following command: ```bash -kubectl create cm custom-metrics-cm --from-file=metrics.toml +kubectl create cm custom-metrics --from-file=metrics.toml ``` Finally, when creating or updating a `databaseObserver` resource, if we assume using the example above, you can set the fields in your YAML file as follows: ```yaml spec: - configuration: + metrics: + configMap: + - name: custom-metrics + key: "metrics.toml" +``` + +When configuring multiple configurations with different configmaps, you can do so by listing them under `spec.metrics.configMap`: +```yaml +spec: + metrics: configMap: - key: "metrics.toml" - name: "custom-metrics-cm" + - name: custom-metrics + key: "metrics.toml" + - name: txeventq-metrics + key: "txeventq.toml" ``` +> Note: This mounts a volume named `metrics-volume` and in one mounted directory located inside the container `/oracle/observability` will include all +the provided TOML files. + ### Prometheus Release -To enable your Prometheus configuration to find and include the `ServiceMonitor` created by the `databaseObserver` resource, the field `spec.prometheus.serviceMonitor.labels` is an __important__ and __required__ field. The label on the ServiceMonitor +To enable your Prometheus configuration to find and include the `ServiceMonitor` created by the `databaseObserver` resource, the field `spec.serviceMonitor.labels` is an __important__ and __required__ field. The label on the ServiceMonitor must match the `spec.serviceMonitorSelector` field in your Prometheus configuration. ```yaml - prometheus: - serviceMonitor: - labels: - release: stable + serviceMonitor: + labels: + release: prometheus ``` ## Scraping Logs @@ -319,8 +835,9 @@ In the following example, `Promtail` is used as a sidecar container that ships t To configure the `databaseObserver` resource with a sidecar, two fields can be used: ```yaml spec: - sidecars: [] - sidecarVolumes: [] + sidecar: + containers: [] + volumes: [] ``` You can find an example in the `samples` directory, which deploys a Promtail sidecar container as an example: @@ -328,13 +845,13 @@ You can find an example in the `samples` directory, which deploys a Promtail sid ### Custom Log Location with PersistentVolumes -The fields `spec.log.filename` and `spec.log.path` enable you to configure a custom location and filename for the log. +The fields `spec.log.filename` and `spec.log.destination` enable you to configure a custom location and filename for the log. Using a custom location enables you to control where to place the logfile, such as a `persistentVolume`. ```yaml log: filename: "alert.log" - path: "/log" + destination: "/log" ``` To configure the `databaseObserver` resource to put the log file in a `persistentVolume`, you can set the following fields @@ -349,32 +866,11 @@ If `spec.log.volume.persistentVolumeClaim.claimName` is not specified, then an ` ```yaml log: volume: - name: my-log-volume + name: log-volume persistentVolumeClaim: claimName: "my-pvc" ``` -The security context defines privilege and access control settings for a pod container, If these privileges and access control settingrs need to be updated in the pod, then the same field is available on the `databaseObserver` spec. You can set this object under deployment: `spec.exporter.deployment.securityContext`. - -```yaml -spec: - exporter: - deployment: - runAsUser: 1000 -``` - -Configuring security context under the PodTemplate is also possible. You can set this object under: `spec.exporter.deployment.podTemplate.securityContext` - -```yaml -spec: - exporter: - deployment: - podTemplate: - securityContext: - supplementalGroups: [1000] -``` - - ### Working with Sidecars to deploy Promtail The fields `spec.sidecars` and `spec.sidecarVolumes` provide the ability to deploy container images as a sidecar container alongside the `observability-exporter` container. @@ -384,7 +880,8 @@ You can specify container images to deploy inside `spec.sidecars` as you would n For example, to deploy a Grafana Promtail image, you can specify the container and its details as an element to the array, `spec.sidecars`. ```yaml - sidecars: + sidecar: + containers: - name: promtail image: grafana/promtail args: @@ -392,19 +889,19 @@ For example, to deploy a Grafana Promtail image, you can specify the container a volumeMounts: - name: promtail-config-volume mountPath: /etc/promtail - - name: my-log-volume + - name: log-volume mountPath: /log ``` -> Important Note: Make sure the volumeMount name matches the actual name of the volumes referenced. In this case, `my-log-volume` is referenced in `spec.log.volume.name`. +> Important Note: The log volume is set by the controller with the name `log-volume` by default, unless set in `spec.log.volume.name`. -In the field `spec.sidecarVolumes`, you can specify and list the volumes you need in your sidecar containers. The field -`spec.sidecarVolumes` is an array of Volumes (`[]corev1.Volume`). +In the field `spec.sidecar.volumes`, you can specify and list the volumes you need in your sidecar containers. The field +`spec.sidecar.volumes` is an array of Volumes (`[]corev1.Volume`). For example, when deploying the Promtail container, you can specify in the field any volume that needs to be mounted in the sidecar container above. ```yaml - sidecarVolumes: + volumes: - name: promtail-config-volume configMap: name: promtail-config-file @@ -441,10 +938,73 @@ To create the `configmap`, you can run the following command: kubectl create cm promtail-config-file --from-file=config.yaml ``` +## Customizing Resources and Available Configuration Options + +### Environment Variables and Default Values +The following environment variables are set and provided by the controller by default: + +| Environment Variable | Related Field | Default | Details | +|----------------------------|-----------------------------------------|-------------------------------------------|-----------------------------------------------------------------------------------------------------------------| +| `ORACLE_HOME` | - | /lib/oracle/21/client64/lib | Location of the Oracle Instant Client | +| `TNS_ADMIN` | - | /lib/oracle/21/client64/lib/network/admin | Location of your (unzipped) wallet | +| `DB_USERNAME` | database.dbUser | - | Database username, retrieved from a Kubernetes secret, not set or used for multi-databases | +| `DB_PASSWORD` | database.dbPassword | - | Database password, retrieved from a Kubernetes secret, not set or used for multi-databases | +| `DB_CONNECT_STRING` | database.dbConnectionString | - | Database connection string, retrieved from a Kubernetes secret, not set or used for multi-databases | +| `_USERNAME` | databases.<key>.dbUser | - | Database username, Environment Variable can be completely renamed through `dbUser.envName` | +| `_PASSWORD` | databases.<key>.dbPassword | - | Database password, Environment Variable can be completely renamed through `dbPassword.envName` | +| `_CONNECT_STRING` | database.<key>.dbConnectionString | - | Database connection string, Environment Variable can be completely renamed through `dbConnectionString.envName` | +| `OCI_VAULT_ID` | database.oci.vaultID | - | Vault OCID containing the OCI Vault Secret referenced | +| `OCI_VAULT_SECRET_NAME` | database.oci.vaultPasswordSecret | - | Vault Secret name containing the DB Password secret | +| `AZ_VAULT_ID` | database.azure.vaultID | - | Vault ID containing the Azure Vault Secret referenced | +| `AZ_VAULT_PASSWORD_SECRET` | database.azure.vaultPasswordSecret | - | Vault Secret name containing the DB Password secret | +| `AZ_VAULT_USERNAME_SECRET` | database.azure.vaultUsernameSecret | - | Vault Secret name containing the DB Username secret | +| `AZURE_TENANT_ID` | azureConfig.configMap.name | - | Your Azure cloud Tenant ID | +| `AZURE_CLIENT_ID` | azureConfig.configMap.name | - | Your Azure cloud client ID | +| `AZURE_CLIENT_SECRET` | azureConfig.configMap.name | - | Your Azure cloud client secret | +| `LOG_DESTINATION` | log.destination | /log/alert.log | Location of where alert.log will be placed | +| `CUSTOM_METRICS` | metrics.ConfigMap[] | - | List of paths where the TOML files are located | + +The following default values or behavior is set: + +| Usage | Related Field(s) | Default | Details | +|---------------------------------------------|----------------------------------------------|---------------------------------------------------------------------|---------| +| Database Username Key | `database.dbUser.key` | username | | +| Database Password Key | `database.dbPassword.key` | password | | +| Database Connection String Key | `database.dbConnectionString.key` | connection | | +| Wallet MountPath | `wallet.MountPath` | /lib/oracle/21/client64/lib/network/admin | | +| Env Var for Database Username | `databases..dbUser.envName` | <key>_USERNAME | | +| Env Var for Database Password | `databases..dbPassword.envName` | <key>_PASSWORD | | +| Env Var for Database Connection String | `databases..dbConnectionString.envName` | <key>_CONNECT_STRING | | +| Exporter Image | `deployment.image` | container-registry.oracle.com/database/observability-exporter:2.0.2 | | +| Volume name of OCI Config file mounted | `ociConfig.configMap.name` | oci-config-volume | | +| Volume name of Metrics Config files mounted | `metrics.configMap.name` | metrics-volume | | +| Volume name of Log volume mounted | `log.volume.name` | log-volume | | +| Volume name of Exporter Config mounted | `exporterConfig.configMap.name` | config-volume | | +| Volume name of Wallet | `wallet` | | | +| Log config filename | `log.filename` | alert.log | | +| Log config file location for mounting | `log.destination` | /log | | +| Metrics config files location for mounting | `metrics` | /oracle/observability | | +| Exporter config file location for mounting | `exporterConfig.mountPath` | /config | | +| OCI Config file location for mounting | `ociConfig.mountPath` | /.oci | | + +Overwriting environment variables can be managed by configuring the fields `spec.deployment.envs` or `spec.deployment.args` or through the [exporter config file](#defining-an-exporter-config-file): +```yaml +spec: + deployment: + args: + - "--config.file=/location" + env: + TNS_ADMIN: /path/to/new/location +``` +These variables and configuration values can be set explicitly, variables such as: +- DB_ROLE +- DATABASE_MAXIDLECONNS +- DATABASE_MAXOPENCONNS +- DATABASE_POOLINCREMENT +- DATABASE_POOLMAXCONNECTIONS +- DATABASE_POOLMINCONNECTIONS -## Other Configuration Options - -### Labels +### Managing Labels __About the Default Label__ - The resources created by the Observability Controller will automatically be labelled with: - `app`: `` @@ -475,78 +1035,92 @@ spec: Meanwhile, you can provide extra labels to the resources created by the `databaseObserver` controller, such as the Deployment, Pods, Service and ServiceMonitor. ```yaml spec: - exporter: - deployment: - labels: - podTemplate: - labels: - service: - labels: - prometheus: - serviceMonitor: + deployment: + labels: + podTemplate: labels: + service: + labels: + serviceMonitor: + labels: ``` ### Custom Exporter Image or Version -The field `spec.exporter.deployment.image` is provided to enable you to make use of a newer or older version of the [observability-exporter](https://github.com/oracle/oracle-db-appdev-monitoring) +The field `spec.deployment.image` is provided to enable you to make use of a newer or older version of the [observability-exporter](https://github.com/oracle/oracle-db-appdev-monitoring) container image. ```yaml spec: - exporter: - deployment: - image: "container-registry.oracle.com/database/observability-exporter:1.5.3" + deployment: + image: "container-registry.oracle.com/database/observability-exporter:2.0.1" ``` ### Custom Environment Variables, Arguments and Commands -The fields `spec.exporter.deployment.env`, `spec.exporter.deployment.args` and `spec.exporter.deployment.commands` are provided for adding custom environment variables, arguments (`args`) and commands to the containers. +The fields `spec.deployment.env`, `spec.deployment.args` and `spec.deployment.commands` are provided for adding custom environment variables, arguments (`args`) and commands to the containers. Any custom environment variable will overwrite environment variables set by the controller. ```yaml spec: - exporter: - deployment: - env: - DB_ROLE: "" - TNS_ADMIN: "" - args: - - "--log.level=info" - commands: - - "/oracledb_exporter" + deployment: + env: + DB_ROLE: "SYSDBA" + TNS_ADMIN: "/path/to/wallet" + args: + - "--log.level=info" + commands: + - "/oracledb_exporter" ``` +### Security Contexts +The security context defines privilege and access control settings for a pod container. If these privileges and access control setting need to be updated in the pod, then the same field is available on the `databaseObserver` spec. You can set this object under deployment: `spec.deployment.securityContext`. + +```yaml +spec: + deployment: + securityContext: + runAsUser: 1000 +``` + +Configuring security context under the PodTemplate is also possible. You can set this object under: `spec.deployment.podTemplate.securityContext` + +```yaml +spec: + deployment: + podSecurityContext: + supplementalGroups: [1000] + +``` + ### Custom Service Ports -The field `spec.exporter.service.ports` is provided to enable setting the ports of the service. If not set, then the following definition is set by default. +The field `spec.service.ports` is provided to enable setting the ports of the service. If not set, then the following definition is set by default. ```yaml spec: - exporter: - service: - ports: - - name: metrics - port: 9161 - targetPort: 9161 + service: + ports: + - name: metrics + port: 9161 + targetPort: 9161 ``` ### Custom ServiceMonitor Endpoints -The field `spec.prometheus.serviceMonitor.endpoints` is provided for providing custom endpoints for the ServiceMonitor resource created by the `databaseObserver`: +The field `spec.serviceMonitor.endpoints` is provided for providing custom endpoints for the ServiceMonitor resource created by the `databaseObserver`: ```yaml spec: - prometheus: - serviceMonitor: - endpoints: - - bearerTokenSecret: - key: '' - interval: 20s - port: metrics - relabelings: - - action: replace - sourceLabels: - - __meta_kubernetes_endpoints_label_app - targetLabel: instance + serviceMonitor: + endpoints: + - bearerTokenSecret: + key: '' + interval: 20s + port: metrics + relabelings: + - action: replace + sourceLabels: + - __meta_kubernetes_endpoints_label_app + targetLabel: instance ``` ## Mandatory roles and privileges requirements for Observability Controller @@ -572,6 +1146,8 @@ and gets and lists configmaps and secrets. | configmaps.apps | get list | | databaseobservers.observability.oracle.com/finalizers | update | + + ## Debugging and troubleshooting ### Show the details of the resource @@ -582,7 +1158,7 @@ kubectl describe databaseobserver/database-observer-sample ``` If any error occurs during the reconciliation loop, then the Operator either reports -the error using the resource's event stream, or it will show the error under conditions. +the error using the resource's event stream, or the resource's Conditions. ### Check the logs of the pod where the operator deploys Follow these steps to check the logs. @@ -599,12 +1175,24 @@ Follow these steps to check the logs. kubectl logs deployment.apps/oracle-database-operator-controller-manager -n oracle-database-operator-system ``` -## Known Potential Issues +## Known Issues + +### Using the OCI Vault (or Azure Vault) causes an error -| Issue | Example error | Potential Workaround | -|---------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------| -| Pod may encounter error Permission denied when creating log file. Pod cannot access file system due to insufficient permissions | ```level=error msg="Failed to create the log file: /log/alert.log"``` | Configure securityContext in the spec, add your group ID to the `supplementalgroups` inside `spec.exporter.deployment.podTemplate.securityContext` field. | +> The development team has identified an issue with v2.0.2 of the exporter when using the OCI vault to store only the username or only the password, leading to an error. +> Using the Azure Vault to store only the username or the password will likely produce the same error. +__WORKAROUND:__
+Because the OCI Vault feature is not functioning for v2 to v2.0.2 of the exporter, Oracle recomends that you use Kubernetes secrets. +For Azure users, only retrieval of both username and password from the Vault is supported. +Retrieving only one (username or password) of the credentials will lead to an error. + +__WORKAROUND AFTER V2.0.2__: +When a new version of the exporter is released with the fix, set the field `deployment.image` to the new version of the exporter. ## Resources +For further information about the Oracle Databases logs and metrics Exporter container image, +consult the official repository documentations: - [GitHub - Unified Observability for Oracle Database Project](https://github.com/oracle/oracle-db-appdev-monitoring) + + diff --git a/docs/oraclerestart/README.md b/docs/oraclerestart/README.md new file mode 100644 index 00000000..a7fbe0a1 --- /dev/null +++ b/docs/oraclerestart/README.md @@ -0,0 +1,75 @@ +# Using Oracle Restart with Oracle Database Operator for Kubernetes + +Oracle Restart is an option to the award-winning Oracle Database Enterprise Edition. Oracle Restart is a feature introduced in Oracle 11gR2 that automatically restarts Oracle components, such as the database instance, listener, and Oracle ASM, after a failure or system reboot. It ensures that these components are started in the correct order and that they are managed by Oracle's High Availability Services (HAS). This enhances the availability of Oracle databases in a standalone server environment. Refer [this documentation](https://docs.oracle.com/cd/E18283_01/server.112/e17120/restart001.htm) + +For more information on Oracle Restart Database 19c refer to the [Oracle Database Documentation](http://docs.oracle.com/en/database/). + +Kubernetes provides essential infrastructure building blocks, including compute, storage, and networking resources, and exposes them as code for infrastructure automation. This approach enables rapid provisioning of multi-node topologies. Additionally, Kubernetes offers the **StatefulSet** workload API object—ideal for managing stateful applications such as Oracle Restart, Single Instance Oracle Databases, and other Oracle features or configurations that require persistent storage and stable network identities. + +The Oracle Restart Controller in the Oracle Database Operator deploys Oracle Databases as a StatefulSet within Kubernetes clusters, using the Oracle Restart Slim Image. The Oracle Restart Controller manages the typical lifecycle operations of an Oracle Database in a Kubernetes environment, including deployment, monitoring, scaling, upgrades, and deletion, as illustrated below: + +* Create Oracle Database + * Install and Configure Oracle Grid Infrastructure + * Install and Configure Oracle Database +* Create Persistent Storage, along with Statefulset +* Create Services +* Oracle Restart Instances Cleanup + +## Using Oracle Restart Controller + +To create an Oracle Database, complete the steps in the following sections: + +1. [Prerequisites for running Oracle Restart Controller](#prerequisites-for-running-oracle-restart-controller) +2. [Provisioning Oracle Restart database in a Oracle Kubernetes Engine Environment](#provisioning-oracle-restart-database-in-an-oracle-kubernetes-engine-environment) +3. [Connecting to Oracle Restart Database](#connecting-to-oracle-restart-database) +4. [Known Issues](#known-issues) +5. [Cleanup](#cleanup) +6. [Debugging and Troubleshooting](#debugging-and-troubleshooting) + +**Note** Before proceeding to the next section, you must complete the instructions given in each section based on your enviornment. + +### Prerequisites for running Oracle Restart Controller + +**IMPORTANT :** You must make the changes specified in this section before you proceed to the next section. + +To become familiar with Oracle Restart in containerized environments, review the [this documentation](https://github.com/oracle/docker-images/blob/main/OracleDatabase/RAC/OracleRealApplicationClusters/docs/orestart/README.md) before proceeding further + +[Prerequisites for running Oracle Restart Controller](./provisioning/prerequisites_oracle_restart_db.md) + +## Provisioning Oracle Restart database in an Oracle Kubernetes Engine Environment + +Deploy Oracle Restart Database YAML files using Kubernetes Cluster on your Oracle Kubernetes Engine Environment (OKE). There are multiple use case possible for deploying the Oracle Restart Database. + +[1. Provisioning an Oracle Restart Database](./provisioning/provisioning_oracle_restart_db.md) +[2. Provisioning an Oracle Restart Database with NodePort Service](./provisioning/provisioning_oracle_restart_db_nodeport.md) +[3. Provisioning an Oracle Restart Database with Load Balancer](./provisioning/provisioning_oracle_restart_db_loadbalancer.md) +[4. Change Memory and CPU allocation for an earlier provisioned Oracle Restart Database](./provisioning/change_memory_cpu_for_oracle_restart_db.md) +[5. Change the size of Software Storage Location for an existing Oracle Restart Database](./provisioning/change_sw_storage_size_for_oracle_restart_db.md) +[6. Provisioning an Oracle Restart Database with Custom Storage Class](./provisioning/provisioning_oracle_restart_storage_class.md) +[7. Provisioning an Oracle Restart Database with RU Patch on FileSystem](./provisioning/provisioning_oracle_restart_db_rupatch.md) +[8. Provisioning an Oracle Restart Database with RU Patch on Existing PVC](./provisioning/provisioning_oracle_restart_rupatch_pvc.md) +[9. Provisioning an Oracle Restart Database with RU Patch and One Offs with Custom Storage Class](./provisioning/provisioning_oracle_restart_db_rupatch_oneoffs.md) +[10. Provisioning an Oracle Restart Database with multiple diskgroups](./provisioning/provisioning_oracle_restart_multiple_diskgroups.md) +[11. Provisioning an Oracle Restart Database with multiple diskgroups with different redundancy](./provisioning/provisioning_oracle_restart_multiple_diskgroups_with_redundancy.md) +[12. Provisioning an Oracle Restart Database with multiple diskgroups with different redundancy and option to specify separate storage class](./provisioning/oraclerestart_prov_multiple_diskgroups_with_redundancy_with_separate_storage_class.md) +[13. Adding ASM Disks - Add ASM Disks to an existing Oracle Restart Database](./provisioning/add_asm_disk_to_an_existing_restart_database.md) +[14. Deleting ASM Disks - Delete ASM Disks from an existing Oracle Restart Database](./provisioning/delete_asm_disks_from_an_existing_restart_database.md) + +**NOTE:** Resizing of the `ASM Disks` is _not_ allowed. You can add new ASM Disks to an exising Oracle Restart Database. + +## Connecting to Oracle Restart Database + +After the Oracle Restart database has been provisioned using the Oracle Restart Controller in Oracle Database Operator, you can follow the steps in this document to connect to the Oracle Restart Database: [Database Connectivity](./provisioning/database_connection.md) + +## Known Issues + +Refer to the Known Issues document for assistance related to issues deploying Oracle Restart Database using Oracle Restart Controller: [Known Issues](./provisioning/known_issues.md) + +## Cleanup + +Steps to clean up Oracle Restart Database deployed using Oracle Restart Controller in this document in Oracle Database Kubernetes Operator are documented in this page: [Cleanup](./provisioning/cleanup.md) + + +## Debugging and Troubleshooting + +To debug the Oracle Restart Database provisioned using the Oracle Restart Controller in Oracle Database Kubernetes Operator, follow this document: [Debugging and troubleshooting](./provisioning/debugging.md) diff --git a/docs/oraclerestart/provisioning/add_asm_disk_to_an_existing_restart_database.md b/docs/oraclerestart/provisioning/add_asm_disk_to_an_existing_restart_database.md new file mode 100644 index 00000000..eee3f5ae --- /dev/null +++ b/docs/oraclerestart/provisioning/add_asm_disk_to_an_existing_restart_database.md @@ -0,0 +1,64 @@ +## Adding ASM Disks - Add ASM Disks to an existing Oracle Restart Database + +### In this use case: + +* You have previously deployed an Oracle Restart Database in Kubernetes (for example, on OKE or OpenShift) using the Oracle Restart Database Controller. Now, you need to expand ASM storage by adding new ASM disks. +* The existing Oracle Restart Database is deployed with Node Port Service using the file `oraclerestart_prov_nodeports.yaml` from Case [Provisioning an Oracle Restart Database with NodePort Service](./provisioning_oracle_restart_db_nodeport.md) using Oracle Restart Controller with: + * Oracle Restart Pod + * Headless services for Oracle Restart + * Oracle Restart Node hostname + * Node Port 30007 mapped to port 1521 for Database Listener. If you are using Loadbalancer then you will see LB service. + * Persistent volumes created automatically based on specified disks for Oracle ASM storage + * Software Persistent Volume and Staged Software Persistent Volume using the specified location on the corresponding worker node. + * Namespace: `orestart` + * Staged Software location on the worker nodes is specified by `hostSwStageLocation`. The Grid Infrastructure and RDBMS Binaries are copied to this location on the worker node. + * Software location on the worker nodes is specified by `hostSwLocation`. The GI HOME and the RDBMS HOME in the Oracle Restart Pod will be mounted using this location on the worker node. + +### General Steps + * If you are using storage class to dynamically provision the ASM disks, then you do not need to allocate block devices. If you are not using storage class, then you must allocate block devices to worker nodes where the Oracle Restart database pod is running. You must clean up the new ASM disks by using the `dd` command. + * Update the Oracle Restart Custom Resource. Edit the custom resource YAML (`oraclerestarts.database.oracle.com`) to reference the new PVCs/disks under the appropriate ASM configuration. + +### In this Example: + * Oracle Restart Database Slim Image `dbocir/oracle/database-orestart:19.3.0-slim` is used. The image is built using files from this [GitHub location](https://github.com/oracle/docker-images/tree/main/OracleDatabase/RAC/OracleRealApplicationClusters#building-oracle-rac-database-container-slim-image). + * The existing disks on the worker nodes for the ASM that are used in Oracle Restart storage are `/dev/disk/by-partlabel/asm-disk1` and `/dev/disk/by-partlabel/asm-disk2`. + * Specify the size and names of these devices using the parameter `storageSizeInGb`. By default, size is in GBs. + * If you are using **storage class to dynamically provision ASM disks**, then you can skip these steps. In this example, two new disks will be added to the existing Oracle Restart Database Deployment. For this purpose, the disks on the worker nodes that will be used are `/dev/disk/by-partlabel/asm-disk3` and `/dev/disk/by-partlabel/asm-disk4`. + * Update the corresponding device list in the `initParams` section. + * The default value in YAML file is `autoUpdate: "true"`, which will delete and recreate the pod with updated ASM disks in the Oracle Restart Deployment. In this case, the new disks will be automatically added to the existing Diskgroup. + * If the value in the YAML file is set to `autoUpdate: "false"`, then the Oracle Restart Database Pod is recreated, but the additional disks are _not_ added to the ASM Disk Group automatically. + + +## When autoUpdate is set to true +* For this use case, use the file [orestart_prov_asm_disk_addition.yaml](./orestart_prov_asm_disk_addition.yaml): +* Deploy the `orestart_prov_asm_disk_addition.yaml` file: + ```sh + kubectl apply -f orestart_prov_asm_disk_addition.yaml + ``` +In this case, the new disks will be added to the existing Diskgroup in the Oracle Restart Database. +* Check the status of the deployment: + ```sh + # Check the status of the Kubernetes Pods: + kubectl get all -n orestart + + # Check the logs of a particular pod. For example, to check status of pod "dbmc1-0": + kubectl exec -it pod/dbmc1-0 -n orestart -- bash -c "tail -f /tmp/orod/oracle_db_setup.log" + ``` + * Example logs are in [ASM addition autoupdate_true logs](./logs/asm_addition_autoupdate_true.txt) for disk addition with the option `autoUpdate: true`. + + +## When autoUpdate is set to false +* For this use case, use the file: [orestart_prov_asm_disk_addition_autoupdate_false.yaml](./orestart_prov_asm_disk_addition_autoupdate_false.yaml): +* Deploy the `orestart_prov_asm_disk_addition_autoupdate_false.yaml` file: + ```sh + kubectl apply -f orestart_prov_asm_disk_addition_autoupdate_false.yaml + ``` +In this scenario, new disks are added to Oracle Restart Database Object Statefulset and Pods are recreated, but this disk is not added to the ASM Disk Group. +* Check the status of the deployment: + ```sh + # Check the status of the Kubernetes Pods: + kubectl get all -n orestart + + # Check the logs of a particular pod. For example, to check status of pod "dbmc1-0": + kubectl exec -it pod/dbmc1-0 -n orestart -- bash -c "tail -f /tmp/orod/oracle_db_setup.log" + ``` + * Example logs are in [ASM addition autoupdate_false logs](./logs/asm_addition_autoupdate_false.txt) for disk addition with option `autoUpdate: false`. diff --git a/docs/oraclerestart/provisioning/asm_addition_autoupdate_false.txt b/docs/oraclerestart/provisioning/asm_addition_autoupdate_false.txt new file mode 100644 index 00000000..5e548d89 --- /dev/null +++ b/docs/oraclerestart/provisioning/asm_addition_autoupdate_false.txt @@ -0,0 +1,424 @@ +#### Status before the additional ASM Disks additional + +$ kubectl describe oraclerestarts.database.oracle.com/oraclerestart-sample -n orestart +Name: oraclerestart-sample +Namespace: orestart +Labels: +Annotations: OracleRestarts.database.oracle.com/old-spec: + {"instDetails":{"name":"dbmc1","hostSwLocation":"/scratch/orestart/","workerNode":["10.0.10.58"],"nodePortSvc":[{"name":"dbmc1-service-nod... +API Version: database.oracle.com/v4 +Kind: OracleRestart +Metadata: + Creation Timestamp: 2025-08-21T15:03:39Z + Finalizers: + database.oracle.com/oraclerestartfinalizer + Generation: 1 + Resource Version: 225385451 + UID: 29deacda-d177-48d2-8092-30a5adbfcdf1 +Spec: + Asm Storage Details: + Disks By Size: + Disk Names: + /dev/disk/by-partlabel/asm-disk1 + /dev/disk/by-partlabel/asm-disk2 + Storage Size In Gb: 50 + Config Params: + Cpu Count: 4 + Crs Asm Device List: /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 + Crs Asm Disk Dg: +DATA + Crs Asm Disk Dg Redundancy: external + Db Base: /u01/app/oracle + Db Char Set: AL32UTF8 + Db Data File Dest Dg: +DATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: +DATA + Db Response File: + Db Sw Zip File: db_home.zip + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Grid Sw Zip File: grid_home.zip + Host Sw Stage Location: /scratch/software/stage + Inventory: /u01/app/oraInventory + Pga Size: 1G + Processes: 2000 + Sga Size: 3G + Sw Mount Location: /u01 + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Image: localhost/oracle/database-orestart:19.3.0-slim + Image Pull Policy: Always + Inst Details: + Host Sw Location: /scratch/orestart/ + Name: dbmc1 + Node Port Svc: + Name: dbmc1-service-nodeport + Svc Type: nodeport + Port Mappings: + Node Port: 30007 + Port: 1521 + Protocol: TCP + Target Port: 1521 + Worker Node: + 10.0.10.58 + Resources: + Limits: + Cpu: 2 + Memory: 16Gi + Requests: + Cpu: 2 + Memory: 16Gi + Security Context: + Sysctls: + Name: kernel.shmall + Value: 2097152 + Name: kernel.sem + Value: 250 32000 100 128 + Name: kernel.shmmax + Value: 8589934592 + Name: kernel.shmmni + Value: 4096 + Service Details: + Name: soepdb + Ssh Key Secret: + Key Mount Location: /mnt/.ssh + Name: ssh-key-secret + Priv Key Secret Name: ssh-privkey + Pub Key Secret Name: ssh-pubkey +Status: + Oracle Restart Nodes: + Name: dbmc1-0 + Node Details: + Instance State: OPEN + Pod State: AVAILABLE + Cluster State: HEALTHY + Mounted Devices: + /dev/disk/by-partlabel/asm-disk1 + /dev/disk/by-partlabel/asm-disk2 + State: AVAILABLE + Asm Details: + Diskgroup: + Disks: + /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 + Name: DATA + Redundancy: EXTERN + Conditions: + Last Transition Time: 2025-08-21T15:14:22Z + Message: oracle restart database is in a restricted state: PROVISIONING + Observed Generation: 1 + Reason: LastReconcileCycleQueued + Status: True + Type: ReconcileQueued + Last Transition Time: 2025-08-21T15:24:02Z + Message: no reconcile errors + Observed Generation: 1 + Reason: LastReconcileCycleCompleted + Status: True + Type: ReconcileComplete + Config Params: + Crs Asm Device List: /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 + Crs Asm Disk Dg: +DATA + Crs Asm Disk Dg Redundancy: external + Db Base: /u01/app/oracle + Db Data File Dest Dg: +DATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: +DATA + Db Response File: + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Inventory: /u01/app/oraInventory + Connect String: dbmc1-0:1521/PORCLCDB + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Db State: OPEN + External Connect String: 129.146.0.149:30007/soepdb + Inst Details: + Name: + Pdb Connect String: dbmc1-0:1521/ORCLPDB + Release Update: 19.28.0.0.0 + Role: PRIMARY + Service Details: + Name: soepdb + Svc State: service soepdb is running + State: AVAILABLE +Events: + + +[grid@dbmc1-0 ~]$ export ORACLE_SID=+ASM +[grid@dbmc1-0 ~]$ asmcmd lsdsk +Path +/dev/disk/by-partlabel/asm-disk1 +/dev/disk/by-partlabel/asm-disk2 +[grid@dbmc1-0 ~]$ +[grid@dbmc1-0 ~]$ ls -lrt /dev/disk/by-partlabel/asm-disk* +brw-rw----. 1 grid asmadmin 8, 49 Aug 21 15:24 /dev/disk/by-partlabel/asm-disk2 +brw-rw----. 1 grid asmadmin 8, 33 Aug 21 15:24 /dev/disk/by-partlabel/asm-disk1 + + + +### Apply the file "orestart_prov_asm_disk_addition_autoupdate_false.yaml" to add two new ASM Disks + +$ kubectl logs -f pod/oracle-database-operator-controller-manager-bc8dd8f68-pwfdk -n oracle-database-operator-system +. +. +2025-08-21T15:26:44Z INFO controllers.OracleRestart Detected Addition of ASM Disks: {"addedAsmDisks": ["/dev/disk/by-partlabel/asm-disk3", "/dev/disk/by-partlabel/asm-disk4"]} +2025-08-21T15:26:44Z INFO controllers.OracleRestart Initialized autoUpdate from provided specification {"autoUpdate": false} +2025-08-21T15:26:44Z INFO controllers.OracleRestart PV Found {"Instance.Namespace": "orestart", "Instance.Name": "oraclerestart-sample", "dep.Name": "asm-pv-disk-0-oraclerestart-sample"} +2025-08-21T15:26:44Z INFO controllers.OracleRestart PV Found {"Instance.Namespace": "orestart", "Instance.Name": "oraclerestart-sample", "dep.Name": "asm-pv-disk-1-oraclerestart-sample"} +2025-08-21T15:26:44Z INFO controllers.OracleRestart Creating a new PV {"Instance.Namespace": "orestart", "Instance.Name": "oraclerestart-sample", "dep.Name": "asm-pv-disk-2-oraclerestart-sample"} +2025-08-21T15:26:44Z INFO controllers.OracleRestart Creating a new PV {"Instance.Namespace": "orestart", "Instance.Name": "oraclerestart-sample", "dep.Name": "asm-pv-disk-3-oraclerestart-sample"} +2025-08-21T15:26:44Z INFO controllers.OracleRestart Creating a PVC {"Instance.Namespace": "orestart", "Instance.Name": "oraclerestart-sample"} +2025-08-21T15:26:44Z INFO controllers.OracleRestart Creating a PVC {"Instance.Namespace": "orestart", "Instance.Name": "oraclerestart-sample"} +2025-08-21T15:26:44Z INFO controllers.OracleRestart Validate New ASM Disks +2025-08-21T15:26:44Z INFO controllers.OracleRestart Creating DaemonSet: {"desiredDaemonSet.Name": "disk-check-daemonset"} +2025-08-21T15:26:54Z INFO controllers.OracleRestart Provided ASM Disks are valid, proceeding further +2025-08-21T15:26:54Z INFO controllers.OracleRestart Updating existing configmap +2025-08-21T15:26:54Z INFO controllers.OracleRestart Config Map updated successfully with new asm details +2025-08-21T15:26:54Z INFO controllers.OracleRestart Change State to UPDATING +2025-08-21T15:26:54Z INFO controllers.OracleRestart Oracle Restart Object updated with updateOracleRestartInstStatus +2025-08-21T15:26:54Z INFO controllers.OracleRestart StatefulSet spec differs for volume devices, updating StatefulSet (pods may be recreated) {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "StatefulSet.Namespace": "orestart", "StatefulSet.Name": "dbmc1"} +2025-08-21T15:26:54Z INFO controllers.OracleRestart StatefulSet update applied, waiting for pod recreation {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "StatefulSet.Namespace": "orestart", "StatefulSet.Name": "dbmc1"} +2025-08-21T15:26:54Z INFO controllers.OracleRestart Waiting for StatefulSet update to be applied {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "StatefulSet.Namespace": "orestart", "StatefulSet.Name": "dbmc1"} +2025-08-21T15:26:59Z INFO controllers.OracleRestart StatefulSet update is applied successfully {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "StatefulSet.Namespace": "orestart", "StatefulSet.Name": "dbmc1"} +2025-08-21T15:27:59Z INFO controllers.OracleRestart All Pods are running {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "StatefulSet.Namespace": "orestart", "StatefulSet.Name": "dbmc1"} +2025-08-21T15:28:14Z INFO controllers.OracleRestart Pod is not ready yet {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "Pod.Name": "dbmc1-0"} +2025-08-21T15:28:44Z INFO controllers.OracleRestart Pod is not ready yet {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "Pod.Name": "dbmc1-0"} +2025-08-21T15:29:14Z INFO controllers.OracleRestart Pod is not ready yet {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "Pod.Name": "dbmc1-0"} +2025-08-21T15:29:44Z INFO controllers.OracleRestart Pod is not ready yet {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "Pod.Name": "dbmc1-0"} +2025-08-21T15:30:14Z INFO controllers.OracleRestart Pod is ready {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "Pod.Name": "dbmc1-0"} +2025-08-21T15:30:19Z INFO controllers.OracleRestart DaemonSet deleted successfully {"DaemonSet.Name": "disk-check-daemonset"} +2025-08-21T15:30:19Z INFO controllers.OracleRestart Oracle Restart Object annotations updated with current spec annotation +2025-08-21T15:30:19Z INFO controllers.OracleRestart Reconcile completed. Requeuing.... +2025-08-21T15:30:31Z INFO controllers.OracleRestart Oracle Restart Object updated with updateOracleRestartInstStatus +2025-08-21T15:30:31Z INFO controllers.OracleRestart Completed Update of Oracle Restart instance status +2025-08-21T15:30:50Z INFO controllers.OracleRestart Updating Oracle Restart instance status with validateoraclerestartdb {"Instance": "oraclerestart-sample"} +2025-08-21T15:30:50Z INFO controllers.OracleRestart Updated Oracle Restart instance status with validateoraclerestartdb {"Instance": "oraclerestart-sample"} +2025-08-21T15:30:50Z INFO controllers.OracleRestart Updated Oracle Restart instance status successfully {"Instance": "oraclerestart-sample"} +2025-08-21T15:30:50Z INFO controllers.OracleRestart Returning from updateReconcileStatus +2025-08-21T15:30:50Z INFO controllers.OracleRestart Reconcile requested +2025-08-21T15:30:50Z INFO controllers.OracleRestart Entering reconcile validation +2025-08-21T15:30:50Z INFO controllers.OracleRestart Completed reconcile validation +2025-08-21T15:30:50Z INFO controllers.OracleRestart Initialized autoUpdate from provided specification {"autoUpdate": false} +2025-08-21T15:30:50Z INFO controllers.OracleRestart Oracle Restart Object annotations updated with current spec annotation +2025-08-21T15:30:50Z INFO controllers.OracleRestart Reconcile completed. Requeuing.... +2025-08-21T15:31:01Z INFO controllers.OracleRestart Oracle Restart Object updated with updateOracleRestartInstStatus +2025-08-21T15:31:01Z INFO controllers.OracleRestart Completed Update of Oracle Restart instance status +2025-08-21T15:31:20Z INFO controllers.OracleRestart Updating Oracle Restart instance status with validateoraclerestartdb {"Instance": "oraclerestart-sample"} +2025-08-21T15:31:20Z INFO controllers.OracleRestart Updated Oracle Restart instance status with validateoraclerestartdb {"Instance": "oraclerestart-sample"} +2025-08-21T15:31:20Z INFO controllers.OracleRestart Updated Oracle Restart instance status successfully {"Instance": "oraclerestart-sample"} +2025-08-21T15:31:20Z INFO controllers.OracleRestart Returning from updateReconcileStatus +2025-08-21T15:31:20Z INFO controllers.OracleRestart Reconcile requested +2025-08-21T15:31:20Z INFO controllers.OracleRestart Entering reconcile validation +2025-08-21T15:31:20Z INFO controllers.OracleRestart Completed reconcile validation +2025-08-21T15:31:20Z INFO controllers.OracleRestart Initialized autoUpdate from provided specification {"autoUpdate": false} +2025-08-21T15:31:20Z INFO controllers.OracleRestart Annotations are already up to date. Skipping update. +2025-08-21T15:31:20Z INFO controllers.OracleRestart Reconcile completed. Requeuing.... +2025-08-21T15:31:32Z INFO controllers.OracleRestart Oracle Restart Object updated with updateOracleRestartInstStatus +2025-08-21T15:31:32Z INFO controllers.OracleRestart Completed Update of Oracle Restart instance status +2025-08-21T15:31:51Z INFO controllers.OracleRestart Updating Oracle Restart instance status with validateoraclerestartdb {"Instance": "oraclerestart-sample"} +2025-08-21T15:31:51Z INFO controllers.OracleRestart Updated Oracle Restart instance status with validateoraclerestartdb {"Instance": "oraclerestart-sample"} +2025-08-21T15:31:51Z INFO controllers.OracleRestart Updated Oracle Restart instance status successfully {"Instance": "oraclerestart-sample"} +2025-08-21T15:31:51Z INFO controllers.OracleRestart Returning from updateReconcileStatus +2025-08-21T15:31:51Z INFO controllers.OracleRestart Reconcile requested +2025-08-21T15:31:51Z INFO controllers.OracleRestart Entering reconcile validation +2025-08-21T15:31:51Z INFO controllers.OracleRestart Completed reconcile validation +2025-08-21T15:31:51Z INFO controllers.OracleRestart Initialized autoUpdate from provided specification {"autoUpdate": false} +2025-08-21T15:31:51Z INFO controllers.OracleRestart Annotations are already up to date. Skipping update. +2025-08-21T15:31:51Z INFO controllers.OracleRestart Reconcile completed. Requeuing.... +2025-08-21T15:32:02Z INFO controllers.OracleRestart Oracle Restart Object updated with updateOracleRestartInstStatus +2025-08-21T15:32:02Z INFO controllers.OracleRestart Completed Update of Oracle Restart instance status + + + +############################################################### +### During this operation, the "pod/dbmc1-0" will be recreated +############################################################### + + + +### Status after the Disks are added with "autoUpdate=true" + +$ kubectl describe oraclerestarts.database.oracle.com/oraclerestart-sample -n orestart +Name: oraclerestart-sample +Namespace: orestart +Labels: +Annotations: OracleRestarts.database.oracle.com/old-spec: + {"instDetails":{"name":"dbmc1","hostSwLocation":"/scratch/orestart/","workerNode":["10.0.10.58"],"nodePortSvc":[{"name":"dbmc1-service-nod... +API Version: database.oracle.com/v4 +Kind: OracleRestart +Metadata: + Creation Timestamp: 2025-08-21T15:03:39Z + Finalizers: + database.oracle.com/oraclerestartfinalizer + Generation: 2 + Resource Version: 225388030 + UID: 29deacda-d177-48d2-8092-30a5adbfcdf1 +Spec: + Asm Storage Details: + Auto Update: false + Disks By Size: + Disk Names: + /dev/disk/by-partlabel/asm-disk1 + /dev/disk/by-partlabel/asm-disk2 + /dev/disk/by-partlabel/asm-disk3 + /dev/disk/by-partlabel/asm-disk4 + Storage Size In Gb: 50 + Config Params: + Cpu Count: 4 + Crs Asm Device List: /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2,/dev/disk/by-partlabel/asm-disk3,/dev/disk/by-partlabel/asm-disk4 + Crs Asm Disk Dg: +DATA + Crs Asm Disk Dg Redundancy: external + Db Base: /u01/app/oracle + Db Char Set: AL32UTF8 + Db Data File Dest Dg: +DATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: +DATA + Db Response File: + Db Sw Zip File: db_home.zip + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Grid Sw Zip File: grid_home.zip + Host Sw Stage Location: /scratch/software/stage + Inventory: /u01/app/oraInventory + Pga Size: 1G + Processes: 2000 + Sga Size: 3G + Sw Mount Location: /u01 + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Image: localhost/oracle/database-orestart:19.3.0-slim + Image Pull Policy: Always + Inst Details: + Host Sw Location: /scratch/orestart/ + Name: dbmc1 + Node Port Svc: + Name: dbmc1-service-nodeport + Svc Type: nodeport + Port Mappings: + Node Port: 30007 + Port: 1521 + Protocol: TCP + Target Port: 1521 + Worker Node: + 10.0.10.58 + Resources: + Limits: + Cpu: 2 + Memory: 16Gi + Requests: + Cpu: 2 + Memory: 16Gi + Security Context: + Sysctls: + Name: kernel.shmall + Value: 2097152 + Name: kernel.sem + Value: 250 32000 100 128 + Name: kernel.shmmax + Value: 8589934592 + Name: kernel.shmmni + Value: 4096 + Service Details: + Name: soepdb + Ssh Key Secret: + Key Mount Location: /mnt/.ssh + Name: ssh-key-secret + Priv Key Secret Name: ssh-privkey + Pub Key Secret Name: ssh-pubkey +Status: + Oracle Restart Nodes: + Name: dbmc1-0 + Node Details: + Instance State: OPEN + Pod State: AVAILABLE + Cluster State: HEALTHY + Mounted Devices: + /dev/disk/by-partlabel/asm-disk1 + /dev/disk/by-partlabel/asm-disk2 + /dev/disk/by-partlabel/asm-disk3 + /dev/disk/by-partlabel/asm-disk4 + State: AVAILABLE + Asm Details: + Diskgroup: + Disks: + /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 + Name: DATA + Redundancy: EXTERN + Conditions: + Last Transition Time: 2025-08-21T15:14:22Z + Message: oracle restart database is in a restricted state: PROVISIONING + Observed Generation: 1 + Reason: LastReconcileCycleQueued + Status: True + Type: ReconcileQueued + Last Transition Time: 2025-08-21T15:32:21Z + Message: no reconcile errors + Observed Generation: 2 + Reason: LastReconcileCycleCompleted + Status: True + Type: ReconcileComplete + Config Params: + Crs Asm Device List: /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2,/dev/disk/by-partlabel/asm-disk3,/dev/disk/by-partlabel/asm-disk4 + Crs Asm Disk Dg: +DATA + Crs Asm Disk Dg Redundancy: external + Db Base: /u01/app/oracle + Db Data File Dest Dg: +DATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: +DATA + Db Response File: + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Inventory: /u01/app/oraInventory + Connect String: dbmc1-0:1521/PORCLCDB + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Db State: OPEN + External Connect String: 129.146.0.149:30007/soepdb + Inst Details: + Name: + Pdb Connect String: dbmc1-0:1521/ORCLPDB + Release Update: 19.28.0.0.0 + Role: PRIMARY + Service Details: + Name: soepdb + Svc State: service soepdb is running + State: AVAILABLE +Events: + + + +[grid@dbmc1-0 ~]$ export ORACLE_SID=+ASM +[grid@dbmc1-0 ~]$ asmcmd lsdsk +Path +/dev/disk/by-partlabel/asm-disk1 +/dev/disk/by-partlabel/asm-disk2 + + +[grid@dbmc1-0 ~]$ ls -lrt /dev/disk/by-partlabel/asm-disk* +brw-rw----. 1 grid asmadmin 8, 81 Aug 21 15:27 /dev/disk/by-partlabel/asm-disk4 +brw-rw----. 1 grid asmadmin 8, 65 Aug 21 15:27 /dev/disk/by-partlabel/asm-disk3 +brw-rw----. 1 grid asmadmin 8, 49 Aug 21 15:34 /dev/disk/by-partlabel/asm-disk2 +brw-rw----. 1 grid asmadmin 8, 33 Aug 21 15:34 /dev/disk/by-partlabel/asm-disk1 \ No newline at end of file diff --git a/docs/oraclerestart/provisioning/asm_addition_autoupdate_true.txt b/docs/oraclerestart/provisioning/asm_addition_autoupdate_true.txt new file mode 100644 index 00000000..1f025fc0 --- /dev/null +++ b/docs/oraclerestart/provisioning/asm_addition_autoupdate_true.txt @@ -0,0 +1,423 @@ +#### Status before the additional ASM Disks additional + +$ kubectl describe oraclerestarts.database.oracle.com/oraclerestart-sample -n orestart +Name: oraclerestart-sample +Namespace: orestart +Labels: +Annotations: OracleRestarts.database.oracle.com/old-spec: + {"instDetails":{"name":"dbmc1","hostSwLocation":"/scratch/orestart/","workerNode":["10.0.10.58"],"nodePortSvc":[{"name":"dbmc1-service-nod... +API Version: database.oracle.com/v4 +Kind: OracleRestart +Metadata: + Creation Timestamp: 2025-08-21T14:24:02Z + Finalizers: + database.oracle.com/oraclerestartfinalizer + Generation: 1 + Resource Version: 225373653 + UID: 9f7225ed-1e97-40f6-bd1d-4efa5acb3007 +Spec: + Asm Storage Details: + Disks By Size: + Disk Names: + /dev/disk/by-partlabel/asm-disk1 + /dev/disk/by-partlabel/asm-disk2 + Storage Size In Gb: 50 + Config Params: + Cpu Count: 4 + Crs Asm Device List: /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 + Crs Asm Disk Dg: +DATA + Crs Asm Disk Dg Redundancy: external + Db Base: /u01/app/oracle + Db Char Set: AL32UTF8 + Db Data File Dest Dg: +DATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: +DATA + Db Response File: + Db Sw Zip File: db_home.zip + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Grid Sw Zip File: grid_home.zip + Host Sw Stage Location: /scratch/software/stage + Inventory: /u01/app/oraInventory + Pga Size: 1G + Processes: 2000 + Sga Size: 3G + Sw Mount Location: /u01 + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Image: localhost/oracle/database-orestart:19.3.0-slim + Image Pull Policy: Always + Inst Details: + Host Sw Location: /scratch/orestart/ + Name: dbmc1 + Node Port Svc: + Name: dbmc1-service-nodeport + Svc Type: nodeport + Port Mappings: + Node Port: 30007 + Port: 1521 + Protocol: TCP + Target Port: 1521 + Worker Node: + 10.0.10.58 + Resources: + Limits: + Cpu: 2 + Memory: 16Gi + Requests: + Cpu: 2 + Memory: 16Gi + Security Context: + Sysctls: + Name: kernel.shmall + Value: 2097152 + Name: kernel.sem + Value: 250 32000 100 128 + Name: kernel.shmmax + Value: 8589934592 + Name: kernel.shmmni + Value: 4096 + Service Details: + Name: soepdb + Ssh Key Secret: + Key Mount Location: /mnt/.ssh + Name: ssh-key-secret + Priv Key Secret Name: ssh-privkey + Pub Key Secret Name: ssh-pubkey +Status: + Oracle Restart Nodes: + Name: dbmc1-0 + Node Details: + Instance State: OPEN + Pod State: AVAILABLE + Cluster State: HEALTHY + Mounted Devices: + /dev/disk/by-partlabel/asm-disk1 + /dev/disk/by-partlabel/asm-disk2 + State: AVAILABLE + Asm Details: + Diskgroup: + Disks: + /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 + Name: DATA + Redundancy: EXTERN + Conditions: + Last Transition Time: 2025-08-21T14:36:35Z + Message: oracle restart database is in a restricted state: PROVISIONING + Observed Generation: 1 + Reason: LastReconcileCycleQueued + Status: True + Type: ReconcileQueued + Last Transition Time: 2025-08-21T14:45:26Z + Message: no reconcile errors + Observed Generation: 1 + Reason: LastReconcileCycleCompleted + Status: True + Type: ReconcileComplete + Config Params: + Crs Asm Device List: /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 + Crs Asm Disk Dg: +DATA + Crs Asm Disk Dg Redundancy: external + Db Base: /u01/app/oracle + Db Data File Dest Dg: +DATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: +DATA + Db Response File: + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Inventory: /u01/app/oraInventory + Connect String: dbmc1-0:1521/PORCLCDB + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Db State: OPEN + External Connect String: 129.146.0.149:30007/soepdb + Inst Details: + Name: + Pdb Connect String: dbmc1-0:1521/ORCLPDB + Release Update: 19.28.0.0.0 + Role: PRIMARY + Service Details: + Name: soepdb + Svc State: service soepdb is running + State: AVAILABLE +Events: + + +[grid@dbmc1-0 ~]$ export ORACLE_SID=+ASM +[grid@dbmc1-0 ~]$ asmcmd lsdsk +Path +/dev/disk/by-partlabel/asm-disk1 +/dev/disk/by-partlabel/asm-disk2 + +[grid@dbmc1-0 ~]$ ls -lrt /dev/disk/by-partlabel/asm-disk* +brw-rw----. 1 grid asmadmin 8, 49 Aug 21 14:46 /dev/disk/by-partlabel/asm-disk2 +brw-rw----. 1 grid asmadmin 8, 33 Aug 21 14:46 /dev/disk/by-partlabel/asm-disk1 + + + +### Apply the file "orestart_prov_asm_disk_addition.yaml" to add two new ASM Disks + +$ kubectl logs -f pod/oracle-database-operator-controller-manager-bc8dd8f68-pwfdk -n oracle-database-operator-system +. +. +2025-08-21T14:49:32Z INFO controllers.OracleRestart Detected Addition of ASM Disks: {"addedAsmDisks": ["/dev/disk/by-partlabel/asm-disk3", "/dev/disk/by-partlabel/asm-disk4"]} +2025-08-21T14:49:32Z INFO controllers.OracleRestart Initialized autoUpdate as true (default) +2025-08-21T14:49:32Z INFO controllers.OracleRestart PV Found {"Instance.Namespace": "orestart", "Instance.Name": "oraclerestart-sample", "dep.Name": "asm-pv-disk-0-oraclerestart-sample"} +2025-08-21T14:49:32Z INFO controllers.OracleRestart PV Found {"Instance.Namespace": "orestart", "Instance.Name": "oraclerestart-sample", "dep.Name": "asm-pv-disk-1-oraclerestart-sample"} +2025-08-21T14:49:32Z INFO controllers.OracleRestart Creating a new PV {"Instance.Namespace": "orestart", "Instance.Name": "oraclerestart-sample", "dep.Name": "asm-pv-disk-2-oraclerestart-sample"} +2025-08-21T14:49:32Z INFO controllers.OracleRestart Creating a new PV {"Instance.Namespace": "orestart", "Instance.Name": "oraclerestart-sample", "dep.Name": "asm-pv-disk-3-oraclerestart-sample"} +2025-08-21T14:49:32Z INFO controllers.OracleRestart Creating a PVC {"Instance.Namespace": "orestart", "Instance.Name": "oraclerestart-sample"} +2025-08-21T14:49:32Z INFO controllers.OracleRestart Creating a PVC {"Instance.Namespace": "orestart", "Instance.Name": "oraclerestart-sample"} +2025-08-21T14:49:32Z INFO controllers.OracleRestart Validate New ASM Disks +2025-08-21T14:49:32Z INFO controllers.OracleRestart Creating DaemonSet: {"desiredDaemonSet.Name": "disk-check-daemonset"} +2025-08-21T14:49:42Z INFO controllers.OracleRestart Provided ASM Disks are valid, proceeding further +2025-08-21T14:49:42Z INFO controllers.OracleRestart Updating existing configmap +2025-08-21T14:49:42Z INFO controllers.OracleRestart Config Map updated successfully with new asm details +2025-08-21T14:49:42Z INFO controllers.OracleRestart Change State to UPDATING +2025-08-21T14:49:42Z INFO controllers.OracleRestart Oracle Restart Object updated with updateOracleRestartInstStatus +2025-08-21T14:49:42Z INFO controllers.OracleRestart StatefulSet spec differs for volume devices, updating StatefulSet (pods may be recreated) {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "StatefulSet.Namespace": "orestart", "StatefulSet.Name": "dbmc1"} +2025-08-21T14:49:42Z INFO controllers.OracleRestart StatefulSet update applied, waiting for pod recreation {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "StatefulSet.Namespace": "orestart", "StatefulSet.Name": "dbmc1"} +2025-08-21T14:49:42Z INFO controllers.OracleRestart Waiting for StatefulSet update to be applied {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "StatefulSet.Namespace": "orestart", "StatefulSet.Name": "dbmc1"} +2025-08-21T14:49:47Z INFO controllers.OracleRestart StatefulSet update is applied successfully {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "StatefulSet.Namespace": "orestart", "StatefulSet.Name": "dbmc1"} +2025-08-21T14:50:47Z INFO controllers.OracleRestart All Pods are running {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "StatefulSet.Namespace": "orestart", "StatefulSet.Name": "dbmc1"} +2025-08-21T14:51:02Z INFO controllers.OracleRestart Pod is not ready yet {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "Pod.Name": "dbmc1-0"} +2025-08-21T14:51:32Z INFO controllers.OracleRestart Pod is not ready yet {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "Pod.Name": "dbmc1-0"} +2025-08-21T14:52:02Z INFO controllers.OracleRestart Pod is not ready yet {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "Pod.Name": "dbmc1-0"} +2025-08-21T14:52:32Z INFO controllers.OracleRestart Pod is not ready yet {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "Pod.Name": "dbmc1-0"} +2025-08-21T14:53:02Z INFO controllers.OracleRestart Pod is ready {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "Pod.Name": "dbmc1-0"} +2025-08-21T14:53:02Z INFO controllers.OracleRestart New disk to be added to CRS ASM device list {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "disk": "/dev/disk/by-partlabel/asm-disk3"} +2025-08-21T14:53:02Z INFO controllers.OracleRestart New disk to be added to CRS ASM device list {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "disk": "/dev/disk/by-partlabel/asm-disk4"} +2025-08-21T14:53:03Z INFO controllers.OracleRestart Executing command to add disk {"Instance.Namespace": "orestart", "Instance.Name": "oraclerestart-sample", "Pod.Name": "dbmc1-0", "Command": "python3 /opt/scripts/startup/scripts/main.py --updateasmdevices=\"diskname=/dev/disk/by-partlabel/asm-disk3;diskgroup=DATA;processtype=addition\""} +2025-08-21T14:53:06Z INFO controllers.OracleRestart Executing command to add disk {"Instance.Namespace": "orestart", "Instance.Name": "oraclerestart-sample", "Pod.Name": "dbmc1-0", "Command": "python3 /opt/scripts/startup/scripts/main.py --updateasmdevices=\"diskname=/dev/disk/by-partlabel/asm-disk4;diskgroup=DATA;processtype=addition\""} +2025-08-21T14:53:16Z INFO controllers.OracleRestart New Disks added to CRS Disks Group {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample"} +2025-08-21T14:53:16Z INFO controllers.OracleRestart DaemonSet deleted successfully {"DaemonSet.Name": "disk-check-daemonset"} +2025-08-21T14:53:16Z INFO OracleRestart-resource validate update {"name": "oraclerestart-sample"} +2025-08-21T14:53:16Z INFO OracleRestart-resource validate create {"name": "oraclerestart-sample"} +2025-08-21T14:53:16Z INFO controllers.OracleRestart Oracle Restart Object annotations updated with current spec annotation +2025-08-21T14:53:16Z INFO controllers.OracleRestart Reconcile completed. Requeuing.... +2025-08-21T14:53:28Z INFO controllers.OracleRestart Oracle Restart Object updated with updateOracleRestartInstStatus +2025-08-21T14:53:28Z INFO controllers.OracleRestart Completed Update of Oracle Restart instance status +2025-08-21T14:53:48Z INFO controllers.OracleRestart Updating Oracle Restart instance status with validateoraclerestartdb {"Instance": "oraclerestart-sample"} +2025-08-21T14:53:48Z INFO controllers.OracleRestart Updated Oracle Restart instance status with validateoraclerestartdb {"Instance": "oraclerestart-sample"} +2025-08-21T14:53:48Z INFO controllers.OracleRestart Updated Oracle Restart instance status successfully {"Instance": "oraclerestart-sample"} +2025-08-21T14:53:48Z INFO controllers.OracleRestart Returning from updateReconcileStatus +2025-08-21T14:53:48Z INFO controllers.OracleRestart Reconcile requested +2025-08-21T14:53:48Z INFO controllers.OracleRestart Entering reconcile validation +2025-08-21T14:53:48Z INFO controllers.OracleRestart Completed reconcile validation +2025-08-21T14:53:48Z INFO controllers.OracleRestart Initialized autoUpdate as true (default) +2025-08-21T14:53:48Z INFO OracleRestart-resource validate update {"name": "oraclerestart-sample"} +2025-08-21T14:53:48Z INFO OracleRestart-resource validate create {"name": "oraclerestart-sample"} +2025-08-21T14:53:48Z INFO controllers.OracleRestart Oracle Restart Object annotations updated with current spec annotation +2025-08-21T14:53:48Z INFO controllers.OracleRestart Reconcile completed. Requeuing.... +2025-08-21T14:53:59Z INFO controllers.OracleRestart Oracle Restart Object updated with updateOracleRestartInstStatus +2025-08-21T14:53:59Z INFO controllers.OracleRestart Completed Update of Oracle Restart instance status +2025-08-21T14:54:19Z INFO controllers.OracleRestart Updating Oracle Restart instance status with validateoraclerestartdb {"Instance": "oraclerestart-sample"} +2025-08-21T14:54:19Z INFO controllers.OracleRestart Updated Oracle Restart instance status with validateoraclerestartdb {"Instance": "oraclerestart-sample"} +2025-08-21T14:54:19Z INFO controllers.OracleRestart Updated Oracle Restart instance status successfully {"Instance": "oraclerestart-sample"} +2025-08-21T14:54:19Z INFO controllers.OracleRestart Returning from updateReconcileStatus +2025-08-21T14:54:19Z INFO controllers.OracleRestart Reconcile requested +2025-08-21T14:54:19Z INFO controllers.OracleRestart Entering reconcile validation +2025-08-21T14:54:19Z INFO controllers.OracleRestart Completed reconcile validation +2025-08-21T14:54:19Z INFO controllers.OracleRestart Initialized autoUpdate as true (default) +2025-08-21T14:54:19Z INFO controllers.OracleRestart Annotations are already up to date. Skipping update. +2025-08-21T14:54:19Z INFO controllers.OracleRestart Reconcile completed. Requeuing.... +2025-08-21T14:54:31Z INFO controllers.OracleRestart Oracle Restart Object updated with updateOracleRestartInstStatus +2025-08-21T14:54:31Z INFO controllers.OracleRestart Completed Update of Oracle Restart instance status + + + +############################################################### +### During this operation, the "pod/dbmc1-0" will be recreated +############################################################### + + + +### Status after the Disks are added with "autoUpdate=true" + +$ kubectl describe oraclerestarts.database.oracle.com/oraclerestart-sample -n orestart +Name: oraclerestart-sample +Namespace: orestart +Labels: +Annotations: OracleRestarts.database.oracle.com/old-spec: + {"instDetails":{"name":"dbmc1","hostSwLocation":"/scratch/orestart/","workerNode":["10.0.10.58"],"nodePortSvc":[{"name":"dbmc1-service-nod... +API Version: database.oracle.com/v4 +Kind: OracleRestart +Metadata: + Creation Timestamp: 2025-08-21T14:24:02Z + Finalizers: + database.oracle.com/oraclerestartfinalizer + Generation: 2 + Resource Version: 225377019 + UID: 9f7225ed-1e97-40f6-bd1d-4efa5acb3007 +Spec: + Asm Storage Details: + Auto Update: true + Disks By Size: + Disk Names: + /dev/disk/by-partlabel/asm-disk1 + /dev/disk/by-partlabel/asm-disk2 + /dev/disk/by-partlabel/asm-disk3 + /dev/disk/by-partlabel/asm-disk4 + Storage Size In Gb: 50 + Config Params: + Cpu Count: 4 + Crs Asm Device List: /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2,/dev/disk/by-partlabel/asm-disk3,/dev/disk/by-partlabel/asm-disk4 + Crs Asm Disk Dg: +DATA + Crs Asm Disk Dg Redundancy: external + Db Base: /u01/app/oracle + Db Char Set: AL32UTF8 + Db Data File Dest Dg: +DATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: +DATA + Db Response File: + Db Sw Zip File: db_home.zip + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Grid Sw Zip File: grid_home.zip + Host Sw Stage Location: /scratch/software/stage + Inventory: /u01/app/oraInventory + Pga Size: 1G + Processes: 2000 + Sga Size: 3G + Sw Mount Location: /u01 + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Image: localhost/oracle/database-orestart:19.3.0-slim + Image Pull Policy: Always + Inst Details: + Host Sw Location: /scratch/orestart/ + Name: dbmc1 + Node Port Svc: + Name: dbmc1-service-nodeport + Svc Type: nodeport + Port Mappings: + Node Port: 30007 + Port: 1521 + Protocol: TCP + Target Port: 1521 + Worker Node: + 10.0.10.58 + Resources: + Limits: + Cpu: 2 + Memory: 16Gi + Requests: + Cpu: 2 + Memory: 16Gi + Security Context: + Sysctls: + Name: kernel.shmall + Value: 2097152 + Name: kernel.sem + Value: 250 32000 100 128 + Name: kernel.shmmax + Value: 8589934592 + Name: kernel.shmmni + Value: 4096 + Service Details: + Name: soepdb + Ssh Key Secret: + Key Mount Location: /mnt/.ssh + Name: ssh-key-secret + Priv Key Secret Name: ssh-privkey + Pub Key Secret Name: ssh-pubkey +Status: + Oracle Restart Nodes: + Name: dbmc1-0 + Node Details: + Instance State: OPEN + Pod State: AVAILABLE + Cluster State: HEALTHY + Mounted Devices: + /dev/disk/by-partlabel/asm-disk1 + /dev/disk/by-partlabel/asm-disk2 + /dev/disk/by-partlabel/asm-disk3 + /dev/disk/by-partlabel/asm-disk4 + State: AVAILABLE + Asm Details: + Diskgroup: + Disks: + /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2,/dev/disk/by-partlabel/asm-disk3,/dev/disk/by-partlabel/asm-disk4 + Name: DATA + Redundancy: EXTERN + Conditions: + Last Transition Time: 2025-08-21T14:36:35Z + Message: oracle restart database is in a restricted state: PROVISIONING + Observed Generation: 1 + Reason: LastReconcileCycleQueued + Status: True + Type: ReconcileQueued + Last Transition Time: 2025-08-21T14:56:23Z + Message: no reconcile errors + Observed Generation: 2 + Reason: LastReconcileCycleCompleted + Status: True + Type: ReconcileComplete + Config Params: + Crs Asm Device List: /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2,/dev/disk/by-partlabel/asm-disk3,/dev/disk/by-partlabel/asm-disk4 + Crs Asm Disk Dg: +DATA + Crs Asm Disk Dg Redundancy: external + Db Base: /u01/app/oracle + Db Data File Dest Dg: +DATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: +DATA + Db Response File: + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Inventory: /u01/app/oraInventory + Connect String: dbmc1-0:1521/PORCLCDB + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Db State: OPEN + External Connect String: 129.146.0.149:30007/soepdb + Inst Details: + Name: + Pdb Connect String: dbmc1-0:1521/ORCLPDB + Release Update: 19.28.0.0.0 + Role: PRIMARY + Service Details: + Name: soepdb + Svc State: service soepdb is running + State: AVAILABLE +Events: + + + +[grid@dbmc1-0 ~]$ export ORACLE_SID=+ASM +[grid@dbmc1-0 ~]$ asmcmd lsdsk +Path +/dev/disk/by-partlabel/asm-disk1 +/dev/disk/by-partlabel/asm-disk2 +/dev/disk/by-partlabel/asm-disk3 +/dev/disk/by-partlabel/asm-disk4 + + +[grid@dbmc1-0 ~]$ ls -lrt /dev/disk/by-partlabel/asm-disk* +brw-rw----. 1 grid asmadmin 8, 81 Aug 21 14:57 /dev/disk/by-partlabel/asm-disk4 +brw-rw----. 1 grid asmadmin 8, 65 Aug 21 14:57 /dev/disk/by-partlabel/asm-disk3 +brw-rw----. 1 grid asmadmin 8, 33 Aug 21 14:57 /dev/disk/by-partlabel/asm-disk1 +brw-rw----. 1 grid asmadmin 8, 49 Aug 21 14:57 /dev/disk/by-partlabel/asm-disk2 \ No newline at end of file diff --git a/docs/oraclerestart/provisioning/asm_disk_deletion.txt b/docs/oraclerestart/provisioning/asm_disk_deletion.txt new file mode 100644 index 00000000..7b0929c6 --- /dev/null +++ b/docs/oraclerestart/provisioning/asm_disk_deletion.txt @@ -0,0 +1,453 @@ +#### Status before the ASM Disk deletion + +$ kubectl describe oraclerestarts.database.oracle.com/oraclerestart-sample -n orestart +Name: oraclerestart-sample +Namespace: orestart +Labels: +Annotations: OracleRestarts.database.oracle.com/old-spec: + {"instDetails":{"name":"dbmc1","hostSwLocation":"/scratch/orestart/","workerNode":["10.0.10.58"],"nodePortSvc":[{"name":"dbmc1-service-nod... +API Version: database.oracle.com/v4 +Kind: OracleRestart +Metadata: + Creation Timestamp: 2025-08-21T15:36:59Z + Finalizers: + database.oracle.com/oraclerestartfinalizer + Generation: 1 + Resource Version: 225402792 + UID: 872a653b-2751-475d-848e-b0d2af5504b6 +Spec: + Asm Storage Details: + Disks By Size: + Disk Names: + /dev/disk/by-partlabel/asm-disk1 + /dev/disk/by-partlabel/asm-disk2 + Storage Size In Gb: 50 + Config Params: + Cpu Count: 4 + Crs Asm Device List: /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 + Crs Asm Disk Dg: +DATA + Crs Asm Disk Dg Redundancy: external + Db Base: /u01/app/oracle + Db Char Set: AL32UTF8 + Db Data File Dest Dg: +DATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: +DATA + Db Response File: + Db Sw Zip File: db_home.zip + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Grid Sw Zip File: grid_home.zip + Host Sw Stage Location: /scratch/software/stage + Inventory: /u01/app/oraInventory + Pga Size: 1G + Processes: 2000 + Sga Size: 3G + Sw Mount Location: /u01 + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Image: localhost/oracle/database-orestart:19.3.0-slim + Image Pull Policy: Always + Inst Details: + Host Sw Location: /scratch/orestart/ + Name: dbmc1 + Node Port Svc: + Name: dbmc1-service-nodeport + Svc Type: nodeport + Port Mappings: + Node Port: 30007 + Port: 1521 + Protocol: TCP + Target Port: 1521 + Worker Node: + 10.0.10.58 + Resources: + Limits: + Cpu: 2 + Memory: 16Gi + Requests: + Cpu: 2 + Memory: 16Gi + Security Context: + Sysctls: + Name: kernel.shmall + Value: 2097152 + Name: kernel.sem + Value: 250 32000 100 128 + Name: kernel.shmmax + Value: 8589934592 + Name: kernel.shmmni + Value: 4096 + Service Details: + Name: soepdb + Ssh Key Secret: + Key Mount Location: /mnt/.ssh + Name: ssh-key-secret + Priv Key Secret Name: ssh-privkey + Pub Key Secret Name: ssh-pubkey +Status: + Oracle Restart Nodes: + Name: dbmc1-0 + Node Details: + Instance State: OPEN + Pod State: AVAILABLE + Cluster State: HEALTHY + Mounted Devices: + /dev/disk/by-partlabel/asm-disk1 + /dev/disk/by-partlabel/asm-disk2 + State: AVAILABLE + Asm Details: + Diskgroup: + Disks: + /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 + Name: DATA + Redundancy: EXTERN + Conditions: + Last Transition Time: 2025-08-21T15:49:46Z + Message: oracle restart database is in a restricted state: PROVISIONING + Observed Generation: 1 + Reason: LastReconcileCycleQueued + Status: True + Type: ReconcileQueued + Last Transition Time: 2025-08-21T16:21:04Z + Message: no reconcile errors + Observed Generation: 1 + Reason: LastReconcileCycleCompleted + Status: True + Type: ReconcileComplete + Config Params: + Crs Asm Device List: /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 + Crs Asm Disk Dg: +DATA + Crs Asm Disk Dg Redundancy: external + Db Base: /u01/app/oracle + Db Data File Dest Dg: +DATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: +DATA + Db Response File: + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Inventory: /u01/app/oraInventory + Connect String: dbmc1-0:1521/PORCLCDB + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Db State: OPEN + External Connect String: 129.146.0.149:30007/soepdb + Inst Details: + Name: + Pdb Connect String: dbmc1-0:1521/ORCLPDB + Release Update: 19.28.0.0.0 + Role: PRIMARY + Service Details: + Name: soepdb + Svc State: service soepdb is running + State: AVAILABLE +Events: + + +[grid@dbmc1-0 ~]$ export ORACLE_SID=+ASM +[grid@dbmc1-0 ~]$ asmcmd lsdsk +Path +/dev/disk/by-partlabel/asm-disk1 +/dev/disk/by-partlabel/asm-disk2 + +[grid@dbmc1-0 ~]$ ls -lrt /dev/disk/by-partlabel/asm-disk* +brw-rw----. 1 grid asmadmin 8, 33 Aug 21 16:21 /dev/disk/by-partlabel/asm-disk2 +brw-rw----. 1 grid asmadmin 8, 49 Aug 21 16:21 /dev/disk/by-partlabel/asm-disk1 + + +### Drop the disk the ASM Level using "ALTER DISKGROUP DROP DISK" command as below: + +[grid@dbmc1-0 ~]$ sqlplus "/ as sysasm" + +SQL*Plus: Release 19.0.0.0.0 - Production on Thu Aug 21 16:23:43 2025 +Version 19.28.0.0.0 + +Copyright (c) 1982, 2025, Oracle. All rights reserved. + + +Connected to: +Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production +Version 19.28.0.0.0 + +SQL> set lines 200 +SQL> col path format a50 +SQL> select group_number, disk_number, name, total_mb, free_mb, path from v$asm_disk; + +GROUP_NUMBER DISK_NUMBER NAME TOTAL_MB FREE_MB PATH +------------ ----------- ------------------------------ ---------- ---------- -------------------------------------------------- + 1 1 DATA_0001 51196 46452 /dev/disk/by-partlabel/asm-disk2 + 1 0 DATA_0000 51196 46464 /dev/disk/by-partlabel/asm-disk1 + +SQL> ALTER DISKGROUP DATA DROP DISK 'DATA_0001' REBALANCE POWER 10; + +Diskgroup altered. + +SQL> select EST_MINUTES,EST_WORK,ACTUAL,SOFAR from v$asm_operation; + +EST_MINUTES EST_WORK ACTUAL SOFAR +----------- ---------- ---------- ---------- + 0 0 10 0 + 0 1183 10 122 + 0 0 10 0 + +SQL> select EST_MINUTES,EST_WORK,ACTUAL,SOFAR from v$asm_operation; + +EST_MINUTES EST_WORK ACTUAL SOFAR +----------- ---------- ---------- ---------- + 0 0 10 0 + 0 1183 10 723 + 0 0 10 0 + +SQL> select EST_MINUTES,EST_WORK,ACTUAL,SOFAR from v$asm_operation; + +no rows selected + +SQL> select group_number, disk_number, name, total_mb, free_mb, path from v$asm_disk; + +GROUP_NUMBER DISK_NUMBER NAME TOTAL_MB FREE_MB PATH +------------ ----------- ------------------------------ ---------- ---------- -------------------------------------------------- + 0 0 0 0 /dev/disk/by-partlabel/asm-disk2 + 1 0 DATA_0000 51196 41732 /dev/disk/by-partlabel/asm-disk1 + + +### Apply the file "orestart_prov_asm_disk_deletion.yaml" to delete an exising ASM Disk + +$ kubectl logs -f pod/oracle-database-operator-controller-manager-bc8dd8f68-pwfdk -n oracle-database-operator-system +. +. +2025-08-21T16:31:12Z INFO controllers.OracleRestart Detected Removal of ASM Disks: {"removedAsmDisks": ["/dev/disk/by-partlabel/asm-disk2"]} +2025-08-21T16:31:12Z INFO controllers.OracleRestart Initialized autoUpdate as true (default) +2025-08-21T16:31:12Z INFO controllers.OracleRestart PV Found {"Instance.Namespace": "orestart", "Instance.Name": "oraclerestart-sample", "dep.Name": "asm-pv-disk-0-oraclerestart-sample"} +2025-08-21T16:31:12Z INFO controllers.OracleRestart Updating existing configmap +2025-08-21T16:31:12Z INFO controllers.OracleRestart Config Map updated successfully with new asm details +2025-08-21T16:31:15Z INFO controllers.OracleRestart Change State to UPDATING +2025-08-21T16:31:15Z INFO controllers.OracleRestart Oracle Restart Object updated with updateOracleRestartInstStatus +2025-08-21T16:31:15Z INFO controllers.OracleRestart StatefulSet spec differs for volume devices, updating StatefulSet (pods may be recreated) {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "StatefulSet.Namespace": "orestart", "StatefulSet.Name": "dbmc1"} +2025-08-21T16:31:15Z INFO controllers.OracleRestart StatefulSet update applied, waiting for pod recreation {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "StatefulSet.Namespace": "orestart", "StatefulSet.Name": "dbmc1"} +2025-08-21T16:31:15Z INFO controllers.OracleRestart StatefulSet update is applied successfully {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "StatefulSet.Namespace": "orestart", "StatefulSet.Name": "dbmc1"} + + +2025-08-21T16:32:15Z INFO controllers.OracleRestart All Pods are running {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "StatefulSet.Namespace": "orestart", "StatefulSet.Name": "dbmc1"} +2025-08-21T16:32:30Z INFO controllers.OracleRestart Pod is not ready yet {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "Pod.Name": "dbmc1-0"} +2025-08-21T16:33:00Z INFO controllers.OracleRestart Pod is not ready yet {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "Pod.Name": "dbmc1-0"} +2025-08-21T16:33:30Z INFO controllers.OracleRestart Pod is not ready yet {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "Pod.Name": "dbmc1-0"} +2025-08-21T16:34:00Z INFO controllers.OracleRestart Pod is not ready yet {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "Pod.Name": "dbmc1-0"} +2025-08-21T16:34:30Z INFO controllers.OracleRestart Pod is ready {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "Pod.Name": "dbmc1-0"} +2025-08-21T16:34:30Z INFO controllers.OracleRestart Successfully deleted PVC {"PVC.Name": "asm-pvc-disk-1-oraclerestart-sample"} +2025-08-21T16:34:30Z INFO controllers.OracleRestart Successfully deleted PV {"PV.Name": "asm-pv-disk-1-oraclerestart-sample"} +2025-08-21T16:34:30Z INFO OracleRestart-resource validate update {"name": "oraclerestart-sample"} +2025-08-21T16:34:30Z INFO OracleRestart-resource validate create {"name": "oraclerestart-sample"} +2025-08-21T16:34:30Z INFO controllers.OracleRestart Oracle Restart Object annotations updated with current spec annotation +2025-08-21T16:34:30Z INFO controllers.OracleRestart Reconcile completed. Requeuing.... +2025-08-21T16:34:42Z INFO controllers.OracleRestart Oracle Restart Object updated with updateOracleRestartInstStatus +2025-08-21T16:34:42Z INFO controllers.OracleRestart Completed Update of Oracle Restart instance status +2025-08-21T16:35:01Z INFO controllers.OracleRestart Updating Oracle Restart instance status with validateoraclerestartdb {"Instance": "oraclerestart-sample"} +2025-08-21T16:35:01Z INFO controllers.OracleRestart Updated Oracle Restart instance status with validateoraclerestartdb {"Instance": "oraclerestart-sample"} +2025-08-21T16:35:01Z INFO controllers.OracleRestart Updated Oracle Restart instance status successfully {"Instance": "oraclerestart-sample"} +2025-08-21T16:35:01Z INFO controllers.OracleRestart Returning from updateReconcileStatus +2025-08-21T16:35:01Z INFO controllers.OracleRestart Reconcile requested +2025-08-21T16:35:01Z INFO controllers.OracleRestart Entering reconcile validation +2025-08-21T16:35:01Z INFO controllers.OracleRestart Completed reconcile validation +2025-08-21T16:35:01Z INFO controllers.OracleRestart Initialized autoUpdate as true (default) +2025-08-21T16:35:01Z INFO OracleRestart-resource validate update {"name": "oraclerestart-sample"} +2025-08-21T16:35:01Z INFO OracleRestart-resource validate create {"name": "oraclerestart-sample"} +2025-08-21T16:35:01Z INFO controllers.OracleRestart Oracle Restart Object annotations updated with current spec annotation +2025-08-21T16:35:01Z INFO controllers.OracleRestart Reconcile completed. Requeuing.... +2025-08-21T16:35:12Z INFO controllers.OracleRestart Oracle Restart Object updated with updateOracleRestartInstStatus +2025-08-21T16:35:12Z INFO controllers.OracleRestart Completed Update of Oracle Restart instance status +2025-08-21T16:35:31Z INFO controllers.OracleRestart Updating Oracle Restart instance status with validateoraclerestartdb {"Instance": "oraclerestart-sample"} +2025-08-21T16:35:31Z INFO controllers.OracleRestart Updated Oracle Restart instance status with validateoraclerestartdb {"Instance": "oraclerestart-sample"} +2025-08-21T16:35:31Z INFO controllers.OracleRestart Updated Oracle Restart instance status successfully {"Instance": "oraclerestart-sample"} +2025-08-21T16:35:31Z INFO controllers.OracleRestart Returning from updateReconcileStatus +2025-08-21T16:35:31Z INFO controllers.OracleRestart Reconcile requested +2025-08-21T16:35:31Z INFO controllers.OracleRestart Entering reconcile validation +2025-08-21T16:35:31Z INFO controllers.OracleRestart Completed reconcile validation +2025-08-21T16:35:31Z INFO controllers.OracleRestart Initialized autoUpdate as true (default) +2025-08-21T16:35:31Z INFO controllers.OracleRestart Annotations are already up to date. Skipping update. +2025-08-21T16:35:31Z INFO controllers.OracleRestart Reconcile completed. Requeuing.... +2025-08-21T16:35:42Z INFO controllers.OracleRestart Oracle Restart Object updated with updateOracleRestartInstStatus +2025-08-21T16:35:42Z INFO controllers.OracleRestart Completed Update of Oracle Restart instance status + + + +############################################################### +### During this operation, the "pod/dbmc1-0" will be recreated +############################################################### + + + +### Status after the Disk removed: + +$ kubectl describe oraclerestarts.database.oracle.com/oraclerestart-sample -n orestart +Name: oraclerestart-sample +Namespace: orestart +Labels: +Annotations: OracleRestarts.database.oracle.com/old-spec: + {"instDetails":{"name":"dbmc1","hostSwLocation":"/scratch/orestart/","workerNode":["10.0.10.58"],"nodePortSvc":[{"name":"dbmc1-service-nod... +API Version: database.oracle.com/v4 +Kind: OracleRestart +Metadata: + Creation Timestamp: 2025-08-21T15:36:59Z + Finalizers: + database.oracle.com/oraclerestartfinalizer + Generation: 2 + Resource Version: 225407022 + UID: 872a653b-2751-475d-848e-b0d2af5504b6 +Spec: + Asm Storage Details: + Disks By Size: + Disk Names: + /dev/disk/by-partlabel/asm-disk1 + Storage Size In Gb: 50 + Config Params: + Cpu Count: 4 + Crs Asm Device List: /dev/disk/by-partlabel/asm-disk1 + Crs Asm Disk Dg: +DATA + Crs Asm Disk Dg Redundancy: external + Db Base: /u01/app/oracle + Db Char Set: AL32UTF8 + Db Data File Dest Dg: +DATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: +DATA + Db Response File: + Db Sw Zip File: db_home.zip + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Grid Sw Zip File: grid_home.zip + Host Sw Stage Location: /scratch/software/stage + Inventory: /u01/app/oraInventory + Pga Size: 1G + Processes: 2000 + Sga Size: 3G + Sw Mount Location: /u01 + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Image: localhost/oracle/database-orestart:19.3.0-slim + Image Pull Policy: Always + Inst Details: + Host Sw Location: /scratch/orestart/ + Name: dbmc1 + Node Port Svc: + Name: dbmc1-service-nodeport + Svc Type: nodeport + Port Mappings: + Node Port: 30007 + Port: 1521 + Protocol: TCP + Target Port: 1521 + Worker Node: + 10.0.10.58 + Resources: + Limits: + Cpu: 2 + Memory: 16Gi + Requests: + Cpu: 2 + Memory: 16Gi + Security Context: + Sysctls: + Name: kernel.shmall + Value: 2097152 + Name: kernel.sem + Value: 250 32000 100 128 + Name: kernel.shmmax + Value: 8589934592 + Name: kernel.shmmni + Value: 4096 + Service Details: + Name: soepdb + Ssh Key Secret: + Key Mount Location: /mnt/.ssh + Name: ssh-key-secret + Priv Key Secret Name: ssh-privkey + Pub Key Secret Name: ssh-pubkey +Status: + Oracle Restart Nodes: + Name: dbmc1-0 + Node Details: + Instance State: OPEN + Pod State: PODAVAILABLE + Cluster State: HEALTHY + Mounted Devices: + /dev/disk/by-partlabel/asm-disk1 + State: AVAILABLE + Asm Details: + Diskgroup: + Disks: + /dev/disk/by-partlabel/asm-disk1 + Name: DATA + Redundancy: EXTERN + Conditions: + Last Transition Time: 2025-08-21T15:49:46Z + Message: oracle restart database is in a restricted state: PROVISIONING + Observed Generation: 1 + Reason: LastReconcileCycleQueued + Status: True + Type: ReconcileQueued + Last Transition Time: 2025-08-21T16:35:01Z + Message: no reconcile errors + Observed Generation: 2 + Reason: LastReconcileCycleCompleted + Status: True + Type: ReconcileComplete + Config Params: + Crs Asm Device List: /dev/disk/by-partlabel/asm-disk1 + Crs Asm Disk Dg: +DATA + Crs Asm Disk Dg Redundancy: external + Db Base: /u01/app/oracle + Db Data File Dest Dg: +DATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: +DATA + Db Response File: + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Inventory: /u01/app/oraInventory + Connect String: dbmc1-0:1521/PORCLCDB + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Db State: OPEN + External Connect String: 129.146.0.149:30007/soepdb + Inst Details: + Name: + Pdb Connect String: dbmc1-0:1521/ORCLPDB + Release Update: 19.28.0.0.0 + Role: PRIMARY + Service Details: + Name: soepdb + Svc State: service soepdb is running + State: AVAILABLE +Events: + + + +[grid@dbmc1-0 ~]$ export ORACLE_SID=+ASM +[grid@dbmc1-0 ~]$ asmcmd lsdsk +Path +/dev/disk/by-partlabel/asm-disk1 + + +[grid@dbmc1-0 ~]$ ls -lrt /dev/disk/by-partlabel/asm-disk* +brw-rw----. 1 grid asmadmin 8, 49 Aug 21 16:36 /dev/disk/by-partlabel/asm-disk1 \ No newline at end of file diff --git a/docs/oraclerestart/provisioning/asm_disk_deletion1.txt b/docs/oraclerestart/provisioning/asm_disk_deletion1.txt new file mode 100644 index 00000000..aa2c6631 --- /dev/null +++ b/docs/oraclerestart/provisioning/asm_disk_deletion1.txt @@ -0,0 +1,619 @@ +#### Status before the ASM Disk deletion + +$ kubectl describe oraclerestarts.database.oracle.com/oraclerestart-sample -n orestart +Name: oraclerestart-sample +Namespace: orestart +Labels: +Annotations: OracleRestarts.database.oracle.com/old-spec: + {"instDetails":{"name":"dbmc1","hostSwLocation":"/scratch/orestart/","workerNode":["10.0.10.58"],"nodePortSvc":[{"name":"dbmc1-service-nod... +API Version: database.oracle.com/v4 +Kind: OracleRestart +Metadata: + Creation Timestamp: 2025-08-22T05:09:46Z + Finalizers: + database.oracle.com/oraclerestartfinalizer + Generation: 1 + Resource Version: 226218859 + UID: e3378459-8aa7-4c05-98b2-9c03a44b3a57 +Spec: + Asm Storage Details: + Disks By Size: + Disk Names: + /dev/disk/by-partlabel/asm-disk1 + /dev/disk/by-partlabel/asm-disk2 + Storage Size In Gb: 50 + Config Params: + Cpu Count: 4 + Crs Asm Device List: /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 + Crs Asm Disk Dg: +DATA + Crs Asm Disk Dg Redundancy: external + Db Base: /u01/app/oracle + Db Char Set: AL32UTF8 + Db Data File Dest Dg: +DATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: +DATA + Db Response File: + Db Sw Zip File: db_home.zip + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Grid Sw Zip File: grid_home.zip + Host Sw Stage Location: /scratch/software/stage + Inventory: /u01/app/oraInventory + Pga Size: 1G + Processes: 2000 + Sga Size: 3G + Sw Mount Location: /u01 + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Image: localhost/oracle/database-orestart:19.3.0-slim + Image Pull Policy: Always + Inst Details: + Host Sw Location: /scratch/orestart/ + Name: dbmc1 + Node Port Svc: + Name: dbmc1-service-nodeport + Svc Type: nodeport + Port Mappings: + Node Port: 30007 + Port: 1521 + Protocol: TCP + Target Port: 1521 + Worker Node: + 10.0.10.58 + Resources: + Limits: + Cpu: 2 + Memory: 16Gi + Requests: + Cpu: 2 + Memory: 16Gi + Security Context: + Sysctls: + Name: kernel.shmall + Value: 2097152 + Name: kernel.sem + Value: 250 32000 100 128 + Name: kernel.shmmax + Value: 8589934592 + Name: kernel.shmmni + Value: 4096 + Service Details: + Name: soepdb + Ssh Key Secret: + Key Mount Location: /mnt/.ssh + Name: ssh-key-secret + Priv Key Secret Name: ssh-privkey + Pub Key Secret Name: ssh-pubkey +Status: + Oracle Restart Nodes: + Name: dbmc1-0 + Node Details: + Instance State: OPEN + Pod State: AVAILABLE + Cluster State: HEALTHY + Mounted Devices: + /dev/disk/by-partlabel/asm-disk1 + /dev/disk/by-partlabel/asm-disk2 + State: AVAILABLE + Asm Details: + Diskgroup: + Disks: + /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 + Name: DATA + Redundancy: EXTERN + Conditions: + Last Transition Time: 2025-08-22T05:22:19Z + Message: oracle restart database is in a restricted state: PROVISIONING + Observed Generation: 1 + Reason: LastReconcileCycleQueued + Status: True + Type: ReconcileQueued + Last Transition Time: 2025-08-23T13:45:49Z + Message: no reconcile errors + Observed Generation: 1 + Reason: LastReconcileCycleCompleted + Status: True + Type: ReconcileComplete + Config Params: + Crs Asm Device List: /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 + Crs Asm Disk Dg: +DATA + Crs Asm Disk Dg Redundancy: external + Db Base: /u01/app/oracle + Db Data File Dest Dg: +DATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: +DATA + Db Response File: + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Inventory: /u01/app/oraInventory + Connect String: dbmc1-0:1521/PORCLCDB + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Db State: OPEN + External Connect String: 129.146.0.149:30007/soepdb + Inst Details: + Name: + Pdb Connect String: dbmc1-0:1521/ORCLPDB + Release Update: 19.28.0.0.0 + Role: PRIMARY + Service Details: + Name: soepdb + Svc State: service soepdb is running + State: AVAILABLE +Events: + + +[grid@dbmc1-0 ~]$ export ORACLE_SID=+ASM +[grid@dbmc1-0 ~]$ asmcmd lsdsk +Path +/dev/disk/by-partlabel/asm-disk1 +/dev/disk/by-partlabel/asm-disk2 + +[grid@dbmc1-0 ~]$ ls -lrt /dev/disk/by-partlabel/asm-disk* +brw-rw----. 1 grid asmadmin 8, 33 Aug 22 11:17 /dev/disk/by-partlabel/asm-disk2 +brw-rw----. 1 grid asmadmin 8, 49 Aug 22 11:17 /dev/disk/by-partlabel/asm-disk1 + + +### Apply the file "orestart_prov_asm_disk_deletion.yaml" to delete an exising ASM Disk +### When the above .yaml file is applied, Operator will check if the disk is in use before deletion +### As the disk is not deleted at the ASM level and rebalance has NOT been done, the Operator will be waiting for the disk to be deleted at the ASM Level + + +$ kubectl logs -f pod/oracle-database-operator-controller-manager-bc8dd8f68-pwfdk -n oracle-database-operator-system +. +. +2025-08-23T13:48:51Z INFO controllers.OracleRestart Detected Removal of ASM Disks: {"removedAsmDisks": ["/dev/disk/by-partlabel/asm-disk2"]} +2025-08-23T13:48:51Z INFO controllers.OracleRestart Initialized autoUpdate as true (default) +2025-08-23T13:48:51Z INFO controllers.OracleRestart PV Found {"Instance.Namespace": "orestart", "Instance.Name": "oraclerestart-sample", "dep.Name": "asm-pv-disk-0-oraclerestart-sample"} +2025-08-23T13:48:51Z INFO controllers.OracleRestart Updating existing configmap +2025-08-23T13:48:51Z INFO controllers.OracleRestart Config Map updated successfully with new asm details +2025-08-23T13:48:54Z INFO controllers.OracleRestart Disk is in use and cannot be removed. Must be manually removed before proceeding {"disk": "/dev/disk/by-partlabel/asm-disk2", "diskgroup": "DATA"} +2025-08-23T13:49:05Z INFO controllers.OracleRestart Oracle Restart Object updated with updateOracleRestartInstStatus +2025-08-23T13:49:05Z INFO controllers.OracleRestart Completed Update of Oracle Restart instance status +2025-08-23T13:49:24Z INFO controllers.OracleRestart Updating Oracle Restart instance status with validateoraclerestartdb {"Instance": "oraclerestart-sample"} +2025-08-23T13:49:24Z INFO controllers.OracleRestart Updated Oracle Restart instance status with validateoraclerestartdb {"Instance": "oraclerestart-sample"} +2025-08-23T13:49:24Z ERROR Reconciler error {"controller": "oraclerestart", "controllerGroup": "database.oracle.com", "controllerKind": "OracleRestart", "OracleRestart": {"name":"oraclerestart-sample","namespace":"orestart"}, "namespace": "orestart", "name": "oraclerestart-sample", "reconcileID": "ae8e9e7d-ee7c-4cae-b19a-63c57471a0d7", "error": "disk '/dev/disk/by-partlabel/asm-disk2' is part of diskgroup 'DATA' and must be manually removed before proceeding"} +sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).reconcileHandler + /gomod-cache/sigs.k8s.io/controller-runtime@v0.21.0/pkg/internal/controller/controller.go:353 +sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).processNextWorkItem + /gomod-cache/sigs.k8s.io/controller-runtime@v0.21.0/pkg/internal/controller/controller.go:300 +sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).Start.func2.1 + /gomod-cache/sigs.k8s.io/controller-runtime@v0.21.0/pkg/internal/controller/controller.go:202 +2025-08-23T13:49:24Z INFO controllers.OracleRestart Reconcile requested +2025-08-23T13:49:24Z INFO controllers.OracleRestart Entering reconcile validation +2025-08-23T13:49:24Z INFO controllers.OracleRestart Completed reconcile validation +2025-08-23T13:49:24Z INFO controllers.OracleRestart Detected Removal of ASM Disks: {"removedAsmDisks": ["/dev/disk/by-partlabel/asm-disk2"]} +2025-08-23T13:49:24Z INFO controllers.OracleRestart Initialized autoUpdate as true (default) +2025-08-23T13:49:24Z INFO controllers.OracleRestart PV Found {"Instance.Namespace": "orestart", "Instance.Name": "oraclerestart-sample", "dep.Name": "asm-pv-disk-0-oraclerestart-sample"} +2025-08-23T13:49:24Z INFO controllers.OracleRestart Updating existing configmap +2025-08-23T13:49:24Z INFO controllers.OracleRestart Config Map updated successfully with new asm details +2025-08-23T13:49:27Z INFO controllers.OracleRestart Disk is in use and cannot be removed. Must be manually removed before proceeding {"disk": "/dev/disk/by-partlabel/asm-disk2", "diskgroup": "DATA"} +2025-08-23T13:49:38Z INFO controllers.OracleRestart Oracle Restart Object updated with updateOracleRestartInstStatus +2025-08-23T13:49:38Z INFO controllers.OracleRestart Completed Update of Oracle Restart instance status +2025-08-23T13:49:57Z INFO controllers.OracleRestart Updating Oracle Restart instance status with validateoraclerestartdb {"Instance": "oraclerestart-sample"} +2025-08-23T13:49:57Z INFO controllers.OracleRestart Updated Oracle Restart instance status with validateoraclerestartdb {"Instance": "oraclerestart-sample"} +2025-08-23T13:49:57Z ERROR Reconciler error {"controller": "oraclerestart", "controllerGroup": "database.oracle.com", "controllerKind": "OracleRestart", "OracleRestart": {"name":"oraclerestart-sample","namespace":"orestart"}, "namespace": "orestart", "name": "oraclerestart-sample", "reconcileID": "e379037b-4a89-415c-b3f5-aad5e72ff818", "error": "disk '/dev/disk/by-partlabel/asm-disk2' is part of diskgroup 'DATA' and must be manually removed before proceeding"} +sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).reconcileHandler + /gomod-cache/sigs.k8s.io/controller-runtime@v0.21.0/pkg/internal/controller/controller.go:353 +sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).processNextWorkItem + /gomod-cache/sigs.k8s.io/controller-runtime@v0.21.0/pkg/internal/controller/controller.go:300 +sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).Start.func2.1 + /gomod-cache/sigs.k8s.io/controller-runtime@v0.21.0/pkg/internal/controller/controller.go:202 +2025-08-23T13:49:57Z INFO controllers.OracleRestart Reconcile requested +2025-08-23T13:49:57Z INFO controllers.OracleRestart Entering reconcile validation +2025-08-23T13:49:57Z INFO controllers.OracleRestart Completed reconcile validation +2025-08-23T13:49:57Z INFO controllers.OracleRestart Detected Removal of ASM Disks: {"removedAsmDisks": ["/dev/disk/by-partlabel/asm-disk2"]} +2025-08-23T13:49:57Z INFO controllers.OracleRestart Initialized autoUpdate as true (default) +2025-08-23T13:49:57Z INFO controllers.OracleRestart PV Found {"Instance.Namespace": "orestart", "Instance.Name": "oraclerestart-sample", "dep.Name": "asm-pv-disk-0-oraclerestart-sample"} +2025-08-23T13:49:57Z INFO controllers.OracleRestart Updating existing configmap +2025-08-23T13:49:57Z INFO controllers.OracleRestart Config Map updated successfully with new asm details +2025-08-23T13:49:59Z INFO controllers.OracleRestart Disk is in use and cannot be removed. Must be manually removed before proceeding {"disk": "/dev/disk/by-partlabel/asm-disk2", "diskgroup": "DATA"} +2025-08-23T13:50:11Z INFO controllers.OracleRestart Oracle Restart Object updated with updateOracleRestartInstStatus +2025-08-23T13:50:11Z INFO controllers.OracleRestart Completed Update of Oracle Restart instance status +2025-08-23T13:50:36Z INFO controllers.OracleRestart Updating Oracle Restart instance status with validateoraclerestartdb {"Instance": "oraclerestart-sample"} +2025-08-23T13:50:36Z INFO controllers.OracleRestart Updated Oracle Restart instance status with validateoraclerestartdb {"Instance": "oraclerestart-sample"} +2025-08-23T13:50:36Z ERROR Reconciler error {"controller": "oraclerestart", "controllerGroup": "database.oracle.com", "controllerKind": "OracleRestart", "OracleRestart": {"name":"oraclerestart-sample","namespace":"orestart"}, "namespace": "orestart", "name": "oraclerestart-sample", "reconcileID": "e51d673e-6475-445e-bb4f-21e175057290", "error": "disk '/dev/disk/by-partlabel/asm-disk2' is part of diskgroup 'DATA' and must be manually removed before proceeding"} +sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).reconcileHandler + /gomod-cache/sigs.k8s.io/controller-runtime@v0.21.0/pkg/internal/controller/controller.go:353 +sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).processNextWorkItem + /gomod-cache/sigs.k8s.io/controller-runtime@v0.21.0/pkg/internal/controller/controller.go:300 +sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).Start.func2.1 + /gomod-cache/sigs.k8s.io/controller-runtime@v0.21.0/pkg/internal/controller/controller.go:202 +2025-08-23T13:50:36Z INFO controllers.OracleRestart Reconcile requested +2025-08-23T13:50:36Z INFO controllers.OracleRestart Entering reconcile validation +2025-08-23T13:50:36Z INFO controllers.OracleRestart Completed reconcile validation +2025-08-23T13:50:36Z INFO controllers.OracleRestart Detected Removal of ASM Disks: {"removedAsmDisks": ["/dev/disk/by-partlabel/asm-disk2"]} +2025-08-23T13:50:36Z INFO controllers.OracleRestart Initialized autoUpdate as true (default) +2025-08-23T13:50:36Z INFO controllers.OracleRestart PV Found {"Instance.Namespace": "orestart", "Instance.Name": "oraclerestart-sample", "dep.Name": "asm-pv-disk-0-oraclerestart-sample"} +2025-08-23T13:50:36Z INFO controllers.OracleRestart Updating existing configmap +2025-08-23T13:50:36Z INFO controllers.OracleRestart Config Map updated successfully with new asm details +2025-08-23T13:50:43Z INFO controllers.OracleRestart Disk is in use and cannot be removed. Must be manually removed before proceeding {"disk": "/dev/disk/by-partlabel/asm-disk2", "diskgroup": "DATA"} +2025-08-23T13:50:54Z INFO controllers.OracleRestart Oracle Restart Object updated with updateOracleRestartInstStatus +2025-08-23T13:50:54Z INFO controllers.OracleRestart Completed Update of Oracle Restart instance status +2025-08-23T13:51:14Z INFO controllers.OracleRestart Updating Oracle Restart instance status with validateoraclerestartdb {"Instance": "oraclerestart-sample"} +2025-08-23T13:51:14Z INFO controllers.OracleRestart Updated Oracle Restart instance status with validateoraclerestartdb {"Instance": "oraclerestart-sample"} +2025-08-23T13:51:14Z ERROR Reconciler error {"controller": "oraclerestart", "controllerGroup": "database.oracle.com", "controllerKind": "OracleRestart", "OracleRestart": {"name":"oraclerestart-sample","namespace":"orestart"}, "namespace": "orestart", "name": "oraclerestart-sample", "reconcileID": "9fd9fffb-ea21-44a6-9fde-7481106503ca", "error": "disk '/dev/disk/by-partlabel/asm-disk2' is part of diskgroup 'DATA' and must be manually removed before proceeding"} +sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).reconcileHandler + /gomod-cache/sigs.k8s.io/controller-runtime@v0.21.0/pkg/internal/controller/controller.go:353 +sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).processNextWorkItem + /gomod-cache/sigs.k8s.io/controller-runtime@v0.21.0/pkg/internal/controller/controller.go:300 +sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).Start.func2.1 + /gomod-cache/sigs.k8s.io/controller-runtime@v0.21.0/pkg/internal/controller/controller.go:202 +2025-08-23T13:51:14Z INFO controllers.OracleRestart Reconcile requested +2025-08-23T13:51:14Z INFO controllers.OracleRestart Entering reconcile validation +2025-08-23T13:51:14Z INFO controllers.OracleRestart Completed reconcile validation +2025-08-23T13:51:14Z INFO controllers.OracleRestart Detected Removal of ASM Disks: {"removedAsmDisks": ["/dev/disk/by-partlabel/asm-disk2"]} +2025-08-23T13:51:14Z INFO controllers.OracleRestart Initialized autoUpdate as true (default) +2025-08-23T13:51:14Z INFO controllers.OracleRestart PV Found {"Instance.Namespace": "orestart", "Instance.Name": "oraclerestart-sample", "dep.Name": "asm-pv-disk-0-oraclerestart-sample"} +2025-08-23T13:51:14Z INFO controllers.OracleRestart Updating existing configmap +2025-08-23T13:51:14Z INFO controllers.OracleRestart Config Map updated successfully with new asm details +2025-08-23T13:51:18Z INFO controllers.OracleRestart Disk is in use and cannot be removed. Must be manually removed before proceeding {"disk": "/dev/disk/by-partlabel/asm-disk2", "diskgroup": "DATA"} +2025-08-23T13:51:30Z INFO controllers.OracleRestart Oracle Restart Object updated with updateOracleRestartInstStatus +2025-08-23T13:51:30Z INFO controllers.OracleRestart Completed Update of Oracle Restart instance status +2025-08-23T13:51:51Z INFO controllers.OracleRestart Updating Oracle Restart instance status with validateoraclerestartdb {"Instance": "oraclerestart-sample"} +2025-08-23T13:51:51Z INFO controllers.OracleRestart Updated Oracle Restart instance status with validateoraclerestartdb {"Instance": "oraclerestart-sample"} +2025-08-23T13:51:51Z ERROR Reconciler error {"controller": "oraclerestart", "controllerGroup": "database.oracle.com", "controllerKind": "OracleRestart", "OracleRestart": {"name":"oraclerestart-sample","namespace":"orestart"}, "namespace": "orestart", "name": "oraclerestart-sample", "reconcileID": "39f5168e-cf8e-4d0d-990e-e291b024cbc8", "error": "disk '/dev/disk/by-partlabel/asm-disk2' is part of diskgroup 'DATA' and must be manually removed before proceeding"} +sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).reconcileHandler + /gomod-cache/sigs.k8s.io/controller-runtime@v0.21.0/pkg/internal/controller/controller.go:353 +sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).processNextWorkItem + /gomod-cache/sigs.k8s.io/controller-runtime@v0.21.0/pkg/internal/controller/controller.go:300 +sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).Start.func2.1 + /gomod-cache/sigs.k8s.io/controller-runtime@v0.21.0/pkg/internal/controller/controller.go:202 +2025-08-23T13:51:51Z INFO controllers.OracleRestart Reconcile requested +2025-08-23T13:51:51Z INFO controllers.OracleRestart Entering reconcile validation +2025-08-23T13:51:51Z INFO controllers.OracleRestart Completed reconcile validation +2025-08-23T13:51:51Z INFO controllers.OracleRestart Detected Removal of ASM Disks: {"removedAsmDisks": ["/dev/disk/by-partlabel/asm-disk2"]} +2025-08-23T13:51:51Z INFO controllers.OracleRestart Initialized autoUpdate as true (default) +2025-08-23T13:51:51Z INFO controllers.OracleRestart PV Found {"Instance.Namespace": "orestart", "Instance.Name": "oraclerestart-sample", "dep.Name": "asm-pv-disk-0-oraclerestart-sample"} +2025-08-23T13:51:51Z INFO controllers.OracleRestart Updating existing configmap +2025-08-23T13:51:51Z INFO controllers.OracleRestart Config Map updated successfully with new asm details +2025-08-23T13:51:55Z INFO controllers.OracleRestart Disk is in use and cannot be removed. Must be manually removed before proceeding {"disk": "/dev/disk/by-partlabel/asm-disk2", "diskgroup": "DATA"} +2025-08-23T13:52:06Z INFO controllers.OracleRestart Oracle Restart Object updated with updateOracleRestartInstStatus +2025-08-23T13:52:06Z INFO controllers.OracleRestart Completed Update of Oracle Restart instance status +2025-08-23T13:52:28Z INFO controllers.OracleRestart Updating Oracle Restart instance status with validateoraclerestartdb {"Instance": "oraclerestart-sample"} +2025-08-23T13:52:28Z INFO controllers.OracleRestart Updated Oracle Restart instance status with validateoraclerestartdb {"Instance": "oraclerestart-sample"} +2025-08-23T13:52:28Z ERROR Reconciler error {"controller": "oraclerestart", "controllerGroup": "database.oracle.com", "controllerKind": "OracleRestart", "OracleRestart": {"name":"oraclerestart-sample","namespace":"orestart"}, "namespace": "orestart", "name": "oraclerestart-sample", "reconcileID": "6120c33d-b761-4661-b2f9-a45042e50790", "error": "disk '/dev/disk/by-partlabel/asm-disk2' is part of diskgroup 'DATA' and must be manually removed before proceeding"} +sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).reconcileHandler + /gomod-cache/sigs.k8s.io/controller-runtime@v0.21.0/pkg/internal/controller/controller.go:353 +sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).processNextWorkItem + /gomod-cache/sigs.k8s.io/controller-runtime@v0.21.0/pkg/internal/controller/controller.go:300 +sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).Start.func2.1 + /gomod-cache/sigs.k8s.io/controller-runtime@v0.21.0/pkg/internal/controller/controller.go:202 +2025-08-23T13:52:28Z INFO controllers.OracleRestart Reconcile requested +2025-08-23T13:52:28Z INFO controllers.OracleRestart Entering reconcile validation +2025-08-23T13:52:28Z INFO controllers.OracleRestart Completed reconcile validation +2025-08-23T13:52:28Z INFO controllers.OracleRestart Detected Removal of ASM Disks: {"removedAsmDisks": ["/dev/disk/by-partlabel/asm-disk2"]} +2025-08-23T13:52:28Z INFO controllers.OracleRestart Initialized autoUpdate as true (default) +2025-08-23T13:52:28Z INFO controllers.OracleRestart PV Found {"Instance.Namespace": "orestart", "Instance.Name": "oraclerestart-sample", "dep.Name": "asm-pv-disk-0-oraclerestart-sample"} +2025-08-23T13:52:28Z INFO controllers.OracleRestart Updating existing configmap +2025-08-23T13:52:28Z INFO controllers.OracleRestart Config Map updated successfully with new asm details +2025-08-23T13:52:32Z INFO controllers.OracleRestart Disk is in use and cannot be removed. Must be manually removed before proceeding {"disk": "/dev/disk/by-partlabel/asm-disk2", "diskgroup": "DATA"} +2025-08-23T13:52:44Z INFO controllers.OracleRestart Oracle Restart Object updated with updateOracleRestartInstStatus +2025-08-23T13:52:44Z INFO controllers.OracleRestart Completed Update of Oracle Restart instance status +2025-08-23T13:53:05Z INFO controllers.OracleRestart Updating Oracle Restart instance status with validateoraclerestartdb {"Instance": "oraclerestart-sample"} +2025-08-23T13:53:05Z INFO controllers.OracleRestart Updated Oracle Restart instance status with validateoraclerestartdb {"Instance": "oraclerestart-sample"} +2025-08-23T13:53:05Z ERROR Reconciler error {"controller": "oraclerestart", "controllerGroup": "database.oracle.com", "controllerKind": "OracleRestart", "OracleRestart": {"name":"oraclerestart-sample","namespace":"orestart"}, "namespace": "orestart", "name": "oraclerestart-sample", "reconcileID": "8cd9f41c-c062-44ab-8436-781b70e41602", "error": "disk '/dev/disk/by-partlabel/asm-disk2' is part of diskgroup 'DATA' and must be manually removed before proceeding"} +sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).reconcileHandler + /gomod-cache/sigs.k8s.io/controller-runtime@v0.21.0/pkg/internal/controller/controller.go:353 +sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).processNextWorkItem + /gomod-cache/sigs.k8s.io/controller-runtime@v0.21.0/pkg/internal/controller/controller.go:300 +sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).Start.func2.1 + /gomod-cache/sigs.k8s.io/controller-runtime@v0.21.0/pkg/internal/controller/controller.go:202 +2025-08-23T13:53:05Z INFO controllers.OracleRestart Reconcile requested +2025-08-23T13:53:05Z INFO controllers.OracleRestart Entering reconcile validation +2025-08-23T13:53:05Z INFO controllers.OracleRestart Completed reconcile validation +2025-08-23T13:53:05Z INFO controllers.OracleRestart Detected Removal of ASM Disks: {"removedAsmDisks": ["/dev/disk/by-partlabel/asm-disk2"]} +2025-08-23T13:53:05Z INFO controllers.OracleRestart Initialized autoUpdate as true (default) +2025-08-23T13:53:05Z INFO controllers.OracleRestart PV Found {"Instance.Namespace": "orestart", "Instance.Name": "oraclerestart-sample", "dep.Name": "asm-pv-disk-0-oraclerestart-sample"} +2025-08-23T13:53:05Z INFO controllers.OracleRestart Updating existing configmap +2025-08-23T13:53:05Z INFO controllers.OracleRestart Config Map updated successfully with new asm details +2025-08-23T13:53:10Z INFO controllers.OracleRestart Disk is in use and cannot be removed. Must be manually removed before proceeding {"disk": "/dev/disk/by-partlabel/asm-disk2", "diskgroup": "DATA"} +2025-08-23T13:53:21Z INFO controllers.OracleRestart Oracle Restart Object updated with updateOracleRestartInstStatus +2025-08-23T13:53:21Z INFO controllers.OracleRestart Completed Update of Oracle Restart instance status +2025-08-23T13:53:43Z INFO controllers.OracleRestart Updating Oracle Restart instance status with validateoraclerestartdb {"Instance": "oraclerestart-sample"} +2025-08-23T13:53:43Z INFO controllers.OracleRestart Updated Oracle Restart instance status with validateoraclerestartdb {"Instance": "oraclerestart-sample"} +2025-08-23T13:53:43Z ERROR Reconciler error {"controller": "oraclerestart", "controllerGroup": "database.oracle.com", "controllerKind": "OracleRestart", "OracleRestart": {"name":"oraclerestart-sample","namespace":"orestart"}, "namespace": "orestart", "name": "oraclerestart-sample", "reconcileID": "f29aaac7-f09b-4036-9adb-3615fb8357d7", "error": "disk '/dev/disk/by-partlabel/asm-disk2' is part of diskgroup 'DATA' and must be manually removed before proceeding"} +sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).reconcileHandler + /gomod-cache/sigs.k8s.io/controller-runtime@v0.21.0/pkg/internal/controller/controller.go:353 +sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).processNextWorkItem + /gomod-cache/sigs.k8s.io/controller-runtime@v0.21.0/pkg/internal/controller/controller.go:300 +sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).Start.func2.1 + /gomod-cache/sigs.k8s.io/controller-runtime@v0.21.0/pkg/internal/controller/controller.go:202 +2025-08-23T13:53:43Z INFO controllers.OracleRestart Reconcile requested +2025-08-23T13:53:43Z INFO controllers.OracleRestart Entering reconcile validation +2025-08-23T13:53:43Z INFO controllers.OracleRestart Completed reconcile validation +2025-08-23T13:53:43Z INFO controllers.OracleRestart Detected Removal of ASM Disks: {"removedAsmDisks": ["/dev/disk/by-partlabel/asm-disk2"]} +2025-08-23T13:53:43Z INFO controllers.OracleRestart Initialized autoUpdate as true (default) +2025-08-23T13:53:43Z INFO controllers.OracleRestart PV Found {"Instance.Namespace": "orestart", "Instance.Name": "oraclerestart-sample", "dep.Name": "asm-pv-disk-0-oraclerestart-sample"} +2025-08-23T13:53:43Z INFO controllers.OracleRestart Updating existing configmap +2025-08-23T13:53:43Z INFO controllers.OracleRestart Config Map updated successfully with new asm details +2025-08-23T13:53:47Z INFO controllers.OracleRestart Disk is in use and cannot be removed. Must be manually removed before proceeding {"disk": "/dev/disk/by-partlabel/asm-disk2", "diskgroup": "DATA"} +2025-08-23T13:53:59Z INFO controllers.OracleRestart Oracle Restart Object updated with updateOracleRestartInstStatus +2025-08-23T13:53:59Z INFO controllers.OracleRestart Completed Update of Oracle Restart instance status +2025-08-23T13:54:18Z INFO controllers.OracleRestart Updating Oracle Restart instance status with validateoraclerestartdb {"Instance": "oraclerestart-sample"} +2025-08-23T13:54:18Z INFO controllers.OracleRestart Updated Oracle Restart instance status with validateoraclerestartdb {"Instance": "oraclerestart-sample"} +2025-08-23T13:54:18Z ERROR Reconciler error {"controller": "oraclerestart", "controllerGroup": "database.oracle.com", "controllerKind": "OracleRestart", "OracleRestart": {"name":"oraclerestart-sample","namespace":"orestart"}, "namespace": "orestart", "name": "oraclerestart-sample", "reconcileID": "c656d21f-6cb5-4006-9cfa-9d3800fbdda3", "error": "disk '/dev/disk/by-partlabel/asm-disk2' is part of diskgroup 'DATA' and must be manually removed before proceeding"} +sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).reconcileHandler + /gomod-cache/sigs.k8s.io/controller-runtime@v0.21.0/pkg/internal/controller/controller.go:353 +sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).processNextWorkItem + /gomod-cache/sigs.k8s.io/controller-runtime@v0.21.0/pkg/internal/controller/controller.go:300 +sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).Start.func2.1 + /gomod-cache/sigs.k8s.io/controller-runtime@v0.21.0/pkg/internal/controller/controller.go:202 +2025-08-23T13:54:18Z INFO controllers.OracleRestart Reconcile requested +2025-08-23T13:54:18Z INFO controllers.OracleRestart Entering reconcile validation +2025-08-23T13:54:18Z INFO controllers.OracleRestart Completed reconcile validation + + + +### Now, in the meanwhile, when the Operator is waiting, if the disk is dropped at the ASM Level using "ALTER DISKGROUP DROP DISK" command as below, it will be detected by the Operator: + +[grid@dbmc1-0 ~]$ sqlplus "/ as sysasm" + +SQL*Plus: Release 19.0.0.0.0 - Production on Sat Aug 23 13:49:55 2025 +Version 19.28.0.0.0 + +Copyright (c) 1982, 2025, Oracle. All rights reserved. + + +Connected to: +Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production +Version 19.28.0.0.0 + +SQL> set lines 200 +SQL> col path format a50 +SQL> select group_number, disk_number, name, total_mb, free_mb, path from v$asm_disk; + +GROUP_NUMBER DISK_NUMBER NAME TOTAL_MB FREE_MB PATH +------------ ----------- ------------------------------ ---------- ---------- -------------------------------------------------- + 1 1 DATA_0001 51196 46452 /dev/disk/by-partlabel/asm-disk2 + 1 0 DATA_0000 51196 46464 /dev/disk/by-partlabel/asm-disk1 + +SQL> ALTER DISKGROUP DATA DROP DISK 'DATA_0001' REBALANCE POWER 10; + +Diskgroup altered. + +SQL> select EST_MINUTES,EST_WORK,ACTUAL,SOFAR from v$asm_operation; + +EST_MINUTES EST_WORK ACTUAL SOFAR +----------- ---------- ---------- ---------- + 0 0 10 0 + 0 1183 10 122 + 0 0 10 0 + +SQL> select EST_MINUTES,EST_WORK,ACTUAL,SOFAR from v$asm_operation; + +EST_MINUTES EST_WORK ACTUAL SOFAR +----------- ---------- ---------- ---------- + 0 0 10 0 + 0 1183 10 723 + 0 0 10 0 + +SQL> select EST_MINUTES,EST_WORK,ACTUAL,SOFAR from v$asm_operation; + +no rows selected + +SQL> select group_number, disk_number, name, total_mb, free_mb, path from v$asm_disk; + +GROUP_NUMBER DISK_NUMBER NAME TOTAL_MB FREE_MB PATH +------------ ----------- ------------------------------ ---------- ---------- -------------------------------------------------- + 0 0 0 0 /dev/disk/by-partlabel/asm-disk2 + 1 0 DATA_0000 51196 41732 /dev/disk/by-partlabel/asm-disk1 + + + + +### When the Operator detectes the Disk is deleted, it will update the Statefulset as well: + +$ kubectl logs -f pod/oracle-database-operator-controller-manager-bc8dd8f68-pwfdk -n oracle-database-operator-system +. +. +2025-08-23T13:54:18Z INFO controllers.OracleRestart Detected Removal of ASM Disks: {"removedAsmDisks": ["/dev/disk/by-partlabel/asm-disk2"]} +2025-08-23T13:54:18Z INFO controllers.OracleRestart Initialized autoUpdate as true (default) +2025-08-23T13:54:18Z INFO controllers.OracleRestart PV Found {"Instance.Namespace": "orestart", "Instance.Name": "oraclerestart-sample", "dep.Name": "asm-pv-disk-0-oraclerestart-sample"} +2025-08-23T13:54:18Z INFO controllers.OracleRestart Updating existing configmap +2025-08-23T13:54:18Z INFO controllers.OracleRestart Config Map updated successfully with new asm details +2025-08-23T13:54:20Z INFO controllers.OracleRestart Change State to UPDATING +2025-08-23T13:54:20Z INFO controllers.OracleRestart Oracle Restart Object updated with updateOracleRestartInstStatus +2025-08-23T13:54:20Z INFO controllers.OracleRestart StatefulSet spec differs for volume devices, updating StatefulSet (pods may be recreated) {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "StatefulSet.Namespace": "orestart", "StatefulSet.Name": "dbmc1"} +2025-08-23T13:54:20Z INFO controllers.OracleRestart StatefulSet update applied, waiting for pod recreation {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "StatefulSet.Namespace": "orestart", "StatefulSet.Name": "dbmc1"} +2025-08-23T13:54:20Z INFO controllers.OracleRestart Waiting for StatefulSet update to be applied {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "StatefulSet.Namespace": "orestart", "StatefulSet.Name": "dbmc1"} +2025-08-23T13:54:25Z INFO controllers.OracleRestart StatefulSet update is applied successfully {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "StatefulSet.Namespace": "orestart", "StatefulSet.Name": "dbmc1"} +2025-08-23T13:55:25Z INFO controllers.OracleRestart All Pods are running {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "StatefulSet.Namespace": "orestart", "StatefulSet.Name": "dbmc1"} +2025-08-23T13:55:40Z INFO controllers.OracleRestart Pod is not ready yet {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "Pod.Name": "dbmc1-0"} +2025-08-23T13:56:10Z INFO controllers.OracleRestart Pod is not ready yet {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "Pod.Name": "dbmc1-0"} +2025-08-23T13:56:40Z INFO controllers.OracleRestart Pod is not ready yet {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "Pod.Name": "dbmc1-0"} +2025-08-23T13:57:10Z INFO controllers.OracleRestart Pod is not ready yet {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "Pod.Name": "dbmc1-0"} +2025-08-23T13:57:40Z INFO controllers.OracleRestart Pod is ready {"oracleRestart.Namespace": "orestart", "oracleRestart.Name": "oraclerestart-sample", "Pod.Name": "dbmc1-0"} +2025-08-23T13:57:40Z INFO controllers.OracleRestart Successfully deleted PVC {"PVC.Name": "asm-pvc-disk-1-oraclerestart-sample"} +2025-08-23T13:57:40Z INFO controllers.OracleRestart Successfully deleted PV {"PV.Name": "asm-pv-disk-1-oraclerestart-sample"} +2025-08-23T13:57:40Z INFO controllers.OracleRestart Oracle Restart Object annotations updated with current spec annotation +2025-08-23T13:57:40Z INFO controllers.OracleRestart Reconcile completed. Requeuing.... +2025-08-23T13:57:52Z INFO controllers.OracleRestart Oracle Restart Object updated with updateOracleRestartInstStatus +2025-08-23T13:57:52Z INFO controllers.OracleRestart Completed Update of Oracle Restart instance status + + + + +############################################################### +### During this operation, the "pod/dbmc1-0" will be recreated +############################################################### + + + +### Status after the Disk removed: + +$ kubectl describe oraclerestarts.database.oracle.com/oraclerestart-sample -n orestart +Name: oraclerestart-sample +Namespace: orestart +Labels: +Annotations: OracleRestarts.database.oracle.com/old-spec: + {"instDetails":{"name":"dbmc1","hostSwLocation":"/scratch/orestart/","workerNode":["10.0.10.58"],"nodePortSvc":[{"name":"dbmc1-service-nod... +API Version: database.oracle.com/v4 +Kind: OracleRestart +Metadata: + Creation Timestamp: 2025-08-22T05:09:46Z + Finalizers: + database.oracle.com/oraclerestartfinalizer + Generation: 2 + Resource Version: 226226031 + UID: e3378459-8aa7-4c05-98b2-9c03a44b3a57 +Spec: + Asm Storage Details: + Auto Update: true + Disks By Size: + Disk Names: + /dev/disk/by-partlabel/asm-disk1 + Storage Size In Gb: 50 + Config Params: + Cpu Count: 4 + Crs Asm Device List: /dev/disk/by-partlabel/asm-disk1 + Crs Asm Disk Dg: +DATA + Crs Asm Disk Dg Redundancy: external + Db Base: /u01/app/oracle + Db Char Set: AL32UTF8 + Db Data File Dest Dg: +DATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: +DATA + Db Response File: + Db Sw Zip File: db_home.zip + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Grid Sw Zip File: grid_home.zip + Host Sw Stage Location: /scratch/software/stage + Inventory: /u01/app/oraInventory + Pga Size: 1G + Processes: 2000 + Sga Size: 3G + Sw Mount Location: /u01 + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Image: localhost/oracle/database-orestart:19.3.0-slim + Image Pull Policy: Always + Inst Details: + Host Sw Location: /scratch/orestart/ + Name: dbmc1 + Node Port Svc: + Name: dbmc1-service-nodeport + Svc Type: nodeport + Port Mappings: + Node Port: 30007 + Port: 1521 + Protocol: TCP + Target Port: 1521 + Worker Node: + 10.0.10.58 + Resources: + Limits: + Cpu: 2 + Memory: 16Gi + Requests: + Cpu: 2 + Memory: 16Gi + Security Context: + Sysctls: + Name: kernel.shmall + Value: 2097152 + Name: kernel.sem + Value: 250 32000 100 128 + Name: kernel.shmmax + Value: 8589934592 + Name: kernel.shmmni + Value: 4096 + Service Details: + Name: soepdb + Ssh Key Secret: + Key Mount Location: /mnt/.ssh + Name: ssh-key-secret + Priv Key Secret Name: ssh-privkey + Pub Key Secret Name: ssh-pubkey +Status: + Oracle Restart Nodes: + Name: dbmc1-0 + Node Details: + Instance State: OPEN + Pod State: AVAILABLE + Cluster State: HEALTHY + Mounted Devices: + /dev/disk/by-partlabel/asm-disk1 + State: AVAILABLE + Asm Details: + Diskgroup: + Disks: + /dev/disk/by-partlabel/asm-disk1 + Name: DATA + Redundancy: EXTERN + Conditions: + Last Transition Time: 2025-08-22T05:22:19Z + Message: oracle restart database is in a restricted state: PROVISIONING + Observed Generation: 1 + Reason: LastReconcileCycleQueued + Status: True + Type: ReconcileQueued + Last Transition Time: 2025-08-23T14:06:52Z + Message: no reconcile errors + Observed Generation: 2 + Reason: LastReconcileCycleCompleted + Status: True + Type: ReconcileComplete + Config Params: + Crs Asm Device List: /dev/disk/by-partlabel/asm-disk1 + Crs Asm Disk Dg: +DATA + Crs Asm Disk Dg Redundancy: external + Db Base: /u01/app/oracle + Db Data File Dest Dg: +DATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: +DATA + Db Response File: + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Inventory: /u01/app/oraInventory + Connect String: dbmc1-0:1521/PORCLCDB + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Db State: OPEN + External Connect String: 129.146.0.149:30007/soepdb + Inst Details: + Name: + Pdb Connect String: dbmc1-0:1521/ORCLPDB + Release Update: 19.28.0.0.0 + Role: PRIMARY + Service Details: + Name: soepdb + Svc State: service soepdb is running + State: AVAILABLE +Events: + + + +[grid@dbmc1-0 ~]$ export ORACLE_SID=+ASM +[grid@dbmc1-0 ~]$ asmcmd lsdsk +Path +/dev/disk/by-partlabel/asm-disk1 + + +[grid@dbmc1-0 ~]$ ls -lrt /dev/disk/by-partlabel/asm-disk* +brw-rw----. 1 grid asmadmin 8, 49 Aug 23 14:10 /dev/disk/by-partlabel/asm-disk1 \ No newline at end of file diff --git a/docs/oraclerestart/provisioning/change_memory_cpu_for_oracle_restart_db.md b/docs/oraclerestart/provisioning/change_memory_cpu_for_oracle_restart_db.md new file mode 100644 index 00000000..cb6e45a1 --- /dev/null +++ b/docs/oraclerestart/provisioning/change_memory_cpu_for_oracle_restart_db.md @@ -0,0 +1,55 @@ +# Change Memory and CPU allocation for an earlier provisioned Oracle Restart Database + +### In this Use case: +* You have previously deployed an Oracle Restart Database in Kubernetes (for example, on OKE or OpenShift) using the Oracle Restart Database Controller. +* You can change the memory and CPU allocation for an existing Oracle Restart Database Pod by updating resource requests and limits in the Custom Resource YAML associated with your Oracle Restart instance. +* This example uses `oraclerestart_prov_nodeports.yaml` to provision the initial Oracle Restart Database using Oracle Restart Controller with: + + * Oracle Restart Pod + * Headless services for Oracle Restart + * Oracle Restart Node hostname + * Node Port 30007 mapped to port 1521 for Database Listener. If you are using Loadbalancer service then you will see `lbservice`. + * Persistent volumes that are created automatically based on specified disks for Oracle ASM storage + * Software Persistent Volume and Staged Software Persistent Volume using the specified location on the corresponding worker node. If you are using Storageclass, then the software volume is dynamically provisioned. + * Namespace: `orestart` + * Staged Software location on the worker nodes is specified by `hostSwStageLocation`. The Grid Infrastructure and RDBMS Binaries are copied to this location on the worker node. If you are using, existing NFS-based PVC for the staged software, then the parameter is `swStagePvcMountLocation` under `configParams`. + * Software location on the worker nodes is specified by `hostSwLocation`. The GI HOME and the RDBMS HOME in the Oracle Restart Pod will be mounted using this location on the worker node. + + +### In this Example: + * Oracle Restart Database Slim Image `dbocir/oracle/database-orestart:19.3.0-slim` is used. It is built using files from this [GitHub location](https://github.com/oracle/docker-images/tree/main/OracleDatabase/RAC/OracleRealApplicationClusters#building-oracle-rac-database-container-slim-image). Default image created using files from this project is `localhost/oracle/database-rac:19.3.0-slim`. You must tag it with the name `localhost/oracle/database-orestart:19.3.0-slim`. + * When you are building the image yourself, update the image value in the `oraclerestart_prov_nodeports.yaml` file to point to the container image that you have built. + * The disks on the worker nodes for the Oracle Restart storage are `/dev/disk/by-partlabel/asm-disk1` and `/dev/disk/by-partlabel/asm-disk2`. + * Specify the sizes and names of these devices using the parameter `storageSizeInGb`. By default, size is in GBs. + +**NOTE:** When no separate diskgroup names are specified for CRS Files, Database Files, Recovery Area Files and Redo Log Files, the default diskgroup named `+DATA` is created from the disks specified by the parameter `crsAsmDeviceList`. + +### Steps - Deploy initial Oracle Restart Database +* Use the file [oraclerestart_prov_nodeports.yaml](./oraclerestart_prov_nodeports.yaml) for the initial deployment before any CPU or memory change. +* Deploy the `oraclerestart_prov_nodeports.yaml` file: + ```sh + kubectl apply -f oraclerestart_prov_nodeports.yaml + oraclerestart.database.oracle.com/oraclerestart-sample created + ``` +* Check the status of the deployment: + ```sh + # Check the status of the Kubernetes Pods: + kubectl get all -n orestart + + # Check the logs of a particular pod. For example, to check status of pod "dbmc1-0": + kubectl exec -it pod/dbmc1-0 -n orestart -- bash -c "tail -f /tmp/orod/oracle_db_setup.log" + =============================== + ORACLE DATABASE IS READY TO USE + =============================== + ``` + +### Steps - Modify the Memory and CPU in the Oracle Restart Database Pod +* Use the file [oraclerestart_prov_nodeports_mcpu_change.yaml](./oraclerestart_prov_nodeports_mcpu_change.yaml) to change the Memory and CPU allocation for the existing Oracle Restart Database Pod: + ```sh + kubectl apply -f oraclerestart_prov_nodeports_mcpu_change.yaml + oraclerestart.database.oracle.com/oraclerestart-sample configured + ``` + +**NOTE:** You will notice that the exiting Oracle Restart Database Pod will be recreated with updated Memory and CPU allocation. + +* Check Details of Kubernetes CRD Object before and after the change as in this [example](./oraclerestart_prov_nodeports_mcpu_change.txt). It also has the details of the memory and cpu inside the pod before and after the change. \ No newline at end of file diff --git a/docs/oraclerestart/provisioning/change_sw_storage_size_for_oracle_restart_db.md b/docs/oraclerestart/provisioning/change_sw_storage_size_for_oracle_restart_db.md new file mode 100644 index 00000000..a93036a5 --- /dev/null +++ b/docs/oraclerestart/provisioning/change_sw_storage_size_for_oracle_restart_db.md @@ -0,0 +1,59 @@ +# Change the size of Software Storage Location for an existing Oracle Restart Database + +### In this Use case: +* You have previously deployed an Oracle Restart Database in Kubernetes (for example, on OKE or OpenShift) using the Oracle Restart Database Controller. +* The Software Home Location for Grid Infrastructure and Database, the ASM Disks are provisioned as Persistent Volumes using custom storage class during the initial deployment. An updated YAML file is applied to `increase` the size of the Software Home Location. + +**NOTE:** The `decrease` in the size of Software Home Location for an existing Oracle Restart Database is _not_ allowed. + +This example uses `oraclerestart_prov_storage_class_before_sw_home_resize.yaml` to initially provision an Oracle Restart Database using Oracle Restart Controller with: + + * Oracle Restart Pod + * Headless services for Oracle Restart + * Oracle Restart Node hostname + * Node Port 30007 mapped to port 1521 for Database Listener. If you are using Loadbalancer service then you will see lbservice. + * Persistent volumes for ASM Disks created automatically using the Storage Class for Oracle ASM storage + * Persistent volume for Software location is created automatically using the Storage Class. The GI HOME and the RDBMS HOME in the Oracle Restart Pod will be mounted using the corresponding Persistent Volume Claim. Its size is specified by `swLocStorageSizeInGb`. + * Namespace: `orestart` + * Staged Software location on the worker nodes is specified by `hostSwStageLocation`. The Grid Infrastructure and RDBMS Binaries are copied to this location on the worker node. + * Name of Custom Storage Class is specified by `storageClass`. + * You will be using storageclass to dynamically allcate the storage. using the storage class **oci-bv**. + +### In this Example: + * Oracle Restart Database Slim Image `dbocir/oracle/database-orestart:19.3.0-slim` is used. It is built using files from this [GitHub location](https://github.com/oracle/docker-images/tree/main/OracleDatabase/RAC/OracleRealApplicationClusters#building-oracle-rac-database-container-slim-image). + * The disks provisioned using storageclass are mounted inside the Oracle Restart Pod as `/dev/asm-disk1` and `/dev/asm-disk2`. + * Specify the size of these devices along with names using the parameter `swLocStorageSizeInGb`. Size is by-default in GBs. + +**NOTE:** When no separate diskgroup names are specified for CRS Files, Database Files, Recovery Area Files and Redo Log Files, the default diskgroup named `+DATA` is created from the disks specified by the parameter `crsAsmDeviceList`. + +### Steps - Deploy the Oracle Restart Database +* Skip this step if you have already deployed the Oracle Restart database using storage class. +* Use the file [oraclerestart_prov_storage_class_before_sw_home_resize.yaml](./oraclerestart_prov_storage_class_before_sw_home_resize.yaml) for this use case in this procedure. +* Update the Oracle Restart container image. In this example, we have `dbocir/oracle/database-orestart:19.3.0-slim` in [oraclerestart_prov_storage_class_before_sw_home_resize.yaml](./oraclerestart_prov_storage_class_before_sw_home_resize.yaml) file to point to the container image you have built. +* Deploy the `oraclerestart_prov_storage_class_before_sw_home_resize.yaml` file: + ```sh + kubectl apply -f oraclerestart_prov_storage_class_before_sw_home_resize.yaml + oraclerestart.database.oracle.com/oraclerestart-sample created + ``` +* Check the status of the deployment: + ```sh + # Check the status of the Kubernetes Pods: + kubectl get all -n orestart + + # Check the logs of a particular pod. For example, to check status of pod "dbmc1-0": + kubectl exec -it pod/dbmc1-0 -n orestart -- bash -c "tail -f /tmp/orod/oracle_db_setup.log" + =============================== + ORACLE DATABASE IS READY TO USE + =============================== + ``` +* Check Details of Kubernetes CRD Object as shown in this [example](./orestart_storage_class_object_before_sw_home_resize.txt) + +### Steps - Update the Software Home Location in Oracle Restart Database +* To `increase` the size of the Software Home Location, you can use the updated file [oraclerestart_prov_storage_class_after_sw_home_resize.yaml](./oraclerestart_prov_storage_class_after_sw_home_resize.yaml). +* Update the Oracle Restart container image. In this example, we use the file `dbocir/oracle/database-orestart:19.3.0-slim` in [oraclerestart_prov_storage_class_before_sw_home_resize.yaml](./oraclerestart_prov_storage_class_before_sw_home_resize.yaml) to point to the container image that you have built. +* Deploy the `oraclerestart_prov_storage_class_after_sw_home_resize.yaml` file: + ```sh + $ kubectl apply -f oraclerestart_prov_storage_class_after_sw_home_resize.yaml + oraclerestart.database.oracle.com/oraclerestart-sample configured + ``` + You will notice the Persistent Volume for the Software Location has been resized. You can check Details of updated Kubernetes CRD Object as shown in this [example](./orestart_storage_class_object_after_sw_home_resize.txt) diff --git a/docs/oraclerestart/provisioning/cleanup.md b/docs/oraclerestart/provisioning/cleanup.md new file mode 100644 index 00000000..846927ce --- /dev/null +++ b/docs/oraclerestart/provisioning/cleanup.md @@ -0,0 +1,20 @@ +# Cleanup an Oracle Restart Database Deployed using Oracle Database Operator + +To delete and clean up the Oracle Restart Database deployed using Oracle Database Operator, run the following commands. + +This example uses `oraclerestart_prov.yaml` to clean up an Oracle Restart Database that was initially used for deployment: + + +1. Use the file `oraclerestart_prov.yaml` to delete an existing deployment: + ```sh + kubectl delete -f oraclerestart_prov.yaml + oraclerestart.database.oracle.com/oraclerestart-sample deleted + ``` +2. Check the status of the deployment: + ```sh + # Check the status of the Kubernetes Pods: + kubectl get all -n orestart + ``` +3. If your deleted deployment used a software location specified by `hostSwLocation` from the worker node, then to reuse this location in your next deployment, you must clear it at the worker node level. + +4. If your deleted deployment used ASM Disks from the worker node, then to reuse the same disks for the next deployment, you must clear the disks at the worker node level using the `dd` command. \ No newline at end of file diff --git a/docs/oraclerestart/provisioning/create_kubernetes_secret_for_db_user.md b/docs/oraclerestart/provisioning/create_kubernetes_secret_for_db_user.md new file mode 100644 index 00000000..90ce9c45 --- /dev/null +++ b/docs/oraclerestart/provisioning/create_kubernetes_secret_for_db_user.md @@ -0,0 +1,27 @@ +# Create Kubernetes secret for db user + +Create a Kubernetes secret named `db-user-pass-pkutl` using a password in a text file, and then encrypt it using an `openssl` key. The text file will be removed after the secret is created. Note that openssl _must_ be installed on worker nodes. + +**NOTE:** The openssl version on the system where you run the commands in this procedure to generate the secret and the openssl version of the Oracle Restart Slim Image must be compatible with each other. + +```sh +mkdir /tmp/.secrets/ + +# Generate a random openssl key +echo Oracle_23ai > /tmp/.secrets/pwdfile.txt +openssl genrsa -out /tmp/.secrets/key.pem +openssl rsa -in /tmp/.secrets/key.pem -out /tmp/.secrets/key.pub -pubout + +# Encrypt the password file +openssl pkeyutl -in /tmp/.secrets/pwdfile.txt -out /tmp/.secrets/pwdfile.enc -pubin -inkey /tmp/.secrets/key.pub -encrypt +rm -rf /tmp/.secrets/pwdfile.txt + +# Deleting the exisitng secret +kubectl delete secret db-user-pass-pkutl -n orestart + +# Create the Kubernetes secret in namespace "rac" +kubectl create secret generic db-user-pass-pkutl --from-file=/tmp/.secrets/pwdfile.enc --from-file=/tmp/.secrets/key.pem -n orestart + +# Check the secret details +kubectl get secret -n orestart +``` diff --git a/docs/oraclerestart/provisioning/create_kubernetes_secret_for_ssh_setup.md b/docs/oraclerestart/provisioning/create_kubernetes_secret_for_ssh_setup.md new file mode 100644 index 00000000..3505f8c5 --- /dev/null +++ b/docs/oraclerestart/provisioning/create_kubernetes_secret_for_ssh_setup.md @@ -0,0 +1,19 @@ +# Create Kubernetes secret for db user using an SSH Key Pair + +Generate an SSH Key Pair using `ssh-keygen`. Then create a Kubernetes secret named `ssh-key-secret` using this key pair. + +```sh +mkdir /tmp/.secrets/ssh + +# Generate a private and public key +ssh-keygen -t rsa -C "your_email@example.info" -f /tmp/.secrets/ssh/id_rsa + +# Deleting the exisitng secret +kubectl delete secret ssh-key-secret -n orestart + +# Create the Kubernetes secret in namespace "orestart" +kubectl create secret generic ssh-key-secret --from-file=ssh-privkey=/tmp/.secrets/ssh/id_rsa --from-file=ssh-pubkey=/tmp/.secrets/ssh/id_rsa.pub -n orestart + +# Check the secret details +kubectl get secret -n orestart +``` diff --git a/docs/oraclerestart/provisioning/database_connection.md b/docs/oraclerestart/provisioning/database_connection.md new file mode 100644 index 00000000..14d3935e --- /dev/null +++ b/docs/oraclerestart/provisioning/database_connection.md @@ -0,0 +1,100 @@ +# Database Connectivity + +To connect to the Oracle Restart Database, follow the example that matches your deployment method—NodePort Service, Load Balancer Service, or another supported method. + +## Database Connection to Oracle Restart Database with NodePort Service +If you deployed the Oracle Restart Database with a NodePort service using the Oracle Restart Controller, then you can connect by specifying the worker node’s IP address and the port of the Node Port service. Follow these steps: + +1. Get the Details of the deployment: +```sh +$ kubectl get all -n orestart -o wide +NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES +pod/dbmc1-0 1/1 Running 0 5h46m 10.244.0.52 10.0.10.58 + +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR +service/dbmc1 NodePort 10.96.53.210 1521:30007/TCP 5h46m statefulset.kubernetes.io/pod-name=dbmc1-0 +service/dbmc1-0 ClusterIP None 171m statefulset.kubernetes.io/pod-name=dbmc1-0 + +NAME READY AGE CONTAINERS IMAGES +statefulset.apps/dbmc1 1/1 5h46m dbmc1 localhost/oracle/database-orestart:19.3.0-slim +``` +In this case, the port 1521 from the pod is mapped to port 30007 on the worker node. To make the connection from outside, you must open the port 30007 on the worker node for INGRESS. + +2. With this NodePort Service deployment, you can make a SQL*Plus database connection to this Oracle Restart Database from a remote client: + +```sh +bash-4.4$ sqlplus system/@//:30007/PORCLCDB + +SQL*Plus: Release 23.0.0.0.0 - for Oracle Cloud and Engineered Systems on Sat Jul 19 04:02:48 2025 +Version 23.9.0.25.09 + +Copyright (c) 1982, 2025, Oracle. All rights reserved. + +Last Successful login time: Sat Jul 19 2025 00:20:14 +00:00 + +Connected to: +Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production +Version 19.28.0.0.0 + +SQL> +SQL> set lines 200 +SQL> col HOST_NAME format a40 +SQL> select INSTANCE_NAME,HOST_NAME, DATABASE_TYPE from v$instance; + +INSTANCE_NAME HOST_NAME DATABASE_TYPE +---------------- ---------------------------------------- --------------- +PORCLCDB dbmc1-0 SINGLE +``` + +## Database Connection to Oracle Restart Database with Load Balancer + +In this case, the Oracle Restart Database is deployed with an External Load Balancer, and the deployment has a Public IP Assigned from the External Load Balancer Service. + +After the deployment is completed, you can make a database connection: + +1. Get the Details of the deployment: +```sh +$ kubectl get all -n orestart -o wide +NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES +pod/dbmc1-0 1/1 Running 0 14m 10.244.0.41 10.0.10.58 + +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR +service/dbmc1 LoadBalancer 10.96.34.208 XXX.XX.XX.XX 1521:30433/TCP,6200:30656/TCP 14m statefulset.kubernetes.io/pod-name=dbmc1-0 +service/dbmc1-0 ClusterIP None 14m statefulset.kubernetes.io/pod-name=dbmc1-0 + +NAME READY AGE CONTAINERS IMAGES +statefulset.apps/dbmc1 1/1 14m dbmc1 localhost/oracle/database-orestart:19.3.0-slim +``` +In this case, you can make a remote database connection using the Load Balancer target port 1521. + +2. With this Load Balancer deployment, you can make a SQL*Plus database connection to this Oracle Restart Database from a remote client: + +```sh +bash-4.4$ sqlplus system/@//:1521/PORCLCDB + +SQL*Plus: Release 21.0.0.0.0 - Production on Tue Sep 2 04:57:56 2025 +Version 21.19.0.0.0 + +Copyright (c) 1982, 2022, Oracle. All rights reserved. + +Last Successful login time: Tue Sep 02 2025 04:53:52 +00:00 + +Connected to: +Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production +Version 19.28.0.0.0 + +SQL> +SQL> set lines 200 +SQL> col HOST_NAME format a40 +SQL> select INSTANCE_NAME,HOST_NAME, DATABASE_TYPE from v$instance; + +INSTANCE_NAME HOST_NAME DATABASE_TYPE +---------------- ---------------------------------------- --------------- +PORCLCDB dbmc1-0 SINGLE +``` + +## Database Connection to Oracle Restart Database without NodePort Service + +In this case, the Oracle Restart Database will NOT be reachable using the Public IP of the worker node and thus, will not be reachable from outside the Kuberenetes Cluster. + +In this case, an application deployed with in the Kubernetes Cluster will be able to reach the Oracle Restart Database on Port 1521. \ No newline at end of file diff --git a/docs/oraclerestart/provisioning/debugging.md b/docs/oraclerestart/provisioning/debugging.md new file mode 100644 index 00000000..d943f2e7 --- /dev/null +++ b/docs/oraclerestart/provisioning/debugging.md @@ -0,0 +1,41 @@ +# Debugging and Troubleshooting + +When provisioning the Oracle Restart Database with the Oracle Restart Controller, your debugging approach depends on the stage in which the issue occurs. Use the guidance below for targeted troubleshooting. + +Below are the possible cases and the steps to debug such an issue: + +## Failure during the provisioning of Kubernetes Pods + +If the failure occurs while creating Kubernetes Pods, start by checking the status of the affected Pod: + +Use the following command to check the logs of the Pod that has a failure. For example, pod `pod/dbmc1-0`, use the following command: + +```sh +kubectl logs -f pod/dbmc1-0 -n orestart +``` + +If the Pod has failed to provision due to an issue with the Docker Image Pull, then you see the error `Error: ErrImagePull` in the logs. + +If the Pod has not yet been initialized, then use the following command to find the reason for it: + +```sh +kubectl describe pod/dbmc1-0 -n orestart +``` + +You will need to further troubleshoot depending on further issues or errors seen after you run `kubectl describe pod`. + +## Oracle Database Provisioning Failure + +If the Oracle Database provisioning fails after the Kubernetes Pods have been created, but during the processing of database creation scripts, then troubleshoot the issue within the affected Pod. + +Initially, check the logs of the Kubernetes Pod using the `kubectl logs` command (exchange the name of the Pod in this example with the actual Pod on your system): + +```sh +kubectl logs -f pod/dbmc1-0 -n orestart +``` + +To check the details of the CRS logs or the RDBMS instance logs at the host level, switch to the corresponding Kubernetes container using the following command: + +```sh +kubectl exec -it dbmc1-0 -n orestart -- tail -f /tmp/orod/oracle_db_setup.log +``` \ No newline at end of file diff --git a/docs/oraclerestart/provisioning/delete_asm_disks_from_an_existing_restart_database.md b/docs/oraclerestart/provisioning/delete_asm_disks_from_an_existing_restart_database.md new file mode 100644 index 00000000..ba70aa40 --- /dev/null +++ b/docs/oraclerestart/provisioning/delete_asm_disks_from_an_existing_restart_database.md @@ -0,0 +1,47 @@ +# Deleting ASM Disks - Delete ASM Disks from an existing Oracle Restart Database + +### In this use case: + +* You have previously deployed an Oracle Restart Database in Kubernetes (for example, on OKE or OpenShift) using the Oracle Restart Database Controller. Now, you need to remove ASM disks from the ASM storage. +* The existing Oracle Restart Database is deployed with Node Port Service using the file `oraclerestart_prov_nodeports.yaml` described in [Provisioning an Oracle Restart Database with NodePort Service](./provisioning/provisioning_oracle_restart_db_nodeport.md) This deployment uses Oracle Restart Controller with: + * Oracle Restart Pod + * Headless services for Oracle Restart + * Oracle Restart Node hostname + * Node Port 30007 mapped to port 1521 for Database Listener. If you are using Load Balancer, then you will see the LB service. + * Persistent volumes created automatically based on specified disks for Oracle ASM storage + * Software Persistent Volume and Staged Software Persistent Volume using the specified location on the corresponding worker node. + * Namespace: `orestart` + * Staged Software location on the worker nodes is specified by `hostSwStageLocation`. The Grid Infrastructure and RDBMS Binaries are copied to this location on the worker node. + * Software location on the worker nodes is specified by `hostSwLocation`. The GI HOME and the RDBMS HOME in the Oracle Restart Pod will be mounted using this location on the worker node. + +### In this exapmple: + * Oracle Restart Database Slim Image `dbocir/oracle/database-orestart:19.3.0-slim` is used. It is built using files from this [GitHub location](https://github.com/oracle/docker-images/tree/main/OracleDatabase/RAC/OracleRealApplicationClusters#building-oracle-rac-database-container-slim-image). + * The disks on the worker nodes for the Oracle Restart storage are `/dev/disk/by-partlabel/asm-disk1` and `/dev/disk/by-partlabel/asm-disk2`. + * Specify the size of these devices along with names using the parameter `storageSizeInGb`. By default, size is in GBs. + * Before deleting the disk, **you will need to remove the disk at the ASM Level** using `ALTER DISKGROUP DROP DISK` command. + * If you delete the disk’s YAML file before first removing the disk at the ASM level, thne the Operator will not delete the disk from the StatefulSet, and the Oracle Restart Database Pod will not be recreated. Instead, to prevent data loss, the operator will wait until you remove the disk at the ASM level before proceeding. + * In this example, out of the two disks mentioned above, the disk `/dev/disk/by-partlabel/asm-disk2` will be deleted from the existing Oracle Restart Database Deployment. + +### Steps: Delete the ASM Disk +Use the file: [orestart_prov_asm_disk_deletion.yaml](./orestart_prov_asm_disk_deletion.yaml) for this procedure: + +* Deploy the `orestart_prov_asm_disk_deletion.yaml` file: + ```sh + kubectl apply -f orestart_prov_asm_disk_deletion.yaml + ``` + +In this case, the disk will be deleted and Oracle Restart Database Pod will be recreated. + +* Check the status of the deployment: + ```sh + # Check the status of the Kubernetes Pods: + kubectl get all -n orestart + + # Check the logs of a particular pod. For example, to check status of pod "dbmc1-0": + kubectl exec -it pod/dbmc1-0 -n orestart -- bash -c "tail -f /tmp/orod/oracle_db_setup.log" + ``` +* You can review example logs in [logs](./asm_disk_deletion.txt) when the disk `/dev/disk/by-partlabel/asm-disk2` is removed at the ASM Level first and then the file [orestart_prov_asm_disk_deletion.yaml](./orestart_prov_asm_disk_deletion.yaml) is applied to remove it from the Stateful set. + +* Example logs in [logs](./asm_disk_deletion1.txt) show what happens when the attempt is made to remove the disk `/dev/disk/by-partlabel/asm-disk2` from the Stateful Set by using the file [orestart_prov_asm_disk_deletion.yaml](./orestart_prov_asm_disk_deletion.yaml) but this disk is NOT yet removed at the ASM Level. + +In this case, the Operator will be waiting for the disk to be deleted at the ASM Level. After it detects the Disk has been deleted at the ASM Level, and it is safe to proceed, it will remove the disk from the Stateful Set. \ No newline at end of file diff --git a/docs/oraclerestart/provisioning/known_issues.md b/docs/oraclerestart/provisioning/known_issues.md new file mode 100644 index 00000000..a2a7d966 --- /dev/null +++ b/docs/oraclerestart/provisioning/known_issues.md @@ -0,0 +1,5 @@ +# Known Issues + +This document lists any known issues when the Oracle Restart Database is provisioned using the Oracle Restart controller. + +- If you describe the resource `oraclerestarts.database.oracle.com/oraclerestart-sample` in the namespace used for Oracle Restart Database provisioning, sometimes it may report the `State` under `Service Details` as `FAILED` while the Service is up and running. Similarly, the `Instance State` may report as `NOTAVAILABLE` while the Oracle Restart Database Instance is up and running fine. \ No newline at end of file diff --git a/docs/oraclerestart/provisioning/nfs_pv_stage_vol.yaml b/docs/oraclerestart/provisioning/nfs_pv_stage_vol.yaml new file mode 100644 index 00000000..5525f3e5 --- /dev/null +++ b/docs/oraclerestart/provisioning/nfs_pv_stage_vol.yaml @@ -0,0 +1,39 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: pv-stage-vol1 + namespace: orestart +spec: + storageClassName: fss-dyn-storage + capacity: + storage: 500Gi + accessModes: + - ReadOnlyMany + nfs: + path: /stage + server: 10.0.10.212 + persistentVolumeReclaimPolicy: Retain + mountOptions: + - rw + - nointr + - hard + - bg + - tcp + - actimeo=0 + - vers=3 + - rsize=1048576 + - wsize=1048576 + - timeo=600 +--- +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: pv-stage-vol-claim + namespace: orestart +spec: + storageClassName: fss-dyn-storage + accessModes: + - ReadOnlyMany + resources: + requests: + storage: 500Gi diff --git a/docs/oraclerestart/provisioning/oraclerestart_prov.yaml b/docs/oraclerestart/provisioning/oraclerestart_prov.yaml new file mode 100644 index 00000000..aa1989d8 --- /dev/null +++ b/docs/oraclerestart/provisioning/oraclerestart_prov.yaml @@ -0,0 +1,100 @@ +# API version of the OracleRestart Custom Resource +apiVersion: database.oracle.com/v4 + +# Resource type being created +kind: OracleRestart + +# Metadata section to define the resource's name and namespace +metadata: + name: oraclerestart-sample # Name of the OracleRestart instance + namespace: orestart # Kubernetes namespace to deploy into + +spec: + # Instance details for Oracle Restart + instDetails: + name: dbmc1 # Logical name of the OracleRestart instance + hostSwLocation: /scratch/orestart/ # Host directory for Oracle GI HOME and Oracle RDBMS HOME + + # Worker nodes where the instance should be scheduled + workerNode: + - 10.0.10.58 # IP address of the target node for deployment + + # Environment variables in case you want to want to ignore prerequisite checks during the GI/RDBSMS Install + envVars: + - name: IGNORE_CRS_PREREQS # If set to true, it will use flags -ignorePreReq and -ignorePrereqFailure during the CRS Installation. Do not set this parameter in the .yaml file if you do not want to use the parameters -ignorePreReq and -ignorePrereqFailure during the CRS Installation. + value: "true" + - name: IGNORE_DB_PREREQS # If set to true, it will use flags -ignorePrereq and -ignorePrereqFailure during the DB Software Installation. Do not set this parameter in the .yaml file if you do not want to use the parameters -ignorePreReq and -ignorePrereqFailure during the DB Software Installation. + value: "true" + + # ASM (Automatic Storage Management) storage configuration + asmStorageDetails: + disksBySize: + - storageSizeInGb: 50 # Each disk in this group has 50Gi of storage + diskNames: + - /dev/disk/by-partlabel/asm-disk1 # ASM disk device path 1 + - /dev/disk/by-partlabel/asm-disk2 # ASM disk device path 2 + + # SSH key secret used for remote access and automation + sshKeySecret: + name: ssh-key-secret # Kubernetes secret name + privKeySecretName: ssh-privkey # Private key inside secret + pubKeySecretName: ssh-pubkey # Public key inside secret + + # Secret containing DB user credentials + dbSecret: + name: db-user-pass-pkutl # Secret name + keyFileName: key.pem # Key file name inside secret + pwdFileName: pwdfile.enc # Password file name inside secret + + # Image for Oracle Restart container + image: dbocir/oracle/database-orestart:19.3.0-slim # <-- Replace with your image registry + + # Policy to pull image + # Use the value as "Always" if you want use the latest version of the image from the container registry. + # Use the value as "IfNotPresent" it the image is not already present locally. + imagePullPolicy: IfNotPresent + + # Optional service account for security context permissions + # serviceAccountName: oraclerestart # <-- Required for OpenShift; optional for vanilla Kubernetes + + # Service-specific database name and related details + serviceDetails: + name: soepdb # Name of the Oracle service (pluggable DB) + + # Resource requests and limits for memory and CPU + resources: + requests: + memory: "16Gi" # Minimum memory required + cpu: "2" # Minimum CPU required + limits: + memory: "16Gi" # Max memory allowed + cpu: "2" # Max CPU allowed + + # Kernel-level system control parameters for Oracle + securityContext: + sysctls: + - name: kernel.shmall + value: "2097152" + - name: kernel.sem + value: "250 32000 100 128" + - name: kernel.shmmax + value: "8589934592" + - name: kernel.shmmni + value: "4096" + + # Configuration parameters for Oracle Grid and Database homes + configParams: + gridHome: "/u01/app/19c/grid" # Oracle Grid home path + gridBase: "/u01/app/grid" # Grid base directory + dbHome: "/u01/app/oracle/product/19c/dbhome_1" # Oracle DB home path + dbBase: "/u01/app/oracle" # Oracle base directory + crsAsmDeviceList: /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 # Comma-separated list of ASM device files + inventory: "/u01/app/oraInventory" # Inventory location + gridSwZipFile: "grid_home.zip" # Grid infrastructure software ZIP + dbSwZipFile: "db_home.zip" # Database software ZIP + sgaSize: "3G" # Size of the System Global Area + pgaSize: "1G" # Size of the Program Global Area + processes: 2000 # Oracle process limit + cpuCount: 4 # Number of CPUs to allocate + dbName: "PORCLCDB" # Oracle database name + hostSwStageLocation: /scratch/software/stage # Host location where Oracle software ZIPs are staged diff --git a/docs/oraclerestart/provisioning/oraclerestart_prov_loadbalancer.yaml b/docs/oraclerestart/provisioning/oraclerestart_prov_loadbalancer.yaml new file mode 100644 index 00000000..5bd64c6a --- /dev/null +++ b/docs/oraclerestart/provisioning/oraclerestart_prov_loadbalancer.yaml @@ -0,0 +1,114 @@ +# API version of the OracleRestart Custom Resource +apiVersion: database.oracle.com/v4 + +# Resource type being created +kind: OracleRestart + +# Metadata section to define the resource's name and namespace +metadata: + name: oraclerestart-sample # Name of the OracleRestart instance + namespace: orestart # Kubernetes namespace to deploy into + +spec: + # Instance details for Oracle Restart + instDetails: + name: dbmc1 # Logical name of the OracleRestart instance + hostSwLocation: /scratch/orestart/ # Host directory for Oracle GI HOME and Oracle RDBMS HOME + + # Worker nodes where the instance should be scheduled + workerNode: + - 10.0.10.58 # IP address of the target node for deployment + + # Environment variables in case you want to want to ignore prerequisite checks during the GI/RDBSMS Install + envVars: + - name: IGNORE_CRS_PREREQS # If set to true, it will use flags -ignorePreReq and -ignorePrereqFailure during the CRS Installation. Do not set this parameter in the .yaml file if you do not want to use the parameters -ignorePreReq and -ignorePrereqFailure during the CRS Installation. + value: "true" + - name: IGNORE_DB_PREREQS # If set to true, it will use flags -ignorePrereq and -ignorePrereqFailure during the DB Software Installation. Do not set this parameter in the .yaml file if you do not want to use the parameters -ignorePreReq and -ignorePrereqFailure during the DB Software Installation. + value: "true" + + # Load Balancer service configuration + lbService: + svcType: lbservice # Service type set to Load Balancer + + # Mapping between container and host ports + portMappings: + - port: 1521 # Port inside container (Oracle Listener port) + targetPort: 1521 # Target port for the service + protocol: TCP # Protocol type (TCP for Oracle) + - port: 6200 # Port inside container (Oracle ONS port) + targetPort: 6200 # Target port for the service + protocol: TCP # Protocol type (TCP for Oracle) + enableOns: "enable" # If you want to have ONS notification configured on the specified port. Default value is "enable" + + # ASM (Automatic Storage Management) storage configuration + asmStorageDetails: + disksBySize: + - storageSizeInGb: 50 # Each disk in this group has 50Gi of storage + diskNames: + - /dev/disk/by-partlabel/asm-disk1 # ASM disk device path 1 + - /dev/disk/by-partlabel/asm-disk2 # ASM disk device path 2 + + # SSH key secret used for remote access and automation + sshKeySecret: + name: ssh-key-secret # Kubernetes secret name + privKeySecretName: ssh-privkey # Private key inside secret + pubKeySecretName: ssh-pubkey # Public key inside secret + + # Secret containing DB user credentials + dbSecret: + name: db-user-pass-pkutl # Secret name + keyFileName: key.pem # Key file name inside secret + pwdFileName: pwdfile.enc # Password file name inside secret + + # Image for Oracle Restart container + image: dbocir/oracle/database-orestart:19.3.0-slim # <-- Replace with your image registry + + # Policy to pull image + # Use the value as "Always" if you want use the latest version of the image from the container registry. + # Use the value as "IfNotPresent" it the image is not already present locally. + imagePullPolicy: IfNotPresent + + # Optional service account for security context permissions + # serviceAccountName: oraclerestart # <-- Required for OpenShift; optional for vanilla Kubernetes + + # Service-specific database name and related details + serviceDetails: + name: soepdb # Name of the Oracle service (pluggable DB) + + # Resource requests and limits for memory and CPU + resources: + requests: + memory: "16Gi" # Minimum memory required + cpu: "2" # Minimum CPU required + limits: + memory: "16Gi" # Max memory allowed + cpu: "2" # Max CPU allowed + + # Kernel-level system control parameters for Oracle + securityContext: + sysctls: + - name: kernel.shmall + value: "2097152" + - name: kernel.sem + value: "250 32000 100 128" + - name: kernel.shmmax + value: "8589934592" + - name: kernel.shmmni + value: "4096" + + # Configuration parameters for Oracle Grid and Database homes + configParams: + gridHome: "/u01/app/19c/grid" # Oracle Grid home path + gridBase: "/u01/app/grid" # Grid base directory + dbHome: "/u01/app/oracle/product/19c/dbhome_1" # Oracle DB home path + dbBase: "/u01/app/oracle" # Oracle base directory + crsAsmDeviceList: /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 # Comma-separated list of ASM device files + inventory: "/u01/app/oraInventory" # Inventory location + gridSwZipFile: "grid_home.zip" # Grid infrastructure software ZIP + dbSwZipFile: "db_home.zip" # Database software ZIP + sgaSize: "3G" # Size of the System Global Area + pgaSize: "1G" # Size of the Program Global Area + processes: 2000 # Oracle process limit + cpuCount: 4 # Number of CPUs to allocate + dbName: "PORCLCDB" # Oracle database name + hostSwStageLocation: /scratch/software/stage # Host location where Oracle software ZIPs are staged, same location will be mounted inside the Oracle Restart Pod diff --git a/docs/oraclerestart/provisioning/oraclerestart_prov_multiple_diskgroups.txt b/docs/oraclerestart/provisioning/oraclerestart_prov_multiple_diskgroups.txt new file mode 100644 index 00000000..c1aac095 --- /dev/null +++ b/docs/oraclerestart/provisioning/oraclerestart_prov_multiple_diskgroups.txt @@ -0,0 +1,315 @@ +$ kubectl describe oraclerestarts.database.oracle.com/oraclerestart-sample -n orestart +Name: oraclerestart-sample +Namespace: orestart +Labels: +Annotations: OracleRestarts.database.oracle.com/old-spec: + {"instDetails":{"name":"dbmc1","hostSwLocation":"/scratch/orestart/","workerNode":["10.0.10.58"],"envVars":[{"name":"IGNORE_CRS_PREREQS","... +API Version: database.oracle.com/v4 +Kind: OracleRestart +Metadata: + Creation Timestamp: 2025-10-09T05:13:25Z + Finalizers: + database.oracle.com/oraclerestartfinalizer + Generation: 1 + Resource Version: 246221746 + UID: 31337564-9573-4c63-bccc-269e232ea9bb +Spec: + Asm Storage Details: + Disks By Size: + Disk Names: + /dev/disk/by-partlabel/asm-disk1 + /dev/disk/by-partlabel/asm-disk2 + /dev/disk/by-partlabel/asm-disk3 + /dev/disk/by-partlabel/asm-disk4 + /dev/disk/by-partlabel/asm-disk5 + /dev/disk/by-partlabel/asm-disk6 + /dev/disk/by-partlabel/asm-disk7 + /dev/disk/by-partlabel/asm-disk8 + Storage Size In Gb: 50 + Config Params: + Cpu Count: 4 + Crs Asm Device List: /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 + Crs Asm Disk Dg: +CRSDATA + Crs Asm Disk Dg Redundancy: external + Db Asm Device List: /dev/disk/by-partlabel/asm-disk3,/dev/disk/by-partlabel/asm-disk4 + Db Base: /u01/app/oracle + Db Char Set: AL32UTF8 + Db Data File Dest Dg: +DBDATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: +RECO + Db Response File: + Db Sw Zip File: db_home.zip + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Grid Sw Zip File: grid_home.zip + Host Sw Stage Location: /scratch/software/stage + Inventory: /u01/app/oraInventory + Pdb Name: TESTPDB + Pga Size: 1G + Processes: 2000 + Reco Asm Device List: /dev/disk/by-partlabel/asm-disk5,/dev/disk/by-partlabel/asm-disk6 + Redo Asm Device List: /dev/disk/by-partlabel/asm-disk7,/dev/disk/by-partlabel/asm-disk8 + Redo Asm Disk Dg: +REDO + Sga Size: 3G + Sw Mount Location: /u01 + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Enable Ons: enable + Image: localhost/oracle/database-orestart:19.3.0-slim + Image Pull Policy: IfNotPresent + Inst Details: + Env Vars: + Name: IGNORE_CRS_PREREQS + Value: true + Name: IGNORE_DB_PREREQS + Value: true + Host Sw Location: /scratch/orestart/ + Name: dbmc1 + Worker Node: + 10.0.10.58 + Lb Service: + Node Port Svc: + Name: dbmc1-service-nodeport + Port Mappings: + Node Port: 30007 + Port: 1521 + Protocol: TCP + Target Port: 1521 + Node Port: 30200 + Port: 6200 + Protocol: TCP + Target Port: 6200 + Svc Type: nodeport + Resources: + Limits: + Cpu: 2 + Memory: 16Gi + Requests: + Cpu: 2 + Memory: 16Gi + Security Context: + Sysctls: + Name: kernel.shmall + Value: 2097152 + Name: kernel.sem + Value: 250 32000 100 128 + Name: kernel.shmmax + Value: 8589934592 + Name: kernel.shmmni + Value: 4096 + Service Details: + Name: soepdb + Ssh Key Secret: + Key Mount Location: /mnt/.ssh + Name: ssh-key-secret + Priv Key Secret Name: ssh-privkey + Pub Key Secret Name: ssh-pubkey +Status: + Oracle Restart Nodes: + Name: dbmc1-0 + Node Details: + Instance State: OPEN + Pod State: AVAILABLE + Cluster State: HEALTHY + Mounted Devices: + /dev/disk/by-partlabel/asm-disk1 + /dev/disk/by-partlabel/asm-disk2 + /dev/disk/by-partlabel/asm-disk3 + /dev/disk/by-partlabel/asm-disk4 + /dev/disk/by-partlabel/asm-disk5 + /dev/disk/by-partlabel/asm-disk6 + /dev/disk/by-partlabel/asm-disk7 + /dev/disk/by-partlabel/asm-disk8 + State: AVAILABLE + Asm Details: + Diskgroup: + Disks: + /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2,/dev/disk/by-partlabel/asm-disk3,/dev/disk/by-partlabel/asm-disk4,/dev/disk/by-partlabel/asm-disk5,/dev/disk/by-partlabel/asm-disk6,/dev/disk/by-partlabel/asm-disk7,/dev/disk/by-partlabel/asm-disk8 + Name: CRSDATA + Redundancy: EXTERN + Disks: + /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2,/dev/disk/by-partlabel/asm-disk3,/dev/disk/by-partlabel/asm-disk4,/dev/disk/by-partlabel/asm-disk5,/dev/disk/by-partlabel/asm-disk6,/dev/disk/by-partlabel/asm-disk7,/dev/disk/by-partlabel/asm-disk8 + Name: REDO + Redundancy: EXTERN + Disks: + /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2,/dev/disk/by-partlabel/asm-disk3,/dev/disk/by-partlabel/asm-disk4,/dev/disk/by-partlabel/asm-disk5,/dev/disk/by-partlabel/asm-disk6,/dev/disk/by-partlabel/asm-disk7,/dev/disk/by-partlabel/asm-disk8 + Name: RECO + Redundancy: EXTERN + Disks: + /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2,/dev/disk/by-partlabel/asm-disk3,/dev/disk/by-partlabel/asm-disk4,/dev/disk/by-partlabel/asm-disk5,/dev/disk/by-partlabel/asm-disk6,/dev/disk/by-partlabel/asm-disk7,/dev/disk/by-partlabel/asm-disk8 + Name: DBDATA + Redundancy: EXTERN + Conditions: + Last Transition Time: 2025-10-09T05:26:31Z + Message: reconcile has been queued + Observed Generation: 1 + Reason: LastReconcileCycleQueued + Status: True + Type: ReconcileQueued + Last Transition Time: 2025-10-09T05:37:15Z + Message: reconcile completed successfully + Observed Generation: 1 + Reason: LastReconcileCycleCompleted + Status: True + Type: ReconcileComplete + Config Params: + Crs Asm Device List: /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 + Crs Asm Disk Dg: +CRSDATA + Crs Asm Disk Dg Redundancy: external + Db Asm Device List: /dev/disk/by-partlabel/asm-disk3,/dev/disk/by-partlabel/asm-disk4 + Db Base: /u01/app/oracle + Db Data File Dest Dg: +DBDATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: +RECO + Db Response File: + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Inventory: /u01/app/oraInventory + Reco Asm Device List: /dev/disk/by-partlabel/asm-disk5,/dev/disk/by-partlabel/asm-disk6 + Connect String: dbmc1-0:1521/PORCLCDB + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Db State: OPEN + External Connect String: 129.146.0.149:30007/soepdb + Pdb Connect String: dbmc1-0:1521/ORCLPDB + Release Update: 19.28.0.0.0 + Role: PRIMARY + Service Details: + Name: soepdb + Svc State: service soepdb is running + State: AVAILABLE +Events: + + + + +[grid@dbmc1-0 ~]$ ls -rlt /dev/disk/by-partlabel/asm-disk* +brw-rw----. 1 grid asmadmin 8, 65 Oct 9 05:21 /dev/disk/by-partlabel/asm-disk6 +brw-rw----. 1 grid asmadmin 8, 49 Oct 9 05:21 /dev/disk/by-partlabel/asm-disk2 +brw-rw----. 1 grid asmadmin 8, 145 Oct 9 05:38 /dev/disk/by-partlabel/asm-disk8 +brw-rw----. 1 grid asmadmin 8, 129 Oct 9 05:38 /dev/disk/by-partlabel/asm-disk1 +brw-rw----. 1 grid asmadmin 8, 33 Oct 9 05:38 /dev/disk/by-partlabel/asm-disk5 +brw-rw----. 1 grid asmadmin 8, 97 Oct 9 05:38 /dev/disk/by-partlabel/asm-disk7 +brw-rw----. 1 grid asmadmin 8, 113 Oct 9 05:38 /dev/disk/by-partlabel/asm-disk3 +brw-rw----. 1 grid asmadmin 8, 81 Oct 9 05:38 /dev/disk/by-partlabel/asm-disk4 +[grid@dbmc1-0 ~]$ +[grid@dbmc1-0 ~]$ export ORACLE_SID=+ASM +[grid@dbmc1-0 ~]$ +[grid@dbmc1-0 ~]$ asmcmd lsdg +State Type Rebal Sector Logical_Sector Block AU Total_MB Free_MB Req_mir_free_MB Usable_file_MB Offline_disks Voting_files Name +MOUNTED EXTERN N 512 512 4096 4194304 102392 102276 0 102276 0 N CRSDATA/ +MOUNTED EXTERN N 512 512 4096 1048576 102396 99345 0 99345 0 N DBDATA/ +MOUNTED EXTERN N 512 512 4096 1048576 102396 102329 0 102329 0 N RECO/ +MOUNTED EXTERN N 512 512 4096 1048576 102396 99226 0 99226 0 N REDO/ +[grid@dbmc1-0 ~]$ +[grid@dbmc1-0 ~]$ asmcmd lsdsk + +Path +/dev/disk/by-partlabel/asm-disk1 +/dev/disk/by-partlabel/asm-disk2 +/dev/disk/by-partlabel/asm-disk3 +/dev/disk/by-partlabel/asm-disk4 +/dev/disk/by-partlabel/asm-disk5 +/dev/disk/by-partlabel/asm-disk6 +/dev/disk/by-partlabel/asm-disk7 +/dev/disk/by-partlabel/asm-disk8 +[grid@dbmc1-0 ~]$ +[grid@dbmc1-0 ~]$ sqlplus "/ as sysdba" + +SQL*Plus: Release 19.0.0.0.0 - Production on Thu Oct 9 05:38:48 2025 +Version 19.28.0.0.0 + +Copyright (c) 1982, 2025, Oracle. All rights reserved. + + +Connected to: +Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production +Version 19.28.0.0.0 + +SQL> set lines 200 +SQL> col DISKGROUP_NAME format a20 +SQL> col PATH format a50 +SQL> SELECT + dg.NAME AS DISKGROUP_NAME, + d.PATH, + d.MOUNT_STATUS, + d.HEADER_STATUS, + d.STATE, + d.TOTAL_MB, + d.FREE_MB +FROM + V$ASM_DISK d +JOIN + V$ASM_DISKGROUP dg ON d.GROUP_NUMBER = dg.GROUP_NUMBER +ORDER BY + dg.NAME, d.DISK_NUMBER; 2 3 4 5 6 7 8 9 10 11 12 13 14 + +DISKGROUP_NAME PATH MOUNT_S HEADER_STATU STATE TOTAL_MB FREE_MB +-------------------- -------------------------------------------------- ------- ------------ -------- ---------- ---------- +CRSDATA /dev/disk/by-partlabel/asm-disk1 CACHED MEMBER NORMAL 51196 51136 +CRSDATA /dev/disk/by-partlabel/asm-disk2 CACHED MEMBER NORMAL 51196 51140 +DBDATA /dev/disk/by-partlabel/asm-disk3 CACHED MEMBER NORMAL 51198 49672 +DBDATA /dev/disk/by-partlabel/asm-disk4 CACHED MEMBER NORMAL 51198 49673 +RECO /dev/disk/by-partlabel/asm-disk5 CACHED MEMBER NORMAL 51198 51165 +RECO /dev/disk/by-partlabel/asm-disk6 CACHED MEMBER NORMAL 51198 51164 +REDO /dev/disk/by-partlabel/asm-disk7 CACHED MEMBER NORMAL 51198 49616 +REDO /dev/disk/by-partlabel/asm-disk8 CACHED MEMBER NORMAL 51198 49610 + +8 rows selected. + + + + +[root@dbmc1-0 rac-work-dir]# su - oracle +Last login: Thu Oct 9 05:39:36 UTC 2025 on pts/0 +[oracle@dbmc1-0 ~]$ +[oracle@dbmc1-0 ~]$ +[oracle@dbmc1-0 ~]$ export ORACLE_SID=PORCLCDB +[oracle@dbmc1-0 ~]$ +[oracle@dbmc1-0 ~]$ sqlplus "/ as sysdba" + +SQL*Plus: Release 19.0.0.0.0 - Production on Thu Oct 9 05:39:43 2025 +Version 19.28.0.0.0 + +Copyright (c) 1982, 2025, Oracle. All rights reserved. + + +Connected to: +Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production +Version 19.28.0.0.0 + +SQL> show pdbs + + CON_ID CON_NAME OPEN MODE RESTRICTED +---------- ------------------------------ ---------- ---------- + 2 PDB$SEED READ ONLY NO + 3 TESTPDB READ WRITE NO +SQL> show parameter DB_CREATE_ONLINE_LOG_DEST_1 + +NAME TYPE VALUE +------------------------------------ ----------- ------------------------------ +db_create_online_log_dest_1 string +REDO +SQL> show parameter db_create_file_dest + +NAME TYPE VALUE +------------------------------------ ----------- ------------------------------ +db_create_file_dest string +DBDATA +SQL> show parameter db_recovery_file_dest + +NAME TYPE VALUE +------------------------------------ ----------- ------------------------------ +db_recovery_file_dest string +RECO +db_recovery_file_dest_size big integer 20058M +SQL> diff --git a/docs/oraclerestart/provisioning/oraclerestart_prov_multiple_diskgroups.yaml b/docs/oraclerestart/provisioning/oraclerestart_prov_multiple_diskgroups.yaml new file mode 100644 index 00000000..d99c4abf --- /dev/null +++ b/docs/oraclerestart/provisioning/oraclerestart_prov_multiple_diskgroups.yaml @@ -0,0 +1,131 @@ +# API version of the OracleRestart Custom Resource +apiVersion: database.oracle.com/v4 + +# Resource type being created +kind: OracleRestart + +# Metadata section to define the resource's name and namespace +metadata: + name: oraclerestart-sample # Name of the OracleRestart instance + namespace: orestart # Kubernetes namespace to deploy into + +spec: + # Instance details for Oracle Restart + instDetails: + name: dbmc1 # Logical name of the OracleRestart instance + hostSwLocation: /scratch/orestart/ # Host directory for Oracle GI HOME and Oracle RDBMS HOME + + # Worker nodes where the instance should be scheduled + workerNode: + - 10.0.10.58 # IP address of the target node for deployment + + # Environment variables in case you want to want to ignore prerequisite checks during the GI/RDBSMS Install + envVars: + - name: IGNORE_CRS_PREREQS # If set to true, it will use flags -ignorePreReq and -ignorePrereqFailure during the CRS Installation. Do not set this parameter in the .yaml file if you do not want to use the parameters -ignorePreReq and -ignorePrereqFailure during the CRS Installation. + value: "true" + - name: IGNORE_DB_PREREQS # If set to true, it will use flags -ignorePrereq and -ignorePrereqFailure during the DB Software Installation. Do not set this parameter in the .yaml file if you do not want to use the parameters -ignorePreReq and -ignorePrereqFailure during the DB Software Installation. + value: "true" + + # NodePort service configuration for exposing DB port externally + nodePortSvc: + name: dbmc1-service-nodeport # Name of the Kubernetes Service + svcType: nodeport # Service type (NodePort to expose externally) + + # Mapping between container and host ports + portMappings: + - port: 1521 # Port inside container (Oracle Listener port) + targetPort: 1521 # Target port for the service + protocol: TCP # Protocol type (TCP for Oracle) + nodePort: 30007 # Host port exposed on worker node (must be in NodePort range) + - port: 6200 # Port inside container (for ONS) + targetPort: 6200 # Target port for the service + protocol: TCP # Protocol type (TCP for Oracle) + nodePort: 30200 # Host port exposed on worker node (must be in NodePort range) + enableOns: "enable" # If you want to have ONS notification configured on the specified port. Default value is "enable" + + # ASM (Automatic Storage Management) storage configuration + asmStorageDetails: + disksBySize: + - storageSizeInGb: 50 # Each disk in this group has 50Gi of storage + diskNames: + - /dev/disk/by-partlabel/asm-disk1 # ASM disk device path 1 + - /dev/disk/by-partlabel/asm-disk2 # ASM disk device path 2 + - /dev/disk/by-partlabel/asm-disk3 # ASM disk device path 3 + - /dev/disk/by-partlabel/asm-disk4 # ASM disk device path 4 + - /dev/disk/by-partlabel/asm-disk5 # ASM disk device path 5 + - /dev/disk/by-partlabel/asm-disk6 # ASM disk device path 6 + - /dev/disk/by-partlabel/asm-disk7 # ASM disk device path 7 + - /dev/disk/by-partlabel/asm-disk8 # ASM disk device path 8 + + # SSH key secret used for remote access and automation + sshKeySecret: + name: ssh-key-secret # Kubernetes secret name + privKeySecretName: ssh-privkey # Private key inside secret + pubKeySecretName: ssh-pubkey # Public key inside secret + + # Secret containing DB user credentials + dbSecret: + name: db-user-pass-pkutl # Secret name + keyFileName: key.pem # Key file name inside secret + pwdFileName: pwdfile.enc # Password file name inside secret + + # Image for Oracle Restart container + image: dbocir/oracle/database-orestart:19.3.0-slim # <-- Replace with your image registry + + # Policy to pull image + # Use the value as "Always" if you want use the latest version of the image from the container registry. + # Use the value as "IfNotPresent" it the image is not already present locally. + imagePullPolicy: IfNotPresent + + # Optional service account for security context permissions + # serviceAccountName: oraclerestart # <-- Required for OpenShift; optional for vanilla Kubernetes + + # Service-specific database name and related details + serviceDetails: + name: soepdb # Name of the Oracle service (pluggable DB) + + # Resource requests and limits for memory and CPU + resources: + requests: + memory: "16Gi" # Minimum memory required + cpu: "2" # Minimum CPU required + limits: + memory: "16Gi" # Max memory allowed + cpu: "2" # Max CPU allowed + + # Kernel-level system control parameters for Oracle + securityContext: + sysctls: + - name: kernel.shmall + value: "2097152" + - name: kernel.sem + value: "250 32000 100 128" + - name: kernel.shmmax + value: "8589934592" + - name: kernel.shmmni + value: "4096" + + # Configuration parameters for Oracle Grid and Database homes + configParams: + gridHome: "/u01/app/19c/grid" # Oracle Grid home path + gridBase: "/u01/app/grid" # Grid base directory + dbHome: "/u01/app/oracle/product/19c/dbhome_1" # Oracle DB home path + dbBase: "/u01/app/oracle" # Oracle base directory + crsAsmDeviceList: /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 # Comma-separated list of ASM devices for CRSDATA Diskgroup + dbAsmDeviceList: /dev/disk/by-partlabel/asm-disk3,/dev/disk/by-partlabel/asm-disk4 # Comma-separated list of ASM device files for DBDATA Diskgroup for database files + recoAsmDeviceList: /dev/disk/by-partlabel/asm-disk5,/dev/disk/by-partlabel/asm-disk6 # Comma-separated list of ASM device files for RECO Diskgroup for Recovery Area files + redoAsmDeviceList: /dev/disk/by-partlabel/asm-disk7,/dev/disk/by-partlabel/asm-disk8 # Comma-separated list of ASM device files for REDO Diskgroup for Redo files + crsAsmDiskDg: "+CRSDATA" # Name of the Diskgroup for CRS Files + dbRecoveryFileDest: "+RECO" # Name of the Diskgroup for Recovery Files + dbDataFileDestDg: "+DBDATA" # Name of the Diskgroup for Database Files + redoAsmDiskDg: "+REDO" # Name of the Diskgroup for Redo Files + inventory: "/u01/app/oraInventory" # Inventory location + gridSwZipFile: "grid_home.zip" # Grid infrastructure software ZIP + dbSwZipFile: "db_home.zip" # Database software ZIP + sgaSize: "3G" # Size of the System Global Area + pgaSize: "1G" # Size of the Program Global Area + processes: 2000 # Oracle process limit + cpuCount: 4 # Number of CPUs to allocate + dbName: "PORCLCDB" # Oracle database name + pdbName: "TESTPDB" # PDB name, in case you want to explicitly specify the PDB Name + hostSwStageLocation: /scratch/software/stage # Host location where Oracle software ZIPs are staged, same location will be mounted inside the Oracle Restart Pod diff --git a/docs/oraclerestart/provisioning/oraclerestart_prov_multiple_diskgroups_with_redundancy.txt b/docs/oraclerestart/provisioning/oraclerestart_prov_multiple_diskgroups_with_redundancy.txt new file mode 100644 index 00000000..cabd436e --- /dev/null +++ b/docs/oraclerestart/provisioning/oraclerestart_prov_multiple_diskgroups_with_redundancy.txt @@ -0,0 +1,335 @@ +$ kubectl describe oraclerestarts.database.oracle.com/oraclerestart-sample -n orestart +Name: oraclerestart-sample +Namespace: orestart +Labels: +Annotations: OracleRestarts.database.oracle.com/old-spec: + {"instDetails":{"name":"dbmc1","hostSwLocation":"/scratch/orestart/","workerNode":["10.0.10.58"],"envVars":[{"name":"IGNORE_CRS_PREREQS","... +API Version: database.oracle.com/v4 +Kind: OracleRestart +Metadata: + Creation Timestamp: 2025-10-09T02:27:24Z + Finalizers: + database.oracle.com/oraclerestartfinalizer + Generation: 1 + Resource Version: 246182750 + UID: 93e5d361-47f7-4be0-a953-b23010228ebd +Spec: + Asm Storage Details: + Disks By Size: + Disk Names: + /dev/disk/by-partlabel/asm-disk1 + /dev/disk/by-partlabel/asm-disk2 + Storage Size In Gb: 50 + Disk Names: + /dev/disk/by-partlabel/asm-disk3 + /dev/disk/by-partlabel/asm-disk4 + /dev/disk/by-partlabel/asm-disk5 + /dev/disk/by-partlabel/asm-disk6 + Storage Size In Gb: 100 + Disk Names: + /dev/disk/by-partlabel/asm-disk7 + /dev/disk/by-partlabel/asm-disk8 + Storage Size In Gb: 100 + Disk Names: + /dev/disk/by-partlabel/asm-disk9 + /dev/disk/by-partlabel/asm-disk10 + Storage Size In Gb: 50 + Config Params: + Cpu Count: 4 + Crs Asm Device List: /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 + Crs Asm Disk Dg: +CRSDATA + Crs Asm Disk Dg Redundancy: EXTERNAL + Db Asm Device List: /dev/disk/by-partlabel/asm-disk3,/dev/disk/by-partlabel/asm-disk4,/dev/disk/by-partlabel/asm-disk5,/dev/disk/by-partlabel/asm-disk6 + Db Asm Disk Dg Redundancy: NORMAL + Db Base: /u01/app/oracle + Db Char Set: AL32UTF8 + Db Data File Dest Dg: +DBDATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: +RECO + Db Response File: + Db Sw Zip File: db_home.zip + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Grid Sw Zip File: grid_home.zip + Host Sw Stage Location: /scratch/software/stage + Inventory: /u01/app/oraInventory + Pdb Name: TESTPDB + Pga Size: 1G + Processes: 2000 + Reco Asm Device List: /dev/disk/by-partlabel/asm-disk7,/dev/disk/by-partlabel/asm-disk8 + Reco Asm Disk Dg Redundancy: EXTERNAL + Redo Asm Device List: /dev/disk/by-partlabel/asm-disk9,/dev/disk/by-partlabel/asm-disk10 + Redo Asm Disk Dg: +REDO + Redo Asm Disk Dg Redundancy: EXTERNAL + Sga Size: 3G + Sw Mount Location: /u01 + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Enable Ons: enable + Image: localhost/oracle/database-orestart:19.3.0-slim + Image Pull Policy: IfNotPresent + Inst Details: + Env Vars: + Name: IGNORE_CRS_PREREQS + Value: true + Name: IGNORE_DB_PREREQS + Value: true + Host Sw Location: /scratch/orestart/ + Name: dbmc1 + Worker Node: + 10.0.10.58 + Lb Service: + Node Port Svc: + Name: dbmc1-service-nodeport + Port Mappings: + Node Port: 30007 + Port: 1521 + Protocol: TCP + Target Port: 1521 + Node Port: 30200 + Port: 6200 + Protocol: TCP + Target Port: 6200 + Svc Type: nodeport + Resources: + Limits: + Cpu: 2 + Memory: 16Gi + Requests: + Cpu: 2 + Memory: 16Gi + Security Context: + Sysctls: + Name: kernel.shmall + Value: 2097152 + Name: kernel.sem + Value: 250 32000 100 128 + Name: kernel.shmmax + Value: 8589934592 + Name: kernel.shmmni + Value: 4096 + Service Details: + Name: soepdb + Ssh Key Secret: + Key Mount Location: /mnt/.ssh + Name: ssh-key-secret + Priv Key Secret Name: ssh-privkey + Pub Key Secret Name: ssh-pubkey +Status: + Oracle Restart Nodes: + Name: dbmc1-0 + Node Details: + Instance State: OPEN + Pod State: AVAILABLE + Cluster State: HEALTHY + Mounted Devices: + /dev/disk/by-partlabel/asm-disk1 + /dev/disk/by-partlabel/asm-disk2 + /dev/disk/by-partlabel/asm-disk3 + /dev/disk/by-partlabel/asm-disk4 + /dev/disk/by-partlabel/asm-disk5 + /dev/disk/by-partlabel/asm-disk6 + /dev/disk/by-partlabel/asm-disk7 + /dev/disk/by-partlabel/asm-disk8 + /dev/disk/by-partlabel/asm-disk9 + /dev/disk/by-partlabel/asm-disk10 + State: AVAILABLE + Asm Details: + Diskgroup: + Disks: + /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk10,/dev/disk/by-partlabel/asm-disk2,/dev/disk/by-partlabel/asm-disk3,/dev/disk/by-partlabel/asm-disk4,/dev/disk/by-partlabel/asm-disk5,/dev/disk/by-partlabel/asm-disk6,/dev/disk/by-partlabel/asm-disk7,/dev/disk/by-partlabel/asm-disk8,/dev/disk/by-partlabel/asm-disk9 + Name: CRSDATA + Redundancy: EXTERN + Disks: + /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk10,/dev/disk/by-partlabel/asm-disk2,/dev/disk/by-partlabel/asm-disk3,/dev/disk/by-partlabel/asm-disk4,/dev/disk/by-partlabel/asm-disk5,/dev/disk/by-partlabel/asm-disk6,/dev/disk/by-partlabel/asm-disk7,/dev/disk/by-partlabel/asm-disk8,/dev/disk/by-partlabel/asm-disk9 + Name: REDO + Redundancy: EXTERN + Disks: + /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk10,/dev/disk/by-partlabel/asm-disk2,/dev/disk/by-partlabel/asm-disk3,/dev/disk/by-partlabel/asm-disk4,/dev/disk/by-partlabel/asm-disk5,/dev/disk/by-partlabel/asm-disk6,/dev/disk/by-partlabel/asm-disk7,/dev/disk/by-partlabel/asm-disk8,/dev/disk/by-partlabel/asm-disk9 + Name: RECO + Redundancy: EXTERN + Disks: + /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk10,/dev/disk/by-partlabel/asm-disk2,/dev/disk/by-partlabel/asm-disk3,/dev/disk/by-partlabel/asm-disk4,/dev/disk/by-partlabel/asm-disk5,/dev/disk/by-partlabel/asm-disk6,/dev/disk/by-partlabel/asm-disk7,/dev/disk/by-partlabel/asm-disk8,/dev/disk/by-partlabel/asm-disk9 + Name: DBDATA + Redundancy: NORMAL + Conditions: + Last Transition Time: 2025-10-09T02:40:30Z + Message: reconcile has been queued + Observed Generation: 1 + Reason: LastReconcileCycleQueued + Status: True + Type: ReconcileQueued + Last Transition Time: 2025-10-09T03:32:32Z + Message: reconcile completed successfully + Observed Generation: 1 + Reason: LastReconcileCycleCompleted + Status: True + Type: ReconcileComplete + Config Params: + Crs Asm Device List: /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 + Crs Asm Disk Dg: +CRSDATA + Crs Asm Disk Dg Redundancy: EXTERNAL + Db Asm Device List: /dev/disk/by-partlabel/asm-disk3,/dev/disk/by-partlabel/asm-disk4,/dev/disk/by-partlabel/asm-disk5,/dev/disk/by-partlabel/asm-disk6 + Db Asm Disk Dg Redundancy: NORMAL + Db Base: /u01/app/oracle + Db Data File Dest Dg: +DBDATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: +RECO + Db Response File: + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Inventory: /u01/app/oraInventory + Reco Asm Device List: /dev/disk/by-partlabel/asm-disk7,/dev/disk/by-partlabel/asm-disk8 + Connect String: dbmc1-0:1521/PORCLCDB + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Db State: OPEN + External Connect String: 129.146.0.149:30007/soepdb + Pdb Connect String: dbmc1-0:1521/ORCLPDB + Release Update: 19.28.0.0.0 + Role: PRIMARY + Service Details: + Name: soepdb + Svc State: service soepdb is running + State: AVAILABLE +Events: + + + + + +[grid@dbmc1-0 ~]$ ls -rlt /dev/disk/by-partlabel/asm-disk* +brw-rw----. 1 grid asmadmin 8, 49 Oct 9 02:35 /dev/disk/by-partlabel/asm-disk2 +brw-rw----. 1 grid asmadmin 8, 145 Oct 9 02:56 /dev/disk/by-partlabel/asm-disk8 +brw-rw----. 1 grid asmadmin 8, 129 Oct 9 03:32 /dev/disk/by-partlabel/asm-disk6 +brw-rw----. 1 grid asmadmin 8, 177 Oct 9 03:35 /dev/disk/by-partlabel/asm-disk10 +brw-rw----. 1 grid asmadmin 8, 161 Oct 9 03:35 /dev/disk/by-partlabel/asm-disk9 +brw-rw----. 1 grid asmadmin 8, 33 Oct 9 03:35 /dev/disk/by-partlabel/asm-disk1 +brw-rw----. 1 grid asmadmin 8, 65 Oct 9 03:35 /dev/disk/by-partlabel/asm-disk7 +brw-rw----. 1 grid asmadmin 8, 113 Oct 9 03:35 /dev/disk/by-partlabel/asm-disk5 +brw-rw----. 1 grid asmadmin 8, 81 Oct 9 03:35 /dev/disk/by-partlabel/asm-disk4 +brw-rw----. 1 grid asmadmin 8, 97 Oct 9 03:35 /dev/disk/by-partlabel/asm-disk3 +[grid@dbmc1-0 ~]$ export ORACLE_SID=+ASM +[grid@dbmc1-0 ~]$ asmcmd lsdg +State Type Rebal Sector Logical_Sector Block AU Total_MB Free_MB Req_mir_free_MB Usable_file_MB Offline_disks Voting_files Name +MOUNTED EXTERN N 512 512 4096 4194304 102392 102276 0 102276 0 N CRSDATA/ +MOUNTED NORMAL N 512 512 4096 1048576 204792 198553 51198 73677 0 N DBDATA/ +MOUNTED EXTERN N 512 512 4096 1048576 102396 102310 0 102310 0 N RECO/ +MOUNTED EXTERN N 512 512 4096 1048576 102396 99226 0 99226 0 N REDO/ +[grid@dbmc1-0 ~]$ +[grid@dbmc1-0 ~]$ asmcmd lsdsk +Path +/dev/disk/by-partlabel/asm-disk1 +/dev/disk/by-partlabel/asm-disk10 +/dev/disk/by-partlabel/asm-disk2 +/dev/disk/by-partlabel/asm-disk3 +/dev/disk/by-partlabel/asm-disk4 +/dev/disk/by-partlabel/asm-disk5 +/dev/disk/by-partlabel/asm-disk6 +/dev/disk/by-partlabel/asm-disk7 +/dev/disk/by-partlabel/asm-disk8 +/dev/disk/by-partlabel/asm-disk9 +[grid@dbmc1-0 ~]$ +[grid@dbmc1-0 ~]$ +[grid@dbmc1-0 ~]$ sqlplus "/ as sysdba" + +SQL*Plus: Release 19.0.0.0.0 - Production on Thu Oct 9 03:35:52 2025 +Version 19.28.0.0.0 + +Copyright (c) 1982, 2025, Oracle. All rights reserved. + + +Connected to: +Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production +Version 19.28.0.0.0 + +SQL> set lines 200 +SQL> col DISKGROUP_NAME format a20 +SQL> col PATH format a50 +SQL> SELECT + dg.NAME AS DISKGROUP_NAME, + d.PATH, + d.MOUNT_STATUS, + d.HEADER_STATUS, + d.STATE, + d.TOTAL_MB, + d.FREE_MB +FROM + V$ASM_DISK d +JOIN + V$ASM_DISKGROUP dg ON d.GROUP_NUMBER = dg.GROUP_NUMBER +ORDER BY + dg.NAME, d.DISK_NUMBER; 2 3 4 5 6 7 8 9 10 11 12 13 14 + +DISKGROUP_NAME PATH MOUNT_S HEADER_STATU STATE TOTAL_MB FREE_MB +-------------------- -------------------------------------------------- ------- ------------ -------- ---------- ---------- +CRSDATA /dev/disk/by-partlabel/asm-disk1 CACHED MEMBER NORMAL 51196 51132 +CRSDATA /dev/disk/by-partlabel/asm-disk2 CACHED MEMBER NORMAL 51196 51144 +DBDATA /dev/disk/by-partlabel/asm-disk3 CACHED MEMBER NORMAL 51198 49635 +DBDATA /dev/disk/by-partlabel/asm-disk4 CACHED MEMBER NORMAL 51198 49634 +DBDATA /dev/disk/by-partlabel/asm-disk5 CACHED MEMBER NORMAL 51198 49644 +DBDATA /dev/disk/by-partlabel/asm-disk6 CACHED MEMBER NORMAL 51198 49640 +RECO /dev/disk/by-partlabel/asm-disk7 CACHED MEMBER NORMAL 51198 51152 +RECO /dev/disk/by-partlabel/asm-disk8 CACHED MEMBER NORMAL 51198 51158 +REDO /dev/disk/by-partlabel/asm-disk9 CACHED MEMBER NORMAL 51198 49610 +REDO /dev/disk/by-partlabel/asm-disk10 CACHED MEMBER NORMAL 51198 49616 + +10 rows selected. + +SQL> + + + + + +[oracle@dbmc1-0 ~]$ export ORACLE_SID=PORCLCDB +[oracle@dbmc1-0 ~]$ sqlplus "/ as sysdba" + +SQL*Plus: Release 19.0.0.0.0 - Production on Thu Oct 9 03:45:27 2025 +Version 19.28.0.0.0 + +Copyright (c) 1982, 2025, Oracle. All rights reserved. + + +Connected to: +Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production +Version 19.28.0.0.0 + +SQL> +SQL> show pdbs + + CON_ID CON_NAME OPEN MODE RESTRICTED +---------- ------------------------------ ---------- ---------- + 2 PDB$SEED READ ONLY NO + 3 TESTPDB READ WRITE NO +SQL> +SQL> show parameter DB_CREATE_ONLINE_LOG_DEST_1 + +NAME TYPE VALUE +------------------------------------ ----------- ------------------------------ +db_create_online_log_dest_1 string +REDO +SQL> +SQL> show parameter db_create_file_dest + +NAME TYPE VALUE +------------------------------------ ----------- ------------------------------ +db_create_file_dest string +DBDATA +SQL> +SQL> show parameter db_recovery_file_dest + +NAME TYPE VALUE +------------------------------------ ----------- ------------------------------ +db_recovery_file_dest string +RECO +db_recovery_file_dest_size big integer 20058M \ No newline at end of file diff --git a/docs/oraclerestart/provisioning/oraclerestart_prov_multiple_diskgroups_with_redundancy.yaml b/docs/oraclerestart/provisioning/oraclerestart_prov_multiple_diskgroups_with_redundancy.yaml new file mode 100644 index 00000000..3fc9c56f --- /dev/null +++ b/docs/oraclerestart/provisioning/oraclerestart_prov_multiple_diskgroups_with_redundancy.yaml @@ -0,0 +1,143 @@ +# API version of the OracleRestart Custom Resource +apiVersion: database.oracle.com/v4 + +# Resource type being created +kind: OracleRestart + +# Metadata section to define the resource's name and namespace +metadata: + name: oraclerestart-sample # Name of the OracleRestart instance + namespace: orestart # Kubernetes namespace to deploy into + +spec: + # Instance details for Oracle Restart + instDetails: + name: dbmc1 # Logical name of the OracleRestart instance + hostSwLocation: /scratch/orestart/ # Host directory for Oracle GI HOME and Oracle RDBMS HOME + + # Worker nodes where the instance should be scheduled + workerNode: + - 10.0.10.58 # IP address of the target node for deployment + + # Environment variables in case you want to want to ignore prerequisite checks during the GI/RDBSMS Install + envVars: + - name: IGNORE_CRS_PREREQS # If set to true, it will use flags -ignorePreReq and -ignorePrereqFailure during the CRS Installation. Do not set this parameter in the .yaml file if you do not want to use the parameters -ignorePreReq and -ignorePrereqFailure during the CRS Installation. + value: "true" + - name: IGNORE_DB_PREREQS # If set to true, it will use flags -ignorePrereq and -ignorePrereqFailure during the DB Software Installation. Do not set this parameter in the .yaml file if you do not want to use the parameters -ignorePreReq and -ignorePrereqFailure during the DB Software Installation. + value: "true" + + # NodePort service configuration for exposing DB port externally + nodePortSvc: + name: dbmc1-service-nodeport # Name of the Kubernetes Service + svcType: nodeport # Service type (NodePort to expose externally) + + # Mapping between container and host ports + portMappings: + - port: 1521 # Port inside container (Oracle Listener port) + targetPort: 1521 # Target port for the service + protocol: TCP # Protocol type (TCP for Oracle) + nodePort: 30007 # Host port exposed on worker node (must be in NodePort range) + - port: 6200 # Port inside container (for ONS) + targetPort: 6200 # Target port for the service + protocol: TCP # Protocol type (TCP for Oracle) + nodePort: 30200 # Host port exposed on worker node (must be in NodePort range) + enableOns: "enable" # If you want to have ONS notification configured on the specified port. Default value is "enable" + + # ASM (Automatic Storage Management) storage configuration + asmStorageDetails: + disksBySize: + - storageSizeInGb: 50 # Each disk in this group has 50Gi of storage, will be used for CRSDATA Diskgroup + diskNames: + - /dev/disk/by-partlabel/asm-disk1 # ASM disk device path 1 + - /dev/disk/by-partlabel/asm-disk2 # ASM disk device path 2 + - storageSizeInGb: 100 # Each disk in this group has 100Gi of storage, will be used for DATA Diskgroup + diskNames: + - /dev/disk/by-partlabel/asm-disk3 # ASM disk device path 3 + - /dev/disk/by-partlabel/asm-disk4 # ASM disk device path 4 + - /dev/disk/by-partlabel/asm-disk5 # ASM disk device path 5 + - /dev/disk/by-partlabel/asm-disk6 # ASM disk device path 6 + - storageSizeInGb: 100 # Each disk in this group has 100Gi of storage, will be used for RECO Diskgroup + diskNames: + - /dev/disk/by-partlabel/asm-disk7 # ASM disk device path 7 + - /dev/disk/by-partlabel/asm-disk8 # ASM disk device path 8 + - storageSizeInGb: 50 # Each disk in this group has 50Gi of storage, will be used for REDO Diskgroup + diskNames: + - /dev/disk/by-partlabel/asm-disk9 # ASM disk device path 9 + - /dev/disk/by-partlabel/asm-disk10 # ASM disk device path 10 + + # SSH key secret used for remote access and automation + sshKeySecret: + name: ssh-key-secret # Kubernetes secret name + privKeySecretName: ssh-privkey # Private key inside secret + pubKeySecretName: ssh-pubkey # Public key inside secret + + # Secret containing DB user credentials + dbSecret: + name: db-user-pass-pkutl # Secret name + keyFileName: key.pem # Key file name inside secret + pwdFileName: pwdfile.enc # Password file name inside secret + + # Image for Oracle Restart container + image: dbocir/oracle/database-orestart:19.3.0-slim # <-- Replace with your image registry + + # Policy to pull image + # Use the value as "Always" if you want use the latest version of the image from the container registry. + # Use the value as "IfNotPresent" it the image is not already present locally. + imagePullPolicy: IfNotPresent + + # Optional service account for security context permissions + # serviceAccountName: oraclerestart # <-- Required for OpenShift; optional for vanilla Kubernetes + + # Service-specific database name and related details + serviceDetails: + name: soepdb # Name of the Oracle service (pluggable DB) + + # Resource requests and limits for memory and CPU + resources: + requests: + memory: "16Gi" # Minimum memory required + cpu: "2" # Minimum CPU required + limits: + memory: "16Gi" # Max memory allowed + cpu: "2" # Max CPU allowed + + # Kernel-level system control parameters for Oracle + securityContext: + sysctls: + - name: kernel.shmall + value: "2097152" + - name: kernel.sem + value: "250 32000 100 128" + - name: kernel.shmmax + value: "8589934592" + - name: kernel.shmmni + value: "4096" + + # Configuration parameters for Oracle Grid and Database homes + configParams: + gridHome: "/u01/app/19c/grid" # Oracle Grid home path + gridBase: "/u01/app/grid" # Grid base directory + dbHome: "/u01/app/oracle/product/19c/dbhome_1" # Oracle DB home path + dbBase: "/u01/app/oracle" # Oracle base directory + crsAsmDiskDg: "+CRSDATA" # Name of the Diskgroup for CRS Files + dbDataFileDestDg: "+DBDATA" # Name of the Diskgroup for Database Files + dbRecoveryFileDest: "+RECO" # Name of the Diskgroup for Recovery Files + redoAsmDiskDg: "+REDO" # Name of the Diskgroup for Redo Files + crsAsmDeviceList: /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 # Comma-separated list of ASM devices for CRSDATA Diskgroup + dbAsmDeviceList: /dev/disk/by-partlabel/asm-disk3,/dev/disk/by-partlabel/asm-disk4,/dev/disk/by-partlabel/asm-disk5,/dev/disk/by-partlabel/asm-disk6 # Comma-separated list of ASM device files for DBDATA Diskgroup for database files + recoAsmDeviceList: /dev/disk/by-partlabel/asm-disk7,/dev/disk/by-partlabel/asm-disk8 # Comma-separated list of ASM device files for RECO Diskgroup for Recovery Area files + redoAsmDeviceList: /dev/disk/by-partlabel/asm-disk9,/dev/disk/by-partlabel/asm-disk10 # Comma-separated list of ASM device files for REDO Diskgroup for Redo files + crsAsmDiskDgRedundancy: "EXTERNAL" # Specify the Redudancy for the diskgroup with CRS Files + dbAsmDiskDgRedundancy: "NORMAL" # Specify the Redudancy for the diskgroup with Database Files + recoAsmDiskDgRedundancy: "EXTERNAL" # Specify the Redudancy for the diskgroup with Recovery Files + redoAsmDiskDgRedundancy: "EXTERNAL" # Specify the Redudancy for the diskgroup with Redo Files + inventory: "/u01/app/oraInventory" # Inventory location + gridSwZipFile: "grid_home.zip" # Grid infrastructure software ZIP + dbSwZipFile: "db_home.zip" # Database software ZIP + sgaSize: "3G" # Size of the System Global Area + pgaSize: "1G" # Size of the Program Global Area + processes: 2000 # Oracle process limit + cpuCount: 4 # Number of CPUs to allocate + dbName: "PORCLCDB" # Oracle database name + pdbName: "TESTPDB" # PDB name, in case you want to explicitly specify the PDB Name + hostSwStageLocation: /scratch/software/stage # Host location where Oracle software ZIPs are staged, same location will be mounted inside the Oracle Restart Pod diff --git a/docs/oraclerestart/provisioning/oraclerestart_prov_multiple_diskgroups_with_redundancy_with_separate_storage_class.md b/docs/oraclerestart/provisioning/oraclerestart_prov_multiple_diskgroups_with_redundancy_with_separate_storage_class.md new file mode 100644 index 00000000..69d4cae3 --- /dev/null +++ b/docs/oraclerestart/provisioning/oraclerestart_prov_multiple_diskgroups_with_redundancy_with_separate_storage_class.md @@ -0,0 +1,53 @@ +# Provisioning an Oracle Restart Database with multiple diskgroups with different redundancy and option to specify separate storage class + +### In this Usecase: +The Oracle Grid Infrastructure and Oracle Restart Database are deployed automatically using Oracle Restart Controller. In this example, multiple diskgroups are created for CRS Files, Database Files, Recovery Area Files and Redo Log Files. Different Disk Groups have different redundancy levels. + +This example uses `oraclerestart_prov_multiple_diskgroups_with_redundancy_with_separate_storage_class.yaml` to provision an Oracle Database configured with Oracle Restart using Oracle Restart Controller. The provisioning includes: + * Oracle Restart Pod + * Headless services for Oracle Restart. + * Oracle Database Node hostname. + * Persistent volumes created automatically based on specified disks for Oracle ASM Storage. + * Software Persistent Volume and Staged Software Persistent Volume using the specified location on the corresponding worker node. + * Namespace: `orestart` + * Staged Software location on the worker nodes is specified by `hostSwStageLocation`. The Grid Infrastructure and RDBMS Binaries are copied to this location on the worker node. + * The GI HOME and the RDBMS HOME in the Oracle Restart Pod are mounted using a Persistent Volume created using the Storage Class (specified using `swDgStorageClass`). This Persistent Volume is mounted to `/u01` inside the Pod. Size of this Persistent Volume is specified using `swLocStorageSizeInGb`. + * Name of Custom Storage Class for Diskgroup having CRS files is specified by `crsDgStorageClass`. + * Name of Custom Storage Class for Diskgroup having Database files is specified by `dataDgStorageClass`. + * Name of Custom Storage Class for Diskgroup having Recovery Area files is specified by `recoDgStorageClass`. + * Name of Custom Storage Class for Diskgroup having Redo Log files is specified by `redoDgStorageClass`. + +### In this example, + * Oracle Restart Database Slim Image `dbocir/oracle/database-orestart:19.3.0-slim` is used and it is built using files from [GitHub location](https://github.com/oracle/docker-images/tree/main/OracleDatabase/RAC/OracleRealApplicationClusters#building-oracle-rac-database-container-slim-image). Default image created using files from this project is `localhost/oracle/database-rac:19.3.0-slim`. You need to tag it with name `localhost/oracle/database-orestart:19.3.0-slim`. + * When you are building the image yourself, update the image value in the `oraclerestart_prov_multiple_diskgroups_with_redundancy_with_separate_storage_class.yaml` file to point to the container image you have built. + * The disks provisioned using custom storage classes are mounted inside the Oracle Restart Pod as `/dev/asm-disk1` to `/dev/asm-disk10`. + * Specify the size of disk devices along with names using the parameter `storageSizeInGb`. Size is by-default in GBs. + * The Diskgroup for CRS files is specified by `crsAsmDiskDg` and the disks on the worker nodes for this diskgroup are specified by `crsAsmDeviceList`. + * The Diskgroup for Database files is specified by `dbDataFileDestDg` and the disks on the worker nodes for this diskgroup are specified by `dbAsmDeviceList`. + * The Diskgroup for Recovery Area files is specified by `dbRecoveryFileDest` and the disks on the worker nodes for this diskgroup are specified by `recoAsmDeviceList`. + * The Diskgroup for Redo Log files is specified by `redoAsmDiskDg` and the disks on the worker nodes for this diskgroup are specified by `redoAsmDeviceList`. + * Redundancy level for the diskgroup with CRS files is mentioned by `crsAsmDiskDgRedundancy`. + * Redundancy level for the diskgroup with Database files is mentioned by `dbAsmDiskDgRedundancy`. + * Redundancy level for the diskgroup with Recovery files is mentioned by `recoAsmDiskDgRedudancy`. + * Redundancy level for the diskgroup with Redo Log files is mentioned by `redoAsmDiskDgRedundancy`. + +### Steps: Deploy Oracle Restart Database +* Use the file: [oraclerestart_prov_multiple_diskgroups_with_redundancy_with_separate_storage_class.yaml](./oraclerestart_prov_multiple_diskgroups_with_redundancy_with_separate_storage_class.yaml) for this use case as below: +* Deploy the `oraclerestart_prov_multiple_diskgroups_with_redundancy_with_separate_storage_class.yaml` file: + ```sh + kubectl apply -f oraclerestart_prov_multiple_diskgroups_with_redundancy_with_separate_storage_class.yaml + oraclerestart.database.oracle.com/oraclerestart-sample created + ``` +* Check the status of the deployment: + ```sh + # Check the status of the Kubernetes Pods: + kubectl get all -n orestart + + # Check the logs of a particular pod. For example, to check status of pod "dbmc1-0": + kubectl exec -it pod/dbmc1-0 -n orestart -- bash -c "tail -f /tmp/orod/oracle_db_setup.log" + =============================== + ORACLE DATABASE IS READY TO USE + =============================== + ``` +* Check Details of Kubernetes CRD Object as in this [example](./oraclerestart_prov_multiple_diskgroups_with_redundancy_with_separate_storage_class.txt) +* Refer to the page [Database Connection](./database_connection.md) for the details to connect to Oracle Restart Database deployed using above example. diff --git a/docs/oraclerestart/provisioning/oraclerestart_prov_multiple_diskgroups_with_redundancy_with_separate_storage_class.txt b/docs/oraclerestart/provisioning/oraclerestart_prov_multiple_diskgroups_with_redundancy_with_separate_storage_class.txt new file mode 100644 index 00000000..595258d8 --- /dev/null +++ b/docs/oraclerestart/provisioning/oraclerestart_prov_multiple_diskgroups_with_redundancy_with_separate_storage_class.txt @@ -0,0 +1,344 @@ +$ kubectl describe oraclerestarts.database.oracle.com/oraclerestart-sample -n orestart +Name: oraclerestart-sample +Namespace: orestart +Labels: +Annotations: OracleRestarts.database.oracle.com/old-spec: + {"instDetails":{"name":"dbmc1","swLocStorageSizeInGb":300,"workerNode":["10.0.10.58"],"envVars":[{"name":"IGNORE_CRS_PREREQS","value":"tru... +API Version: database.oracle.com/v4 +Kind: OracleRestart +Metadata: + Creation Timestamp: 2025-10-09T03:57:56Z + Finalizers: + database.oracle.com/oraclerestartfinalizer + Generation: 1 + Resource Version: 246198029 + UID: 19d43856-477c-43d2-a01f-8d1492107370 +Spec: + Asm Storage Details: + Disks By Size: + Disk Names: + /dev/asm-disk1 + /dev/asm-disk2 + Storage Size In Gb: 50 + Disk Names: + /dev/asm-disk3 + /dev/asm-disk4 + /dev/asm-disk5 + /dev/asm-disk6 + Storage Size In Gb: 100 + Disk Names: + /dev/asm-disk7 + /dev/asm-disk8 + Storage Size In Gb: 100 + Disk Names: + /dev/asm-disk9 + /dev/asm-disk10 + Storage Size In Gb: 50 + Config Params: + Cpu Count: 4 + Crs Asm Device List: /dev/asm-disk1,/dev/asm-disk2 + Crs Asm Disk Dg: +CRSDATA + Crs Asm Disk Dg Redundancy: EXTERNAL + Db Asm Device List: /dev/asm-disk3,/dev/asm-disk4,/dev/asm-disk5,/dev/asm-disk6 + Db Asm Disk Dg Redundancy: NORMAL + Db Base: /u01/app/oracle + Db Char Set: AL32UTF8 + Db Data File Dest Dg: +DBDATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: +RECO + Db Response File: + Db Sw Zip File: db_home.zip + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Grid Sw Zip File: grid_home.zip + Host Sw Stage Location: /scratch/software/stage + Inventory: /u01/app/oraInventory + Pdb Name: TESTPDB + Pga Size: 1G + Processes: 2000 + Reco Asm Device List: /dev/asm-disk7,/dev/asm-disk8 + Reco Asm Disk Dg Redundancy: EXTERNAL + Redo Asm Device List: /dev/asm-disk9,/dev/asm-disk10 + Redo Asm Disk Dg: +REDO + Redo Asm Disk Dg Redundancy: EXTERNAL + Sga Size: 3G + Sw Mount Location: /u01 + Crs Dg Storage Class: oci-bv + Data Dg Storage Class: oci-bv + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Enable Ons: enable + Image: localhost/oracle/database-orestart:19.3.0-slim + Image Pull Policy: IfNotPresent + Inst Details: + Env Vars: + Name: IGNORE_CRS_PREREQS + Value: true + Name: IGNORE_DB_PREREQS + Value: true + Name: dbmc1 + Sw Loc Storage Size In Gb: 300 + Worker Node: + 10.0.10.58 + Lb Service: + Node Port Svc: + Name: dbmc1-service-nodeport + Port Mappings: + Node Port: 30007 + Port: 1521 + Protocol: TCP + Target Port: 1521 + Node Port: 30200 + Port: 6200 + Protocol: TCP + Target Port: 6200 + Svc Type: nodeport + Reco Dg Storage Class: oci-bv + Redo Dg Storage Class: oci-bv + Resources: + Limits: + Cpu: 2 + Memory: 16Gi + Requests: + Cpu: 2 + Memory: 16Gi + Security Context: + Sysctls: + Name: kernel.shmall + Value: 2097152 + Name: kernel.sem + Value: 250 32000 100 128 + Name: kernel.shmmax + Value: 8589934592 + Name: kernel.shmmni + Value: 4096 + Service Details: + Name: soepdb + Ssh Key Secret: + Key Mount Location: /mnt/.ssh + Name: ssh-key-secret + Priv Key Secret Name: ssh-privkey + Pub Key Secret Name: ssh-pubkey + Sw Dg Storage Class: oci-bv +Status: + Oracle Restart Nodes: + Name: dbmc1-0 + Node Details: + Instance State: OPEN + Pod State: AVAILABLE + Cluster State: HEALTHY + Mounted Devices: + /dev/asm-disk1 + /dev/asm-disk2 + /dev/asm-disk3 + /dev/asm-disk4 + /dev/asm-disk5 + /dev/asm-disk6 + /dev/asm-disk7 + /dev/asm-disk8 + /dev/asm-disk9 + /dev/asm-disk10 + State: AVAILABLE + Asm Details: + Diskgroup: + Disks: + /dev/asm-disk1,/dev/asm-disk10,/dev/asm-disk2,/dev/asm-disk3,/dev/asm-disk4,/dev/asm-disk5,/dev/asm-disk6,/dev/asm-disk7,/dev/asm-disk8,/dev/asm-disk9 + Name: CRSDATA + Redundancy: EXTERN + Disks: + /dev/asm-disk1,/dev/asm-disk10,/dev/asm-disk2,/dev/asm-disk3,/dev/asm-disk4,/dev/asm-disk5,/dev/asm-disk6,/dev/asm-disk7,/dev/asm-disk8,/dev/asm-disk9 + Name: REDO + Redundancy: EXTERN + Disks: + /dev/asm-disk1,/dev/asm-disk10,/dev/asm-disk2,/dev/asm-disk3,/dev/asm-disk4,/dev/asm-disk5,/dev/asm-disk6,/dev/asm-disk7,/dev/asm-disk8,/dev/asm-disk9 + Name: RECO + Redundancy: EXTERN + Disks: + /dev/asm-disk1,/dev/asm-disk10,/dev/asm-disk2,/dev/asm-disk3,/dev/asm-disk4,/dev/asm-disk5,/dev/asm-disk6,/dev/asm-disk7,/dev/asm-disk8,/dev/asm-disk9 + Name: DBDATA + Redundancy: NORMAL + Conditions: + Last Transition Time: 2025-10-09T04:11:38Z + Message: reconcile has been queued + Observed Generation: 1 + Reason: LastReconcileCycleQueued + Status: True + Type: ReconcileQueued + Last Transition Time: 2025-10-09T04:20:34Z + Message: reconcile completed successfully + Observed Generation: 1 + Reason: LastReconcileCycleCompleted + Status: True + Type: ReconcileComplete + Config Params: + Crs Asm Device List: /dev/asm-disk1,/dev/asm-disk2 + Crs Asm Disk Dg: +CRSDATA + Crs Asm Disk Dg Redundancy: EXTERNAL + Db Asm Device List: /dev/asm-disk3,/dev/asm-disk4,/dev/asm-disk5,/dev/asm-disk6 + Db Asm Disk Dg Redundancy: NORMAL + Db Base: /u01/app/oracle + Db Data File Dest Dg: +DBDATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: +RECO + Db Response File: + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Inventory: /u01/app/oraInventory + Reco Asm Device List: /dev/asm-disk7,/dev/asm-disk8 + Connect String: dbmc1-0:1521/PORCLCDB + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Db State: OPEN + External Connect String: 129.146.0.149:30007/soepdb + Pdb Connect String: dbmc1-0:1521/ORCLPDB + Release Update: 19.28.0.0.0 + Role: PRIMARY + Service Details: + Name: soepdb + Svc State: service soepdb is running + State: AVAILABLE +Events: + + + + + +[grid@dbmc1-0 ~]$ ls -rlt /dev/asm-disk* +brw-rw----. 1 grid asmadmin 65, 80 Oct 9 04:04 /dev/asm-disk2 +brw-rw----. 1 grid asmadmin 8, 224 Oct 9 04:06 /dev/asm-disk8 +brw-rw----. 1 grid asmadmin 65, 0 Oct 9 04:18 /dev/asm-disk6 +brw-rw----. 1 grid asmadmin 65, 64 Oct 9 04:21 /dev/asm-disk1 +brw-rw----. 1 grid asmadmin 65, 32 Oct 9 04:21 /dev/asm-disk9 +brw-rw----. 1 grid asmadmin 8, 208 Oct 9 04:21 /dev/asm-disk7 +brw-rw----. 1 grid asmadmin 8, 240 Oct 9 04:21 /dev/asm-disk5 +brw-rw----. 1 grid asmadmin 65, 96 Oct 9 04:21 /dev/asm-disk4 +brw-rw----. 1 grid asmadmin 65, 48 Oct 9 04:21 /dev/asm-disk3 +brw-rw----. 1 grid asmadmin 8, 192 Oct 9 04:21 /dev/asm-disk10 +[grid@dbmc1-0 ~]$ +[grid@dbmc1-0 ~]$ +[grid@dbmc1-0 ~]$ export ORACLE_SID=+ASM +[grid@dbmc1-0 ~]$ asmcmd lsdg +State Type Rebal Sector Logical_Sector Block AU Total_MB Free_MB Req_mir_free_MB Usable_file_MB Offline_disks Voting_files Name +MOUNTED EXTERN N 512 512 4096 4194304 102400 102284 0 102284 0 N CRSDATA/ +MOUNTED NORMAL N 512 512 4096 1048576 409600 403401 102400 150500 0 N DBDATA/ +MOUNTED EXTERN N 512 512 4096 1048576 204800 204733 0 204733 0 N RECO/ +MOUNTED EXTERN N 512 512 4096 1048576 102400 99230 0 99230 0 N REDO/ +[grid@dbmc1-0 ~]$ +[grid@dbmc1-0 ~]$ asmcmd lsdsk +Path +/dev/asm-disk1 +/dev/asm-disk10 +/dev/asm-disk2 +/dev/asm-disk3 +/dev/asm-disk4 +/dev/asm-disk5 +/dev/asm-disk6 +/dev/asm-disk7 +/dev/asm-disk8 +/dev/asm-disk9 +[grid@dbmc1-0 ~]$ +[grid@dbmc1-0 ~]$ +[grid@dbmc1-0 ~]$ sqlplus "/ as sysdba" + +SQL*Plus: Release 19.0.0.0.0 - Production on Thu Oct 9 04:22:20 2025 +Version 19.28.0.0.0 + +Copyright (c) 1982, 2025, Oracle. All rights reserved. + + +Connected to: +Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production +Version 19.28.0.0.0 + +SQL> set lines 200 +SQL> col DISKGROUP_NAME format a20 +SQL> col PATH format a50 +SQL> SELECT + dg.NAME AS DISKGROUP_NAME, + d.PATH, + d.MOUNT_STATUS, + d.HEADER_STATUS, + d.STATE, + d.TOTAL_MB, + d.FREE_MB +FROM + V$ASM_DISK d +JOIN + V$ASM_DISKGROUP dg ON d.GROUP_NUMBER = dg.GROUP_NUMBER +ORDER BY + dg.NAME, d.DISK_NUMBER; 2 3 4 5 6 7 8 9 10 11 12 13 14 + +DISKGROUP_NAME PATH MOUNT_S HEADER_STATU STATE TOTAL_MB FREE_MB +-------------------- -------------------------------------------------- ------- ------------ -------- ---------- ---------- +CRSDATA /dev/asm-disk1 CACHED MEMBER NORMAL 51200 51144 +CRSDATA /dev/asm-disk2 CACHED MEMBER NORMAL 51200 51140 +DBDATA /dev/asm-disk3 CACHED MEMBER NORMAL 102400 100846 +DBDATA /dev/asm-disk4 CACHED MEMBER NORMAL 102400 100851 +DBDATA /dev/asm-disk5 CACHED MEMBER NORMAL 102400 100851 +DBDATA /dev/asm-disk6 CACHED MEMBER NORMAL 102400 100853 +RECO /dev/asm-disk7 CACHED MEMBER NORMAL 102400 102366 +RECO /dev/asm-disk8 CACHED MEMBER NORMAL 102400 102367 +REDO /dev/asm-disk9 CACHED MEMBER NORMAL 51200 49613 +REDO /dev/asm-disk10 CACHED MEMBER NORMAL 51200 49617 + +10 rows selected. + +SQL> + + + + + +[oracle@dbmc1-0 ~]$ export ORACLE_SID=PORCLCDB +[oracle@dbmc1-0 ~]$ sqlplus "/ as sysdba" + +SQL*Plus: Release 19.0.0.0.0 - Production on Thu Oct 9 04:23:14 2025 +Version 19.28.0.0.0 + +Copyright (c) 1982, 2025, Oracle. All rights reserved. + + +Connected to: +Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production +Version 19.28.0.0.0 + +SQL> +SQL> show pdbs + + CON_ID CON_NAME OPEN MODE RESTRICTED +---------- ------------------------------ ---------- ---------- + 2 PDB$SEED READ ONLY NO + 3 TESTPDB READ WRITE NO +SQL> +SQL> +SQL> show parameter DB_CREATE_ONLINE_LOG_DEST_1 + +NAME TYPE VALUE +------------------------------------ ----------- ------------------------------ +db_create_online_log_dest_1 string +REDO +SQL> +SQL> +SQL> show parameter db_create_file_dest + +NAME TYPE VALUE +------------------------------------ ----------- ------------------------------ +db_create_file_dest string +DBDATA +SQL> +SQL> show parameter db_recovery_file_dest + +NAME TYPE VALUE +------------------------------------ ----------- ------------------------------ +db_recovery_file_dest string +RECO +db_recovery_file_dest_size big integer 20058M diff --git a/docs/oraclerestart/provisioning/oraclerestart_prov_multiple_diskgroups_with_redundancy_with_separate_storage_class.yaml b/docs/oraclerestart/provisioning/oraclerestart_prov_multiple_diskgroups_with_redundancy_with_separate_storage_class.yaml new file mode 100644 index 00000000..175604bf --- /dev/null +++ b/docs/oraclerestart/provisioning/oraclerestart_prov_multiple_diskgroups_with_redundancy_with_separate_storage_class.yaml @@ -0,0 +1,154 @@ +# API version of the OracleRestart Custom Resource +apiVersion: database.oracle.com/v4 + +# Resource type being created +kind: OracleRestart + +# Metadata section to define the resource's name and namespace +metadata: + name: oraclerestart-sample # Name of the OracleRestart instance + namespace: orestart # Kubernetes namespace to deploy into + +spec: + # Instance details for Oracle Restart + instDetails: + name: dbmc1 # Logical name of the OracleRestart instance + + # Worker nodes where the instance should be scheduled + workerNode: + - 10.0.10.58 # IP address of the target node for deployment + + # Size of the storage location for software. This parameter will be effective when the local SW location is NOT specified by hostSwLocation + swLocStorageSizeInGb: 300 + + # Environment variables in case you want to want to ignore prerequisite checks during the GI/RDBSMS Install + envVars: + - name: IGNORE_CRS_PREREQS # If set to true, it will use flags -ignorePreReq and -ignorePrereqFailure during the CRS Installation. Do not set this parameter in the .yaml file if you do not want to use the parameters -ignorePreReq and -ignorePrereqFailure during the CRS Installation. + value: "true" + - name: IGNORE_DB_PREREQS # If set to true, it will use flags -ignorePrereq and -ignorePrereqFailure during the DB Software Installation. Do not set this parameter in the .yaml file if you do not want to use the parameters -ignorePreReq and -ignorePrereqFailure during the DB Software Installation. + value: "true" + + # NodePort service configuration for exposing DB port externally + nodePortSvc: + name: dbmc1-service-nodeport # Name of the Kubernetes Service + svcType: nodeport # Service type (NodePort to expose externally) + + # Mapping between container and host ports + portMappings: + - port: 1521 # Port inside container (Oracle Listener port) + targetPort: 1521 # Target port for the service + protocol: TCP # Protocol type (TCP for Oracle) + nodePort: 30007 # Host port exposed on worker node (must be in NodePort range) + - port: 6200 # Port inside container (for ONS) + targetPort: 6200 # Target port for the service + protocol: TCP # Protocol type (TCP for Oracle) + nodePort: 30200 # Host port exposed on worker node (must be in NodePort range) + enableOns: "enable" # If you want to have ONS notification configured on the specified port. Default value is "enable" + + # Custom Storage class to be used for dynamic provisioning (if applicable) + # You have separate parameters to specify the storage class for all the diskgroups (CRS, DATA, RECO, REDO) + # You have a parameter to specify the storage class for the software location for the GI and RDBMS HOME + crsDgStorageClass: "oci-bv" # Specify storage class for the CRS Diskgroup, Currently using "oci-bv" but you can specify another available storage class as well + dataDgStorageClass: "oci-bv" # Specify storage class for the DATA Diskgroup, Currently using "oci-bv" but you can specify another available storage class as well + recoDgStorageClass: "oci-bv" # Specify storage class for the RECO Diskgroup, Currently using "oci-bv" but you can specify another available storage class as well + redoDgStorageClass: "oci-bv" # Specify storage class for the REDO Diskgroup, Currently using "oci-bv" but you can specify another available storage class as well + swDgStorageClass: "oci-bv" # Specify storage class for the storage location for software, Currently using "oci-bv" but you can specify another available storage class as well + + # ASM (Automatic Storage Management) storage configuration + asmStorageDetails: + disksBySize: + - storageSizeInGb: 50 # Each disk in this group has 50Gi of storage, will be used for CRSDATA Diskgroup + diskNames: + - /dev/asm-disk1 # ASM disk device path 1 + - /dev/asm-disk2 # ASM disk device path 2 + - storageSizeInGb: 100 # Each disk in this group has 100Gi of storage, will be used for DATA Diskgroup + diskNames: + - /dev/asm-disk3 # ASM disk device path 3 + - /dev/asm-disk4 # ASM disk device path 4 + - /dev/asm-disk5 # ASM disk device path 5 + - /dev/asm-disk6 # ASM disk device path 6 + - storageSizeInGb: 100 # Each disk in this group has 100Gi of storage, will be used for RECO Diskgroup + diskNames: + - /dev/asm-disk7 # ASM disk device path 7 + - /dev/asm-disk8 # ASM disk device path 8 + - storageSizeInGb: 50 # Each disk in this group has 50Gi of storage, will be used for REDO Diskgroup + diskNames: + - /dev/asm-disk9 # ASM disk device path 9 + - /dev/asm-disk10 # ASM disk device path 10 + + # SSH key secret used for remote access and automation + sshKeySecret: + name: ssh-key-secret # Kubernetes secret name + privKeySecretName: ssh-privkey # Private key inside secret + pubKeySecretName: ssh-pubkey # Public key inside secret + + # Secret containing DB user credentials + dbSecret: + name: db-user-pass-pkutl # Secret name + keyFileName: key.pem # Key file name inside secret + pwdFileName: pwdfile.enc # Password file name inside secret + + # Image for Oracle Restart container + image: dbocir/oracle/database-orestart:19.3.0-slim # <-- Replace with your image registry + + # Policy to pull image + # Use the value as "Always" if you want use the latest version of the image from the container registry. + # Use the value as "IfNotPresent" it the image is not already present locally. + imagePullPolicy: IfNotPresent + + # Optional service account for security context permissions + # serviceAccountName: oraclerestart # <-- Required for OpenShift; optional for vanilla Kubernetes + + # Service-specific database name and related details + serviceDetails: + name: soepdb # Name of the Oracle service (pluggable DB) + + # Resource requests and limits for memory and CPU + resources: + requests: + memory: "16Gi" # Minimum memory required + cpu: "2" # Minimum CPU required + limits: + memory: "16Gi" # Max memory allowed + cpu: "2" # Max CPU allowed + + # Kernel-level system control parameters for Oracle + securityContext: + sysctls: + - name: kernel.shmall + value: "2097152" + - name: kernel.sem + value: "250 32000 100 128" + - name: kernel.shmmax + value: "8589934592" + - name: kernel.shmmni + value: "4096" + + # Configuration parameters for Oracle Grid and Database homes + configParams: + gridHome: "/u01/app/19c/grid" # Oracle Grid home path + gridBase: "/u01/app/grid" # Grid base directory + dbHome: "/u01/app/oracle/product/19c/dbhome_1" # Oracle DB home path + dbBase: "/u01/app/oracle" # Oracle base directory + crsAsmDiskDg: "+CRSDATA" # Name of the Diskgroup for Voting Disks + dbDataFileDestDg: "+DBDATA" # Name of the Diskgroup for Database Files + dbRecoveryFileDest: "+RECO" # Name of the Diskgroup for Recovery Files + redoAsmDiskDg: "+REDO" # Name of the Diskgroup for Redo Files + crsAsmDeviceList: /dev/asm-disk1,/dev/asm-disk2 # Comma-separated list of ASM device files for CRS Diskgroup CRSDATA + dbAsmDeviceList: /dev/asm-disk3,/dev/asm-disk4,/dev/asm-disk5,/dev/asm-disk6 # Comma-separated list of ASM device files for DBDATA Diskgroup for database files + recoAsmDeviceList: /dev/asm-disk7,/dev/asm-disk8 # Comma-separated list of ASM device files for RECO Diskgroup for Recovery Area files + redoAsmDeviceList: /dev/asm-disk9,/dev/asm-disk10 # Comma-separated list of ASM device files for REDO Diskgroup for Redo files + crsAsmDiskDgRedundancy: "EXTERNAL" # Specify the Redudancy for the diskgroup with CRS Files + dbAsmDiskDgRedundancy: "NORMAL" # Specify the Redudancy for the diskgroup with Database Files + recoAsmDiskDgRedundancy: "EXTERNAL" # Specify the Redudancy for the diskgroup with Recovery Files + redoAsmDiskDgRedundancy: "EXTERNAL" # Specify the Redudancy for the diskgroup with Redo Files + inventory: "/u01/app/oraInventory" # Inventory location + gridSwZipFile: "grid_home.zip" # Grid infrastructure software ZIP + dbSwZipFile: "db_home.zip" # Database software ZIP + sgaSize: "3G" # Size of the System Global Area + pgaSize: "1G" # Size of the Program Global Area + processes: 2000 # Oracle process limit + cpuCount: 4 # Number of CPUs to allocate + dbName: "PORCLCDB" # Oracle database name + pdbName: "TESTPDB" # PDB name, in case you want to explicitly specify the PDB Name + hostSwStageLocation: /scratch/software/stage # Host location where Oracle software ZIPs are staged, same location will be mounted inside the Oracle Restart Pod diff --git a/docs/oraclerestart/provisioning/oraclerestart_prov_nodeports.yaml b/docs/oraclerestart/provisioning/oraclerestart_prov_nodeports.yaml new file mode 100644 index 00000000..02aa274c --- /dev/null +++ b/docs/oraclerestart/provisioning/oraclerestart_prov_nodeports.yaml @@ -0,0 +1,117 @@ +# API version of the OracleRestart Custom Resource +apiVersion: database.oracle.com/v4 + +# Resource type being created +kind: OracleRestart + +# Metadata section to define the resource's name and namespace +metadata: + name: oraclerestart-sample # Name of the OracleRestart instance + namespace: orestart # Kubernetes namespace to deploy into + +spec: + # Instance details for Oracle Restart + instDetails: + name: dbmc1 # Logical name of the OracleRestart instance + hostSwLocation: /scratch/orestart/ # Host directory for Oracle GI HOME and Oracle RDBMS HOME + + # Worker nodes where the instance should be scheduled + workerNode: + - 10.0.10.58 # IP address of the target node for deployment + + # Environment variables in case you want to want to ignore prerequisite checks during the GI/RDBSMS Install + envVars: + - name: IGNORE_CRS_PREREQS # If set to true, it will use flags -ignorePreReq and -ignorePrereqFailure during the CRS Installation. Do not set this parameter in the .yaml file if you do not want to use the parameters -ignorePreReq and -ignorePrereqFailure during the CRS Installation. + value: "true" + - name: IGNORE_DB_PREREQS # If set to true, it will use flags -ignorePrereq and -ignorePrereqFailure during the DB Software Installation. Do not set this parameter in the .yaml file if you do not want to use the parameters -ignorePreReq and -ignorePrereqFailure during the DB Software Installation. + value: "true" + + # NodePort service configuration for exposing DB port externally + nodePortSvc: + name: dbmc1-service-nodeport # Name of the Kubernetes Service + svcType: nodeport # Service type (NodePort to expose externally) + + # Mapping between container and host ports + portMappings: + - port: 1521 # Port inside container (Oracle Listener port) + targetPort: 1521 # Target port for the service + protocol: TCP # Protocol type (TCP for Oracle) + nodePort: 30007 # Host port exposed on worker node (must be in NodePort range) + - port: 6200 # Port inside container (for ONS) + targetPort: 6200 # Target port for the service + protocol: TCP # Protocol type (TCP for Oracle) + nodePort: 30200 # Host port exposed on worker node (must be in NodePort range) + enableOns: "enable" # If you want to have ONS notification configured on the specified port. Default value is "enable" + + # ASM (Automatic Storage Management) storage configuration + asmStorageDetails: + disksBySize: + - storageSizeInGb: 50 # Each disk in this group has 50Gi of storage + diskNames: + - /dev/disk/by-partlabel/asm-disk1 # ASM disk device path 1 + - /dev/disk/by-partlabel/asm-disk2 # ASM disk device path 2 + + # SSH key secret used for remote access and automation + sshKeySecret: + name: ssh-key-secret # Kubernetes secret name + privKeySecretName: ssh-privkey # Private key inside secret + pubKeySecretName: ssh-pubkey # Public key inside secret + + # Secret containing DB user credentials + dbSecret: + name: db-user-pass-pkutl # Secret name + keyFileName: key.pem # Key file name inside secret + pwdFileName: pwdfile.enc # Password file name inside secret + + # Image for Oracle Restart container + image: dbocir/oracle/database-orestart:19.3.0-slim # <-- Replace with your image registry + + # Policy to pull image + # Use the value as "Always" if you want use the latest version of the image from the container registry. + # Use the value as "IfNotPresent" it the image is not already present locally. + imagePullPolicy: IfNotPresent + + # Optional service account for security context permissions + # serviceAccountName: oraclerestart # <-- Required for OpenShift; optional for vanilla Kubernetes + + # Service-specific database name and related details + serviceDetails: + name: soepdb # Name of the Oracle service (pluggable DB) + + # Resource requests and limits for memory and CPU + resources: + requests: + memory: "16Gi" # Minimum memory required + cpu: "2" # Minimum CPU required + limits: + memory: "16Gi" # Max memory allowed + cpu: "2" # Max CPU allowed + + # Kernel-level system control parameters for Oracle + securityContext: + sysctls: + - name: kernel.shmall + value: "2097152" + - name: kernel.sem + value: "250 32000 100 128" + - name: kernel.shmmax + value: "8589934592" + - name: kernel.shmmni + value: "4096" + + # Configuration parameters for Oracle Grid and Database homes + configParams: + gridHome: "/u01/app/19c/grid" # Oracle Grid home path + gridBase: "/u01/app/grid" # Grid base directory + dbHome: "/u01/app/oracle/product/19c/dbhome_1" # Oracle DB home path + dbBase: "/u01/app/oracle" # Oracle base directory + crsAsmDeviceList: /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 # Comma-separated list of ASM device files + inventory: "/u01/app/oraInventory" # Inventory location + gridSwZipFile: "grid_home.zip" # Grid infrastructure software ZIP + dbSwZipFile: "db_home.zip" # Database software ZIP + sgaSize: "3G" # Size of the System Global Area + pgaSize: "1G" # Size of the Program Global Area + processes: 2000 # Oracle process limit + cpuCount: 4 # Number of CPUs to allocate + dbName: "PORCLCDB" # Oracle database name + hostSwStageLocation: /scratch/software/stage # Host location where Oracle software ZIPs are staged, same location will be mounted inside the Oracle Restart Pod diff --git a/docs/oraclerestart/provisioning/oraclerestart_prov_nodeports_mcpu_change.txt b/docs/oraclerestart/provisioning/oraclerestart_prov_nodeports_mcpu_change.txt new file mode 100644 index 00000000..ebb7218e --- /dev/null +++ b/docs/oraclerestart/provisioning/oraclerestart_prov_nodeports_mcpu_change.txt @@ -0,0 +1,389 @@ +$ kubectl apply -f oraclerestart_prov_nodeports.yaml + +-- Before the memory and cpu change: +$ kubectl describe oraclerestarts.database.oracle.com/oraclerestart-sample -n orestart +Name: oraclerestart-sample +Namespace: orestart +Labels: +Annotations: OracleRestarts.database.oracle.com/old-spec: + {"instDetails":{"name":"dbmc1","hostSwLocation":"/scratch/orestart/","workerNode":["10.0.10.58"],"envVars":[{"name":"IGNORE_CRS_PREREQS","... +API Version: database.oracle.com/v4 +Kind: OracleRestart +Metadata: + Creation Timestamp: 2025-09-10T15:45:13Z + Finalizers: + database.oracle.com/oraclerestartfinalizer + Generation: 1 + Resource Version: 234001859 + UID: 4b261940-9e2e-4ee0-8c21-75a57eb1b0ed +Spec: + Asm Storage Details: + Disks By Size: + Disk Names: + /dev/disk/by-partlabel/asm-disk1 + /dev/disk/by-partlabel/asm-disk2 + Storage Size In Gb: 50 + Config Params: + Cpu Count: 4 + Crs Asm Device List: /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 + Crs Asm Disk Dg: +DATA + Crs Asm Disk Dg Redundancy: external + Db Base: /u01/app/oracle + Db Char Set: AL32UTF8 + Db Data File Dest Dg: +DATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: +DATA + Db Response File: + Db Sw Zip File: db_home.zip + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Grid Sw Zip File: grid_home.zip + Host Sw Stage Location: /scratch/software/stage + Inventory: /u01/app/oraInventory + Pga Size: 1G + Processes: 2000 + Sga Size: 3G + Sw Mount Location: /u01 + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Enable Ons: enable + Image: localhost/oracle/database-orestart:19.3.0-slim + Image Pull Policy: IfNotPresent + Inst Details: + Env Vars: + Name: IGNORE_CRS_PREREQS + Value: true + Name: IGNORE_DB_PREREQS + Value: true + Host Sw Location: /scratch/orestart/ + Name: dbmc1 + Worker Node: + 10.0.10.58 + Lb Service: + Node Port Svc: + Name: dbmc1-service-nodeport + Port Mappings: + Node Port: 30007 + Port: 1521 + Protocol: TCP + Target Port: 1521 + Node Port: 30200 + Port: 6200 + Protocol: TCP + Target Port: 6200 + Svc Type: nodeport + Resources: + Limits: + Cpu: 2 + Memory: 16Gi + Requests: + Cpu: 2 + Memory: 16Gi + Security Context: + Sysctls: + Name: kernel.shmall + Value: 2097152 + Name: kernel.sem + Value: 250 32000 100 128 + Name: kernel.shmmax + Value: 8589934592 + Name: kernel.shmmni + Value: 4096 + Service Details: + Name: soepdb + Ssh Key Secret: + Key Mount Location: /mnt/.ssh + Name: ssh-key-secret + Priv Key Secret Name: ssh-privkey + Pub Key Secret Name: ssh-pubkey +Status: + Oracle Restart Nodes: + Name: dbmc1-0 + Node Details: + Instance State: OPEN + Pod State: AVAILABLE + Cluster State: HEALTHY + Mounted Devices: + /dev/disk/by-partlabel/asm-disk1 + /dev/disk/by-partlabel/asm-disk2 + State: AVAILABLE + Asm Details: + Diskgroup: + Disks: + /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 + Name: DATA + Redundancy: EXTERN + Conditions: + Last Transition Time: 2025-09-10T15:57:27Z + Message: reconcile has been queued + Observed Generation: 1 + Reason: LastReconcileCycleQueued + Status: True + Type: ReconcileQueued + Last Transition Time: 2025-09-10T20:38:23Z + Message: reconcile completed successfully + Observed Generation: 1 + Reason: LastReconcileCycleCompleted + Status: True + Type: ReconcileComplete + Config Params: + Crs Asm Device List: /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 + Crs Asm Disk Dg: +DATA + Crs Asm Disk Dg Redundancy: external + Db Base: /u01/app/oracle + Db Data File Dest Dg: +DATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: +DATA + Db Response File: + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Inventory: /u01/app/oraInventory + Connect String: dbmc1-0:1521/PORCLCDB + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Db State: OPEN + External Connect String: Pending + Pdb Connect String: dbmc1-0:1521/ORCLPDB + Release Update: 19.28.0.0.0 + Role: PRIMARY + Service Details: + Name: soepdb + Svc State: service soepdb is running + State: AVAILABLE +Events: + + +[root@dbmc1-0 rac-work-dir]# cd /sys/fs/cgroup/memory +[root@dbmc1-0 memory]# cat memory.limit_in_bytes +17179869184 +[root@dbmc1-0 memory]# +[root@dbmc1-0 memory]# cd /sys/fs/cgroup/cpu/ +[root@dbmc1-0 cpu]# cat cpu.cfs_quota_us +200000 +[root@dbmc1-0 cpu]# + + + + + +-- Attempt the memory and cpu change: +$ kubectl apply -f oraclerestart_prov_nodeports_mcpu_change.yaml + +-- After the memory and cpu change: +-- The Oracle Restart Database Pod will be recreated: + +$ kubectl get all -n orestart +NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES +pod/dbmc1-0 0/1 Running 0 13s 10.244.0.124 10.0.10.58 + +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR +service/dbmc1-0-local ClusterIP None 5h3m statefulset.kubernetes.io/pod-name=dbmc1-0 +service/dbmc1-service-nodeport-0-npsvc NodePort 10.96.249.147 1521:30007/TCP,6200:30200/TCP 5h3m statefulset.kubernetes.io/pod-name=dbmc1-0 + +NAME READY AGE CONTAINERS IMAGES +statefulset.apps/dbmc1 0/1 5h3m dbmc1 localhost/oracle/database-orestart:19.3.0-slim + + + +-- After some time: +$ kubectl get all -n orestart +NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES +pod/dbmc1-0 1/1 Running 0 3m 10.244.0.124 10.0.10.58 + +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR +service/dbmc1-0-local ClusterIP None 5h17m statefulset.kubernetes.io/pod-name=dbmc1-0 +service/dbmc1-service-nodeport-0-npsvc NodePort 10.96.249.147 1521:30007/TCP,6200:30200/TCP 5h17m statefulset.kubernetes.io/pod-name=dbmc1-0 + +NAME READY AGE CONTAINERS IMAGES +statefulset.apps/dbmc1 1/1 5h17m dbmc1 localhost/oracle/database-orestart:19.3.0-slim + + + +-- Memory and CPU changes will be reflected as below: + +$ kubectl describe oraclerestarts.database.oracle.com/oraclerestart-sample -n orestart +Name: oraclerestart-sample +Namespace: orestart +Labels: +Annotations: OracleRestarts.database.oracle.com/old-spec: + {"instDetails":{"name":"dbmc1","hostSwLocation":"/scratch/orestart/","workerNode":["10.0.10.58"],"envVars":[{"name":"IGNORE_CRS_PREREQS","... +API Version: database.oracle.com/v4 +Kind: OracleRestart +Metadata: + Creation Timestamp: 2025-09-10T15:45:13Z + Finalizers: + database.oracle.com/oraclerestartfinalizer + Generation: 2 + Resource Version: 234009515 + UID: 4b261940-9e2e-4ee0-8c21-75a57eb1b0ed +Spec: + Asm Storage Details: + Disks By Size: + Disk Names: + /dev/disk/by-partlabel/asm-disk1 + /dev/disk/by-partlabel/asm-disk2 + Storage Size In Gb: 50 + Config Params: + Cpu Count: 4 + Crs Asm Device List: /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 + Crs Asm Disk Dg: +DATA + Crs Asm Disk Dg Redundancy: external + Db Base: /u01/app/oracle + Db Char Set: AL32UTF8 + Db Data File Dest Dg: +DATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: +DATA + Db Response File: + Db Sw Zip File: db_home.zip + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Grid Sw Zip File: grid_home.zip + Host Sw Stage Location: /scratch/software/stage + Inventory: /u01/app/oraInventory + Pga Size: 1G + Processes: 2000 + Sga Size: 3G + Sw Mount Location: /u01 + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Enable Ons: enable + Image: localhost/oracle/database-orestart:19.3.0-slim + Image Pull Policy: IfNotPresent + Inst Details: + Env Vars: + Name: IGNORE_CRS_PREREQS + Value: true + Name: IGNORE_DB_PREREQS + Value: true + Host Sw Location: /scratch/orestart/ + Name: dbmc1 + Worker Node: + 10.0.10.58 + Lb Service: + Node Port Svc: + Name: dbmc1-service-nodeport + Port Mappings: + Node Port: 30007 + Port: 1521 + Protocol: TCP + Target Port: 1521 + Node Port: 30200 + Port: 6200 + Protocol: TCP + Target Port: 6200 + Svc Type: nodeport + Resources: + Limits: + Cpu: 4 + Memory: 20Gi + Requests: + Cpu: 4 + Memory: 20Gi + Security Context: + Sysctls: + Name: kernel.shmall + Value: 2097152 + Name: kernel.sem + Value: 250 32000 100 128 + Name: kernel.shmmax + Value: 8589934592 + Name: kernel.shmmni + Value: 4096 + Service Details: + Name: soepdb + Ssh Key Secret: + Key Mount Location: /mnt/.ssh + Name: ssh-key-secret + Priv Key Secret Name: ssh-privkey + Pub Key Secret Name: ssh-pubkey +Status: + Oracle Restart Nodes: + Name: dbmc1-0 + Node Details: + Instance State: OPEN + Pod State: AVAILABLE + Cluster State: HEALTHY + Mounted Devices: + /dev/disk/by-partlabel/asm-disk1 + /dev/disk/by-partlabel/asm-disk2 + State: AVAILABLE + Asm Details: + Diskgroup: + Disks: + /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 + Name: DATA + Redundancy: EXTERN + Conditions: + Last Transition Time: 2025-09-10T15:57:27Z + Message: reconcile has been queued + Observed Generation: 1 + Reason: LastReconcileCycleQueued + Status: True + Type: ReconcileQueued + Last Transition Time: 2025-09-10T21:03:50Z + Message: reconcile completed successfully + Observed Generation: 2 + Reason: LastReconcileCycleCompleted + Status: True + Type: ReconcileComplete + Config Params: + Crs Asm Device List: /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 + Crs Asm Disk Dg: +DATA + Crs Asm Disk Dg Redundancy: external + Db Base: /u01/app/oracle + Db Data File Dest Dg: +DATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: +DATA + Db Response File: + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Inventory: /u01/app/oraInventory + Connect String: dbmc1-0:1521/PORCLCDB + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Db State: OPEN + External Connect String: Pending + Pdb Connect String: dbmc1-0:1521/ORCLPDB + Release Update: 19.28.0.0.0 + Role: PRIMARY + Service Details: + Name: soepdb + Svc State: service soepdb is running + State: AVAILABLE +Events: + + + + +[root@dbmc1-0 rac-work-dir]# cd /sys/fs/cgroup/memory +[root@dbmc1-0 memory]# cat memory.limit_in_bytes +21474836480 +[root@dbmc1-0 memory]# +[root@dbmc1-0 memory]# cd /sys/fs/cgroup/cpu/ +[root@dbmc1-0 cpu]# cat cpu.cfs_quota_us +400000 +[root@dbmc1-0 cpu]# \ No newline at end of file diff --git a/docs/oraclerestart/provisioning/oraclerestart_prov_nodeports_mcpu_change.yaml b/docs/oraclerestart/provisioning/oraclerestart_prov_nodeports_mcpu_change.yaml new file mode 100644 index 00000000..de342075 --- /dev/null +++ b/docs/oraclerestart/provisioning/oraclerestart_prov_nodeports_mcpu_change.yaml @@ -0,0 +1,117 @@ +# API version of the OracleRestart Custom Resource +apiVersion: database.oracle.com/v4 + +# Resource type being created +kind: OracleRestart + +# Metadata section to define the resource's name and namespace +metadata: + name: oraclerestart-sample # Name of the OracleRestart instance + namespace: orestart # Kubernetes namespace to deploy into + +spec: + # Instance details for Oracle Restart + instDetails: + name: dbmc1 # Logical name of the OracleRestart instance + hostSwLocation: /scratch/orestart/ # Host directory for Oracle GI HOME and Oracle RDBMS HOME + + # Worker nodes where the instance should be scheduled + workerNode: + - 10.0.10.58 # IP address of the target node for deployment + + # Environment variables in case you want to want to ignore prerequisite checks during the GI/RDBSMS Install + envVars: + - name: IGNORE_CRS_PREREQS # If set to true, it will use flags -ignorePreReq and -ignorePrereqFailure during the CRS Installation. Do not set this parameter in the .yaml file if you do not want to use the parameters -ignorePreReq and -ignorePrereqFailure during the CRS Installation. + value: "true" + - name: IGNORE_DB_PREREQS # If set to true, it will use flags -ignorePrereq and -ignorePrereqFailure during the DB Software Installation. Do not set this parameter in the .yaml file if you do not want to use the parameters -ignorePreReq and -ignorePrereqFailure during the DB Software Installation. + value: "true" + + # NodePort service configuration for exposing DB port externally + nodePortSvc: + name: dbmc1-service-nodeport # Name of the Kubernetes Service + svcType: nodeport # Service type (NodePort to expose externally) + + # Mapping between container and host ports + portMappings: + - port: 1521 # Port inside container (Oracle Listener port) + targetPort: 1521 # Target port for the service + protocol: TCP # Protocol type (TCP for Oracle) + nodePort: 30007 # Host port exposed on worker node (must be in NodePort range) + - port: 6200 # Port inside container (for ONS) + targetPort: 6200 # Target port for the service + protocol: TCP # Protocol type (TCP for Oracle) + nodePort: 30200 # Host port exposed on worker node (must be in NodePort range) + enableOns: "enable" # If you want to have ONS notification configured on the specified port. Default value is "enable" + + # ASM (Automatic Storage Management) storage configuration + asmStorageDetails: + disksBySize: + - storageSizeInGb: 50 # Each disk in this group has 50Gi of storage + diskNames: + - /dev/disk/by-partlabel/asm-disk1 # ASM disk device path 1 + - /dev/disk/by-partlabel/asm-disk2 # ASM disk device path 2 + + # SSH key secret used for remote access and automation + sshKeySecret: + name: ssh-key-secret # Kubernetes secret name + privKeySecretName: ssh-privkey # Private key inside secret + pubKeySecretName: ssh-pubkey # Public key inside secret + + # Secret containing DB user credentials + dbSecret: + name: db-user-pass-pkutl # Secret name + keyFileName: key.pem # Key file name inside secret + pwdFileName: pwdfile.enc # Password file name inside secret + + # Image for Oracle Restart container + image: dbocir/oracle/database-orestart:19.3.0-slim # <-- Replace with your image registry + + # Policy to pull image + # Use the value as "Always" if you want use the latest version of the image from the container registry. + # Use the value as "IfNotPresent" it the image is not already present locally. + imagePullPolicy: IfNotPresent + + # Optional service account for security context permissions + # serviceAccountName: oraclerestart # <-- Required for OpenShift; optional for vanilla Kubernetes + + # Service-specific database name and related details + serviceDetails: + name: soepdb # Name of the Oracle service (pluggable DB) + + # Resource requests and limits for memory and CPU + resources: + requests: + memory: "20Gi" # Minimum memory required + cpu: "4" # Minimum CPU required + limits: + memory: "20Gi" # Max memory allowed + cpu: "4" # Max CPU allowed + + # Kernel-level system control parameters for Oracle + securityContext: + sysctls: + - name: kernel.shmall + value: "2097152" + - name: kernel.sem + value: "250 32000 100 128" + - name: kernel.shmmax + value: "8589934592" + - name: kernel.shmmni + value: "4096" + + # Configuration parameters for Oracle Grid and Database homes + configParams: + gridHome: "/u01/app/19c/grid" # Oracle Grid home path + gridBase: "/u01/app/grid" # Grid base directory + dbHome: "/u01/app/oracle/product/19c/dbhome_1" # Oracle DB home path + dbBase: "/u01/app/oracle" # Oracle base directory + crsAsmDeviceList: /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 # Comma-separated list of ASM device files + inventory: "/u01/app/oraInventory" # Inventory location + gridSwZipFile: "grid_home.zip" # Grid infrastructure software ZIP + dbSwZipFile: "db_home.zip" # Database software ZIP + sgaSize: "3G" # Size of the System Global Area + pgaSize: "1G" # Size of the Program Global Area + processes: 2000 # Oracle process limit + cpuCount: 4 # Number of CPUs to allocate + dbName: "PORCLCDB" # Oracle database name + hostSwStageLocation: /scratch/software/stage # Host location where Oracle software ZIPs are staged, same location will be mounted inside the Oracle Restart Pod diff --git a/docs/oraclerestart/provisioning/oraclerestart_prov_rupatch.yaml b/docs/oraclerestart/provisioning/oraclerestart_prov_rupatch.yaml new file mode 100644 index 00000000..f8ba6484 --- /dev/null +++ b/docs/oraclerestart/provisioning/oraclerestart_prov_rupatch.yaml @@ -0,0 +1,120 @@ +# API version of the OracleRestart Custom Resource +apiVersion: database.oracle.com/v4 + +# Resource type being created +kind: OracleRestart + +# Metadata section to define the resource's name and namespace +metadata: + name: oraclerestart-sample # Name of the OracleRestart instance + namespace: orestart # Kubernetes namespace to deploy into + +spec: + # Instance details for Oracle Restart + instDetails: + name: dbmc1 # Logical name of the OracleRestart instance + hostSwLocation: /scratch/orestart/ # Host directory for Oracle GI HOME and Oracle RDBMS HOME + + # Worker nodes where the instance should be scheduled + workerNode: + - 10.0.10.58 # IP address of the target node for deployment + + # Environment variables in case you want to want to ignore prerequisite checks during the GI/RDBSMS Install + envVars: + - name: IGNORE_CRS_PREREQS # If set to true, it will use flags -ignorePreReq and -ignorePrereqFailure during the CRS Installation. Do not set this parameter in the .yaml file if you do not want to use the parameters -ignorePreReq and -ignorePrereqFailure during the CRS Installation. + value: "true" + - name: IGNORE_DB_PREREQS # If set to true, it will use flags -ignorePrereq and -ignorePrereqFailure during the DB Software Installation. Do not set this parameter in the .yaml file if you do not want to use the parameters -ignorePreReq and -ignorePrereqFailure during the DB Software Installation. + value: "true" + + # NodePort service configuration for exposing DB port externally + nodePortSvc: + name: dbmc1-service-nodeport # Name of the Kubernetes Service + svcType: nodeport # Service type (NodePort to expose externally) + + # Mapping between container and host ports + portMappings: + - port: 1521 # Port inside container (Oracle Listener port) + targetPort: 1521 # Target port for the service + protocol: TCP # Protocol type (TCP for Oracle) + nodePort: 30007 # Host port exposed on worker node (must be in NodePort range) + - port: 6200 # Port inside container (for ONS) + targetPort: 6200 # Target port for the service + protocol: TCP # Protocol type (TCP for Oracle) + nodePort: 30200 # Host port exposed on worker node (must be in NodePort range) + enableOns: "enable" # If you want to have ONS notification configured on the specified port. Default value is "enable" + + # ASM (Automatic Storage Management) storage configuration + asmStorageDetails: + disksBySize: + - storageSizeInGb: 50 # Each disk in this group has 50Gi of storage + diskNames: + - /dev/disk/by-partlabel/asm-disk1 # ASM disk device path 1 + - /dev/disk/by-partlabel/asm-disk2 # ASM disk device path 2 + + # SSH key secret used for remote access and automation + sshKeySecret: + name: ssh-key-secret # Kubernetes secret name + privKeySecretName: ssh-privkey # Private key inside secret + pubKeySecretName: ssh-pubkey # Public key inside secret + + # Secret containing DB user credentials + dbSecret: + name: db-user-pass-pkutl # Secret name + keyFileName: key.pem # Key file name inside secret + pwdFileName: pwdfile.enc # Password file name inside secret + + # Image for Oracle Restart container + image: dbocir/oracle/database-orestart:19.3.0-slim # <-- Replace with your image registry + + # Policy to pull image + # Use the value as "Always" if you want use the latest version of the image from the container registry. + # Use the value as "IfNotPresent" it the image is not already present locally. + imagePullPolicy: IfNotPresent + + # Optional service account for security context permissions + # serviceAccountName: oraclerestart # <-- Required for OpenShift; optional for vanilla Kubernetes + + # Service-specific database name and related details + serviceDetails: + name: soepdb # Name of the Oracle service (pluggable DB) + + # Resource requests and limits for memory and CPU + resources: + requests: + memory: "16Gi" # Minimum memory required + cpu: "2" # Minimum CPU required + limits: + memory: "16Gi" # Max memory allowed + cpu: "2" # Max CPU allowed + + # Kernel-level system control parameters for Oracle + securityContext: + sysctls: + - name: kernel.shmall + value: "2097152" + - name: kernel.sem + value: "250 32000 100 128" + - name: kernel.shmmax + value: "8589934592" + - name: kernel.shmmni + value: "4096" + + # Configuration parameters for Oracle Grid and Database homes + configParams: + gridHome: "/u01/app/19c/grid" # Oracle Grid home path + gridBase: "/u01/app/grid" # Grid base directory + dbHome: "/u01/app/oracle/product/19c/dbhome_1" # Oracle DB home path + dbBase: "/u01/app/oracle" # Oracle base directory + crsAsmDeviceList: /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 # Comma-separated list of ASM device files + inventory: "/u01/app/oraInventory" # Inventory location + gridSwZipFile: "grid_home.zip" # Grid infrastructure software ZIP + dbSwZipFile: "db_home.zip" # Database software ZIP + sgaSize: "3G" # Size of the System Global Area + pgaSize: "1G" # Size of the Program Global Area + processes: 2000 # Oracle process limit + cpuCount: 4 # Number of CPUs to allocate + dbName: "PORCLCDB" # Oracle database name + oPatchSwZipFile: "p6880880_190000_Linux-x86-64.zip" # Opatch software ZIP + hostSwStageLocation: /scratch/software/stage/19.3.0 # Host location where Oracle software ZIPs are staged, same location will be mounted inside the Oracle Restart Pod + ruPatchLocation: /scratch/software/ru_patch/37957391 # Directory containing the unzipped RU patch, same location will be mounted inside the Oracle Restart Pod + oPatchLocation: /scratch/software/opatch # Location of the Opatch Software, same location will be mounted inside the Oracle Restart Pod diff --git a/docs/oraclerestart/provisioning/oraclerestart_prov_rupatch_oneoff_storageclass.yaml b/docs/oraclerestart/provisioning/oraclerestart_prov_rupatch_oneoff_storageclass.yaml new file mode 100644 index 00000000..dce27302 --- /dev/null +++ b/docs/oraclerestart/provisioning/oraclerestart_prov_rupatch_oneoff_storageclass.yaml @@ -0,0 +1,134 @@ +# API version of the OracleRestart Custom Resource +apiVersion: database.oracle.com/v4 + +# Resource type being created +kind: OracleRestart + +# Metadata section to define the resource's name and namespace +metadata: + name: oraclerestart-sample # Name of the OracleRestart instance + namespace: orestart # Kubernetes namespace to deploy into + +spec: + # Instance details for Oracle Restart + instDetails: + name: dbmc1 # Logical name of the OracleRestart instance + + # Worker nodes where the instance should be scheduled + workerNode: + - 10.0.10.58 # IP address of the target node for deployment + + # Size of the storage location for software. This parameter will be effective when the local SW location is NOT specified by hostSwLocation + swLocStorageSizeInGb: 300 + + # Environment variables in case you want to want to ignore prerequisite checks during the GI/RDBSMS Install + envVars: + - name: IGNORE_CRS_PREREQS # If set to true, it will use flags -ignorePreReq and -ignorePrereqFailure during the CRS Installation. Do not set this parameter in the .yaml file if you do not want to use the parameters -ignorePreReq and -ignorePrereqFailure during the CRS Installation. + value: "true" + - name: IGNORE_DB_PREREQS # If set to true, it will use flags -ignorePrereq and -ignorePrereqFailure during the DB Software Installation. Do not set this parameter in the .yaml file if you do not want to use the parameters -ignorePreReq and -ignorePrereqFailure during the DB Software Installation. + value: "true" + + # NodePort service configuration for exposing DB port externally + nodePortSvc: + name: dbmc1-service-nodeport # Name of the Kubernetes Service + svcType: nodeport # Service type (NodePort to expose externally) + + # Mapping between container and host ports + portMappings: + - port: 1521 # Port inside container (Oracle Listener port) + targetPort: 1521 # Target port for the service + protocol: TCP # Protocol type (TCP for Oracle) + nodePort: 30007 # Host port exposed on worker node (must be in NodePort range) + - port: 6200 # Port inside container (for ONS) + targetPort: 6200 # Target port for the service + protocol: TCP # Protocol type (TCP for Oracle) + nodePort: 30200 # Host port exposed on worker node (must be in NodePort range) + enableOns: "enable" # If you want to have ONS notification configured on the specified port. Default value is "enable" + + # Custom Storage class to be used for dynamic provisioning (if applicable) + # You have separate parameters to specify the storage class for all the diskgroups (CRS, DATA, RECO, REDO) + # You have a parameter to specify the storage class for the software location for the GI and RDBMS HOME + crsDgStorageClass: "oci-bv" # Specify storage class for the CRS Diskgroup, Currently using "oci-bv" but you can specify another available storage class as well + swDgStorageClass: "oci-bv" # Specify storage class for the storage location for software, Currently using "oci-bv" but you can specify another available storage class as well + + # ASM (Automatic Storage Management) storage configuration + asmStorageDetails: + disksBySize: + - storageSizeInGb: 50 # Each disk in this group has 50Gi of storage + diskNames: + - /dev/asm-disk1 # Device for ASM disk 1 + - /dev/asm-disk2 # Device for ASM disk 2 + + # SSH key secret used for remote access and automation + sshKeySecret: + name: ssh-key-secret # Kubernetes secret name + privKeySecretName: ssh-privkey # Private key inside secret + pubKeySecretName: ssh-pubkey # Public key inside secret + + # Secret containing DB user credentials + dbSecret: + name: db-user-pass-pkutl # Secret name + keyFileName: key.pem # Key file name inside secret + pwdFileName: pwdfile.enc # Password file name inside secret + + # Image for Oracle Restart container + image: dbocir/oracle/database-orestart:19.3.0-slim # <-- Replace with your image registry + + # Policy to pull image + # Use the value as "Always" if you want use the latest version of the image from the container registry. + # Use the value as "IfNotPresent" it the image is not already present locally. + imagePullPolicy: IfNotPresent + + # Optional service account for security context permissions + # serviceAccountName: oraclerestart # <-- Required for OpenShift; optional for vanilla Kubernetes + + # Service-specific database name and related details + serviceDetails: + name: soepdb # Name of the Oracle service (pluggable DB) + + # Resource requests and limits for memory and CPU + resources: + requests: + memory: "16Gi" # Minimum memory required + cpu: "2" # Minimum CPU required + limits: + memory: "16Gi" # Max memory allowed + cpu: "2" # Max CPU allowed + + # Kernel-level system control parameters for Oracle + securityContext: + sysctls: + - name: kernel.shmall + value: "2097152" + - name: kernel.sem + value: "250 32000 100 128" + - name: kernel.shmmax + value: "8589934592" + - name: kernel.shmmni + value: "4096" + + # Configuration parameters for Oracle Grid and Database homes + configParams: + gridHome: "/u01/app/19c/grid" # Oracle Grid home path + gridBase: "/u01/app/grid" # Grid base directory + dbHome: "/u01/app/oracle/product/19c/dbhome_1" # Oracle DB home path + dbBase: "/u01/app/oracle" # Oracle base directory + crsAsmDeviceList: /dev/asm-disk1,/dev/asm-disk2 # Comma-separated list of ASM device files + inventory: "/u01/app/oraInventory" # Inventory location + gridSwZipFile: "grid_home.zip" # Grid infrastructure software ZIP + dbSwZipFile: "db_home.zip" # Database software ZIP + sgaSize: "3G" # Size of the System Global Area + pgaSize: "1G" # Size of the Program Global Area + processes: 2000 # Oracle process limit + cpuCount: 4 # Number of CPUs to allocate + dbName: "PORCLCDB" # Oracle database name + pdbName: "TESTPDB" # PDB name, in case you want to explicitly specify the PDB Name + oPatchSwZipFile: "p6880880_190000_Linux-x86-64.zip" # Opatch software ZIP + swStagePvc: pv-stage-vol-claim # PVC name for the Software Location mounting the NFS + swStagePvcMountLocation: "/stage" # Mount point for the Software Location inside the Pod + hostSwStageLocation: /stage/software/19c/19.3.0 # Base Release Software Location on the NFS based Persistent Volume, Pod will also have this software in the same path + ruPatchLocation: /stage/software/19c/19.28/37957391 # Directory containing the unzipped RU patch on the NFS based Persistent Volume, Pod will also have this software in the same path + oPatchLocation: /stage/software/19c # Location of the Opatch Software on the NFS based Persistent Volume, Pod will also have this software in the same path + oneOffLocation: /stage/software/19c/oneoff # One-off patch files directory where all the one-off patches for GI and RDBMS Home are unzipped + gridOneOffIds: "38336965" # Comma-separated Grid one-off patch IDs + dbOneOffIds: "38336965,34436514" # Comma-separated DB one-off patch IDs diff --git a/docs/oraclerestart/provisioning/oraclerestart_prov_rupatch_pvc.yaml b/docs/oraclerestart/provisioning/oraclerestart_prov_rupatch_pvc.yaml new file mode 100644 index 00000000..9211c549 --- /dev/null +++ b/docs/oraclerestart/provisioning/oraclerestart_prov_rupatch_pvc.yaml @@ -0,0 +1,131 @@ +# API version of the OracleRestart Custom Resource +apiVersion: database.oracle.com/v4 + +# Resource type being created +kind: OracleRestart + +# Metadata section to define the resource's name and namespace +metadata: + name: oraclerestart-sample # Name of the OracleRestart instance + namespace: orestart # Kubernetes namespace to deploy into + +spec: + # Instance details for Oracle Restart + instDetails: + name: dbmc1 # Logical name of the OracleRestart instance + + # Worker nodes where the instance should be scheduled + workerNode: + - 10.0.10.58 # IP address of the target node for deployment + + # Size of the storage location for software. This parameter will be effective when the local SW location is NOT specified by hostSwLocation + swLocStorageSizeInGb: 300 + + # Environment variables in case you want to want to ignore prerequisite checks during the GI/RDBSMS Install + envVars: + - name: IGNORE_CRS_PREREQS # If set to true, it will use flags -ignorePreReq and -ignorePrereqFailure during the CRS Installation. Do not set this parameter in the .yaml file if you do not want to use the parameters -ignorePreReq and -ignorePrereqFailure during the CRS Installation. + value: "true" + - name: IGNORE_DB_PREREQS # If set to true, it will use flags -ignorePrereq and -ignorePrereqFailure during the DB Software Installation. Do not set this parameter in the .yaml file if you do not want to use the parameters -ignorePreReq and -ignorePrereqFailure during the DB Software Installation. + value: "true" + + # NodePort service configuration for exposing DB port externally + nodePortSvc: + name: dbmc1-service-nodeport # Name of the Kubernetes Service + svcType: nodeport # Service type (NodePort to expose externally) + + # Mapping between container and host ports + portMappings: + - port: 1521 # Port inside container (Oracle Listener port) + targetPort: 1521 # Target port for the service + protocol: TCP # Protocol type (TCP for Oracle) + nodePort: 30007 # Host port exposed on worker node (must be in NodePort range) + - port: 6200 # Port inside container (for ONS) + targetPort: 6200 # Target port for the service + protocol: TCP # Protocol type (TCP for Oracle) + nodePort: 30200 # Host port exposed on worker node (must be in NodePort range) + enableOns: "enable" # If you want to have ONS notification configured on the specified port. Default value is "enable" + + # Custom Storage class to be used for dynamic provisioning (if applicable) + # You have separate parameters to specify the storage class for all the diskgroups (CRS, DATA, RECO, REDO) + # You have a parameter to specify the storage class for the software location for the GI and RDBMS HOME + crsDgStorageClass: "oci-bv" # Specify storage class for the CRS Diskgroup, Currently using "oci-bv" but you can specify another available storage class as well + swDgStorageClass: "oci-bv" # Specify storage class for the storage location for software, Currently using "oci-bv" but you can specify another available storage class as well + + # ASM (Automatic Storage Management) storage configuration + asmStorageDetails: + disksBySize: + - storageSizeInGb: 50 # Each disk in this group has 50Gi of storage + diskNames: + - /dev/asm-disk1 # Device for ASM disk 1 + - /dev/asm-disk2 # Device for ASM disk 2 + + # SSH key secret used for remote access and automation + sshKeySecret: + name: ssh-key-secret # Kubernetes secret name + privKeySecretName: ssh-privkey # Private key inside secret + pubKeySecretName: ssh-pubkey # Public key inside secret + + # Secret containing DB user credentials + dbSecret: + name: db-user-pass-pkutl # Secret name + keyFileName: key.pem # Key file name inside secret + pwdFileName: pwdfile.enc # Password file name inside secret + + # Image for Oracle Restart container + image: dbocir/oracle/database-orestart:19.3.0-slim # <-- Replace with your image registry + + # Policy to pull image + # Use the value as "Always" if you want use the latest version of the image from the container registry. + # Use the value as "IfNotPresent" it the image is not already present locally. + imagePullPolicy: IfNotPresent + + # Optional service account for security context permissions + # serviceAccountName: oraclerestart # <-- Required for OpenShift; optional for vanilla Kubernetes + + # Service-specific database name and related details + serviceDetails: + name: soepdb # Name of the Oracle service (pluggable DB) + + # Resource requests and limits for memory and CPU + resources: + requests: + memory: "16Gi" # Minimum memory required + cpu: "2" # Minimum CPU required + limits: + memory: "16Gi" # Max memory allowed + cpu: "2" # Max CPU allowed + + # Kernel-level system control parameters for Oracle + securityContext: + sysctls: + - name: kernel.shmall + value: "2097152" + - name: kernel.sem + value: "250 32000 100 128" + - name: kernel.shmmax + value: "8589934592" + - name: kernel.shmmni + value: "4096" + + # Configuration parameters for Oracle Grid and Database homes + configParams: + gridHome: "/u01/app/19c/grid" # Oracle Grid home path + gridBase: "/u01/app/grid" # Grid base directory + dbHome: "/u01/app/oracle/product/19c/dbhome_1" # Oracle DB home path + dbBase: "/u01/app/oracle" # Oracle base directory + crsAsmDeviceList: /dev/asm-disk1,/dev/asm-disk2 # Comma-separated list of ASM device files + inventory: "/u01/app/oraInventory" # Inventory location + gridSwZipFile: "grid_home.zip" # Grid infrastructure software ZIP + dbSwZipFile: "db_home.zip" # Database software ZIP + oPatchSwZipFile: "p6880880_190000_Linux-x86-64.zip" # Opatch software ZIP + sgaSize: "3G" # Size of the System Global Area + pgaSize: "1G" # Size of the Program Global Area + processes: 2000 # Oracle process limit + cpuCount: 4 # Number of CPUs to allocate + dbName: "PORCLCDB" # Oracle database name + pdbName: "TESTPDB" # PDB name, in case you want to explicitly specify the PDB Name + swStagePvc: pv-stage-vol-claim # PVC name for the Software Location mounting the NFS + swStagePvcMountLocation: "/stage" # Mount point for the Software Location inside the Pod + hostSwStageLocation: /stage/software/19c/19.3.0 # Base Release Software Location on the NFS based Persistent Volume, Pod will also have this software in the same path + ruPatchLocation: /stage/software/19c/19.28/37957391 # Directory containing the unzipped RU patch on the NFS based Persistent Volume, Pod will also have this software in the same path + oPatchLocation: /stage/software/19c # Location of the Opatch Software on the NFS based Persistent Volume, Pod will also have this software in the same path diff --git a/docs/oraclerestart/provisioning/oraclerestart_prov_storage_class.yaml b/docs/oraclerestart/provisioning/oraclerestart_prov_storage_class.yaml new file mode 100644 index 00000000..9c19754b --- /dev/null +++ b/docs/oraclerestart/provisioning/oraclerestart_prov_storage_class.yaml @@ -0,0 +1,124 @@ +# API version of the OracleRestart Custom Resource +apiVersion: database.oracle.com/v4 + +# Resource type being created +kind: OracleRestart + +# Metadata section to define the resource's name and namespace +metadata: + name: oraclerestart-sample # Name of the OracleRestart instance + namespace: orestart # Kubernetes namespace to deploy into + +spec: + # Instance details for Oracle Restart + instDetails: + name: dbmc1 # Logical name of the OracleRestart instance + + # Worker nodes where the instance should be scheduled + workerNode: + - 10.0.10.58 # IP address of the target node for deployment + + # Size of the storage location for software. This parameter will be effective when the local SW location is NOT specified by hostSwLocation + swLocStorageSizeInGb: 300 + + # Environment variables in case you want to want to ignore prerequisite checks during the GI/RDBSMS Install + envVars: + - name: IGNORE_CRS_PREREQS # If set to true, it will use flags -ignorePreReq and -ignorePrereqFailure during the CRS Installation. Do not set this parameter in the .yaml file if you do not want to use the parameters -ignorePreReq and -ignorePrereqFailure during the CRS Installation. + value: "true" + - name: IGNORE_DB_PREREQS # If set to true, it will use flags -ignorePrereq and -ignorePrereqFailure during the DB Software Installation. Do not set this parameter in the .yaml file if you do not want to use the parameters -ignorePreReq and -ignorePrereqFailure during the DB Software Installation. + value: "true" + + # NodePort service configuration for exposing DB port externally + nodePortSvc: + name: dbmc1-service-nodeport # Name of the Kubernetes Service + svcType: nodeport # Service type (NodePort to expose externally) + + # Mapping between container and host ports + portMappings: + - port: 1521 # Port inside container (Oracle Listener port) + targetPort: 1521 # Target port for the service + protocol: TCP # Protocol type (TCP for Oracle) + nodePort: 30007 # Host port exposed on worker node (must be in NodePort range) + - port: 6200 # Port inside container (for ONS) + targetPort: 6200 # Target port for the service + protocol: TCP # Protocol type (TCP for Oracle) + nodePort: 30200 # Host port exposed on worker node (must be in NodePort range) + enableOns: "enable" # If you want to have ONS notification configured on the specified port. Default value is "enable" + + # Custom Storage class to be used for dynamic provisioning (if applicable) + # You have separate parameters to specify the storage class for all the diskgroups (CRS, DATA, RECO, REDO) + crsDgStorageClass: "oci-bv" # Specify storage class for the CRS Diskgroup, Currently using "oci-bv" but you can specify another available storage class as well + swDgStorageClass: "oci-bv" # Specify storage class for the storage location for software, Currently using "oci-bv" but you can specify another available storage class as well + + # ASM (Automatic Storage Management) storage configuration + asmStorageDetails: + disksBySize: + - storageSizeInGb: 50 # Each disk in this group has 50Gi of storage + diskNames: + - /dev/asm-disk1 # ASM disk device path 1 + - /dev/asm-disk2 # ASM disk device path 2 + + # SSH key secret used for remote access and automation + sshKeySecret: + name: ssh-key-secret # Kubernetes secret name + privKeySecretName: ssh-privkey # Private key inside secret + pubKeySecretName: ssh-pubkey # Public key inside secret + + # Secret containing DB user credentials + dbSecret: + name: db-user-pass-pkutl # Secret name + keyFileName: key.pem # Key file name inside secret + pwdFileName: pwdfile.enc # Password file name inside secret + + # Image for Oracle Restart container + image: dbocir/oracle/database-orestart:19.3.0-slim # <-- Replace with your image registry + + # Policy to pull image + # Use the value as "Always" if you want use the latest version of the image from the container registry. + # Use the value as "IfNotPresent" it the image is not already present locally. + imagePullPolicy: IfNotPresent + + # Optional service account for security context permissions + # serviceAccountName: oraclerestart # <-- Required for OpenShift; optional for vanilla Kubernetes + + # Service-specific database name and related details + serviceDetails: + name: soepdb # Name of the Oracle service (pluggable DB) + + # Resource requests and limits for memory and CPU + resources: + requests: + memory: "16Gi" # Minimum memory required + cpu: "2" # Minimum CPU required + limits: + memory: "16Gi" # Max memory allowed + cpu: "2" # Max CPU allowed + + # Kernel-level system control parameters for Oracle + securityContext: + sysctls: + - name: kernel.shmall + value: "2097152" + - name: kernel.sem + value: "250 32000 100 128" + - name: kernel.shmmax + value: "8589934592" + - name: kernel.shmmni + value: "4096" + + # Configuration parameters for Oracle Grid and Database homes + configParams: + gridHome: "/u01/app/19c/grid" # Oracle Grid home path + gridBase: "/u01/app/grid" # Grid base directory + dbHome: "/u01/app/oracle/product/19c/dbhome_1" # Oracle DB home path + dbBase: "/u01/app/oracle" # Oracle base directory + crsAsmDeviceList: /dev/asm-disk1,/dev/asm-disk2 # Comma-separated list of ASM device files + inventory: "/u01/app/oraInventory" # Inventory location + gridSwZipFile: "grid_home.zip" # Grid infrastructure software ZIP + dbSwZipFile: "db_home.zip" # Database software ZIP + sgaSize: "3G" # Size of the System Global Area + pgaSize: "1G" # Size of the Program Global Area + processes: 2000 # Oracle process limit + cpuCount: 4 # Number of CPUs to allocate + dbName: "PORCLCDB" # Oracle database name + hostSwStageLocation: /scratch/software/stage # Host location where Oracle software ZIPs are staged, same location will be mounted inside the Oracle Restart Pod diff --git a/docs/oraclerestart/provisioning/oraclerestart_prov_storage_class_after_sw_home_resize.yaml b/docs/oraclerestart/provisioning/oraclerestart_prov_storage_class_after_sw_home_resize.yaml new file mode 100644 index 00000000..1f59f3cc --- /dev/null +++ b/docs/oraclerestart/provisioning/oraclerestart_prov_storage_class_after_sw_home_resize.yaml @@ -0,0 +1,125 @@ +# API version of the OracleRestart Custom Resource +apiVersion: database.oracle.com/v4 + +# Resource type being created +kind: OracleRestart + +# Metadata section to define the resource's name and namespace +metadata: + name: oraclerestart-sample # Name of the OracleRestart instance + namespace: orestart # Kubernetes namespace to deploy into + +spec: + # Instance details for Oracle Restart + instDetails: + name: dbmc1 # Logical name of the OracleRestart instance + + # Worker nodes where the instance should be scheduled + workerNode: + - 10.0.10.58 # IP address of the target node for deployment + + # Size of the storage location for software. This parameter will be effective when the local SW location is NOT specified by hostSwLocation + swLocStorageSizeInGb: 400 + + # Environment variables in case you want to want to ignore prerequisite checks during the GI/RDBSMS Install + envVars: + - name: IGNORE_CRS_PREREQS # If set to true, it will use flags -ignorePreReq and -ignorePrereqFailure during the CRS Installation. Do not set this parameter in the .yaml file if you do not want to use the parameters -ignorePreReq and -ignorePrereqFailure during the CRS Installation. + value: "true" + - name: IGNORE_DB_PREREQS # If set to true, it will use flags -ignorePrereq and -ignorePrereqFailure during the DB Software Installation. Do not set this parameter in the .yaml file if you do not want to use the parameters -ignorePreReq and -ignorePrereqFailure during the DB Software Installation. + value: "true" + + # NodePort service configuration for exposing DB port externally + nodePortSvc: + name: dbmc1-service-nodeport # Name of the Kubernetes Service + svcType: nodeport # Service type (NodePort to expose externally) + + # Mapping between container and host ports + portMappings: + - port: 1521 # Port inside container (Oracle Listener port) + targetPort: 1521 # Target port for the service + protocol: TCP # Protocol type (TCP for Oracle) + nodePort: 30007 # Host port exposed on worker node (must be in NodePort range) + - port: 6200 # Port inside container (for ONS) + targetPort: 6200 # Target port for the service + protocol: TCP # Protocol type (TCP for Oracle) + nodePort: 30200 # Host port exposed on worker node (must be in NodePort range) + enableOns: "enable" # If you want to have ONS notification configured on the specified port. Default value is "enable" + + # Custom Storage class to be used for dynamic provisioning (if applicable) + # You have separate parameters to specify the storage class for all the diskgroups (CRS, DATA, RECO, REDO) + crsDgStorageClass: "oci-bv" # Specify storage class for the CRS Diskgroup, Currently using "oci-bv" but you can specify another available storage class as well + swDgStorageClass: "oci-bv" # Specify storage class for the storage location for software, Currently using "oci-bv" but you can specify another available storage class as well + + # ASM (Automatic Storage Management) storage configuration + asmStorageDetails: + disksBySize: + - storageSizeInGb: 50 # Each disk in this group has 50Gi of storage + diskNames: + - /dev/asm-disk1 # ASM disk device path 1 + - /dev/asm-disk2 # ASM disk device path 2 + + # SSH key secret used for remote access and automation + sshKeySecret: + name: ssh-key-secret # Kubernetes secret name + privKeySecretName: ssh-privkey # Private key inside secret + pubKeySecretName: ssh-pubkey # Public key inside secret + + # Secret containing DB user credentials + dbSecret: + name: db-user-pass-pkutl # Secret name + keyFileName: key.pem # Key file name inside secret + pwdFileName: pwdfile.enc # Password file name inside secret + + # Image for Oracle Restart container + image: dbocir/oracle/database-orestart:19.3.0-slim # <-- Replace with your image registry + + # Policy to pull image + # Use the value as "Always" if you want use the latest version of the image from the container registry. + # Use the value as "IfNotPresent" it the image is not already present locally. + imagePullPolicy: IfNotPresent + + # Optional service account for security context permissions + # serviceAccountName: oraclerestart # <-- Required for OpenShift; optional for vanilla Kubernetes + + # Service-specific database name and related details + serviceDetails: + name: soepdb # Name of the Oracle service (pluggable DB) + + # Resource requests and limits for memory and CPU + resources: + requests: + memory: "16Gi" # Minimum memory required + cpu: "2" # Minimum CPU required + limits: + memory: "16Gi" # Max memory allowed + cpu: "2" # Max CPU allowed + + # Kernel-level system control parameters for Oracle + securityContext: + sysctls: + - name: kernel.shmall + value: "2097152" + - name: kernel.sem + value: "250 32000 100 128" + - name: kernel.shmmax + value: "8589934592" + - name: kernel.shmmni + value: "4096" + + # Configuration parameters for Oracle Grid and Database homes + configParams: + gridHome: "/u01/app/19c/grid" # Oracle Grid home path + gridBase: "/u01/app/grid" # Grid base directory + dbHome: "/u01/app/oracle/product/19c/dbhome_1" # Oracle DB home path + dbBase: "/u01/app/oracle" # Oracle base directory + crsAsmDeviceList: /dev/asm-disk1,/dev/asm-disk2 # Comma-separated list of ASM device files + inventory: "/u01/app/oraInventory" # Inventory location + gridSwZipFile: "grid_home.zip" # Grid infrastructure software ZIP + dbSwZipFile: "db_home.zip" # Database software ZIP + sgaSize: "3G" # Size of the System Global Area + pgaSize: "1G" # Size of the Program Global Area + processes: 2000 # Oracle process limit + cpuCount: 4 # Number of CPUs to allocate + dbName: "PORCLCDB" # Oracle database name + hostSwStageLocation: /scratch/software/stage # Host location where Oracle software ZIPs are staged, same location will be mounted inside the Oracle Restart Pod + diff --git a/docs/oraclerestart/provisioning/oraclerestart_prov_storage_class_before_sw_home_resize.yaml b/docs/oraclerestart/provisioning/oraclerestart_prov_storage_class_before_sw_home_resize.yaml new file mode 100644 index 00000000..1d221bd7 --- /dev/null +++ b/docs/oraclerestart/provisioning/oraclerestart_prov_storage_class_before_sw_home_resize.yaml @@ -0,0 +1,125 @@ +# API version of the OracleRestart Custom Resource +apiVersion: database.oracle.com/v4 + +# Resource type being created +kind: OracleRestart + +# Metadata section to define the resource's name and namespace +metadata: + name: oraclerestart-sample # Name of the OracleRestart instance + namespace: orestart # Kubernetes namespace to deploy into + +spec: + # Instance details for Oracle Restart + instDetails: + name: dbmc1 # Logical name of the OracleRestart instance + + # Worker nodes where the instance should be scheduled + workerNode: + - 10.0.10.58 # IP address of the target node for deployment + + # Size of the storage location for software. This parameter will be effective when the local SW location is NOT specified by hostSwLocation + swLocStorageSizeInGb: 300 + + # Environment variables in case you want to want to ignore prerequisite checks during the GI/RDBSMS Install + envVars: + - name: IGNORE_CRS_PREREQS # If set to true, it will use flags -ignorePreReq and -ignorePrereqFailure during the CRS Installation. Do not set this parameter in the .yaml file if you do not want to use the parameters -ignorePreReq and -ignorePrereqFailure during the CRS Installation. + value: "true" + - name: IGNORE_DB_PREREQS # If set to true, it will use flags -ignorePrereq and -ignorePrereqFailure during the DB Software Installation. Do not set this parameter in the .yaml file if you do not want to use the parameters -ignorePreReq and -ignorePrereqFailure during the DB Software Installation. + value: "true" + + # NodePort service configuration for exposing DB port externally + nodePortSvc: + name: dbmc1-service-nodeport # Name of the Kubernetes Service + svcType: nodeport # Service type (NodePort to expose externally) + + # Mapping between container and host ports + portMappings: + - port: 1521 # Port inside container (Oracle Listener port) + targetPort: 1521 # Target port for the service + protocol: TCP # Protocol type (TCP for Oracle) + nodePort: 30007 # Host port exposed on worker node (must be in NodePort range) + - port: 6200 # Port inside container (for ONS) + targetPort: 6200 # Target port for the service + protocol: TCP # Protocol type (TCP for Oracle) + nodePort: 30200 # Host port exposed on worker node (must be in NodePort range) + enableOns: "enable" # If you want to have ONS notification configured on the specified port. Default value is "enable" + + # Custom Storage class to be used for dynamic provisioning (if applicable) + # You have separate parameters to specify the storage class for all the diskgroups (CRS, DATA, RECO, REDO) + crsDgStorageClass: "oci-bv" # Specify storage class for the CRS Diskgroup, Currently using "oci-bv" but you can specify another available storage class as well + swDgStorageClass: "oci-bv" # Specify storage class for the storage location for software, Currently using "oci-bv" but you can specify another available storage class as well + + # ASM (Automatic Storage Management) storage configuration + asmStorageDetails: + disksBySize: + - storageSizeInGb: 50 # Each disk in this group has 50Gi of storage + diskNames: + - /dev/asm-disk1 # ASM disk device path 1 + - /dev/asm-disk2 # ASM disk device path 2 + + # SSH key secret used for remote access and automation + sshKeySecret: + name: ssh-key-secret # Kubernetes secret name + privKeySecretName: ssh-privkey # Private key inside secret + pubKeySecretName: ssh-pubkey # Public key inside secret + + # Secret containing DB user credentials + dbSecret: + name: db-user-pass-pkutl # Secret name + keyFileName: key.pem # Key file name inside secret + pwdFileName: pwdfile.enc # Password file name inside secret + + # Image for Oracle Restart container + image: dbocir/oracle/database-orestart:19.3.0-slim # <-- Replace with your image registry + + # Policy to pull image + # Use the value as "Always" if you want use the latest version of the image from the container registry. + # Use the value as "IfNotPresent" it the image is not already present locally. + imagePullPolicy: IfNotPresent + + # Optional service account for security context permissions + # serviceAccountName: oraclerestart # <-- Required for OpenShift; optional for vanilla Kubernetes + + # Service-specific database name and related details + serviceDetails: + name: soepdb # Name of the Oracle service (pluggable DB) + + # Resource requests and limits for memory and CPU + resources: + requests: + memory: "16Gi" # Minimum memory required + cpu: "2" # Minimum CPU required + limits: + memory: "16Gi" # Max memory allowed + cpu: "2" # Max CPU allowed + + # Kernel-level system control parameters for Oracle + securityContext: + sysctls: + - name: kernel.shmall + value: "2097152" + - name: kernel.sem + value: "250 32000 100 128" + - name: kernel.shmmax + value: "8589934592" + - name: kernel.shmmni + value: "4096" + + # Configuration parameters for Oracle Grid and Database homes + configParams: + gridHome: "/u01/app/19c/grid" # Oracle Grid home path + gridBase: "/u01/app/grid" # Grid base directory + dbHome: "/u01/app/oracle/product/19c/dbhome_1" # Oracle DB home path + dbBase: "/u01/app/oracle" # Oracle base directory + crsAsmDeviceList: /dev/asm-disk1,/dev/asm-disk2 # Comma-separated list of ASM device files + inventory: "/u01/app/oraInventory" # Inventory location + gridSwZipFile: "grid_home.zip" # Grid infrastructure software ZIP + dbSwZipFile: "db_home.zip" # Database software ZIP + sgaSize: "3G" # Size of the System Global Area + pgaSize: "1G" # Size of the Program Global Area + processes: 2000 # Oracle process limit + cpuCount: 4 # Number of CPUs to allocate + dbName: "PORCLCDB" # Oracle database name + hostSwStageLocation: /scratch/software/stage # Host location where Oracle software ZIPs are staged, same location will be mounted inside the Oracle Restart Pod + diff --git a/docs/oraclerestart/provisioning/orestart_db_rupatch_oneoffs_object.txt b/docs/oraclerestart/provisioning/orestart_db_rupatch_oneoffs_object.txt new file mode 100644 index 00000000..b08bb817 --- /dev/null +++ b/docs/oraclerestart/provisioning/orestart_db_rupatch_oneoffs_object.txt @@ -0,0 +1,197 @@ +$ kubectl describe oraclerestarts.database.oracle.com/oraclerestart-sample -n orestart +Name: oraclerestart-sample +Namespace: orestart +Labels: +Annotations: OracleRestarts.database.oracle.com/old-spec: + {"instDetails":{"name":"dbmc1","swLocStorageSizeInGb":300,"workerNode":["10.0.10.58"],"envVars":[{"name":"IGNORE_CRS_PREREQS","value":"tru... +API Version: database.oracle.com/v4 +Kind: OracleRestart +Metadata: + Creation Timestamp: 2025-10-09T05:53:39Z + Finalizers: + database.oracle.com/oraclerestartfinalizer + Generation: 1 + Resource Version: 246360830 + UID: d37b51c4-58ae-42d9-bec4-983ce94ee540 +Spec: + Asm Storage Details: + Disks By Size: + Disk Names: + /dev/asm-disk1 + /dev/asm-disk2 + Storage Size In Gb: 50 + Config Params: + Cpu Count: 4 + Crs Asm Device List: /dev/asm-disk1,/dev/asm-disk2 + Crs Asm Disk Dg: DATA + Crs Asm Disk Dg Redundancy: external + Db Base: /u01/app/oracle + Db Char Set: AL32UTF8 + Db Data File Dest Dg: DATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db One Off Ids: 38336965,34436514 + Db Recovery File Dest: DATA + Db Response File: + Db Sw Zip File: db_home.zip + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid One Off Ids: 38336965 + Grid Response File: + Grid Sw Zip File: grid_home.zip + Host Sw Stage Location: /stage/software/19c/19.3.0 + Inventory: /u01/app/oraInventory + O Patch Location: /stage/software/19c + O Patch Sw Zip File: p6880880_190000_Linux-x86-64.zip + One Off Location: /stage/software/19c/oneoff + Pdb Name: TESTPDB + Pga Size: 1G + Processes: 2000 + Ru Patch Location: /stage/software/19c/19.28/37957391 + Sga Size: 3G + Sw Mount Location: /u01 + Sw Stage Pvc: pv-stage-vol-claim + Sw Stage Pvc Mount Location: /stage + Crs Dg Storage Class: oci-bv + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Enable Ons: enable + Image: localhost/oracle/database-orestart:19.3.0-slim + Image Pull Policy: IfNotPresent + Inst Details: + Env Vars: + Name: IGNORE_CRS_PREREQS + Value: true + Name: IGNORE_DB_PREREQS + Value: true + Name: dbmc1 + Sw Loc Storage Size In Gb: 300 + Worker Node: + 10.0.10.58 + Lb Service: + Node Port Svc: + Name: dbmc1-service-nodeport + Port Mappings: + Node Port: 30007 + Port: 1521 + Protocol: TCP + Target Port: 1521 + Node Port: 30200 + Port: 6200 + Protocol: TCP + Target Port: 6200 + Svc Type: nodeport + Resources: + Limits: + Cpu: 2 + Memory: 16Gi + Requests: + Cpu: 2 + Memory: 16Gi + Security Context: + Sysctls: + Name: kernel.shmall + Value: 2097152 + Name: kernel.sem + Value: 250 32000 100 128 + Name: kernel.shmmax + Value: 8589934592 + Name: kernel.shmmni + Value: 4096 + Service Details: + Name: soepdb + Ssh Key Secret: + Key Mount Location: /mnt/.ssh + Name: ssh-key-secret + Priv Key Secret Name: ssh-privkey + Pub Key Secret Name: ssh-pubkey + Sw Dg Storage Class: oci-bv +Status: + Oracle Restart Nodes: + Name: dbmc1-0 + Node Details: + Instance State: OPEN + Pod State: AVAILABLE + Cluster State: HEALTHY + Mounted Devices: + + /dev/asm-disk1 + /dev/asm-disk2 + State: AVAILABLE + Asm Details: + Diskgroup: + Disks: + /dev/asm-disk1,/dev/asm-disk2 + Name: DATA + Redundancy: EXTERN + Conditions: + Last Transition Time: 2025-10-09T08:04:31Z + Message: reconcile has been queued + Observed Generation: 1 + Reason: LastReconcileCycleQueued + Status: True + Type: ReconcileQueued + Last Transition Time: 2025-10-09T13:21:11Z + Message: reconcile completed successfully + Observed Generation: 1 + Reason: LastReconcileCycleCompleted + Status: True + Type: ReconcileComplete + Config Params: + Crs Asm Device List: /dev/asm-disk1,/dev/asm-disk2 + Crs Asm Disk Dg: DATA + Crs Asm Disk Dg Redundancy: external + Db Base: /u01/app/oracle + Db Data File Dest Dg: DATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: DATA + Db Response File: + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Inventory: /u01/app/oraInventory + Connect String: dbmc1-0:1521/PORCLCDB + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Db State: OPEN + External Connect String: 129.146.0.149:30007/soepdb + Pdb Connect String: dbmc1-0:1521/ORCLPDB + Release Update: 19.28.0.0.0 + Role: PRIMARY + Service Details: + Name: soepdb + Svc State: service soepdb is running + State: AVAILABLE +Events: + + + + +-- Patches Details for GI HOME and RDBMS HOME: +[grid@dbmc1-0 ~]$ $ORACLE_HOME/OPatch/opatch lspatches +38336965;OCW Interim patch for 38336965 +38124772;TOMCAT RELEASE UPDATE 19.0.0.0.0 (38124772) +37962938;ACFS RELEASE UPDATE 19.28.0.0.0 (37962938) +37960098;Database Release Update : 19.28.0.0.250715 (37960098) +36758186;DBWLM RELEASE UPDATE 19.0.0.0.0 (36758186) + +OPatch succeeded. + + + + +[oracle@dbmc1-0 ~]$ $ORACLE_HOME/OPatch/opatch lspatches +34436514;DBCA REPORTS INCORRECT MEMORY IN PODMAN CONTAINERS +38336965;OCW Interim patch for 38336965 +37960098;Database Release Update : 19.28.0.0.250715 (37960098) + +OPatch succeeded. diff --git a/docs/oraclerestart/provisioning/orestart_loadbalancer_object.txt b/docs/oraclerestart/provisioning/orestart_loadbalancer_object.txt new file mode 100644 index 00000000..b018eaf0 --- /dev/null +++ b/docs/oraclerestart/provisioning/orestart_loadbalancer_object.txt @@ -0,0 +1,169 @@ +$ kubectl describe oraclerestarts.database.oracle.com/oraclerestart-sample -n orestart +Name: oraclerestart-sample +Namespace: orestart +Labels: +Annotations: OracleRestarts.database.oracle.com/old-spec: + {"instDetails":{"name":"dbmc1","hostSwLocation":"/scratch/orestart/","workerNode":["10.0.10.58"],"envVars":[{"name":"IGNORE_CRS_PREREQS","... +API Version: database.oracle.com/v4 +Kind: OracleRestart +Metadata: + Creation Timestamp: 2025-09-02T19:48:48Z + Finalizers: + database.oracle.com/oraclerestartfinalizer + Generation: 1 + Resource Version: 230654736 + UID: 38d28453-0906-4e31-b1cf-b0c2980293fa +Spec: + Asm Storage Details: + Disks By Size: + Disk Names: + /dev/disk/by-partlabel/asm-disk1 + /dev/disk/by-partlabel/asm-disk2 + Storage Size In Gb: 50 + Config Params: + Cpu Count: 4 + Crs Asm Device List: /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 + Crs Asm Disk Dg: +DATA + Crs Asm Disk Dg Redundancy: external + Db Base: /u01/app/oracle + Db Char Set: AL32UTF8 + Db Data File Dest Dg: +DATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: +DATA + Db Response File: + Db Sw Zip File: db_home.zip + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Grid Sw Zip File: grid_home.zip + Host Sw Stage Location: /scratch/software/stage + Inventory: /u01/app/oraInventory + Pga Size: 1G + Processes: 2000 + Sga Size: 3G + Sw Mount Location: /u01 + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Image: localhost/oracle/database-orestart:19.3.0-slim + Image Pull Policy: IfNotPresent + Inst Details: + Env Vars: + Name: IGNORE_CRS_PREREQS + Value: true + Name: IGNORE_DB_PREREQS + Value: true + Host Sw Location: /scratch/orestart/ + Name: dbmc1 + Worker Node: + 10.0.10.58 + Lb Service: + Port Mappings: + Port: 1521 + Protocol: TCP + Target Port: 1521 + Port: 6200 + Protocol: TCP + Target Port: 6200 + Svc Type: lbservice + Resources: + Limits: + Cpu: 2 + Memory: 16Gi + Requests: + Cpu: 2 + Memory: 16Gi + Security Context: + Sysctls: + Name: kernel.shmall + Value: 2097152 + Name: kernel.sem + Value: 250 32000 100 128 + Name: kernel.shmmax + Value: 8589934592 + Name: kernel.shmmni + Value: 4096 + Service Details: + Name: soepdb + Ssh Key Secret: + Key Mount Location: /mnt/.ssh + Name: ssh-key-secret + Priv Key Secret Name: ssh-privkey + Pub Key Secret Name: ssh-pubkey +Status: + Oracle Restart Nodes: + Name: dbmc1-0 + Node Details: + Instance State: OPEN + Pod State: AVAILABLE + Cluster State: HEALTHY + Mounted Devices: + /dev/disk/by-partlabel/asm-disk1 + /dev/disk/by-partlabel/asm-disk2 + State: AVAILABLE + Asm Details: + Diskgroup: + Disks: + /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 + Name: DATA + Redundancy: EXTERN + Conditions: + Last Transition Time: 2025-09-02T20:06:26Z + Message: reconcile has been queued + Observed Generation: 1 + Reason: LastReconcileCycleQueued + Status: True + Type: ReconcileQueued + Last Transition Time: 2025-09-02T21:13:28Z + Message: reconcile completed successfully + Observed Generation: 1 + Reason: LastReconcileCycleCompleted + Status: True + Type: ReconcileComplete + Config Params: + Crs Asm Device List: /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 + Crs Asm Disk Dg: +DATA + Crs Asm Disk Dg Redundancy: external + Db Base: /u01/app/oracle + Db Data File Dest Dg: +DATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: +DATA + Db Response File: + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Inventory: /u01/app/oraInventory + Connect String: dbmc1-0:1521/PORCLCDB + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Db State: OPEN + Pdb Connect String: dbmc1-0:1521/ORCLPDB + Release Update: 19.28.0.0.0 + Role: PRIMARY + Service Details: + Name: soepdb + Svc State: service soepdb is running + State: AVAILABLE +Events: + + + +$ kubectl get all -n orestart +NAME READY STATUS RESTARTS AGE +pod/dbmc1-0 1/1 Running 0 85m + +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +service/dbmc1 LoadBalancer 10.96.60.91 XXX.XXX.XXX.XX 1521:32083/TCP,6200:30857/TCP 85m +service/dbmc1-0 ClusterIP None 85m + +NAME READY AGE +statefulset.apps/dbmc1 1/1 85m \ No newline at end of file diff --git a/docs/oraclerestart/provisioning/orestart_nodeport_object.txt b/docs/oraclerestart/provisioning/orestart_nodeport_object.txt new file mode 100644 index 00000000..ef1385ac --- /dev/null +++ b/docs/oraclerestart/provisioning/orestart_nodeport_object.txt @@ -0,0 +1,153 @@ +$ kubectl describe oraclerestarts.database.oracle.com/oraclerestart-sample -n orestart +Name: oraclerestart-sample +Namespace: orestart +Labels: +Annotations: OracleRestarts.database.oracle.com/old-spec: + {"instDetails":{"name":"dbmc1","hostSwLocation":"/scratch/orestart/","workerNode":["10.0.10.58"],"nodePortSvc":[{"name":"dbmc1-service-nod... +API Version: database.oracle.com/v4 +Kind: OracleRestart +Metadata: + Creation Timestamp: 2025-08-20T01:53:13Z + Finalizers: + database.oracle.com/oraclerestartfinalizer + Generation: 1 + Resource Version: 224754865 + UID: f9404b92-c1b2-400d-8fff-9bda472a7d54 +Spec: + Asm Storage Details: + Disks By Size: + Disk Names: + /dev/disk/by-partlabel/asm-disk1 + /dev/disk/by-partlabel/asm-disk2 + Storage Size In Gb: 50 + Config Params: + Cpu Count: 4 + Crs Asm Device List: /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 + Crs Asm Disk Dg: +DATA + Crs Asm Disk Dg Redundancy: external + Db Base: /u01/app/oracle + Db Char Set: AL32UTF8 + Db Data File Dest Dg: +DATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: +DATA + Db Response File: + Db Sw Zip File: db_home.zip + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Grid Sw Zip File: grid_home.zip + Host Sw Stage Location: /scratch/software/stage + Inventory: /u01/app/oraInventory + Pga Size: 1G + Processes: 2000 + Sga Size: 3G + Sw Mount Location: /u01 + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Image: localhost/oracle/database-orestart:19.3.0-slim + Image Pull Policy: Always + Inst Details: + Host Sw Location: /scratch/orestart/ + Name: dbmc1 + Node Port Svc: + Name: dbmc1-service-nodeport + Svc Type: nodeport + Port Mappings: + Node Port: 30007 + Port: 1521 + Protocol: TCP + Target Port: 1521 + Worker Node: + 10.0.10.58 + Resources: + Limits: + Cpu: 2 + Memory: 16Gi + Requests: + Cpu: 2 + Memory: 16Gi + Security Context: + Sysctls: + Name: kernel.shmall + Value: 2097152 + Name: kernel.sem + Value: 250 32000 100 128 + Name: kernel.shmmax + Value: 8589934592 + Name: kernel.shmmni + Value: 4096 + Service Details: + Name: soepdb + Ssh Key Secret: + Key Mount Location: /mnt/.ssh + Name: ssh-key-secret + Priv Key Secret Name: ssh-privkey + Pub Key Secret Name: ssh-pubkey +Status: + Oracle Restart Nodes: + Name: dbmc1-0 + Node Details: + Instance State: OPEN + Pod State: AVAILABLE + Cluster State: HEALTHY + Mounted Devices: + /dev/disk/by-partlabel/asm-disk1 + /dev/disk/by-partlabel/asm-disk2 + State: AVAILABLE + Asm Details: + Diskgroup: + Disks: + /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 + Name: DATA + Redundancy: EXTERN + Conditions: + Last Transition Time: 2025-08-20T02:04:46Z + Message: oracle restart database is in a restricted state: PROVISIONING + Observed Generation: 1 + Reason: LastReconcileCycleQueued + Status: True + Type: ReconcileQueued + Last Transition Time: 2025-08-20T04:22:18Z + Message: no reconcile errors + Observed Generation: 1 + Reason: LastReconcileCycleCompleted + Status: True + Type: ReconcileComplete + Config Params: + Crs Asm Device List: /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 + Crs Asm Disk Dg: +DATA + Crs Asm Disk Dg Redundancy: external + Db Base: /u01/app/oracle + Db Data File Dest Dg: +DATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: +DATA + Db Response File: + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Inventory: /u01/app/oraInventory + Connect String: dbmc1-0:1521/PORCLCDB + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Db State: OPEN + External Connect String: 129.146.0.149:30007/soepdb + Inst Details: + Name: + Pdb Connect String: dbmc1-0:1521/ORCLPDB + Release Update: 19.28.0.0.0 + Role: PRIMARY + Service Details: + Name: soepdb + Svc State: service soepdb is running + State: AVAILABLE +Events: \ No newline at end of file diff --git a/docs/oraclerestart/provisioning/orestart_object.txt b/docs/oraclerestart/provisioning/orestart_object.txt new file mode 100644 index 00000000..41fb5c50 --- /dev/null +++ b/docs/oraclerestart/provisioning/orestart_object.txt @@ -0,0 +1,147 @@ +$ kubectl describe oraclerestarts.database.oracle.com/oraclerestart-sample -n orestart +Name: oraclerestart-sample +Namespace: orestart +Labels: +Annotations: OracleRestarts.database.oracle.com/old-spec: + {"instDetails":{"name":"dbmc1","hostSwLocation":"/scratch/orestart/","workerNode":["10.0.10.58"],"envVars":[{"name":"IGNORE_CRS_PREREQS","... +API Version: database.oracle.com/v4 +Kind: OracleRestart +Metadata: + Creation Timestamp: 2025-08-27T03:31:52Z + Finalizers: + database.oracle.com/oraclerestartfinalizer + Generation: 1 + Resource Version: 227759597 + UID: 41235735-cadc-49d3-b198-56e6bb4a1d9f +Spec: + Asm Storage Details: + Disks By Size: + Disk Names: + /dev/disk/by-partlabel/asm-disk1 + /dev/disk/by-partlabel/asm-disk2 + Storage Size In Gb: 50 + Config Params: + Cpu Count: 4 + Crs Asm Device List: /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 + Crs Asm Disk Dg: +DATA + Crs Asm Disk Dg Redundancy: external + Db Base: /u01/app/oracle + Db Char Set: AL32UTF8 + Db Data File Dest Dg: +DATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: +DATA + Db Response File: + Db Sw Zip File: db_home.zip + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Grid Sw Zip File: grid_home.zip + Host Sw Stage Location: /scratch/software/stage + Inventory: /u01/app/oraInventory + Pga Size: 1G + Processes: 2000 + Sga Size: 3G + Sw Mount Location: /u01 + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Image: localhost/oracle/database-orestart:19.3.0-slim + Image Pull Policy: IfNotPresent + Inst Details: + Env Vars: + Name: IGNORE_CRS_PREREQS + Value: true + Name: IGNORE_DB_PREREQS + Value: true + Host Sw Location: /scratch/orestart/ + Name: dbmc1 + Worker Node: + 10.0.10.58 + Resources: + Limits: + Cpu: 2 + Memory: 16Gi + Requests: + Cpu: 2 + Memory: 16Gi + Security Context: + Sysctls: + Name: kernel.shmall + Value: 2097152 + Name: kernel.sem + Value: 250 32000 100 128 + Name: kernel.shmmax + Value: 8589934592 + Name: kernel.shmmni + Value: 4096 + Service Details: + Name: soepdb + Ssh Key Secret: + Key Mount Location: /mnt/.ssh + Name: ssh-key-secret + Priv Key Secret Name: ssh-privkey + Pub Key Secret Name: ssh-pubkey +Status: + Oracle Restart Nodes: + Name: dbmc1-0 + Node Details: + Instance State: OPEN + Pod State: AVAILABLE + Cluster State: HEALTHY + Mounted Devices: + /dev/disk/by-partlabel/asm-disk1 + /dev/disk/by-partlabel/asm-disk2 + State: AVAILABLE + Asm Details: + Diskgroup: + Disks: + /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 + Name: DATA + Redundancy: EXTERN + Conditions: + Last Transition Time: 2025-08-27T03:43:52Z + Message: reconcile has been queued + Observed Generation: 1 + Reason: LastReconcileCycleQueued + Status: True + Type: ReconcileQueued + Last Transition Time: 2025-08-27T03:58:18Z + Message: reconcile completed successfully + Observed Generation: 1 + Reason: LastReconcileCycleCompleted + Status: True + Type: ReconcileComplete + Config Params: + Crs Asm Device List: /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 + Crs Asm Disk Dg: +DATA + Crs Asm Disk Dg Redundancy: external + Db Base: /u01/app/oracle + Db Data File Dest Dg: +DATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: +DATA + Db Response File: + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Inventory: /u01/app/oraInventory + Connect String: dbmc1-0:1521/PORCLCDB + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Db State: OPEN + Pdb Connect String: dbmc1-0:1521/ORCLPDB + Release Update: 19.28.0.0.0 + Role: PRIMARY + Service Details: + Name: soepdb + Svc State: service soepdb is running + State: AVAILABLE +Events: \ No newline at end of file diff --git a/docs/oraclerestart/provisioning/orestart_prov_asm_disk_addition.yaml b/docs/oraclerestart/provisioning/orestart_prov_asm_disk_addition.yaml new file mode 100644 index 00000000..8f36a496 --- /dev/null +++ b/docs/oraclerestart/provisioning/orestart_prov_asm_disk_addition.yaml @@ -0,0 +1,120 @@ +# API version of the OracleRestart Custom Resource +apiVersion: database.oracle.com/v4 + +# Resource type being created +kind: OracleRestart + +# Metadata section to define the resource's name and namespace +metadata: + name: oraclerestart-sample # Name of the OracleRestart instance + namespace: orestart # Kubernetes namespace to deploy into + +spec: + # Instance details for Oracle Restart + instDetails: + name: dbmc1 # Logical name of the OracleRestart instance + hostSwLocation: /scratch/orestart/ # Host directory for Oracle GI HOME and Oracle RDBMS HOME + + # Worker nodes where the instance should be scheduled + workerNode: + - 10.0.10.58 # IP address of the target node for deployment + + # Environment variables in case you want to want to ignore prerequisite checks during the GI/RDBSMS Install + envVars: + - name: IGNORE_CRS_PREREQS # If set to true, it will use flags -ignorePreReq and -ignorePrereqFailure during the CRS Installation. Do not set this parameter in the .yaml file if you do not want to use the parameters -ignorePreReq and -ignorePrereqFailure during the CRS Installation. + value: "true" + - name: IGNORE_DB_PREREQS # If set to true, it will use flags -ignorePrereq and -ignorePrereqFailure during the DB Software Installation. Do not set this parameter in the .yaml file if you do not want to use the parameters -ignorePreReq and -ignorePrereqFailure during the DB Software Installation. + value: "true" + + # NodePort service configuration for exposing DB port externally + nodePortSvc: + name: dbmc1-service-nodeport # Name of the Kubernetes Service + svcType: nodeport # Service type (NodePort to expose externally) + + # Mapping between container and host ports + portMappings: + - port: 1521 # Port inside container (Oracle Listener port) + targetPort: 1521 # Target port for the service + protocol: TCP # Protocol type (TCP for Oracle) + nodePort: 30007 # Host port exposed on worker node (must be in NodePort range) + - port: 6200 # Port inside container (for ONS) + targetPort: 6200 # Target port for the service + protocol: TCP # Protocol type (TCP for Oracle) + nodePort: 30200 # Host port exposed on worker node (must be in NodePort range) + enableOns: "enable" # If you want to have ONS notification configured on the specified port. Default value is "enable" + + # ASM (Automatic Storage Management) storage configuration + asmStorageDetails: + disksBySize: + - storageSizeInGb: 50 # Each disk in this group has 50Gi of storage + diskNames: + - /dev/disk/by-partlabel/asm-disk1 # ASM disk device path 1 + - /dev/disk/by-partlabel/asm-disk2 # ASM disk device path 2 + - /dev/disk/by-partlabel/asm-disk3 # ASM disk device path 3 + - /dev/disk/by-partlabel/asm-disk4 # ASM disk device path 4 + autoUpdate: "true" + + # SSH key secret used for remote access and automation + sshKeySecret: + name: ssh-key-secret # Kubernetes secret name + privKeySecretName: ssh-privkey # Private key inside secret + pubKeySecretName: ssh-pubkey # Public key inside secret + + # Secret containing DB user credentials + dbSecret: + name: db-user-pass-pkutl # Secret name + keyFileName: key.pem # Key file name inside secret + pwdFileName: pwdfile.enc # Password file name inside secret + + # Image for Oracle Restart container + image: dbocir/oracle/database-orestart:19.3.0-slim # <-- Replace with your image registry + + # Policy to pull image + # Use the value as "Always" if you want use the latest version of the image from the container registry. + # Use the value as "IfNotPresent" it the image is not already present locally. + imagePullPolicy: IfNotPresent + + # Optional service account for security context permissions + # serviceAccountName: oraclerestart # <-- Required for OpenShift; optional for vanilla Kubernetes + + # Service-specific database name and related details + serviceDetails: + name: soepdb # Name of the Oracle service (pluggable DB) + + # Resource requests and limits for memory and CPU + resources: + requests: + memory: "16Gi" # Minimum memory required + cpu: "2" # Minimum CPU required + limits: + memory: "16Gi" # Max memory allowed + cpu: "2" # Max CPU allowed + + # Kernel-level system control parameters for Oracle + securityContext: + sysctls: + - name: kernel.shmall + value: "2097152" + - name: kernel.sem + value: "250 32000 100 128" + - name: kernel.shmmax + value: "8589934592" + - name: kernel.shmmni + value: "4096" + + # Configuration parameters for Oracle Grid and Database homes + configParams: + gridHome: "/u01/app/19c/grid" # Oracle Grid home path + gridBase: "/u01/app/grid" # Grid base directory + dbHome: "/u01/app/oracle/product/19c/dbhome_1" # Oracle DB home path + dbBase: "/u01/app/oracle" # Oracle base directory + crsAsmDeviceList: /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2,/dev/disk/by-partlabel/asm-disk3,/dev/disk/by-partlabel/asm-disk4 # Comma-separated list of ASM device files + inventory: "/u01/app/oraInventory" # Inventory location + gridSwZipFile: "grid_home.zip" # Grid infrastructure software ZIP + dbSwZipFile: "db_home.zip" # Database software ZIP + sgaSize: "3G" # Size of the System Global Area + pgaSize: "1G" # Size of the Program Global Area + processes: 2000 # Oracle process limit + cpuCount: 4 # Number of CPUs to allocate + dbName: "PORCLCDB" # Oracle database name + hostSwStageLocation: /scratch/software/stage # Host location where Oracle software ZIPs are staged, same location will be mounted inside the Oracle Restart Pod diff --git a/docs/oraclerestart/provisioning/orestart_prov_asm_disk_addition_autoupdate_false.yaml b/docs/oraclerestart/provisioning/orestart_prov_asm_disk_addition_autoupdate_false.yaml new file mode 100644 index 00000000..532cdc00 --- /dev/null +++ b/docs/oraclerestart/provisioning/orestart_prov_asm_disk_addition_autoupdate_false.yaml @@ -0,0 +1,120 @@ +# API version of the OracleRestart Custom Resource +apiVersion: database.oracle.com/v4 + +# Resource type being created +kind: OracleRestart + +# Metadata section to define the resource's name and namespace +metadata: + name: oraclerestart-sample # Name of the OracleRestart instance + namespace: orestart # Kubernetes namespace to deploy into + +spec: + # Instance details for Oracle Restart + instDetails: + name: dbmc1 # Logical name of the OracleRestart instance + hostSwLocation: /scratch/orestart/ # Host directory for Oracle GI HOME and Oracle RDBMS HOME + + # Worker nodes where the instance should be scheduled + workerNode: + - 10.0.10.58 # IP address of the target node for deployment + + # Environment variables in case you want to want to ignore prerequisite checks during the GI/RDBSMS Install + envVars: + - name: IGNORE_CRS_PREREQS # If set to true, it will use flags -ignorePreReq and -ignorePrereqFailure during the CRS Installation. Do not set this parameter in the .yaml file if you do not want to use the parameters -ignorePreReq and -ignorePrereqFailure during the CRS Installation. + value: "true" + - name: IGNORE_DB_PREREQS # If set to true, it will use flags -ignorePrereq and -ignorePrereqFailure during the DB Software Installation. Do not set this parameter in the .yaml file if you do not want to use the parameters -ignorePreReq and -ignorePrereqFailure during the DB Software Installation. + value: "true" + + # NodePort service configuration for exposing DB port externally + nodePortSvc: + name: dbmc1-service-nodeport # Name of the Kubernetes Service + svcType: nodeport # Service type (NodePort to expose externally) + + # Mapping between container and host ports + portMappings: + - port: 1521 # Port inside container (Oracle Listener port) + targetPort: 1521 # Target port for the service + protocol: TCP # Protocol type (TCP for Oracle) + nodePort: 30007 # Host port exposed on worker node (must be in NodePort range) + - port: 6200 # Port inside container (for ONS) + targetPort: 6200 # Target port for the service + protocol: TCP # Protocol type (TCP for Oracle) + nodePort: 30200 # Host port exposed on worker node (must be in NodePort range) + enableOns: "enable" # If you want to have ONS notification configured on the specified port. Default value is "enable" + + # ASM (Automatic Storage Management) storage configuration + asmStorageDetails: + disksBySize: + - storageSizeInGb: 50 # Each disk in this group has 50Gi of storage + diskNames: + - /dev/disk/by-partlabel/asm-disk1 # ASM disk device path 1 + - /dev/disk/by-partlabel/asm-disk2 # ASM disk device path 2 + - /dev/disk/by-partlabel/asm-disk3 # ASM disk device path 3 + - /dev/disk/by-partlabel/asm-disk4 # ASM disk device path 4 + autoUpdate: "false" + + # SSH key secret used for remote access and automation + sshKeySecret: + name: ssh-key-secret # Kubernetes secret name + privKeySecretName: ssh-privkey # Private key inside secret + pubKeySecretName: ssh-pubkey # Public key inside secret + + # Secret containing DB user credentials + dbSecret: + name: db-user-pass-pkutl # Secret name + keyFileName: key.pem # Key file name inside secret + pwdFileName: pwdfile.enc # Password file name inside secret + + # Image for Oracle Restart container + image: dbocir/oracle/database-orestart:19.3.0-slim # <-- Replace with your image registry + + # Policy to pull image + # Use the value as "Always" if you want use the latest version of the image from the container registry. + # Use the value as "IfNotPresent" it the image is not already present locally. + imagePullPolicy: IfNotPresent + + # Optional service account for security context permissions + # serviceAccountName: oraclerestart # <-- Required for OpenShift; optional for vanilla Kubernetes + + # Service-specific database name and related details + serviceDetails: + name: soepdb # Name of the Oracle service (pluggable DB) + + # Resource requests and limits for memory and CPU + resources: + requests: + memory: "16Gi" # Minimum memory required + cpu: "2" # Minimum CPU required + limits: + memory: "16Gi" # Max memory allowed + cpu: "2" # Max CPU allowed + + # Kernel-level system control parameters for Oracle + securityContext: + sysctls: + - name: kernel.shmall + value: "2097152" + - name: kernel.sem + value: "250 32000 100 128" + - name: kernel.shmmax + value: "8589934592" + - name: kernel.shmmni + value: "4096" + + # Configuration parameters for Oracle Grid and Database homes + configParams: + gridHome: "/u01/app/19c/grid" # Oracle Grid home path + gridBase: "/u01/app/grid" # Grid base directory + dbHome: "/u01/app/oracle/product/19c/dbhome_1" # Oracle DB home path + dbBase: "/u01/app/oracle" # Oracle base directory + crsAsmDeviceList: /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2,/dev/disk/by-partlabel/asm-disk3,/dev/disk/by-partlabel/asm-disk4 # Comma-separated list of ASM device files + inventory: "/u01/app/oraInventory" # Inventory location + gridSwZipFile: "grid_home.zip" # Grid infrastructure software ZIP + dbSwZipFile: "db_home.zip" # Database software ZIP + sgaSize: "3G" # Size of the System Global Area + pgaSize: "1G" # Size of the Program Global Area + processes: 2000 # Oracle process limit + cpuCount: 4 # Number of CPUs to allocate + dbName: "PORCLCDB" # Oracle database name + hostSwStageLocation: /scratch/software/stage # Host location where Oracle software ZIPs are staged, same location will be mounted inside the Oracle Restart Pod diff --git a/docs/oraclerestart/provisioning/orestart_prov_asm_disk_deletion.yaml b/docs/oraclerestart/provisioning/orestart_prov_asm_disk_deletion.yaml new file mode 100644 index 00000000..a91d967b --- /dev/null +++ b/docs/oraclerestart/provisioning/orestart_prov_asm_disk_deletion.yaml @@ -0,0 +1,116 @@ +# API version of the OracleRestart Custom Resource +apiVersion: database.oracle.com/v4 + +# Resource type being created +kind: OracleRestart + +# Metadata section to define the resource's name and namespace +metadata: + name: oraclerestart-sample # Name of the OracleRestart instance + namespace: orestart # Kubernetes namespace to deploy into + +spec: + # Instance details for Oracle Restart + instDetails: + name: dbmc1 # Logical name of the OracleRestart instance + hostSwLocation: /scratch/orestart/ # Host directory for Oracle GI HOME and Oracle RDBMS HOME + + # Worker nodes where the instance should be scheduled + workerNode: + - 10.0.10.58 # IP address of the target node for deployment + + # Environment variables in case you want to want to ignore prerequisite checks during the GI/RDBSMS Install + envVars: + - name: IGNORE_CRS_PREREQS # If set to true, it will use flags -ignorePreReq and -ignorePrereqFailure during the CRS Installation. Do not set this parameter in the .yaml file if you do not want to use the parameters -ignorePreReq and -ignorePrereqFailure during the CRS Installation. + value: "true" + - name: IGNORE_DB_PREREQS # If set to true, it will use flags -ignorePrereq and -ignorePrereqFailure during the DB Software Installation. Do not set this parameter in the .yaml file if you do not want to use the parameters -ignorePreReq and -ignorePrereqFailure during the DB Software Installation. + value: "true" + + # NodePort service configuration for exposing DB port externally + nodePortSvc: + name: dbmc1-service-nodeport # Name of the Kubernetes Service + svcType: nodeport # Service type (NodePort to expose externally) + + # Mapping between container and host ports + portMappings: + - port: 1521 # Port inside container (Oracle Listener port) + targetPort: 1521 # Target port for the service + protocol: TCP # Protocol type (TCP for Oracle) + nodePort: 30007 # Host port exposed on worker node (must be in NodePort range) + - port: 6200 # Port inside container (for ONS) + targetPort: 6200 # Target port for the service + protocol: TCP # Protocol type (TCP for Oracle) + nodePort: 30200 # Host port exposed on worker node (must be in NodePort range) + enableOns: "enable" # If you want to have ONS notification configured on the specified port. Default value is "enable" + + # ASM (Automatic Storage Management) storage configuration + asmStorageDetails: + disksBySize: + - storageSizeInGb: 50 # Each disk in this group has 50Gi of storage + diskNames: + - /dev/disk/by-partlabel/asm-disk1 # ASM disk device path 1 + + # SSH key secret used for remote access and automation + sshKeySecret: + name: ssh-key-secret # Kubernetes secret name + privKeySecretName: ssh-privkey # Private key inside secret + pubKeySecretName: ssh-pubkey # Public key inside secret + + # Secret containing DB user credentials + dbSecret: + name: db-user-pass-pkutl # Secret name + keyFileName: key.pem # Key file name inside secret + pwdFileName: pwdfile.enc # Password file name inside secret + + # Image for Oracle Restart container + image: dbocir/oracle/database-orestart:19.3.0-slim # <-- Replace with your image registry + + # Policy to pull image + # Use the value as "Always" if you want use the latest version of the image from the container registry. + # Use the value as "IfNotPresent" it the image is not already present locally. + imagePullPolicy: IfNotPresent + + # Optional service account for security context permissions + # serviceAccountName: oraclerestart # <-- Required for OpenShift; optional for vanilla Kubernetes + + # Service-specific database name and related details + serviceDetails: + name: soepdb # Name of the Oracle service (pluggable DB) + + # Resource requests and limits for memory and CPU + resources: + requests: + memory: "16Gi" # Minimum memory required + cpu: "2" # Minimum CPU required + limits: + memory: "16Gi" # Max memory allowed + cpu: "2" # Max CPU allowed + + # Kernel-level system control parameters for Oracle + securityContext: + sysctls: + - name: kernel.shmall + value: "2097152" + - name: kernel.sem + value: "250 32000 100 128" + - name: kernel.shmmax + value: "8589934592" + - name: kernel.shmmni + value: "4096" + + # Configuration parameters for Oracle Grid and Database homes + configParams: + gridHome: "/u01/app/19c/grid" # Oracle Grid home path + gridBase: "/u01/app/grid" # Grid base directory + dbHome: "/u01/app/oracle/product/19c/dbhome_1" # Oracle DB home path + dbBase: "/u01/app/oracle" # Oracle base directory + crsAsmDeviceList: /dev/disk/by-partlabel/asm-disk1 # Comma-separated list of ASM device files + inventory: "/u01/app/oraInventory" # Inventory location + gridSwZipFile: "grid_home.zip" # Grid infrastructure software ZIP + dbSwZipFile: "db_home.zip" # Database software ZIP + sgaSize: "3G" # Size of the System Global Area + pgaSize: "1G" # Size of the Program Global Area + processes: 2000 # Oracle process limit + cpuCount: 4 # Number of CPUs to allocate + dbName: "PORCLCDB" # Oracle database name + hostSwStageLocation: /scratch/software/stage # Host location where Oracle software ZIPs are staged, same location will be mounted inside the Oracle Restart Pod diff --git a/docs/oraclerestart/provisioning/orestart_rupatch_object.txt b/docs/oraclerestart/provisioning/orestart_rupatch_object.txt new file mode 100644 index 00000000..c2868d6b --- /dev/null +++ b/docs/oraclerestart/provisioning/orestart_rupatch_object.txt @@ -0,0 +1,176 @@ +$ kubectl describe oraclerestarts.database.oracle.com/oraclerestart-sample -n orestart +Name: oraclerestart-sample +Namespace: orestart +Labels: +Annotations: OracleRestarts.database.oracle.com/old-spec: + {"instDetails":{"name":"dbmc1","hostSwLocation":"/scratch/orestart/","workerNode":["10.0.10.58"],"nodePortSvc":[{"name":"dbmc1-service-nod... +API Version: database.oracle.com/v4 +Kind: OracleRestart +Metadata: + Creation Timestamp: 2025-08-20T05:11:47Z + Finalizers: + database.oracle.com/oraclerestartfinalizer + Generation: 1 + Resource Version: 225105545 + UID: 6da86e89-0c6b-4c4d-aa14-c04c72071388 +Spec: + Asm Storage Details: + Disks By Size: + Disk Names: + /dev/disk/by-partlabel/asm-disk1 + /dev/disk/by-partlabel/asm-disk2 + Storage Size In Gb: 50 + Config Params: + Cpu Count: 4 + Crs Asm Device List: /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 + Crs Asm Disk Dg: +DATA + Crs Asm Disk Dg Redundancy: external + Db Base: /u01/app/oracle + Db Char Set: AL32UTF8 + Db Data File Dest Dg: +DATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: +DATA + Db Response File: + Db Sw Zip File: db_home.zip + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Grid Sw Zip File: grid_home.zip + Host Sw Stage Location: /scratch/software/stage + Inventory: /u01/app/oraInventory + O Patch Location: /scratch/software/opatch + O Patch Sw Zip File: p6880880_190000_Linux-x86-64.zip + Pga Size: 1G + Processes: 2000 + Ru Patch Location: /scratch/software/ru_patch/37957391 + Sga Size: 3G + Sw Mount Location: /u01 + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Image: localhost/oracle/database-orestart:19.3.0-slim + Image Pull Policy: Always + Inst Details: + Host Sw Location: /scratch/orestart/ + Name: dbmc1 + Node Port Svc: + Name: dbmc1-service-nodeport + Svc Type: nodeport + Port Mappings: + Node Port: 30007 + Port: 1521 + Protocol: TCP + Target Port: 1521 + Worker Node: + 10.0.10.58 + Resources: + Limits: + Cpu: 2 + Memory: 16Gi + Requests: + Cpu: 2 + Memory: 16Gi + Security Context: + Sysctls: + Name: kernel.shmall + Value: 2097152 + Name: kernel.sem + Value: 250 32000 100 128 + Name: kernel.shmmax + Value: 8589934592 + Name: kernel.shmmni + Value: 4096 + Service Details: + Name: soepdb + Ssh Key Secret: + Key Mount Location: /mnt/.ssh + Name: ssh-key-secret + Priv Key Secret Name: ssh-privkey + Pub Key Secret Name: ssh-pubkey +Status: + Oracle Restart Nodes: + Name: dbmc1-0 + Node Details: + Instance State: OPEN + Pod State: AVAILABLE + Cluster State: GI_NOT_INSTALLED_OR_CONFIGURED + Mounted Devices: + /dev/disk/by-partlabel/asm-disk1 + /dev/disk/by-partlabel/asm-disk2 + State: AVAILABLE + Asm Details: + Diskgroup: + Disks: + /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 + Name: DATA + Redundancy: EXTERN + Conditions: + Last Transition Time: 2025-08-20T06:23:57Z + Message: oracle restart database is in a restricted state: PROVISIONING + Observed Generation: 1 + Reason: LastReconcileCycleQueued + Status: True + Type: ReconcileQueued + Last Transition Time: 2025-08-20T23:52:35Z + Message: no reconcile errors + Observed Generation: 1 + Reason: LastReconcileCycleCompleted + Status: True + Type: ReconcileComplete + Config Params: + Crs Asm Device List: /dev/disk/by-partlabel/asm-disk1,/dev/disk/by-partlabel/asm-disk2 + Crs Asm Disk Dg: +DATA + Crs Asm Disk Dg Redundancy: external + Db Base: /u01/app/oracle + Db Data File Dest Dg: +DATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: +DATA + Db Response File: + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Inventory: /u01/app/oraInventory + Connect String: dbmc1-0:1521/PORCLCDB + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Db State: OPEN + External Connect String: 129.146.0.149:30007/soepdb + Inst Details: + Name: + Pdb Connect String: dbmc1-0:1521/ORCLPDB + Release Update: 19.28.0.0.0 + Role: PRIMARY + Service Details: + Name: soepdb + Svc State: service soepdb is running + State: AVAILABLE +Events: + + + +# When the 19.28 RU Patch 37957391 is applied to both the GI HOME and RDBMS HOME, you can check the status of the patches applied once the deployment is completed: + +[grid@dbmc1-0 ~]$ $ORACLE_HOME/OPatch/opatch lspatches +37962946;OCW RELEASE UPDATE 19.28.0.0.0 (37962946) +37962938;ACFS RELEASE UPDATE 19.28.0.0.0 (37962938) +37960098;Database Release Update : 19.28.0.0.250715 (37960098) +37954209;TOMCAT RELEASE UPDATE 19.0.0.0.0 (37954209) +36758186;DBWLM RELEASE UPDATE 19.0.0.0.0 (36758186) + +OPatch succeeded. + + +[oracle@dbmc1-0 ~]$ $ORACLE_HOME/OPatch/opatch lspatches +37962946;OCW RELEASE UPDATE 19.28.0.0.0 (37962946) +37960098;Database Release Update : 19.28.0.0.250715 (37960098) + +OPatch succeeded. \ No newline at end of file diff --git a/docs/oraclerestart/provisioning/orestart_rupatch_pvc_object.txt b/docs/oraclerestart/provisioning/orestart_rupatch_pvc_object.txt new file mode 100644 index 00000000..31bfa302 --- /dev/null +++ b/docs/oraclerestart/provisioning/orestart_rupatch_pvc_object.txt @@ -0,0 +1,209 @@ +$ kubectl describe oraclerestarts.database.oracle.com/oraclerestart-sample -n orestart +Name: oraclerestart-sample +Namespace: orestart +Labels: +Annotations: OracleRestarts.database.oracle.com/old-spec: + {"instDetails":{"name":"dbmc1","swLocStorageSizeInGb":300,"workerNode":["10.0.10.58"],"envVars":[{"name":"IGNORE_CRS_PREREQS","value":"tru... +API Version: database.oracle.com/v4 +Kind: OracleRestart +Metadata: + Creation Timestamp: 2025-10-09T13:31:37Z + Finalizers: + database.oracle.com/oraclerestartfinalizer + Generation: 1 + Resource Version: 246434925 + UID: 0a283621-f64c-4d34-ad46-92e02376d7ad +Spec: + Asm Storage Details: + Disks By Size: + Disk Names: + /dev/asm-disk1 + /dev/asm-disk2 + Storage Size In Gb: 50 + Config Params: + Cpu Count: 4 + Crs Asm Device List: /dev/asm-disk1,/dev/asm-disk2 + Crs Asm Disk Dg: DATA + Crs Asm Disk Dg Redundancy: external + Db Base: /u01/app/oracle + Db Char Set: AL32UTF8 + Db Data File Dest Dg: DATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: DATA + Db Response File: + Db Sw Zip File: db_home.zip + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Grid Sw Zip File: grid_home.zip + Host Sw Stage Location: /stage/software/19c/19.3.0 + Inventory: /u01/app/oraInventory + O Patch Location: /stage/software/19c + O Patch Sw Zip File: p6880880_190000_Linux-x86-64.zip + Pdb Name: TESTPDB + Pga Size: 1G + Processes: 2000 + Ru Patch Location: /stage/software/19c/19.28/37957391 + Sga Size: 3G + Sw Mount Location: /u01 + Sw Stage Pvc: pv-stage-vol-claim + Sw Stage Pvc Mount Location: /stage + Crs Dg Storage Class: oci-bv + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Enable Ons: enable + Image: localhost/oracle/database-orestart:19.3.0-slim + Image Pull Policy: IfNotPresent + Inst Details: + Env Vars: + Name: IGNORE_CRS_PREREQS + Value: true + Name: IGNORE_DB_PREREQS + Value: true + Name: dbmc1 + Sw Loc Storage Size In Gb: 300 + Worker Node: + 10.0.10.58 + Lb Service: + Node Port Svc: + Name: dbmc1-service-nodeport + Port Mappings: + Node Port: 30007 + Port: 1521 + Protocol: TCP + Target Port: 1521 + Node Port: 30200 + Port: 6200 + Protocol: TCP + Target Port: 6200 + Svc Type: nodeport + Resources: + Limits: + Cpu: 2 + Memory: 16Gi + Requests: + Cpu: 2 + Memory: 16Gi + Security Context: + Sysctls: + Name: kernel.shmall + Value: 2097152 + Name: kernel.sem + Value: 250 32000 100 128 + Name: kernel.shmmax + Value: 8589934592 + Name: kernel.shmmni + Value: 4096 + Service Details: + Name: soepdb + Ssh Key Secret: + Key Mount Location: /mnt/.ssh + Name: ssh-key-secret + Priv Key Secret Name: ssh-privkey + Pub Key Secret Name: ssh-pubkey + Sw Dg Storage Class: oci-bv +Status: + Oracle Restart Nodes: + Name: dbmc1-0 + Node Details: + Instance State: OPEN + Pod State: AVAILABLE + Cluster State: HEALTHY + Mounted Devices: + /dev/asm-disk1 + /dev/asm-disk2 + State: AVAILABLE + Asm Details: + Diskgroup: + Disks: + /dev/asm-disk1,/dev/asm-disk2 + Name: DATA + Redundancy: EXTERN + Conditions: + Last Transition Time: 2025-10-09T15:39:34Z + Message: reconcile has been queued + Observed Generation: 1 + Reason: LastReconcileCycleQueued + Status: True + Type: ReconcileQueued + Last Transition Time: 2025-10-09T17:29:10Z + Message: reconcile completed successfully + Observed Generation: 1 + Reason: LastReconcileCycleCompleted + Status: True + Type: ReconcileComplete + Config Params: + Crs Asm Device List: /dev/asm-disk1,/dev/asm-disk2 + Crs Asm Disk Dg: DATA + Crs Asm Disk Dg Redundancy: external + Db Base: /u01/app/oracle + Db Data File Dest Dg: DATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: DATA + Db Response File: + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Inventory: /u01/app/oraInventory + Connect String: dbmc1-0:1521/PORCLCDB + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Db State: OPEN + External Connect String: 129.146.0.149:30007/soepdb + Pdb Connect String: dbmc1-0:1521/ORCLPDB + Release Update: 19.28.0.0.0 + Role: PRIMARY + Service Details: + Name: soepdb + Svc State: service soepdb is running + State: AVAILABLE +Events: + + +$ kubectl get pvc -n orestart +NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE +asm-pvc-crsdg-asm-disk1-oraclerestart-sample-dbmc1-0 Bound csi-dd26294f-caa3-4c1c-b5f4-a64145afe926 50Gi RWO oci-bv 3h57m +asm-pvc-crsdg-asm-disk2-oraclerestart-sample-dbmc1-0 Bound csi-972a9dc2-f49b-4475-8add-c185f4cfe05f 50Gi RWO oci-bv 3h57m +dbmc1-oradata-sw-vol-pvc-dbmc1-0 Bound csi-6722f64f-71a2-458d-857f-921aee569b43 300Gi RWO oci-bv 3h57m +pv-stage-vol-claim Bound pv-stage-vol1 500Gi ROX fss-dyn-storage 11h + + + +[root@dbmc1-0 rac-work-dir]# ls -lrt /stage/software/19c/ +total 71682 +-rwxrwxrwx. 1 2001 2001 72539776 Aug 19 01:21 p6880880_190000_Linux-x86-64.zip +drwxrwxrwx. 2 2001 2001 3 Aug 19 03:56 19.3.0 +drwxrwxrwx. 3 2001 2001 3 Sep 11 04:36 19.28 +[root@dbmc1-0 rac-work-dir]# ls -lrt /stage/software/19c/19.3.0/ +total 6774784 +-rwxrwxrwx. 1 2001 2001 3059705302 Aug 19 01:10 db_home.zip +-rwxrwxrwx. 1 2001 2001 2889184573 Aug 19 01:11 grid_home.zip +-rwxrwxrwx. 1 2001 2001 987216967 Aug 19 01:13 LINUX.X64_193000_client_home.zip +[root@dbmc1-0 rac-work-dir]# +[root@dbmc1-0 rac-work-dir]# ls -lrt /stage/software/19c/19.28/ +total 3847169 +drwxrwxrwx. 8 2001 2001 9 Jul 15 14:13 37957391 +-rwxrwxrwx. 1 2001 2001 2930311 Jul 15 20:49 PatchSearch.xml +-rwxrwxrwx. 1 2001 2001 3934314158 Sep 11 04:31 p37957391_190000_Linux-x86-64.zip +[root@dbmc1-0 rac-work-dir]# ls -lrt /stage/software/19c/19.28/37957391/ +total 171 +drwxrwxrwx. 5 2001 2001 4 Jul 15 14:13 37962946 +drwxrwxrwx. 5 2001 2001 4 Jul 15 14:14 37962938 +drwxrwxrwx. 4 2001 2001 3 Jul 15 14:14 38124772 +drwxrwxrwx. 5 2001 2001 5 Jul 15 14:14 37960098 +drwxrwxrwx. 4 2001 2001 3 Jul 15 14:14 36758186 +drwxrwxrwx. 2 2001 2001 13 Jul 15 14:19 automation +-rwxrwxrwx. 1 2001 2001 0 Jul 15 14:19 README.txt +-rwxrwxrwx. 1 2001 2001 152522 Jul 15 14:29 README.html +-rwxrwxrwx. 1 2001 2001 5824 Jul 15 14:47 bundle.xml +[root@dbmc1-0 rac-work-dir]# \ No newline at end of file diff --git a/docs/oraclerestart/provisioning/orestart_storage_class_object.txt b/docs/oraclerestart/provisioning/orestart_storage_class_object.txt new file mode 100644 index 00000000..27fdf146 --- /dev/null +++ b/docs/oraclerestart/provisioning/orestart_storage_class_object.txt @@ -0,0 +1,180 @@ +$ kubectl describe oraclerestarts.database.oracle.com/oraclerestart-sample -n orestart +Name: oraclerestart-sample +Namespace: orestart +Labels: +Annotations: OracleRestarts.database.oracle.com/old-spec: + {"instDetails":{"name":"dbmc1","swLocStorageSizeInGb":300,"workerNode":["10.0.10.58"],"envVars":[{"name":"IGNORE_CRS_PREREQS","value":"tru... +API Version: database.oracle.com/v4 +Kind: OracleRestart +Metadata: + Creation Timestamp: 2025-10-09T18:01:21Z + Finalizers: + database.oracle.com/oraclerestartfinalizer + Generation: 1 + Resource Version: 246453216 + UID: c42b7961-6cdb-416e-a17c-616cc0c08b1f +Spec: + Asm Storage Details: + Disks By Size: + Disk Names: + /dev/asm-disk1 + /dev/asm-disk2 + Storage Size In Gb: 50 + Config Params: + Cpu Count: 4 + Crs Asm Device List: /dev/asm-disk1,/dev/asm-disk2 + Crs Asm Disk Dg: DATA + Crs Asm Disk Dg Redundancy: external + Db Base: /u01/app/oracle + Db Char Set: AL32UTF8 + Db Data File Dest Dg: DATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: DATA + Db Response File: + Db Sw Zip File: db_home.zip + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Grid Sw Zip File: grid_home.zip + Host Sw Stage Location: /scratch/software/stage + Inventory: /u01/app/oraInventory + Pga Size: 1G + Processes: 2000 + Sga Size: 3G + Sw Mount Location: /u01 + Crs Dg Storage Class: oci-bv + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Enable Ons: enable + Image: localhost/oracle/database-orestart:19.3.0-slim + Image Pull Policy: IfNotPresent + Inst Details: + Env Vars: + Name: IGNORE_CRS_PREREQS + Value: true + Name: IGNORE_DB_PREREQS + Value: true + Name: dbmc1 + Sw Loc Storage Size In Gb: 300 + Worker Node: + 10.0.10.58 + Lb Service: + Node Port Svc: + Name: dbmc1-service-nodeport + Port Mappings: + Node Port: 30007 + Port: 1521 + Protocol: TCP + Target Port: 1521 + Node Port: 30200 + Port: 6200 + Protocol: TCP + Target Port: 6200 + Svc Type: nodeport + Resources: + Limits: + Cpu: 2 + Memory: 16Gi + Requests: + Cpu: 2 + Memory: 16Gi + Security Context: + Sysctls: + Name: kernel.shmall + Value: 2097152 + Name: kernel.sem + Value: 250 32000 100 128 + Name: kernel.shmmax + Value: 8589934592 + Name: kernel.shmmni + Value: 4096 + Service Details: + Name: soepdb + Ssh Key Secret: + Key Mount Location: /mnt/.ssh + Name: ssh-key-secret + Priv Key Secret Name: ssh-privkey + Pub Key Secret Name: ssh-pubkey + Sw Dg Storage Class: oci-bv +Status: + Oracle Restart Nodes: + Name: dbmc1-0 + Node Details: + Instance State: OPEN + Pod State: AVAILABLE + Cluster State: HEALTHY + Mounted Devices: + /dev/asm-disk1 + /dev/asm-disk2 + State: AVAILABLE + Asm Details: + Diskgroup: + Disks: + /dev/asm-disk1,/dev/asm-disk2 + Name: DATA + Redundancy: EXTERN + Conditions: + Last Transition Time: 2025-10-09T18:10:31Z + Message: reconcile has been queued + Observed Generation: 1 + Reason: LastReconcileCycleQueued + Status: True + Type: ReconcileQueued + Last Transition Time: 2025-10-09T18:28:17Z + Message: reconcile completed successfully + Observed Generation: 1 + Reason: LastReconcileCycleCompleted + Status: True + Type: ReconcileComplete + Config Params: + Crs Asm Device List: /dev/asm-disk1,/dev/asm-disk2 + Crs Asm Disk Dg: DATA + Crs Asm Disk Dg Redundancy: external + Db Base: /u01/app/oracle + Db Data File Dest Dg: DATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: DATA + Db Response File: + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Inventory: /u01/app/oraInventory + Connect String: dbmc1-0:1521/PORCLCDB + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Db State: OPEN + External Connect String: 129.146.0.149:30007/soepdb + Pdb Connect String: dbmc1-0:1521/ORCLPDB + Release Update: 19.28.0.0.0 + Role: PRIMARY + Service Details: + Name: soepdb + Svc State: service soepdb is running + State: AVAILABLE +Events: + + + +$ kubectl get pvc -n orestart +NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE +asm-pvc-crsdg-asm-disk1-oraclerestart-sample-dbmc1-0 Bound csi-4c9b2faa-9cd5-45a5-9ca1-dd77e708f03f 50Gi RWO oci-bv 27m +asm-pvc-crsdg-asm-disk2-oraclerestart-sample-dbmc1-0 Bound csi-ed7630fc-4f79-426b-a877-ab70ca6ebb87 50Gi RWO oci-bv 27m +dbmc1-oradata-sw-vol-pvc-dbmc1-0 Bound csi-15fbddd5-3994-4e3e-b072-c760eaa76188 300Gi RWO oci-bv 27m +pv-stage-vol-claim Bound pv-stage-vol1 500Gi ROX fss-dyn-storage 12h + + +$ kubectl get pv +NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE +csi-15fbddd5-3994-4e3e-b072-c760eaa76188 300Gi RWO Delete Bound orestart/dbmc1-oradata-sw-vol-pvc-dbmc1-0 oci-bv 27m +csi-4c9b2faa-9cd5-45a5-9ca1-dd77e708f03f 50Gi RWO Delete Bound orestart/asm-pvc-crsdg-asm-disk1-oraclerestart-sample-dbmc1-0 oci-bv 27m +csi-ed7630fc-4f79-426b-a877-ab70ca6ebb87 50Gi RWO Delete Bound orestart/asm-pvc-crsdg-asm-disk2-oraclerestart-sample-dbmc1-0 oci-bv 27m \ No newline at end of file diff --git a/docs/oraclerestart/provisioning/orestart_storage_class_object_after_sw_home_resize.txt b/docs/oraclerestart/provisioning/orestart_storage_class_object_after_sw_home_resize.txt new file mode 100644 index 00000000..1ddb0a47 --- /dev/null +++ b/docs/oraclerestart/provisioning/orestart_storage_class_object_after_sw_home_resize.txt @@ -0,0 +1,197 @@ +$ kubectl describe oraclerestarts.database.oracle.com/oraclerestart-sample -n orestart +Name: oraclerestart-sample +Namespace: orestart +Labels: +Annotations: OracleRestarts.database.oracle.com/old-spec: + {"instDetails":{"name":"dbmc1","swLocStorageSizeInGb":400,"workerNode":["10.0.10.58"],"envVars":[{"name":"IGNORE_CRS_PREREQS","value":"tru... +API Version: database.oracle.com/v4 +Kind: OracleRestart +Metadata: + Creation Timestamp: 2025-10-09T18:01:21Z + Finalizers: + database.oracle.com/oraclerestartfinalizer + Generation: 2 + Resource Version: 246493782 + UID: c42b7961-6cdb-416e-a17c-616cc0c08b1f +Spec: + Asm Storage Details: + Disks By Size: + Disk Names: + /dev/asm-disk1 + /dev/asm-disk2 + Storage Size In Gb: 50 + Config Params: + Cpu Count: 4 + Crs Asm Device List: /dev/asm-disk1,/dev/asm-disk2 + Crs Asm Disk Dg: DATA + Crs Asm Disk Dg Redundancy: external + Db Base: /u01/app/oracle + Db Char Set: AL32UTF8 + Db Data File Dest Dg: DATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: DATA + Db Response File: + Db Sw Zip File: db_home.zip + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Grid Sw Zip File: grid_home.zip + Host Sw Stage Location: /scratch/software/stage + Inventory: /u01/app/oraInventory + Pga Size: 1G + Processes: 2000 + Sga Size: 3G + Sw Mount Location: /u01 + Crs Dg Storage Class: oci-bv + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Enable Ons: enable + Image: localhost/oracle/database-orestart:19.3.0-slim + Image Pull Policy: IfNotPresent + Inst Details: + Env Vars: + Name: IGNORE_CRS_PREREQS + Value: true + Name: IGNORE_DB_PREREQS + Value: true + Name: dbmc1 + Sw Loc Storage Size In Gb: 400 + Worker Node: + 10.0.10.58 + Lb Service: + Node Port Svc: + Name: dbmc1-service-nodeport + Port Mappings: + Node Port: 30007 + Port: 1521 + Protocol: TCP + Target Port: 1521 + Node Port: 30200 + Port: 6200 + Protocol: TCP + Target Port: 6200 + Svc Type: nodeport + Resources: + Limits: + Cpu: 2 + Memory: 16Gi + Requests: + Cpu: 2 + Memory: 16Gi + Security Context: + Sysctls: + Name: kernel.shmall + Value: 2097152 + Name: kernel.sem + Value: 250 32000 100 128 + Name: kernel.shmmax + Value: 8589934592 + Name: kernel.shmmni + Value: 4096 + Service Details: + Name: soepdb + Ssh Key Secret: + Key Mount Location: /mnt/.ssh + Name: ssh-key-secret + Priv Key Secret Name: ssh-privkey + Pub Key Secret Name: ssh-pubkey + Sw Dg Storage Class: oci-bv +Status: + Oracle Restart Nodes: + Name: dbmc1-0 + Node Details: + Instance State: OPEN + Pod State: AVAILABLE + Cluster State: HEALTHY + Mounted Devices: + /dev/asm-disk1 + /dev/asm-disk2 + State: AVAILABLE + Asm Details: + Diskgroup: + Disks: + /dev/asm-disk1,/dev/asm-disk2 + Name: DATA + Redundancy: EXTERN + Conditions: + Last Transition Time: 2025-10-09T18:10:31Z + Message: reconcile has been queued + Observed Generation: 1 + Reason: LastReconcileCycleQueued + Status: True + Type: ReconcileQueued + Last Transition Time: 2025-10-09T20:44:39Z + Message: reconcile completed successfully + Observed Generation: 2 + Reason: LastReconcileCycleCompleted + Status: True + Type: ReconcileComplete + Config Params: + Crs Asm Device List: /dev/asm-disk1,/dev/asm-disk2 + Crs Asm Disk Dg: DATA + Crs Asm Disk Dg Redundancy: external + Db Base: /u01/app/oracle + Db Data File Dest Dg: DATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: DATA + Db Response File: + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Inventory: /u01/app/oraInventory + Connect String: dbmc1-0:1521/PORCLCDB + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Db State: OPEN + External Connect String: 129.146.0.149:30007/soepdb + Pdb Connect String: dbmc1-0:1521/ORCLPDB + Release Update: 19.28.0.0.0 + Role: PRIMARY + Service Details: + Name: soepdb + Svc State: service soepdb is running + State: AVAILABLE +Events: + + + +$ kubectl get pvc -n orestart +NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE +asm-pvc-crsdg-asm-disk1-oraclerestart-sample-dbmc1-0 Bound csi-4c9b2faa-9cd5-45a5-9ca1-dd77e708f03f 50Gi RWO oci-bv 164m +asm-pvc-crsdg-asm-disk2-oraclerestart-sample-dbmc1-0 Bound csi-ed7630fc-4f79-426b-a877-ab70ca6ebb87 50Gi RWO oci-bv 164m +dbmc1-oradata-sw-vol-pvc-dbmc1-0 Bound csi-15fbddd5-3994-4e3e-b072-c760eaa76188 400Gi RWO oci-bv 164m +pv-stage-vol-claim Bound pv-stage-vol1 500Gi ROX fss-dyn-storage 14h + + + + +[root@dbmc1-0 rac-work-dir]# df -h +Filesystem Size Used Avail Use% Mounted on +overlay 489G 37G 452G 8% / +tmpfs 64M 0 64M 0% /dev +tmpfs 46G 0 46G 0% /sys/fs/cgroup +tmpfs 45G 3.9G 42G 9% /etc/hostname +tmpfs 46G 112K 46G 1% /run +/dev/sdo 393G 14G 380G 4% /u01 +tmpfs 83G 8.0K 83G 1% /mnt/.ssh +tmpfs 83G 8.0K 83G 1% /mnt/.dbsecrets +tmpfs 83G 1.1G 82G 2% /dev/shm +/dev/mapper/ocivolume-root 489G 37G 452G 8% /etc/hosts +/dev/sdb1 196G 55G 132G 30% /scratch/software/stage +tmpfs 46G 0 46G 0% /run/lock +tmpfs 46G 75M 46G 1% /tmp +tmpfs 46G 64M 46G 1% /var/log/journal +tmpfs 46G 0 46G 0% /proc/acpi +tmpfs 46G 0 46G 0% /proc/scsi +tmpfs 46G 0 46G 0% /sys/firmware +tmpfs 46G 0 46G 0% /sys/fs/selinux \ No newline at end of file diff --git a/docs/oraclerestart/provisioning/orestart_storage_class_object_before_sw_home_resize.txt b/docs/oraclerestart/provisioning/orestart_storage_class_object_before_sw_home_resize.txt new file mode 100644 index 00000000..d7dab6c9 --- /dev/null +++ b/docs/oraclerestart/provisioning/orestart_storage_class_object_before_sw_home_resize.txt @@ -0,0 +1,196 @@ +$ kubectl describe oraclerestarts.database.oracle.com/oraclerestart-sample -n orestart +Name: oraclerestart-sample +Namespace: orestart +Labels: +Annotations: OracleRestarts.database.oracle.com/old-spec: + {"instDetails":{"name":"dbmc1","swLocStorageSizeInGb":300,"workerNode":["10.0.10.58"],"envVars":[{"name":"IGNORE_CRS_PREREQS","value":"tru... +API Version: database.oracle.com/v4 +Kind: OracleRestart +Metadata: + Creation Timestamp: 2025-10-09T18:01:21Z + Finalizers: + database.oracle.com/oraclerestartfinalizer + Generation: 1 + Resource Version: 246491127 + UID: c42b7961-6cdb-416e-a17c-616cc0c08b1f +Spec: + Asm Storage Details: + Disks By Size: + Disk Names: + /dev/asm-disk1 + /dev/asm-disk2 + Storage Size In Gb: 50 + Config Params: + Cpu Count: 4 + Crs Asm Device List: /dev/asm-disk1,/dev/asm-disk2 + Crs Asm Disk Dg: DATA + Crs Asm Disk Dg Redundancy: external + Db Base: /u01/app/oracle + Db Char Set: AL32UTF8 + Db Data File Dest Dg: DATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: DATA + Db Response File: + Db Sw Zip File: db_home.zip + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Grid Sw Zip File: grid_home.zip + Host Sw Stage Location: /scratch/software/stage + Inventory: /u01/app/oraInventory + Pga Size: 1G + Processes: 2000 + Sga Size: 3G + Sw Mount Location: /u01 + Crs Dg Storage Class: oci-bv + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Enable Ons: enable + Image: localhost/oracle/database-orestart:19.3.0-slim + Image Pull Policy: IfNotPresent + Inst Details: + Env Vars: + Name: IGNORE_CRS_PREREQS + Value: true + Name: IGNORE_DB_PREREQS + Value: true + Name: dbmc1 + Sw Loc Storage Size In Gb: 300 + Worker Node: + 10.0.10.58 + Lb Service: + Node Port Svc: + Name: dbmc1-service-nodeport + Port Mappings: + Node Port: 30007 + Port: 1521 + Protocol: TCP + Target Port: 1521 + Node Port: 30200 + Port: 6200 + Protocol: TCP + Target Port: 6200 + Svc Type: nodeport + Resources: + Limits: + Cpu: 2 + Memory: 16Gi + Requests: + Cpu: 2 + Memory: 16Gi + Security Context: + Sysctls: + Name: kernel.shmall + Value: 2097152 + Name: kernel.sem + Value: 250 32000 100 128 + Name: kernel.shmmax + Value: 8589934592 + Name: kernel.shmmni + Value: 4096 + Service Details: + Name: soepdb + Ssh Key Secret: + Key Mount Location: /mnt/.ssh + Name: ssh-key-secret + Priv Key Secret Name: ssh-privkey + Pub Key Secret Name: ssh-pubkey + Sw Dg Storage Class: oci-bv +Status: + Oracle Restart Nodes: + Name: dbmc1-0 + Node Details: + Instance State: OPEN + Pod State: AVAILABLE + Cluster State: HEALTHY + Mounted Devices: + /dev/asm-disk1 + /dev/asm-disk2 + State: AVAILABLE + Asm Details: + Diskgroup: + Disks: + /dev/asm-disk1,/dev/asm-disk2 + Name: DATA + Redundancy: EXTERN + Conditions: + Last Transition Time: 2025-10-09T18:10:31Z + Message: reconcile has been queued + Observed Generation: 1 + Reason: LastReconcileCycleQueued + Status: True + Type: ReconcileQueued + Last Transition Time: 2025-10-09T20:35:50Z + Message: reconcile completed successfully + Observed Generation: 1 + Reason: LastReconcileCycleCompleted + Status: True + Type: ReconcileComplete + Config Params: + Crs Asm Device List: /dev/asm-disk1,/dev/asm-disk2 + Crs Asm Disk Dg: DATA + Crs Asm Disk Dg Redundancy: external + Db Base: /u01/app/oracle + Db Data File Dest Dg: DATA + Db Home: /u01/app/oracle/product/19c/dbhome_1 + Db Name: PORCLCDB + Db Recovery File Dest: DATA + Db Response File: + Grid Base: /u01/app/grid + Grid Home: /u01/app/19c/grid + Grid Response File: + Inventory: /u01/app/oraInventory + Connect String: dbmc1-0:1521/PORCLCDB + Db Secret: + Key File Mount Location: /mnt/.dbsecrets + Key File Name: key.pem + Name: db-user-pass-pkutl + Pwd File Mount Location: /mnt/.dbsecrets + Pwd File Name: pwdfile.enc + Db State: OPEN + External Connect String: 129.146.0.149:30007/soepdb + Pdb Connect String: dbmc1-0:1521/ORCLPDB + Release Update: 19.28.0.0.0 + Role: PRIMARY + Service Details: + Name: soepdb + Svc State: service soepdb is running + State: AVAILABLE +Events: + + +$ kubectl get pvc -n orestart +NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE +asm-pvc-crsdg-asm-disk1-oraclerestart-sample-dbmc1-0 Bound csi-4c9b2faa-9cd5-45a5-9ca1-dd77e708f03f 50Gi RWO oci-bv 154m +asm-pvc-crsdg-asm-disk2-oraclerestart-sample-dbmc1-0 Bound csi-ed7630fc-4f79-426b-a877-ab70ca6ebb87 50Gi RWO oci-bv 154m +dbmc1-oradata-sw-vol-pvc-dbmc1-0 Bound csi-15fbddd5-3994-4e3e-b072-c760eaa76188 300Gi RWO oci-bv 154m + + + + +[root@dbmc1-0 rac-work-dir]# df -h +Filesystem Size Used Avail Use% Mounted on +overlay 489G 37G 452G 8% / +tmpfs 64M 0 64M 0% /dev +tmpfs 46G 0 46G 0% /sys/fs/cgroup +tmpfs 45G 3.9G 42G 9% /etc/hostname +tmpfs 46G 112K 46G 1% /run +/dev/sdo 295G 14G 281G 5% /u01 +tmpfs 83G 8.0K 83G 1% /mnt/.ssh +tmpfs 83G 8.0K 83G 1% /mnt/.dbsecrets +tmpfs 83G 1.1G 82G 2% /dev/shm +/dev/mapper/ocivolume-root 489G 37G 452G 8% /etc/hosts +/dev/sdb1 196G 55G 132G 30% /scratch/software/stage +tmpfs 46G 0 46G 0% /run/lock +tmpfs 46G 71M 46G 1% /tmp +tmpfs 46G 64M 46G 1% /var/log/journal +tmpfs 46G 0 46G 0% /proc/acpi +tmpfs 46G 0 46G 0% /proc/scsi +tmpfs 46G 0 46G 0% /sys/firmware +tmpfs 46G 0 46G 0% /sys/fs/selinux +[root@dbmc1-0 rac-work-dir]# \ No newline at end of file diff --git a/docs/oraclerestart/provisioning/prerequisites_oracle_restart_db.md b/docs/oraclerestart/provisioning/prerequisites_oracle_restart_db.md new file mode 100644 index 00000000..3aa64552 --- /dev/null +++ b/docs/oraclerestart/provisioning/prerequisites_oracle_restart_db.md @@ -0,0 +1,323 @@ +# Prerequisites for running Oracle Restart Database Controller + * [Kubernetes Cluster Requirements](#kubernetes-cluster-requirements) + * [Mandatory roles and privileges requirements for Oracle Restart Database Controller](#mandatory-roles-and-privileges-requirements-for-oracle-restart-database-controller) + * [Prerequisites for Oracle Restart on OKE](#prerequisites-for-oracle-restart-on-oke) + * [Preparing to Install Oracle Restart on OKE](#preparing-to-install-oracle-restart-on-oke) + * [Worker Node Preparation for Oracle Restart on OKE](#worker-node-preparation-for-oracle-restart-on-oke) + + [Download Oracle Grid Infrastructure and Oracle Database Software](#download-oracle-grid-infrastructure-and-oracle-database-software) + + [Permission on the software files](#permission-on-the-software-files) + + [Prepare the Worker Node for Oracle Restart Deployment](#prepare-the-worker-node-for-oracle-restart-deployment) + + [Set up SELinux Module on Worker Nodes](#set-up-selinux-module-on-worker-nodes) + * [Create a namespace for the Oracle Restart Setup](#create-a-namespace-for-the-oracle-restart-setup) + * [Install Cert Manager and Setup Access Permissions](#install-cert-manager-and-setup-access-permissions) + * [Additional requirements for OpenShift Security Context Constraints](#additional-requirements-for-openshift-security-context-constraints) + * [Deploy Oracle Database Operator](#deploy-oracle-database-operator) + * [Oracle Restart Database Slim Image](#oracle-restart-database-slim-image) + * [Create a Kubernetes secret for the Oracle Restart Database installation owner for the Oracle Restart Database Deployment](#create-a-kubernetes-secret-for-the-oracle-restart-database-installation-owner-for-the-oracle-restart-database-deployment) + +To deploy an Oracle Database using the Oracle Restart Database Controller in the Oracle Database Operator, you require a Kubernetes cluster such as Oracle Kubernetes Engine (OKE). + +If you are using an Oracle Kubernetes Engine (OKE) Kubernetes Cluster, you will require: + + ## Kubernetes Cluster Requirements + You must ensure that your Kubernetes Cluster meets the necessary requirements for Oracle Restart Database deployment. The minimum required OKE cluster version is 1.33 or higher. Refer documentation for details [Oracle Kubernetes Engine](https://docs.oracle.com/en-us/iaas/Content/ContEng/Concepts/contengoverview.htm) cluster. + + ## Mandatory roles and privileges requirements for Oracle Restart Database Controller + Oracle Restart Database Controller uses Kubernetes objects such as :- + + | Resources | Verbs | + | --- | --- | + | Pods | create delete get list patch update watch | + | Containers | create delete get list patch update watch | + | PersistentVolumeClaims | create delete get list patch update watch | + | Services | create delete get list patch update watch | + | Secrets | create delete get list patch update watch | + | Events | create patch | + + ## Prerequisites for Oracle Restart on OKE + Before proceeding with the Oracle Database deployment, ensure that you have completed all required prerequisites on your Oracle Kubernetes Engine (OKE) cluster. This includes setting up the necessary infrastructure and configuring all relevant components. To effectively use these instructions, you should have background knowledge of both Kubernetes technology and the underlying operating system. + * Verify that all necessary dependencies are installed and up-to-date. You should be familiar with the following technologies: + * Linux + * Kubernetes + * Oracle Kubernetes Engine Environment (OKE) + * Oracle Real Database installation for Oracle Restart + * Oracle Grid Infrastructure installation + * Oracle Automatic Storage Management (Oracle ASM) + + ## Preparing to Install Oracle Restart on OKE + To prepare for the Oracle Restart Database installation, follow these steps: + * Ensure that your OKE cluster is properly configured and meets the necessary requirements. + Each pod that you deploy as part of your cluster must satisfy the minimum hardware requirements of the Oracle Restart Database and Oracle Grid Infrastructure software. If you are planning to install Oracle Grid Infrastructure and Oracle Restart database software on host local volumes exposed from your environment, then you must have at least 50 GB space allocated for the Oracle Restart on OKE Pod. + * In addition to the standard memory (RAM) required for Oracle Linux (Linux-x86-64), the Oracle Grid Infrastructure and Oracle Database instances, Oracle recommends that you provide an additional 2 GB of RAM to each Kubernetes node for the control plane. + * Database storage for Oracle Database on OKE must use Oracle Automatic Storage Management (Oracle ASM) configured on block storage. + + * Prerequisite Software and Environment Versions: + * Oracle Grid Infrastructure: Release 19.28 or later + * Oracle Database: Release 19.28 or later + * Oracle Kubernetes Engine (OKE): Version 1.33 or later + * Unbreakable Enterprise Kernel (UEK): Release 7 UEKR7 (Kernel Release 5.15.0-202.135.2.el9uek.x86_64) or later + * Oracle Linux: For Operator, control plane, and worker nodes—Oracle Linux 8 (Linux-x86-64) Update 10 or later + + ## Worker Node Preparation for Oracle Restart on OKE + * When configuring your Worker Nodes, follow these guidelines, and see the configuration Oracle used for testing. + Each OKE worker node must have sufficient resources to support the intended number of Oracle Database Pods, each of which must meet at least the minimum requirements for Oracle Grid Infrastructure servers hosting an Oracle Database node. + * The Oracle Database Pods in this example configuration were created on the machine `10.0.10.58` for Oracle Restart: + - Oracle Database Node + - Worker Node: 10.0.10.58 + - Container Pod: dbmc1-0 + * Worker node has the following configuration: + - RAM:32GB + - Operating system disk: + - Ensure that your storage has at least the following available space: + - / (Root): 40 GB + - /scratch/orestart/: 80 GB (the worker node directory which will be used for /u01 to store Oracle Grid Infrastructure and Oracle Database homes) + - /var/lib/containers: 100 GB xfs + - Oracle Linux 8.10 with Unbreakable Enterprise Kernel Release UEKR7 (Kernel Release 5.15.0-308.179.6.el8uek.x86_64) or later + - Network Cards: + - ens3: Default network interface. The Oracle Restart pod will use this network interface for cluster public network. + - Skip this step **if you are using a StorageClass**, You only need to configure the following storage locations on the worker node for the software volume and ASM disks. + - /scratch/software/stage (the worker node directory will be exposed as local host volume to the Oracle Restart pod for staging Oracle Grid Infrastructure and Oracle RDBMS software) + - Block devices: + - You can use any supported storage options for Oracle Grid Infrastructure. Ensure that your storage has at least the following space available: + - /dev/oracleoci/oraclevdd (50 GB) + - /dev/oracleoci/oraclevde (50 GB) + + **Notes** + - Make sure the devices you are using for ASM Storage are cleared of any data from a previous usage or installation.** + - If you want to use the devices from the worker nodes for ASM storage, you will need to mark any default StorageClass as non-default in your Kubernetes Cluster.** + + ### Download Oracle Grid Infrastructure and Oracle Database Software + You need to download the Oracle Grid Infrastructure and Oracle Database software, you can make it available inside the Pod using following steps: + + * Prepare the Persistent Volume (PVC) for Staging: + * If you plan to use an existing Persistent Volume Claim (PVC) as a staging area, ensure it is pre-created and available before deployment. + * NFS (Network File System) is commonly used as a backing storage for staging, since it allows multiple pods/nodes to access the staged files. + * Copy Software to Staging Location: + * Download the Oracle Grid Infrastructure and Oracle Database installation media from Oracle's official sources. + * Copy (stage) these files onto the PVC (or NFS volume) you intend to use. If you opt to stage on a worker node's local storage, ensure sufficient space and security practices. + * Mounting in Pod: + * The Oracle Restart Database Controller is responsible for mounting the staged software into the Pod at runtime, making the installers available for use during installation or upgrade tasks. + * You should define the appropriate volume mounts in your deployment YAML manifests to ensure the pod sees the staged content. + + * The Oracle Database Container does not contain any Oracle software binaries. Download the following software from the [Oracle Technology Network](https://www.oracle.com/technetwork/database/enterprise-edition/downloads/index.html). + - Oracle Grid Infrastructure 19c (19.28) for Linux x86-64 + - Oracle Database 19c (19.28) for Linux x86-64 + + ### Permission on the software files + + Depending on the wheter you are provisioning the Oracle Restart Database using Base Release sofware or you are applying an RU patch or any one-off patch, please set the below permissions on the software files in the staging location: + + - Set the permission on the GRID Infrastructure Software and RDBMS Software .zip files to be 755. + - Set the permission on the Opatch .zip file to be 755 + - Set the permission on the unzipped RU software directory to be 755 recursively. + - Set the permission on the unzipped oneoff patch software directory to be 755 recursively. + + + ### Worker Node Preparation Checklist + Preparing the worker node is a critical foundation for a secure and successful Oracle Restart Database deployment in a Kubernetes environmen. These steps need to be executed by Kuberernetes administrator as root user on worker nodes and follow these steps: + + #### System Requirements + * Verify OS and Kernel Versions: Ensure your node’s operating system and kernel version are supported by Oracle Database and Kubernetes. + * Resource Allocation: Confirm the node has sufficient CPU, memory, and storage for Oracle Grid and Database. + #### Kernel and System Settings + * Use the vim editor to update `/etc/sysctl.conf` parameters to the following values: + ```sh + fs.file-max = 6815744 + net.core.rmem_default = 262144 + net.core.rmem_max = 4194304 + net.core.wmem_default = 262144 + net.core.wmem_max = 1048576 + fs.aio-max-nr = 1048576 + vm.nr_hugepages=16384 + ``` + * Run the following commands: + * `# sysctl -a` + * `# sysctl –p` + * Verify that the swap memory is disabled by running: + ```sh + # free -m + ..... + Swap: 0 0 0 + ``` + * Enable kernel parameters at the Kubelet level, so that kernel parameters can be set at the Pod level. This is a one-time activity. + * In the `/etc/systemd/system/kubelet.service.d/00-default.conf` file of OKE Worker nodes, add below environment variable: + ```txt + Environment="KUBELET_EXTRA_ARGS=--fail-swap-on=false --allowed-unsafe-sysctls='kernel.shm*,net.*,kernel.sem'" + ``` + * Reload Configurations: `# systemctl daemon-reload` + * Restart Kubelet: `# systemctl restart kubelet` + * Check the Kubelet status: `# systemctl status kubelet` + + **Note: For openshift worker nodes**, path to edit is `/etc/systemd/system/kubelet.service.d/99-kubelet-extra-args.conf` and add below content in this file: + ```txt + [Service] + Environment="KUBELET_EXTRA_ARGS=--fail-swap-on=false --allowed-unsafe-sysctls='kernel.shm*,net.*,kernel.sem'" + ``` + * Reload Configurations: `# systemctl daemon-reload` + * Restart Kubelet: `# systemctl restart kubelet` + * Check the Kubelet status: `# systemctl status kubelet` + + * Skip this step **if you are using a StorageClass**.Otherwise, create the necessary mount points on the worker node. These mount points will be used by the Oracle Restart pod for Oracle Grid Infrastructure and RDBMS Home, as well as for the software staging location. + * On worker node: + + `# mkdir -p /scratch/orestart/` + + `# mkdir -p /scratch/software/stage` + * For the case where you are installing Oracle Base Release with RU Patch, create the required mount points for Base Release Software, for the location to unzip the RU Patch etc: + * For Example, for Release 19c with 19.28 RU, on worker node: + + `# mkdir -p /stage/software/19c/19.3.0` + + `# mkdir -p /stage/software/19c/19.28` + + * Download the Oracle Grid Infrastructure and Oracle RDBMS Software .zip files. Copy those files to the worker node at the staging location `/scratch/software/stage/` + + #### Security Controls + Traditional Unix security uses discretionary access control (DAC). SELinux is an example of mandatory access control. SELinux restricts many commands from the Oracle Restart Pod that are not allowed to run, which results in permission denied errors. To avoid such errors, you must create an SELinux policy package to allow certain commands. + + To set up the SELinux module on the worker node, follow these steps as `root` user to create an SELinux policy package on the worker node: + + * Verify that SELinux is enabled on the Worker node. For example: + ```sh + # getenforce + enforcing + ``` + * Install the SELinux devel package on the Worker nodes: `# dnf install selinux-policy-devel` + * Create a file `oradb-oke.te` under `/var/opt` on the Worker nodes with the below content: + ```sh + module oradb-oke 1.0; + + require { + type kernel_t; + class system syslog_read; + type container_runtime_t; + type container_init_t; + class file getattr; + type container_file_t; + type lib_t; + type textrel_shlib_t; + type bin_t; + class file { execmod execute map setattr }; + } + + #============= container_init_t ============== + allow container_init_t container_runtime_t:file getattr; + allow container_init_t bin_t:file map; + allow container_init_t bin_t:file execute; + allow container_init_t container_file_t:file execmod; + allow container_init_t lib_t:file execmod; + allow container_init_t textrel_shlib_t:file setattr; + allow container_init_t kernel_t:system syslog_read; + ``` +* Create and install the policy package: + * `# cd /var/opt` + * `make -f /usr/share/selinux/devel/Makefile oradb-oke.pp` + * `semodule -i oradb-oke.pp` + * `semodule -l | grep oradb-oke` +* Skip this step, **if you are using storgaclass**. Configure the SELinux context for the required worker node directory and files: + * On worker node: + + `# semanage fcontext -a -t container_file_t /scratch/orestart` + + `# sudo restorecon -vF /scratch/orestart` + + `# semanage fcontext -a -t container_file_t /scratch/software/stage/grid_home.zip` + + `# sudo restorecon -vF /scratch/software/stage/grid_home.zip` + + `# semanage fcontext -a -t container_file_t /scratch/software/stage/db_home.zip` + + `# sudo restorecon -vF /scratch/software/stage/db_home.zip` + + **Notes:** + * Change these paths and file names as per location of your environment for setting Oracle Restart and names of the software .zip files. + * In case of Oracle Base Release and RU Patch software, you will need to run similar commands for the corresponding .zip files. + +## Create a namespace for the Oracle Restart Setup +Create a Kubernetes namespace named `orestart`. All the resources belonging to the Oracle Restart Database will be provisioned in this namespace named `orestart`. For example: + + ```sh + #### Create the namespace + kubectl create ns orestart + + #### Check the created namespace + kubectl get ns orestart + ``` +If you want, you can choose any name for the namespace to deploy Oracle Restart Database. + +## Install Cert Manager and Setup Access Permissions +Before using Oracle Restart Database Controller, ensure you have completed the prerequisites which includes: + +* The installation of cert-manager +* Creation of Role Bindings for Access Management + +Refer to the section [Prerequisites](../../../README.md#prerequisites) + +Apart from the default Role Bindings for access management mentioned in above section, you will require the below mentioned role bindings: + + For exposing the database using Nodeport services, apply [RBAC](../../../rbac/node-rbac.yaml) + ```sh + kubectl apply -f rbac/node-rbac.yaml + ``` + For automatic storage expansion of block volumes, apply [RBAC](../../../rbac/storage-class-rbac.yaml) + ```sh + kubectl apply -f rbac/storage-class-rbac.yaml + ``` + For getting get, list and watch privileges on the persistent volumes, apply [RBAC](../../rbac/persistent-volume-rbac.yaml) + ```sh + kubectl apply -f rbac/persistent-volume-rbac.yaml + ``` + + +## Additional requirements for OpenShift Security Context Constraints +When you deploy Oracle Restart Database using the Oracle Restart Database Controller on an OpenShift cluster, you must account for OpenShift's stricter security model, especially around Security Context Constraints (SCCs). + +Apart from the same steps listed above for setting up Oracle Restart Database using Oracle Restart Database Controller on an OKE Cluster, there are some additional steps required to setup Oracle Restart Database using Oracle Restart Database Controller on an OpenShift Cluster. + +OpenShift requires additional Security Context Constraints (SCC) for deploying and managing the `oraclerestarts.database.oracle.com` resource. To create the appropriate SCCs before deploying the `oraclerestarts.database.oracle.com` resource, complete these steps: + + * Apply the file [custom-kubeletconfig.yaml](../../config/samples/orestart/custom-kubeletconfig.yaml) with cluster-admin user privileges. + + ```sh + oc apply -f custom-kubeletconfig.yaml + ``` + Watch the worker MCP update: + ```sh + watch oc get mcp + # Wait for: UPDATING = False ,UPDATED = True ,DEGRADED = False + oc label mcp worker custom-kubelet=enable-unsafe-sysctls + oc get kubeletconfig + NAME AGE + enable-unsafe-sysctls 10m + ``` + + **Note:** OpenShift recommends that you should not deploy in namespaces starting with `kube`, `openshift` and the `default` namespace. + + * Create service account to be used for Openshift cluster to be used for `oraclerestarts.database.oracle.com` resource. + ```sh + oc create serviceaccount oraclerestart -n orestart + ``` + Note: We are using `oraclerestart` as service account name, you can change and make sure to use same in yaml file while creating `oraclerestarts.database.oracle.com` resource and step 3 below. + + * Apply the file [custom-scc.yaml](../../config/samples/orestart/custom-scc.yaml) with cluster-admin user privileges. + + ```sh + oc apply -f custom-scc.yaml + oc adm policy add-scc-to-user privileged -z oraclerestart -n orestart + ``` + +## Deploy Oracle Database Operator +After you have completed the prerequisite steps, you can install the operator. To install the operator in the cluster quickly, you can apply the modified `oracle-database-operator.yaml` file from the previous step. + +```sh +kubectl apply -f oracle-database-operator.yaml +``` + +For more details, please refer to [Install Oracle DB Operator](../../../README.md#install-oracle-db-operator) + +## Oracle Restart Database Slim Image + + #### Build your own Oracle Restart Database Slim Image + You can build this image using instructions provided in below documentation: + * [Building Oracle RAC Database Container Slim Image](https://github.com/oracle/docker-images/blob/main/OracleDatabase/RAC/OracleRealApplicationClusters/README.md#building-oracle-rac-database-container-slim-image) + +After the image is ready, push it to your private container images repository, so that you can pull this image during Oracle Restart Database provisioning.. + +**Note**: In the Oracle Restart Database provisioning sample .yaml files, we are using Oracle Restart Database slim image `odbcir/oracle/database-orestart:19.3.0-slim`. + +## Create a Kubernetes secret for the Oracle Restart Database installation owner for the Oracle Restart Database Deployment + * Create a Kubernetes secret named `db-user-pass` in `orestart` namespace using these steps: [Create Kubernetes Secret](./create_kubernetes_secret_for_db_user.md) + * Once the setup completes, you can change the password inside the pod for Oracle sys user. + * Create a Kubernetes secret named `ssh-key-secret` in `orestart` namespace using these steps: [Create Kubernetes Secret for SSH Key](./create_kubernetes_secret_for_ssh_setup.md) + +After you have the above prerequsites completed, you can proceed to the next section for your environment to provision the Oracle Restart Database. \ No newline at end of file diff --git a/docs/oraclerestart/provisioning/provisioning_oracle_restart_db.md b/docs/oraclerestart/provisioning/provisioning_oracle_restart_db.md new file mode 100644 index 00000000..90720156 --- /dev/null +++ b/docs/oraclerestart/provisioning/provisioning_oracle_restart_db.md @@ -0,0 +1,41 @@ +# Provisioning an Oracle Restart Database +### In this Usecase: +* In this use case, the Oracle Grid Infrastructure and Oracle Restart Database are deployed automatically using Oracle Restart Controller. +* This example uses `oraclerestart_prov.yaml` to provision an Oracle Database configured with Oracle Restart using Oracle Restart Controller. The provisioning includes: + * Oracle Restart Pod + * Headless services for Oracle Restart. + * Oracle Database Node hostname. + * Persistent volumes created automatically based on specified disks for Oracle ASM Storage. + * Software Persistent Volume and Staged Software Persistent Volume using the specified location on the corresponding worker node. + * Namespace: `orestart` + * Staged Software location on the worker nodes is specified by `hostSwStageLocation`. The Grid Infrastructure and RDBMS Binaries are copied to this location on the worker node. + * Software location on the worker nodes is specified by `hostSwLocation`. The GI HOME and the RDBMS HOME in the Oracle Restart Pod will be mounted using this location on the worker node. + +### In this Example: + * Oracle Restart Database Slim Image `dbocir/oracle/database-orestart:19.3.0-slim` is used. It is built using files from thsi [GitHub location](https://github.com/oracle/docker-images/tree/main/OracleDatabase/RAC/OracleRealApplicationClusters#building-oracle-rac-database-container-slim-image). The default image created using files from this project is `localhost/oracle/database-rac:19.3.0-slim`. You must tag it with the name `dbocir/oracle/database-orestart:19.3.0-slim`. + * When you are building the image yourself, update the image value in the `oraclerestart_prov.yaml` file to point to the container image that you have built. + * The disks on the worker nodes for the Oracle Restart storage are `/dev/disk/by-partlabel/asm-disk1` and `/dev/disk/by-partlabel/asm-disk2`. + * Specify the sizes and names of these devices using the parameter `storageSizeInGb`. By default, size is in GBs. + +**NOTE:** When no separate diskgroup names are specified for CRS Files, Database Files, Recovery Area Files and Redo Log Files, the default diskgroup named `+DATA` is created from the disks specified by the parameter `crsAsmDeviceList`. + +### Steps: Deploy Oracle Restart Database +* Use the file [oraclerestart_prov.yaml](./oraclerestart_prov.yaml) for this procedure: +* Deploy the `oraclerestart_prov.yaml` file: + ```sh + kubectl apply -f oraclerestart_prov.yaml + oraclerestart.database.oracle.com/oraclerestart-sample created + ``` +* Check the status of the deployment: + ```sh + # Check the status of the Kubernetes Pods: + kubectl get all -n orestart + + # Check the logs of a particular pod. For example, to check status of pod "dbmc1-0": + kubectl exec -it pod/dbmc1-0 -n orestart -- bash -c "tail -f /tmp/orod/oracle_db_setup.log" + =============================== + ORACLE DATABASE IS READY TO USE + =============================== + ``` +* Check Details of Kubernetes CRD Object as shown in this [example](./orestart_object.txt) +* For details about how ot connect to the Oracle Restart Database deployed in this procedure, refer to [Database Connection](./database_connection.md). diff --git a/docs/oraclerestart/provisioning/provisioning_oracle_restart_db_loadbalancer.md b/docs/oraclerestart/provisioning/provisioning_oracle_restart_db_loadbalancer.md new file mode 100644 index 00000000..ed91a65f --- /dev/null +++ b/docs/oraclerestart/provisioning/provisioning_oracle_restart_db_loadbalancer.md @@ -0,0 +1,44 @@ +# Provisioning an Oracle Restart Database with Load Balancer + +### In this Use case: +* In this use case, the Oracle Grid Infrastructure and Oracle Database are deployed automatically using Oracle Restart Controller. +* When Oracle Restart is deployed using the Oracle Restart Controller with a Load Balancer Service, the database is exposed externally through the cloud provider’s (for example, Oracle Cloud's) load balancer. This setup allows you to connect remotely to the database using the load balancer’s external IP and the target port, which is commonly 1521 (the Oracle default). +* This example uses the file `oraclerestart_prov_loadbalancer.yaml` to provision an Oracle Restart Database using Oracle Restart Controller with: + * Oracle Restart Pod + * Headless services for Oracle Restart + * Oracle Restart Node hostname + * Load Balancer Service with target port 1521 + * Persistent volumes created automatically based on specified disks for Oracle ASM storage + * Software Persistent Volume and Staged Software Persistent Volume using the specified location on the corresponding worker node. + * Namespace: `orestart` + * Staged Software location on the worker nodes is specified by `hostSwStageLocation`. The Grid Infrastructure and RDBMS Binaries are copied to this location on the worker node. + * Software location on the worker nodes is specified by `hostSwLocation`. The GI HOME and the RDBMS HOME in the Oracle Restart Pod will be mounted using this location on the worker node. + +### In this Example: + * Oracle Restart Database Slim Image `dbocir/oracle/database-orestart:19.3.0-slim` is used. It is built using files from this [GitHub location](https://github.com/oracle/docker-images/tree/main/OracleDatabase/RAC/OracleRealApplicationClusters#building-oracle-rac-database-container-slim-image). + * The disks on the worker nodes for the Oracle Restart storage are `/dev/disk/by-partlabel/asm-disk1` and `/dev/disk/by-partlabel/asm-disk2`. + * Specify the size of these devices along with names using the parameter `storageSizeInGb`. Size is by-default in GBs. + +**NOTE:** When no separate diskgroup names are specified for CRS Files, Database Files, Recovery Area Files and Redo Log Files, the default diskgroup named `+DATA` is created from the disks specified by the parameter `crsAsmDeviceList`. + +### Steps: Provision Oracle Restart Database +* Use the file [oraclerestart_prov_loadbalancer.yaml](./oraclerestart_prov_loadbalancer.yaml) for this procedure. +* When you are building the image yourself, update the image value in the `oraclerestart_prov_loadbalancer.yaml` file to point to the container image that you have built. +* Deploy the `oraclerestart_prov_loadbalancer.yaml` file: + ```sh + kubectl apply -f oraclerestart_prov_loadbalancer.yaml + oraclerestart.database.oracle.com/oraclerestart-sample created + ``` +* Check the status of the deployment: + ```sh + # Check the status of the Kubernetes Pods: + kubectl get all -n orestart + + # Check the logs of a particular pod. For example, to check status of pod "dbmc1-0": + kubectl exec -it pod/dbmc1-0 -n orestart -- bash -c "tail -f /tmp/orod/oracle_db_setup.log" + =============================== + ORACLE DATABASE IS READY TO USE + =============================== + ``` +* Check Details of Kubernetes CRD Object as in this [example](./orestart_loadbalancer_object.txt) +* For details about how to connect to the Oracle Restart Database deployed using this procedure, see: [Database Connection](./database_connection.md) . diff --git a/docs/oraclerestart/provisioning/provisioning_oracle_restart_db_nodeport.md b/docs/oraclerestart/provisioning/provisioning_oracle_restart_db_nodeport.md new file mode 100644 index 00000000..78e427f9 --- /dev/null +++ b/docs/oraclerestart/provisioning/provisioning_oracle_restart_db_nodeport.md @@ -0,0 +1,43 @@ +# Provisioning an Oracle Restart Database with NodePort Service +### In this Usecase: +* In this use case, the Oracle Grid Infrastructure and Oracle Database are deployed automatically using Oracle Restart Controller. In this use case, Oracle Restart is deployed with Node Port Service. +* A node port exposes the service on a static port on the node IP address and NodePorts are in the 30000-32767 range by default. +* This example uses `oraclerestart_prov_nodeports.yaml` to provision an Oracle Restart Database using Oracle Restart Controller with: + * Oracle Restart Pod + * Headless services for Oracle Restart + * Oracle Restart Node hostname + * Node Port 30007 mapped to port 1521 for Database Listener + * Persistent volumes created automatically based on specified disks for Oracle ASM storage + * Software Persistent Volume and Staged Software Persistent Volume using the specified location on the corresponding worker node. + * Namespace: `orestart` + * Staged Software location on the worker nodes is specified by `hostSwStageLocation`. The Grid Infrastructure and RDBMS Binaries are copied to this location on the worker node. + * Software location on the worker nodes is specified by `hostSwLocation`. The GI HOME and the RDBMS HOME in the Oracle Restart Pod will be mounted using this location on the worker node. + +### In this Example: + * Oracle Restart Database slim image `dbocir/oracle/database-orestart:19.3.0-slim` is used and it is built using files from [GitHub location](https://github.com/oracle/docker-images/tree/main/OracleDatabase/RAC/OracleRealApplicationClusters#building-oracle-rac-database-container-slim-image). Default image created using files from this project is `localhost/oracle/database-rac:19.3.0-slim`. You need to tag it with name `dbocir/oracle/database-orestart:19.3.0-slim`. + * When you are building the image yourself, update the image value in the `oraclerestart_prov_nodeports.yaml` file to point to the container image you have built. + * The disks on the worker nodes for the Oracle Restart storage are `/dev/disk/by-partlabel/asm-disk1` and `/dev/disk/by-partlabel/asm-disk2`. + * Specify the size of these devices along with names using the parameter `storageSizeInGb`. Size is by-default in GBs. + +**NOTE:** When no separate diskgroup names are specified for CRS Files, Database Files, Recovery Area Files and Redo Log Files, then the default diskgroup named `+DATA` is created from the disks specified by the parameter `crsAsmDeviceList`. + +### Steps: Deploy Oracle Restart Database +* Use the file: [oraclerestart_prov_nodeports.yaml](./oraclerestart_prov_nodeports.yaml) for this use case as below: +* Deploy the `oraclerestart_prov_nodeports.yaml` file: + ```sh + kubectl apply -f oraclerestart_prov_nodeports.yaml + oraclerestart.database.oracle.com/oraclerestart-sample created + ``` +* Check the status of the deployment: + ```sh + # Check the status of the Kubernetes Pods: + kubectl get all -n orestart + + # Check the logs of a particular pod. For example, to check status of pod "dbmc1-0": + kubectl exec -it pod/dbmc1-0 -n orestart -- bash -c "tail -f /tmp/orod/oracle_db_setup.log" + =============================== + ORACLE DATABASE IS READY TO USE + =============================== + ``` +* Check Details of Kubernetes CRD Object as in this [example](./orestart_nodeport_object.txt) +* Refer to the page [Database Connection](./database_connection.md) for the details to connect to Oracle Restart Database deployed using above example. diff --git a/docs/oraclerestart/provisioning/provisioning_oracle_restart_db_rupatch.md b/docs/oraclerestart/provisioning/provisioning_oracle_restart_db_rupatch.md new file mode 100644 index 00000000..09393ec1 --- /dev/null +++ b/docs/oraclerestart/provisioning/provisioning_oracle_restart_db_rupatch.md @@ -0,0 +1,45 @@ +# Provisioning an Oracle Restart Database with RU Patch on FileSystem +### In this Usecase: +* In this use case, the Oracle Grid Infrastructure and Oracle Restart Database are deployed automatically using Oracle Restart Controller. +* In this use case, before the installation of Oracle Restart, the GRID HOME and RDBMS HOME are patched with the provided `RU Patch`. +* This example uses `oraclerestart_prov_rupatch.yaml` to provision an Oracle Restart Database using Oracle Restart Controller with: + * Oracle Restart Pod + * Headless services for Oracle Restart + * Oracle Restart Node hostname + * Node Port 30007 mapped to port 1521 for Database Listener + * Persistent volumes created automatically based on specified disks for Oracle ASM storage + * Software Persistent Volume and Staged Software Persistent Volume using the specified location on the corresponding worker node. + * Namespace: `orestart` + * Staged Software location on the worker nodes is specified by `hostSwStageLocation`. The Grid Infrastructure and RDBMS Binaries are copied to this location on the worker node. + * Software location on the worker nodes is specified by `hostSwLocation`. The GI HOME and the RDBMS HOME in the Oracle Restart Pod will be mounted using this location on the worker node. + * Directory where the **Release Update (RU) patch** has been unzipped is specified by `ruPatchLocation`. + * For Example: To apply the 19.28 RU Patch `37957391`, if you have unzipped the RU Patch .zip file `p37957391_190000_Linux-x86-64.zip` to location `/scratch/software/19c/19.28/` on the worker node, then set this parameter `ruPatchLocation` to value `/scratch/software/ru_patch/37957391`. + +### In this Example: + * Oracle Restart Database Slim Image `dbocir/oracle/database-orestart:19.3.0-slim` is used and it is built using files from [GitHub location](https://github.com/oracle/docker-images/tree/main/OracleDatabase/RAC/OracleRealApplicationClusters#building-oracle-rac-database-container-slim-image). Default image created using files from this project is `localhost/oracle/database-rac:19.3.0-slim`. You need to tag it with name `dbocir/oracle/database-orestart:19.3.0-slim`. + * When you are building the image yourself, update the image value in the `oraclerestart_prov_rupatch.yaml` file to point to the container image you have built. + * The disks on the worker nodes for the Oracle Restart storage are `/dev/disk/by-partlabel/asm-disk1` and `/dev/disk/by-partlabel/asm-disk2`. + * Specify the size of these devices along with names using the parameter `storageSizeInGb`. Size is by-default in GBs. + +**NOTE:** When no separate diskgroup names are specified for CRS Files, Database Files, Recovery Area Files and Redo Log Files, then the default diskgroup named `+DATA` is created from the disks specified by the parameter `crsAsmDeviceList`. + +### Steps: Deploy Oracle Restart Database +* Use the file: [oraclerestart_prov_rupatch.yaml](./oraclerestart_prov_rupatch.yaml) for this use case as below: +* Deploy the `oraclerestart_prov_rupatch.yaml` file: + ```sh + kubectl apply -f oraclerestart_prov_rupatch.yaml + oraclerestart.database.oracle.com/oraclerestart-sample created + ``` +* Check the status of the deployment: + ```sh + # Check the status of the Kubernetes Pods: + kubectl get all -n orestart + + # Check the logs of a particular pod. For example, to check status of pod "dbmc1-0": + kubectl exec -it pod/dbmc1-0 -n orestart -- bash -c "tail -f /tmp/orod/oracle_db_setup.log" + =============================== + ORACLE DATABASE IS READY TO USE + =============================== + ``` +* Check Details of Kubernetes CRD Object as in this [example](./orestart_rupatch_object.txt) +* Refer to the page [Database Connection](./database_connection.md) for the details to connect to Oracle Restart Database deployed using above example. diff --git a/docs/oraclerestart/provisioning/provisioning_oracle_restart_db_rupatch_oneoffs.md b/docs/oraclerestart/provisioning/provisioning_oracle_restart_db_rupatch_oneoffs.md new file mode 100644 index 00000000..86edbff1 --- /dev/null +++ b/docs/oraclerestart/provisioning/provisioning_oracle_restart_db_rupatch_oneoffs.md @@ -0,0 +1,47 @@ +# Provisioning an Oracle Restart Database with RU Patch and One Offs with Custom Storage Class +### In this Usecase: +* In this use case, the Oracle Grid Infrastructure and Oracle Restart Database are deployed automatically using Oracle Restart Controller. +* This example uses `oraclerestart_prov_rupatch_oneoff_storageclass.yaml` to provision an Oracle Restart Database using Oracle Restart Controller with: + * Oracle Restart Pod + * Headless services for Oracle Restart + * Oracle Restart node hostname + * Node Port 30007 mapped to port 1521 for Database Listener. + * Persistent volumes created automatically based on specified disks for Oracle ASM storage. + * Staged Software location using a mount location which is using a pre created Persistent Volume from a network file system(NFS). + * Namespace: `orestart` + * Mount point for the Software Stage Location inside the Pod is specified by `swStagePvcMountLocation`. + * Staged Software location inside the Pod is specified by `hostSwStageLocation`. It is assumed that the Grid Infrastructure and RDBMS Binaries are already available in this path on the Persistent Volume used. + * The GI HOME and the RDBMS HOME in the Oracle Restart Pod are mounted using a Persistent Volume created using the Storage Class (specified using `swDgStorageClass`). This Persistent Volume is mounted to `/u01` inside the Pod. Size of this Persistent Volume is specified using `swLocStorageSizeInGb`. + * Directory where the **Release Update (RU) patch** has been unzipped is specified by `ruPatchLocation`. + * For Example: To apply the 19.28 RU Patch `37957391`, if you have unzipped the RU Patch .zip file `p37957391_190000_Linux-x86-64.zip` to location `/scratch/software/19c/19.28/` on the worker node, then set this parameter `ruPatchLocation` to value `/scratch/software/ru_patch/37957391`. + * Directory, where latest opatch zip file is available, is specified by `oPatchLocation`. + * Directory, where unzipped One-off patch files are available, is specified by `oneOffLocation`. + * Specify Comma-separated one-off patch IDs to be applied to the GI HOME using `gridOneOffIds`. For Example: `gridOneOffIds: "38336965,34436514"` + * Specify Comma-separated one-off patch IDs to be applied to the RDBMS HOME using `dbOneOffIds`. For Example: `dbOneOffIds: "38336965,34436514"` + +### In this example, + * We are using Oracle Restart Database slim image by building it from Git location(./https://orahub.oci.oraclecorp.com/rac-docker-dev/rac-docker-images/-/blob/master/OracleRealApplicationClusters/README.md#building-oracle-rac-database-container-slim-image) i.e. `dbocir/oracle/database-rac:19.3.0-slim`. To use this in your in own environment, update the image value in the `oraclerestart_prov_rupatch_oneoff_storageclass.yaml` file to point to your own container registry base container image. + * The disks provisioned using customer storage class (specified by `crsDgStorageClass`) for the Oracle Restart storage are `/dev/asm-disk1` and `/dev/asm-disk2`. + * Specify the size of these devices along with names using the parameter `storageSizeInGb`. Size is by-default in GBs. + +**NOTE:** When no separate diskgroup names are specified for CRS Files, Database Files, Recovery Area Files and Redo Log Files, then the default diskgroup named `+DATA` is created from the disks specified by the parameter `crsAsmDeviceList`. + +### Steps: Deploy Oracle Restart Database +* Use the file: [oraclerestart_prov_rupatch_oneoff_storageclass.yaml](./oraclerestart_prov_rupatch_oneoff_storageclass.yaml) for this use case as below: +* Deploy the `oraclerestart_prov_rupatch_oneoff_storageclass.yaml` file: + ```sh + kubectl apply -f oraclerestart_prov_rupatch_oneoff_storageclass.yaml + oraclerestart.database.oracle.com/oraclerestart-sample created + ``` +* Check the status of the deployment: + ```sh + # Check the status of the Kubernetes Pods: + kubectl get all -n orestart + + # Check the logs of a particular pod. For example, to check status of pod "dbmc1-0": + kubectl exec -it pod/dbmc1-0 -n orestart -- bash -c "tail -f /tmp/orod/oracle_db_setup.log" + =============================== + ORACLE DATABASE IS READY TO USE + =============================== + ``` +* Check Details of Kubernetes CRD Object as in this [example](./orestart_db_rupatch_oneoffs_object.txt) diff --git a/docs/oraclerestart/provisioning/provisioning_oracle_restart_multiple_diskgroups.md b/docs/oraclerestart/provisioning/provisioning_oracle_restart_multiple_diskgroups.md new file mode 100644 index 00000000..0cc3800d --- /dev/null +++ b/docs/oraclerestart/provisioning/provisioning_oracle_restart_multiple_diskgroups.md @@ -0,0 +1,42 @@ +# Provisioning an Oracle Restart Database with multiple diskgroups +### In this Usecase: +* In this use case, the Oracle Grid Infrastructure and Oracle Restart Database are deployed automatically using Oracle Restart Controller. In this example, multiple diskgroups are created for CRS Files, Database Files and Recovery Area Files. +* This example uses `oraclerestart_prov_multiple_diskgroups.yaml` to provision an Oracle Database configured with Oracle Restart using Oracle Restart Controller. The provisioning includes: + * Oracle Restart Database Pod + * Headless services for Oracle Restart. + * Oracle Database Node hostname. + * Persistent volumes created automatically based on specified disks for Oracle ASM Storage. + * Software Persistent Volume and Staged Software Persistent Volume using the specified location on the corresponding worker node. + * Namespace: `orestart` + * Staged Software location on the worker nodes is specified by `hostSwStageLocation`. The Grid Infrastructure and RDBMS Binaries are copied to this location on the worker node. + * Software location on the worker nodes is specified by `hostSwLocation`. The GI HOME and the RDBMS HOME in the Oracle Restart Pod will be mounted using this location on the worker node. +### In this Example: + * Oracle Restart Database Slim Image `dbocir/oracle/database-orestart:19.3.0-slim` is used and it is built using files from [GitHub location](https://github.com/oracle/docker-images/tree/main/OracleDatabase/RAC/OracleRealApplicationClusters#building-oracle-rac-database-container-slim-image). Default image created using files from this project is `localhost/oracle/database-rac:19.3.0-slim`. You need to tag it with name `dbocir/oracle/database-orestart:19.3.0-slim`. + * When you are building the image yourself, update the image value in the `oraclerestart_prov_multiple_diskgroups.yaml` file to point to the container image you have built. + * The disks on the worker nodes for the Oracle Restart storage are `/dev/disk/by-partlabel/asm-disk1` to`/dev/disk/by-partlabel/asm-disk8`. + * Specify the size of these devices along with names using the parameter `storageSizeInGb`. Size is by-default in GBs. + * The Diskgroup for CRS files is specified by `crsAsmDiskDg` and the disks on the worker nodes for this diskgroup are specified by `crsAsmDeviceList`. + * The Diskgroup for Database files is specified by `dbDataFileDestDg` and the disks on the worker nodes for this diskgroup are specified by `dbAsmDeviceList`. + * The Diskgroup for Recovery Area files is specified by `dbRecoveryFileDest` and the disks on the worker nodes for this diskgroup are specified by `recoAsmDeviceList`. + * The Diskgroup for Redo Log files is specified by `redoAsmDiskDg` and the disks on the worker nodes for this diskgroup are specified by `redoAsmDeviceList`. + +### Steps: Deploy Oracle Restart Database +* Use the file: [oraclerestart_prov_multiple_diskgroups.yaml](./oraclerestart_prov_multiple_diskgroups.yaml) for this use case as below: +* Deploy the `oraclerestart_prov_multiple_diskgroups.yaml` file: + ```sh + kubectl apply -f oraclerestart_prov_multiple_diskgroups.yaml + oraclerestart.database.oracle.com/oraclerestart-sample created + ``` +* Check the status of the deployment: + ```sh + # Check the status of the Kubernetes Pods: + kubectl get all -n orestart + + # Check the logs of a particular pod. For example, to check status of pod "dbmc1-0": + kubectl exec -it pod/dbmc1-0 -n orestart -- bash -c "tail -f /tmp/orod/oracle_db_setup.log" + =============================== + ORACLE DATABASE IS READY TO USE + =============================== + ``` +* Check Details of Kubernetes CRD Object as in this [example](./oraclerestart_prov_multiple_diskgroups.txt) +* Refer to the page [Database Connection](./database_connection.md) for the details to connect to Oracle Restart Database deployed using above example. diff --git a/docs/oraclerestart/provisioning/provisioning_oracle_restart_multiple_diskgroups_with_redundancy.md b/docs/oraclerestart/provisioning/provisioning_oracle_restart_multiple_diskgroups_with_redundancy.md new file mode 100644 index 00000000..ecd09b16 --- /dev/null +++ b/docs/oraclerestart/provisioning/provisioning_oracle_restart_multiple_diskgroups_with_redundancy.md @@ -0,0 +1,48 @@ +# Provisioning an Oracle Restart Database with multiple diskgroups with different redundancy +# In this Usecase: +The Oracle Grid Infrastructure and Oracle Restart Database are deployed automatically using Oracle Restart Controller. In this example, multiple diskgroups are created for CRS Files, Database Files, Recovery Area Files and Redo Log Files. Different Disk Groups have different redundancy levels. + +This example uses `oraclerestart_prov_multiple_diskgroups_with_redundancy.yaml` to provision an Oracle Database configured with Oracle Restart using Oracle Restart Controller. The provisioning includes: + * Oracle Restart Pod + * Headless services for Oracle Restart. + * Oracle Database Node hostname. + * Persistent volumes created automatically based on specified disks for Oracle ASM Storage. + * Software Persistent Volume and Staged Software Persistent Volume using the specified location on the corresponding worker node. + * Namespace: `orestart` + * Staged Software location on the worker nodes is specified by `hostSwStageLocation`. The Grid Infrastructure and RDBMS Binaries are copied to this location on the worker node. + * Software location on the worker nodes is specified by `hostSwLocation`. The GI HOME and the RDBMS HOME in the Oracle Restart Pod will be mounted using this location on the worker node. + +### In this example, + * Oracle Restart Database Slim Image `dbocir/oracle/database-orestart:19.3.0-slim` is used and it is built using files from [GitHub location](https://github.com/oracle/docker-images/tree/main/OracleDatabase/RAC/OracleRealApplicationClusters#building-oracle-rac-database-container-slim-image). Default image created using files from this project is `localhost/oracle/database-rac:19.3.0-slim`. You need to tag it with name `localhost/oracle/database-orestart:19.3.0-slim`. + * When you are building the image yourself, update the image value in the `oraclerestart_prov_multiple_diskgroups_with_redundancy.yaml` file to point to the container image you have built. + * The disks on the worker nodes for the Oracle Restart storage are `/dev/disk/by-partlabel/asm-disk1` to`/dev/disk/by-partlabel/asm-disk10`. + * Specify the size of disk devices along with names using the parameter `storageSizeInGb`. Size is by-default in GBs. + * The Diskgroup for CRS files is specified by `crsAsmDiskDg` and the disks on the worker nodes for this diskgroup are specified by `crsAsmDeviceList`. + * The Diskgroup for Database files is specified by `dbDataFileDestDg` and the disks on the worker nodes for this diskgroup are specified by `dbAsmDeviceList`. + * The Diskgroup for Recovery Area files is specified by `dbRecoveryFileDest` and the disks on the worker nodes for this diskgroup are specified by `recoAsmDeviceList`. + * The Diskgroup for Redo Log files is specified by `redoAsmDiskDg` and the disks on the worker nodes for this diskgroup are specified by `redoAsmDeviceList`. + * Redundancy level for the diskgroup with CRS files is mentioned by `crsAsmDiskDgRedundancy`. + * Redundancy level for the diskgroup with Database files is mentioned by `dbAsmDiskDgRedundancy`. + * Redundancy level for the diskgroup with Recovery files is mentioned by `recoAsmDiskDgRedudancy`. + * Redundancy level for the diskgroup with Redo Log files is mentioned by `redoAsmDiskDgRedundancy`. + +### Steps: Deploy Oracle Restart Database +* Use the file: [oraclerestart_prov_multiple_diskgroups_with_redundancy.yaml](./oraclerestart_prov_multiple_diskgroups_with_redundancy.yaml) for this use case as below: +* Deploy the `oraclerestart_prov_multiple_diskgroups_with_redundancy.yaml` file: + ```sh + kubectl apply -f oraclerestart_prov_multiple_diskgroups_with_redundancy.yaml + oraclerestart.database.oracle.com/oraclerestart-sample created + ``` +* Check the status of the deployment: + ```sh + # Check the status of the Kubernetes Pods: + kubectl get all -n orestart + + # Check the logs of a particular pod. For example, to check status of pod "dbmc1-0": + kubectl exec -it pod/dbmc1-0 -n orestart -- bash -c "tail -f /tmp/orod/oracle_db_setup.log" + =============================== + ORACLE DATABASE IS READY TO USE + =============================== + ``` +* Check Details of Kubernetes CRD Object as in this [example](./oraclerestart_prov_multiple_diskgroups_with_redundancy.txt) +* Refer to the page [Database Connection](./database_connection.md) for the details to connect to Oracle Restart Database deployed using above example. diff --git a/docs/oraclerestart/provisioning/provisioning_oracle_restart_rupatch_pvc.md b/docs/oraclerestart/provisioning/provisioning_oracle_restart_rupatch_pvc.md new file mode 100644 index 00000000..d964eb66 --- /dev/null +++ b/docs/oraclerestart/provisioning/provisioning_oracle_restart_rupatch_pvc.md @@ -0,0 +1,47 @@ +# Provisioning an Oracle Restart Database with RU Patch on Existing PVC + +### In this Usecase: +* The Oracle Grid Infrastructure and Oracle Restart Database are deployed automatically using Oracle Restart Controller. In this case, no storage location from the worker node is used for ASM Disks or GI HOME or RDBMS HOME or Software Staging etc. +* This example uses `oraclerestart_prov_rupatch_pvc.yaml` to provision an Oracle Restart Database using Oracle Restart Controller with: + * Oracle Restart Database pod + * Headless services for Oracle Restart + * Oracle Restart Node hostname + * Node Port 30007 mapped to port 1521 for Database Listener + * Persistent volumes created automatically based on specified disks for Oracle ASM storage. + * Staged Software location using a mount location which is using a pre created Persistent Volume from a network file system(NFS) + * Namespace: `orestart` + * Mount point for the Software Stage Location inside the Pod is specified by `swStagePvcMountLocation`. + * Staged Software location inside the Pod is specified by `hostSwStageLocation`. It is assumed that the Grid Infrastructure and RDBMS Binaries are already available in this path on the Persistent Volume used. + * The GI HOME and the RDBMS HOME in the Oracle Restart Pod are mounted using a Persistent Volume created using the Storage Class (specified using `swDgStorageClass`). This Persistent Volume is mounted to `/u01` inside the Pod. Size of this Persistent Volume is specified using `swLocStorageSizeInGb`. + * Location where the `RU Patch` has been unzipped on the mounted PV is specified by `ruPatchLocation`. + * Path to the Opatch Software compatible with the RU Patch is specified using `oPatchLocation`. + +### In this Example: + * Oracle Restart Database Slim Image `dbocir/oracle/database-orestart:19.3.0-slim` is used and it is built using files from [GitHub location](https://github.com/oracle/docker-images/tree/main/OracleDatabase/RAC/OracleRealApplicationClusters#building-oracle-rac-database-container-slim-image). Default image created using files from this project is `localhost/oracle/database-rac:19.3.0-slim`. You need to tag it with name `dbocir/oracle/database-orestart:19.3.0-slim`. + * When you are building the image yourself, update the image value in the `oraclerestart_prov_rupatch_pvc.yaml` file to point to the container image you have built. + * Use the file [nfs_pv_stage_vol.yaml](./nfs_pv_stage_vol.yaml) to mount the Network File System as a Persistent Volume named `pv-stage-vol1`. It is assumed this NFS has the required GI and RDBMS Base Software, unzipped RU Patch binaries and Opatch binaries in the specified location. In current case, an OCI File System is used with its export path as `/stage` and Mount Target IP as `10.0.10.212`. + * The disks provisioned using customer storage class (specified by `crsDgStorageClass`) for the Oracle Restart storage are `/dev/asm-disk1` and `/dev/asm-disk2`. + * Specify the size of these devices using the parameter `storageSizeInGb`. Size is by-default in GBs. + +**NOTE:** When no separate diskgroup names are specified for CRS Files, Database Files, Recovery Area Files and Redo Log Files, then the default diskgroup named `+DATA` is created from the disks specified by the parameter `crsAsmDeviceList`. + +### Steps: Deploy Oracle Restart Database +* Use the file: [oraclerestart_prov_rupatch_pvc.yaml](./oraclerestart_prov_rupatch_pvc.yaml) for this use case as below: +* Deploy the `oraclerestart_prov_rupatch_pvc.yaml` file: + ```sh + kubectl apply -f oraclerestart_prov_rupatch_pvc.yaml + oraclerestart.database.oracle.com/oraclerestart-sample created + ``` +* Check the status of the deployment: + ```sh + # Check the status of the Kubernetes Pods: + kubectl get all -n orestart + + # Check the logs of a particular pod. For example, to check status of pod "dbmc1-0": + kubectl exec -it pod/dbmc1-0 -n orestart -- bash -c "tail -f /tmp/orod/oracle_db_setup.log" + =============================== + ORACLE DATABASE IS READY TO USE + =============================== + ``` +* Check Details of Kubernetes CRD Object as in this [example](./orestart_rupatch_pvc_object.txt) +* Refer to the page [Database Connection](./database_connection.md) for the details to connect to Oracle Restart Database deployed using above example. diff --git a/docs/oraclerestart/provisioning/provisioning_oracle_restart_storage_class.md b/docs/oraclerestart/provisioning/provisioning_oracle_restart_storage_class.md new file mode 100644 index 00000000..acf95d4e --- /dev/null +++ b/docs/oraclerestart/provisioning/provisioning_oracle_restart_storage_class.md @@ -0,0 +1,42 @@ +# Provisioning an Oracle Restart Database with Custom Storage Class +### In this Usecase: +* The Oracle Grid Infrastructure and Oracle Restart Database are deployed automatically using Oracle Restart Controller with Custom Storage Class. +* In this use case, the ASM Disks are provisioned as Persistent Volumes using custom storage class during the deployment. +* This example uses `oraclerestart_prov_storage_class.yaml` to provision an Oracle Restart Database using Oracle Restart Controller with: + * Oracle Restart Pod + * Headless services for Oracle Restart + * Oracle Restart node hostname + * Node Port 30007 mapped to port 1521 for Database Listener + * Persistent volumes for ASM Disks created automatically using the Storage Class for Oracle ASM storage + * Software Persistent Volume and Staged Software Persistent Volume using the specified location on the corresponding worker node + * Namespace: `orestart` + * Staged Software location on the worker nodes is specified by `hostSwStageLocation`. The Grid Infrastructure and RDBMS Binaries are copied to this location on the worker node. + * The GI HOME and the RDBMS HOME in the Oracle Restart Pod are mounted using a Persistent Volume created using the Storage Class (specified using `storageClass`). This Persistent Volume is mounted to `/u01` inside the Pod. Size of this Persistent Volume is specified using `swLocStorageSizeInGb`. + +### In this Example: + * Oracle Restart Database Slim Image `localhost/oracle/database-orestart:19.3.0-slim` is used and it is built using files from [GitHub location](https://github.com/oracle/docker-images/tree/main/OracleDatabase/RAC/OracleRealApplicationClusters#building-oracle-rac-database-container-slim-image). Default image created using files from this project is `localhost/oracle/database-rac:19.3.0-slim`. You need to tag it with name `localhost/oracle/database-orestart:19.3.0-slim`. + * When you are building the image yourself, update the image value in the `oraclerestart_prov_storage_class.yaml` file to point to the container image you have built. + * The disks provisioned using customer storage class (specified by `crsDgStorageClass`) for the Oracle Restart storage are `/dev/asm-disk1` and `/dev/asm-disk2`. + * Specify the size of these devices along with names using the parameter `storageSizeInGb`. Size is by-default in GBs. + +**NOTE:** When no separate diskgroup names are specified for CRS Files, Database Files, Recovery Area Files and Redo Log Files, then the default diskgroup named `+DATA` is created from the disks specified by the parameter `crsAsmDeviceList`. + +### Steps: Deploy Oracle Restart Database +* Use the file: [oraclerestart_prov_storage_class.yaml](./oraclerestart_prov_storage_class.yaml) for this use case as below: +* Deploy the `oraclerestart_prov_storage_class.yaml` file: + ```sh + kubectl apply -f oraclerestart_prov_storage_class.yaml + oraclerestart.database.oracle.com/oraclerestart-sample created + ``` +* Check the status of the deployment: + ```sh + # Check the status of the Kubernetes Pods: + kubectl get all -n orestart + + # Check the logs of a particular pod. For example, to check status of pod "dbmc1-0": + kubectl exec -it pod/dbmc1-0 -n orestart -- bash -c "tail -f /tmp/orod/oracle_db_setup.log" + =============================== + ORACLE DATABASE IS READY TO USE + =============================== + ``` +* Check Details of Kubernetes CRD Object as in this [example](./orestart_storage_class_object.txt) diff --git a/docs/ordsservices/README.md b/docs/ordsservices/README.md index e2fa97be..d012d4ef 100644 --- a/docs/ordsservices/README.md +++ b/docs/ordsservices/README.md @@ -1,10 +1,10 @@ -# Oracle Rest Data Services (ORDSSRVS) Controller for Kubernetes - ORDS Life cycle management +# Oracle Rest Data Services (OrdsSrvs) Controller for Kubernetes - ORDS Life cycle management ## Description -The ORDSRVS controller extends the Kubernetes API with a Custom Resource (CR) and Controller for automating Oracle Rest Data -Services (ORDS) lifecycle management. Using the ORDS controller, you can easily migrate existing, or create new, ORDS implementations +The OrdsSrvs controller extends the Kubernetes API with a Custom Resource (CR) and Controller for automating Oracle Rest Data +Services (ORDS) lifecycle management. Using the OrdsSrvs controller, you can easily migrate existing, or create new, ORDS implementations into an existing Kubernetes cluster. This controller allows you to run what would otherwise be an On-Premises ORDS middle-tier, configured as you require, inside Kubernetes with the additional ability of the controller to perform automatic ORDS/APEX install/upgrades inside the database. @@ -17,55 +17,92 @@ The custom RestDataServices resource supports the following configurations as a * Single OrdsSrvs resource with multiple database pools* * Multiple OrdsSrvs resources, each with one database pool * Multiple OrdsSrvs resources, each with multiple database pools* +* ORDS and APEX database schemas [automatic installation/upgrade](./autoupgrade.md) *See [Limitations](#limitations) -It supports the majority of ORDS configuration settings as per the [API Documentation](./api.md). +ORDS Version supported : 25.1.0+ +OrdsSrvs controller supports the majority of ORDS configuration settings as per the [API Documentation](./api.md). -The ORDS and APEX schemas can be [automatically installed/upgraded](./autoupgrade.md) into the Oracle Database by the ORDS controller. -ORDS Version support: -* 24.1.1 -(Newer versions of ORDS will be supported in the next update of OraOperator) +## Prerequisites -Oracle Database Version: -* 19c -* 23ai (incl. 23ai Free) + This chapter outlines the necessary requirements that must be satisfied to successfully deploy and operate the OrdsSrvs controller within your Kubernetes cluster. -### Prerequisites +### Oracle Database Operator -1. Oracle Database Operator +Before installing the OrdsSrvs controller, ensure that the Oracle Database Operator (OraOperator) is installed in your Kubernetes environment. Please follow the detailed installation steps provided in the [README](https://github.com/oracle/oracle-database-operator/blob/main/README.md) to complete this process. The OraOperator must be properly configured and running, as OrdsSrvs depends on its services for functionality. - Install the Oracle Database Operator (OraOperator) using the instructions in the [README](https://github.com/oracle/oracle-database-operator/blob/main/README.md) file. -1. Namespace +### Namespace Namespace Scoped Deployment - For a dedicated namespace deployment of the ORDSSRVS controller, refer to the "Namespace Scoped Deployment" section in the OraOperator [README](https://github.com/oracle/oracle-database-operator/blob/main/README.md#2-namespace-scoped-deployment). +For a dedicated namespace deployment of the OrdsSrvs controller, refer to the "Namespace Scoped Deployment" section in the OraOperator [README](https://github.com/oracle/oracle-database-operator/blob/main/README.md#2-namespace-scoped-deployment). +The following examples demonstrate deploying the controller to the ordsnamespace namespace. - The following examples deploy the controller to the 'ordsnamespace' namespace. +Create the namespace: - Create the namespace: - ```bash - kubectl create namespace ordsnamespace - ``` +```bash +kubectl create namespace ordsnamespace +``` - Apply namespace role binding [ordsnamespace-role-binding.yaml](./examples/ordsnamespace-role-binding.yaml): - ```bash - kubectl apply -f ordsnamespace-role-binding.yaml - ``` +Apply namespace role binding [ordsnamespace-role-binding.yaml](./ordsnamespace-role-binding.yaml): - Edit OraOperator to add the namespace under WATCH_NAMESPACE: - ```yaml - - name: WATCH_NAMESPACE +```bash +kubectl apply -f ordsnamespace-role-binding.yaml +``` + +Edit OraOperator to add the namespace under WATCH_NAMESPACE: +```yaml + - name: WATCH_NAMESPACE value: "default,,ordsnamespace" - ``` +``` -### Common configuration examples +## OpenShift Security Context Constraints -A few common configuration examples can be used to quickly familiarise yourself with the ORDS Custom Resource Definition. -The "Conclusion" section of each example highlights specific settings to enable functionality that maybe of interest. +If you are deploying the OrdsSrvs controller on OpenShift, ensure that the appropriate Security Context Constraints (SCCs) are configured. This involves assigning privileged SCCs to the service accounts used by OrdsSrvs to permit required operations. + +### Create a Service Account + +This account will be used to assign the necessary Security Context Constraints (SCCs) for the controller’s operation. +Below is an example [YAML](./examples/ordssrvs-sa.yaml) manifest to create a service account named "ordssrvs-sa" in the "ordsnamespace" namespace: + +```yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: ordssrvs-sa + namespace: ordsnamespace +``` + +### Create a Custom Security Context Constraint (SCC) + +To configure the required security permissions, use the attached [YAML](./examples/ordssrvs-sa-scc.yaml) file to create a custom Security Context Constraint (SCC) and bind it to the "ordssrvs-sa" service account. +This will ensure the service account has the necessary permissions for the OrdsSrvs controller to operate on OpenShift. + +### Set serviceAccountName in OrdsSrvs + +Ensure that the OrdsSrvs controller uses the dedicated service account you created. In the deployment manifest for OrdsSrvs, specify the serviceAccountName field with the name of your service account (e.g., ordssrvs-sa) as in this [example](./examples/ordssrvs.yaml). -Before +```yaml +apiVersion: database.oracle.com/v4 +kind: OrdsSrvs +metadata: + name: ordssrvs + namespace: ordsnamespace +spec: + ... + globalSettings: + ... + poolSettings: + ... + serviceAccountName: ordssrvs-sa +``` + + +## Common configuration examples + +A few common configuration examples can be used to quickly familiarise yourself with the OrdsSrvs Custom Resource Definition. +The "Conclusion" section of each example highlights specific settings to enable functionality that maybe of interest. * [Pre-existing Database](./examples/existing_db.md) * [Containerised Single Instance Database (SIDB)](./examples/sidb_container.md) @@ -73,16 +110,31 @@ Before * [Autonomous Database using the OraOperator](./examples/adb_oraoper.md) *See [Limitations](#limitations) * [Autonomous Database without the OraOperator](./examples/adb.md) * [Oracle API for MongoDB Support](./examples/mongo_api.md) +* [ORDS and APEX database schemas automatic installation/upgrade](./autoupgrade.md) Running through all examples in the same Kubernetes cluster illustrates the ability to run multiple ORDS instances with a variety of different configurations. -If you have a specific use-case that is not covered and would like it to be feel free to contribute it via a Pull Request. -### Limitations +## Change Log + +### Version 2.0 + + - ORDS image 25.1+ + - OpenShift installation + - APEX installation files from download url or PersistenceVolume + - BUGFIX [#181 enhancement, init script refactoring and improved logging](https://github.com/oracle/oracle-database-operator/issues/181) + - BUGFIX [#186 autoUpgradeORDS: true and autoUpgradeAPEX: false not working](https://github.com/oracle/oracle-database-operator/issues/186) + - BUGFIX [#188 int64 for configuration settings of type Duration](https://github.com/oracle/oracle-database-operator/issues/188) + +### Upgrading from 1.2 to 2.0 +Password secrets must be recreated when upgrading the operator from version 1.2 to 2.0. +Secrets can be recreated either before or after the operator upgrade. Restarting the OrdsSrvs deployment is not required for this procedure; however, if the deployment does need to be restarted for any reason, ensure secrets are recreated first. + +## Limitations When connecting to a mTLS enabled ADB and using the Oracontroller to retreive the Wallet, it is currently not supported to have multiple, different databases supported by the single RestDataServices resource. This is due to a requirement to set the `TNS_ADMIN` parameter at the Pod level ([#97](https://github.com/oracle/oracle-database-controller/issues/97)). -### Troubleshooting +## Troubleshooting See [Troubleshooting](./TROUBLESHOOTING.md) ## Contributing diff --git a/docs/ordsservices/api.md b/docs/ordsservices/api.md index da4db09c..4268e98c 100644 --- a/docs/ordsservices/api.md +++ b/docs/ordsservices/api.md @@ -291,14 +291,32 @@ Contains settings that are configured across the entire ORDS instance. Format: int64
false - + + debug.printDebugToScreen boolean Specifies whether to display error messages on the browser.
false - + + + downloadAPEX + boolean + + Specifies whether to downloan APEX installation files.
+ + false + + + downloadUrlAPEX + string + + Specifies the URL to downloan APEX installation files.
+ + https://download.oracle.com/otn_software/apex/apex-latest.zip + + enable.mongo.access.log boolean diff --git a/docs/ordsservices/autoupgrade.md b/docs/ordsservices/autoupgrade.md index fddc30b3..55d431cb 100644 --- a/docs/ordsservices/autoupgrade.md +++ b/docs/ordsservices/autoupgrade.md @@ -1,10 +1,117 @@ -# AutoUpgrade +# ORDS and APEX AutoUpgrade -Each pool can be configured to automatically install and upgrade the ORDS and/or APEX schemas in the database. -The ORDS and APEX version is based on the ORDS image used for the RestDataServices resource. +Each pool can be configured to automatically install and upgrade the ORDS and/or APEX schemas in the database. -For example, in the below manifest: -* `Pool: pdb1` is configured to automatically install/ugrade both ORDS and APEX to version 24.1.0 +## ORDS autoUpgrade + +The ORDS version is determined by the ORDS image used for the RestDataServices resource. +ORDS schema installation and upgrade can be activated at the pool level: + +```yaml +apiVersion: database.oracle.com/v1 +kind: OrdsSrvs +metadata: + name: ordspoc-server +spec: + ... + poolSettings: + - poolName: pdb1 + autoUpgradeORDS: true +``` + +## APEX autoUpgrade + +ORDS image does **not** contain APEX installation files. +APEX installation files can be provided to the pod in two ways: + + - automatic download + - external storage (PersistenceVolume) + + +### APEX installation automatic download + +The ORDS container can download the latest APEX version either from "Oracle APEX Downloads" or a specified custom URL. +To download APEX installation files, the Kubernetes worker node must have internet access. +The APEX download is defined globally, and upgrades can be enabled or disabled for each pool individually. + +```yaml +apiVersion: database.oracle.com/v1 +kind: OrdsSrvs +metadata: + name: ordspoc-server +spec: + ... + globalSettings: + downloadAPEX : true + downloadUrlAPEX : https://download.oracle.com/otn_software/apex/apex_24.2.zip + encPrivKey: + ... + poolSettings: + - poolName: pdb1 + autoUpgradeAPEX: true + ... + - poolName: pdb2 + autoUpgradeAPEX: false + ... +``` + +If you do not specify a download URL (downloadUrlAPEX), the default value is used: +https://download.oracle.com/otn_software/apex/apex-latest.zip + + +### APEX installation files on external storage + +Alternatively, you can provide APEX installation files in a dedicated PersistentVolume containing a single apex.zip file. + +You can download apex.zip from: +https://www.oracle.com/tools/downloads/apex-downloads/ + +```yaml +apiVersion: database.oracle.com/v4 +kind: OrdsSrvs +metadata: + name: ordssrvs + namespace: testcase +spec: + ... + globalSettings: + apex.download : false + apex.installation.persistence: + volumeName : apexpv + storageClass : + size : 20Gi + accessMode : ReadWriteMany + ... + poolSettings: + - poolName: default + autoUpgradeAPEX: true +``` + +The OrdsSrvs controller will create a PersistentVolumeClaim (PVC) for the PV and mount it in the pod’s container at /opt/oracle/apex. + +The volume can be static or dynamic. If the volume is empty, the init container will wait until it finds apex.zip at the mount point. +The init container logs the following message: + +``` bash +Missing /opt/oracle/apex/apex.zip, manually copy apex.zip in /opt/oracle/apex on the init container of the pod +``` + +You can copy the apex.zip file into the container while the init script is waiting: + +``` bash +kubectl cp /tmp/apex.zip :/tmp -c ordssrvs-init -n ordsnamespace +kubectl exec -c ordssrvs-init -n ordsnamespace -- mv /tmp/apex.zip /opt/oracle/apex +``` + + + +## Example: ORDS autoUpgrade and APEX download/autoUpgrade + +In the following manifest example: + +* APEX installation files will be downloaded from latest version. +* `Pool: pdb1` is configured to automatically install/ugrade both ORDS and APEX to version 25.1.0 +* `Pool: pdb2` will install or upgrade ORDS * `Pool: pdb2` will not install or upgrade ORDS/APEX As an additional requirement for `Pool: pdb1`, the `spec.poolSettings.db.adminUser` and `spec.poolSettings.db.adminUser.secret` @@ -16,10 +123,12 @@ kind: OrdsSrvs metadata: name: ordspoc-server spec: - image: container-registry.oracle.com/database/ords:24.1.0 + image: container-registry.oracle.com/database/ords:25.1.0 forceRestart: true globalSettings: database.api.enabled: true + downloadAPEX : true + downloadUrlAPEX : https://download.oracle.com/otn_software/apex/apex_24.2.zip encPrivKey: secretName: prvkey passwordKey: privateKey @@ -35,12 +144,20 @@ spec: db.adminUser.secret: secretName: pdb1-sys-auth-enc - poolName: pdb2 + autoUpgradeORDS: true db.connectionType: customurl db.customURL: jdbc:oracle:thin:@//localhost:1521/PDB2 db.secret: secretName: pdb2-ords-auth-enc + - poolName: pdb3 + db.connectionType: customurl + db.customURL: jdbc:oracle:thin:@//localhost:1521/PDB3 + db.secret: + secretName: pdb3-ords-auth-enc ``` + + ## Minimum Privileges for Admin User The `db.adminUser` must have privileges to create users and objects in the database. For Oracle Autonomous Database (ADB), this could be `ADMIN` while for diff --git a/docs/ordsservices/examples/adb.md b/docs/ordsservices/examples/adb.md index 90a21b5c..ab87a3b5 100644 --- a/docs/ordsservices/examples/adb.md +++ b/docs/ordsservices/examples/adb.md @@ -21,13 +21,13 @@ kubectl create secret generic adb-wallet \ Create a Secret for the ADB ADMIN password, replacing with the real password: ```bash -echo ${ADMIN_PASSWORD} > adb-db-auth-enc -openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_keygen_pubexp:65537 > ca.key +echo ${ADMIN_PASSWORD} > db-auth +openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_keygen_pubexp:65537 > ca.key openssl rsa -in ca.key -outform PEM -pubout -out public.pem kubectl create secret generic prvkey --from-file=privateKey=ca.key -n ordsnamespace -openssl rsautl -encrypt -pubin -inkey public.pem -in adb-db-auth-enc |base64 > e_adb-db-auth-enc -kubectl create secret generic adb-oraoper-db-auth-enc --from-file=password=e_adb-db-auth-enc -n ordsnamespace -rm adb-db-auth-enc e_adb-db-auth-enc +openssl pkeyutl -encrypt -pubin -inkey public.pem -in db-auth -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 |base64 > e_db-auth +kubectl create secret generic adb-oraoper-db-auth-enc --from-file=password=e_db-auth -n ordsnamespace +rm db-auth e_db-auth ``` ### Create RestDataServices Resource @@ -46,7 +46,7 @@ rm adb-db-auth-enc e_adb-db-auth-enc name: ords-adb namespace: ordsnamespace spec: - image: container-registry.oracle.com/database/ords:24.1.1 + image: container-registry.oracle.com/database/ords:25.1.0 forceRestart: true encPrivKey: secretName: prvkey @@ -71,7 +71,7 @@ rm adb-db-auth-enc e_adb-db-auth-enc db.adminUser.secret: secretName: adb-oraoper-db-auth-enc ``` - latest container-registry.oracle.com/database/ords version, **24.1.1**, valid as of **30-May-2024** + latest container-registry.oracle.com/database/ords version, **25.1.0**, valid as of **26-May-2025** 1. Watch the restdataservices resource until the status is **Healthy**: ```bash diff --git a/docs/ordsservices/examples/adb_oraoper.md b/docs/ordsservices/examples/adb_oraoper.md index 253365c5..36180194 100644 --- a/docs/ordsservices/examples/adb_oraoper.md +++ b/docs/ordsservices/examples/adb_oraoper.md @@ -74,13 +74,13 @@ kubectl create secret generic adb-oraoper-db-auth \ ### Create encrypted password ```bash -echo ${DB_PWD} > adb-db-auth-enc +echo ${DB_PWD} > db-auth openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_keygen_pubexp:65537 > ca.key openssl rsa -in ca.key -outform PEM -pubout -out public.pem kubectl create secret generic prvkey --from-file=privateKey=ca.key -n ordsnamespace -openssl rsautl -encrypt -pubin -inkey public.pem -in adb-db-auth-enc |base64 > e_adb-db-auth-enc -kubectl create secret generic adb-oraoper-db-auth-enc --from-file=password=e_adb-db-auth-enc -n ordsnamespace -rm adb-db-auth-enc e_adb-db-auth-enc +openssl pkeyutl -encrypt -pubin -inkey public.pem -in db-auth -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 |base64 > e_db-auth +kubectl create secret generic adb-oraoper-db-auth-enc --from-file=password=e_db-auth -n ordsnamespace +rm db-auth e_db-auth ``` ### Create OrdsSrvs Resource @@ -103,7 +103,7 @@ rm adb-db-auth-enc e_adb-db-auth-enc name: ords-adb-oraoper namespace: ordsnamespace spec: - image: container-registry.oracle.com/database/ords:24.1.1 + image: container-registry.oracle.com/database/ords:25.1.0 forceRestart: true encPrivKey: secretName: prvkey @@ -126,7 +126,7 @@ rm adb-db-auth-enc e_adb-db-auth-enc db.adminUser.secret: secretName: adb-oraoper-db-auth-enc ``` - latest container-registry.oracle.com/database/ords version, **24.1.1**, valid as of **30-May-2024** + latest container-registry.oracle.com/database/ords version, **25.1.0**, valid as of **26-May-2025** 1. Watch the ordssrvs resource until the status is **Healthy**: ```bash diff --git a/docs/ordsservices/examples/existing_db.md b/docs/ordsservices/examples/existing_db.md index 6d4791ae..a43849e2 100644 --- a/docs/ordsservices/examples/existing_db.md +++ b/docs/ordsservices/examples/existing_db.md @@ -16,17 +16,14 @@ export CONN_STRING=:/ ```bash DB_PWD= - openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_keygen_pubexp:65537 > ca.key openssl rsa -in ca.key -outform PEM -pubout -out public.pem kubectl create secret generic prvkey --from-file=privateKey=ca.key -n ordsnamespace echo "${DB_PWD}" > db-auth -openssl rsautl -encrypt -pubin -inkey public.pem -in db-auth |base64 > e_db-auth-enc -kubectl create secret generic db-auth-enc --from-file=password=e_db-auth-enc -n ordsnamespace - -rm db-auth e_db-auth-enc - +openssl pkeyutl -encrypt -pubin -inkey public.pem -in db-auth -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 |base64 > e_db-auth +kubectl create secret generic db-auth-enc --from-file=password=e_db-auth -n ordsnamespace +rm db-auth e_db-auth ``` ### Create ordssrvs Resource @@ -49,7 +46,7 @@ rm db-auth e_db-auth-enc name: ords-db namespace: ordsnamespace spec: - image: container-registry.oracle.com/database/ords:24.1.1 + image: container-registry.oracle.com/database/ords:25.1.0 forceRestart: true encPrivKey: secretName: prvkey @@ -74,7 +71,7 @@ rm db-auth e_db-auth-enc kubectl apply -f ords-db.yaml ``` - latest container-registry.oracle.com/database/ords version, **24.1.1**, valid as of **30-May-2024** + latest container-registry.oracle.com/database/ords version, **25.1.0**, valid as of **26-May-2025** 1. Watch the restdataservices resource until the status is **Healthy**: ```bash diff --git a/docs/ordsservices/examples/mongo_api.md b/docs/ordsservices/examples/mongo_api.md index f0fd0cf5..c7ea2695 100644 --- a/docs/ordsservices/examples/mongo_api.md +++ b/docs/ordsservices/examples/mongo_api.md @@ -40,10 +40,10 @@ openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_keyg openssl rsa -in ca.key -outform PEM -pubout -out public.pem kubectl create secret generic prvkey --from-file=privateKey=ca.key -n ordsnamespace -echo "${DB_PWD}" > sidb-db-auth-enc -openssl rsautl -encrypt -pubin -inkey public.pem -in sidb-db-auth-enc |base64 > e_sidb-db-auth-enc -kubectl create secret generic sidb-db-auth-enc --from-file=password=e_sidb-db-auth-enc -n ordsnamespace -rm sidb-db-auth-enc e_sidb-db-auth-enc +echo "${DB_PWD}" > db-auth +openssl pkeyutl -encrypt -pubin -inkey public.pem -in db-auth -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 |base64 > e_db-auth +kubectl create secret generic sidb-db-auth-enc --from-file=password=e_db-auth -n ordsnamespace +rm db-auth e_db-auth ``` ### Create ordssrvs Resource @@ -74,7 +74,7 @@ rm sidb-db-auth-enc e_sidb-db-auth-enc name: ords-sidb namespace: ordsnamespace spec: - image: container-registry.oracle.com/database/ords:24.1.1 + image: container-registry.oracle.com/database/ords:25.1.0 forceRestart: true encPrivKey: secretName: prvkey @@ -101,7 +101,8 @@ rm sidb-db-auth-enc e_sidb-db-auth-enc db.adminUser.secret: secretName: sidb-db-auth-enc" | kubectl apply -f - ``` - latest container-registry.oracle.com/database/ords version, **24.1.1**, valid as of **30-May-2024** + latest container-registry.oracle.com/database/ords version, **25.1.0**, valid as of **26-May-2025** + 1. Watch the restdataservices resource until the status is **Healthy**: ```bash diff --git a/docs/ordsservices/examples/multi_pool.md b/docs/ordsservices/examples/multi_pool.md index ffb537bf..b7d3441d 100644 --- a/docs/ordsservices/examples/multi_pool.md +++ b/docs/ordsservices/examples/multi_pool.md @@ -55,7 +55,7 @@ The following secret will be used for PDB1: ```bash echo "THIS_IS_A_PASSWORD" > ordspwdfile -openssl rsautl -encrypt -pubin -inkey public.pem -in ordspwdfile |base64 > e_ordspwdfile +openssl pkeyutl -encrypt -pubin -inkey public.pem -in ordspwdfile -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 |base64 > e_ordspwdfile kubectl create secret generic pdb1-ords-auth-enc --from-file=password=e_ordspwdfile -n ordsnamespace rm ordspwdfile e_ordspwdfile ``` @@ -64,7 +64,7 @@ The following secret will be used for PDB2: ```bash echo "THIS_IS_A_PASSWORD" > ordspwdfile -openssl rsautl -encrypt -pubin -inkey public.pem -in ordspwdfile |base64 > e_ordspwdfile +openssl pkeyutl -encrypt -pubin -inkey public.pem -in ordspwdfile -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 |base64 > e_ordspwdfile kubectl create secret generic pdb2-ords-auth-enc --from-file=password=e_ordspwdfile -n ordsnamespace rm ordspwdfile e_ordspwdfile ``` @@ -73,7 +73,7 @@ The following secret will be used for PDB3 and PDB4: ```bash echo "THIS_IS_A_PASSWORD" > ordspwdfile -openssl rsautl -encrypt -pubin -inkey public.pem -in ordspwdfile |base64 > e_ordspwdfile +openssl pkeyutl -encrypt -pubin -inkey public.pem -in ordspwdfile -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 |base64 > e_ordspwdfile kubectl create secret generic multi-ords-auth-enc --from-file=password=e_ordspwdfile -n ordsnamespace rm ordspwdfile e_ordspwdfile ``` @@ -86,7 +86,7 @@ In this example, only PDB1 will be set for [AutoUpgrade](../autoupgrade.md), the ```bash echo "THIS_IS_A_PASSWORD" > syspwdfile -openssl rsautl -encrypt -pubin -inkey public.pem -in syspwdfile |base64 > e_syspwdfile +openssl pkeyutl -encrypt -pubin -inkey public.pem -in syspwdfile -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 |base64 > e_syspwdfile kubectl create secret generic pdb1-priv-auth-enc --from-file=password=e_syspwdfile -n ordsnamespace rm syspwdfile e_syspwdfile ``` @@ -102,7 +102,7 @@ rm syspwdfile e_syspwdfile name: ords-multi-pool namespace: ordsnamespace spec: - image: container-registry.oracle.com/database/ords:24.1.1 + image: container-registry.oracle.com/database/ords:25.1.0 forceRestart: true encPrivKey: secretName: prvkey @@ -112,7 +112,6 @@ rm syspwdfile e_syspwdfile poolSettings: - poolName: pdb1 autoUpgradeORDS: true - autoUpgradeAPEX: true db.connectionType: tns db.tnsAliasName: PDB1 tnsAdminSecret: @@ -160,7 +159,8 @@ rm syspwdfile e_syspwdfile db.secret: secretName: multi-ords-auth-enc ``` - latest container-registry.oracle.com/database/ords version, **24.1.1**, valid as of **30-May-2024** + latest container-registry.oracle.com/database/ords version, **25.1.0**, valid as of **26-May-2025** + 1. Apply the yaml file: ```bash @@ -196,5 +196,4 @@ This example has multiple pools, named `pdb1`, `pdb2`, `pdb3`, and `pdb4`. * They all share the same `tnsAdminSecret` to connect using thier individual `db.tnsAliasName` * They will all automatically restart when the configuration changes: `forceRestart: true` * Only the `pdb1` pool will automatically install/update ORDS on startup, if required: `autoUpgradeORDS: true` -* Only the `pdb1` pool will automatically install/update APEX on startup, if required: `autoUpgradeAPEX: true` * The `passwordKey` has been ommitted from both `db.secret` and `db.adminUser.secret` as the password was stored in the default key (`password`) diff --git a/docs/ordsservices/examples/ordssrvs-sa-scc.yaml b/docs/ordsservices/examples/ordssrvs-sa-scc.yaml new file mode 100644 index 00000000..0f854f59 --- /dev/null +++ b/docs/ordsservices/examples/ordssrvs-sa-scc.yaml @@ -0,0 +1,50 @@ +kind: SecurityContextConstraints +apiVersion: security.openshift.io/v1 +metadata: + name: ordssrvs-scc + namespace: ordsnamespace +allowPrivilegedContainer: false +runAsUser: + type: MustRunAs + uid: 54321 +seLinuxContext: + type: RunAsAny +fsGroup: + type: MustRunAs + ranges: + - min: 54321 + max: 54321 +supplementalGroups: + type: MustRunAs + ranges: + - min: 54321 + max: 54321 +--- +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: ordssrvs-role + namespace: ordsnamespace +rules: + - apiGroups: + - security.openshift.io + verbs: + - use + resources: + - securitycontextconstraints + resourceNames: + - ordssrvs-scc +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: ordssrvs-rb + namespace: ordsnamespace +subjects: + - kind: ServiceAccount + name: ordssrvs-sa + namespace: ordsnamespace +roleRef: + kind: Role + name: ordssrvs-role + apiGroup: rbac.authorization.k8s.io diff --git a/docs/ordsservices/examples/ordssrvs-sa.yaml b/docs/ordsservices/examples/ordssrvs-sa.yaml new file mode 100644 index 00000000..2eb59a18 --- /dev/null +++ b/docs/ordsservices/examples/ordssrvs-sa.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: ordssrvs-sa + namespace: ordsnamespace diff --git a/docs/ordsservices/examples/ordssrvs.yaml b/docs/ordsservices/examples/ordssrvs.yaml new file mode 100644 index 00000000..2f40d945 --- /dev/null +++ b/docs/ordsservices/examples/ordssrvs.yaml @@ -0,0 +1,32 @@ +apiVersion: database.oracle.com/v4 +kind: OrdsSrvs +metadata: + name: ordssrvs + namespace: ordsnamespace +spec: + image: container-registry.oracle.com/database/ords:latest + forceRestart: true + encPrivKey: + secretName: prvkey + passwordKey: privateKey + globalSettings: + database.api.enabled: true + mongo.enabled: true + apex.download : false + db.invalidPoolTimeout: 1m + poolSettings: + - poolName: default + autoUpgradeORDS: true + autoUpgradeAPEX: false + feature.sdw: true + restEnabledSql.active: true + plsql.gateway.mode: direct + db.connectionType: customurl + db.customURL: jdbc:oracle:thin:@//:1521/FREEPDB1 + db.username: ORDS_PUBLIC_USER + db.secret: + secretName: db-auth-enc + db.adminUser: SYS + db.adminUser.secret: + secretName: db-auth-enc + serviceAccountName: ordssrvs-sa diff --git a/docs/ordsservices/examples/sidb_container.md b/docs/ordsservices/examples/sidb_container.md index 3cda09ea..b493e516 100644 --- a/docs/ordsservices/examples/sidb_container.md +++ b/docs/ordsservices/examples/sidb_container.md @@ -12,7 +12,7 @@ Refer to Single Instance Database (SIDB) [README](https://github.com/oracle/orac ```bash DB_PWD= - kubectl create secret generic sidb-db-auth --from-literal=password=${DB_PWD} --namespace ordsnamespace + kubectl create secret generic sidb-db-auth --from-literal=oracle_pwd=${DB_PWD} --namespace ordsnamespace ``` 1. Create a manifest for the containerised Oracle Database. @@ -50,13 +50,12 @@ openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_keyg openssl rsa -in ca.key -outform PEM -pubout -out public.pem kubectl create secret generic prvkey --from-file=privateKey=ca.key -n ordsnamespace -echo "${DB_PWD}" > sidb-db-auth -openssl rsautl -encrypt -pubin -inkey public.pem -in sidb-db-auth |base64 > e_sidb-db-auth -kubectl create secret generic sidb-db-auth-enc --from-file=password=e_sidb-db-auth -n ordsnamespace -rm sidb-db-auth e_sidb-db-auth +echo "${DB_PWD}" > db-auth +openssl pkeyutl -encrypt -pubin -inkey public.pem -in db-auth -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 |base64 > e_db-auth +kubectl create secret generic sidb-db-auth-enc --from-file=password=e_db-auth -n ordsnamespace +rm db-auth e_db-auth ``` - ### Create RestDataServices Resource 1. Retrieve the Connection String from the containerised SIDB. @@ -73,7 +72,6 @@ rm sidb-db-auth e_sidb-db-auth As the DB in the Free image does not contain ORDS (or APEX), the following additional keys are specified for the pool: * `autoUpgradeORDS` - Boolean; when true the ORDS will be installed/upgraded in the database - * `autoUpgradeAPEX` - Boolean; when true the APEX will be installed/upgraded in the database * `db.adminUser` - User with privileges to install, upgrade or uninstall ORDS in the database (SYS). * `db.adminUser.secret` - Secret containing the password for `db.adminUser` (created in the first step) @@ -87,7 +85,7 @@ rm sidb-db-auth e_sidb-db-auth name: ords-sidb namespace: ordsnamespace spec: - image: container-registry.oracle.com/database/ords:24.1.1 + image: container-registry.oracle.com/database/ords:25.1.0 forceRestart: true encPrivKey: secretName: prvkey @@ -97,7 +95,6 @@ rm sidb-db-auth e_sidb-db-auth poolSettings: - poolName: default autoUpgradeORDS: true - autoUpgradeAPEX: true restEnabledSql.active: true plsql.gateway.mode: direct db.connectionType: customurl @@ -112,7 +109,7 @@ rm sidb-db-auth e_sidb-db-auth kubectl apply -f ords-sidb.yaml ``` - latest container-registry.oracle.com/database/ords version, **24.1.1**, valid as of **30-May-2024** + latest container-registry.oracle.com/database/ords version, **25.1.0**, valid as of **26-May-2025** 1. Watch the ordssrvs resource until the status is **Healthy**: ```bash @@ -147,6 +144,5 @@ This example has a single database pool, named `default`. It is set to: * Automatically restart when the configuration changes: `forceRestart: true` * Automatically install/update ORDS on startup, if required: `autoUpgradeORDS: true` -* Automatically install/update APEX on startup, if required: `autoUpgradeAPEX: true` * Use a basic connection string to connect to the database: `db.customURL: jdbc:oracle:thin:@//${CONN_STRING}` * The `passwordKey` has been ommitted from both `db.secret` and `db.adminUser.secret` as the password was stored in the default key (`password`) diff --git a/docs/ordsservices/usecase01/makefile b/docs/ordsservices/usecase01/makefile index 76b47210..c16a7c30 100644 --- a/docs/ordsservices/usecase01/makefile +++ b/docs/ordsservices/usecase01/makefile @@ -80,9 +80,9 @@ export PDB1_PRIV_AUTH_SECRET=pdb1-priv-auth-enc export PDB2_PRIV_AUTH_SECRET=pdb2-priv-auth-enc -export SIDB_IMAGE=container-registry.oracle.com/database/free:23.4.0.0 -export ORDS_IMAGE=container-registry.oracle.com/database/ords:24.1.0 -export ORDS_IMAGE.1=container-registry.oracle.com/database/ords:24.1.1 +export SIDB_IMAGE=container-registry.oracle.com/database/free:23.7.0.0 +export ORDS_IMAGE=container-registry.oracle.com/database/ords:25.1.0 +export ORDS_IMAGE.1=container-registry.oracle.com/database/ords:25.1.0 export SECRET_CONTAINER_REGISTRY=oracle-container-registry-secret export ORACLE_CONTAINER_REGISTRY=container-registry.oracle.com export REST_SERVER_NAME=ords-sidb @@ -386,7 +386,7 @@ step5a: $(OPENSSL) rsa -in $(PRVKEY) -outform PEM -pubout -out $(PUBKEY) $(KUBECTL) create secret generic pubkey --from-file=publicKey=$(PUBKEY) -n $(ORDSNAMESPACE) $(KUBECTL) create secret generic prvkey --from-file=privateKey=$(PRVKEY) -n $(ORDSNAMESPACE) - $(OPENSSL) rsautl -encrypt -pubin -inkey $(PUBKEY) -in $(SIDB_PASSWORD_FILE) |base64 > e_$(SIDB_PASSWORD_FILE) + $(OPENSSL) pkeyutl -encrypt -pubin -inkey $(PUBKEY) -in $(SIDB_PASSWORD_FILE) -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256|base64 > e_$(SIDB_PASSWORD_FILE) $(KUBECTL) create secret generic $(SIDB_SECRET) --from-literal=password=$(SIDB_PASSWORD) -n $(OPRNAMESPACE) $(KUBECTL) create secret generic $(ORDS_SECRET) --from-file=password=e_$(SIDB_PASSWORD_FILE) -n $(ORDSNAMESPACE) $(RM) e_$(SIDB_PASSWORD_FILE) $(SIDB_PASSWORD_FILE) @@ -467,7 +467,6 @@ cat<$(REST_SERVER_CREATION) # poolSettings: # - poolName: default # autoUpgradeORDS: true -# autoUpgradeAPEX: true # restEnabledSql.active: true # plsql.gateway.mode: direct # db.connectionType: customurl @@ -675,9 +674,9 @@ step13a: $(OPENSSL) rsa -in $(PRVKEY) -outform PEM -pubout -out $(PUBKEY) #$(KUBECTL) create secret generic pubkey --from-file=publicKey=$(PUBKEY) -n $(ORDSNAMESPACE) $(KUBECTL) create secret generic prvkey --from-file=privateKey=$(PRVKEY) -n $(ORDSNAMESPACE) - $(OPENSSL) rsautl -encrypt -pubin -inkey $(PUBKEY) -in $(SYSPWDFILE1) |base64 > e_$(SYSPWDFILE1) - $(OPENSSL) rsautl -encrypt -pubin -inkey $(PUBKEY) -in $(SYSPWDFILE2) |base64 > e_$(SYSPWDFILE2) - $(OPENSSL) rsautl -encrypt -pubin -inkey $(PUBKEY) -in $(ORDPWDFILE) |base64 > e_$(ORDPWDFILE) + $(OPENSSL) pkeyutl -encrypt -pubin -inkey $(PUBKEY) -in $(SYSPWDFILE1) -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256|base64 > e_$(SYSPWDFILE1) + $(OPENSSL) pkeyutl -encrypt -pubin -inkey $(PUBKEY) -in $(SYSPWDFILE2) -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256|base64 > e_$(SYSPWDFILE2) + $(OPENSSL) pkeyutl -encrypt -pubin -inkey $(PUBKEY) -in $(ORDPWDFILE) -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256|base64 > e_$(ORDPWDFILE) $(KUBECTL) create secret generic $(PDB1_PRIV_AUTH_SECRET) --from-file=password=e_$(SYSPWDFILE1) -n $(ORDSNAMESPACE) $(KUBECTL) create secret generic $(PDB2_PRIV_AUTH_SECRET) --from-file=password=e_$(SYSPWDFILE2) -n $(ORDSNAMESPACE) $(KUBECTL) create secret generic $(MULTI_ORDS_AUTH_SECRET) --from-file=password=e_$(ORDPWDFILE) -n $(ORDSNAMESPACE) @@ -698,7 +697,7 @@ cat <$(MULTISRV_MANIFEST) # name: ords-multi-pool # namespace: $(ORDSNAMESPACE) #spec: -# image: container-registry.oracle.com/database/ords:24.1.1 +# image: $(ORDS_IMAGE.1) # forceRestart: true # encPrivKey: # secretName: prvkey @@ -707,7 +706,6 @@ cat <$(MULTISRV_MANIFEST) # database.api.enabled: true # poolSettings: # - poolName: pdb1 -# autoUpgradeAPEX: false # autoUpgradeORDS: false # db.connectionType: tns # db.tnsAliasName: pdb1 @@ -723,7 +721,6 @@ cat <$(MULTISRV_MANIFEST) # db.adminUser.secret: # secretName: $(PDB1_PRIV_AUTH_SECRET) # - poolName: pdb2 -# autoUpgradeAPEX: false # autoUpgradeORDS: false # db.connectionType: tns # db.tnsAliasName: PDB2 diff --git a/docs/sharding/README.md b/docs/sharding/README.md index 487d9ec3..47c5853e 100644 --- a/docs/sharding/README.md +++ b/docs/sharding/README.md @@ -132,6 +132,7 @@ If you want to use Oracle Database 23ai Free Image for Database and GSM for depl * For Oracle Database 23ai Free, you can control the `CPU` and `Memory` allocation of the PODs using tags `cpu` and `memory` respectively but tags `INIT_SGA_SIZE` and `INIT_PGA_SIZE` to control the SGA and PGA allocation at the database level are `not` supported. * Provisioning the Oracle Globally Distributed Database using Cloning from Database Gold Image is `NOT` supported with Oracle Database 23ai Free. * Total number of chunks for FREE Database defaults to `12` if `CATALOG_CHUNKS` parameter is not specified. This default value is determined considering limitation of 12 GB of user data on disk for oracle free database. +* Oracle 23ai FREE Database supports maximum three shards. Please refer [here](https://docs.oracle.com/en/database/oracle/oracle-database/23/dblic/Licensing-Information.html) ## Provisioning Oracle Globally Distributed Database Topology with System-Managed Sharding in a Cloud-Based Kubernetes Cluster @@ -169,18 +170,15 @@ In this example, the deployment uses the YAML file based on `OCI OKE` cluster. T Deploy Oracle Globally Distributed Database Topology with `System-Managed Sharding` and with `RAFT Replication` enabled on your Cloud based Kubernetes cluster. -**NOTE: RAFT Replication Feature is available only for Oracle 23ai RDBMS and Oracle 23ai GSM version.** +**NOTE:** RAFT Replication Feature is available only for Oracle 23ai RDBMS and Oracle 23ai GSM version. +**NOTE:** RAFT Replication requires atleast three shards. Oracle 23ai FREE Database supports maximum three shards. Please refer [here](https://docs.oracle.com/en/database/oracle/oracle-database/23/dblic/Licensing-Information.html) In this example, the deployment uses the YAML file based on `OCI OKE` cluster. There are multiple use case possible for deploying the Oracle Globally Distributed Database Topology covered by below examples: [1. Provisioning Oracle Globally Distributed Database Topology with System-Managed Sharding and Raft replication enabled without Database Gold Image](./provisioning/snr_system_sharding/snr_ssharding_provisioning_without_db_gold_image.md) -[2. Provisioning Oracle Globally Distributed Database Topology with System-Managed Sharding and Raft replication enabled with number of chunks specified](./provisioning/snr_system_sharding/snr_ssharding_provisioning_with_chunks_specified.md) -[3. Provisioning Oracle Globally Distributed Database Topology with System-Managed Sharding and Raft replication enabled with additional control on resources like Memory and CPU allocated to Pods](./provisioning/snr_system_sharding/snr_ssharding_provisioning_with_control_on_resources.md) -[4. Provisioning Oracle Globally Distributed Database Topology with System-Managed Sharding and Raft replication enabled by cloning database from your own Database Gold Image in the same Availability Domain(AD)](./provisioning/snr_system_sharding/snr_ssharding_provisioning_by_cloning_db_gold_image_in_same_ad.md) -[5. Provisioning Oracle Globally Distributed Database Topology with System-Managed Sharding and Raft replication enabled by cloning database from your own Database Gold Image across Availability Domains(ADs)](./provisioning/snr_system_sharding/snr_ssharding_provisioning_by_cloning_db_from_gold_image_across_ads.md) -[6. Provisioning Oracle Globally Distributed Database Topology with System-Managed Sharding and Raft replication enabled send Notification using OCI Notification Service](./provisioning/snr_system_sharding/snr_ssharding_provisioning_with_notification_using_oci_notification.md) -[7. Scale Out - Add Shards to an existing Oracle Globally Distributed Database provisioned earlier with System-Managed Sharding and RAFT replication enabled](./provisioning/snr_system_sharding/snr_ssharding_scale_out_add_shards.md) -[8. Scale In - Delete an existing Shard from a working Oracle Globally Distributed Database provisioned earlier with System-Managed Sharding and RAFT reolication enabled](./provisioning/snr_system_sharding/snr_ssharding_scale_in_delete_an_existing_shard.md) +[2. Provisioning Oracle Globally Distributed Database Topology with System-Managed Sharding and Raft replication enabled with additional control on resources like Memory and CPU allocated to Pods](./provisioning/snr_system_sharding/snr_ssharding_provisioning_with_control_on_resources.md) +[3. Provisioning Oracle Globally Distributed Database Topology with System-Managed Sharding and Raft replication enabled send Notification using OCI Notification Service](./provisioning/snr_system_sharding/snr_ssharding_provisioning_with_notification_using_oci_notification.md) + ## Connecting to Oracle Globally Distributed Database diff --git a/docs/sharding/provisioning/debugging/sharding_provisioning_with_db_events.yaml b/docs/sharding/provisioning/debugging/sharding_provisioning_with_db_events.yaml index 40ad600a..fcf5939f 100644 --- a/docs/sharding/provisioning/debugging/sharding_provisioning_with_db_events.yaml +++ b/docs/sharding/provisioning/debugging/sharding_provisioning_with_db_events.yaml @@ -3,18 +3,29 @@ # Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. # --- +# API version of the ShardingDatabase Custom Resource apiVersion: database.oracle.com/v4 + +# Resource type being created kind: ShardingDatabase + +# Metadata section to define the resource's name and namespace metadata: - name: shardingdatabase-sample - namespace: shns + name: shardingdatabase-sample # Name of the ShardingDatabase instance + namespace: shns # Kubernetes namespace to deploy into + +# Specs for the ShardingDatabase instance spec: + + # Details of the individual shard databases shard: - - name: shard1 - storageSizeInGb: 50 - imagePullPolicy: "Always" - shardGroup: shardgroup1 - shardRegion: primary + - name: shard1 # Name of the shard database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image + shardGroup: shardgroup1 # Shard Group name for the Shard Database + shardRegion: primary # Name of the region for the Shard Database + + # Defined Environment Variable "DB_EVENTS" and its Value as the event tracing to be enabled at the Database Level envVars: - name: "DB_EVENTS" value: "10798 trace name context forever, level 7:scope=spfile;immediate trace name GWM_TRACE level 263" @@ -25,7 +36,7 @@ spec: shardRegion: primary envVars: - name: "DB_EVENTS" - value: "10798 trace name context forever, level 7:scope=spfile;immediate trace name GWM_TRACE level 263" + value: "10798 trace name context forever, level 7:scope=spfile;immediate trace name GWM_TRACE level 263" - name: shard3 storageSizeInGb: 50 imagePullPolicy: "Always" @@ -34,35 +45,58 @@ spec: envVars: - name: "DB_EVENTS" value: "10798 trace name context forever, level 7:scope=spfile;immediate trace name GWM_TRACE level 263" + + # Details of the individual shard databases catalog: - - name: catalog - storageSizeInGb: 50 - imagePullPolicy: "Always" - envVars: + - name: catalog # Name of the Catalog database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image + envVars: - name: "DB_EVENTS" - value: "10798 trace name context forever, level 7:scope=spfile;immediate trace name GWM_TRACE level 263" + value: "10798 trace name context forever, level 7:scope=spfile;immediate trace name GWM_TRACE level 263" + + # Details of the GSM Pods gsm: - - name: gsm1 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: primary - - name: gsm2 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: standby + - name: gsm1 # Name of the Primary Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: primary # Name of the Primary region for the Shard Database + + - name: gsm2 # Name of the Standby Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: standby # Name of the Standby region for the Shard Database + + # Kubernetes Storage Class Name storageClass: oci - dbImage: container-registry.oracle.com/database/enterprise:latest + + # Database Container Image to be used for Catalog and Shard Database + dbImage: container-registry.oracle.com/database/enterprise_ru:19.27.0.0 + + # Database Conrainer Image Pull Secret dbImagePullSecret: ocr-reg-cred - gsmImage: container-registry.oracle.com/database/gsm:latest + + # GSM Container Image to be used for GSM Pods + gsmImage: container-registry.oracle.com/database/gsm_ru:19.27.0.0 + + # GSM Conrainer Image Pull Secret gsmImagePullSecret: ocr-reg-cred + + # Whether an External Load Balancer has to be configured for the Catalog, Shard and GSM Pods isExternalSvc: False + + # If set to true, the Persistent Volumes will be deleted when the deployment for the Oracle Globally Distributed Database is deleted isDeleteOraPvc: True + + # Details of the Database Secret dbSecret: name: db-user-pass-rsa pwdFileName: pwdfile.enc keyFileName: key.pem + + # Details of Services to be created for the Oracle Globally Distributed Database Resource gsmService: - name: oltp_rw_svc role: primary - name: oltp_ro_svc - role: primary + role: primary \ No newline at end of file diff --git a/docs/sharding/provisioning/free/sharding_provisioning_with_free_images.yaml b/docs/sharding/provisioning/free/sharding_provisioning_with_free_images.yaml index dadd619a..a2258f96 100644 --- a/docs/sharding/provisioning/free/sharding_provisioning_with_free_images.yaml +++ b/docs/sharding/provisioning/free/sharding_provisioning_with_free_images.yaml @@ -3,18 +3,27 @@ # Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. # --- +# API version of the ShardingDatabase Custom Resource apiVersion: database.oracle.com/v4 + +# Resource type being created kind: ShardingDatabase + +# Metadata section to define the resource's name and namespace metadata: - name: shardingdatabase-sample - namespace: shns + name: shardingdatabase-sample # Name of the ShardingDatabase instance + namespace: shns # Kubernetes namespace to deploy into + +# Specs for the ShardingDatabase instance spec: + + # Details of the individual shard databases shard: - - name: shard1 - storageSizeInGb: 50 - imagePullPolicy: "Always" - shardGroup: shardgroup1 - shardRegion: primary + - name: shard1 # Name of the shard database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image + shardGroup: shardgroup1 # Shard Group name for the Shard Database + shardRegion: primary # Name of the region for the Shard Database - name: shard2 storageSizeInGb: 50 imagePullPolicy: "Always" @@ -25,33 +34,60 @@ spec: imagePullPolicy: "Always" shardGroup: shardgroup1 shardRegion: primary + + # Details of the individual shard databases catalog: - - name: catalog - storageSizeInGb: 50 - imagePullPolicy: "Always" + - name: catalog # Name of the Catalog database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image + + # Details of the GSM Pods gsm: - - name: gsm1 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: primary - - name: gsm2 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: standby + - name: gsm1 # Name of the Primary Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: primary # Name of the Primary region for the Shard Database + + - name: gsm2 # Name of the Standby Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: standby # Name of the Standby region for the Shard Database + + # Kubernetes Storage Class Name storageClass: oci + + # Database Container Image to be used for Catalog and Shard Database dbImage: container-registry.oracle.com/database/free:latest + + # Database Conrainer Image Pull Secret dbImagePullSecret: ocr-reg-cred + + # GSM Container Image to be used for GSM Pods gsmImage: container-registry.oracle.com/database/gsm:latest + + # GSM Conrainer Image Pull Secret gsmImagePullSecret: ocr-reg-cred + + # Specify the Database Edition as "free" for Oracle Database FREE dbEdition: "free" + + # Whether an External Load Balancer has to be configured for the Catalog, Shard and GSM Pods isExternalSvc: False + + # If set to true, the Persistent Volumes will be deleted when the deployment for the Oracle Globally Distributed Database is deleted isDeleteOraPvc: True + + # Details of the Database Secret dbSecret: name: db-user-pass-rsa pwdFileName: pwdfile.enc keyFileName: key.pem + + # Details of Services to be created for the Oracle Globally Distributed Database Resource gsmService: - name: oltp_rw_svc role: primary + ruMode: READWRITE # Specify the ruMode for the service as READWRITE in RAFT Replication Setup - name: oltp_ro_svc role: primary + ruMode: READONLY # Specify the ruMode for the service as READWRITE in RAFT Replication Setup diff --git a/docs/sharding/provisioning/oraclesi.yaml b/docs/sharding/provisioning/oraclesi.yaml index cac70ffa..fa36a10a 100644 --- a/docs/sharding/provisioning/oraclesi.yaml +++ b/docs/sharding/provisioning/oraclesi.yaml @@ -5,10 +5,10 @@ apiVersion: v1 kind: PersistentVolumeClaim metadata: - name: oshard-gold-image-pvc21c + name: oshard-gold-image-pvc19c namespace: shns labels: - app: oshard21cdb-dep + app: oshard19cdb-dep spec: accessModes: - ReadWriteOnce @@ -18,28 +18,28 @@ spec: storageClassName: oci selector: matchLabels: - topology.kubernetes.io/zone: "PHX-AD-1" + topology.kubernetes.io/zone: "PHX-AD-3" --- apiVersion: apps/v1 kind: StatefulSet metadata: - name: oshard21cdb + name: oshard19cdb namespace: shns labels: - app: oshard21cdb-dep + app: oshard19cdb-dep spec: selector: matchLabels: - app: oshard21cdb-dep + app: oshard19cdb-dep serviceName: gold-shard template: metadata: labels: - app: oshard21cdb-dep + app: oshard19cdb-dep spec: containers: - - image: container-registry.oracle.com/database/enterprise:latest - name: oshard21cdb + - image: container-registry.oracle.com/database/enterprise_ru:19.28.0.0 + name: oshard19cdb ports: - containerPort: 1521 name: db1-dbport @@ -68,17 +68,17 @@ spec: volumes: - name: data persistentVolumeClaim: - claimName: oshard-gold-image-pvc21c + claimName: oshard-gold-image-pvc19c - name: dshm emptyDir: medium: Memory nodeSelector: - topology.kubernetes.io/zone: "PHX-AD-1" + topology.kubernetes.io/zone: "PHX-AD-3" --- apiVersion: v1 kind: Service metadata: - name: oshard21cdb + name: oshard19cdb namespace: shns spec: ports: @@ -95,4 +95,4 @@ spec: port: 6234 targetPort: db1-onsrport selector: - app: oshard21cdb-dep \ No newline at end of file + app: oshard19cdb-dep diff --git a/docs/sharding/provisioning/oraclesi_pvc_commented.yaml b/docs/sharding/provisioning/oraclesi_pvc_commented.yaml index 43b50a5e..a54e07a5 100644 --- a/docs/sharding/provisioning/oraclesi_pvc_commented.yaml +++ b/docs/sharding/provisioning/oraclesi_pvc_commented.yaml @@ -5,10 +5,10 @@ #apiVersion: v1 #kind: PersistentVolumeClaim #metadata: -# name: oshard-gold-image-pvc21c +# name: oshard-gold-image-pvc19c # namespace: shns # labels: -# app: oshard21cdb-dep +# app: oshard19cdb-dep #spec: # accessModes: # - ReadWriteOnce @@ -18,28 +18,28 @@ # storageClassName: oci # selector: # matchLabels: -# topology.kubernetes.io/zone: "PHX-AD-1" -#--- +# topology.kubernetes.io/zone: "PHX-AD-3" +--- apiVersion: apps/v1 kind: StatefulSet metadata: - name: oshard21cdb + name: oshard19cdb namespace: shns labels: - app: oshard21cdb-dep + app: oshard19cdb-dep spec: selector: matchLabels: - app: oshard21cdb-dep + app: oshard19cdb-dep serviceName: gold-shard template: metadata: labels: - app: oshard21cdb-dep + app: oshard19cdb-dep spec: containers: - - image: container-registry.oracle.com/database/enterprise:latest - name: oshard21cdb + - image: container-registry.oracle.com/database/enterprise_ru:19.28.0.0 + name: oshard19cdb ports: - containerPort: 1521 name: db1-dbport @@ -68,17 +68,17 @@ spec: volumes: - name: data persistentVolumeClaim: - claimName: oshard-gold-image-pvc21c + claimName: oshard-gold-image-pvc19c - name: dshm emptyDir: medium: Memory nodeSelector: - topology.kubernetes.io/zone: "PHX-AD-1" + topology.kubernetes.io/zone: "PHX-AD-3" --- apiVersion: v1 kind: Service metadata: - name: oshard21cdb + name: oshard19cdb namespace: shns spec: ports: @@ -95,4 +95,4 @@ spec: port: 6234 targetPort: db1-onsrport selector: - app: oshard21cdb-dep \ No newline at end of file + app: oshard19cdb-dep diff --git a/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_provisioning_by_cloning_db_from_gold_image_across_ads.md b/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_provisioning_by_cloning_db_from_gold_image_across_ads.md deleted file mode 100644 index 9ffebad9..00000000 --- a/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_provisioning_by_cloning_db_from_gold_image_across_ads.md +++ /dev/null @@ -1,58 +0,0 @@ -# Provisioning System managed Sharding Topology with Raft replication enabled by cloning database from your own Database Gold Image across Availability Domains(ADs) - -**NOTE: RAFT Replication Feature is available only for Oracle 23ai RDBMS and Oracle 23ai GSM version.** - -**IMPORTANT:** Make sure you have completed the steps for [Prerequsites for Running Oracle Sharding Database Controller](../../README.md#prerequsites-for-running-oracle-sharding-database-controller) before using Oracle Sharding Controller. - -In this test case, you provision the System managed Sharding Topology with Raft replication enabled while provisioning the Catalog and Shard Databases by cloning from an existing Oracle Database Gold Image created earlier. - -This use case applies when you want to provision the database Pods on a Kubernetes Node in any availability domain (AD), which can also be different from the availability domain (AD) of the Block Volume that has the Oracle Database Gold Image provisioned earlier. - -Choosing this option takes substantially less time during the Oracle Database Sharding Topology setup across ADs. - -NOTE: - -* Cloning from Block Volume Backup in OCI enables the new Persistent Volumes to be created in other ADs. -* To specify the AD where you want to provision the database Pod, use the tag `nodeSelector` and the POD will be provisioned in a node running in that AD. -* To specify GSM containers, you can also use the tag `nodeSelector` to specify the AD. -* Before you can provision with the Gold Image, you need the OCID of the Persistent Volume that has the Oracle Database Gold Image. - -1. Check the OCID of the Persistent Volume provisioned for the Oracle Database Gold Image: - ```sh - kubectl get pv -n shns - ``` -2. Create a Block Volume Backup for this Block Volume, and use the OCID of the Block Volume Backup in the next step. This example uses `snr_ssharding_shard_prov_clone_across_ads.yaml` to provision an Oracle Database sharding topology using Oracle Sharding controller with: - -* Primary GSM Pods `gsm1` and standby GSM Pod `gsm2` -* Three sharding Pods: `shard1`, `shard2` and `shard3` -* One Catalog Pod: `catalog` -* Namespace: `shns` -* Database Cloning from the `BLOCK VOLUME FULL BACKUP` of the Persistent Volume which had the Gold Image. -* OCID of the Block Volume Backup: `ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq` -* `RAFT Replication` enabled - -NOTE: In this case, the Persistent Volume with DB Gold Image was provisioned in the Availability Domain `PHX-AD-1`. The Shards and Catalog will be provisioned across multiple Availability Domains by cloning the database. - -**NOTE:** Provisioning the Sharded Database using Cloning from Database Gold Image is `NOT` supported with Oracle Database 23ai Free. - -In this example, we are using pre-built Oracle Database and Global Data Services container images available on [Oracle Container Registry](https://container-registry.oracle.com/) - * To pull the above images from Oracle Container Registry, create a Kubernetes secret named `ocr-reg-cred` using your credentials with type set to `kubernetes.io/dockerconfigjson` in the namespace `shns`. - * If you plan to use images built by you, you need to change `dbImage` and `gsmImage` tag with the images you have built in your enviornment in file `snr_ssharding_shard_prov_clone_across_ads.yaml`. - * The `dbImage` used during provisioning the Persistent Volume with Database Gold Image and the `dbImage` used for deploying the Shard or Catalog Database by cloning should be same. - * To understand the Pre-requisite of Database and Global Data Services docker images, refer [Oracle Database and Global Data Services Docker Images](../../README.md#3-oracle-database-and-global-data-services-docker-images) - * Make sure the version of `openssl` in the Oracle Database and Oracle GSM images is compatible with the `openssl` version on the machine where you will run the openssl commands to generated the encrypted password file during the deployment. - -Use the file: [snr_ssharding_shard_prov_clone_across_ads.yaml](./snr_ssharding_shard_prov_clone_across_ads.yaml) for this use case as below: - -1. Deploy the `snr_ssharding_shard_prov_clone_across_ads.yaml` file: - ```sh - kubectl apply -f snr_ssharding_shard_prov_clone_across_ads.yaml - ``` -2. Check the status of the deployment: - ```sh - # Check the status of the Kubernetes Pods: - kubectl get all -n shns - - # Check the logs of a particular pod. For example, to check status of pod "shard1-0": - kubectl logs -f pod/shard1-0 -n shns - diff --git a/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_provisioning_by_cloning_db_gold_image_in_same_ad.md b/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_provisioning_by_cloning_db_gold_image_in_same_ad.md deleted file mode 100644 index 054d760e..00000000 --- a/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_provisioning_by_cloning_db_gold_image_in_same_ad.md +++ /dev/null @@ -1,54 +0,0 @@ -# Provisioning System managed Sharding Topology with Raft replication enabled by cloning database from your own Database Gold Image in the same Availability Domain(AD) - -**NOTE: RAFT Replication Feature is available only for Oracle 23ai RDBMS and Oracle 23ai GSM version.** - -**IMPORTANT:** Make sure you have completed the steps for [Prerequsites for Running Oracle Sharding Database Controller](../../README.md#prerequsites-for-running-oracle-sharding-database-controller) before using Oracle Sharding Controller. - -In this test case, you provision the System managed Sharding Topology with Raft replication enabled while provisioning the Catalog and Shard Databases by cloning from an existing Oracle Database Gold Image created earlier. - -This use case applies when you are cloning from a Block Volume, and you can clone _only_ in the same availability domain (AD). The result is that the cloned shard database PODs can be created _only_ in the same AD where the Gold Image Block Volume is present. - -Choosing this option takes substantially less time during the Oracle Database Sharding Topology setup. - -**NOTE** For this step, the Persistent Volume that has the Oracle Database Gold Image is identified using its OCID. - -1. Check the OCID of the Persistent Volume provisioned earlier using below command: - - ```sh - kubectl get pv -n shns - ``` - -2. This example uses `snr_ssharding_shard_prov_clone.yaml` to provision an Oracle Database sharding topology using Oracle Sharding controller with: - -* Primary GSM Pods `gsm1` and standby GSM Pod `gsm2` -* Three sharding Pods: `shard1`, `shard2` and `shard3` -* One Catalog Pod: `catalog` -* Namespace: `shns` -* Database Cloning from the Database Gold Image present in Persistent Volume having OCID: `ocid1.volume.oc1.phx.abyhqljr3z3w72t6ay5eud7d5w3kdfhktfp6gwb6euy5tzwfaxgmbvwqlvsq` -* `RAFT Replication` enabled - -NOTE: In this case, the Persistent Volume with DB Gold Image was provisioned in the Availability Domain `PHX-AD-1`. The Shards and Catalog will be provisioned in the same Availability Domain `PHX-AD-1` by cloning the database. - -In this example, we are using pre-built Oracle Database and Global Data Services container images available on [Oracle Container Registry](https://container-registry.oracle.com/) - * To pull the above images from Oracle Container Registry, create a Kubernetes secret named `ocr-reg-cred` using your credentials with type set to `kubernetes.io/dockerconfigjson` in the namespace `shns`. - * If you plan to use images built by you, you need to change `dbImage` and `gsmImage` tag with the images you have built in your enviornment in file `snr_ssharding_shard_prov_clone.yaml`. - * The `dbImage` used during provisioning the Persistent Volume with Database Gold Image and the `dbImage` used for deploying the Shard or Catalog Database by cloning should be same. - * To understand the Pre-requisite of Database and Global Data Services docker images, refer [Oracle Database and Global Data Services Docker Images](../../README.md#3-oracle-database-and-global-data-services-docker-images) - * Make sure the version of `openssl` in the Oracle Database and Oracle GSM images is compatible with the `openssl` version on the machine where you will run the openssl commands to generated the encrypted password file during the deployment. - -**NOTE:** Provisioning the Sharded Database using Cloning from Database Gold Image is `NOT` supported with Oracle Database 23ai Free. - -Use the file: [snr_ssharding_shard_prov_clone.yaml](./snr_ssharding_shard_prov_clone.yaml) for this use case as below: - -1. Deploy the `snr_ssharding_shard_prov_clone.yaml` file: - ```sh - kubectl apply -f snr_ssharding_shard_prov_clone.yaml - ``` -2. Check the status of the deployment: - ```sh - # Check the status of the Kubernetes Pods: - kubectl get all -n shns - - # Check the logs of a particular pod. For example, to check status of pod "shard1-0": - kubectl logs -f pod/shard1-0 -n shns - ``` diff --git a/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_provisioning_with_chunks_specified.md b/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_provisioning_with_chunks_specified.md deleted file mode 100644 index 253d099b..00000000 --- a/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_provisioning_with_chunks_specified.md +++ /dev/null @@ -1,44 +0,0 @@ -# Provisioning System-Managed Sharding Topology with Raft replication enabled with number of chunks specified - -**NOTE: RAFT Replication Feature is available only for Oracle 23ai RDBMS and Oracle 23ai GSM version.** - -**IMPORTANT:** Make sure you have completed the steps for [Prerequsites for Running Oracle Sharding Database Controller](../../README.md#prerequsites-for-running-oracle-sharding-database-controller) before using Oracle Sharding Controller. - -In this use case, the database is created automatically using DBCA during the provisioning of the shard databases and the catalog database when the Oracle Sharding topology with System-Managed with RAFT Replication enabled is deployed using Oracle Sharding controller. - -**NOTE** In this use case, because DBCA creates the database automatically during the deployment, the time required to create the database is greater than the time it takes when the database is created by cloning from a Database Gold Image. - -By default, the System-Managed with RAFT Replication deploys the Sharded Database with 360 chunks per Shard Database (because there are 3 chunks created for each replication unit). In this example, the Sharded Database will be deployed with non-default number of chunks specified using parameter `CATALOG_CHUNKS`. - -This example uses `snr_ssharding_shard_prov_chunks.yaml` to provision an Oracle Database sharding topology using Oracle Sharding controller with: - -* Primary GSM Pods `gsm1` and standby GSM Pod `gsm2` -* Three sharding Pods: `shard1`, `shard2` and `shard3` -* One Catalog Pod: `catalog` -* Total number of chunks as `120` specified by variable `CATALOG_CHUNKS` (it will be 120 chunks per shard) -* Namespace: `shns` -* `RAFT Replication` enabled - - -In this example, we are using pre-built Oracle Database and Global Data Services container images available on [Oracle Container Registry](https://container-registry.oracle.com/) - * To pull the above images from Oracle Container Registry, create a Kubernetes secret named `ocr-reg-cred` using your credentials with type set to `kubernetes.io/dockerconfigjson` in the namespace `shns`. - * If you plan to use images built by you, you need to change `dbImage` and `gsmImage` tag with the images you have built in your enviornment in file `snr_ssharding_shard_prov.yaml`. - * To understand the Pre-requisite of Database and Global Data Services docker images, refer [Oracle Database and Global Data Services Docker Images](../../README.md#3-oracle-database-and-global-data-services-docker-images) - * In case you want to use the [Oracle Database 23ai Free](https://www.oracle.com/database/free/get-started/) Image for Database and GSM, then you will need to add the additional parameter `dbEdition: "free"` to the below .yaml file. - * Make sure the version of `openssl` in the Oracle Database and Oracle GSM images is compatible with the `openssl` version on the machine where you will run the openssl commands to generated the encrypted password file during the deployment. - - -Use the file: [snr_ssharding_shard_prov_chunks.yaml](./snr_ssharding_shard_prov_chunks.yaml) for this use case as below: - -1. Deploy the `snr_ssharding_shard_prov_chunks.yaml` file: - ```sh - kubectl apply -f snr_ssharding_shard_prov_chunks.yaml - ``` -1. Check the status of the deployment: - ```sh - # Check the status of the Kubernetes Pods: - kubectl get all -n shns - - # Check the logs of a particular pod. For example, to check status of pod "shard1-0": - kubectl logs -f pod/shard1-0 -n shns - ``` diff --git a/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_scale_in_delete_an_existing_shard.md b/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_scale_in_delete_an_existing_shard.md deleted file mode 100644 index fc093654..00000000 --- a/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_scale_in_delete_an_existing_shard.md +++ /dev/null @@ -1,51 +0,0 @@ -# Scale In - Delete an existing Shard from a working Oracle Sharded Database provisioned earlier with System-Managed Sharding with RAFT reolication enabled - -**NOTE: RAFT Replication Feature is available only for Oracle 23ai RDBMS and Oracle 23ai GSM version.** - -**IMPORTANT:** Make sure you have completed the steps for [Prerequsites for Running Oracle Sharding Database Controller](../../README.md#prerequsites-for-running-oracle-sharding-database-controller) before using Oracle Sharding Controller. - -This use case demonstrates how to delete an existing Shard from an existing Oracle Database sharding topology with System-Managed with RAFT Replication enabled provisioned using Oracle Database Sharding controller. - -**NOTE** The deletion of a shard is done after verifying the Chunks have been moved out of that shard. - -In this use case, the existing database Sharding is having: - -* Primary GSM Pods `gsm1` and standby GSM Pod `gsm2` -* Five sharding Pods: `shard1`,`shard2`,`shard3`,`shard4` and `shard5` -* One Catalog Pod: `catalog` -* Namespace: `shns` -* `RAFT Replication` enabled - -In this example, we are using pre-built Oracle Database and Global Data Services container images available on [Oracle Container Registry](https://container-registry.oracle.com/) - * To pull the above images from Oracle Container Registry, create a Kubernetes secret named `ocr-reg-cred` using your credentials with type set to `kubernetes.io/dockerconfigjson` in the namespace `shns`. - * If you plan to use images built by you, you need to change `dbImage` and `gsmImage` tag with the images you have built in your enviornment in file `snr_ssharding_shard_prov_delshard.yaml`. - * To understand the Pre-requisite of Database and Global Data Services docker images, refer [Oracle Database and Global Data Services Docker Images](../../README.md#3-oracle-database-and-global-data-services-docker-images) - * In case you want to use the [Oracle Database 23ai Free](https://www.oracle.com/database/free/get-started/) Image for Database and GSM, then you will need to add the additional parameter `dbEdition: "free"` to the below .yaml file. - * Make sure the version of `openssl` in the Oracle Database and Oracle GSM images is compatible with the `openssl` version on the machine where you will run the openssl commands to generated the encrypted password file during the deployment. - -NOTE: Use tag `isDelete: enable` to delete the shard you want. - -This use case deletes the shard `shard4` from the above Sharding Topology. - -Use the file: [snr_ssharding_shard_prov_delshard.yaml](./snr_ssharding_shard_prov_delshard.yaml) for this use case as below: - -1. Deploy the `snr_ssharding_shard_prov_delshard.yaml` file: - ```sh - kubectl apply -f snr_ssharding_shard_prov_delshard.yaml - ``` -2. Check the status of the deployment: - ```sh - # Check the status of the Kubernetes Pods: - kubectl get all -n shns - -**NOTE:** After you apply `snr_ssharding_shard_prov_delshard.yaml`, the change may not be visible immediately. When the shard is removed, first the chunks will be moved out of that shard that is going to be deleted. - -To monitor the chunk movement, use the following command: - -```sh -# Switch to the primary GSM Container: -kubectl exec -i -t gsm1-0 -n shns /bin/bash - -# Check the status of the chunks and repeat to observe the chunk movement: -gdsctl config chunks -``` diff --git a/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_scale_out_add_shards.md b/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_scale_out_add_shards.md deleted file mode 100644 index 3461bf13..00000000 --- a/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_scale_out_add_shards.md +++ /dev/null @@ -1,38 +0,0 @@ -# Scale Out - Add Shards to an existing Oracle Sharded Database provisioned earlier with System-Managed Sharding with RAFT replication enabled - -**NOTE: RAFT Replication Feature is available only for Oracle 23ai RDBMS and Oracle 23ai GSM version.** - -**IMPORTANT:** Make sure you have completed the steps for [Prerequsites for Running Oracle Sharding Database Controller](../../README.md#prerequsites-for-running-oracle-sharding-database-controller) before using Oracle Sharding Controller. - -This use case demonstrates adding a new shard to an existing Oracle Database sharding topology with System-Managed with RAFT Replication enabled provisioned earlier using Oracle Database Sharding controller. - -In this use case, the existing Oracle Database sharding topology is having: - -* Primary GSM Pods `gsm1` and standby GSM Pod `gsm2` -* Three sharding Pods: `shard1`, `shard2` and `shard3` -* One Catalog Pod: `catalog` -* Namespace: `shns` -* `RAFT Replication` enabled - -In this example, we are using pre-built Oracle Database and Global Data Services container images available on [Oracle Container Registry](https://container-registry.oracle.com/) - * To pull the above images from Oracle Container Registry, create a Kubernetes secret named `ocr-reg-cred` using your credentials with type set to `kubernetes.io/dockerconfigjson` in the namespace `shns`. - * If you plan to use images built by you, you need to change `dbImage` and `gsmImage` tag with the images you have built in your enviornment in file `snr_ssharding_shard_prov_extshard.yaml`. - * To understand the Pre-requisite of Database and Global Data Services docker images, refer [Oracle Database and Global Data Services Docker Images](../../README.md#3-oracle-database-and-global-data-services-docker-images) - * If the existing Sharding Topology was deployed using [Oracle Database 23ai Free](https://www.oracle.com/database/free/get-started/) Image for Database and GSM, then the additional parameter `dbEdition: "free"` will be needed for the below .yaml file as well. - * Make sure the version of `openssl` in the Oracle Database and Oracle GSM images is compatible with the `openssl` version on the machine where you will run the openssl commands to generated the encrypted password file during the deployment. - -This use case adds two new shards `shard4`,`shard5` to above Sharding Topology. - -Use the file: [snr_ssharding_shard_prov_extshard.yaml](./snr_ssharding_shard_prov_extshard.yaml) for this use case as below: - -1. Deploy the `snr_ssharding_shard_prov_extshard.yaml` file: - ```sh - kubectl apply -f snr_ssharding_shard_prov_extshard.yaml - ``` -2. Check the status of the deployment: - ```sh - # Check the status of the Kubernetes Pods: - kubectl get all -n shns - - # Check the logs of a particular pod. For example, to check status of pod "shard4-0": - kubectl logs -f pod/shard4-0 -n shns diff --git a/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_shard_prov.yaml b/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_shard_prov.yaml index 53b93a0d..2f7b6ebe 100644 --- a/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_shard_prov.yaml +++ b/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_shard_prov.yaml @@ -3,18 +3,27 @@ # Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. # --- +# API version of the ShardingDatabase Custom Resource apiVersion: database.oracle.com/v4 + +# Resource type being created kind: ShardingDatabase + +# Metadata section to define the resource's name and namespace metadata: - name: shardingdatabase-sample - namespace: shns + name: shardingdatabase-sample # Name of the ShardingDatabase instance + namespace: shns # Kubernetes namespace to deploy into + +# Specs for the ShardingDatabase instance spec: + + # Details of the individual shard databases shard: - - name: shard1 - storageSizeInGb: 50 - imagePullPolicy: "Always" - shardGroup: shardgroup1 - shardRegion: primary + - name: shard1 # Name of the shard database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image + shardGroup: shardgroup1 # Shard Group name for the Shard Database + shardRegion: primary # Name of the region for the Shard Database - name: shard2 storageSizeInGb: 50 imagePullPolicy: "Always" @@ -25,34 +34,63 @@ spec: imagePullPolicy: "Always" shardGroup: shardgroup1 shardRegion: primary + + # Details of the individual shard databases catalog: - - name: catalog - storageSizeInGb: 50 - imagePullPolicy: "Always" + - name: catalog # Name of the Catalog database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image + + # Details of the GSM Pods gsm: - - name: gsm1 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: primary - - name: gsm2 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: standby + - name: gsm1 # Name of the Primary Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: primary # Name of the Primary region for the Shard Database + + - name: gsm2 # Name of the Standby Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: standby # Name of the Standby region for the Shard Database + + # Kubernetes Storage Class Name storageClass: oci + + # Database Container Image to be used for Catalog and Shard Database dbImage: container-registry.oracle.com/database/free:latest + + # Database Conrainer Image Pull Secret dbImagePullSecret: ocr-reg-cred + + # GSM Container Image to be used for GSM Pods gsmImage: container-registry.oracle.com/database/gsm:latest + + # GSM Conrainer Image Pull Secret gsmImagePullSecret: ocr-reg-cred + + # Specify the Database Edition as "free" for Oracle Database FREE dbEdition: "free" + + # Parameter value for RAFT Replication for the Oracle Globally Distributed Database replicationType: "native" + + # Whether an External Load Balancer has to be configured for the Catalog, Shard and GSM Pods isExternalSvc: False + + # If set to true, the Persistent Volumes will be deleted when the deployment for the Oracle Globally Distributed Database is deleted isDeleteOraPvc: True + + # Details of the Database Secret dbSecret: name: db-user-pass-rsa pwdFileName: pwdfile.enc keyFileName: key.pem + + # Details of Services to be created for the Oracle Globally Distributed Database Resource gsmService: - name: oltp_rw_svc role: primary + ruMode: READWRITE # Specify the ruMode for the service as READWRITE in RAFT Replication Setup - name: oltp_ro_svc role: primary + ruMode: READONLY # Specify the ruMode for the service as READWRITE in RAFT Replication Setup diff --git a/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_shard_prov_chunks.yaml b/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_shard_prov_chunks.yaml deleted file mode 100644 index 0230eac2..00000000 --- a/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_shard_prov_chunks.yaml +++ /dev/null @@ -1,61 +0,0 @@ -# -# Copyright (c) 2022, Oracle and/or its affiliates. -# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. -# ---- -apiVersion: database.oracle.com/v4 -kind: ShardingDatabase -metadata: - name: shardingdatabase-sample - namespace: shns -spec: - shard: - - name: shard1 - storageSizeInGb: 50 - imagePullPolicy: "Always" - shardGroup: shardgroup1 - shardRegion: primary - - name: shard2 - storageSizeInGb: 50 - imagePullPolicy: "Always" - shardGroup: shardgroup1 - shardRegion: primary - - name: shard3 - storageSizeInGb: 50 - imagePullPolicy: "Always" - shardGroup: shardgroup1 - shardRegion: primary - catalog: - - name: catalog - storageSizeInGb: 50 - imagePullPolicy: "Always" - envVars: - - name: "CATALOG_CHUNKS" - value: "120" - gsm: - - name: gsm1 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: primary - - name: gsm2 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: standby - storageClass: oci - dbImage: container-registry.oracle.com/database/free:latest - dbImagePullSecret: ocr-reg-cred - gsmImage: container-registry.oracle.com/database/gsm:latest - gsmImagePullSecret: ocr-reg-cred - dbEdition: "free" - replicationType: "native" - isExternalSvc: False - isDeleteOraPvc: True - dbSecret: - name: db-user-pass-rsa - pwdFileName: pwdfile.enc - keyFileName: key.pem - gsmService: - - name: oltp_rw_svc - role: primary - - name: oltp_ro_svc - role: primary diff --git a/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_shard_prov_clone.yaml b/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_shard_prov_clone.yaml deleted file mode 100644 index fcc18da0..00000000 --- a/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_shard_prov_clone.yaml +++ /dev/null @@ -1,83 +0,0 @@ -# -# Copyright (c) 2022, Oracle and/or its affiliates. -# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. -# ---- -apiVersion: database.oracle.com/v4 -kind: ShardingDatabase -metadata: - name: shardingdatabase-sample - namespace: shns -spec: - shard: - - name: shard1 - storageSizeInGb: 50 - nodeSelector: - "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" - pvMatchLabels: - "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" - pvAnnotations: - volume.beta.kubernetes.io/oci-volume-source: ocid1.volume.oc1.phx.abyhqljr3z3w72t6ay5eud7d5w3kdfhktfp6gwb6euy5tzwfaxgmbvwqlvsq - imagePullPolicy: "Always" - shardGroup: shardgroup1 - shardRegion: primary - - name: shard2 - storageSizeInGb: 50 - nodeSelector: - "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" - pvMatchLabels: - "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" - pvAnnotations: - volume.beta.kubernetes.io/oci-volume-source: ocid1.volume.oc1.phx.abyhqljr3z3w72t6ay5eud7d5w3kdfhktfp6gwb6euy5tzwfaxgmbvwqlvsq - imagePullPolicy: "Always" - shardGroup: shardgroup1 - shardRegion: primary - - name: shard3 - storageSizeInGb: 50 - nodeSelector: - "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" - pvMatchLabels: - "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" - pvAnnotations: - volume.beta.kubernetes.io/oci-volume-source: ocid1.volume.oc1.phx.abyhqljr3z3w72t6ay5eud7d5w3kdfhktfp6gwb6euy5tzwfaxgmbvwqlvsq - imagePullPolicy: "Always" - shardGroup: shardgroup1 - shardRegion: primary - catalog: - - name: catalog - storageSizeInGb: 50 - nodeSelector: - "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" - pvMatchLabels: - "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" - pvAnnotations: - volume.beta.kubernetes.io/oci-volume-source: ocid1.volume.oc1.phx.abyhqljr3z3w72t6ay5eud7d5w3kdfhktfp6gwb6euy5tzwfaxgmbvwqlvsq - imagePullPolicy: "Always" - gsm: - - name: gsm1 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: primary - - name: gsm2 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: standby - storageClass: oci - dbImage: container-registry.oracle.com/database/free:latest - dbImagePullSecret: ocr-reg-cred - gsmImage: container-registry.oracle.com/database/gsm:latest - gsmImagePullSecret: ocr-reg-cred - dbEdition: "free" - replicationType: "native" - isExternalSvc: False - isDeleteOraPvc: True - isClone: True - dbSecret: - name: db-user-pass-rsa - pwdFileName: pwdfile.enc - keyFileName: key.pem - gsmService: - - name: oltp_rw_svc - role: primary - - name: oltp_ro_svc - role: primary diff --git a/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_shard_prov_clone_across_ads.yaml b/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_shard_prov_clone_across_ads.yaml deleted file mode 100644 index 0663f8a5..00000000 --- a/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_shard_prov_clone_across_ads.yaml +++ /dev/null @@ -1,91 +0,0 @@ -# -# Copyright (c) 2022, Oracle and/or its affiliates. -# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. -# ---- -apiVersion: database.oracle.com/v4 -kind: ShardingDatabase -metadata: - name: shardingdatabase-sample - namespace: shns -spec: - shard: - - name: shard1 - storageSizeInGb: 50 - nodeSelector: - "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" - pvMatchLabels: - "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" - pvAnnotations: - volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq - imagePullPolicy: "Always" - shardGroup: shardgroup1 - shardRegion: primary - - name: shard2 - storageSizeInGb: 50 - nodeSelector: - "failure-domain.beta.kubernetes.io/zone": "PHX-AD-2" - pvMatchLabels: - "failure-domain.beta.kubernetes.io/zone": "PHX-AD-2" - pvAnnotations: - volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq - imagePullPolicy: "Always" - shardGroup: shardgroup1 - shardRegion: primary - - name: shard3 - storageSizeInGb: 50 - nodeSelector: - "failure-domain.beta.kubernetes.io/zone": "PHX-AD-3" - pvMatchLabels: - "failure-domain.beta.kubernetes.io/zone": "PHX-AD-3" - pvAnnotations: - volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq - imagePullPolicy: "Always" - shardGroup: shardgroup1 - shardRegion: primary - catalog: - - name: catalog - storageSizeInGb: 50 - nodeSelector: - "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" - pvMatchLabels: - "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" - pvAnnotations: - volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq - imagePullPolicy: "Always" - gsm: - - name: gsm1 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: primary - nodeSelector: - "failure-domain.beta.kubernetes.io/zone": "PHX-AD-3" - pvMatchLabels: - "failure-domain.beta.kubernetes.io/zone": "PHX-AD-3" - - name: gsm2 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: standby - nodeSelector: - "failure-domain.beta.kubernetes.io/zone": "PHX-AD-3" - pvMatchLabels: - "failure-domain.beta.kubernetes.io/zone": "PHX-AD-3" - storageClass: oci - dbImage: container-registry.oracle.com/database/free:latest - dbImagePullSecret: ocr-reg-cred - gsmImage: container-registry.oracle.com/database/gsm:latest - gsmImagePullSecret: ocr-reg-cred - dbEdition: "free" - replicationType: "native" - isExternalSvc: False - isDeleteOraPvc: True - isClone: True - dbSecret: - name: db-user-pass-rsa - pwdFileName: pwdfile.enc - keyFileName: key.pem - gsmService: - - name: oltp_rw_svc - role: primary - - name: oltp_ro_svc - role: primary diff --git a/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_shard_prov_delshard.yaml b/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_shard_prov_delshard.yaml deleted file mode 100644 index ce194246..00000000 --- a/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_shard_prov_delshard.yaml +++ /dev/null @@ -1,69 +0,0 @@ -# -# Copyright (c) 2022, Oracle and/or its affiliates. -# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. -# ---- -apiVersion: database.oracle.com/v4 -kind: ShardingDatabase -metadata: - name: shardingdatabase-sample - namespace: shns -spec: - shard: - - name: shard1 - storageSizeInGb: 50 - imagePullPolicy: "Always" - shardGroup: shardgroup1 - shardRegion: primary - - name: shard2 - storageSizeInGb: 50 - imagePullPolicy: "Always" - shardGroup: shardgroup1 - shardRegion: primary - - name: shard3 - storageSizeInGb: 50 - imagePullPolicy: "Always" - shardGroup: shardgroup1 - shardRegion: primary - - name: shard4 - isDelete: enable - storageSizeInGb: 50 - imagePullPolicy: "Always" - shardGroup: shardgroup1 - shardRegion: primary - - name: shard5 - storageSizeInGb: 50 - imagePullPolicy: "Always" - shardGroup: shardgroup1 - shardRegion: primary - catalog: - - name: catalog - storageSizeInGb: 50 - imagePullPolicy: "Always" - gsm: - - name: gsm1 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: primary - - name: gsm2 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: standby - storageClass: oci - dbImage: container-registry.oracle.com/database/free:latest - dbImagePullSecret: ocr-reg-cred - gsmImage: container-registry.oracle.com/database/gsm:latest - gsmImagePullSecret: ocr-reg-cred - dbEdition: "free" - replicationType: "native" - isExternalSvc: False - isDeleteOraPvc: True - dbSecret: - name: db-user-pass-rsa - pwdFileName: pwdfile.enc - keyFileName: key.pem - gsmService: - - name: oltp_rw_svc - role: primary - - name: oltp_ro_svc - role: primary diff --git a/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_shard_prov_extshard.yaml b/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_shard_prov_extshard.yaml deleted file mode 100644 index 8848b8c7..00000000 --- a/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_shard_prov_extshard.yaml +++ /dev/null @@ -1,68 +0,0 @@ -# -# Copyright (c) 2022, Oracle and/or its affiliates. -# Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. -# ---- -apiVersion: database.oracle.com/v4 -kind: ShardingDatabase -metadata: - name: shardingdatabase-sample - namespace: shns -spec: - shard: - - name: shard1 - storageSizeInGb: 50 - imagePullPolicy: "Always" - shardGroup: shardgroup1 - shardRegion: primary - - name: shard2 - storageSizeInGb: 50 - imagePullPolicy: "Always" - shardGroup: shardgroup1 - shardRegion: primary - - name: shard3 - storageSizeInGb: 50 - imagePullPolicy: "Always" - shardGroup: shardgroup1 - shardRegion: primary - - name: shard4 - storageSizeInGb: 50 - imagePullPolicy: "Always" - shardGroup: shardgroup1 - shardRegion: primary - - name: shard5 - storageSizeInGb: 50 - imagePullPolicy: "Always" - shardGroup: shardgroup1 - shardRegion: primary - catalog: - - name: catalog - storageSizeInGb: 50 - imagePullPolicy: "Always" - gsm: - - name: gsm1 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: primary - - name: gsm2 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: standby - storageClass: oci - dbImage: container-registry.oracle.com/database/free:latest - dbImagePullSecret: ocr-reg-cred - gsmImage: container-registry.oracle.com/database/gsm:latest - gsmImagePullSecret: ocr-reg-cred - dbEdition: "free" - replicationType: "native" - isExternalSvc: False - isDeleteOraPvc: True - dbSecret: - name: db-user-pass-rsa - pwdFileName: pwdfile.enc - keyFileName: key.pem - gsmService: - - name: oltp_rw_svc - role: primary - - name: oltp_ro_svc - role: primary diff --git a/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_shard_prov_memory_cpu.yaml b/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_shard_prov_memory_cpu.yaml index dce4ba29..191fd6dc 100644 --- a/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_shard_prov_memory_cpu.yaml +++ b/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_shard_prov_memory_cpu.yaml @@ -3,27 +3,40 @@ # Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. # --- +# API version of the ShardingDatabase Custom Resource apiVersion: database.oracle.com/v4 + +# Resource type being created kind: ShardingDatabase + +# Metadata section to define the resource's name and namespace metadata: - name: shardingdatabase-sample - namespace: shns + name: shardingdatabase-sample # Name of the ShardingDatabase instance + namespace: shns # Kubernetes namespace to deploy into + +# Specs for the ShardingDatabase instance spec: + + # Details of the individual shard databases shard: - - name: shard1 - storageSizeInGb: 50 + - name: shard1 # Name of the shard database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod + + # Memory and CPU allocation for the Shard Database pod resources: requests: memory: "1000Mi" cpu: "1000m" + + # SGA and PGA values for the Shard Database envVars: - name: "INIT_SGA_SIZE" value: "600" - name: "INIT_PGA_SIZE" - value: "400" - imagePullPolicy: "Always" - shardGroup: shardgroup1 - shardRegion: primary + value: "400" + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image + shardGroup: shardgroup1 # Shard Group name for the Shard Database + shardRegion: primary # Name of the region for the Shard Database - name: shard2 storageSizeInGb: 50 resources: @@ -34,7 +47,7 @@ spec: - name: "INIT_SGA_SIZE" value: "600" - name: "INIT_PGA_SIZE" - value: "400" + value: "400" imagePullPolicy: "Always" shardGroup: shardgroup1 shardRegion: primary @@ -48,42 +61,71 @@ spec: - name: "INIT_SGA_SIZE" value: "600" - name: "INIT_PGA_SIZE" - value: "400" + value: "400" imagePullPolicy: "Always" shardGroup: shardgroup1 shardRegion: primary + + # Details of the individual shard databases catalog: - - name: catalog - storageSizeInGb: 50 + - name: catalog # Name of the Catalog database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod resources: requests: memory: "1000Mi" - cpu: "1000m" - imagePullPolicy: "Always" + cpu: "1000m" + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image + + # Details of the GSM Pods gsm: - - name: gsm1 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: primary - - name: gsm2 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: standby + - name: gsm1 # Name of the Primary Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: primary # Name of the Primary region for the Shard Database + + - name: gsm2 # Name of the Standby Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: standby # Name of the Standby region for the Shard Database + + # Kubernetes Storage Class Name storageClass: oci + + # Database Container Image to be used for Catalog and Shard Database dbImage: container-registry.oracle.com/database/free:latest + + # Database Conrainer Image Pull Secret dbImagePullSecret: ocr-reg-cred + + # GSM Container Image to be used for GSM Pods gsmImage: container-registry.oracle.com/database/gsm:latest + + # GSM Conrainer Image Pull Secret gsmImagePullSecret: ocr-reg-cred + + # Specify the Database Edition as "free" for Oracle Database FREE dbEdition: "free" - replicationType: "native" + + # Parameter value for RAFT Replication for the Oracle Globally Distributed Database + replicationType: "native" + + # Whether an External Load Balancer has to be configured for the Catalog, Shard and GSM Pods isExternalSvc: False + + # If set to true, the Persistent Volumes will be deleted when the deployment for the Oracle Globally Distributed Database is deleted isDeleteOraPvc: True + + # Details of the Database Secret dbSecret: name: db-user-pass-rsa pwdFileName: pwdfile.enc keyFileName: key.pem + + # Details of Services to be created for the Oracle Globally Distributed Database Resource gsmService: - name: oltp_rw_svc role: primary + ruMode: READWRITE # Specify the ruMode for the service as READWRITE in RAFT Replication Setup - name: oltp_ro_svc role: primary + ruMode: READONLY # Specify the ruMode for the service as READWRITE in RAFT Replication Setup \ No newline at end of file diff --git a/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_shard_prov_send_notification.yaml b/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_shard_prov_send_notification.yaml index 2b410e8b..55690010 100644 --- a/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_shard_prov_send_notification.yaml +++ b/docs/sharding/provisioning/snr_system_sharding/snr_ssharding_shard_prov_send_notification.yaml @@ -3,24 +3,33 @@ # Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. # --- +# API version of the ShardingDatabase Custom Resource apiVersion: database.oracle.com/v4 + +# Resource type being created kind: ShardingDatabase + +# Metadata section to define the resource's name and namespace metadata: - name: shardingdatabase-sample - namespace: shns + name: shardingdatabase-sample # Name of the ShardingDatabase instance + namespace: shns # Kubernetes namespace to deploy into + +# Specs for the ShardingDatabase instance spec: + + # Details of the individual shard databases shard: - - name: shard1 - storageSizeInGb: 50 - nodeSelector: + - name: shard1 # Name of the shard database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod + nodeSelector: # Node Selector the worker node to deploy the Pod. Worker Node with this label will be selected for the Pod. "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" - pvMatchLabels: + pvMatchLabels: # To specify the availability domain where the Persistent Volume will be created. It should be same as the nodeSelector. "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" pvAnnotations: - volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq - imagePullPolicy: "Always" - shardGroup: shardgroup1 - shardRegion: primary + volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq # Specify the OCID of the Block Volume Backup which has the Database Gold Image + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image + shardGroup: shardgroup1 # Shard Group name for the Shard Database + shardRegion: primary # Name of the region for the Shard Database - name: shard2 storageSizeInGb: 50 nodeSelector: @@ -28,7 +37,7 @@ spec: pvMatchLabels: "failure-domain.beta.kubernetes.io/zone": "PHX-AD-2" pvAnnotations: - volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq + volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq imagePullPolicy: "Always" shardGroup: shardgroup1 shardRegion: primary @@ -39,47 +48,82 @@ spec: pvMatchLabels: "failure-domain.beta.kubernetes.io/zone": "PHX-AD-3" pvAnnotations: - volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq + volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq imagePullPolicy: "Always" shardGroup: shardgroup1 shardRegion: primary + + # Details of the individual shard databases catalog: - - name: catalog - storageSizeInGb: 50 + - name: catalog # Name of the Catalog database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod nodeSelector: "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" pvMatchLabels: "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" pvAnnotations: - volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq - imagePullPolicy: "Always" + volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image + + # Details of the GSM Pods gsm: - - name: gsm1 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: primary - - name: gsm2 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: standby + - name: gsm1 # Name of the Primary Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: primary # Name of the Primary region for the Shard Database + + - name: gsm2 # Name of the Standby Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: standby # Name of the Standby region for the Shard Database + + # Kubernetes Storage Class Name storageClass: oci + + # Database Container Image to be used for Catalog and Shard Database dbImage: container-registry.oracle.com/database/free:latest + + # Database Conrainer Image Pull Secret dbImagePullSecret: ocr-reg-cred + + # GSM Container Image to be used for GSM Pods gsmImage: container-registry.oracle.com/database/gsm:latest + + # GSM Conrainer Image Pull Secret gsmImagePullSecret: ocr-reg-cred + + # Specify the Database Edition as "free" for Oracle Database FREE dbEdition: "free" - replicationType: "native" + + # Parameter value for RAFT Replication for the Oracle Globally Distributed Database + replicationType: "native" + + # Whether an External Load Balancer has to be configured for the Catalog, Shard and GSM Pods isExternalSvc: False + + # If set to true, the Persistent Volumes will be deleted when the deployment for the Oracle Globally Distributed Database is deleted isDeleteOraPvc: True - isClone: True + + # To specify if Catalog and Shard Databases will be deployed by cloning from the Gold Image + isClone: True + + # Details of the Database Secret dbSecret: name: db-user-pass-rsa pwdFileName: pwdfile.enc keyFileName: key.pem + + # Specify the configmap created with the credentials of the OCI User Account which will be used for sending the OCI Notification nsConfigMap: onsconfigmap - nsSecret: my-secret + + # Specify the secret which has the privatekey for the OCI User Account to be used for sending the OCI Notification + nsSecret: my-secret + + # Details of Services to be created for the Oracle Globally Distributed Database Resource gsmService: - name: oltp_rw_svc role: primary + ruMode: READWRITE # Specify the ruMode for the service as READWRITE in RAFT Replication Setup - name: oltp_ro_svc role: primary + ruMode: READONLY # Specify the ruMode for the service as READWRITE in RAFT Replication Setup \ No newline at end of file diff --git a/docs/sharding/provisioning/system_sharding/ssharding_provisioning_with_chunks_specified.md b/docs/sharding/provisioning/system_sharding/ssharding_provisioning_with_chunks_specified.md index 649fc7c4..5e287e60 100644 --- a/docs/sharding/provisioning/system_sharding/ssharding_provisioning_with_chunks_specified.md +++ b/docs/sharding/provisioning/system_sharding/ssharding_provisioning_with_chunks_specified.md @@ -21,7 +21,7 @@ In this example, we are using pre-built Oracle Database and Global Data Services * To pull the above images from Oracle Container Registry, create a Kubernetes secret named `ocr-reg-cred` using your credentials with type set to `kubernetes.io/dockerconfigjson` in the namespace `shns`. * If you plan to use images built by you, you need to change `dbImage` and `gsmImage` tag with the images you have built in your enviornment in file `ssharding_shard_prov.yaml`. * To understand the Pre-requisite of Database and Global Data Services docker images, refer [Oracle Database and Global Data Services Docker Images](../../README.md#3-oracle-database-and-global-data-services-docker-images) - * In case you want to use the [Oracle Database 23ai Free](https://www.oracle.com/database/free/get-started/) Image for Database and GSM, then you will need to add the additional parameter `dbEdition: "free"` to the below .yaml file. + * In case you want to use the [Oracle Database 23ai Free](https://www.oracle.com/database/free/get-started/) Image for Database and GSM, then you will need to add the additional parameter `dbEdition: "free"` to the below .yaml file. * Make sure the version of `openssl` in the Oracle Database and Oracle GSM images is compatible with the `openssl` version on the machine where you will run the openssl commands to generated the encrypted password file during the deployment. diff --git a/docs/sharding/provisioning/system_sharding/ssharding_provisioning_with_control_on_resources.md b/docs/sharding/provisioning/system_sharding/ssharding_provisioning_with_control_on_resources.md index d284bf9b..2e3d516f 100644 --- a/docs/sharding/provisioning/system_sharding/ssharding_provisioning_with_control_on_resources.md +++ b/docs/sharding/provisioning/system_sharding/ssharding_provisioning_with_control_on_resources.md @@ -17,7 +17,7 @@ In this example, we are using pre-built Oracle Database and Global Data Services * To pull the above images from Oracle Container Registry, create a Kubernetes secret named `ocr-reg-cred` using your credentials with type set to `kubernetes.io/dockerconfigjson` in the namespace `shns`. * If you plan to use images built by you, you need to change `dbImage` and `gsmImage` tag with the images you have built in your enviornment in file `ssharding_shard_prov_memory_cpu.yaml`. * To understand the Pre-requisite of Database and Global Data Services docker images, refer [Oracle Database and Global Data Services Docker Images](../../README.md#3-oracle-database-and-global-data-services-docker-images) - * In case you want to use the [Oracle Database 23ai Free](https://www.oracle.com/database/free/get-started/) Image for Database and GSM, then you will need to add the additional parameter `dbEdition: "free"` to the below .yaml file. + * In case you want to use the [Oracle Database 23ai Free](https://www.oracle.com/database/free/get-started/) Image for Database and GSM, then you will need to add the additional parameter `dbEdition: "free"` to the below .yaml file. * Make sure the version of `openssl` in the Oracle Database and Oracle GSM images is compatible with the `openssl` version on the machine where you will run the openssl commands to generated the encrypted password file during the deployment. **NOTE:** For Oracle Database 23ai Free, you can control the `CPU` and `Memory` allocation of the PODs using tags `cpu` and `memory` respectively but tags `INIT_SGA_SIZE` and `INIT_PGA_SIZE` to control the SGA and PGA allocation at the database level are `not` supported. diff --git a/docs/sharding/provisioning/system_sharding/ssharding_provisioning_with_notification_using_oci_notification.md b/docs/sharding/provisioning/system_sharding/ssharding_provisioning_with_notification_using_oci_notification.md index e77718f4..8cce1b18 100644 --- a/docs/sharding/provisioning/system_sharding/ssharding_provisioning_with_notification_using_oci_notification.md +++ b/docs/sharding/provisioning/system_sharding/ssharding_provisioning_with_notification_using_oci_notification.md @@ -16,13 +16,13 @@ This example uses `ssharding_shard_prov_send_notification.yaml` to provision an **NOTE:** -* The notification will be sent using a configmap created with the credentials of the OCI user account in this use case. +* The notification will be sent using a configmap created with the credentials of the OCI user account used with this procedure. We will create a topic in Notification Service of the OCI Console and use its OCID. To do this: -1. Create a `configmap_data.txt` file, such as the following, which has the OCI User details that will be used to send notfication: +1. Create a `configmap_data.txt` file, such as the following, which has the OCI User details that will be used to send notification: ```sh user=ocid1.user.oc1........fx7omxfq @@ -31,12 +31,12 @@ To do this: region=us-phoenix-1 topicid=ocid1.onstopic.oc1.phx.aaa............6xrq ``` -2. Create a configmap using the below command using the file created above: +2. Using the file created in step 1, create a configmap with the following command: ```sh kubectl create configmap onsconfigmap --from-file=./configmap_data.txt -n shns ``` -3. Create a key file `priavatekey` having the PEM key of the OCI user being used to send notification: +3. Create a key file called `privatekey` that has the PEM key of the OCI user being used to send notification: ```sh -----BEGIN PRIVATE KEY-G---- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXYxA0DJvEwtVR @@ -61,16 +61,16 @@ To do this: kubectl describe secret my-secret -n shns ``` -In this example, we are using pre-built Oracle Database and Global Data Services container images available on [Oracle Container Registry](https://container-registry.oracle.com/) - * To pull the above images from Oracle Container Registry, create a Kubernetes secret named `ocr-reg-cred` using your credentials with type set to `kubernetes.io/dockerconfigjson` in the namespace `shns`. - * If you plan to use images built by you, you need to change `dbImage` and `gsmImage` tag with the images you have built in your enviornment in file `ssharding_shard_prov_send_notification.yaml`. - * To understand the Pre-requisite of Database and Global Data Services docker images, refer [Oracle Database and Global Data Services Docker Images](../../README.md#3-oracle-database-and-global-data-services-docker-images) - * In case you want to use the [Oracle Database 23ai Free](https://www.oracle.com/database/free/get-started/) Image for Database and GSM, then you will need to add the additional parameter `dbEdition: "free"` to the below .yaml file. - * Make sure the version of `openssl` in the Oracle Database and Oracle GSM images is compatible with the `openssl` version on the machine where you will run the openssl commands to generated the encrypted password file during the deployment. +In this example, we are using pre-built Oracle Database and Global Data Services container images available on the [Oracle Container Registry](https://container-registry.oracle.com/) + * To pull these images from Oracle Container Registry, create a Kubernetes secret named `ocr-reg-cred` using your credentials with type set to `kubernetes.io/dockerconfigjson` in the namespace `shns`. + * If you plan to use images built by you, then you must exchange the `dbImage` and `gsmImage` tags for the images that you have built in your enviornment in file `ssharding_shard_prov_send_notification.yaml`. + * To understand the Database and Global Data Services Docker images prerequsites, see: [Oracle Database and Global Data Services Docker Images](../../README.md#3-oracle-database-and-global-data-services-docker-images) + * If you want to use the [Oracle Database 23ai Free image](https://www.oracle.com/database/free/get-started/) for Database and GSM, then you must add the additional parameter `dbEdition: "free"` to the `.yaml` file used in this procedure. + * Ensure that the version of `openssl` in the Oracle Database and Oracle GSM images is compatible with the `openssl` version on the machine where you will run the openssl commands to generated the encrypted password file during the deployment. -**NOTE:** Provisioning the Sharded Database using Cloning from Database Gold Image is `NOT` supported with Oracle Database 23ai Free. +**NOTE:** Provisioning the Sharded Database using Cloning from Database Gold Image is _not_ supported with Oracle Database 23ai Free. -Use the file: [ssharding_shard_prov_send_notification.yaml](./ssharding_shard_prov_send_notification.yaml) for this use case as below: +Use the file: [ssharding_shard_prov_send_notification.yaml](./ssharding_shard_prov_send_notification.yaml) for this use case: 1. Deploy the `ssharding_shard_prov_send_notification.yaml` file: ```sh diff --git a/docs/sharding/provisioning/system_sharding/ssharding_provisioning_without_db_gold_image.md b/docs/sharding/provisioning/system_sharding/ssharding_provisioning_without_db_gold_image.md index 1ecb0ec1..ae28a635 100644 --- a/docs/sharding/provisioning/system_sharding/ssharding_provisioning_without_db_gold_image.md +++ b/docs/sharding/provisioning/system_sharding/ssharding_provisioning_without_db_gold_image.md @@ -16,12 +16,12 @@ This example uses `ssharding_shard_prov.yaml` to provision an Oracle Database sh In this example, we are using pre-built Oracle Database and Global Data Services container images available on [Oracle Container Registry](https://container-registry.oracle.com/) * To pull the above images from Oracle Container Registry, create a Kubernetes secret named `ocr-reg-cred` using your credentials with type set to `kubernetes.io/dockerconfigjson` in the namespace `shns`. - * If you plan to use images built by you, you need to change `dbImage` and `gsmImage` tag with the images you have built in your enviornment in file `ssharding_shard_prov.yaml`. - * To understand the Pre-requisite of Database and Global Data Services docker images, refer [Oracle Database and Global Data Services Docker Images](../../README.md#3-oracle-database-and-global-data-services-docker-images) - * In case you want to use the [Oracle Database 23ai Free](https://www.oracle.com/database/free/get-started/) Image for Database and GSM, then you will need to add the additional parameter `dbEdition: "free"` to the below .yaml file. - * Make sure the version of `openssl` in the Oracle Database and Oracle GSM images is compatible with the `openssl` version on the machine where you will run the openssl commands to generated the encrypted password file during the deployment. + * If you plan to use images built by you, then you must change `dbImage` and `gsmImage` tag with the images you have built in your enviornment in file `ssharding_shard_prov.yaml`. + * To understand Database and Global Data Services Docker images prerequsites, see [Oracle Database and Global Data Services Docker Images](../../README.md#3-oracle-database-and-global-data-services-docker-images) + * If you want to use the [Oracle Database 23ai Free](https://www.oracle.com/database/free/get-started/) Image for Database and GSM, then you must add the additional parameter `dbEdition: "free"` to the `.yaml` file with this document. + * Ensure the version of `openssl` in the Oracle Database and Oracle GSM images is compatible with the `openssl` version on the machine where you will run the openssl commands to generated the encrypted password file during the deployment. -Use the file: [ssharding_shard_prov.yaml](./ssharding_shard_prov.yaml) for this use case as below: +Use the file: [ssharding_shard_prov.yaml](./ssharding_shard_prov.yaml) for this use case: 1. Deploy the `ssharding_shard_prov.yaml` file: ```sh diff --git a/docs/sharding/provisioning/system_sharding/ssharding_scale_in_delete_an_existing_shard.md b/docs/sharding/provisioning/system_sharding/ssharding_scale_in_delete_an_existing_shard.md index 889de98c..da7cf3fe 100644 --- a/docs/sharding/provisioning/system_sharding/ssharding_scale_in_delete_an_existing_shard.md +++ b/docs/sharding/provisioning/system_sharding/ssharding_scale_in_delete_an_existing_shard.md @@ -17,7 +17,7 @@ In this example, we are using pre-built Oracle Database and Global Data Services * To pull the above images from Oracle Container Registry, create a Kubernetes secret named `ocr-reg-cred` using your credentials with type set to `kubernetes.io/dockerconfigjson` in the namespace `shns`. * If you plan to use images built by you, you need to change `dbImage` and `gsmImage` tag with the images you have built in your enviornment in file `ssharding_shard_prov_delshard.yaml`. * To understand the Pre-requisite of Database and Global Data Services docker images, refer [Oracle Database and Global Data Services Docker Images](../../README.md#3-oracle-database-and-global-data-services-docker-images) - * In case you want to use the [Oracle Database 23ai Free](https://www.oracle.com/database/free/get-started/) Image for Database and GSM, then you will need to add the additional parameter `dbEdition: "free"` to the below .yaml file. + * In case you want to use the [Oracle Database 23ai Free](https://www.oracle.com/database/free/get-started/) Image for Database and GSM, then you will need to add the additional parameter `dbEdition: "free"` to the below .yaml file. * Make sure the version of `openssl` in the Oracle Database and Oracle GSM images is compatible with the `openssl` version on the machine where you will run the openssl commands to generated the encrypted password file during the deployment. NOTE: Use tag `isDelete: enable` to delete the shard you want. diff --git a/docs/sharding/provisioning/system_sharding/ssharding_scale_out_add_shards.md b/docs/sharding/provisioning/system_sharding/ssharding_scale_out_add_shards.md index 5086d887..32ecfc7c 100644 --- a/docs/sharding/provisioning/system_sharding/ssharding_scale_out_add_shards.md +++ b/docs/sharding/provisioning/system_sharding/ssharding_scale_out_add_shards.md @@ -15,7 +15,7 @@ In this example, we are using pre-built Oracle Database and Global Data Services * To pull the above images from Oracle Container Registry, create a Kubernetes secret named `ocr-reg-cred` using your credentials with type set to `kubernetes.io/dockerconfigjson` in the namespace `shns`. * If you plan to use images built by you, you need to change `dbImage` and `gsmImage` tag with the images you have built in your enviornment in file `ssharding_shard_prov_extshard.yaml`. * To understand the Pre-requisite of Database and Global Data Services docker images, refer [Oracle Database and Global Data Services Docker Images](../../README.md#3-oracle-database-and-global-data-services-docker-images) - * If the existing Sharding Topology was deployed using [Oracle Database 23ai Free](https://www.oracle.com/database/free/get-started/) Image for Database and GSM, then the additional parameter `dbEdition: "free"` will be needed for the below .yaml file as well. + * In case you want to use the [Oracle Database 23ai Free](https://www.oracle.com/database/free/get-started/) Image for Database and GSM, then you will need to add the additional parameter `dbEdition: "free"` to the below .yaml file. * Make sure the version of `openssl` in the Oracle Database and Oracle GSM images is compatible with the `openssl` version on the machine where you will run the openssl commands to generated the encrypted password file during the deployment. This use case adds two new shards `shard4`,`shard5` to above Sharding Topology. diff --git a/docs/sharding/provisioning/system_sharding/ssharding_shard_prov.yaml b/docs/sharding/provisioning/system_sharding/ssharding_shard_prov.yaml index 1bdb9ce5..d09e93c7 100644 --- a/docs/sharding/provisioning/system_sharding/ssharding_shard_prov.yaml +++ b/docs/sharding/provisioning/system_sharding/ssharding_shard_prov.yaml @@ -3,18 +3,27 @@ # Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. # --- +# API version of the ShardingDatabase Custom Resource apiVersion: database.oracle.com/v4 + +# Resource type being created kind: ShardingDatabase + +# Metadata section to define the resource's name and namespace metadata: - name: shardingdatabase-sample - namespace: shns + name: shardingdatabase-sample # Name of the ShardingDatabase instance + namespace: shns # Kubernetes namespace to deploy into + +# Specs for the ShardingDatabase instance spec: + + # Details of the individual shard databases shard: - - name: shard1 - storageSizeInGb: 50 - imagePullPolicy: "Always" - shardGroup: shardgroup1 - shardRegion: primary + - name: shard1 # Name of the shard database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image + shardGroup: shardgroup1 # Shard Group name for the Shard Database + shardRegion: primary # Name of the region for the Shard Database - name: shard2 storageSizeInGb: 50 imagePullPolicy: "Always" @@ -25,30 +34,53 @@ spec: imagePullPolicy: "Always" shardGroup: shardgroup1 shardRegion: primary + + # Details of the individual shard databases catalog: - - name: catalog - storageSizeInGb: 50 - imagePullPolicy: "Always" + - name: catalog # Name of the Catalog database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image + + # Details of the GSM Pods gsm: - - name: gsm1 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: primary - - name: gsm2 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: standby + - name: gsm1 # Name of the Primary Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: primary # Name of the Primary region for the Shard Database + + - name: gsm2 # Name of the Standby Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: standby # Name of the Standby region for the Shard Database + + # Kubernetes Storage Class Name storageClass: oci - dbImage: container-registry.oracle.com/database/enterprise_ru:19.25.0.0 + + # Database Container Image to be used for Catalog and Shard Database + dbImage: container-registry.oracle.com/database/enterprise_ru:19.28.0.0 + + # Database Conrainer Image Pull Secret dbImagePullSecret: ocr-reg-cred - gsmImage: container-registry.oracle.com/database/gsm_ru:19.25.0.0 + + # GSM Container Image to be used for GSM Pods + gsmImage: container-registry.oracle.com/database/gsm_ru:19.28.0.0 + + # GSM Conrainer Image Pull Secret gsmImagePullSecret: ocr-reg-cred + + # Whether an External Load Balancer has to be configured for the Catalog, Shard and GSM Pods isExternalSvc: False + + # If set to true, the Persistent Volumes will be deleted when the deployment for the Oracle Globally Distributed Database is deleted isDeleteOraPvc: True + + # Details of the Database Secret dbSecret: name: db-user-pass-rsa pwdFileName: pwdfile.enc keyFileName: key.pem + + # Details of Services to be created for the Oracle Globally Distributed Database Resource gsmService: - name: oltp_rw_svc role: primary diff --git a/docs/sharding/provisioning/system_sharding/ssharding_shard_prov_chunks.yaml b/docs/sharding/provisioning/system_sharding/ssharding_shard_prov_chunks.yaml index 868e8bc1..c261a1cb 100644 --- a/docs/sharding/provisioning/system_sharding/ssharding_shard_prov_chunks.yaml +++ b/docs/sharding/provisioning/system_sharding/ssharding_shard_prov_chunks.yaml @@ -3,18 +3,27 @@ # Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. # --- +# API version of the ShardingDatabase Custom Resource apiVersion: database.oracle.com/v4 + +# Resource type being created kind: ShardingDatabase + +# Metadata section to define the resource's name and namespace metadata: - name: shardingdatabase-sample - namespace: shns + name: shardingdatabase-sample # Name of the ShardingDatabase instance + namespace: shns # Kubernetes namespace to deploy into + +# Specs for the ShardingDatabase instance spec: + + # Details of the individual shard databases shard: - - name: shard1 - storageSizeInGb: 50 - imagePullPolicy: "Always" - shardGroup: shardgroup1 - shardRegion: primary + - name: shard1 # Name of the shard database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image + shardGroup: shardgroup1 # Shard Group name for the Shard Database + shardRegion: primary # Name of the region for the Shard Database - name: shard2 storageSizeInGb: 50 imagePullPolicy: "Always" @@ -25,33 +34,58 @@ spec: imagePullPolicy: "Always" shardGroup: shardgroup1 shardRegion: primary + + # Details of the individual shard databases catalog: - - name: catalog - storageSizeInGb: 50 - imagePullPolicy: "Always" + - name: catalog # Name of the Catalog database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image + + # Specify the environment variables for the Catalog Database envVars: - - name: "CATALOG_CHUNKS" - value: "120" + - name: "CATALOG_CHUNKS" # Specify variable for the the number of chunks to be configured + value: "120" # Specify the value of the variable + + # Details of the GSM Pods gsm: - - name: gsm1 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: primary - - name: gsm2 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: standby + - name: gsm1 # Name of the Primary Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: primary # Name of the Primary region for the Shard Database + + - name: gsm2 # Name of the Standby Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: standby # Name of the Standby region for the Shard Database + + # Kubernetes Storage Class Name storageClass: oci - dbImage: container-registry.oracle.com/database/enterprise_ru:19.25.0.0 + + # Database Container Image to be used for Catalog and Shard Database + dbImage: container-registry.oracle.com/database/enterprise_ru:19.28.0.0 + + # Database Conrainer Image Pull Secret dbImagePullSecret: ocr-reg-cred - gsmImage: container-registry.oracle.com/database/gsm_ru:19.25.0.0 + + # GSM Container Image to be used for GSM Pods + gsmImage: container-registry.oracle.com/database/gsm_ru:19.28.0.0 + + # GSM Conrainer Image Pull Secret gsmImagePullSecret: ocr-reg-cred + + # Whether an External Load Balancer has to be configured for the Catalog, Shard and GSM Pods isExternalSvc: False + + # If set to true, the Persistent Volumes will be deleted when the deployment for the Oracle Globally Distributed Database is deleted isDeleteOraPvc: True + + # Details of the Database Secret dbSecret: name: db-user-pass-rsa pwdFileName: pwdfile.enc keyFileName: key.pem + + # Details of Services to be created for the Oracle Globally Distributed Database Resource gsmService: - name: oltp_rw_svc role: primary diff --git a/docs/sharding/provisioning/system_sharding/ssharding_shard_prov_clone.yaml b/docs/sharding/provisioning/system_sharding/ssharding_shard_prov_clone.yaml index 3cafeba7..6bc1917f 100644 --- a/docs/sharding/provisioning/system_sharding/ssharding_shard_prov_clone.yaml +++ b/docs/sharding/provisioning/system_sharding/ssharding_shard_prov_clone.yaml @@ -3,24 +3,33 @@ # Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. # --- +# API version of the ShardingDatabase Custom Resource apiVersion: database.oracle.com/v4 + +# Resource type being created kind: ShardingDatabase + +# Metadata section to define the resource's name and namespace metadata: - name: shardingdatabase-sample - namespace: shns + name: shardingdatabase-sample # Name of the ShardingDatabase instance + namespace: shns # Kubernetes namespace to deploy into + +# Specs for the ShardingDatabase instance spec: + + # Details of the individual shard databases shard: - - name: shard1 - storageSizeInGb: 50 - nodeSelector: + - name: shard1 # Name of the shard database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod + nodeSelector: # Node Selector the worker node to deploy the Pod. Worker Node with this label will be selected for the Pod. "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" - pvMatchLabels: + pvMatchLabels: # To specify the availability domain where the Persistent Volume will be created. It should be same as the nodeSelector. "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" pvAnnotations: - volume.beta.kubernetes.io/oci-volume-source: ocid1.volume.oc1.phx.abyhqljr3z3w72t6ay5eud7d5w3kdfhktfp6gwb6euy5tzwfaxgmbvwqlvsq - imagePullPolicy: "Always" - shardGroup: shardgroup1 - shardRegion: primary + volume.beta.kubernetes.io/oci-volume-source: ocid1.volume.oc1.phx.abyhqljr3z3w72t6ay5eud7d5w3kdfhktfp6gwb6euy5tzwfaxgmbvwqlvsq # Specify the OCID of the Block Volume which has the Database Gold Image + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image + shardGroup: shardgroup1 # Shard Group name for the Shard Database + shardRegion: primary # Name of the region for the Shard Database - name: shard2 storageSizeInGb: 50 nodeSelector: @@ -28,7 +37,7 @@ spec: pvMatchLabels: "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" pvAnnotations: - volume.beta.kubernetes.io/oci-volume-source: ocid1.volume.oc1.phx.abyhqljr3z3w72t6ay5eud7d5w3kdfhktfp6gwb6euy5tzwfaxgmbvwqlvsq + volume.beta.kubernetes.io/oci-volume-source: ocid1.volume.oc1.phx.abyhqljr3z3w72t6ay5eud7d5w3kdfhktfp6gwb6euy5tzwfaxgmbvwqlvsq imagePullPolicy: "Always" shardGroup: shardgroup1 shardRegion: primary @@ -39,43 +48,68 @@ spec: pvMatchLabels: "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" pvAnnotations: - volume.beta.kubernetes.io/oci-volume-source: ocid1.volume.oc1.phx.abyhqljr3z3w72t6ay5eud7d5w3kdfhktfp6gwb6euy5tzwfaxgmbvwqlvsq + volume.beta.kubernetes.io/oci-volume-source: ocid1.volume.oc1.phx.abyhqljr3z3w72t6ay5eud7d5w3kdfhktfp6gwb6euy5tzwfaxgmbvwqlvsq imagePullPolicy: "Always" shardGroup: shardgroup1 shardRegion: primary + + # Details of the individual shard databases catalog: - - name: catalog - storageSizeInGb: 50 + - name: catalog # Name of the Catalog database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod nodeSelector: "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" pvMatchLabels: "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" pvAnnotations: - volume.beta.kubernetes.io/oci-volume-source: ocid1.volume.oc1.phx.abyhqljr3z3w72t6ay5eud7d5w3kdfhktfp6gwb6euy5tzwfaxgmbvwqlvsq - imagePullPolicy: "Always" + volume.beta.kubernetes.io/oci-volume-source: ocid1.volume.oc1.phx.abyhqljr3z3w72t6ay5eud7d5w3kdfhktfp6gwb6euy5tzwfaxgmbvwqlvsq + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image + + # Details of the GSM Pods gsm: - - name: gsm1 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: primary - - name: gsm2 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: standby + - name: gsm1 # Name of the Primary Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: primary # Name of the Primary region for the Shard Database + + - name: gsm2 # Name of the Standby Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: standby # Name of the Standby region for the Shard Database + + # Kubernetes Storage Class Name storageClass: oci - dbImage: container-registry.oracle.com/database/enterprise_ru:19.25.0.0 + + # Database Container Image to be used for Catalog and Shard Database + dbImage: container-registry.oracle.com/database/enterprise_ru:19.28.0.0 + + # Database Conrainer Image Pull Secret dbImagePullSecret: ocr-reg-cred - gsmImage: container-registry.oracle.com/database/gsm_ru:19.25.0.0 + + # GSM Container Image to be used for GSM Pods + gsmImage: container-registry.oracle.com/database/gsm_ru:19.28.0.0 + + # GSM Conrainer Image Pull Secret gsmImagePullSecret: ocr-reg-cred + + # Whether an External Load Balancer has to be configured for the Catalog, Shard and GSM Pods isExternalSvc: False + + # If set to true, the Persistent Volumes will be deleted when the deployment for the Oracle Globally Distributed Database is deleted isDeleteOraPvc: True - isClone: True + + # To specify if Catalog and Shard Databases will be deployed by cloning from the Gold Image + isClone: True + + # Details of the Database Secret dbSecret: name: db-user-pass-rsa pwdFileName: pwdfile.enc keyFileName: key.pem + + # Details of Services to be created for the Oracle Globally Distributed Database Resource gsmService: - name: oltp_rw_svc role: primary - name: oltp_ro_svc - role: primary + role: primary \ No newline at end of file diff --git a/docs/sharding/provisioning/system_sharding/ssharding_shard_prov_clone_across_ads.yaml b/docs/sharding/provisioning/system_sharding/ssharding_shard_prov_clone_across_ads.yaml index d7ec6365..c0b4760e 100644 --- a/docs/sharding/provisioning/system_sharding/ssharding_shard_prov_clone_across_ads.yaml +++ b/docs/sharding/provisioning/system_sharding/ssharding_shard_prov_clone_across_ads.yaml @@ -3,24 +3,33 @@ # Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. # --- +# API version of the ShardingDatabase Custom Resource apiVersion: database.oracle.com/v4 + +# Resource type being created kind: ShardingDatabase + +# Metadata section to define the resource's name and namespace metadata: - name: shardingdatabase-sample - namespace: shns + name: shardingdatabase-sample # Name of the ShardingDatabase instance + namespace: shns # Kubernetes namespace to deploy into + +# Specs for the ShardingDatabase instance spec: + + # Details of the individual shard databases shard: - - name: shard1 - storageSizeInGb: 50 - nodeSelector: + - name: shard1 # Name of the shard database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod + nodeSelector: # Node Selector the worker node to deploy the Pod. Worker Node with this label will be selected for the Pod. "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" - pvMatchLabels: + pvMatchLabels: # To specify the availability domain where the Persistent Volume will be created.It should be same as the nodeSelector. "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" pvAnnotations: - volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq - imagePullPolicy: "Always" - shardGroup: shardgroup1 - shardRegion: primary + volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq # Specify the OCID of the Block Volume Backup which has the Database Gold Image + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image + shardGroup: shardgroup1 # Shard Group name for the Shard Database + shardRegion: primary # Name of the region for the Shard Database - name: shard2 storageSizeInGb: 50 nodeSelector: @@ -28,7 +37,7 @@ spec: pvMatchLabels: "failure-domain.beta.kubernetes.io/zone": "PHX-AD-2" pvAnnotations: - volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq + volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq imagePullPolicy: "Always" shardGroup: shardgroup1 shardRegion: primary @@ -39,51 +48,76 @@ spec: pvMatchLabels: "failure-domain.beta.kubernetes.io/zone": "PHX-AD-3" pvAnnotations: - volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq + volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq imagePullPolicy: "Always" shardGroup: shardgroup1 shardRegion: primary + + # Details of the individual shard databases catalog: - - name: catalog - storageSizeInGb: 50 + - name: catalog # Name of the Catalog database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod nodeSelector: "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" pvMatchLabels: "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" pvAnnotations: - volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq - imagePullPolicy: "Always" + volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image + + # Details of the GSM Pods gsm: - - name: gsm1 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: primary - nodeSelector: - "failure-domain.beta.kubernetes.io/zone": "PHX-AD-3" - pvMatchLabels: + - name: gsm1 # Name of the Primary Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: primary # Name of the Primary region for the Shard Database + nodeSelector: # Node Selector the worker node to deploy the Pod. Worker Node with this label will be selected for the Pod. "failure-domain.beta.kubernetes.io/zone": "PHX-AD-3" - - name: gsm2 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: standby + pvMatchLabels: # To specify the availability domain where the Persistent Volume will be created. It should be same as the nodeSelector. + "failure-domain.beta.kubernetes.io/zone": "PHX-AD-3" + + - name: gsm2 # Name of the Standby Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: standby # Name of the Standby region for the Shard Database nodeSelector: "failure-domain.beta.kubernetes.io/zone": "PHX-AD-3" pvMatchLabels: - "failure-domain.beta.kubernetes.io/zone": "PHX-AD-3" + "failure-domain.beta.kubernetes.io/zone": "PHX-AD-3" + + # Kubernetes Storage Class Name storageClass: oci - dbImage: container-registry.oracle.com/database/enterprise_ru:19.25.0.0 + + # Database Container Image to be used for Catalog and Shard Database + dbImage: container-registry.oracle.com/database/enterprise_ru:19.28.0.0 + + # Database Conrainer Image Pull Secret dbImagePullSecret: ocr-reg-cred - gsmImage: container-registry.oracle.com/database/gsm_ru:19.25.0.0 + + # GSM Container Image to be used for GSM Pods + gsmImage: container-registry.oracle.com/database/gsm_ru:19.28.0.0 + + # GSM Conrainer Image Pull Secret gsmImagePullSecret: ocr-reg-cred + + # Whether an External Load Balancer has to be configured for the Catalog, Shard and GSM Pods isExternalSvc: False + + # If set to true, the Persistent Volumes will be deleted when the deployment for the Oracle Globally Distributed Database is deleted isDeleteOraPvc: True - isClone: True + + # To specify if Catalog and Shard Databases will be deployed by cloning from the Gold Image + isClone: True + + # Details of the Database Secret dbSecret: name: db-user-pass-rsa pwdFileName: pwdfile.enc keyFileName: key.pem + + # Details of Services to be created for the Oracle Globally Distributed Database Resource gsmService: - name: oltp_rw_svc role: primary - name: oltp_ro_svc - role: primary + role: primary \ No newline at end of file diff --git a/docs/sharding/provisioning/system_sharding/ssharding_shard_prov_delshard.yaml b/docs/sharding/provisioning/system_sharding/ssharding_shard_prov_delshard.yaml index 1017a9d5..01619fd9 100644 --- a/docs/sharding/provisioning/system_sharding/ssharding_shard_prov_delshard.yaml +++ b/docs/sharding/provisioning/system_sharding/ssharding_shard_prov_delshard.yaml @@ -3,18 +3,27 @@ # Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. # --- +# API version of the ShardingDatabase Custom Resource apiVersion: database.oracle.com/v4 + +# Resource type being created kind: ShardingDatabase + +# Metadata section to define the resource's name and namespace metadata: - name: shardingdatabase-sample - namespace: shns + name: shardingdatabase-sample # Name of the ShardingDatabase instance + namespace: shns # Kubernetes namespace to deploy into + +# Specs for the ShardingDatabase instance spec: + + # Details of the individual shard databases shard: - - name: shard1 - storageSizeInGb: 50 - imagePullPolicy: "Always" - shardGroup: shardgroup1 - shardRegion: primary + - name: shard1 # Name of the shard database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image + shardGroup: shardgroup1 # Shard Group name for the Shard Database + shardRegion: primary # Name of the region for the Shard Database - name: shard2 storageSizeInGb: 50 imagePullPolicy: "Always" @@ -26,7 +35,7 @@ spec: shardGroup: shardgroup1 shardRegion: primary - name: shard4 - isDelete: enable + isDelete: enable # Specify this parameter "isDelete: enable" for the shard which has to be deleted during the Scale In Operation storageSizeInGb: 50 imagePullPolicy: "Always" shardGroup: shardgroup1 @@ -36,32 +45,55 @@ spec: imagePullPolicy: "Always" shardGroup: shardgroup1 shardRegion: primary + + # Details of the individual shard databases catalog: - - name: catalog - storageSizeInGb: 50 - imagePullPolicy: "Always" + - name: catalog # Name of the Catalog database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image + + # Details of the GSM Pods gsm: - - name: gsm1 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: primary - - name: gsm2 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: standby + - name: gsm1 # Name of the Primary Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: primary # Name of the Primary region for the Shard Database + + - name: gsm2 # Name of the Standby Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: standby # Name of the Standby region for the Shard Database + + # Kubernetes Storage Class Name storageClass: oci - dbImage: container-registry.oracle.com/database/enterprise_ru:19.25.0.0 + + # Database Container Image to be used for Catalog and Shard Database + dbImage: container-registry.oracle.com/database/enterprise_ru:19.28.0.0 + + # Database Conrainer Image Pull Secret dbImagePullSecret: ocr-reg-cred - gsmImage: container-registry.oracle.com/database/gsm_ru:19.25.0.0 + + # GSM Container Image to be used for GSM Pods + gsmImage: container-registry.oracle.com/database/gsm_ru:19.28.0.0 + + # GSM Conrainer Image Pull Secret gsmImagePullSecret: ocr-reg-cred + + # Whether an External Load Balancer has to be configured for the Catalog, Shard and GSM Pods isExternalSvc: False + + # If set to true, the Persistent Volumes will be deleted when the deployment for the Oracle Globally Distributed Database is deleted isDeleteOraPvc: True + + # Details of the Database Secret dbSecret: name: db-user-pass-rsa pwdFileName: pwdfile.enc keyFileName: key.pem + + # Details of Services to be created for the Oracle Globally Distributed Database Resource gsmService: - name: oltp_rw_svc role: primary - name: oltp_ro_svc - role: primary + role: primary \ No newline at end of file diff --git a/docs/sharding/provisioning/system_sharding/ssharding_shard_prov_extshard.yaml b/docs/sharding/provisioning/system_sharding/ssharding_shard_prov_extshard.yaml index d23052fb..078a7858 100644 --- a/docs/sharding/provisioning/system_sharding/ssharding_shard_prov_extshard.yaml +++ b/docs/sharding/provisioning/system_sharding/ssharding_shard_prov_extshard.yaml @@ -3,18 +3,27 @@ # Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. # --- +# API version of the ShardingDatabase Custom Resource apiVersion: database.oracle.com/v4 + +# Resource type being created kind: ShardingDatabase + +# Metadata section to define the resource's name and namespace metadata: - name: shardingdatabase-sample - namespace: shns + name: shardingdatabase-sample # Name of the ShardingDatabase instance + namespace: shns # Kubernetes namespace to deploy into + +# Specs for the ShardingDatabase instance spec: + + # Details of the individual shard databases shard: - - name: shard1 - storageSizeInGb: 50 - imagePullPolicy: "Always" - shardGroup: shardgroup1 - shardRegion: primary + - name: shard1 # Name of the shard database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image + shardGroup: shardgroup1 # Shard Group name for the Shard Database + shardRegion: primary # Name of the region for the Shard Database - name: shard2 storageSizeInGb: 50 imagePullPolicy: "Always" @@ -25,6 +34,8 @@ spec: imagePullPolicy: "Always" shardGroup: shardgroup1 shardRegion: primary + + # Details of the Additional two shards to be added during the Scale Out Operation - name: shard4 storageSizeInGb: 50 imagePullPolicy: "Always" @@ -34,33 +45,56 @@ spec: storageSizeInGb: 50 imagePullPolicy: "Always" shardGroup: shardgroup1 - shardRegion: primary + shardRegion: primary + + # Details of the individual shard databases catalog: - - name: catalog - storageSizeInGb: 50 - imagePullPolicy: "Always" + - name: catalog # Name of the Catalog database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image + + # Details of the GSM Pods gsm: - - name: gsm1 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: primary - - name: gsm2 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: standby + - name: gsm1 # Name of the Primary Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: primary # Name of the Primary region for the Shard Database + + - name: gsm2 # Name of the Standby Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: standby # Name of the Standby region for the Shard Database + + # Kubernetes Storage Class Name storageClass: oci - dbImage: container-registry.oracle.com/database/enterprise_ru:19.25.0.0 + + # Database Container Image to be used for Catalog and Shard Database + dbImage: container-registry.oracle.com/database/enterprise_ru:19.28.0.0 + + # Database Conrainer Image Pull Secret dbImagePullSecret: ocr-reg-cred - gsmImage: container-registry.oracle.com/database/gsm_ru:19.25.0.0 + + # GSM Container Image to be used for GSM Pods + gsmImage: container-registry.oracle.com/database/gsm_ru:19.28.0.0 + + # GSM Conrainer Image Pull Secret gsmImagePullSecret: ocr-reg-cred + + # Whether an External Load Balancer has to be configured for the Catalog, Shard and GSM Pods isExternalSvc: False + + # If set to true, the Persistent Volumes will be deleted when the deployment for the Oracle Globally Distributed Database is deleted isDeleteOraPvc: True + + # Details of the Database Secret dbSecret: name: db-user-pass-rsa pwdFileName: pwdfile.enc keyFileName: key.pem + + # Details of Services to be created for the Oracle Globally Distributed Database Resource gsmService: - name: oltp_rw_svc role: primary - name: oltp_ro_svc - role: primary + role: primary \ No newline at end of file diff --git a/docs/sharding/provisioning/system_sharding/ssharding_shard_prov_memory_cpu.yaml b/docs/sharding/provisioning/system_sharding/ssharding_shard_prov_memory_cpu.yaml index 075919f7..8876084a 100644 --- a/docs/sharding/provisioning/system_sharding/ssharding_shard_prov_memory_cpu.yaml +++ b/docs/sharding/provisioning/system_sharding/ssharding_shard_prov_memory_cpu.yaml @@ -3,27 +3,40 @@ # Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. # --- +# API version of the ShardingDatabase Custom Resource apiVersion: database.oracle.com/v4 + +# Resource type being created kind: ShardingDatabase + +# Metadata section to define the resource's name and namespace metadata: - name: shardingdatabase-sample - namespace: shns + name: shardingdatabase-sample # Name of the ShardingDatabase instance + namespace: shns # Kubernetes namespace to deploy into + +# Specs for the ShardingDatabase instance spec: + + # Details of the individual shard databases shard: - - name: shard1 - storageSizeInGb: 50 + - name: shard1 # Name of the shard database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod + + # Memory and CPU allocation for the Shard Database pod resources: requests: memory: "1000Mi" cpu: "1000m" + + # SGA and PGA values for the Shard Database envVars: - name: "INIT_SGA_SIZE" value: "600" - name: "INIT_PGA_SIZE" - value: "400" - imagePullPolicy: "Always" - shardGroup: shardgroup1 - shardRegion: primary + value: "400" + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image + shardGroup: shardgroup1 # Shard Group name for the Shard Database + shardRegion: primary # Name of the region for the Shard Database - name: shard2 storageSizeInGb: 50 resources: @@ -34,7 +47,7 @@ spec: - name: "INIT_SGA_SIZE" value: "600" - name: "INIT_PGA_SIZE" - value: "400" + value: "400" imagePullPolicy: "Always" shardGroup: shardgroup1 shardRegion: primary @@ -48,40 +61,65 @@ spec: - name: "INIT_SGA_SIZE" value: "600" - name: "INIT_PGA_SIZE" - value: "400" + value: "400" imagePullPolicy: "Always" shardGroup: shardgroup1 shardRegion: primary + + # Details of the individual shard databases catalog: - - name: catalog - storageSizeInGb: 50 + - name: catalog # Name of the Catalog database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod + + # Memory and CPU allocation for the Catalog Database pod resources: requests: memory: "1000Mi" - cpu: "1000m" - imagePullPolicy: "Always" + cpu: "1000m" + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image + + # Details of the GSM Pods gsm: - - name: gsm1 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: primary - - name: gsm2 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: standby + - name: gsm1 # Name of the Primary Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: primary # Name of the Primary region for the Shard Database + + - name: gsm2 # Name of the Standby Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: standby # Name of the Standby region for the Shard Database + + # Kubernetes Storage Class Name storageClass: oci - dbImage: container-registry.oracle.com/database/enterprise_ru:19.25.0.0 + + # Database Container Image to be used for Catalog and Shard Database + dbImage: container-registry.oracle.com/database/enterprise_ru:19.28.0.0 + + # Database Conrainer Image Pull Secret dbImagePullSecret: ocr-reg-cred - gsmImage: container-registry.oracle.com/database/gsm_ru:19.25.0.0 + + # GSM Container Image to be used for GSM Pods + gsmImage: container-registry.oracle.com/database/gsm_ru:19.28.0.0 + + # GSM Conrainer Image Pull Secret gsmImagePullSecret: ocr-reg-cred + + # Whether an External Load Balancer has to be configured for the Catalog, Shard and GSM Pods isExternalSvc: False + + # If set to true, the Persistent Volumes will be deleted when the deployment for the Oracle Globally Distributed Database is deleted isDeleteOraPvc: True + + # Details of the Database Secret dbSecret: name: db-user-pass-rsa pwdFileName: pwdfile.enc keyFileName: key.pem + + # Details of Services to be created for the Oracle Globally Distributed Database Resource gsmService: - name: oltp_rw_svc role: primary - name: oltp_ro_svc - role: primary + role: primary \ No newline at end of file diff --git a/docs/sharding/provisioning/system_sharding/ssharding_shard_prov_send_notification.yaml b/docs/sharding/provisioning/system_sharding/ssharding_shard_prov_send_notification.yaml index aea6fc7c..abda743c 100644 --- a/docs/sharding/provisioning/system_sharding/ssharding_shard_prov_send_notification.yaml +++ b/docs/sharding/provisioning/system_sharding/ssharding_shard_prov_send_notification.yaml @@ -3,24 +3,33 @@ # Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. # --- +# API version of the ShardingDatabase Custom Resource apiVersion: database.oracle.com/v4 + +# Resource type being created kind: ShardingDatabase + +# Metadata section to define the resource's name and namespace metadata: - name: shardingdatabase-sample - namespace: shns + name: shardingdatabase-sample # Name of the ShardingDatabase instance + namespace: shns # Kubernetes namespace to deploy into + +# Specs for the ShardingDatabase instance spec: + + # Details of the individual shard databases shard: - - name: shard1 - storageSizeInGb: 50 - nodeSelector: + - name: shard1 # Name of the shard database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod + nodeSelector: # Node Selector the worker node to deploy the Pod. Worker Node with this label will be selected for the Pod. "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" - pvMatchLabels: + pvMatchLabels: # To specify the availability domain where the Persistent Volume will be created. It should be same as the nodeSelector. "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" pvAnnotations: - volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq - imagePullPolicy: "Always" - shardGroup: shardgroup1 - shardRegion: primary + volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq # Specify the OCID of the Block Volume Backup which has the Database Gold Image + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image + shardGroup: shardgroup1 # Shard Group name for the Shard Database + shardRegion: primary # Name of the region for the Shard Database - name: shard2 storageSizeInGb: 50 nodeSelector: @@ -28,7 +37,7 @@ spec: pvMatchLabels: "failure-domain.beta.kubernetes.io/zone": "PHX-AD-2" pvAnnotations: - volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq + volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq imagePullPolicy: "Always" shardGroup: shardgroup1 shardRegion: primary @@ -39,46 +48,74 @@ spec: pvMatchLabels: "failure-domain.beta.kubernetes.io/zone": "PHX-AD-3" pvAnnotations: - volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq + volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq imagePullPolicy: "Always" shardGroup: shardgroup1 shardRegion: primary + + # Details of the individual shard databases catalog: - - name: catalog - storageSizeInGb: 50 + - name: catalog # Name of the Catalog database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod nodeSelector: "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" pvMatchLabels: "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" pvAnnotations: - volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq - imagePullPolicy: "Always" + volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image + + # Details of the GSM Pods gsm: - - name: gsm1 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: primary - - name: gsm2 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: standby + - name: gsm1 # Name of the Primary Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: primary # Name of the Primary region for the Shard Database + + - name: gsm2 # Name of the Standby Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: standby # Name of the Standby region for the Shard Database + + # Kubernetes Storage Class Name storageClass: oci - dbImage: container-registry.oracle.com/database/enterprise_ru:19.25.0.0 + + # Database Container Image to be used for Catalog and Shard Database + dbImage: container-registry.oracle.com/database/enterprise_ru:19.28.0.0 + + # Database Conrainer Image Pull Secret dbImagePullSecret: ocr-reg-cred - gsmImage: container-registry.oracle.com/database/gsm_ru:19.25.0.0 + + # GSM Container Image to be used for GSM Pods + gsmImage: container-registry.oracle.com/database/gsm_ru:19.28.0.0 + + # GSM Conrainer Image Pull Secret gsmImagePullSecret: ocr-reg-cred + + # Whether an External Load Balancer has to be configured for the Catalog, Shard and GSM Pods isExternalSvc: False + + # If set to true, the Persistent Volumes will be deleted when the deployment for the Oracle Globally Distributed Database is deleted isDeleteOraPvc: True - isClone: True + + # To specify if Catalog and Shard Databases will be deployed by cloning from the Gold Image + isClone: True + + # Details of the Database Secret dbSecret: name: db-user-pass-rsa pwdFileName: pwdfile.enc keyFileName: key.pem + + # Specify the configmap created with the credentials of the OCI User Account which will be used for sending the OCI Notification nsConfigMap: onsconfigmap - nsSecret: my-secret + + # Specify the secret which has the privatekey for the OCI User Account to be used for sending the OCI Notification + nsSecret: my-secret + + # Details of Services to be created for the Oracle Globally Distributed Database Resource gsmService: - name: oltp_rw_svc role: primary - name: oltp_ro_svc - role: primary - + role: primary \ No newline at end of file diff --git a/docs/sharding/provisioning/user-defined-sharding/udsharding_provisioning_with_control_on_resources.md b/docs/sharding/provisioning/user-defined-sharding/udsharding_provisioning_with_control_on_resources.md index 638b7124..61ce6317 100644 --- a/docs/sharding/provisioning/user-defined-sharding/udsharding_provisioning_with_control_on_resources.md +++ b/docs/sharding/provisioning/user-defined-sharding/udsharding_provisioning_with_control_on_resources.md @@ -14,14 +14,14 @@ This example uses `udsharding_shard_prov_memory_cpu.yaml` to provision an Oracle * Additional tags `INIT_SGA_SIZE` and `INIT_PGA_SIZE` to control the SGA and PGA allocation at the database level * User Defined Sharding is specified using `shardingType: USER` -In this example, we are using pre-built Oracle Database and Global Data Services container images available on [Oracle Container Registry](https://container-registry.oracle.com/) +In this example, we are using pre-built Oracle Database and Global Data Services container images available on the [Oracle Container Registry](https://container-registry.oracle.com/) * To pull the above images from Oracle Container Registry, create a Kubernetes secret named `ocr-reg-cred` using your credentials with type set to `kubernetes.io/dockerconfigjson` in the namespace `shns`. - * If you plan to use images built by you, you need to change `dbImage` and `gsmImage` tag with the images you have built in your enviornment in file `udsharding_shard_prov_memory_cpu.yaml`. - * To understand the Pre-requisite of Database and Global Data Services docker images, refer [Oracle Database and Global Data Services Docker Images](../../README.md#3-oracle-database-and-global-data-services-docker-images) - * In case you want to use the [Oracle Database 23ai Free](https://www.oracle.com/database/free/get-started/) Image for Database and GSM, then you will need to add the additional parameter `dbEdition: "free"` to the below .yaml file. - * Make sure the version of `openssl` in the Oracle Database and Oracle GSM images is compatible with the `openssl` version on the machine where you will run the openssl commands to generated the encrypted password file during the deployment. + * If you plan to use images built by you, then you must change the `dbImage` and `gsmImage` tags with the images that you have built in your enviornment in file `udsharding_shard_prov_memory_cpu.yaml`. + * To understand the Database and Global Data Services Docker images prerequisites, see: [Oracle Database and Global Data Services Docker Images](../../README.md#3-oracle-database-and-global-data-services-docker-images) + * If you want to use the [Oracle Database 23ai Free image](https://www.oracle.com/database/free/get-started/) for Database and GSM, then you must add the additional parameter `dbEdition: "free"` to the `.yaml` file used with this procedure. + * Ensure that the version of `openssl` in the Oracle Database and Oracle GSM images is compatible with the `openssl` version on the machine where you will run the openssl commands to generated the encrypted password file during the deployment. -**NOTE:** For Oracle Database 23ai Free, you can control the `CPU` and `Memory` allocation of the PODs using tags `cpu` and `memory` respectively but tags `INIT_SGA_SIZE` and `INIT_PGA_SIZE` to control the SGA and PGA allocation at the database level are `not` supported. +**NOTE:** For Oracle Database 23ai Free, you can control the `CPU` and `Memory` allocation of the PODs by using tags `cpu` and `memory` respectively, but tags `INIT_SGA_SIZE` and `INIT_PGA_SIZE` to control the SGA and PGA allocation at the database level are _not_ supported. Use the YAML file [udsharding_shard_prov_memory_cpu.yaml](./udsharding_shard_prov_memory_cpu.yaml). diff --git a/docs/sharding/provisioning/user-defined-sharding/udsharding_provisioning_with_notification_using_oci_notification.md b/docs/sharding/provisioning/user-defined-sharding/udsharding_provisioning_with_notification_using_oci_notification.md index fe1ca870..976798e2 100644 --- a/docs/sharding/provisioning/user-defined-sharding/udsharding_provisioning_with_notification_using_oci_notification.md +++ b/docs/sharding/provisioning/user-defined-sharding/udsharding_provisioning_with_notification_using_oci_notification.md @@ -17,7 +17,7 @@ This example uses `udsharding_shard_prov_send_notification.yaml` to provision an **NOTE:** -* The notification will be sent using a configmap created with the credentials of the OCI user account in this use case. +* The notification will be sent using a configmap created with the credentials of the OCI user account used in this procedure. We will create a topic in Notification Service of the OCI Console and use its OCID. @@ -37,7 +37,7 @@ To do this: kubectl create configmap onsconfigmap --from-file=./configmap_data.txt -n shns ``` -3. Create a key file `priavatekey` having the PEM key of the OCI user being used to send notification: +3. Create a key file `privatekey` that has the PEM key of the OCI user being used to send notification: ```sh -----BEGIN PRIVATE KEY-G---- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXYxA0DJvEwtVR @@ -62,16 +62,16 @@ To do this: kubectl describe secret my-secret -n shns ``` -In this example, we are using pre-built Oracle Database and Global Data Services container images available on [Oracle Container Registry](https://container-registry.oracle.com/) - * To pull the above images from Oracle Container Registry, create a Kubernetes secret named `ocr-reg-cred` using your credentials with type set to `kubernetes.io/dockerconfigjson` in the namespace `shns`. - * If you plan to use images built by you, you need to change `dbImage` and `gsmImage` tag with the images you have built in your enviornment in file `udsharding_shard_prov_send_notification.yaml`. - * To understand the Pre-requisite of Database and Global Data Services docker images, refer [Oracle Database and Global Data Services Docker Images](../../README.md#3-oracle-database-and-global-data-services-docker-images) - * In case you want to use the [Oracle Database 23ai Free](https://www.oracle.com/database/free/get-started/) Image for Database and GSM, then you will need to add the additional parameter `dbEdition: "free"` to the below .yaml file. - * Make sure the version of `openssl` in the Oracle Database and Oracle GSM images is compatible with the `openssl` version on the machine where you will run the openssl commands to generated the encrypted password file during the deployment. +In this example, we are using pre-built Oracle Database and Global Data Services container images available on the [Oracle Container Registry](https://container-registry.oracle.com/) + * To pull these images from Oracle Container Registry, create a Kubernetes secret named `ocr-reg-cred` using your credentials with type set to `kubernetes.io/dockerconfigjson` in the namespace `shns`. + * If you plan to use images built by you, then you must exchanage the `dbImage` and `gsmImage` tags for the images that you have built in your enviornment in file `udsharding_shard_prov_send_notification.yaml`. + * To understand the Database and Global Data Services Docker images prerequisites, see: [Oracle Database and Global Data Services Docker Images](../../README.md#3-oracle-database-and-global-data-services-docker-images) + * If you want to use the [Oracle Database 23ai Free image](https://www.oracle.com/database/free/get-started/) for Database and GSM, then you must add the additional parameter `dbEdition: "free"` to the _.yaml_ file used in this procedure. + * Ensure the version of `openssl` in the Oracle Database and Oracle GSM images is compatible with the `openssl` version on the machine where you will run the openssl commands to generated the encrypted password file during the deployment. -**NOTE:** Provisioning the Sharded Database using Cloning from Database Gold Image is `NOT` supported with Oracle Database 23ai Free. +**NOTE:** Provisioning the Sharded Database using Cloning from Database Gold Image is _not_ supported with Oracle Database 23ai Free. -Use the file: [udsharding_shard_prov_send_notification.yaml](./udsharding_shard_prov_send_notification.yaml) for this use case as below: +Use the file: [udsharding_shard_prov_send_notification.yaml](./udsharding_shard_prov_send_notification.yaml) for this use case: 1. Deploy the `udsharding_shard_prov_send_notification.yaml` file: ```sh diff --git a/docs/sharding/provisioning/user-defined-sharding/udsharding_provisioning_without_db_gold_image.md b/docs/sharding/provisioning/user-defined-sharding/udsharding_provisioning_without_db_gold_image.md index b0378e04..cd187f2e 100644 --- a/docs/sharding/provisioning/user-defined-sharding/udsharding_provisioning_without_db_gold_image.md +++ b/docs/sharding/provisioning/user-defined-sharding/udsharding_provisioning_without_db_gold_image.md @@ -15,14 +15,14 @@ This example uses `udsharding_shard_prov.yaml` to provision an Oracle Database s * User Defined Sharding is specified using `shardingType: USER` -In this example, we are using pre-built Oracle Database and Global Data Services container images available on [Oracle Container Registry](https://container-registry.oracle.com/) - * To pull the above images from Oracle Container Registry, create a Kubernetes secret named `ocr-reg-cred` using your credentials with type set to `kubernetes.io/dockerconfigjson` in the namespace `shns`. - * If you plan to use images built by you, you need to change `dbImage` and `gsmImage` tag with the images you have built in your enviornment in file `udsharding_shard_prov.yaml`. - * To understand the Pre-requisite of Database and Global Data Services docker images, refer [Oracle Database and Global Data Services Docker Images](../../README.md#3-oracle-database-and-global-data-services-docker-images) - * In case you want to use the [Oracle Database 23ai Free](https://www.oracle.com/database/free/get-started/) Image for Database and GSM, then you will need to add the additional parameter `dbEdition: "free"` to the below .yaml file. - * Make sure the version of `openssl` in the Oracle Database and Oracle GSM images is compatible with the `openssl` version on the machine where you will run the openssl commands to generated the encrypted password file during the deployment. - -Use the file: [udsharding_shard_prov.yaml](./udsharding_shard_prov.yaml) for this use case as below: +In this example, we are using pre-built Oracle Database and Global Data Services container images available on the [Oracle Container Registry](https://container-registry.oracle.com/) + * To pull these images from Oracle Container Registry, create a Kubernetes secret named `ocr-reg-cred` using your credentials with type set to `kubernetes.io/dockerconfigjson` in the namespace `shns`. + * If you plan to use images built by you, then you must exchange the `dbImage` and `gsmImage` tags for the images that you have built in your enviornment in file `udsharding_shard_prov.yaml`. + * To understand the Database and Global Data Services docker images prerequisites, see: [Oracle Database and Global Data Services Docker Images](../../README.md#3-oracle-database-and-global-data-services-docker-images) + * If you want to use the [Oracle Database 23ai Free image](https://www.oracle.com/database/free/get-started/) for Database and GSM, then you must add the additional parameter `dbEdition: "free"` to the `.yaml` file usd in this procedure. + * Ensure that the version of `openssl` in the Oracle Database and Oracle GSM images is compatible with the `openssl` version on the machine where you will run the openssl commands to generated the encrypted password file during the deployment. + +Use the file: [udsharding_shard_prov.yaml](./udsharding_shard_prov.yaml) for this use case: 1. Deploy the `udsharding_shard_prov.yaml` file: ```sh diff --git a/docs/sharding/provisioning/user-defined-sharding/udsharding_scale_in_delete_an_existing_shard.md b/docs/sharding/provisioning/user-defined-sharding/udsharding_scale_in_delete_an_existing_shard.md index 673e455e..60a8b5c7 100644 --- a/docs/sharding/provisioning/user-defined-sharding/udsharding_scale_in_delete_an_existing_shard.md +++ b/docs/sharding/provisioning/user-defined-sharding/udsharding_scale_in_delete_an_existing_shard.md @@ -4,7 +4,7 @@ This use case demonstrates how to delete an existing Shard from an existing Oracle Database sharding topology with User Defined Sharding provisioned using Oracle Database Sharding controller. -In this use case, the existing database Sharding is having: +In this use case, the existing database Sharding has the following: * Primary GSM Pods `gsm1` and standby GSM Pod `gsm2` * Five sharding Pods: `shard1`,`shard2`,`shard3`,`shard4` and `shard5` @@ -12,24 +12,24 @@ In this use case, the existing database Sharding is having: * Namespace: `shns` * User Defined Sharding is specified using `shardingType: USER` -In this example, we are using pre-built Oracle Database and Global Data Services container images available on [Oracle Container Registry](https://container-registry.oracle.com/) - * To pull the above images from Oracle Container Registry, create a Kubernetes secret named `ocr-reg-cred` using your credentials with type set to `kubernetes.io/dockerconfigjson` in the namespace `shns`. - * If you plan to use images built by you, you need to change `dbImage` and `gsmImage` tag with the images you have built in your enviornment in file `udsharding_shard_prov_delshard.yaml`. - * To understand the Pre-requisite of Database and Global Data Services docker images, refer [Oracle Database and Global Data Services Docker Images](../../README.md#3-oracle-database-and-global-data-services-docker-images) - * In case you want to use the [Oracle Database 23ai Free](https://www.oracle.com/database/free/get-started/) Image for Database and GSM, then you will need to add the additional parameter `dbEdition: "free"` to the below .yaml file. - * Make sure the version of `openssl` in the Oracle Database and Oracle GSM images is compatible with the `openssl` version on the machine where you will run the openssl commands to generated the encrypted password file during the deployment. +In this example, we are using pre-built Oracle Database and Global Data Services container images available on the [Oracle Container Registry](https://container-registry.oracle.com/) + * To pull these images from Oracle Container Registry, create a Kubernetes secret named `ocr-reg-cred` using your credentials with type set to `kubernetes.io/dockerconfigjson` in the namespace `shns`. + * If you plan to use images built by you, then you need to change the `dbImage` and `gsmImage` tags with the images you have built in your enviornment in the file `udsharding_shard_prov_delshard.yaml`. + * To understand the Database and Global Data Services Docker images prerequisites, see: [Oracle Database and Global Data Services Docker Images](../../README.md#3-oracle-database-and-global-data-services-docker-images) + * If you want to use the [Oracle Database 23ai Free image](https://www.oracle.com/database/free/get-started/) for Database and GSM, then you must add the additional parameter `dbEdition: "free"` to the `.yaml` file we show in these steps. + * Ensure that the version of `openssl` in the Oracle Database and Oracle GSM images is compatible with the `openssl` version on the machine where you will run the `openssl` commands to generate the encrypted password file during the deployment. -**NOTE:** Use tag `isDelete: enable` to delete the shard you want. +**NOTE:** Use the tag `isDelete: enable` to delete the shard that you want to remove. -This use case deletes the shard `shard4` from the above Sharding Topology. +This use case deletes the shard `shard4` from this Sharding Topology. -Use the file: [udsharding_shard_prov_delshard.yaml](./udsharding_shard_prov_delshard.yaml) for this use case as below: +Use the file: [udsharding_shard_prov_delshard.yaml](./udsharding_shard_prov_delshard.yaml) for this use case, as described in the following steps: -1. Move out the chunks from the shard to be deleted to another shard. For example, in the current case, before deleting the `shard4`, if you want to move the chunks from `shard4` to `shard2`, then you can run the below `kubectl` command where `/u01/app/oracle/product/23ai/gsmhome_1` is the GSM HOME: +1. Move out the chunks from the shard to be deleted to another shard. For example, in the current case, before deleting `shard4`, if you want to move the chunks from `shard4` to `shard2`, then you can run the `kubectl` command where `/u01/app/oracle/product/23ai/gsmhome_1` is the GSM HOME: ```sh kubectl exec -it pod/gsm1-0 -n shns -- /u01/app/oracle/product/23ai/gsmhome_1/bin/gdsctl "move chunk -chunk all -source shard4_shard4pdb -target shard4_shard4pdb" ``` -2. Confirm the shard to be deleted (`shard4` in this case) is not having any chunk using below command: +2. To confirm that the shard that you want to be deleted (`shard4` in this case) does not have any chunks, use the following command: ```sh kubectl exec -it pod/gsm1-0 -n shns -- /u01/app/oracle/product/23ai/gsmhome_1/bin/gdsctl "config chunks" ``` @@ -46,12 +46,12 @@ Use the file: [udsharding_shard_prov_delshard.yaml](./udsharding_shard_prov_dels ``` **NOTE:** -- After you apply `udsharding_shard_prov_delshard.yaml`, the change may not be visible immediately and it may take some time for the delete operation to complete. -- If the shard, that you are trying to delete, is still having chunks, then the you will see message like below in the logs of the Oracle Database Operator Pod. +- After you apply `udsharding_shard_prov_delshard.yaml`, the change may not be visible immediately. It can take some time for the delete operation to complete. +- If the shard that you are trying to delete still contains chunks, then a message such as the following is displayed in the logs of the Oracle Database Operator Pod: ```sh INFO controllers.database.ShardingDatabase manual intervention required ``` - In this case, you will need to first move out the chunks from the shard to be deleted using Step 2 above and then apply the file in Step 3 to delete that shard. + When you see that message, you are required to first move the chunks out of the shard that you want to delete using Step 2 as described above, and then apply the file in Step 3 to delete that shard. To check the status, use the following command: ```sh diff --git a/docs/sharding/provisioning/user-defined-sharding/udsharding_scale_out_add_shards.md b/docs/sharding/provisioning/user-defined-sharding/udsharding_scale_out_add_shards.md index abdc53ff..c1ec963e 100644 --- a/docs/sharding/provisioning/user-defined-sharding/udsharding_scale_out_add_shards.md +++ b/docs/sharding/provisioning/user-defined-sharding/udsharding_scale_out_add_shards.md @@ -4,7 +4,7 @@ This use case demonstrates adding a new shard to an existing Oracle Database sharding topology with User Defined Sharding provisioned earlier using Oracle Database Sharding controller. -In this use case, the existing Oracle Database sharding topology is having: +In this use case, the existing Oracle Database sharding topology is as follows: * Primary GSM Pods `gsm1` and standby GSM Pod `gsm2` * Three sharding Pods: `shard1`, `shard2` and `shard3` @@ -12,16 +12,16 @@ In this use case, the existing Oracle Database sharding topology is having: * Namespace: `shns` * User Defined Sharding is specified using `shardingType: USER` -In this example, we are using pre-built Oracle Database and Global Data Services container images available on [Oracle Container Registry](https://container-registry.oracle.com/) - * To pull the above images from Oracle Container Registry, create a Kubernetes secret named `ocr-reg-cred` using your credentials with type set to `kubernetes.io/dockerconfigjson` in the namespace `shns`. - * If you plan to use images built by you, you need to change `dbImage` and `gsmImage` tag with the images you have built in your enviornment in file `udsharding_shard_prov_extshard.yaml`. - * To understand the Pre-requisite of Database and Global Data Services docker images, refer [Oracle Database and Global Data Services Docker Images](../../README.md#3-oracle-database-and-global-data-services-docker-images) - * If the existing Sharding Topology was deployed using [Oracle Database 23ai Free](https://www.oracle.com/database/free/get-started/) Image for Database and GSM, then the additional parameter `dbEdition: "free"` will be needed for the below .yaml file as well. - * Make sure the version of `openssl` in the Oracle Database and Oracle GSM images is compatible with the `openssl` version on the machine where you will run the openssl commands to generated the encrypted password file during the deployment. +In this example, we are using pre-built Oracle Database and Global Data Services container images available on the [Oracle Container Registry](https://container-registry.oracle.com/) + * To pull these images from Oracle Container Registry, create a Kubernetes secret named `ocr-reg-cred` using your credentials with type set to `kubernetes.io/dockerconfigjson` in the namespace `shns`. + * If you plan to use images built by you, then you must exchange the `dbImage` and `gsmImage` tags for the images that you have built in your enviornment in file `udsharding_shard_prov_extshard.yaml`. + * To understand the Database and Global Data Services Docker images prerequisites, see: [Oracle Database and Global Data Services Docker Images](../../README.md#3-oracle-database-and-global-data-services-docker-images) + * If you want to use the [Oracle Database 23ai Free image](https://www.oracle.com/database/free/get-started/) for Database and GSM, then you must add the additional parameter `dbEdition: "free"` to the `.yaml` file used with this procedure. + * Ensure the version of `openssl` in the Oracle Database and Oracle GSM images is compatible with the `openssl` version on the machine where you will run the openssl commands to generated the encrypted password file during the deployment. -This use case adds two new shards `shard4`,`shard5` to above Sharding Topology. +This use case adds two new shards `shard4`,`shard5` to the Sharding Topology. -Use the file: [udsharding_shard_prov_extshard.yaml](./udsharding_shard_prov_extshard.yaml) for this use case as below: +Use the file: [udsharding_shard_prov_extshard.yaml](./udsharding_shard_prov_extshard.yaml) for this use case: 1. Deploy the `udsharding_shard_prov_extshard.yaml` file: ```sh diff --git a/docs/sharding/provisioning/user-defined-sharding/udsharding_shard_prov.yaml b/docs/sharding/provisioning/user-defined-sharding/udsharding_shard_prov.yaml index c9f20eb3..6553fdb3 100644 --- a/docs/sharding/provisioning/user-defined-sharding/udsharding_shard_prov.yaml +++ b/docs/sharding/provisioning/user-defined-sharding/udsharding_shard_prov.yaml @@ -3,18 +3,27 @@ # Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. # --- +# API version of the ShardingDatabase Custom Resource apiVersion: database.oracle.com/v4 + +# Resource type being created kind: ShardingDatabase + +# Metadata section to define the resource's name and namespace metadata: - name: shardingdatabase-sample - namespace: shns + name: shardingdatabase-sample # Name of the ShardingDatabase instance + namespace: shns # Kubernetes namespace to deploy into + +# Specs for the ShardingDatabase instance spec: + + # Details of the individual shard databases shard: - - name: shard1 - storageSizeInGb: 50 - imagePullPolicy: "Always" - shardSpace: sspace1 - shardRegion: primary + - name: shard1 # Name of the shard database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image + shardSpace: sspace1 # Shard Space name for the Shard Database with User Defined Sharding + shardRegion: primary # Name of the region for the Shard Database - name: shard2 storageSizeInGb: 50 imagePullPolicy: "Always" @@ -25,33 +34,58 @@ spec: imagePullPolicy: "Always" shardSpace: sspace3 shardRegion: primary + + # Details of the individual shard databases catalog: - - name: catalog - storageSizeInGb: 50 - imagePullPolicy: "Always" + - name: catalog # Name of the Catalog database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image + + # Details of the GSM Pods gsm: - - name: gsm1 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: primary - - name: gsm2 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: standby + - name: gsm1 # Name of the Primary Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: primary # Name of the Primary region for the Shard Database + + - name: gsm2 # Name of the Standby Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: standby # Name of the Standby region for the Shard Database + + # Kubernetes Storage Class Name storageClass: oci - dbImage: container-registry.oracle.com/database/enterprise_ru:19.25.0.0 + + # Database Container Image to be used for Catalog and Shard Database + dbImage: container-registry.oracle.com/database/enterprise_ru:19.28.0.0 + + # Database Conrainer Image Pull Secret dbImagePullSecret: ocr-reg-cred - gsmImage: container-registry.oracle.com/database/gsm_ru:19.25.0.0 + + # GSM Container Image to be used for GSM Pods + gsmImage: container-registry.oracle.com/database/gsm_ru:19.28.0.0 + + # GSM Conrainer Image Pull Secret gsmImagePullSecret: ocr-reg-cred + + # Type of the Sharding. By Default, it is System Sharding unless specified to be USER or NATIVE shardingType: USER + + # Whether an External Load Balancer has to be configured for the Catalog, Shard and GSM Pods isExternalSvc: False + + # If set to true, the Persistent Volumes will be deleted when the deployment for the Oracle Globally Distributed Database is deleted isDeleteOraPvc: True + + # Details of the Database Secret dbSecret: name: db-user-pass-rsa pwdFileName: pwdfile.enc keyFileName: key.pem + + # Details of Services to be created for the Oracle Globally Distributed Database Resource gsmService: - name: oltp_rw_svc role: primary - name: oltp_ro_svc - role: primary + role: primary \ No newline at end of file diff --git a/docs/sharding/provisioning/user-defined-sharding/udsharding_shard_prov_clone.yaml b/docs/sharding/provisioning/user-defined-sharding/udsharding_shard_prov_clone.yaml index d7e5ce78..522f6cd9 100644 --- a/docs/sharding/provisioning/user-defined-sharding/udsharding_shard_prov_clone.yaml +++ b/docs/sharding/provisioning/user-defined-sharding/udsharding_shard_prov_clone.yaml @@ -3,24 +3,33 @@ # Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. # --- +# API version of the ShardingDatabase Custom Resource apiVersion: database.oracle.com/v4 + +# Resource type being created kind: ShardingDatabase + +# Metadata section to define the resource's name and namespace metadata: - name: shardingdatabase-sample - namespace: shns + name: shardingdatabase-sample # Name of the ShardingDatabase instance + namespace: shns # Kubernetes namespace to deploy into + +# Specs for the ShardingDatabase instance spec: + + # Details of the individual shard databases shard: - - name: shard1 - storageSizeInGb: 50 - imagePullPolicy: "Always" - nodeSelector: + - name: shard1 # Name of the shard database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image + nodeSelector: # Node Selector the worker node to deploy the Pod. Worker Node with this label will be selected for the Pod. "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" - pvMatchLabels: + pvMatchLabels: # To specify the availability domain where the Persistent Volume will be created. It should be same as the nodeSelector. "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" pvAnnotations: - volume.beta.kubernetes.io/oci-volume-source: ocid1.volume.oc1.phx.abyhqljr3z3w72t6ay5eud7d5w3kdfhktfp6gwb6euy5tzwfaxgmbvwqlvsq - shardSpace: sspace1 - shardRegion: primary + volume.beta.kubernetes.io/oci-volume-source: ocid1.volume.oc1.phx.abyhqljr3z3w72t6ay5eud7d5w3kdfhktfp6gwb6euy5tzwfaxgmbvwqlvsq # Specify the OCID of the Block Volume which has the Database Gold Image + shardSpace: sspace1 # Shard Space name for the Shard Database with User Defined Sharding + shardRegion: primary # Name of the region for the Shard Database - name: shard2 storageSizeInGb: 50 imagePullPolicy: "Always" @@ -40,43 +49,70 @@ spec: pvMatchLabels: "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" pvAnnotations: - volume.beta.kubernetes.io/oci-volume-source: ocid1.volume.oc1.phx.abyhqljr3z3w72t6ay5eud7d5w3kdfhktfp6gwb6euy5tzwfaxgmbvwqlvsq + volume.beta.kubernetes.io/oci-volume-source: ocid1.volume.oc1.phx.abyhqljr3z3w72t6ay5eud7d5w3kdfhktfp6gwb6euy5tzwfaxgmbvwqlvsq shardSpace: sspace3 shardRegion: primary + + # Details of the individual shard databases catalog: - - name: catalog - storageSizeInGb: 50 - imagePullPolicy: "Always" + - name: catalog # Name of the Catalog database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image nodeSelector: "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" pvMatchLabels: "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" pvAnnotations: - volume.beta.kubernetes.io/oci-volume-source: ocid1.volume.oc1.phx.abyhqljr3z3w72t6ay5eud7d5w3kdfhktfp6gwb6euy5tzwfaxgmbvwqlvsq + volume.beta.kubernetes.io/oci-volume-source: ocid1.volume.oc1.phx.abyhqljr3z3w72t6ay5eud7d5w3kdfhktfp6gwb6euy5tzwfaxgmbvwqlvsq + + # Details of the GSM Pods gsm: - - name: gsm1 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: primary - - name: gsm2 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: standby + - name: gsm1 # Name of the Primary Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: primary # Name of the Primary region for the Shard Database + + - name: gsm2 # Name of the Standby Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: standby # Name of the Standby region for the Shard Database + + # Kubernetes Storage Class Name storageClass: oci - dbImage: container-registry.oracle.com/database/enterprise_ru:19.25.0.0 + + # Database Container Image to be used for Catalog and Shard Database + dbImage: container-registry.oracle.com/database/enterprise_ru:19.28.0.0 + + # Database Conrainer Image Pull Secret dbImagePullSecret: ocr-reg-cred - gsmImage: container-registry.oracle.com/database/gsm_ru:19.25.0.0 + + # GSM Container Image to be used for GSM Pods + gsmImage: container-registry.oracle.com/database/gsm_ru:19.28.0.0 + + # GSM Conrainer Image Pull Secret gsmImagePullSecret: ocr-reg-cred + + # Type of the Sharding. By Default, it is System Sharding unless specified to be USER or NATIVE shardingType: USER + + # To specify if Catalog and Shard Databases will be deployed by cloning from the Gold Image + isClone: True + + # Whether an External Load Balancer has to be configured for the Catalog, Shard and GSM Pods isExternalSvc: False + + # If set to true, the Persistent Volumes will be deleted when the deployment for the Oracle Globally Distributed Database is deleted isDeleteOraPvc: True - isClone: True + + # Details of the Database Secret dbSecret: name: db-user-pass-rsa pwdFileName: pwdfile.enc keyFileName: key.pem + + # Details of Services to be created for the Oracle Globally Distributed Database Resource gsmService: - name: oltp_rw_svc role: primary - name: oltp_ro_svc - role: primary + role: primary \ No newline at end of file diff --git a/docs/sharding/provisioning/user-defined-sharding/udsharding_shard_prov_clone_across_ads.yaml b/docs/sharding/provisioning/user-defined-sharding/udsharding_shard_prov_clone_across_ads.yaml index ae02c7fe..0fc7ed0d 100644 --- a/docs/sharding/provisioning/user-defined-sharding/udsharding_shard_prov_clone_across_ads.yaml +++ b/docs/sharding/provisioning/user-defined-sharding/udsharding_shard_prov_clone_across_ads.yaml @@ -3,24 +3,33 @@ # Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. # --- +# API version of the ShardingDatabase Custom Resource apiVersion: database.oracle.com/v4 + +# Resource type being created kind: ShardingDatabase + +# Metadata section to define the resource's name and namespace metadata: - name: shardingdatabase-sample - namespace: shns + name: shardingdatabase-sample # Name of the ShardingDatabase instance + namespace: shns # Kubernetes namespace to deploy into + +# Specs for the ShardingDatabase instance spec: + + # Details of the individual shard databases shard: - - name: shard1 - storageSizeInGb: 50 - imagePullPolicy: "Always" - nodeSelector: + - name: shard1 # Name of the shard database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image + nodeSelector: # Node Selector the worker node to deploy the Pod. Worker Node with this label will be selected for the Pod. "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" - pvMatchLabels: + pvMatchLabels: # To specify the availability domain where the Persistent Volume will be created. It should be same as the nodeSelector. "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" pvAnnotations: - volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq - shardSpace: sspace1 - shardRegion: primary + volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq # Specify the OCID of the Block Volume Backup which has the Database Gold Image + shardSpace: sspace1 # Shard Space name for the Shard Database with User Defined Sharding + shardRegion: primary # Name of the region for the Shard Database - name: shard2 storageSizeInGb: 50 imagePullPolicy: "Always" @@ -40,51 +49,78 @@ spec: pvMatchLabels: "failure-domain.beta.kubernetes.io/zone": "PHX-AD-3" pvAnnotations: - volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq + volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq shardSpace: sspace3 shardRegion: primary + + # Details of the individual shard databases catalog: - - name: catalog - storageSizeInGb: 50 - imagePullPolicy: "Always" + - name: catalog # Name of the Catalog database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image nodeSelector: "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" pvMatchLabels: "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" pvAnnotations: - volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq + volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq + + # Details of the GSM Pods gsm: - - name: gsm1 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: primary - nodeSelector: + - name: gsm1 # Name of the Primary Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: primary # Name of the Primary region for the Shard Database + nodeSelector: # Node Selector the worker node to deploy the Pod. Worker Node with this label will be selected for the Pod. "failure-domain.beta.kubernetes.io/zone": "PHX-AD-3" - pvMatchLabels: - "failure-domain.beta.kubernetes.io/zone": "PHX-AD-3" - - name: gsm2 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: standby + pvMatchLabels: # To specify the availability domain where the Persistent Volume will be created. It should be same as the nodeSelector. + "failure-domain.beta.kubernetes.io/zone": "PHX-AD-3" + + - name: gsm2 # Name of the Standby Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: standby # Name of the Standby region for the Shard Database nodeSelector: "failure-domain.beta.kubernetes.io/zone": "PHX-AD-3" pvMatchLabels: "failure-domain.beta.kubernetes.io/zone": "PHX-AD-3" + + # Kubernetes Storage Class Name storageClass: oci - dbImage: container-registry.oracle.com/database/enterprise_ru:19.25.0.0 + + # Database Container Image to be used for Catalog and Shard Database + dbImage: container-registry.oracle.com/database/enterprise_ru:19.28.0.0 + + # Database Conrainer Image Pull Secret dbImagePullSecret: ocr-reg-cred - gsmImage: container-registry.oracle.com/database/gsm_ru:19.25.0.0 + + # GSM Container Image to be used for GSM Pods + gsmImage: container-registry.oracle.com/database/gsm_ru:19.28.0.0 + + # GSM Conrainer Image Pull Secret gsmImagePullSecret: ocr-reg-cred + + # Type of the Sharding. By Default, it is System Sharding unless specified to be USER or NATIVE shardingType: USER + + # To specify if Catalog and Shard Databases will be deployed by cloning from the Gold Image + isClone: True + + # Whether an External Load Balancer has to be configured for the Catalog, Shard and GSM Pods isExternalSvc: False + + # If set to true, the Persistent Volumes will be deleted when the deployment for the Oracle Globally Distributed Database is deleted isDeleteOraPvc: True - isClone: True + + # Details of the Database Secret dbSecret: name: db-user-pass-rsa pwdFileName: pwdfile.enc keyFileName: key.pem + + # Details of Services to be created for the Oracle Globally Distributed Database Resource gsmService: - name: oltp_rw_svc role: primary - name: oltp_ro_svc - role: primary + role: primary \ No newline at end of file diff --git a/docs/sharding/provisioning/user-defined-sharding/udsharding_shard_prov_delshard.yaml b/docs/sharding/provisioning/user-defined-sharding/udsharding_shard_prov_delshard.yaml index d83bf546..4f858b87 100644 --- a/docs/sharding/provisioning/user-defined-sharding/udsharding_shard_prov_delshard.yaml +++ b/docs/sharding/provisioning/user-defined-sharding/udsharding_shard_prov_delshard.yaml @@ -3,18 +3,27 @@ # Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. # --- +# API version of the ShardingDatabase Custom Resource apiVersion: database.oracle.com/v4 + +# Resource type being created kind: ShardingDatabase + +# Metadata section to define the resource's name and namespace metadata: - name: shardingdatabase-sample - namespace: shns + name: shardingdatabase-sample # Name of the ShardingDatabase instance + namespace: shns # Kubernetes namespace to deploy into + +# Specs for the ShardingDatabase instance spec: + + # Details of the individual shard databases shard: - - name: shard1 - storageSizeInGb: 50 - imagePullPolicy: "Always" - shardSpace: sspace1 - shardRegion: primary + - name: shard1 # Name of the shard database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image + shardSpace: sspace1 # Shard Space name for the Shard Database with User Defined Sharding + shardRegion: primary # Name of the region for the Shard Database - name: shard2 storageSizeInGb: 50 imagePullPolicy: "Always" @@ -26,43 +35,68 @@ spec: shardSpace: sspace3 shardRegion: primary - name: shard4 + isDelete: enable # Specify this parameter "isDelete: enable" for the shard which has to be deleted during the Scale In Operation storageSizeInGb: 50 imagePullPolicy: "Always" shardSpace: sspace4 shardRegion: primary - isDelete: enable - name: shard5 storageSizeInGb: 50 imagePullPolicy: "Always" shardSpace: sspace5 shardRegion: primary + + # Details of the individual shard databases catalog: - - name: catalog - storageSizeInGb: 50 - imagePullPolicy: "Always" + - name: catalog # Name of the Catalog database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image + + # Details of the GSM Pods gsm: - - name: gsm1 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: primary - - name: gsm2 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: standby + - name: gsm1 # Name of the Primary Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: primary # Name of the Primary region for the Shard Database + + - name: gsm2 # Name of the Standby Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: standby # Name of the Standby region for the Shard Database + + # Kubernetes Storage Class Name storageClass: oci - dbImage: container-registry.oracle.com/database/enterprise_ru:19.25.0.0 + + # Database Container Image to be used for Catalog and Shard Database + dbImage: container-registry.oracle.com/database/enterprise_ru:19.28.0.0 + + # Database Conrainer Image Pull Secret dbImagePullSecret: ocr-reg-cred - gsmImage: container-registry.oracle.com/database/gsm_ru:19.25.0.0 + + # GSM Container Image to be used for GSM Pods + gsmImage: container-registry.oracle.com/database/gsm_ru:19.28.0.0 + + # GSM Conrainer Image Pull Secret gsmImagePullSecret: ocr-reg-cred + + # Type of the Sharding. By Default, it is System Sharding unless specified to be USER or NATIVE shardingType: USER + + # Whether an External Load Balancer has to be configured for the Catalog, Shard and GSM Pods isExternalSvc: False + + # If set to true, the Persistent Volumes will be deleted when the deployment for the Oracle Globally Distributed Database is deleted isDeleteOraPvc: True + + # Details of the Database Secret dbSecret: name: db-user-pass-rsa pwdFileName: pwdfile.enc keyFileName: key.pem + + # Details of Services to be created for the Oracle Globally Distributed Database Resource gsmService: - name: oltp_rw_svc role: primary - name: oltp_ro_svc - role: primary + role: primary \ No newline at end of file diff --git a/docs/sharding/provisioning/user-defined-sharding/udsharding_shard_prov_extshard.yaml b/docs/sharding/provisioning/user-defined-sharding/udsharding_shard_prov_extshard.yaml index 7526feb7..0929ae89 100644 --- a/docs/sharding/provisioning/user-defined-sharding/udsharding_shard_prov_extshard.yaml +++ b/docs/sharding/provisioning/user-defined-sharding/udsharding_shard_prov_extshard.yaml @@ -2,18 +2,28 @@ # Copyright (c) 2022, Oracle and/or its affiliates. # Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. # +--- +# API version of the ShardingDatabase Custom Resource apiVersion: database.oracle.com/v4 + +# Resource type being created kind: ShardingDatabase + +# Metadata section to define the resource's name and namespace metadata: - name: shardingdatabase-sample - namespace: shns + name: shardingdatabase-sample # Name of the ShardingDatabase instance + namespace: shns # Kubernetes namespace to deploy into + +# Specs for the ShardingDatabase instance spec: + + # Details of the individual shard databases shard: - - name: shard1 - storageSizeInGb: 50 - imagePullPolicy: "Always" - shardSpace: sspace1 - shardRegion: primary + - name: shard1 # Name of the shard database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image + shardSpace: sspace1 # Shard Space name for the Shard Database with User Defined Sharding + shardRegion: primary # Name of the region for the Shard Database - name: shard2 storageSizeInGb: 50 imagePullPolicy: "Always" @@ -24,6 +34,8 @@ spec: imagePullPolicy: "Always" shardSpace: sspace3 shardRegion: primary + + # Details of the Additional two shards to be added during the Scale Out Operation - name: shard4 storageSizeInGb: 50 imagePullPolicy: "Always" @@ -33,34 +45,59 @@ spec: storageSizeInGb: 50 imagePullPolicy: "Always" shardSpace: sspace5 - shardRegion: primary + shardRegion: primary + + # Details of the individual shard databases catalog: - - name: catalog - storageSizeInGb: 50 - imagePullPolicy: "Always" + - name: catalog # Name of the Catalog database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image + + # Details of the GSM Pods gsm: - - name: gsm1 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: primary - - name: gsm2 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: standby + - name: gsm1 # Name of the Primary Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: primary # Name of the Primary region for the Shard Database + + - name: gsm2 # Name of the Standby Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: standby # Name of the Standby region for the Shard Database + + # Kubernetes Storage Class Name storageClass: oci - dbImage: container-registry.oracle.com/database/enterprise_ru:19.25.0.0 + + # Database Container Image to be used for Catalog and Shard Database + dbImage: container-registry.oracle.com/database/enterprise_ru:19.28.0.0 + + # Database Conrainer Image Pull Secret dbImagePullSecret: ocr-reg-cred - gsmImage: container-registry.oracle.com/database/gsm_ru:19.25.0.0 + + # GSM Container Image to be used for GSM Pods + gsmImage: container-registry.oracle.com/database/gsm_ru:19.28.0.0 + + # GSM Conrainer Image Pull Secret gsmImagePullSecret: ocr-reg-cred + + # Type of the Sharding. By Default, it is System Sharding unless specified to be USER or NATIVE shardingType: USER + + # Whether an External Load Balancer has to be configured for the Catalog, Shard and GSM Pods isExternalSvc: False + + # If set to true, the Persistent Volumes will be deleted when the deployment for the Oracle Globally Distributed Database is deleted isDeleteOraPvc: True + + # Details of the Database Secret dbSecret: name: db-user-pass-rsa pwdFileName: pwdfile.enc keyFileName: key.pem + + # Details of Services to be created for the Oracle Globally Distributed Database Resource gsmService: - name: oltp_rw_svc role: primary - name: oltp_ro_svc - role: primary + role: primary \ No newline at end of file diff --git a/docs/sharding/provisioning/user-defined-sharding/udsharding_shard_prov_memory_cpu.yaml b/docs/sharding/provisioning/user-defined-sharding/udsharding_shard_prov_memory_cpu.yaml index 8be81d39..a2b16ebb 100644 --- a/docs/sharding/provisioning/user-defined-sharding/udsharding_shard_prov_memory_cpu.yaml +++ b/docs/sharding/provisioning/user-defined-sharding/udsharding_shard_prov_memory_cpu.yaml @@ -3,27 +3,40 @@ # Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. # --- +# API version of the ShardingDatabase Custom Resource apiVersion: database.oracle.com/v4 + +# Resource type being created kind: ShardingDatabase + +# Metadata section to define the resource's name and namespace metadata: - name: shardingdatabase-sample - namespace: shns + name: shardingdatabase-sample # Name of the ShardingDatabase instance + namespace: shns # Kubernetes namespace to deploy into + +# Specs for the ShardingDatabase instance spec: + + # Details of the individual shard databases shard: - - name: shard1 - storageSizeInGb: 50 + - name: shard1 # Name of the shard database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod + + # Memory and CPU allocation for the Shard Database pod resources: requests: memory: "1000Mi" cpu: "1000m" + + # SGA and PGA values for the Shard Database envVars: - name: "INIT_SGA_SIZE" value: "600" - name: "INIT_PGA_SIZE" - value: "400" - imagePullPolicy: "Always" - shardSpace: sspace1 - shardRegion: primary + value: "400" + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image + shardSpace: sspace1 # Shard Space name for the Shard Database with User Defined Sharding + shardRegion: primary # Name of the region for the Shard Database - name: shard2 storageSizeInGb: 50 resources: @@ -34,7 +47,7 @@ spec: - name: "INIT_SGA_SIZE" value: "600" - name: "INIT_PGA_SIZE" - value: "400" + value: "400" imagePullPolicy: "Always" shardSpace: sspace2 shardRegion: primary @@ -48,41 +61,67 @@ spec: - name: "INIT_SGA_SIZE" value: "600" - name: "INIT_PGA_SIZE" - value: "400" + value: "400" imagePullPolicy: "Always" shardSpace: sspace3 shardRegion: primary + + # Details of the individual shard databases catalog: - - name: catalog - storageSizeInGb: 50 + - name: catalog # Name of the Catalog database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image resources: requests: memory: "1000Mi" cpu: "1000m" - imagePullPolicy: "Always" + imagePullPolicy: "Always" + + # Details of the GSM Pods gsm: - - name: gsm1 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: primary - - name: gsm2 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: standby + - name: gsm1 # Name of the Primary Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: primary # Name of the Primary region for the Shard Database + + - name: gsm2 # Name of the Standby Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: standby # Name of the Standby region for the Shard Database + + # Kubernetes Storage Class Name storageClass: oci - dbImage: container-registry.oracle.com/database/enterprise_ru:19.25.0.0 + + # Database Container Image to be used for Catalog and Shard Database + dbImage: container-registry.oracle.com/database/enterprise_ru:19.28.0.0 + + # Database Conrainer Image Pull Secret dbImagePullSecret: ocr-reg-cred - gsmImage: container-registry.oracle.com/database/gsm_ru:19.25.0.0 + + # GSM Container Image to be used for GSM Pods + gsmImage: container-registry.oracle.com/database/gsm_ru:19.28.0.0 + + # GSM Conrainer Image Pull Secret gsmImagePullSecret: ocr-reg-cred + + # Type of the Sharding. By Default, it is System Sharding unless specified to be USER or NATIVE shardingType: USER + + # Whether an External Load Balancer has to be configured for the Catalog, Shard and GSM Pods isExternalSvc: False + + # If set to true, the Persistent Volumes will be deleted when the deployment for the Oracle Globally Distributed Database is deleted isDeleteOraPvc: True + + # Details of the Database Secret dbSecret: name: db-user-pass-rsa pwdFileName: pwdfile.enc keyFileName: key.pem + + # Details of Services to be created for the Oracle Globally Distributed Database Resource gsmService: - name: oltp_rw_svc role: primary - name: oltp_ro_svc - role: primary + role: primary \ No newline at end of file diff --git a/docs/sharding/provisioning/user-defined-sharding/udsharding_shard_prov_send_notification.yaml b/docs/sharding/provisioning/user-defined-sharding/udsharding_shard_prov_send_notification.yaml index 4dda6db9..163e77b8 100644 --- a/docs/sharding/provisioning/user-defined-sharding/udsharding_shard_prov_send_notification.yaml +++ b/docs/sharding/provisioning/user-defined-sharding/udsharding_shard_prov_send_notification.yaml @@ -3,24 +3,33 @@ # Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. # --- +# API version of the ShardingDatabase Custom Resource apiVersion: database.oracle.com/v4 + +# Resource type being created kind: ShardingDatabase + +# Metadata section to define the resource's name and namespace metadata: - name: shardingdatabase-sample - namespace: shns + name: shardingdatabase-sample # Name of the ShardingDatabase instance + namespace: shns # Kubernetes namespace to deploy into + +# Specs for the ShardingDatabase instance spec: + + # Details of the individual shard databases shard: - - name: shard1 - storageSizeInGb: 50 - imagePullPolicy: "Always" - nodeSelector: + - name: shard1 # Name of the shard database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image + nodeSelector: # Node Selector the worker node to deploy the Pod. Worker Node with this label will be selected for the Pod. "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" - pvMatchLabels: + pvMatchLabels: # To specify the availability domain where the Persistent Volume will be created. It should be same as the nodeSelector. "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" pvAnnotations: - volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq - shardSpace: sspace1 - shardRegion: primary + volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq # Specify the OCID of the Block Volume Backup which has the Database Gold Image + shardSpace: sspace1 # Shard Space name for the Shard Database with User Defined Sharding + shardRegion: primary # Name of the region for the Shard Database - name: shard2 storageSizeInGb: 50 imagePullPolicy: "Always" @@ -40,45 +49,84 @@ spec: pvMatchLabels: "failure-domain.beta.kubernetes.io/zone": "PHX-AD-3" pvAnnotations: - volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq + volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq shardSpace: sspace3 shardRegion: primary + + # Details of the individual shard databases catalog: - - name: catalog - storageSizeInGb: 50 - imagePullPolicy: "Always" + - name: catalog # Name of the Catalog database + storageSizeInGb: 50 # Size of the Disk Storage allocated to the Shard Database pod + imagePullPolicy: "Always" # Image Pull Policy for the Database Container Image nodeSelector: "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" pvMatchLabels: "failure-domain.beta.kubernetes.io/zone": "PHX-AD-1" pvAnnotations: - volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq + volume.beta.kubernetes.io/oci-volume-source: ocid1.volumebackup.oc1.phx.abyhqljrxtv7tu5swqb3lzc7vpzwbwzdktd2y4k2vjjy2srmgu2w7bqdftjq + + # Details of the GSM Pods gsm: - - name: gsm1 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: primary - - name: gsm2 - imagePullPolicy: "Always" - storageSizeInGb: 50 - region: standby + - name: gsm1 # Name of the Primary Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: primary # Name of the Primary region for the Shard Database + nodeSelector: # Node Selector the worker node to deploy the Pod. Worker Node with this label will be selected for the Pod. + "failure-domain.beta.kubernetes.io/zone": "PHX-AD-3" + pvMatchLabels: # To specify the availability domain where the Persistent Volume will be created. It should be same as the nodeSelector. + "failure-domain.beta.kubernetes.io/zone": "PHX-AD-3" + + - name: gsm2 # Name of the Standby Global Service Manager + imagePullPolicy: "Always" # Image Pull Policy for the GSM Container Image + storageSizeInGb: 50 # Size of the Disk Storage allocated to the GSM pod + region: standby # Name of the Standby region for the Shard Database + nodeSelector: + "failure-domain.beta.kubernetes.io/zone": "PHX-AD-3" + pvMatchLabels: + "failure-domain.beta.kubernetes.io/zone": "PHX-AD-3" + + # Kubernetes Storage Class Name storageClass: oci - dbImage: container-registry.oracle.com/database/enterprise_ru:19.25.0.0 + + # Database Container Image to be used for Catalog and Shard Database + dbImage: container-registry.oracle.com/database/enterprise_ru:19.28.0.0 + + # Database Conrainer Image Pull Secret dbImagePullSecret: ocr-reg-cred - gsmImage: container-registry.oracle.com/database/gsm_ru:19.25.0.0 + + # GSM Container Image to be used for GSM Pods + gsmImage: container-registry.oracle.com/database/gsm_ru:19.28.0.0 + + # GSM Conrainer Image Pull Secret gsmImagePullSecret: ocr-reg-cred + + # Type of the Sharding. By Default, it is System Sharding unless specified to be USER or NATIVE shardingType: USER + + # To specify if Catalog and Shard Databases will be deployed by cloning from the Gold Image + isClone: True + + # Whether an External Load Balancer has to be configured for the Catalog, Shard and GSM Pods isExternalSvc: False + + # If set to true, the Persistent Volumes will be deleted when the deployment for the Oracle Globally Distributed Database is deleted isDeleteOraPvc: True - isClone: True + + # Details of the Database Secret dbSecret: name: db-user-pass-rsa pwdFileName: pwdfile.enc keyFileName: key.pem + + # Specify the configmap created with the credentials of the OCI User Account which will be used for sending the OCI Notification nsConfigMap: onsconfigmap - nsSecret: my-secret + + # Specify the secret which has the privatekey for the OCI User Account to be used for sending the OCI Notification + nsSecret: my-secret + + # Details of Services to be created for the Oracle Globally Distributed Database Resource gsmService: - name: oltp_rw_svc role: primary - name: oltp_ro_svc - role: primary + role: primary \ No newline at end of file diff --git a/docs/sidb/README.md b/docs/sidb/README.md index 35f42f22..e1bc6148 100644 --- a/docs/sidb/README.md +++ b/docs/sidb/README.md @@ -649,10 +649,10 @@ The following table depicts the fail over matrix for any destructive operation t - If the `ReadWriteMany` access mode is used, then all the replicas will be distributed on different nodes. For this reason, Oracle recommends that you have replicas more than or equal to the number of the nodes, because the database image is downloaded on all those nodes. This is beneficial in quick cold fail-over scenario (when the active pod dies) as the image would already be available on that node. #### Database Pod Resource Management -When creating a Single Instance Database, you can specify the CPU and memory resources needed by the database pod. These specified resources are passed to the `kube-scheduler` so that the pod is scheduled on one of the pods that has the required resources available. To use database pod resource management, specify values for the `resources` attributes in the [`config/samples/sidb/singleinstancedatabase.yaml`](../../config/samples/sidb/singleinstancedatabase.yaml) file, and apply it. - -#### Database Pod Resource Management -When creating a Single Instance Database you can specify the cpu and memory resources needed by the database pod. These specified resources are passed to the `kube-scheduler` so that the pod gets scheduled on one of the pods that has the required resources available. To use database pod resource management specify values for the `resources` attributes in the [config/samples/sidb/singleinstancedatabase.yaml](../../config/samples/sidb/singleinstancedatabase.yaml) file, and apply it. +When creating a Single Instance Database you can specify the cpu and memory resources needed by the database pod. These specified resources are passed to the `kube-scheduler` so that the pod gets scheduled on one of the nodes that has the required resources available. To use database pod resource management specify values for the `resources` attributes in the [config/samples/sidb/singleinstancedatabase.yaml](../../config/samples/sidb/singleinstancedatabase.yaml) file, and apply it. +For Enterprise Edition, the recommended values are: +cpu="2" +memory="16Gi" #### Setup Database with LoadBalancer For the Single Instance Database, the default service is the `NodePort` service. You can enable the `LoadBalancer` service by using the `kubectl patch` command. diff --git a/go.mod b/go.mod index 863f2e99..1c9c56ec 100644 --- a/go.mod +++ b/go.mod @@ -1,106 +1,120 @@ module github.com/oracle/oracle-database-operator -go 1.23.3 +go 1.25.1 require ( - github.com/go-logr/logr v1.4.2 - github.com/onsi/ginkgo/v2 v2.20.2 - github.com/onsi/gomega v1.34.2 - github.com/oracle/oci-go-sdk/v65 v65.77.1 - github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.78.2 - go.uber.org/zap v1.26.0 - golang.org/x/text v0.19.0 - gopkg.in/yaml.v3 v3.0.1 - k8s.io/api v0.31.3 - k8s.io/apimachinery v0.31.3 - k8s.io/cli-runtime v0.31.3 - k8s.io/client-go v0.31.3 - k8s.io/kubectl v0.31.3 - sigs.k8s.io/controller-runtime v0.19.3 - sigs.k8s.io/yaml v1.4.0 + github.com/go-logr/logr v1.4.3 + github.com/onsi/ginkgo/v2 v2.25.2 + github.com/onsi/gomega v1.38.2 + github.com/oracle/oci-go-sdk/v65 v65.99.1 + github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.85.0 + go.uber.org/zap v1.27.0 + golang.org/x/text v0.29.0 + k8s.io/api v0.34.1 + k8s.io/apimachinery v0.34.1 + k8s.io/cli-runtime v0.34.1 + k8s.io/client-go v0.34.1 + k8s.io/kubectl v0.34.1 + k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect + sigs.k8s.io/controller-runtime v0.22.1 + sigs.k8s.io/yaml v1.6.0 ) require ( - github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect + github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect + github.com/Masterminds/semver/v3 v3.4.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/chai2010/gettext-go v1.0.2 // indirect + github.com/chai2010/gettext-go v1.0.3 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect - github.com/evanphx/json-patch/v5 v5.9.0 // indirect - github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect + github.com/evanphx/json-patch/v5 v5.9.11 // indirect + github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/fatih/camelcase v1.0.0 // indirect - github.com/fxamacker/cbor/v2 v2.7.0 // indirect - github.com/go-errors/errors v1.4.2 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/go-errors/errors v1.5.1 // indirect github.com/go-logr/zapr v1.3.0 // indirect - github.com/go-openapi/jsonpointer v0.19.6 // indirect - github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.22.4 // indirect + github.com/go-openapi/jsonpointer v0.22.0 // indirect + github.com/go-openapi/jsonreference v0.21.1 // indirect + github.com/go-openapi/swag v0.24.1 // indirect + github.com/go-openapi/swag/cmdutils v0.24.0 // indirect + github.com/go-openapi/swag/conv v0.24.0 // indirect + github.com/go-openapi/swag/fileutils v0.24.0 // indirect + github.com/go-openapi/swag/jsonname v0.24.0 // indirect + github.com/go-openapi/swag/jsonutils v0.24.0 // indirect + github.com/go-openapi/swag/loading v0.24.0 // indirect + github.com/go-openapi/swag/mangling v0.24.0 // indirect + github.com/go-openapi/swag/netutils v0.24.0 // indirect + github.com/go-openapi/swag/stringutils v0.24.0 // indirect + github.com/go-openapi/swag/typeutils v0.24.0 // indirect + github.com/go-openapi/swag/yamlutils v0.24.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect - github.com/gofrs/flock v0.8.1 // indirect + github.com/gofrs/flock v0.12.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.4 // indirect - github.com/google/btree v1.0.1 // indirect - github.com/google/gnostic-models v0.6.8 // indirect - github.com/google/go-cmp v0.6.0 // indirect - github.com/google/gofuzz v1.2.0 // indirect - github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 // indirect - github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/google/btree v1.1.3 // indirect + github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/pprof v0.0.0-20250903194437-c28834ac2320 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/websocket v1.5.0 // indirect - github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect - github.com/imdario/mergo v0.3.6 // indirect + github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect + github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect + github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect - github.com/mailru/easyjson v0.7.7 // indirect + github.com/mailru/easyjson v0.9.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect - github.com/moby/spdystream v0.4.0 // indirect - github.com/moby/term v0.5.0 // indirect + github.com/moby/spdystream v0.5.0 // indirect + github.com/moby/term v0.5.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/prometheus/client_golang v1.19.1 // indirect - github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.55.0 // indirect - github.com/prometheus/procfs v0.15.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_golang v1.23.0 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.66.0 // indirect + github.com/prometheus/procfs v0.17.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/sony/gobreaker v0.5.0 // indirect - github.com/spf13/cobra v1.8.1 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/sony/gobreaker v1.0.0 // indirect + github.com/spf13/cobra v1.10.1 // indirect + github.com/spf13/pflag v1.0.10 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xlab/treeprint v1.2.0 // indirect - go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect + github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect + go.uber.org/automaxprocs v1.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect - golang.org/x/net v0.29.0 // indirect - golang.org/x/oauth2 v0.21.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.24.0 // indirect - gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect - gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/crypto v0.41.0 // indirect + golang.org/x/net v0.43.0 // indirect + golang.org/x/oauth2 v0.30.0 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/term v0.34.0 // indirect + golang.org/x/time v0.12.0 // indirect + golang.org/x/tools v0.36.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect + google.golang.org/protobuf v1.36.8 // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/apiextensions-apiserver v0.31.2 // indirect - k8s.io/component-base v0.31.3 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/apiextensions-apiserver v0.34.0 // indirect + k8s.io/component-base v0.34.1 // indirect + k8s.io/component-helpers v0.34.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect - k8s.io/utils v0.0.0-20240902221715-702e33fdd3c3 // indirect - sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/kustomize/api v0.17.2 // indirect - sigs.k8s.io/kustomize/kyaml v0.17.1 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + k8s.io/kube-openapi v0.0.0-20250902184714-7fc278399c7f // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect + sigs.k8s.io/kustomize/api v0.20.1 // indirect + sigs.k8s.io/kustomize/kyaml v0.20.1 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect ) diff --git a/go.sum b/go.sum index d23debb8..ec9b90c0 100644 --- a/go.sum +++ b/go.sum @@ -1,107 +1,101 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= -github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/chai2010/gettext-go v1.0.3 h1:9liNh8t+u26xl5ddmWLmsOsdNLwkdRTg5AG+JnTiM80= +github.com/chai2010/gettext-go v1.0.3/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= -github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= +github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= -github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= -github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= -github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= +github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= +github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= +github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= +github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= -github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= -github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= -github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= +github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= -github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= -github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= -github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= -github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/jsonpointer v0.22.0 h1:TmMhghgNef9YXxTu1tOopo+0BGEytxA+okbry0HjZsM= +github.com/go-openapi/jsonpointer v0.22.0/go.mod h1:xt3jV88UtExdIkkL7NloURjRQjbeUgcxFblMjq2iaiU= +github.com/go-openapi/jsonreference v0.21.1 h1:bSKrcl8819zKiOgxkbVNRUBIr6Wwj9KYrDbMjRs0cDA= +github.com/go-openapi/jsonreference v0.21.1/go.mod h1:PWs8rO4xxTUqKGu+lEvvCxD5k2X7QYkKAepJyCmSTT8= +github.com/go-openapi/swag v0.24.1 h1:DPdYTZKo6AQCRqzwr/kGkxJzHhpKxZ9i/oX0zag+MF8= +github.com/go-openapi/swag v0.24.1/go.mod h1:sm8I3lCPlspsBBwUm1t5oZeWZS0s7m/A+Psg0ooRU0A= +github.com/go-openapi/swag/cmdutils v0.24.0 h1:KlRCffHwXFI6E5MV9n8o8zBRElpY4uK4yWyAMWETo9I= +github.com/go-openapi/swag/cmdutils v0.24.0/go.mod h1:uxib2FAeQMByyHomTlsP8h1TtPd54Msu2ZDU/H5Vuf8= +github.com/go-openapi/swag/conv v0.24.0 h1:ejB9+7yogkWly6pnruRX45D1/6J+ZxRu92YFivx54ik= +github.com/go-openapi/swag/conv v0.24.0/go.mod h1:jbn140mZd7EW2g8a8Y5bwm8/Wy1slLySQQ0ND6DPc2c= +github.com/go-openapi/swag/fileutils v0.24.0 h1:U9pCpqp4RUytnD689Ek/N1d2N/a//XCeqoH508H5oak= +github.com/go-openapi/swag/fileutils v0.24.0/go.mod h1:3SCrCSBHyP1/N+3oErQ1gP+OX1GV2QYFSnrTbzwli90= +github.com/go-openapi/swag/jsonname v0.24.0 h1:2wKS9bgRV/xB8c62Qg16w4AUiIrqqiniJFtZGi3dg5k= +github.com/go-openapi/swag/jsonname v0.24.0/go.mod h1:GXqrPzGJe611P7LG4QB9JKPtUZ7flE4DOVechNaDd7Q= +github.com/go-openapi/swag/jsonutils v0.24.0 h1:F1vE1q4pg1xtO3HTyJYRmEuJ4jmIp2iZ30bzW5XgZts= +github.com/go-openapi/swag/jsonutils v0.24.0/go.mod h1:vBowZtF5Z4DDApIoxcIVfR8v0l9oq5PpYRUuteVu6f0= +github.com/go-openapi/swag/loading v0.24.0 h1:ln/fWTwJp2Zkj5DdaX4JPiddFC5CHQpvaBKycOlceYc= +github.com/go-openapi/swag/loading v0.24.0/go.mod h1:gShCN4woKZYIxPxbfbyHgjXAhO61m88tmjy0lp/LkJk= +github.com/go-openapi/swag/mangling v0.24.0 h1:PGOQpViCOUroIeak/Uj/sjGAq9LADS3mOyjznmHy2pk= +github.com/go-openapi/swag/mangling v0.24.0/go.mod h1:Jm5Go9LHkycsz0wfoaBDkdc4CkpuSnIEf62brzyCbhc= +github.com/go-openapi/swag/netutils v0.24.0 h1:Bz02HRjYv8046Ycg/w80q3g9QCWeIqTvlyOjQPDjD8w= +github.com/go-openapi/swag/netutils v0.24.0/go.mod h1:WRgiHcYTnx+IqfMCtu0hy9oOaPR0HnPbmArSRN1SkZM= +github.com/go-openapi/swag/stringutils v0.24.0 h1:i4Z/Jawf9EvXOLUbT97O0HbPUja18VdBxeadyAqS1FM= +github.com/go-openapi/swag/stringutils v0.24.0/go.mod h1:5nUXB4xA0kw2df5PRipZDslPJgJut+NjL7D25zPZ/4w= +github.com/go-openapi/swag/typeutils v0.24.0 h1:d3szEGzGDf4L2y1gYOSSLeK6h46F+zibnEas2Jm/wIw= +github.com/go-openapi/swag/typeutils v0.24.0/go.mod h1:q8C3Kmk/vh2VhpCLaoR2MVWOGP8y7Jc8l82qCTd1DYI= +github.com/go-openapi/swag/yamlutils v0.24.0 h1:bhw4894A7Iw6ne+639hsBNRHg9iZg/ISrOVr+sJGp4c= +github.com/go-openapi/swag/yamlutils v0.24.0/go.mod h1:DpKv5aYuaGm/sULePoeiG8uwMpZSfReo1HR3Ik0yaG8= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= +github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= -github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= -github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 h1:5iH8iuqE5apketRbSFBy+X1V0o+l+8NF1avt4HWl7cA= -github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/pprof v0.0.0-20250903194437-c28834ac2320 h1:c7ayAhbRP9HnEl/hg/WQOM9s0snWztfW6feWXZbGHw0= +github.com/google/pprof v0.0.0-20250903194437-c28834ac2320/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= -github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -110,226 +104,259 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY= github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= -github.com/moby/spdystream v0.4.0 h1:Vy79D6mHeJJjiPdFEL2yku1kl0chZpJfZcPpb16BRl8= -github.com/moby/spdystream v0.4.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= +github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= -github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag= -github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8= -github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc= -github.com/oracle/oci-go-sdk/v65 v65.77.1 h1:gqjTXIUWvTihkn470AclxSAMcR1JecqjD2IUtp+sDIU= -github.com/oracle/oci-go-sdk/v65 v65.77.1/go.mod h1:IBEV9l1qBzUpo7zgGaRUhbB05BVfcDGYRFBCPlTcPp0= +github.com/onsi/ginkgo/v2 v2.25.2 h1:hepmgwx1D+llZleKQDMEvy8vIlCxMGt7W5ZxDjIEhsw= +github.com/onsi/ginkgo/v2 v2.25.2/go.mod h1:43uiyQC4Ed2tkOzLsEYm7hnrb7UJTWHYNsuy3bG/snE= +github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= +github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= +github.com/oracle/oci-go-sdk/v65 v65.99.1 h1:fWNC7Ef1XQ3m7QyWa7848eHnGR8c4O3+t9k//hLOkqU= +github.com/oracle/oci-go-sdk/v65 v65.99.1/go.mod h1:RGiXfpDDmRRlLtqlStTzeBjjdUNXyqm3KXKyLCm3A/Q= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.78.2 h1:SyoVBXD/r0PntR1rprb90ClI32FSUNOCWqqTatnipHM= -github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.78.2/go.mod h1:SvsRXw4m1F2vk7HquU5h475bFpke27mIUswfyw9u3ug= -github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= -github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= -github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= -github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= -github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= +github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.85.0 h1:oY+F5FZFmCjCyzkHWPjVQpzvnvEB/0FP+iyzDUUlqFc= +github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.85.0/go.mod h1:VB7wtBmDT6W2RJHzsvPZlBId+EnmeQA0d33fFTXvraM= +github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= +github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.66.0 h1:K/rJPHrG3+AoQs50r2+0t7zMnMzek2Vbv31OFVsMeVY= +github.com/prometheus/common v0.66.0/go.mod h1:Ux6NtV1B4LatamKE63tJBntoxD++xmtI/lK0VtEplN4= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/sony/gobreaker v0.5.0 h1:dRCvqm0P490vZPmy7ppEk2qCnCieBooFJ+YoXGYB+yg= github.com/sony/gobreaker v0.5.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/sony/gobreaker v1.0.0 h1:feX5fGGXSl3dYd4aHZItw+FpHLvvoaqkawKjVNiFMNQ= +github.com/sony/gobreaker v1.0.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= +github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY= -go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= +go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= -go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= -golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= +golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= +golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= -golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= -gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= +gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= +google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.31.3 h1:umzm5o8lFbdN/hIXbrK9oRpOproJO62CV1zqxXrLgk8= -k8s.io/api v0.31.3/go.mod h1:UJrkIp9pnMOI9K2nlL6vwpxRzzEX5sWgn8kGQe92kCE= -k8s.io/apiextensions-apiserver v0.31.2 h1:W8EwUb8+WXBLu56ser5IudT2cOho0gAKeTOnywBLxd0= -k8s.io/apiextensions-apiserver v0.31.2/go.mod h1:i+Geh+nGCJEGiCGR3MlBDkS7koHIIKWVfWeRFiOsUcM= -k8s.io/apimachinery v0.31.3 h1:6l0WhcYgasZ/wk9ktLq5vLaoXJJr5ts6lkaQzgeYPq4= -k8s.io/apimachinery v0.31.3/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= -k8s.io/cli-runtime v0.31.3 h1:fEQD9Xokir78y7pVK/fCJN090/iYNrLHpFbGU4ul9TI= -k8s.io/cli-runtime v0.31.3/go.mod h1:Q2jkyTpl+f6AtodQvgDI8io3jrfr+Z0LyQBPJJ2Btq8= -k8s.io/client-go v0.31.3 h1:CAlZuM+PH2cm+86LOBemaJI/lQ5linJ6UFxKX/SoG+4= -k8s.io/client-go v0.31.3/go.mod h1:2CgjPUTpv3fE5dNygAr2NcM8nhHzXvxB8KL5gYc3kJs= -k8s.io/component-base v0.31.3 h1:DMCXXVx546Rfvhj+3cOm2EUxhS+EyztH423j+8sOwhQ= -k8s.io/component-base v0.31.3/go.mod h1:xME6BHfUOafRgT0rGVBGl7TuSg8Z9/deT7qq6w7qjIU= +k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= +k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= +k8s.io/apiextensions-apiserver v0.34.0 h1:B3hiB32jV7BcyKcMU5fDaDxk882YrJ1KU+ZSkA9Qxoc= +k8s.io/apiextensions-apiserver v0.34.0/go.mod h1:hLI4GxE1BDBy9adJKxUxCEHBGZtGfIg98Q+JmTD7+g0= +k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= +k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/cli-runtime v0.34.1 h1:btlgAgTrYd4sk8vJTRG6zVtqBKt9ZMDeQZo2PIzbL7M= +k8s.io/cli-runtime v0.34.1/go.mod h1:aVA65c+f0MZiMUPbseU/M9l1Wo2byeaGwUuQEQVVveE= +k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY= +k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= +k8s.io/component-base v0.34.1 h1:v7xFgG+ONhytZNFpIz5/kecwD+sUhVE6HU7qQUiRM4A= +k8s.io/component-base v0.34.1/go.mod h1:mknCpLlTSKHzAQJJnnHVKqjxR7gBeHRv0rPXA7gdtQ0= +k8s.io/component-helpers v0.34.1 h1:gWhH3CCdwAx5P3oJqZKb4Lg5FYZTWVbdWtOI8n9U4XY= +k8s.io/component-helpers v0.34.1/go.mod h1:4VgnUH7UA/shuBur+OWoQC0xfb69sy/93ss0ybZqm3c= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= -k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= -k8s.io/kubectl v0.31.3 h1:3r111pCjPsvnR98oLLxDMwAeM6OPGmPty6gSKaLTQes= -k8s.io/kubectl v0.31.3/go.mod h1:lhMECDCbJN8He12qcKqs2QfmVo9Pue30geovBVpH5fs= -k8s.io/utils v0.0.0-20240902221715-702e33fdd3c3 h1:b2FmK8YH+QEwq/Sy2uAEhmqL5nPfGYbJOcaqjeYYZoA= -k8s.io/utils v0.0.0-20240902221715-702e33fdd3c3/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/controller-runtime v0.19.3 h1:XO2GvC9OPftRst6xWCpTgBZO04S2cbp0Qqkj8bX1sPw= -sigs.k8s.io/controller-runtime v0.19.3/go.mod h1:j4j87DqtsThvwTv5/Tc5NFRyyF/RF0ip4+62tbTSIUM= -sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= -sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/kustomize/api v0.17.2 h1:E7/Fjk7V5fboiuijoZHgs4aHuexi5Y2loXlVOAVAG5g= -sigs.k8s.io/kustomize/api v0.17.2/go.mod h1:UWTz9Ct+MvoeQsHcJ5e+vziRRkwimm3HytpZgIYqye0= -sigs.k8s.io/kustomize/kyaml v0.17.1 h1:TnxYQxFXzbmNG6gOINgGWQt09GghzgTP6mIurOgrLCQ= -sigs.k8s.io/kustomize/kyaml v0.17.1/go.mod h1:9V0mCjIEYjlXuCdYsSXvyoy2BTsLESH7TlGV81S282U= -sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= -sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +k8s.io/kube-openapi v0.0.0-20250902184714-7fc278399c7f h1:wyRlmLgBSXi3kgawro8klrMRljXeRo1HFkQRs+meYfs= +k8s.io/kube-openapi v0.0.0-20250902184714-7fc278399c7f/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/kubectl v0.34.1 h1:1qP1oqT5Xc93K+H8J7ecpBjaz511gan89KO9Vbsh/OI= +k8s.io/kubectl v0.34.1/go.mod h1:JRYlhJpGPyk3dEmJ+BuBiOB9/dAvnrALJEiY/C5qa6A= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/controller-runtime v0.22.1 h1:Ah1T7I+0A7ize291nJZdS1CabF/lB4E++WizgV24Eqg= +sigs.k8s.io/controller-runtime v0.22.1/go.mod h1:FwiwRjkRPbiN+zp2QRp7wlTCzbUXxZ/D4OzuQUDwBHY= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I= +sigs.k8s.io/kustomize/api v0.20.1/go.mod h1:t6hUFxO+Ph0VxIk1sKp1WS0dOjbPCtLJ4p8aADLwqjM= +sigs.k8s.io/kustomize/kyaml v0.20.1 h1:PCMnA2mrVbRP3NIB6v9kYCAc38uvFLVs8j/CD567A78= +sigs.k8s.io/kustomize/kyaml v0.20.1/go.mod h1:0EmkQHRUsJxY8Ug9Niig1pUMSCGHxQ5RklbpV/Ri6po= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/main.go b/main.go index ee9992b7..e717978a 100644 --- a/main.go +++ b/main.go @@ -55,10 +55,13 @@ import ( clientgoscheme "k8s.io/client-go/kubernetes/scheme" _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" "k8s.io/client-go/rest" + "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/cache" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + ctrlzap "sigs.k8s.io/controller-runtime/pkg/log/zap" metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" databasev1alpha1 "github.com/oracle/oracle-database-operator/apis/database/v1alpha1" @@ -99,13 +102,12 @@ func main() { flag.Parse() // Initialize new logger Opts - options := &zap.Options{ + options := &ctrlzap.Options{ Development: true, TimeEncoder: zapcore.RFC3339TimeEncoder, } ctrl.SetLogger(zap.New(func(o *zap.Options) { *o = *options })) - watchNamespaces, err := getWatchNamespace() if err != nil { setupLog.Error(err, "Failed to get watch namespaces") @@ -122,6 +124,10 @@ func main() { opts.DefaultNamespaces = watchNamespaces return cache.New(config, opts) }, + EventBroadcaster: record.NewBroadcasterWithCorrelatorOptions(record.CorrelatorOptions{ + BurstSize: 10, + QPS: 1, + }), } mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), opt) @@ -210,6 +216,16 @@ func main() { os.Exit(1) } + if err = (&databasecontroller.OracleRestartReconciler{ + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("OracleRestart"), + Scheme: mgr.GetScheme(), + Config: mgr.GetConfig(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "OracleRestart") + os.Exit(1) + } + // Set RECONCILE_INTERVAL environment variable if you want to change the default value from 15 secs interval := os.Getenv("RECONCILE_INTERVAL") i, err := strconv.ParseInt(interval, 10, 64) @@ -228,18 +244,10 @@ func main() { setupLog.Error(err, "unable to create webhook", "webhook", "OracleRestDataService") os.Exit(1) } - if err = (&databasev4.PDB{}).SetupWebhookWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create webhook", "webhook", "PDB") - os.Exit(1) - } if err = (&databasev4.LRPDB{}).SetupWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "LRPDB") os.Exit(1) } - if err = (&databasev4.CDB{}).SetupWebhookWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create webhook", "webhook", "CDB") - os.Exit(1) - } if err = (&databasev4.LREST{}).SetupWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "LREST") os.Exit(1) @@ -312,18 +320,22 @@ func main() { setupLog.Error(err, "unable to create webhook", "webhook", "DatabaseObserver") os.Exit(1) } - } - - // PDB Reconciler - if err = (&databasecontroller.PDBReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - Log: ctrl.Log.WithName("controllers").WithName("PDB"), - Interval: time.Duration(i), - Recorder: mgr.GetEventRecorderFor("PDB"), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "PDB") - os.Exit(1) + if err = (&databasev4.SingleInstanceDatabase{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "SingleInstanceDatabase") + os.Exit(1) + } + if err = (&databasev4.DataguardBroker{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "DataguardBroker") + os.Exit(1) + } + if err = (&databasev4.OracleRestDataService{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "OracleRestDataService") + os.Exit(1) + } + if err = (&databasev4.OracleRestart{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "OracleRestart") + os.Exit(1) + } } // LRPDBR Reconciler @@ -338,19 +350,6 @@ func main() { os.Exit(1) } - // CDB Reconciler - if err = (&databasecontroller.CDBReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - Config: mgr.GetConfig(), - Log: ctrl.Log.WithName("controllers").WithName("CDB"), - Interval: time.Duration(i), - Recorder: mgr.GetEventRecorderFor("CDB"), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "CDB") - os.Exit(1) - } - // LREST Reconciler if err = (&databasecontroller.LRESTReconciler{ Client: mgr.GetClient(), @@ -394,30 +393,8 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "DatabaseObserver") os.Exit(1) } - - if err = (&databasev4.SingleInstanceDatabase{}).SetupWebhookWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create webhook", "webhook", "SingleInstanceDatabase") - os.Exit(1) - } - if err = (&databasev4.DataguardBroker{}).SetupWebhookWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create webhook", "webhook", "DataguardBroker") - os.Exit(1) - } - if err = (&databasev4.OracleRestDataService{}).SetupWebhookWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create webhook", "webhook", "OracleRestDataService") - os.Exit(1) - } // +kubebuilder:scaffold:builder - // Add index for PDB CR to enable mgr to cache PDBs - indexFunc := func(obj client.Object) []string { - return []string{obj.(*databasev4.PDB).Spec.PDBName} - } - if err = cache.IndexField(context.TODO(), &databasev4.PDB{}, "spec.pdbName", indexFunc); err != nil { - setupLog.Error(err, "unable to create index function for ", "controller", "PDB") - os.Exit(1) - } - indexFunc2 := func(obj client.Object) []string { return []string{obj.(*databasev4.LRPDB).Spec.LRPDBName} } diff --git a/oracle-database-operator.yaml b/oracle-database-operator.yaml index 1179b272..98535d86 100644 --- a/oracle-database-operator.yaml +++ b/oracle-database-operator.yaml @@ -10,7 +10,7 @@ kind: CustomResourceDefinition metadata: annotations: cert-manager.io/inject-ca-from: oracle-database-operator-system/oracle-database-operator-serving-cert - controller-gen.kubebuilder.io/version: v0.16.5 + controller-gen.kubebuilder.io/version: v0.17.3 name: autonomouscontainerdatabases.database.oracle.com spec: conversion: @@ -182,7 +182,7 @@ kind: CustomResourceDefinition metadata: annotations: cert-manager.io/inject-ca-from: oracle-database-operator-system/oracle-database-operator-serving-cert - controller-gen.kubebuilder.io/version: v0.16.5 + controller-gen.kubebuilder.io/version: v0.17.3 name: autonomousdatabasebackups.database.oracle.com spec: conversion: @@ -253,14 +253,14 @@ spec: type: integer target: properties: - k8sADB: + k8sAdb: properties: name: type: string type: object - ociADB: + ociAdb: properties: - ocid: + id: type: string type: object type: object @@ -344,14 +344,14 @@ spec: type: integer target: properties: - k8sADB: + k8sAdb: properties: name: type: string type: object - ociADB: + ociAdb: properties: - ocid: + id: type: string type: object type: object @@ -396,7 +396,7 @@ kind: CustomResourceDefinition metadata: annotations: cert-manager.io/inject-ca-from: oracle-database-operator-system/oracle-database-operator-serving-cert - controller-gen.kubebuilder.io/version: v0.16.5 + controller-gen.kubebuilder.io/version: v0.17.3 name: autonomousdatabaserestores.database.oracle.com spec: conversion: @@ -453,7 +453,7 @@ spec: type: object source: properties: - k8sADBBackup: + k8sAdbBackup: properties: name: type: string @@ -466,14 +466,14 @@ spec: type: object target: properties: - k8sADB: + k8sAdb: properties: name: type: string type: object - ociADB: + ociAdb: properties: - ocid: + id: type: string type: object type: object @@ -539,7 +539,7 @@ spec: type: object source: properties: - k8sADBBackup: + k8sAdbBackup: properties: name: type: string @@ -552,14 +552,14 @@ spec: type: object target: properties: - k8sADB: + k8sAdb: properties: name: type: string type: object - ociADB: + ociAdb: properties: - ocid: + id: type: string type: object type: object @@ -600,7 +600,7 @@ kind: CustomResourceDefinition metadata: annotations: cert-manager.io/inject-ca-from: oracle-database-operator-system/oracle-database-operator-serving-cert - controller-gen.kubebuilder.io/version: v0.16.5 + controller-gen.kubebuilder.io/version: v0.17.3 name: autonomousdatabases.database.oracle.com spec: conversion: @@ -639,9 +639,9 @@ spec: - jsonPath: .spec.details.isDedicated name: Dedicated type: string - - jsonPath: .spec.details.cpuCoreCount - name: OCPUs - type: integer + - jsonPath: .spec.details.computeCount + name: Compute Count + type: number - jsonPath: .spec.details.dataStorageSizeInTBs name: Storage (TB) type: integer @@ -673,6 +673,8 @@ spec: - Start - Terminate - Clone + - Switchover + - Failover type: string clone: properties: @@ -972,9 +974,9 @@ spec: - jsonPath: .spec.details.isDedicated name: Dedicated type: string - - jsonPath: .spec.details.cpuCoreCount - name: OCPUs - type: integer + - jsonPath: .spec.details.computeCount + name: Compute Count + type: number - jsonPath: .spec.details.dataStorageSizeInTBs name: Storage (TB) type: integer @@ -1006,6 +1008,8 @@ spec: - Start - Terminate - Clone + - Switchover + - Failover type: string clone: properties: @@ -1298,47 +1302,31 @@ kind: CustomResourceDefinition metadata: annotations: cert-manager.io/inject-ca-from: oracle-database-operator-system/oracle-database-operator-serving-cert - controller-gen.kubebuilder.io/version: v0.16.5 - name: cdbs.database.oracle.com + controller-gen.kubebuilder.io/version: v0.17.3 + name: databaseobservers.observability.oracle.com spec: - group: database.oracle.com + group: observability.oracle.com names: - kind: CDB - listKind: CDBList - plural: cdbs - singular: cdb + kind: DatabaseObserver + listKind: DatabaseObserverList + plural: databaseobservers + shortNames: + - dbobserver + - dbobservers + singular: databaseobserver scope: Namespaced versions: - additionalPrinterColumns: - - description: Name of the CDB - jsonPath: .spec.cdbName - name: CDB Name - type: string - - description: ' Name of the DB Server' - jsonPath: .spec.dbServer - name: DB Server + - jsonPath: .status.metricsConfig + name: MetricsConfig type: string - - description: DB server port - jsonPath: .spec.dbPort - name: DB Port - type: integer - - description: Replicas - jsonPath: .spec.replicas - name: Replicas - type: integer - - description: Status of the CDB Resource - jsonPath: .status.phase + - jsonPath: .status.status name: Status type: string - - description: Error message, if any - jsonPath: .status.msg - name: Message - type: string - - description: ' string of the tnsalias' - jsonPath: .spec.dbTnsurl - name: TNS STRING + - jsonPath: .status.version + name: Version type: string - name: v1alpha1 + name: v1 schema: openAPIV3Schema: properties: @@ -1350,808 +1338,554 @@ spec: type: object spec: properties: - cdbAdminPwd: + azureConfig: properties: - secret: + configMap: properties: key: type: string - secretName: + name: type: string - required: - - key - - secretName type: object - required: - - secret type: object - cdbAdminUser: + database: properties: - secret: + azure: properties: - key: + vaultID: type: string - secretName: + vaultPasswordSecret: + type: string + vaultUsernameSecret: type: string - required: - - key - - secretName type: object - required: - - secret - type: object - cdbName: - type: string - cdbOrdsPrvKey: - properties: - secret: + dbConnectionString: properties: + envName: + type: string key: type: string - secretName: + secret: type: string - required: - - key - - secretName type: object - required: - - secret - type: object - cdbOrdsPubKey: - properties: - secret: + dbPassword: properties: + envName: + type: string key: type: string - secretName: + secret: type: string - required: - - key - - secretName type: object - required: - - secret - type: object - cdbTlsCrt: - properties: - secret: + dbUser: properties: + envName: + type: string key: type: string - secretName: + secret: type: string - required: - - key - - secretName type: object - required: - - secret - type: object - cdbTlsKey: - properties: - secret: + oci: properties: - key: + vaultID: type: string - secretName: + vaultPasswordSecret: type: string - required: - - key - - secretName type: object - required: - - secret type: object - dbPort: - type: integer - dbServer: - type: string - dbTnsurl: - type: string - deletePdbCascade: - type: boolean - nodeSelector: + databases: additionalProperties: - type: string + properties: + dbConnectionString: + properties: + envName: + type: string + key: + type: string + secret: + type: string + type: object + dbPassword: + properties: + envName: + type: string + key: + type: string + secret: + type: string + type: object + dbUser: + properties: + envName: + type: string + key: + type: string + secret: + type: string + type: object + type: object type: object - ordsImage: - type: string - ordsImagePullPolicy: - enum: - - Always - - Never - type: string - ordsImagePullSecret: - type: string - ordsPort: - type: integer - ordsPwd: + deployment: properties: - secret: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName + args: + items: + type: string + type: array + commands: + items: + type: string + type: array + env: + additionalProperties: + type: string type: object - required: - - secret - type: object - replicas: - type: integer - serviceName: - type: string - sysAdminPwd: - properties: - secret: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName + image: + type: string + labels: + additionalProperties: + type: string type: object - required: - - secret - type: object - webServerPwd: - properties: - secret: + podSecurityContext: properties: - key: + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + fsGroup: + format: int64 + type: integer + fsGroupChangePolicy: type: string - secretName: + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxChangePolicy: type: string - required: - - key - - secretName + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + supplementalGroups: + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + supplementalGroupsPolicy: + type: string + sysctls: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object type: object - required: - - secret - type: object - webServerUser: - properties: - secret: + podTemplate: properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName + labels: + additionalProperties: + type: string + type: object type: object - required: - - secret - type: object - type: object - status: - properties: - msg: - type: string - phase: - type: string - status: - type: boolean - required: - - phase - - status - type: object - type: object - served: true - storage: false - subresources: - status: {} - - additionalPrinterColumns: - - description: Name of the CDB - jsonPath: .spec.cdbName - name: CDB Name - type: string - - description: ' Name of the DB Server' - jsonPath: .spec.dbServer - name: DB Server - type: string - - description: DB server port - jsonPath: .spec.dbPort - name: DB Port - type: integer - - description: Replicas - jsonPath: .spec.replicas - name: Replicas - type: integer - - description: Status of the CDB Resource - jsonPath: .status.phase - name: Status - type: string - - description: Error message, if any - jsonPath: .status.msg - name: Message - type: string - - description: ' string of the tnsalias' - jsonPath: .spec.dbTnsurl - name: TNS STRING - type: string - name: v4 - schema: - openAPIV3Schema: - properties: - apiVersion: - type: string - kind: - type: string - metadata: - type: object - spec: - properties: - cdbAdminPwd: - properties: - secret: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - secret - type: object - cdbAdminUser: - properties: - secret: + securityContext: properties: - key: - type: string - secretName: + allowPrivilegeEscalation: + type: boolean + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + capabilities: + properties: + add: + items: + type: string + type: array + x-kubernetes-list-type: atomic + drop: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + type: boolean + procMount: type: string - required: - - key - - secretName + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object type: object - required: - - secret type: object - cdbName: - type: string - cdbOrdsPrvKey: + exporterConfig: properties: - secret: + configMap: properties: key: type: string - secretName: + name: type: string - required: - - key - - secretName type: object - required: - - secret + mountPath: + type: string type: object - cdbOrdsPubKey: + inheritLabels: + items: + type: string + type: array + log: properties: - secret: + destination: + type: string + disable: + type: boolean + filename: + type: string + volume: properties: - key: - type: string - secretName: + name: type: string - required: - - key - - secretName + persistentVolumeClaim: + properties: + claimName: + type: string + type: object type: object - required: - - secret type: object - cdbTlsCrt: + metrics: properties: - secret: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - secret + configMap: + items: + properties: + key: + type: string + name: + type: string + type: object + type: array type: object - cdbTlsKey: + ociConfig: properties: - secret: + configMap: properties: key: type: string - secretName: + name: type: string - required: - - key - - secretName type: object - required: - - secret - type: object - dbPort: - type: integer - dbServer: - type: string - dbTnsurl: - type: string - deletePdbCascade: - type: boolean - nodeSelector: - additionalProperties: - type: string - type: object - ordsImage: - type: string - ordsImagePullPolicy: - enum: - - Always - - Never - type: string - ordsImagePullSecret: - type: string - ordsPort: - type: integer - ordsPwd: - properties: - secret: + mountPath: + type: string + privateKey: properties: - key: - type: string - secretName: + secret: type: string - required: - - key - - secretName type: object - required: - - secret type: object replicas: + format: int32 type: integer - serviceName: - type: string - sysAdminPwd: + service: properties: - secret: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName + labels: + additionalProperties: + type: string type: object - required: - - secret + ports: + items: + properties: + appProtocol: + type: string + name: + type: string + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + default: TCP + type: string + targetPort: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array type: object - webServerPwd: + serviceMonitor: properties: - secret: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - secret - type: object - webServerUser: - properties: - secret: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - secret - type: object - type: object - status: - properties: - msg: - type: string - phase: - type: string - status: - type: boolean - required: - - phase - - status - type: object - type: object - served: true - storage: true - subresources: - status: {} ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.16.5 - name: databaseobservers.observability.oracle.com -spec: - group: observability.oracle.com - names: - kind: DatabaseObserver - listKind: DatabaseObserverList - plural: databaseobservers - shortNames: - - dbobserver - - dbobservers - singular: databaseobserver - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .status.exporterConfig - name: ExporterConfig - type: string - - jsonPath: .status.status - name: Status - type: string - - jsonPath: .status.version - name: Version - type: string - name: v1 - schema: - openAPIV3Schema: - properties: - apiVersion: - type: string - kind: - type: string - metadata: - type: object - spec: - properties: - configuration: - properties: - configMap: - properties: - key: - type: string - name: - type: string - type: object - type: object - database: - properties: - dbConnectionString: - properties: - key: - type: string - secret: - type: string - type: object - dbPassword: - properties: - key: - type: string - secret: - type: string - vaultOCID: - type: string - vaultSecretName: - type: string - type: object - dbUser: - properties: - key: - type: string - secret: - type: string - type: object - dbWallet: - properties: - key: - type: string - secret: - type: string - type: object - type: object - exporter: - properties: - deployment: - properties: - args: - items: - type: string - type: array - commands: - items: - type: string - type: array - env: - additionalProperties: - type: string - type: object - image: - type: string - labels: - additionalProperties: + endpoints: + items: + properties: + authorization: + properties: + credentials: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: + type: string + type: object + basicAuth: + properties: + password: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + username: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + bearerTokenFile: type: string - type: object - podTemplate: - properties: - labels: - additionalProperties: + bearerTokenSecret: + properties: + key: type: string - type: object - securityContext: + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + enableHttp2: + type: boolean + filterRunning: + type: boolean + followRedirects: + type: boolean + honorLabels: + type: boolean + honorTimestamps: + type: boolean + interval: + pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ + type: string + metricRelabelings: + items: properties: - appArmorProfile: - properties: - localhostProfile: - type: string - type: - type: string - required: - - type - type: object - fsGroup: - format: int64 - type: integer - fsGroupChangePolicy: + action: + default: replace + enum: + - replace + - Replace + - keep + - Keep + - drop + - Drop + - hashmod + - HashMod + - labelmap + - LabelMap + - labeldrop + - LabelDrop + - labelkeep + - LabelKeep + - lowercase + - Lowercase + - uppercase + - Uppercase + - keepequal + - KeepEqual + - dropequal + - DropEqual type: string - runAsGroup: - format: int64 - type: integer - runAsNonRoot: - type: boolean - runAsUser: + modulus: format: int64 type: integer - seLinuxOptions: - properties: - level: - type: string - role: - type: string - type: - type: string - user: - type: string - type: object - seccompProfile: - properties: - localhostProfile: - type: string - type: - type: string - required: - - type - type: object - supplementalGroups: + regex: + type: string + replacement: + type: string + separator: + type: string + sourceLabels: items: - format: int64 - type: integer + pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ + type: string type: array - x-kubernetes-list-type: atomic - supplementalGroupsPolicy: + targetLabel: type: string - sysctls: - items: + type: object + type: array + noProxy: + type: string + oauth2: + properties: + clientId: + properties: + configMap: properties: + key: + type: string name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + secret: + properties: + key: type: string - value: + name: + default: "" type: string + optional: + type: boolean required: - - name - - value + - key type: object - type: array - x-kubernetes-list-type: atomic - windowsOptions: - properties: - gmsaCredentialSpec: - type: string - gmsaCredentialSpecName: - type: string - hostProcess: - type: boolean - runAsUserName: - type: string - type: object - type: object - type: object - securityContext: - properties: - allowPrivilegeEscalation: - type: boolean - appArmorProfile: - properties: - localhostProfile: - type: string - type: - type: string - required: - - type - type: object - capabilities: - properties: - add: - items: + x-kubernetes-map-type: atomic + type: object + clientSecret: + properties: + key: type: string - type: array - x-kubernetes-list-type: atomic - drop: - items: + name: + default: "" type: string - type: array - x-kubernetes-list-type: atomic - type: object - privileged: - type: boolean - procMount: - type: string - readOnlyRootFilesystem: - type: boolean - runAsGroup: - format: int64 - type: integer - runAsNonRoot: - type: boolean - runAsUser: - format: int64 - type: integer - seLinuxOptions: - properties: - level: - type: string - role: - type: string - type: - type: string - user: - type: string - type: object - seccompProfile: - properties: - localhostProfile: - type: string - type: - type: string - required: - - type - type: object - windowsOptions: - properties: - gmsaCredentialSpec: - type: string - gmsaCredentialSpecName: - type: string - hostProcess: - type: boolean - runAsUserName: + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + endpointParams: + additionalProperties: type: string - type: object - type: object - type: object - service: - properties: - labels: - additionalProperties: - type: string - type: object - ports: - items: - properties: - appProtocol: - type: string - name: - type: string - nodePort: - format: int32 - type: integer - port: - format: int32 - type: integer - protocol: - default: TCP - type: string - targetPort: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - required: - - port - type: object - type: array - type: object - type: object - inheritLabels: - items: - type: string - type: array - log: - properties: - filename: - type: string - path: - type: string - volume: - properties: - name: - type: string - persistentVolumeClaim: - properties: - claimName: - type: string - type: object - type: object - type: object - ociConfig: - properties: - configMapName: - type: string - secretName: - type: string - type: object - prometheus: - properties: - serviceMonitor: - properties: - endpoints: - items: - properties: - authorization: - properties: - credentials: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: - type: string type: object - basicAuth: - properties: - password: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - username: + noProxy: + type: string + proxyConnectHeader: + additionalProperties: + items: properties: key: type: string @@ -2164,85 +1898,21 @@ spec: - key type: object x-kubernetes-map-type: atomic - type: object - bearerTokenFile: - type: string - bearerTokenSecret: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key + type: array type: object x-kubernetes-map-type: atomic - enableHttp2: - type: boolean - filterRunning: - type: boolean - followRedirects: - type: boolean - honorLabels: + proxyFromEnvironment: type: boolean - honorTimestamps: - type: boolean - interval: - pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ + proxyUrl: + pattern: ^(http|https|socks5)://.+$ type: string - metricRelabelings: + scopes: items: - properties: - action: - default: replace - enum: - - replace - - Replace - - keep - - Keep - - drop - - Drop - - hashmod - - HashMod - - labelmap - - LabelMap - - labeldrop - - LabelDrop - - labelkeep - - LabelKeep - - lowercase - - Lowercase - - uppercase - - Uppercase - - keepequal - - KeepEqual - - dropequal - - DropEqual - type: string - modulus: - format: int64 - type: integer - regex: - type: string - replacement: - type: string - separator: - type: string - sourceLabels: - items: - pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ - type: string - type: array - targetLabel: - type: string - type: object + type: string type: array - oauth2: + tlsConfig: properties: - clientId: + ca: properties: configMap: properties: @@ -2271,28 +1941,9 @@ spec: type: object x-kubernetes-map-type: atomic type: object - clientSecret: + cert: properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - endpointParams: - additionalProperties: - type: string - type: object - noProxy: - type: string - proxyConnectHeader: - additionalProperties: - items: + configMap: properties: key: type: string @@ -2305,81 +1956,7 @@ spec: - key type: object x-kubernetes-map-type: atomic - type: array - type: object - x-kubernetes-map-type: atomic - proxyFromEnvironment: - type: boolean - proxyUrl: - pattern: ^http(s)?://.+$ - type: string - scopes: - items: - type: string - type: array - tlsConfig: - properties: - ca: - properties: - configMap: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - cert: - properties: - configMap: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - insecureSkipVerify: - type: boolean - keySecret: + secret: properties: key: type: string @@ -2392,190 +1969,28 @@ spec: - key type: object x-kubernetes-map-type: atomic - maxVersion: - enum: - - TLS10 - - TLS11 - - TLS12 - - TLS13 - type: string - minVersion: - enum: - - TLS10 - - TLS11 - - TLS12 - - TLS13 + type: object + insecureSkipVerify: + type: boolean + keySecret: + properties: + key: type: string - serverName: + name: + default: "" type: string + optional: + type: boolean + required: + - key type: object - tokenUrl: - minLength: 1 - type: string - required: - - clientId - - clientSecret - - tokenUrl - type: object - params: - additionalProperties: - items: - type: string - type: array - type: object - path: - type: string - port: - type: string - proxyUrl: - type: string - relabelings: - items: - properties: - action: - default: replace - enum: - - replace - - Replace - - keep - - Keep - - drop - - Drop - - hashmod - - HashMod - - labelmap - - LabelMap - - labeldrop - - LabelDrop - - labelkeep - - LabelKeep - - lowercase - - Lowercase - - uppercase - - Uppercase - - keepequal - - KeepEqual - - dropequal - - DropEqual - type: string - modulus: - format: int64 - type: integer - regex: - type: string - replacement: - type: string - separator: - type: string - sourceLabels: - items: - pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ - type: string - type: array - targetLabel: - type: string - type: object - type: array - scheme: - enum: - - http - - https - type: string - scrapeTimeout: - pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ - type: string - targetPort: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - tlsConfig: - properties: - ca: - properties: - configMap: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - caFile: - type: string - cert: - properties: - configMap: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - certFile: - type: string - insecureSkipVerify: - type: boolean - keyFile: - type: string - keySecret: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - maxVersion: - enum: - - TLS10 - - TLS11 - - TLS12 - - TLS13 + x-kubernetes-map-type: atomic + maxVersion: + enum: + - TLS10 + - TLS11 + - TLS12 + - TLS13 type: string minVersion: enum: @@ -2587,640 +2002,329 @@ spec: serverName: type: string type: object - trackTimestampsStaleness: - type: boolean + tokenUrl: + minLength: 1 + type: string + required: + - clientId + - clientSecret + - tokenUrl type: object - type: array - labels: - additionalProperties: - type: string - type: object - namespaceSelector: - properties: - any: - type: boolean - matchNames: + params: + additionalProperties: items: type: string type: array - type: object - type: object - type: object - replicas: - format: int32 - type: integer - sidecarVolumes: - items: - properties: - awsElasticBlockStore: - properties: - fsType: - type: string - partition: - format: int32 - type: integer - readOnly: - type: boolean - volumeID: - type: string - required: - - volumeID - type: object - azureDisk: - properties: - cachingMode: - type: string - diskName: - type: string - diskURI: - type: string - fsType: - default: ext4 - type: string - kind: - type: string - readOnly: - default: false - type: boolean - required: - - diskName - - diskURI - type: object - azureFile: - properties: - readOnly: - type: boolean - secretName: - type: string - shareName: - type: string - required: - - secretName - - shareName - type: object - cephfs: - properties: - monitors: - items: - type: string - type: array - x-kubernetes-list-type: atomic + type: object path: type: string - readOnly: - type: boolean - secretFile: + port: type: string - secretRef: - properties: - name: - default: "" - type: string + proxyConnectHeader: + additionalProperties: + items: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: array type: object x-kubernetes-map-type: atomic - user: - type: string - required: - - monitors - type: object - cinder: - properties: - fsType: - type: string - readOnly: + proxyFromEnvironment: type: boolean - secretRef: - properties: - name: - default: "" - type: string - type: object - x-kubernetes-map-type: atomic - volumeID: + proxyUrl: + pattern: ^(http|https|socks5)://.+$ type: string - required: - - volumeID - type: object - configMap: - properties: - defaultMode: - format: int32 - type: integer - items: + relabelings: items: properties: - key: + action: + default: replace + enum: + - replace + - Replace + - keep + - Keep + - drop + - Drop + - hashmod + - HashMod + - labelmap + - LabelMap + - labeldrop + - LabelDrop + - labelkeep + - LabelKeep + - lowercase + - Lowercase + - uppercase + - Uppercase + - keepequal + - KeepEqual + - dropequal + - DropEqual type: string - mode: - format: int32 + modulus: + format: int64 type: integer - path: + regex: type: string - required: - - key - - path - type: object - type: array - x-kubernetes-list-type: atomic - name: - default: "" - type: string - optional: - type: boolean - type: object - x-kubernetes-map-type: atomic - csi: - properties: - driver: - type: string - fsType: - type: string - nodePublishSecretRef: - properties: - name: - default: "" - type: string - type: object - x-kubernetes-map-type: atomic - readOnly: - type: boolean - volumeAttributes: - additionalProperties: - type: string - type: object - required: - - driver - type: object - downwardAPI: - properties: - defaultMode: - format: int32 - type: integer - items: - items: - properties: - fieldRef: - properties: - apiVersion: - type: string - fieldPath: - type: string - required: - - fieldPath - type: object - x-kubernetes-map-type: atomic - mode: - format: int32 - type: integer - path: + replacement: + type: string + separator: + type: string + sourceLabels: + items: + pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ + type: string + type: array + targetLabel: type: string - resourceFieldRef: - properties: - containerName: - type: string - divisor: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - resource: - type: string - required: - - resource - type: object - x-kubernetes-map-type: atomic - required: - - path type: object type: array - x-kubernetes-list-type: atomic - type: object - emptyDir: - properties: - medium: + scheme: + enum: + - http + - https type: string - sizeLimit: + scrapeTimeout: + pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ + type: string + targetPort: anyOf: - type: integer - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - type: object - ephemeral: - properties: - volumeClaimTemplate: + tlsConfig: properties: - metadata: - type: object - spec: + ca: properties: - accessModes: - items: - type: string - type: array - x-kubernetes-list-type: atomic - dataSource: + configMap: properties: - apiGroup: - type: string - kind: + key: type: string name: + default: "" type: string + optional: + type: boolean required: - - kind - - name + - key type: object x-kubernetes-map-type: atomic - dataSourceRef: + secret: properties: - apiGroup: - type: string - kind: + key: type: string name: + default: "" type: string - namespace: - type: string + optional: + type: boolean required: - - kind - - name + - key type: object - resources: + x-kubernetes-map-type: atomic + type: object + caFile: + type: string + cert: + properties: + configMap: properties: - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key type: object - selector: + x-kubernetes-map-type: atomic + secret: properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key type: object x-kubernetes-map-type: atomic - storageClassName: - type: string - volumeAttributesClassName: - type: string - volumeMode: + type: object + certFile: + type: string + insecureSkipVerify: + type: boolean + keyFile: + type: string + keySecret: + properties: + key: type: string - volumeName: + name: + default: "" type: string + optional: + type: boolean + required: + - key type: object - required: - - spec + x-kubernetes-map-type: atomic + maxVersion: + enum: + - TLS10 + - TLS11 + - TLS12 + - TLS13 + type: string + minVersion: + enum: + - TLS10 + - TLS11 + - TLS12 + - TLS13 + type: string + serverName: + type: string type: object + trackTimestampsStaleness: + type: boolean type: object - fc: - properties: - fsType: + type: array + labels: + additionalProperties: + type: string + type: object + namespaceSelector: + properties: + any: + type: boolean + matchNames: + items: type: string - lun: - format: int32 - type: integer - readOnly: - type: boolean - targetWWNs: - items: - type: string - type: array - x-kubernetes-list-type: atomic - wwids: + type: array + type: object + type: object + sidecar: + properties: + containers: + items: + properties: + args: items: type: string type: array x-kubernetes-list-type: atomic - type: object - flexVolume: - properties: - driver: - type: string - fsType: - type: string - options: - additionalProperties: - type: string - type: object - readOnly: - type: boolean - secretRef: - properties: - name: - default: "" - type: string - type: object - x-kubernetes-map-type: atomic - required: - - driver - type: object - flocker: - properties: - datasetName: - type: string - datasetUUID: - type: string - type: object - gcePersistentDisk: - properties: - fsType: - type: string - partition: - format: int32 - type: integer - pdName: - type: string - readOnly: - type: boolean - required: - - pdName - type: object - gitRepo: - properties: - directory: - type: string - repository: - type: string - revision: - type: string - required: - - repository - type: object - glusterfs: - properties: - endpoints: - type: string - path: - type: string - readOnly: - type: boolean - required: - - endpoints - - path - type: object - hostPath: - properties: - path: - type: string - type: - type: string - required: - - path - type: object - image: - properties: - pullPolicy: - type: string - reference: - type: string - type: object - iscsi: - properties: - chapAuthDiscovery: - type: boolean - chapAuthSession: - type: boolean - fsType: - type: string - initiatorName: - type: string - iqn: - type: string - iscsiInterface: - default: default - type: string - lun: - format: int32 - type: integer - portals: + command: items: type: string type: array x-kubernetes-list-type: atomic - readOnly: - type: boolean - secretRef: - properties: - name: - default: "" - type: string - type: object - x-kubernetes-map-type: atomic - targetPortal: - type: string - required: - - iqn - - lun - - targetPortal - type: object - name: - type: string - nfs: - properties: - path: - type: string - readOnly: - type: boolean - server: - type: string - required: - - path - - server - type: object - persistentVolumeClaim: - properties: - claimName: - type: string - readOnly: - type: boolean - required: - - claimName - type: object - photonPersistentDisk: - properties: - fsType: - type: string - pdID: - type: string - required: - - pdID - type: object - portworxVolume: - properties: - fsType: - type: string - readOnly: - type: boolean - volumeID: - type: string - required: - - volumeID - type: object - projected: - properties: - defaultMode: - format: int32 - type: integer - sources: + env: items: properties: - clusterTrustBundle: + name: + type: string + value: + type: string + valueFrom: properties: - labelSelector: + configMapKeyRef: properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + fileKeyRef: + properties: + key: + type: string + optional: + default: false + type: boolean + path: + type: string + volumeName: + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key type: object x-kubernetes-map-type: atomic - name: - type: string - optional: - type: boolean - path: - type: string - signerName: - type: string - required: - - path - type: object - configMap: - properties: - items: - items: - properties: - key: - type: string - mode: - format: int32 - type: integer - path: - type: string - required: - - key - - path - type: object - type: array - x-kubernetes-list-type: atomic - name: - default: "" - type: string - optional: - type: boolean - type: object - x-kubernetes-map-type: atomic - downwardAPI: - properties: - items: - items: - properties: - fieldRef: - properties: - apiVersion: - type: string - fieldPath: - type: string - required: - - fieldPath - type: object - x-kubernetes-map-type: atomic - mode: - format: int32 - type: integer - path: - type: string - resourceFieldRef: - properties: - containerName: - type: string - divisor: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - resource: - type: string - required: - - resource - type: object - x-kubernetes-map-type: atomic - required: - - path - type: object - type: array - x-kubernetes-list-type: atomic type: object - secret: + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + envFrom: + items: + properties: + configMapRef: properties: - items: - items: - properties: - key: - type: string - mode: - format: int32 - type: integer - path: - type: string - required: - - key - - path - type: object - type: array - x-kubernetes-list-type: atomic name: default: "" type: string @@ -3228,283 +2332,257 @@ spec: type: boolean type: object x-kubernetes-map-type: atomic - serviceAccountToken: + prefix: + type: string + secretRef: properties: - audience: - type: string - expirationSeconds: - format: int64 - type: integer - path: + name: + default: "" type: string - required: - - path + optional: + type: boolean type: object + x-kubernetes-map-type: atomic type: object type: array x-kubernetes-list-type: atomic - type: object - quobyte: - properties: - group: - type: string - readOnly: - type: boolean - registry: - type: string - tenant: - type: string - user: - type: string - volume: - type: string - required: - - registry - - volume - type: object - rbd: - properties: - fsType: - type: string image: type: string - keyring: - default: /etc/ceph/keyring - type: string - monitors: - items: - type: string - type: array - x-kubernetes-list-type: atomic - pool: - default: rbd + imagePullPolicy: type: string - readOnly: - type: boolean - secretRef: + lifecycle: properties: - name: - default: "" + postStart: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + stopSignal: type: string type: object - x-kubernetes-map-type: atomic - user: - default: admin - type: string - required: - - image - - monitors - type: object - scaleIO: - properties: - fsType: - default: xfs - type: string - gateway: - type: string - protectionDomain: - type: string - readOnly: - type: boolean - secretRef: + livenessProbe: properties: - name: - default: "" - type: string + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer type: object - x-kubernetes-map-type: atomic - sslEnabled: - type: boolean - storageMode: - default: ThinProvisioned - type: string - storagePool: - type: string - system: - type: string - volumeName: + name: type: string - required: - - gateway - - secretRef - - system - type: object - secret: - properties: - defaultMode: - format: int32 - type: integer - items: + ports: items: properties: - key: + containerPort: + format: int32 + type: integer + hostIP: type: string - mode: + hostPort: format: int32 type: integer - path: + name: + type: string + protocol: + default: TCP type: string required: - - key - - path + - containerPort type: object type: array - x-kubernetes-list-type: atomic - optional: - type: boolean - secretName: - type: string - type: object - storageos: - properties: - fsType: - type: string - readOnly: - type: boolean - secretRef: - properties: - name: - default: "" - type: string - type: object - x-kubernetes-map-type: atomic - volumeName: - type: string - volumeNamespace: - type: string - type: object - vsphereVolume: - properties: - fsType: - type: string - storagePolicyID: - type: string - storagePolicyName: - type: string - volumePath: - type: string - required: - - volumePath - type: object - required: - - name - type: object - type: array - sidecars: - items: - properties: - args: - items: - type: string - type: array - x-kubernetes-list-type: atomic - command: - items: - type: string - type: array - x-kubernetes-list-type: atomic - env: - items: - properties: - name: - type: string - value: - type: string - valueFrom: - properties: - configMapKeyRef: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - fieldRef: - properties: - apiVersion: - type: string - fieldPath: - type: string - required: - - fieldPath - type: object - x-kubernetes-map-type: atomic - resourceFieldRef: - properties: - containerName: - type: string - divisor: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - resource: - type: string - required: - - resource - type: object - x-kubernetes-map-type: atomic - secretKeyRef: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - envFrom: - items: - properties: - configMapRef: - properties: - name: - default: "" - type: string - optional: - type: boolean - type: object - x-kubernetes-map-type: atomic - prefix: - type: string - secretRef: - properties: - name: - default: "" - type: string - optional: - type: boolean - type: object - x-kubernetes-map-type: atomic - type: object - type: array - x-kubernetes-list-type: atomic - image: - type: string - imagePullPolicy: - type: string - lifecycle: - properties: - postStart: + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: properties: exec: properties: @@ -3514,6 +2592,20 @@ spec: type: array x-kubernetes-list-type: atomic type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object httpGet: properties: host: @@ -3543,14 +2635,15 @@ spec: required: - port type: object - sleep: - properties: - seconds: - format: int64 - type: integer - required: - - seconds - type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer tcpSocket: properties: host: @@ -3563,8 +2656,157 @@ spec: required: - port type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + resizePolicy: + items: + properties: + resourceName: + type: string + restartPolicy: + type: string + required: + - resourceName + - restartPolicy + type: object + type: array + x-kubernetes-list-type: atomic + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + restartPolicy: + type: string + restartPolicyRules: + items: + properties: + action: + type: string + exitCodes: + properties: + operator: + type: string + values: + items: + format: int32 + type: integer + type: array + x-kubernetes-list-type: set + required: + - operator + type: object + required: + - action + type: object + type: array + x-kubernetes-list-type: atomic + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + capabilities: + properties: + add: + items: + type: string + type: array + x-kubernetes-list-type: atomic + drop: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object type: object - preStop: + startupProbe: properties: exec: properties: @@ -3574,6 +2816,20 @@ spec: type: array x-kubernetes-list-type: atomic type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object httpGet: properties: host: @@ -3603,14 +2859,15 @@ spec: required: - port type: object - sleep: - properties: - seconds: - format: int64 - type: integer - required: - - seconds - type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer tcpSocket: properties: host: @@ -3623,1927 +2880,1965 @@ spec: required: - port type: object - type: object - type: object - livenessProbe: - properties: - exec: - properties: - command: - items: - type: string - type: array - x-kubernetes-list-type: atomic - type: object - failureThreshold: - format: int32 - type: integer - grpc: - properties: - port: + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: format: int32 type: integer - service: - default: "" - type: string - required: - - port type: object - httpGet: - properties: - host: - type: string - httpHeaders: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - x-kubernetes-list-type: atomic - path: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - scheme: - type: string - required: - - port - type: object - initialDelaySeconds: - format: int32 - type: integer - periodSeconds: - format: int32 - type: integer - successThreshold: - format: int32 - type: integer - tcpSocket: - properties: - host: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - required: - - port - type: object - terminationGracePeriodSeconds: - format: int64 - type: integer - timeoutSeconds: - format: int32 - type: integer - type: object - name: - type: string - ports: - items: - properties: - containerPort: - format: int32 - type: integer - hostIP: - type: string - hostPort: - format: int32 - type: integer - name: - type: string - protocol: - default: TCP - type: string - required: - - containerPort - type: object - type: array - x-kubernetes-list-map-keys: - - containerPort - - protocol - x-kubernetes-list-type: map - readinessProbe: - properties: - exec: - properties: - command: - items: + stdin: + type: boolean + stdinOnce: + type: boolean + terminationMessagePath: + type: string + terminationMessagePolicy: + type: string + tty: + type: boolean + volumeDevices: + items: + properties: + devicePath: type: string - type: array - x-kubernetes-list-type: atomic - type: object - failureThreshold: - format: int32 - type: integer - grpc: - properties: - port: - format: int32 - type: integer - service: - default: "" - type: string - required: - - port - type: object - httpGet: - properties: - host: - type: string - httpHeaders: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - x-kubernetes-list-type: atomic - path: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - scheme: - type: string - required: - - port - type: object - initialDelaySeconds: - format: int32 - type: integer - periodSeconds: - format: int32 - type: integer - successThreshold: - format: int32 - type: integer - tcpSocket: - properties: - host: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - required: - - port - type: object - terminationGracePeriodSeconds: - format: int64 - type: integer - timeoutSeconds: - format: int32 - type: integer - type: object - resizePolicy: - items: - properties: - resourceName: - type: string - restartPolicy: - type: string - required: - - resourceName - - restartPolicy - type: object - type: array - x-kubernetes-list-type: atomic - resources: - properties: - claims: + name: + type: string + required: + - devicePath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - devicePath + x-kubernetes-list-type: map + volumeMounts: items: properties: + mountPath: + type: string + mountPropagation: + type: string name: type: string - request: + readOnly: + type: boolean + recursiveReadOnly: + type: string + subPath: + type: string + subPathExpr: type: string required: + - mountPath - name type: object type: array x-kubernetes-list-map-keys: - - name + - mountPath x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object + workingDir: + type: string + required: + - name type: object - restartPolicy: - type: string - securityContext: + type: array + volumes: + items: properties: - allowPrivilegeEscalation: - type: boolean - appArmorProfile: + awsElasticBlockStore: properties: - localhostProfile: + fsType: type: string - type: + partition: + format: int32 + type: integer + readOnly: + type: boolean + volumeID: type: string required: - - type - type: object - capabilities: - properties: - add: - items: - type: string - type: array - x-kubernetes-list-type: atomic - drop: - items: - type: string - type: array - x-kubernetes-list-type: atomic + - volumeID type: object - privileged: - type: boolean - procMount: - type: string - readOnlyRootFilesystem: - type: boolean - runAsGroup: - format: int64 - type: integer - runAsNonRoot: - type: boolean - runAsUser: - format: int64 - type: integer - seLinuxOptions: + azureDisk: properties: - level: + cachingMode: type: string - role: - type: string - type: + diskName: type: string - user: + diskURI: type: string - type: object - seccompProfile: - properties: - localhostProfile: + fsType: + default: ext4 type: string - type: + kind: type: string + readOnly: + default: false + type: boolean required: - - type + - diskName + - diskURI type: object - windowsOptions: + azureFile: properties: - gmsaCredentialSpec: - type: string - gmsaCredentialSpecName: - type: string - hostProcess: + readOnly: type: boolean - runAsUserName: + secretName: + type: string + shareName: type: string + required: + - secretName + - shareName type: object - type: object - startupProbe: - properties: - exec: + cephfs: properties: - command: + monitors: items: type: string type: array x-kubernetes-list-type: atomic + path: + type: string + readOnly: + type: boolean + secretFile: + type: string + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + user: + type: string + required: + - monitors type: object - failureThreshold: - format: int32 - type: integer - grpc: + cinder: properties: - port: - format: int32 - type: integer - service: - default: "" + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + volumeID: type: string required: - - port + - volumeID type: object - httpGet: + configMap: properties: - host: - type: string - httpHeaders: + defaultMode: + format: int32 + type: integer + items: items: properties: - name: + key: type: string - value: + mode: + format: int32 + type: integer + path: type: string required: - - name - - value + - key + - path type: object type: array x-kubernetes-list-type: atomic - path: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - scheme: + name: + default: "" type: string - required: - - port + optional: + type: boolean type: object - initialDelaySeconds: - format: int32 - type: integer - periodSeconds: - format: int32 - type: integer - successThreshold: - format: int32 - type: integer - tcpSocket: + x-kubernetes-map-type: atomic + csi: properties: - host: + driver: type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true + fsType: + type: string + nodePublishSecretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: + type: boolean + volumeAttributes: + additionalProperties: + type: string + type: object required: - - port + - driver type: object - terminationGracePeriodSeconds: - format: int64 - type: integer - timeoutSeconds: - format: int32 - type: integer - type: object - stdin: - type: boolean - stdinOnce: - type: boolean - terminationMessagePath: - type: string - terminationMessagePolicy: - type: string - tty: - type: boolean - volumeDevices: - items: - properties: - devicePath: - type: string - name: - type: string - required: - - devicePath - - name - type: object - type: array - x-kubernetes-list-map-keys: - - devicePath - x-kubernetes-list-type: map - volumeMounts: - items: - properties: - mountPath: - type: string - mountPropagation: - type: string - name: - type: string - readOnly: - type: boolean - recursiveReadOnly: - type: string - subPath: - type: string - subPathExpr: - type: string - required: - - mountPath - - name - type: object - type: array - x-kubernetes-list-map-keys: - - mountPath - x-kubernetes-list-type: map - workingDir: - type: string - required: - - name - type: object - type: array - type: object - status: - properties: - conditions: - items: - properties: - lastTransitionTime: - format: date-time - type: string - message: - maxLength: 32768 - type: string - observedGeneration: - format: int64 - minimum: 0 - type: integer - reason: - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - enum: - - "True" - - "False" - - Unknown - type: string - type: - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - exporterConfig: - type: string - replicas: - type: integer - status: - type: string - version: - type: string - required: - - conditions - - exporterConfig - - version - type: object - type: object - served: true - storage: false - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .status.exporterConfig - name: ExporterConfig - type: string - - jsonPath: .status.status - name: Status - type: string - - jsonPath: .status.version - name: Version - type: string - name: v1alpha1 - schema: - openAPIV3Schema: - properties: - apiVersion: - type: string - kind: - type: string - metadata: - type: object - spec: - properties: - configuration: - properties: - configMap: - properties: - key: - type: string - name: - type: string - type: object - type: object - database: - properties: - dbConnectionString: - properties: - key: - type: string - secret: - type: string - type: object - dbPassword: - properties: - key: - type: string - secret: - type: string - vaultOCID: - type: string - vaultSecretName: - type: string - type: object - dbUser: - properties: - key: - type: string - secret: - type: string - type: object - dbWallet: - properties: - key: - type: string - secret: - type: string - type: object - type: object - exporter: - properties: - deployment: - properties: - args: - items: - type: string - type: array - commands: - items: - type: string - type: array - env: - additionalProperties: - type: string - type: object - image: - type: string - labels: - additionalProperties: - type: string - type: object - podTemplate: - properties: - labels: - additionalProperties: - type: string - type: object - securityContext: - properties: - appArmorProfile: - properties: - localhostProfile: - type: string - type: - type: string - required: - - type - type: object - fsGroup: - format: int64 - type: integer - fsGroupChangePolicy: - type: string - runAsGroup: - format: int64 - type: integer - runAsNonRoot: - type: boolean - runAsUser: - format: int64 - type: integer - seLinuxOptions: - properties: - level: - type: string - role: - type: string - type: - type: string - user: - type: string - type: object - seccompProfile: + downwardAPI: + properties: + defaultMode: + format: int32 + type: integer + items: + items: properties: - localhostProfile: - type: string - type: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + format: int32 + type: integer + path: type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic required: - - type + - path type: object - supplementalGroups: - items: - format: int64 - type: integer - type: array - x-kubernetes-list-type: atomic - supplementalGroupsPolicy: - type: string - sysctls: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value + type: array + x-kubernetes-list-type: atomic + type: object + emptyDir: + properties: + medium: + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + properties: + volumeClaimTemplate: + properties: + metadata: type: object - type: array - x-kubernetes-list-type: atomic - windowsOptions: - properties: - gmsaCredentialSpec: - type: string - gmsaCredentialSpecName: - type: string - hostProcess: - type: boolean - runAsUserName: - type: string - type: object - type: object - type: object - securityContext: - properties: - allowPrivilegeEscalation: - type: boolean - appArmorProfile: - properties: - localhostProfile: - type: string - type: - type: string - required: - - type - type: object - capabilities: - properties: - add: - items: - type: string - type: array - x-kubernetes-list-type: atomic - drop: - items: - type: string - type: array - x-kubernetes-list-type: atomic - type: object - privileged: - type: boolean - procMount: - type: string - readOnlyRootFilesystem: - type: boolean - runAsGroup: - format: int64 - type: integer - runAsNonRoot: - type: boolean - runAsUser: - format: int64 - type: integer - seLinuxOptions: - properties: - level: - type: string - role: - type: string - type: - type: string - user: - type: string - type: object - seccompProfile: - properties: - localhostProfile: - type: string - type: - type: string - required: - - type - type: object - windowsOptions: - properties: - gmsaCredentialSpec: + spec: + properties: + accessModes: + items: + type: string + type: array + x-kubernetes-list-type: atomic + dataSource: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + dataSourceRef: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + required: + - kind + - name + type: object + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + storageClassName: + type: string + volumeAttributesClassName: + type: string + volumeMode: + type: string + volumeName: + type: string + type: object + required: + - spec + type: object + type: object + fc: + properties: + fsType: + type: string + lun: + format: int32 + type: integer + readOnly: + type: boolean + targetWWNs: + items: type: string - gmsaCredentialSpecName: + type: array + x-kubernetes-list-type: atomic + wwids: + items: type: string - hostProcess: - type: boolean - runAsUserName: + type: array + x-kubernetes-list-type: atomic + type: object + flexVolume: + properties: + driver: + type: string + fsType: + type: string + options: + additionalProperties: type: string - type: object - type: object - type: object - service: - properties: - labels: - additionalProperties: - type: string - type: object - ports: - items: + type: object + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + required: + - driver + type: object + flocker: properties: - appProtocol: + datasetName: type: string - name: + datasetUUID: type: string - nodePort: - format: int32 - type: integer - port: + type: object + gcePersistentDisk: + properties: + fsType: + type: string + partition: format: int32 type: integer - protocol: - default: TCP + pdName: type: string - targetPort: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true + readOnly: + type: boolean required: - - port + - pdName type: object - type: array - type: object - type: object - inheritLabels: - items: - type: string - type: array - log: - properties: - filename: - type: string - path: - type: string - volume: - properties: - name: - type: string - persistentVolumeClaim: - properties: - claimName: - type: string - type: object - type: object - type: object - ociConfig: - properties: - configMapName: - type: string - secretName: - type: string - type: object - prometheus: - properties: - serviceMonitor: - properties: - endpoints: - items: + gitRepo: properties: - authorization: - properties: - credentials: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: - type: string - type: object - basicAuth: - properties: - password: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - username: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - bearerTokenFile: + directory: + type: string + repository: + type: string + revision: + type: string + required: + - repository + type: object + glusterfs: + properties: + endpoints: type: string - bearerTokenSecret: + path: + type: string + readOnly: + type: boolean + required: + - endpoints + - path + type: object + hostPath: + properties: + path: + type: string + type: + type: string + required: + - path + type: object + image: + properties: + pullPolicy: + type: string + reference: + type: string + type: object + iscsi: + properties: + chapAuthDiscovery: + type: boolean + chapAuthSession: + type: boolean + fsType: + type: string + initiatorName: + type: string + iqn: + type: string + iscsiInterface: + default: default + type: string + lun: + format: int32 + type: integer + portals: + items: + type: string + type: array + x-kubernetes-list-type: atomic + readOnly: + type: boolean + secretRef: properties: - key: - type: string name: default: "" type: string - optional: - type: boolean - required: - - key type: object x-kubernetes-map-type: atomic - enableHttp2: - type: boolean - filterRunning: - type: boolean - followRedirects: + targetPortal: + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + type: string + nfs: + properties: + path: + type: string + readOnly: type: boolean - honorLabels: + server: + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + properties: + claimName: + type: string + readOnly: type: boolean - honorTimestamps: + required: + - claimName + type: object + photonPersistentDisk: + properties: + fsType: + type: string + pdID: + type: string + required: + - pdID + type: object + portworxVolume: + properties: + fsType: + type: string + readOnly: type: boolean - interval: - pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ + volumeID: type: string - metricRelabelings: - items: - properties: - action: - default: replace - enum: - - replace - - Replace - - keep - - Keep - - drop - - Drop - - hashmod - - HashMod - - labelmap - - LabelMap - - labeldrop - - LabelDrop - - labelkeep - - LabelKeep - - lowercase - - Lowercase - - uppercase - - Uppercase - - keepequal - - KeepEqual - - dropequal - - DropEqual - type: string - modulus: - format: int64 - type: integer - regex: - type: string - replacement: - type: string - separator: - type: string - sourceLabels: - items: - pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ - type: string - type: array - targetLabel: - type: string - type: object - type: array - oauth2: - properties: - clientId: - properties: - configMap: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - clientSecret: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - endpointParams: - additionalProperties: - type: string - type: object - noProxy: - type: string - proxyConnectHeader: - additionalProperties: - items: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: array - type: object - x-kubernetes-map-type: atomic - proxyFromEnvironment: - type: boolean - proxyUrl: - pattern: ^http(s)?://.+$ - type: string - scopes: - items: - type: string - type: array - tlsConfig: - properties: - ca: - properties: - configMap: - properties: - key: - type: string - name: - default: "" + required: + - volumeID + type: object + projected: + properties: + defaultMode: + format: int32 + type: integer + sources: + items: + properties: + clusterTrustBundle: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: + type: object + type: object + x-kubernetes-map-type: atomic + name: + type: string + optional: + type: boolean + path: + type: string + signerName: + type: string + required: + - path + type: object + configMap: + properties: + items: + items: properties: key: type: string - name: - default: "" + mode: + format: int32 + type: integer + path: type: string - optional: - type: boolean required: - key + - path type: object - x-kubernetes-map-type: atomic - type: object - cert: - properties: - configMap: + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + downwardAPI: + properties: + items: + items: properties: - key: - type: string - name: - default: "" + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + format: int32 + type: integer + path: type: string - optional: - type: boolean + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic required: - - key + - path type: object - x-kubernetes-map-type: atomic - secret: + type: array + x-kubernetes-list-type: atomic + type: object + podCertificate: + properties: + certificateChainPath: + type: string + credentialBundlePath: + type: string + keyPath: + type: string + keyType: + type: string + maxExpirationSeconds: + format: int32 + type: integer + signerName: + type: string + required: + - keyType + - signerName + type: object + secret: + properties: + items: + items: properties: key: type: string - name: - default: "" + mode: + format: int32 + type: integer + path: type: string - optional: - type: boolean required: - key + - path type: object - x-kubernetes-map-type: atomic - type: object - insecureSkipVerify: - type: boolean - keySecret: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - maxVersion: - enum: - - TLS10 - - TLS11 - - TLS12 - - TLS13 - type: string - minVersion: - enum: - - TLS10 - - TLS11 - - TLS12 - - TLS13 - type: string - serverName: - type: string - type: object - tokenUrl: - minLength: 1 + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + serviceAccountToken: + properties: + audience: + type: string + expirationSeconds: + format: int64 + type: integer + path: + type: string + required: + - path + type: object + type: object + type: array + x-kubernetes-list-type: atomic + type: object + quobyte: + properties: + group: + type: string + readOnly: + type: boolean + registry: + type: string + tenant: + type: string + user: + type: string + volume: + type: string + required: + - registry + - volume + type: object + rbd: + properties: + fsType: + type: string + image: + type: string + keyring: + default: /etc/ceph/keyring + type: string + monitors: + items: + type: string + type: array + x-kubernetes-list-type: atomic + pool: + default: rbd + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" type: string - required: - - clientId - - clientSecret - - tokenUrl type: object - params: - additionalProperties: - items: + x-kubernetes-map-type: atomic + user: + default: admin + type: string + required: + - image + - monitors + type: object + scaleIO: + properties: + fsType: + default: xfs + type: string + gateway: + type: string + protectionDomain: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" type: string - type: array type: object - path: + x-kubernetes-map-type: atomic + sslEnabled: + type: boolean + storageMode: + default: ThinProvisioned type: string - port: + storagePool: type: string - proxyUrl: + system: type: string - relabelings: + volumeName: + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + properties: + defaultMode: + format: int32 + type: integer + items: items: properties: - action: - default: replace - enum: - - replace - - Replace - - keep - - Keep - - drop - - Drop - - hashmod - - HashMod - - labelmap - - LabelMap - - labeldrop - - LabelDrop - - labelkeep - - LabelKeep - - lowercase - - Lowercase - - uppercase - - Uppercase - - keepequal - - KeepEqual - - dropequal - - DropEqual + key: type: string - modulus: - format: int64 + mode: + format: int32 type: integer - regex: - type: string - replacement: - type: string - separator: - type: string - sourceLabels: - items: - pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ - type: string - type: array - targetLabel: + path: type: string + required: + - key + - path type: object type: array - scheme: - enum: - - http - - https + x-kubernetes-list-type: atomic + optional: + type: boolean + secretName: type: string - scrapeTimeout: - pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ + type: object + storageos: + properties: + fsType: type: string - targetPort: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - tlsConfig: + readOnly: + type: boolean + secretRef: properties: - ca: - properties: - configMap: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - caFile: - type: string - cert: - properties: - configMap: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - certFile: - type: string - insecureSkipVerify: - type: boolean - keyFile: - type: string - keySecret: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - maxVersion: - enum: - - TLS10 - - TLS11 - - TLS12 - - TLS13 - type: string - minVersion: - enum: - - TLS10 - - TLS11 - - TLS12 - - TLS13 - type: string - serverName: + name: + default: "" type: string type: object - trackTimestampsStaleness: - type: boolean + x-kubernetes-map-type: atomic + volumeName: + type: string + volumeNamespace: + type: string type: object - type: array - labels: - additionalProperties: - type: string - type: object - namespaceSelector: - properties: - any: - type: boolean - matchNames: - items: + vsphereVolume: + properties: + fsType: type: string - type: array - type: object - type: object + storagePolicyID: + type: string + storagePolicyName: + type: string + volumePath: + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + type: object + wallet: + properties: + additional: + items: + properties: + mountPath: + type: string + name: + type: string + secret: + type: string + type: object + type: array + mountPath: + type: string + secret: + type: string type: object + type: object + status: + properties: + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + maxLength: 32768 + type: string + observedGeneration: + format: int64 + minimum: 0 + type: integer + reason: + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + enum: + - "True" + - "False" + - Unknown + type: string + type: + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + metricsConfig: + type: string replicas: - format: int32 type: integer - sidecarVolumes: - items: + status: + type: string + version: + type: string + required: + - conditions + - metricsConfig + - version + type: object + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .status.metricsConfig + name: MetricsConfig + type: string + - jsonPath: .status.status + name: Status + type: string + - jsonPath: .status.version + name: Version + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + azureConfig: + properties: + configMap: + properties: + key: + type: string + name: + type: string + type: object + type: object + database: + properties: + azure: + properties: + vaultID: + type: string + vaultPasswordSecret: + type: string + vaultUsernameSecret: + type: string + type: object + dbConnectionString: + properties: + envName: + type: string + key: + type: string + secret: + type: string + type: object + dbPassword: + properties: + envName: + type: string + key: + type: string + secret: + type: string + type: object + dbUser: + properties: + envName: + type: string + key: + type: string + secret: + type: string + type: object + oci: + properties: + vaultID: + type: string + vaultPasswordSecret: + type: string + type: object + type: object + databases: + additionalProperties: properties: - awsElasticBlockStore: + dbConnectionString: properties: - fsType: + envName: type: string - partition: - format: int32 - type: integer - readOnly: - type: boolean - volumeID: + key: + type: string + secret: type: string - required: - - volumeID type: object - azureDisk: + dbPassword: properties: - cachingMode: - type: string - diskName: + envName: type: string - diskURI: + key: type: string - fsType: - default: ext4 + secret: type: string - kind: - type: string - readOnly: - default: false - type: boolean - required: - - diskName - - diskURI type: object - azureFile: + dbUser: properties: - readOnly: - type: boolean - secretName: + envName: type: string - shareName: + key: + type: string + secret: type: string - required: - - secretName - - shareName type: object - cephfs: - properties: - monitors: - items: - type: string - type: array - x-kubernetes-list-type: atomic - path: - type: string - readOnly: - type: boolean - secretFile: - type: string - secretRef: + type: object + type: object + deployment: + properties: + args: + items: + type: string + type: array + commands: + items: + type: string + type: array + env: + additionalProperties: + type: string + type: object + image: + type: string + labels: + additionalProperties: + type: string + type: object + podSecurityContext: + properties: + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + fsGroup: + format: int64 + type: integer + fsGroupChangePolicy: + type: string + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxChangePolicy: + type: string + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + supplementalGroups: + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + supplementalGroupsPolicy: + type: string + sysctls: + items: properties: name: - default: "" type: string - type: object - x-kubernetes-map-type: atomic - user: - type: string - required: - - monitors - type: object - cinder: - properties: - fsType: - type: string - readOnly: - type: boolean - secretRef: - properties: - name: - default: "" + value: type: string + required: + - name + - value type: object - x-kubernetes-map-type: atomic - volumeID: + type: array + x-kubernetes-list-type: atomic + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + podTemplate: + properties: + labels: + additionalProperties: type: string - required: - - volumeID - type: object - configMap: + type: object + type: object + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + capabilities: + properties: + add: + items: + type: string + type: array + x-kubernetes-list-type: atomic + drop: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + type: object + exporterConfig: + properties: + configMap: + properties: + key: + type: string + name: + type: string + type: object + mountPath: + type: string + type: object + inheritLabels: + items: + type: string + type: array + log: + properties: + destination: + type: string + disable: + type: boolean + filename: + type: string + volume: + properties: + name: + type: string + persistentVolumeClaim: + properties: + claimName: + type: string + type: object + type: object + type: object + metrics: + properties: + configMap: + items: properties: - defaultMode: - format: int32 - type: integer - items: - items: - properties: - key: - type: string - mode: - format: int32 - type: integer - path: - type: string - required: - - key - - path - type: object - type: array - x-kubernetes-list-type: atomic + key: + type: string name: - default: "" type: string - optional: - type: boolean type: object - x-kubernetes-map-type: atomic - csi: + type: array + type: object + ociConfig: + properties: + configMap: + properties: + key: + type: string + name: + type: string + type: object + mountPath: + type: string + privateKey: + properties: + secret: + type: string + type: object + type: object + replicas: + format: int32 + type: integer + service: + properties: + labels: + additionalProperties: + type: string + type: object + ports: + items: properties: - driver: + appProtocol: type: string - fsType: + name: type: string - nodePublishSecretRef: - properties: - name: - default: "" - type: string - type: object - x-kubernetes-map-type: atomic - readOnly: - type: boolean - volumeAttributes: - additionalProperties: - type: string - type: object - required: - - driver - type: object - downwardAPI: - properties: - defaultMode: + nodePort: format: int32 type: integer - items: - items: - properties: - fieldRef: - properties: - apiVersion: - type: string - fieldPath: - type: string - required: - - fieldPath - type: object - x-kubernetes-map-type: atomic - mode: - format: int32 - type: integer - path: - type: string - resourceFieldRef: - properties: - containerName: - type: string - divisor: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - resource: - type: string - required: - - resource - type: object - x-kubernetes-map-type: atomic - required: - - path - type: object - type: array - x-kubernetes-list-type: atomic - type: object - emptyDir: - properties: - medium: + port: + format: int32 + type: integer + protocol: + default: TCP type: string - sizeLimit: + targetPort: anyOf: - type: integer - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true + required: + - port type: object - ephemeral: + type: array + type: object + serviceMonitor: + properties: + endpoints: + items: properties: - volumeClaimTemplate: + authorization: properties: - metadata: + credentials: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key type: object - spec: + x-kubernetes-map-type: atomic + type: + type: string + type: object + basicAuth: + properties: + password: properties: - accessModes: - items: - type: string - type: array - x-kubernetes-list-type: atomic - dataSource: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + username: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + bearerTokenFile: + type: string + bearerTokenSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + enableHttp2: + type: boolean + filterRunning: + type: boolean + followRedirects: + type: boolean + honorLabels: + type: boolean + honorTimestamps: + type: boolean + interval: + pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ + type: string + metricRelabelings: + items: + properties: + action: + default: replace + enum: + - replace + - Replace + - keep + - Keep + - drop + - Drop + - hashmod + - HashMod + - labelmap + - LabelMap + - labeldrop + - LabelDrop + - labelkeep + - LabelKeep + - lowercase + - Lowercase + - uppercase + - Uppercase + - keepequal + - KeepEqual + - dropequal + - DropEqual + type: string + modulus: + format: int64 + type: integer + regex: + type: string + replacement: + type: string + separator: + type: string + sourceLabels: + items: + pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ + type: string + type: array + targetLabel: + type: string + type: object + type: array + noProxy: + type: string + oauth2: + properties: + clientId: + properties: + configMap: properties: - apiGroup: - type: string - kind: + key: type: string name: + default: "" type: string + optional: + type: boolean required: - - kind - - name + - key type: object x-kubernetes-map-type: atomic - dataSourceRef: + secret: properties: - apiGroup: - type: string - kind: + key: type: string name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + clientSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + endpointParams: + additionalProperties: + type: string + type: object + noProxy: + type: string + proxyConnectHeader: + additionalProperties: + items: + properties: + key: type: string - namespace: + name: + default: "" type: string + optional: + type: boolean required: - - kind - - name + - key type: object - resources: + x-kubernetes-map-type: atomic + type: array + type: object + x-kubernetes-map-type: atomic + proxyFromEnvironment: + type: boolean + proxyUrl: + pattern: ^(http|https|socks5)://.+$ + type: string + scopes: + items: + type: string + type: array + tlsConfig: + properties: + ca: properties: - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true + configMap: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true + x-kubernetes-map-type: atomic + secret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key type: object + x-kubernetes-map-type: atomic type: object - selector: + cert: properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string + configMap: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + secret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key type: object + x-kubernetes-map-type: atomic + type: object + insecureSkipVerify: + type: boolean + keySecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key type: object x-kubernetes-map-type: atomic - storageClassName: - type: string - volumeAttributesClassName: + maxVersion: + enum: + - TLS10 + - TLS11 + - TLS12 + - TLS13 type: string - volumeMode: + minVersion: + enum: + - TLS10 + - TLS11 + - TLS12 + - TLS13 type: string - volumeName: + serverName: type: string type: object + tokenUrl: + minLength: 1 + type: string required: - - spec + - clientId + - clientSecret + - tokenUrl type: object - type: object - fc: - properties: - fsType: - type: string - lun: - format: int32 - type: integer - readOnly: - type: boolean - targetWWNs: - items: - type: string - type: array - x-kubernetes-list-type: atomic - wwids: - items: - type: string - type: array - x-kubernetes-list-type: atomic - type: object - flexVolume: - properties: - driver: - type: string - fsType: - type: string - options: + params: additionalProperties: - type: string - type: object - readOnly: - type: boolean - secretRef: - properties: - name: - default: "" + items: type: string + type: array type: object - x-kubernetes-map-type: atomic - required: - - driver - type: object - flocker: - properties: - datasetName: - type: string - datasetUUID: - type: string - type: object - gcePersistentDisk: - properties: - fsType: + path: type: string - partition: - format: int32 - type: integer - pdName: + port: type: string - readOnly: - type: boolean - required: - - pdName - type: object - gitRepo: - properties: - directory: - type: string - repository: - type: string - revision: - type: string - required: - - repository - type: object - glusterfs: - properties: - endpoints: - type: string - path: - type: string - readOnly: - type: boolean - required: - - endpoints - - path - type: object - hostPath: - properties: - path: - type: string - type: - type: string - required: - - path - type: object - image: - properties: - pullPolicy: - type: string - reference: - type: string - type: object - iscsi: - properties: - chapAuthDiscovery: - type: boolean - chapAuthSession: + proxyConnectHeader: + additionalProperties: + items: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: array + type: object + x-kubernetes-map-type: atomic + proxyFromEnvironment: type: boolean - fsType: + proxyUrl: + pattern: ^(http|https|socks5)://.+$ type: string - initiatorName: - type: string - iqn: - type: string - iscsiInterface: - default: default - type: string - lun: - format: int32 - type: integer - portals: + relabelings: items: - type: string + properties: + action: + default: replace + enum: + - replace + - Replace + - keep + - Keep + - drop + - Drop + - hashmod + - HashMod + - labelmap + - LabelMap + - labeldrop + - LabelDrop + - labelkeep + - LabelKeep + - lowercase + - Lowercase + - uppercase + - Uppercase + - keepequal + - KeepEqual + - dropequal + - DropEqual + type: string + modulus: + format: int64 + type: integer + regex: + type: string + replacement: + type: string + separator: + type: string + sourceLabels: + items: + pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ + type: string + type: array + targetLabel: + type: string + type: object type: array - x-kubernetes-list-type: atomic - readOnly: - type: boolean - secretRef: + scheme: + enum: + - http + - https + type: string + scrapeTimeout: + pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ + type: string + targetPort: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + tlsConfig: properties: - name: - default: "" + ca: + properties: + configMap: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + secret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + caFile: + type: string + cert: + properties: + configMap: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + secret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + certFile: + type: string + insecureSkipVerify: + type: boolean + keyFile: + type: string + keySecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + maxVersion: + enum: + - TLS10 + - TLS11 + - TLS12 + - TLS13 + type: string + minVersion: + enum: + - TLS10 + - TLS11 + - TLS12 + - TLS13 + type: string + serverName: type: string type: object - x-kubernetes-map-type: atomic - targetPortal: - type: string - required: - - iqn - - lun - - targetPortal - type: object - name: - type: string - nfs: - properties: - path: - type: string - readOnly: - type: boolean - server: - type: string - required: - - path - - server - type: object - persistentVolumeClaim: - properties: - claimName: - type: string - readOnly: + trackTimestampsStaleness: type: boolean - required: - - claimName type: object - photonPersistentDisk: - properties: - fsType: - type: string - pdID: - type: string - required: - - pdID - type: object - portworxVolume: - properties: - fsType: - type: string - readOnly: - type: boolean - volumeID: + type: array + labels: + additionalProperties: + type: string + type: object + namespaceSelector: + properties: + any: + type: boolean + matchNames: + items: type: string - required: - - volumeID - type: object - projected: + type: array + type: object + type: object + sidecar: + properties: + containers: + items: properties: - defaultMode: - format: int32 - type: integer - sources: + args: + items: + type: string + type: array + x-kubernetes-list-type: atomic + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + env: items: properties: - clusterTrustBundle: + name: + type: string + value: + type: string + valueFrom: properties: - labelSelector: + configMapKeyRef: properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + fileKeyRef: + properties: + key: + type: string + optional: + default: false + type: boolean + path: + type: string + volumeName: + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key type: object x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + envFrom: + items: + properties: + configMapRef: + properties: name: - type: string - optional: - type: boolean - path: - type: string - signerName: - type: string - required: - - path - type: object - configMap: - properties: - items: - items: - properties: - key: - type: string - mode: - format: int32 - type: integer - path: - type: string - required: - - key - - path - type: object - type: array - x-kubernetes-list-type: atomic - name: - default: "" + default: "" type: string optional: type: boolean type: object x-kubernetes-map-type: atomic - downwardAPI: - properties: - items: - items: - properties: - fieldRef: - properties: - apiVersion: - type: string - fieldPath: - type: string - required: - - fieldPath - type: object - x-kubernetes-map-type: atomic - mode: - format: int32 - type: integer - path: - type: string - resourceFieldRef: - properties: - containerName: - type: string - divisor: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - resource: - type: string - required: - - resource - type: object - x-kubernetes-map-type: atomic - required: - - path - type: object - type: array - x-kubernetes-list-type: atomic - type: object - secret: + prefix: + type: string + secretRef: properties: - items: - items: - properties: - key: - type: string - mode: - format: int32 - type: integer - path: - type: string - required: - - key - - path - type: object - type: array - x-kubernetes-list-type: atomic name: default: "" type: string @@ -5551,283 +4846,246 @@ spec: type: boolean type: object x-kubernetes-map-type: atomic - serviceAccountToken: - properties: - audience: - type: string - expirationSeconds: - format: int64 - type: integer - path: - type: string - required: - - path - type: object type: object type: array x-kubernetes-list-type: atomic - type: object - quobyte: - properties: - group: - type: string - readOnly: - type: boolean - registry: - type: string - tenant: - type: string - user: - type: string - volume: - type: string - required: - - registry - - volume - type: object - rbd: - properties: - fsType: - type: string image: type: string - keyring: - default: /etc/ceph/keyring - type: string - monitors: - items: - type: string - type: array - x-kubernetes-list-type: atomic - pool: - default: rbd + imagePullPolicy: type: string - readOnly: - type: boolean - secretRef: + lifecycle: properties: - name: - default: "" + postStart: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + stopSignal: type: string type: object - x-kubernetes-map-type: atomic - user: - default: admin - type: string - required: - - image - - monitors - type: object - scaleIO: - properties: - fsType: - default: xfs - type: string - gateway: - type: string - protectionDomain: - type: string - readOnly: - type: boolean - secretRef: + livenessProbe: properties: - name: - default: "" - type: string + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer type: object - x-kubernetes-map-type: atomic - sslEnabled: - type: boolean - storageMode: - default: ThinProvisioned - type: string - storagePool: - type: string - system: - type: string - volumeName: + name: type: string - required: - - gateway - - secretRef - - system - type: object - secret: - properties: - defaultMode: - format: int32 - type: integer - items: + ports: items: properties: - key: + containerPort: + format: int32 + type: integer + hostIP: type: string - mode: + hostPort: format: int32 type: integer - path: + name: + type: string + protocol: + default: TCP type: string required: - - key - - path + - containerPort type: object type: array - x-kubernetes-list-type: atomic - optional: - type: boolean - secretName: - type: string - type: object - storageos: - properties: - fsType: - type: string - readOnly: - type: boolean - secretRef: - properties: - name: - default: "" - type: string - type: object - x-kubernetes-map-type: atomic - volumeName: - type: string - volumeNamespace: - type: string - type: object - vsphereVolume: - properties: - fsType: - type: string - storagePolicyID: - type: string - storagePolicyName: - type: string - volumePath: - type: string - required: - - volumePath - type: object - required: - - name - type: object - type: array - sidecars: - items: - properties: - args: - items: - type: string - type: array - x-kubernetes-list-type: atomic - command: - items: - type: string - type: array - x-kubernetes-list-type: atomic - env: - items: - properties: - name: - type: string - value: - type: string - valueFrom: - properties: - configMapKeyRef: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - fieldRef: - properties: - apiVersion: - type: string - fieldPath: - type: string - required: - - fieldPath - type: object - x-kubernetes-map-type: atomic - resourceFieldRef: - properties: - containerName: - type: string - divisor: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - resource: - type: string - required: - - resource - type: object - x-kubernetes-map-type: atomic - secretKeyRef: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - envFrom: - items: - properties: - configMapRef: - properties: - name: - default: "" - type: string - optional: - type: boolean - type: object - x-kubernetes-map-type: atomic - prefix: - type: string - secretRef: - properties: - name: - default: "" - type: string - optional: - type: boolean - type: object - x-kubernetes-map-type: atomic - type: object - type: array - x-kubernetes-list-type: atomic - image: - type: string - imagePullPolicy: - type: string - lifecycle: - properties: - postStart: + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: properties: exec: properties: @@ -5837,6 +5095,20 @@ spec: type: array x-kubernetes-list-type: atomic type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object httpGet: properties: host: @@ -5866,14 +5138,15 @@ spec: required: - port type: object - sleep: - properties: - seconds: - format: int64 - type: integer - required: - - seconds - type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer tcpSocket: properties: host: @@ -5886,8 +5159,157 @@ spec: required: - port type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + resizePolicy: + items: + properties: + resourceName: + type: string + restartPolicy: + type: string + required: + - resourceName + - restartPolicy + type: object + type: array + x-kubernetes-list-type: atomic + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + restartPolicy: + type: string + restartPolicyRules: + items: + properties: + action: + type: string + exitCodes: + properties: + operator: + type: string + values: + items: + format: int32 + type: integer + type: array + x-kubernetes-list-type: set + required: + - operator + type: object + required: + - action + type: object + type: array + x-kubernetes-list-type: atomic + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + capabilities: + properties: + add: + items: + type: string + type: array + x-kubernetes-list-type: atomic + drop: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object type: object - preStop: + startupProbe: properties: exec: properties: @@ -5897,6 +5319,20 @@ spec: type: array x-kubernetes-list-type: atomic type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object httpGet: properties: host: @@ -5926,14 +5362,15 @@ spec: required: - port type: object - sleep: - properties: - seconds: - format: int64 - type: integer - required: - - seconds - type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer tcpSocket: properties: host: @@ -5946,2234 +5383,2142 @@ spec: required: - port type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer type: object + stdin: + type: boolean + stdinOnce: + type: boolean + terminationMessagePath: + type: string + terminationMessagePolicy: + type: string + tty: + type: boolean + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + required: + - devicePath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - devicePath + x-kubernetes-list-type: map + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + recursiveReadOnly: + type: string + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - mountPath + x-kubernetes-list-type: map + workingDir: + type: string + required: + - name type: object - livenessProbe: + type: array + volumes: + items: properties: - exec: - properties: - command: - items: - type: string - type: array - x-kubernetes-list-type: atomic - type: object - failureThreshold: - format: int32 - type: integer - grpc: + awsElasticBlockStore: properties: - port: + fsType: + type: string + partition: format: int32 type: integer - service: - default: "" + readOnly: + type: boolean + volumeID: type: string required: - - port + - volumeID type: object - httpGet: + azureDisk: properties: - host: + cachingMode: type: string - httpHeaders: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - x-kubernetes-list-type: atomic - path: + diskName: type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - scheme: + diskURI: type: string - required: - - port - type: object - initialDelaySeconds: - format: int32 - type: integer - periodSeconds: - format: int32 - type: integer - successThreshold: - format: int32 - type: integer - tcpSocket: - properties: - host: + fsType: + default: ext4 type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true + kind: + type: string + readOnly: + default: false + type: boolean required: - - port + - diskName + - diskURI type: object - terminationGracePeriodSeconds: - format: int64 - type: integer - timeoutSeconds: - format: int32 - type: integer - type: object - name: - type: string - ports: - items: - properties: - containerPort: - format: int32 - type: integer - hostIP: - type: string - hostPort: - format: int32 - type: integer - name: - type: string - protocol: - default: TCP - type: string - required: - - containerPort - type: object - type: array - x-kubernetes-list-map-keys: - - containerPort - - protocol - x-kubernetes-list-type: map - readinessProbe: - properties: - exec: - properties: - command: - items: - type: string - type: array - x-kubernetes-list-type: atomic - type: object - failureThreshold: - format: int32 - type: integer - grpc: + azureFile: properties: - port: - format: int32 - type: integer - service: - default: "" + readOnly: + type: boolean + secretName: + type: string + shareName: type: string required: - - port + - secretName + - shareName type: object - httpGet: + cephfs: properties: - host: - type: string - httpHeaders: + monitors: items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object + type: string type: array x-kubernetes-list-type: atomic path: type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - scheme: + readOnly: + type: boolean + secretFile: type: string - required: - - port - type: object - initialDelaySeconds: - format: int32 - type: integer - periodSeconds: - format: int32 - type: integer - successThreshold: - format: int32 - type: integer - tcpSocket: - properties: - host: + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + user: type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true required: - - port - type: object - terminationGracePeriodSeconds: - format: int64 - type: integer - timeoutSeconds: - format: int32 - type: integer - type: object - resizePolicy: - items: - properties: - resourceName: - type: string - restartPolicy: - type: string - required: - - resourceName - - restartPolicy - type: object - type: array - x-kubernetes-list-type: atomic - resources: - properties: - claims: - items: - properties: - name: - type: string - request: - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true + - monitors type: object - type: object - restartPolicy: - type: string - securityContext: - properties: - allowPrivilegeEscalation: - type: boolean - appArmorProfile: + cinder: properties: - localhostProfile: + fsType: type: string - type: + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + volumeID: type: string required: - - type + - volumeID type: object - capabilities: + configMap: properties: - add: - items: - type: string - type: array - x-kubernetes-list-type: atomic - drop: + defaultMode: + format: int32 + type: integer + items: items: - type: string + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object type: array x-kubernetes-list-type: atomic - type: object - privileged: - type: boolean - procMount: - type: string - readOnlyRootFilesystem: - type: boolean - runAsGroup: - format: int64 - type: integer - runAsNonRoot: - type: boolean - runAsUser: - format: int64 - type: integer - seLinuxOptions: - properties: - level: - type: string - role: - type: string - type: - type: string - user: - type: string - type: object - seccompProfile: - properties: - localhostProfile: - type: string - type: + name: + default: "" type: string - required: - - type + optional: + type: boolean type: object - windowsOptions: + x-kubernetes-map-type: atomic + csi: properties: - gmsaCredentialSpec: + driver: type: string - gmsaCredentialSpecName: + fsType: type: string - hostProcess: + nodePublishSecretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: type: boolean - runAsUserName: - type: string - type: object - type: object - startupProbe: - properties: - exec: - properties: - command: - items: + volumeAttributes: + additionalProperties: type: string - type: array - x-kubernetes-list-type: atomic + type: object + required: + - driver type: object - failureThreshold: - format: int32 - type: integer - grpc: + downwardAPI: properties: - port: + defaultMode: format: int32 type: integer - service: - default: "" - type: string - required: - - port - type: object - httpGet: - properties: - host: - type: string - httpHeaders: + items: items: properties: - name: - type: string - value: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + format: int32 + type: integer + path: type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic required: - - name - - value + - path type: object type: array x-kubernetes-list-type: atomic - path: + type: object + emptyDir: + properties: + medium: type: string - port: + sizeLimit: anyOf: - type: integer - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - scheme: - type: string - required: - - port type: object - initialDelaySeconds: - format: int32 - type: integer - periodSeconds: - format: int32 - type: integer - successThreshold: - format: int32 - type: integer - tcpSocket: + ephemeral: properties: - host: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - required: - - port - type: object - terminationGracePeriodSeconds: - format: int64 - type: integer - timeoutSeconds: - format: int32 - type: integer - type: object - stdin: - type: boolean - stdinOnce: - type: boolean - terminationMessagePath: - type: string - terminationMessagePolicy: - type: string - tty: - type: boolean - volumeDevices: - items: - properties: - devicePath: - type: string - name: - type: string - required: - - devicePath - - name - type: object - type: array - x-kubernetes-list-map-keys: - - devicePath - x-kubernetes-list-type: map - volumeMounts: - items: - properties: - mountPath: - type: string - mountPropagation: - type: string - name: - type: string - readOnly: - type: boolean - recursiveReadOnly: - type: string - subPath: - type: string - subPathExpr: - type: string - required: - - mountPath - - name - type: object - type: array - x-kubernetes-list-map-keys: - - mountPath - x-kubernetes-list-type: map - workingDir: - type: string - required: - - name - type: object - type: array - type: object - status: - properties: - conditions: - items: - properties: - lastTransitionTime: - format: date-time - type: string - message: - maxLength: 32768 - type: string - observedGeneration: - format: int64 - minimum: 0 - type: integer - reason: - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - enum: - - "True" - - "False" - - Unknown - type: string - type: - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - exporterConfig: - type: string - replicas: - type: integer - status: - type: string - version: - type: string - required: - - conditions - - exporterConfig - - version - type: object - type: object - served: true - storage: false - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .status.exporterConfig - name: ExporterConfig - type: string - - jsonPath: .status.status - name: Status - type: string - - jsonPath: .status.version - name: Version - type: string - name: v4 - schema: - openAPIV3Schema: - properties: - apiVersion: - type: string - kind: - type: string - metadata: - type: object - spec: - properties: - configuration: - properties: - configMap: - properties: - key: - type: string - name: - type: string - type: object - type: object - database: - properties: - dbConnectionString: - properties: - key: - type: string - secret: - type: string - type: object - dbPassword: - properties: - key: - type: string - secret: - type: string - vaultOCID: - type: string - vaultSecretName: - type: string - type: object - dbUser: - properties: - key: - type: string - secret: - type: string - type: object - dbWallet: - properties: - key: - type: string - secret: - type: string - type: object - type: object - exporter: - properties: - deployment: - properties: - args: - items: - type: string - type: array - commands: - items: - type: string - type: array - env: - additionalProperties: - type: string - type: object - image: - type: string - labels: - additionalProperties: - type: string - type: object - podTemplate: - properties: - labels: - additionalProperties: - type: string - type: object - securityContext: - properties: - appArmorProfile: - properties: - localhostProfile: - type: string - type: - type: string - required: - - type - type: object - fsGroup: - format: int64 - type: integer - fsGroupChangePolicy: - type: string - runAsGroup: - format: int64 - type: integer - runAsNonRoot: - type: boolean - runAsUser: - format: int64 - type: integer - seLinuxOptions: - properties: - level: - type: string - role: - type: string - type: - type: string - user: - type: string - type: object - seccompProfile: - properties: - localhostProfile: - type: string - type: - type: string - required: - - type - type: object - supplementalGroups: - items: - format: int64 - type: integer - type: array - x-kubernetes-list-type: atomic - supplementalGroupsPolicy: - type: string - sysctls: - items: + volumeClaimTemplate: + properties: + metadata: + type: object + spec: properties: - name: + accessModes: + items: + type: string + type: array + x-kubernetes-list-type: atomic + dataSource: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + dataSourceRef: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + required: + - kind + - name + type: object + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + storageClassName: type: string - value: + volumeAttributesClassName: + type: string + volumeMode: + type: string + volumeName: type: string - required: - - name - - value type: object - type: array - x-kubernetes-list-type: atomic - windowsOptions: - properties: - gmsaCredentialSpec: - type: string - gmsaCredentialSpecName: - type: string - hostProcess: - type: boolean - runAsUserName: - type: string - type: object - type: object - type: object - securityContext: - properties: - allowPrivilegeEscalation: - type: boolean - appArmorProfile: - properties: - localhostProfile: - type: string - type: - type: string - required: - - type - type: object - capabilities: - properties: - add: - items: - type: string - type: array - x-kubernetes-list-type: atomic - drop: - items: - type: string - type: array - x-kubernetes-list-type: atomic - type: object - privileged: - type: boolean - procMount: - type: string - readOnlyRootFilesystem: - type: boolean - runAsGroup: - format: int64 - type: integer - runAsNonRoot: - type: boolean - runAsUser: - format: int64 - type: integer - seLinuxOptions: - properties: - level: - type: string - role: - type: string - type: - type: string - user: - type: string - type: object - seccompProfile: - properties: - localhostProfile: - type: string - type: - type: string - required: - - type - type: object - windowsOptions: - properties: - gmsaCredentialSpec: + required: + - spec + type: object + type: object + fc: + properties: + fsType: + type: string + lun: + format: int32 + type: integer + readOnly: + type: boolean + targetWWNs: + items: type: string - gmsaCredentialSpecName: + type: array + x-kubernetes-list-type: atomic + wwids: + items: type: string - hostProcess: - type: boolean - runAsUserName: + type: array + x-kubernetes-list-type: atomic + type: object + flexVolume: + properties: + driver: + type: string + fsType: + type: string + options: + additionalProperties: type: string - type: object - type: object - type: object - service: - properties: - labels: - additionalProperties: - type: string - type: object - ports: - items: + type: object + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + required: + - driver + type: object + flocker: properties: - appProtocol: + datasetName: type: string - name: + datasetUUID: type: string - nodePort: - format: int32 - type: integer - port: + type: object + gcePersistentDisk: + properties: + fsType: + type: string + partition: format: int32 type: integer - protocol: - default: TCP + pdName: type: string - targetPort: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true + readOnly: + type: boolean required: - - port + - pdName type: object - type: array - type: object - type: object - inheritLabels: - items: - type: string - type: array - log: - properties: - filename: - type: string - path: - type: string - volume: - properties: - name: - type: string - persistentVolumeClaim: - properties: - claimName: - type: string - type: object - type: object - type: object - ociConfig: - properties: - configMapName: - type: string - secretName: - type: string - type: object - prometheus: - properties: - serviceMonitor: - properties: - endpoints: - items: + gitRepo: properties: - authorization: - properties: - credentials: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: - type: string - type: object - basicAuth: - properties: - password: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - username: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - bearerTokenFile: + directory: + type: string + repository: + type: string + revision: type: string - bearerTokenSecret: + required: + - repository + type: object + glusterfs: + properties: + endpoints: + type: string + path: + type: string + readOnly: + type: boolean + required: + - endpoints + - path + type: object + hostPath: + properties: + path: + type: string + type: + type: string + required: + - path + type: object + image: + properties: + pullPolicy: + type: string + reference: + type: string + type: object + iscsi: + properties: + chapAuthDiscovery: + type: boolean + chapAuthSession: + type: boolean + fsType: + type: string + initiatorName: + type: string + iqn: + type: string + iscsiInterface: + default: default + type: string + lun: + format: int32 + type: integer + portals: + items: + type: string + type: array + x-kubernetes-list-type: atomic + readOnly: + type: boolean + secretRef: properties: - key: - type: string name: default: "" type: string - optional: - type: boolean - required: - - key type: object x-kubernetes-map-type: atomic - enableHttp2: - type: boolean - filterRunning: - type: boolean - followRedirects: + targetPortal: + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + type: string + nfs: + properties: + path: + type: string + readOnly: type: boolean - honorLabels: + server: + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + properties: + claimName: + type: string + readOnly: type: boolean - honorTimestamps: + required: + - claimName + type: object + photonPersistentDisk: + properties: + fsType: + type: string + pdID: + type: string + required: + - pdID + type: object + portworxVolume: + properties: + fsType: + type: string + readOnly: type: boolean - interval: - pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ + volumeID: type: string - metricRelabelings: + required: + - volumeID + type: object + projected: + properties: + defaultMode: + format: int32 + type: integer + sources: items: properties: - action: - default: replace - enum: - - replace - - Replace - - keep - - Keep - - drop - - Drop - - hashmod - - HashMod - - labelmap - - LabelMap - - labeldrop - - LabelDrop - - labelkeep - - LabelKeep - - lowercase - - Lowercase - - uppercase - - Uppercase - - keepequal - - KeepEqual - - dropequal - - DropEqual - type: string - modulus: - format: int64 - type: integer - regex: - type: string - replacement: - type: string - separator: - type: string - sourceLabels: - items: - pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ - type: string - type: array - targetLabel: - type: string - type: object - type: array - oauth2: - properties: - clientId: - properties: - configMap: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - clientSecret: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - endpointParams: - additionalProperties: - type: string - type: object - noProxy: - type: string - proxyConnectHeader: - additionalProperties: - items: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: array - type: object - x-kubernetes-map-type: atomic - proxyFromEnvironment: - type: boolean - proxyUrl: - pattern: ^http(s)?://.+$ - type: string - scopes: - items: - type: string - type: array - tlsConfig: - properties: - ca: - properties: - configMap: - properties: - key: - type: string - name: - default: "" + clusterTrustBundle: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: + type: object + type: object + x-kubernetes-map-type: atomic + name: + type: string + optional: + type: boolean + path: + type: string + signerName: + type: string + required: + - path + type: object + configMap: + properties: + items: + items: properties: key: type: string - name: - default: "" + mode: + format: int32 + type: integer + path: type: string - optional: - type: boolean required: - key + - path type: object - x-kubernetes-map-type: atomic - type: object - cert: - properties: - configMap: + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + downwardAPI: + properties: + items: + items: properties: - key: - type: string - name: - default: "" + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + format: int32 + type: integer + path: type: string - optional: - type: boolean + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic required: - - key + - path type: object - x-kubernetes-map-type: atomic - secret: + type: array + x-kubernetes-list-type: atomic + type: object + podCertificate: + properties: + certificateChainPath: + type: string + credentialBundlePath: + type: string + keyPath: + type: string + keyType: + type: string + maxExpirationSeconds: + format: int32 + type: integer + signerName: + type: string + required: + - keyType + - signerName + type: object + secret: + properties: + items: + items: properties: key: type: string - name: - default: "" + mode: + format: int32 + type: integer + path: type: string - optional: - type: boolean required: - key + - path type: object - x-kubernetes-map-type: atomic - type: object - insecureSkipVerify: - type: boolean - keySecret: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - maxVersion: - enum: - - TLS10 - - TLS11 - - TLS12 - - TLS13 - type: string - minVersion: - enum: - - TLS10 - - TLS11 - - TLS12 - - TLS13 - type: string - serverName: - type: string - type: object - tokenUrl: - minLength: 1 + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + serviceAccountToken: + properties: + audience: + type: string + expirationSeconds: + format: int64 + type: integer + path: + type: string + required: + - path + type: object + type: object + type: array + x-kubernetes-list-type: atomic + type: object + quobyte: + properties: + group: + type: string + readOnly: + type: boolean + registry: + type: string + tenant: + type: string + user: + type: string + volume: + type: string + required: + - registry + - volume + type: object + rbd: + properties: + fsType: + type: string + image: + type: string + keyring: + default: /etc/ceph/keyring + type: string + monitors: + items: + type: string + type: array + x-kubernetes-list-type: atomic + pool: + default: rbd + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" type: string - required: - - clientId - - clientSecret - - tokenUrl type: object - params: - additionalProperties: - items: + x-kubernetes-map-type: atomic + user: + default: admin + type: string + required: + - image + - monitors + type: object + scaleIO: + properties: + fsType: + default: xfs + type: string + gateway: + type: string + protectionDomain: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" type: string - type: array type: object - path: + x-kubernetes-map-type: atomic + sslEnabled: + type: boolean + storageMode: + default: ThinProvisioned type: string - port: + storagePool: type: string - proxyUrl: + system: type: string - relabelings: + volumeName: + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + properties: + defaultMode: + format: int32 + type: integer + items: items: properties: - action: - default: replace - enum: - - replace - - Replace - - keep - - Keep - - drop - - Drop - - hashmod - - HashMod - - labelmap - - LabelMap - - labeldrop - - LabelDrop - - labelkeep - - LabelKeep - - lowercase - - Lowercase - - uppercase - - Uppercase - - keepequal - - KeepEqual - - dropequal - - DropEqual + key: type: string - modulus: - format: int64 + mode: + format: int32 type: integer - regex: - type: string - replacement: - type: string - separator: - type: string - sourceLabels: - items: - pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ - type: string - type: array - targetLabel: + path: type: string + required: + - key + - path type: object type: array - scheme: - enum: - - http - - https + x-kubernetes-list-type: atomic + optional: + type: boolean + secretName: type: string - scrapeTimeout: - pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ + type: object + storageos: + properties: + fsType: type: string - targetPort: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - tlsConfig: + readOnly: + type: boolean + secretRef: properties: - ca: - properties: - configMap: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - caFile: - type: string - cert: - properties: - configMap: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - secret: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - type: object - certFile: - type: string - insecureSkipVerify: - type: boolean - keyFile: - type: string - keySecret: - properties: - key: - type: string - name: - default: "" - type: string - optional: - type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - maxVersion: - enum: - - TLS10 - - TLS11 - - TLS12 - - TLS13 - type: string - minVersion: - enum: - - TLS10 - - TLS11 - - TLS12 - - TLS13 - type: string - serverName: + name: + default: "" type: string type: object - trackTimestampsStaleness: - type: boolean + x-kubernetes-map-type: atomic + volumeName: + type: string + volumeNamespace: + type: string type: object - type: array - labels: - additionalProperties: - type: string - type: object - namespaceSelector: - properties: - any: - type: boolean - matchNames: - items: + vsphereVolume: + properties: + fsType: type: string - type: array - type: object - type: object - type: object - replicas: - format: int32 - type: integer - sidecarVolumes: - items: - properties: - awsElasticBlockStore: - properties: - fsType: - type: string - partition: - format: int32 - type: integer - readOnly: - type: boolean - volumeID: - type: string + storagePolicyID: + type: string + storagePolicyName: + type: string + volumePath: + type: string + required: + - volumePath + type: object required: - - volumeID + - name type: object - azureDisk: + type: array + type: object + wallet: + properties: + additional: + items: properties: - cachingMode: - type: string - diskName: + mountPath: type: string - diskURI: - type: string - fsType: - default: ext4 + name: type: string - kind: + secret: type: string - readOnly: - default: false - type: boolean - required: - - diskName - - diskURI type: object - azureFile: + type: array + mountPath: + type: string + secret: + type: string + type: object + type: object + status: + properties: + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + maxLength: 32768 + type: string + observedGeneration: + format: int64 + minimum: 0 + type: integer + reason: + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + enum: + - "True" + - "False" + - Unknown + type: string + type: + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + metricsConfig: + type: string + replicas: + type: integer + status: + type: string + version: + type: string + required: + - conditions + - metricsConfig + - version + type: object + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .status.metricsConfig + name: MetricsConfig + type: string + - jsonPath: .status.status + name: Status + type: string + - jsonPath: .status.version + name: Version + type: string + name: v4 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + azureConfig: + properties: + configMap: + properties: + key: + type: string + name: + type: string + type: object + type: object + database: + properties: + azure: + properties: + vaultID: + type: string + vaultPasswordSecret: + type: string + vaultUsernameSecret: + type: string + type: object + dbConnectionString: + properties: + envName: + type: string + key: + type: string + secret: + type: string + type: object + dbPassword: + properties: + envName: + type: string + key: + type: string + secret: + type: string + type: object + dbUser: + properties: + envName: + type: string + key: + type: string + secret: + type: string + type: object + oci: + properties: + vaultID: + type: string + vaultPasswordSecret: + type: string + type: object + type: object + databases: + additionalProperties: + properties: + dbConnectionString: properties: - readOnly: - type: boolean - secretName: + envName: type: string - shareName: + key: + type: string + secret: type: string - required: - - secretName - - shareName type: object - cephfs: + dbPassword: properties: - monitors: - items: - type: string - type: array - x-kubernetes-list-type: atomic - path: + envName: type: string - readOnly: - type: boolean - secretFile: + key: type: string - secretRef: - properties: - name: - default: "" - type: string - type: object - x-kubernetes-map-type: atomic - user: + secret: type: string - required: - - monitors type: object - cinder: + dbUser: properties: - fsType: + envName: type: string - readOnly: - type: boolean - secretRef: - properties: - name: - default: "" - type: string - type: object - x-kubernetes-map-type: atomic - volumeID: + key: + type: string + secret: type: string - required: - - volumeID type: object - configMap: - properties: - defaultMode: - format: int32 - type: integer - items: - items: - properties: - key: - type: string - mode: - format: int32 - type: integer - path: - type: string - required: - - key - - path - type: object - type: array - x-kubernetes-list-type: atomic - name: - default: "" - type: string - optional: - type: boolean - type: object - x-kubernetes-map-type: atomic - csi: - properties: - driver: - type: string - fsType: - type: string - nodePublishSecretRef: + type: object + type: object + deployment: + properties: + args: + items: + type: string + type: array + commands: + items: + type: string + type: array + env: + additionalProperties: + type: string + type: object + image: + type: string + labels: + additionalProperties: + type: string + type: object + podSecurityContext: + properties: + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + fsGroup: + format: int64 + type: integer + fsGroupChangePolicy: + type: string + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxChangePolicy: + type: string + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + supplementalGroups: + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + supplementalGroupsPolicy: + type: string + sysctls: + items: properties: name: - default: "" type: string + value: + type: string + required: + - name + - value type: object - x-kubernetes-map-type: atomic - readOnly: - type: boolean - volumeAttributes: - additionalProperties: + type: array + x-kubernetes-list-type: atomic + windowsOptions: + properties: + gmsaCredentialSpec: type: string - type: object - required: - - driver + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + podTemplate: + properties: + labels: + additionalProperties: + type: string + type: object + type: object + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + capabilities: + properties: + add: + items: + type: string + type: array + x-kubernetes-list-type: atomic + drop: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + type: object + exporterConfig: + properties: + configMap: + properties: + key: + type: string + name: + type: string + type: object + mountPath: + type: string + type: object + inheritLabels: + items: + type: string + type: array + log: + properties: + destination: + type: string + disable: + type: boolean + filename: + type: string + volume: + properties: + name: + type: string + persistentVolumeClaim: + properties: + claimName: + type: string + type: object + type: object + type: object + metrics: + properties: + configMap: + items: + properties: + key: + type: string + name: + type: string type: object - downwardAPI: + type: array + type: object + ociConfig: + properties: + configMap: + properties: + key: + type: string + name: + type: string + type: object + mountPath: + type: string + privateKey: + properties: + secret: + type: string + type: object + type: object + replicas: + format: int32 + type: integer + service: + properties: + labels: + additionalProperties: + type: string + type: object + ports: + items: properties: - defaultMode: + appProtocol: + type: string + name: + type: string + nodePort: format: int32 type: integer - items: - items: - properties: - fieldRef: - properties: - apiVersion: - type: string - fieldPath: - type: string - required: - - fieldPath - type: object - x-kubernetes-map-type: atomic - mode: - format: int32 - type: integer - path: - type: string - resourceFieldRef: - properties: - containerName: - type: string - divisor: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - resource: - type: string - required: - - resource - type: object - x-kubernetes-map-type: atomic - required: - - path - type: object - type: array - x-kubernetes-list-type: atomic - type: object - emptyDir: - properties: - medium: + port: + format: int32 + type: integer + protocol: + default: TCP type: string - sizeLimit: + targetPort: anyOf: - type: integer - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true + required: + - port type: object - ephemeral: + type: array + type: object + serviceMonitor: + properties: + endpoints: + items: properties: - volumeClaimTemplate: + authorization: properties: - metadata: + credentials: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key type: object - spec: + x-kubernetes-map-type: atomic + type: + type: string + type: object + basicAuth: + properties: + password: properties: - accessModes: - items: - type: string - type: array - x-kubernetes-list-type: atomic - dataSource: - properties: - apiGroup: - type: string - kind: - type: string - name: - type: string - required: - - kind - - name - type: object - x-kubernetes-map-type: atomic - dataSourceRef: - properties: - apiGroup: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - required: - - kind - - name - type: object - resources: - properties: - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - type: object - selector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object - type: object - x-kubernetes-map-type: atomic - storageClassName: + key: type: string - volumeAttributesClassName: + name: + default: "" type: string - volumeMode: + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + username: + properties: + key: type: string - volumeName: + name: + default: "" type: string + optional: + type: boolean + required: + - key type: object - required: - - spec + x-kubernetes-map-type: atomic type: object - type: object - fc: - properties: - fsType: - type: string - lun: - format: int32 - type: integer - readOnly: - type: boolean - targetWWNs: - items: - type: string - type: array - x-kubernetes-list-type: atomic - wwids: - items: - type: string - type: array - x-kubernetes-list-type: atomic - type: object - flexVolume: - properties: - driver: + bearerTokenFile: type: string - fsType: - type: string - options: - additionalProperties: - type: string - type: object - readOnly: - type: boolean - secretRef: + bearerTokenSecret: properties: + key: + type: string name: default: "" type: string + optional: + type: boolean + required: + - key type: object x-kubernetes-map-type: atomic - required: - - driver - type: object - flocker: - properties: - datasetName: - type: string - datasetUUID: - type: string - type: object - gcePersistentDisk: - properties: - fsType: - type: string - partition: - format: int32 - type: integer - pdName: - type: string - readOnly: - type: boolean - required: - - pdName - type: object - gitRepo: - properties: - directory: - type: string - repository: - type: string - revision: - type: string - required: - - repository - type: object - glusterfs: - properties: - endpoints: - type: string - path: - type: string - readOnly: - type: boolean - required: - - endpoints - - path - type: object - hostPath: - properties: - path: - type: string - type: - type: string - required: - - path - type: object - image: - properties: - pullPolicy: - type: string - reference: - type: string - type: object - iscsi: - properties: - chapAuthDiscovery: + enableHttp2: type: boolean - chapAuthSession: - type: boolean - fsType: - type: string - initiatorName: - type: string - iqn: - type: string - iscsiInterface: - default: default - type: string - lun: - format: int32 - type: integer - portals: - items: - type: string - type: array - x-kubernetes-list-type: atomic - readOnly: + filterRunning: type: boolean - secretRef: - properties: - name: - default: "" - type: string - type: object - x-kubernetes-map-type: atomic - targetPortal: - type: string - required: - - iqn - - lun - - targetPortal - type: object - name: - type: string - nfs: - properties: - path: - type: string - readOnly: + followRedirects: type: boolean - server: - type: string - required: - - path - - server - type: object - persistentVolumeClaim: - properties: - claimName: - type: string - readOnly: + honorLabels: type: boolean - required: - - claimName - type: object - photonPersistentDisk: - properties: - fsType: - type: string - pdID: - type: string - required: - - pdID - type: object - portworxVolume: - properties: - fsType: - type: string - readOnly: + honorTimestamps: type: boolean - volumeID: + interval: + pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ type: string - required: - - volumeID - type: object - projected: - properties: - defaultMode: - format: int32 - type: integer - sources: + metricRelabelings: items: properties: - clusterTrustBundle: - properties: - labelSelector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object - type: object - x-kubernetes-map-type: atomic - name: - type: string - optional: - type: boolean - path: - type: string - signerName: - type: string - required: - - path - type: object - configMap: - properties: - items: - items: - properties: - key: - type: string - mode: - format: int32 - type: integer - path: - type: string - required: - - key - - path - type: object - type: array - x-kubernetes-list-type: atomic - name: - default: "" - type: string - optional: - type: boolean - type: object - x-kubernetes-map-type: atomic - downwardAPI: - properties: - items: - items: - properties: - fieldRef: - properties: - apiVersion: - type: string - fieldPath: - type: string - required: - - fieldPath - type: object - x-kubernetes-map-type: atomic - mode: - format: int32 - type: integer - path: - type: string - resourceFieldRef: - properties: - containerName: - type: string - divisor: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - resource: - type: string - required: - - resource - type: object - x-kubernetes-map-type: atomic - required: - - path - type: object - type: array - x-kubernetes-list-type: atomic - type: object - secret: - properties: - items: - items: - properties: - key: - type: string - mode: - format: int32 - type: integer - path: - type: string - required: - - key - - path - type: object - type: array - x-kubernetes-list-type: atomic - name: - default: "" - type: string - optional: - type: boolean - type: object - x-kubernetes-map-type: atomic - serviceAccountToken: - properties: - audience: - type: string - expirationSeconds: - format: int64 - type: integer - path: - type: string - required: - - path - type: object + action: + default: replace + enum: + - replace + - Replace + - keep + - Keep + - drop + - Drop + - hashmod + - HashMod + - labelmap + - LabelMap + - labeldrop + - LabelDrop + - labelkeep + - LabelKeep + - lowercase + - Lowercase + - uppercase + - Uppercase + - keepequal + - KeepEqual + - dropequal + - DropEqual + type: string + modulus: + format: int64 + type: integer + regex: + type: string + replacement: + type: string + separator: + type: string + sourceLabels: + items: + pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ + type: string + type: array + targetLabel: + type: string type: object type: array - x-kubernetes-list-type: atomic - type: object - quobyte: - properties: - group: - type: string - readOnly: - type: boolean - registry: - type: string - tenant: + noProxy: type: string - user: - type: string - volume: - type: string - required: - - registry - - volume - type: object - rbd: - properties: - fsType: - type: string - image: - type: string - keyring: - default: /etc/ceph/keyring - type: string - monitors: - items: - type: string - type: array - x-kubernetes-list-type: atomic - pool: - default: rbd - type: string - readOnly: - type: boolean - secretRef: + oauth2: properties: - name: - default: "" + clientId: + properties: + configMap: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + secret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + clientSecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + endpointParams: + additionalProperties: + type: string + type: object + noProxy: + type: string + proxyConnectHeader: + additionalProperties: + items: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: array + type: object + x-kubernetes-map-type: atomic + proxyFromEnvironment: + type: boolean + proxyUrl: + pattern: ^(http|https|socks5)://.+$ + type: string + scopes: + items: + type: string + type: array + tlsConfig: + properties: + ca: + properties: + configMap: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + secret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + cert: + properties: + configMap: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + secret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + insecureSkipVerify: + type: boolean + keySecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + maxVersion: + enum: + - TLS10 + - TLS11 + - TLS12 + - TLS13 + type: string + minVersion: + enum: + - TLS10 + - TLS11 + - TLS12 + - TLS13 + type: string + serverName: + type: string + type: object + tokenUrl: + minLength: 1 type: string + required: + - clientId + - clientSecret + - tokenUrl type: object - x-kubernetes-map-type: atomic - user: - default: admin - type: string - required: - - image - - monitors - type: object - scaleIO: - properties: - fsType: - default: xfs - type: string - gateway: + params: + additionalProperties: + items: + type: string + type: array + type: object + path: type: string - protectionDomain: + port: type: string - readOnly: - type: boolean - secretRef: - properties: - name: - default: "" - type: string + proxyConnectHeader: + additionalProperties: + items: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: array type: object x-kubernetes-map-type: atomic - sslEnabled: + proxyFromEnvironment: type: boolean - storageMode: - default: ThinProvisioned - type: string - storagePool: + proxyUrl: + pattern: ^(http|https|socks5)://.+$ type: string - system: - type: string - volumeName: - type: string - required: - - gateway - - secretRef - - system - type: object - secret: - properties: - defaultMode: - format: int32 - type: integer - items: + relabelings: items: properties: - key: + action: + default: replace + enum: + - replace + - Replace + - keep + - Keep + - drop + - Drop + - hashmod + - HashMod + - labelmap + - LabelMap + - labeldrop + - LabelDrop + - labelkeep + - LabelKeep + - lowercase + - Lowercase + - uppercase + - Uppercase + - keepequal + - KeepEqual + - dropequal + - DropEqual type: string - mode: - format: int32 + modulus: + format: int64 type: integer - path: + regex: + type: string + replacement: + type: string + separator: + type: string + sourceLabels: + items: + pattern: ^[a-zA-Z_][a-zA-Z0-9_]*$ + type: string + type: array + targetLabel: type: string - required: - - key - - path type: object type: array - x-kubernetes-list-type: atomic - optional: - type: boolean - secretName: + scheme: + enum: + - http + - https type: string - type: object - storageos: - properties: - fsType: + scrapeTimeout: + pattern: ^(0|(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?)$ type: string - readOnly: - type: boolean - secretRef: + targetPort: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + tlsConfig: properties: - name: - default: "" - type: string - type: object - x-kubernetes-map-type: atomic - volumeName: - type: string - volumeNamespace: - type: string - type: object - vsphereVolume: - properties: - fsType: - type: string - storagePolicyID: - type: string - storagePolicyName: - type: string - volumePath: - type: string - required: - - volumePath - type: object - required: - - name - type: object - type: array - sidecars: - items: - properties: - args: - items: - type: string - type: array - x-kubernetes-list-type: atomic - command: - items: - type: string - type: array - x-kubernetes-list-type: atomic - env: - items: - properties: - name: + ca: + properties: + configMap: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + secret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + caFile: + type: string + cert: + properties: + configMap: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + secret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + certFile: + type: string + insecureSkipVerify: + type: boolean + keyFile: + type: string + keySecret: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + maxVersion: + enum: + - TLS10 + - TLS11 + - TLS12 + - TLS13 + type: string + minVersion: + enum: + - TLS10 + - TLS11 + - TLS12 + - TLS13 + type: string + serverName: + type: string + type: object + trackTimestampsStaleness: + type: boolean + type: object + type: array + labels: + additionalProperties: + type: string + type: object + namespaceSelector: + properties: + any: + type: boolean + matchNames: + items: + type: string + type: array + type: object + type: object + sidecar: + properties: + containers: + items: + properties: + args: + items: type: string - value: + type: array + x-kubernetes-list-type: atomic + command: + items: type: string - valueFrom: + type: array + x-kubernetes-list-type: atomic + env: + items: properties: - configMapKeyRef: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + fileKeyRef: + properties: + key: + type: string + optional: + default: false + type: boolean + path: + type: string + volumeName: + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + envFrom: + items: + properties: + configMapRef: properties: - key: - type: string name: default: "" type: string optional: type: boolean - required: - - key - type: object - x-kubernetes-map-type: atomic - fieldRef: - properties: - apiVersion: - type: string - fieldPath: - type: string - required: - - fieldPath - type: object - x-kubernetes-map-type: atomic - resourceFieldRef: - properties: - containerName: - type: string - divisor: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - resource: - type: string - required: - - resource type: object x-kubernetes-map-type: atomic - secretKeyRef: + prefix: + type: string + secretRef: properties: - key: - type: string name: default: "" type: string optional: type: boolean - required: - - key type: object x-kubernetes-map-type: atomic type: object - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - envFrom: - items: - properties: - configMapRef: - properties: - name: - default: "" - type: string - optional: - type: boolean - type: object - x-kubernetes-map-type: atomic - prefix: - type: string - secretRef: - properties: - name: - default: "" - type: string - optional: - type: boolean - type: object - x-kubernetes-map-type: atomic - type: object - type: array - x-kubernetes-list-type: atomic - image: - type: string - imagePullPolicy: - type: string - lifecycle: - properties: - postStart: + type: array + x-kubernetes-list-type: atomic + image: + type: string + imagePullPolicy: + type: string + lifecycle: properties: - exec: + postStart: properties: - command: - items: - type: string - type: array - x-kubernetes-list-type: atomic + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object type: object - httpGet: + preStop: properties: - host: - type: string - httpHeaders: - items: - properties: - name: - type: string - value: + exec: + properties: + command: + items: type: string - required: - - name - - value + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + sleep: + properties: + seconds: + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + stopSignal: + type: string + type: object + livenessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value type: object type: array x-kubernetes-list-type: atomic @@ -8189,14 +7534,15 @@ spec: required: - port type: object - sleep: - properties: - seconds: - format: int64 - type: integer - required: - - seconds - type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer tcpSocket: properties: host: @@ -8209,8 +7555,40 @@ spec: required: - port type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer type: object - preStop: + name: + type: string + ports: + items: + properties: + containerPort: + format: int32 + type: integer + hostIP: + type: string + hostPort: + format: int32 + type: integer + name: + type: string + protocol: + default: TCP + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: properties: exec: properties: @@ -8220,6 +7598,20 @@ spec: type: array x-kubernetes-list-type: atomic type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object httpGet: properties: host: @@ -8249,14 +7641,15 @@ spec: required: - port type: object - sleep: - properties: - seconds: - format: int64 - type: integer - required: - - seconds - type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer tcpSocket: properties: host: @@ -8269,453 +7662,1112 @@ spec: required: - port type: object - type: object - type: object - livenessProbe: - properties: - exec: - properties: - command: - items: - type: string - type: array - x-kubernetes-list-type: atomic - type: object - failureThreshold: - format: int32 - type: integer - grpc: - properties: - port: + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: format: int32 type: integer - service: - default: "" - type: string - required: - - port type: object - httpGet: + resizePolicy: + items: + properties: + resourceName: + type: string + restartPolicy: + type: string + required: + - resourceName + - restartPolicy + type: object + type: array + x-kubernetes-list-type: atomic + resources: properties: - host: - type: string - httpHeaders: + claims: items: properties: name: type: string - value: + request: type: string required: - name - - value type: object type: array - x-kubernetes-list-type: atomic - path: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - scheme: - type: string - required: - - port - type: object - initialDelaySeconds: - format: int32 - type: integer - periodSeconds: - format: int32 - type: integer - successThreshold: - format: int32 - type: integer - tcpSocket: - properties: - host: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - required: - - port + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object type: object - terminationGracePeriodSeconds: - format: int64 - type: integer - timeoutSeconds: - format: int32 - type: integer - type: object - name: - type: string - ports: - items: - properties: - containerPort: - format: int32 - type: integer - hostIP: - type: string - hostPort: - format: int32 - type: integer - name: - type: string - protocol: - default: TCP - type: string - required: - - containerPort - type: object - type: array - x-kubernetes-list-map-keys: - - containerPort - - protocol - x-kubernetes-list-type: map - readinessProbe: - properties: - exec: - properties: - command: - items: + restartPolicy: + type: string + restartPolicyRules: + items: + properties: + action: type: string - type: array - x-kubernetes-list-type: atomic - type: object - failureThreshold: - format: int32 - type: integer - grpc: - properties: - port: - format: int32 - type: integer - service: - default: "" - type: string - required: - - port - type: object - httpGet: - properties: - host: - type: string - httpHeaders: - items: + exitCodes: properties: - name: - type: string - value: + operator: type: string + values: + items: + format: int32 + type: integer + type: array + x-kubernetes-list-type: set required: - - name - - value + - operator type: object - type: array - x-kubernetes-list-type: atomic - path: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - scheme: - type: string - required: - - port - type: object - initialDelaySeconds: - format: int32 - type: integer - periodSeconds: - format: int32 - type: integer - successThreshold: - format: int32 - type: integer - tcpSocket: - properties: - host: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - required: - - port - type: object - terminationGracePeriodSeconds: - format: int64 - type: integer - timeoutSeconds: - format: int32 - type: integer - type: object - resizePolicy: - items: - properties: - resourceName: - type: string - restartPolicy: - type: string - required: - - resourceName - - restartPolicy - type: object - type: array - x-kubernetes-list-type: atomic - resources: - properties: - claims: - items: - properties: - name: - type: string - request: - type: string required: - - name + - action type: object type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - type: object - restartPolicy: - type: string - securityContext: - properties: - allowPrivilegeEscalation: - type: boolean - appArmorProfile: + x-kubernetes-list-type: atomic + securityContext: properties: - localhostProfile: - type: string - type: + allowPrivilegeEscalation: + type: boolean + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + capabilities: + properties: + add: + items: + type: string + type: array + x-kubernetes-list-type: atomic + drop: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + type: boolean + procMount: type: string - required: - - type + readOnlyRootFilesystem: + type: boolean + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object type: object - capabilities: + startupProbe: properties: - add: - items: - type: string - type: array - x-kubernetes-list-type: atomic - drop: - items: - type: string - type: array - x-kubernetes-list-type: atomic + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer type: object - privileged: + stdin: type: boolean - procMount: - type: string - readOnlyRootFilesystem: + stdinOnce: type: boolean - runAsGroup: - format: int64 - type: integer - runAsNonRoot: + terminationMessagePath: + type: string + terminationMessagePolicy: + type: string + tty: type: boolean - runAsUser: - format: int64 - type: integer - seLinuxOptions: + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + required: + - devicePath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - devicePath + x-kubernetes-list-type: map + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + recursiveReadOnly: + type: string + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - mountPath + x-kubernetes-list-type: map + workingDir: + type: string + required: + - name + type: object + type: array + volumes: + items: + properties: + awsElasticBlockStore: properties: - level: + fsType: type: string - role: - type: string - type: - type: string - user: + partition: + format: int32 + type: integer + readOnly: + type: boolean + volumeID: type: string + required: + - volumeID type: object - seccompProfile: + azureDisk: properties: - localhostProfile: + cachingMode: type: string - type: + diskName: + type: string + diskURI: + type: string + fsType: + default: ext4 + type: string + kind: type: string + readOnly: + default: false + type: boolean required: - - type + - diskName + - diskURI type: object - windowsOptions: + azureFile: properties: - gmsaCredentialSpec: - type: string - gmsaCredentialSpecName: - type: string - hostProcess: + readOnly: type: boolean - runAsUserName: + secretName: + type: string + shareName: type: string + required: + - secretName + - shareName type: object - type: object - startupProbe: - properties: - exec: + cephfs: properties: - command: + monitors: items: type: string type: array x-kubernetes-list-type: atomic + path: + type: string + readOnly: + type: boolean + secretFile: + type: string + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + user: + type: string + required: + - monitors type: object - failureThreshold: - format: int32 - type: integer - grpc: + cinder: properties: - port: - format: int32 - type: integer - service: - default: "" + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + volumeID: type: string required: - - port + - volumeID type: object - httpGet: + configMap: properties: - host: - type: string - httpHeaders: + defaultMode: + format: int32 + type: integer + items: items: properties: - name: + key: type: string - value: + mode: + format: int32 + type: integer + path: type: string required: - - name - - value + - key + - path type: object type: array x-kubernetes-list-type: atomic - path: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - scheme: + name: + default: "" type: string - required: - - port + optional: + type: boolean type: object - initialDelaySeconds: - format: int32 - type: integer - periodSeconds: - format: int32 - type: integer - successThreshold: - format: int32 - type: integer - tcpSocket: + x-kubernetes-map-type: atomic + csi: properties: - host: + driver: type: string - port: - anyOf: - - type: integer - - type: string + fsType: + type: string + nodePublishSecretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: + type: boolean + volumeAttributes: + additionalProperties: + type: string + type: object + required: + - driver + type: object + downwardAPI: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + emptyDir: + properties: + medium: + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true + type: object + ephemeral: + properties: + volumeClaimTemplate: + properties: + metadata: + type: object + spec: + properties: + accessModes: + items: + type: string + type: array + x-kubernetes-list-type: atomic + dataSource: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + dataSourceRef: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + required: + - kind + - name + type: object + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + storageClassName: + type: string + volumeAttributesClassName: + type: string + volumeMode: + type: string + volumeName: + type: string + type: object + required: + - spec + type: object + type: object + fc: + properties: + fsType: + type: string + lun: + format: int32 + type: integer + readOnly: + type: boolean + targetWWNs: + items: + type: string + type: array + x-kubernetes-list-type: atomic + wwids: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + flexVolume: + properties: + driver: + type: string + fsType: + type: string + options: + additionalProperties: + type: string + type: object + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic required: - - port + - driver type: object - terminationGracePeriodSeconds: - format: int64 - type: integer - timeoutSeconds: - format: int32 - type: integer + flocker: + properties: + datasetName: + type: string + datasetUUID: + type: string + type: object + gcePersistentDisk: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + pdName: + type: string + readOnly: + type: boolean + required: + - pdName + type: object + gitRepo: + properties: + directory: + type: string + repository: + type: string + revision: + type: string + required: + - repository + type: object + glusterfs: + properties: + endpoints: + type: string + path: + type: string + readOnly: + type: boolean + required: + - endpoints + - path + type: object + hostPath: + properties: + path: + type: string + type: + type: string + required: + - path + type: object + image: + properties: + pullPolicy: + type: string + reference: + type: string + type: object + iscsi: + properties: + chapAuthDiscovery: + type: boolean + chapAuthSession: + type: boolean + fsType: + type: string + initiatorName: + type: string + iqn: + type: string + iscsiInterface: + default: default + type: string + lun: + format: int32 + type: integer + portals: + items: + type: string + type: array + x-kubernetes-list-type: atomic + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + targetPortal: + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + type: string + nfs: + properties: + path: + type: string + readOnly: + type: boolean + server: + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + properties: + claimName: + type: string + readOnly: + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + properties: + fsType: + type: string + pdID: + type: string + required: + - pdID + type: object + portworxVolume: + properties: + fsType: + type: string + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + projected: + properties: + defaultMode: + format: int32 + type: integer + sources: + items: + properties: + clusterTrustBundle: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-map-type: atomic + name: + type: string + optional: + type: boolean + path: + type: string + signerName: + type: string + required: + - path + type: object + configMap: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + downwardAPI: + properties: + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podCertificate: + properties: + certificateChainPath: + type: string + credentialBundlePath: + type: string + keyPath: + type: string + keyType: + type: string + maxExpirationSeconds: + format: int32 + type: integer + signerName: + type: string + required: + - keyType + - signerName + type: object + secret: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + serviceAccountToken: + properties: + audience: + type: string + expirationSeconds: + format: int64 + type: integer + path: + type: string + required: + - path + type: object + type: object + type: array + x-kubernetes-list-type: atomic + type: object + quobyte: + properties: + group: + type: string + readOnly: + type: boolean + registry: + type: string + tenant: + type: string + user: + type: string + volume: + type: string + required: + - registry + - volume + type: object + rbd: + properties: + fsType: + type: string + image: + type: string + keyring: + default: /etc/ceph/keyring + type: string + monitors: + items: + type: string + type: array + x-kubernetes-list-type: atomic + pool: + default: rbd + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + user: + default: admin + type: string + required: + - image + - monitors + type: object + scaleIO: + properties: + fsType: + default: xfs + type: string + gateway: + type: string + protectionDomain: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + sslEnabled: + type: boolean + storageMode: + default: ThinProvisioned + type: string + storagePool: + type: string + system: + type: string + volumeName: + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + optional: + type: boolean + secretName: + type: string + type: object + storageos: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + volumeName: + type: string + volumeNamespace: + type: string + type: object + vsphereVolume: + properties: + fsType: + type: string + storagePolicyID: + type: string + storagePolicyName: + type: string + volumePath: + type: string + required: + - volumePath + type: object + required: + - name type: object - stdin: - type: boolean - stdinOnce: - type: boolean - terminationMessagePath: - type: string - terminationMessagePolicy: - type: string - tty: - type: boolean - volumeDevices: - items: - properties: - devicePath: - type: string - name: - type: string - required: - - devicePath - - name - type: object - type: array - x-kubernetes-list-map-keys: - - devicePath - x-kubernetes-list-type: map - volumeMounts: - items: - properties: - mountPath: - type: string - mountPropagation: - type: string - name: - type: string - readOnly: - type: boolean - recursiveReadOnly: - type: string - subPath: - type: string - subPathExpr: - type: string - required: - - mountPath - - name - type: object - type: array - x-kubernetes-list-map-keys: - - mountPath - x-kubernetes-list-type: map - workingDir: - type: string - required: - - name - type: object - type: array + type: array + type: object + wallet: + properties: + additional: + items: + properties: + mountPath: + type: string + name: + type: string + secret: + type: string + type: object + type: array + mountPath: + type: string + secret: + type: string + type: object type: object status: properties: @@ -8755,7 +8807,7 @@ spec: - type type: object type: array - exporterConfig: + metricsConfig: type: string replicas: type: integer @@ -8765,7 +8817,7 @@ spec: type: string required: - conditions - - exporterConfig + - metricsConfig - version type: object type: object @@ -8778,7 +8830,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.5 + cert-manager.io/inject-ca-from: oracle-database-operator-system/oracle-database-operator-serving-cert + controller-gen.kubebuilder.io/version: v0.17.3 name: dataguardbrokers.database.oracle.com spec: group: database.oracle.com @@ -8786,6 +8839,9 @@ spec: kind: DataguardBroker listKind: DataguardBrokerList plural: dataguardbrokers + shortNames: + - dgbroker + - dgbrokers singular: dataguardbroker scope: Namespaced versions: @@ -8989,7 +9045,7 @@ kind: CustomResourceDefinition metadata: annotations: cert-manager.io/inject-ca-from: oracle-database-operator-system/oracle-database-operator-serving-cert - controller-gen.kubebuilder.io/version: v0.16.5 + controller-gen.kubebuilder.io/version: v0.17.3 name: dbcssystems.database.oracle.com spec: group: database.oracle.com @@ -9373,7 +9429,23 @@ spec: storage: false subresources: status: {} - - name: v4 + - additionalPrinterColumns: + - jsonPath: .status.displayName + name: Display Name + type: string + - jsonPath: .status.dbInfo[0].dbName + name: DB Name + type: string + - jsonPath: .status.state + name: State + type: string + - jsonPath: .status.dbInfo[0].dbVersion + name: DB Version + type: string + - jsonPath: .status.dbInfo[0].connectionString + name: ConnString + type: string + name: v4 schema: openAPIV3Schema: properties: @@ -9385,6 +9457,45 @@ spec: type: object spec: properties: + dataGuard: + properties: + availabilityDomain: + type: string + dbAdminPasswordSecret: + type: string + dbName: + type: string + dbSystemFreeformTags: + additionalProperties: + type: string + type: object + displayName: + type: string + enabled: + type: boolean + hostName: + type: string + isDelete: + type: boolean + peerDbHomeId: + type: string + peerDbSystemId: + type: string + peerRole: + type: string + primaryDatabaseId: + type: string + protectionMode: + type: string + shape: + type: string + sidPrefix: + type: string + subnetId: + type: string + transportType: + type: string + type: object databaseId: type: string dbBackupId: @@ -9393,10 +9504,10 @@ spec: properties: dbAdminPasswordSecret: type: string - dbDbUniqueName: - type: string dbName: type: string + dbUniqueName: + type: string displayName: type: string domain: @@ -9424,7 +9535,6 @@ spec: tdeWalletPasswordSecret: type: string required: - - dbDbUniqueName - dbName - displayName - hostName @@ -9434,6 +9544,8 @@ spec: properties: availabilityDomain: type: string + backupDisplayName: + type: string backupSubnetId: type: string clusterName: @@ -9459,10 +9571,16 @@ spec: type: string dbEdition: type: string + dbHomeId: + type: string dbName: type: string + dbPatchOcid: + type: string dbUniqueName: type: string + dbUpgradeVersion: + type: string dbVersion: type: string dbWorkload: @@ -9502,6 +9620,16 @@ spec: type: string privateIp: type: string + restoreConfig: + properties: + latest: + type: boolean + scn: + type: string + timestamp: + format: date-time + type: string + type: object shape: type: string sshPublicKeys: @@ -9520,18 +9648,17 @@ spec: type: string timeZone: type: string - required: - - availabilityDomain - - compartmentId - - dbAdminPasswordSecret - - hostName - - shape - - subnetId type: object + enableBackup: + type: boolean hardLink: type: boolean id: type: string + isPatch: + type: boolean + isUpgrade: + type: boolean kmsConfig: properties: compartmentId: @@ -9585,20 +9712,72 @@ spec: properties: availabilityDomain: type: string + backups: + items: + properties: + backupId: + type: string + name: + type: string + timestamp: + type: string + required: + - backupId + - name + - timestamp + type: object + type: array cpuCoreCount: type: integer + dataGuardStatus: + properties: + dbAdminPasswordSecret: + type: string + dbName: + type: string + dbWorkload: + type: string + id: + type: string + isActiveDataGuardEnabled: + type: boolean + lifecycleDetails: + type: string + lifecycleState: + type: string + peerDataGuardAssociationId: + type: string + peerDatabaseId: + type: string + peerDbHomeId: + type: string + peerDbSystemId: + type: string + peerRole: + type: string + primaryDatabaseId: + type: string + protectionMode: + type: string + shape: + type: string + subnetId: + type: string + transportType: + type: string + type: object dataStoragePercentage: type: integer dataStorageSizeInGBs: type: integer dbCloneStatus: properties: - dbAdminPaswordSecret: - type: string - dbDbUniqueName: + dbAdminPasswordSecret: type: string dbName: type: string + dbUniqueName: + type: string displayName: type: string domain: @@ -9615,15 +9794,16 @@ spec: type: array subnetId: type: string - required: - - dbDbUniqueName - - hostName type: object dbEdition: type: string dbInfo: items: properties: + connectionString: + type: string + connectionStringLong: + type: string dbHomeId: type: string dbName: @@ -9636,6 +9816,8 @@ spec: type: string type: object type: array + dbVersion: + type: string displayName: type: string id: @@ -9661,6 +9843,8 @@ spec: type: object licenseModel: type: string + message: + type: string network: properties: clientSubnet: @@ -9729,9 +9913,6 @@ spec: type: string timeStarted: type: string - required: - - operationId - - operationType type: object type: array required: @@ -9747,7 +9928,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.5 + cert-manager.io/inject-ca-from: oracle-database-operator-system/oracle-database-operator-serving-cert + controller-gen.kubebuilder.io/version: v0.17.3 name: lrests.database.oracle.com spec: group: database.oracle.com @@ -9799,6 +9981,8 @@ spec: type: object spec: properties: + autodiscover: + type: boolean cdbAdminPwd: properties: secret: @@ -9861,6 +10045,21 @@ spec: required: - secret type: object + cdbTlsCat: + properties: + secret: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - secret + type: object cdbTlsCrt: properties: secret: @@ -9891,6 +10090,9 @@ spec: required: - secret type: object + clusterIp: + default: false + type: boolean dbPort: type: integer dbServer: @@ -9899,6 +10101,9 @@ spec: type: string deletePdbCascade: type: boolean + loadBalancer: + default: false + type: boolean lrestImage: type: string lrestImagePullPolicy: @@ -9909,6 +10114,7 @@ spec: lrestImagePullSecret: type: string lrestPort: + default: 8888 type: integer lrestPwd: properties: @@ -9925,12 +10131,16 @@ spec: required: - secret type: object + namespaceAutoDiscover: + type: string nodeSelector: additionalProperties: type: string type: object replicas: type: integer + serviceAccountName: + type: string serviceName: type: string sysAdminPwd: @@ -9948,6 +10158,9 @@ spec: required: - secret type: object + trace_level_client: + default: 0 + type: integer webServerPwd: properties: secret: @@ -10001,7 +10214,8 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.5 + cert-manager.io/inject-ca-from: oracle-database-operator-system/oracle-database-operator-serving-cert + controller-gen.kubebuilder.io/version: v0.17.3 name: lrpdbs.database.oracle.com spec: group: database.oracle.com @@ -10029,18 +10243,26 @@ spec: jsonPath: .status.totalSize name: PDB Size type: string - - description: Status of the LRPDB Resource - jsonPath: .status.phase - name: Status - type: string - description: Error message, if any jsonPath: .status.msg name: Message type: string + - description: open restricted + jsonPath: .status.restricted + name: Restricted + type: string - description: last sqlcode jsonPath: .status.sqlCode name: last sqlcode type: integer + - description: last plsql applied + jsonPath: .status.lastplsql + name: last PLSQL + type: string + - description: Bitmask status + jsonPath: .status.pdbBitMaskStr + name: BITMASK STATUS + type: string - description: The connect string to be used jsonPath: .status.connString name: Connect_String @@ -10058,17 +10280,6 @@ spec: spec: properties: action: - enum: - - Create - - Clone - - Plug - - Unplug - - Delete - - Modify - - Status - - Map - - Alter - - Noaction type: string adminName: properties: @@ -10100,113 +10311,7 @@ spec: required: - secret type: object - adminpdbPass: - properties: - secret: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - secret - type: object - adminpdbUser: - properties: - secret: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - secret - type: object - alterSystem: - type: string - alterSystemParameter: - type: string - alterSystemValue: - type: string - asClone: - type: boolean - assertiveLrpdbDeletion: - type: boolean - cdbName: - type: string - cdbNamespace: - type: string - cdbPrvKey: - properties: - secret: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - secret - type: object - cdbResName: - type: string - copyAction: - enum: - - COPY - - NOCOPY - - MOVE - type: string - dropAction: - enum: - - INCLUDING - - KEEP - type: string - fileNameConversions: - type: string - getScript: - type: boolean - lrpdbTlsCat: - properties: - secret: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - secret - type: object - lrpdbTlsCrt: - properties: - secret: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - secret - type: object - lrpdbTlsKey: + adminpdbPass: properties: secret: properties: @@ -10221,41 +10326,7 @@ spec: required: - secret type: object - modifyOption: - enum: - - IMMEDIATE - - NORMAL - - READ ONLY - - READ WRITE - - RESTRICTED - type: string - parameterScope: - type: string - pdbName: - type: string - pdbState: - enum: - - OPEN - - CLOSE - - ALTER - type: string - pdbconfigmap: - type: string - reuseTempFile: - type: boolean - sourceFileNameConversions: - type: string - sparseClonePath: - type: string - srcPdbName: - type: string - tdeExport: - type: boolean - tdeImport: - type: boolean - tdeKeystorePath: - type: string - tdePassword: + adminpdbUser: properties: secret: properties: @@ -10270,7 +10341,19 @@ spec: required: - secret type: object - tdeSecret: + alterSystem: + type: string + alterSystemParameter: + type: string + alterSystemValue: + type: string + asClone: + type: boolean + cdbName: + type: string + cdbNamespace: + type: string + cdbPrvKey: properties: secret: properties: @@ -10285,13 +10368,31 @@ spec: required: - secret type: object - tempSize: + cdbResName: type: string - totalSize: + codeconfigmap: type: string - unlimitedStorage: + copyAction: + enum: + - COPY + - NOCOPY + - MOVE + type: string + debug: + type: integer + dropAction: + enum: + - INCLUDING + - KEEP + type: string + fileNameConversions: + type: string + getScript: + default: false type: boolean - webServerPwd: + imperativeLrpdbDeletion: + type: boolean + lrpdbTlsCat: properties: secret: properties: @@ -10306,7 +10407,7 @@ spec: required: - secret type: object - webServerUser: + lrpdbTlsCrt: properties: secret: properties: @@ -10321,406 +10422,177 @@ spec: required: - secret type: object - xmlFileName: - type: string - required: - - action - - alterSystemParameter - - alterSystemValue - - webServerPwd - type: object - status: - properties: - action: - type: string - alterSystem: - type: string - bitstat: - type: integer - bitstatstr: - type: string - connString: - type: string - modifyOption: - type: string - msg: - type: string - openMode: - type: string - phase: - type: string - sqlCode: - type: integer - status: - type: boolean - totalSize: - type: string - required: - - phase - - sqlCode - - status - type: object - type: object - served: true - storage: true - subresources: - status: {} ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.16.5 - name: oraclerestdataservices.database.oracle.com -spec: - group: database.oracle.com - names: - kind: OracleRestDataService - listKind: OracleRestDataServiceList - plural: oraclerestdataservices - singular: oraclerestdataservice - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .status.status - name: Status - type: string - - jsonPath: .spec.databaseRef - name: Database - type: string - - jsonPath: .status.databaseApiUrl - name: Database API URL - type: string - - jsonPath: .status.databaseActionsUrl - name: Database Actions URL - type: string - - jsonPath: .status.apexUrl - name: Apex URL - type: string - - jsonPath: .status.mongoDbApiAccessUrl - name: MongoDbApi Access URL - type: string - name: v1alpha1 - schema: - openAPIV3Schema: - properties: - apiVersion: - type: string - kind: - type: string - metadata: - type: object - spec: - properties: - adminPassword: - properties: - keepSecret: - type: boolean - secretKey: - default: oracle_pwd - type: string - secretName: - type: string - required: - - secretName - type: object - databaseRef: - type: string - image: - properties: - pullFrom: - type: string - pullSecrets: - type: string - version: - type: string - required: - - pullFrom - type: object - loadBalancer: - type: boolean - mongoDbApi: - type: boolean - nodeSelector: - additionalProperties: - type: string - type: object - oracleService: - type: string - ordsPassword: - properties: - keepSecret: - type: boolean - secretKey: - default: oracle_pwd - type: string - secretName: - type: string - required: - - secretName - type: object - ordsUser: - type: string - persistence: - properties: - accessMode: - enum: - - ReadWriteOnce - - ReadWriteMany - type: string - setWritePermissions: - type: boolean - size: - type: string - storageClass: - type: string - volumeName: - type: string - type: object - readinessCheckPeriod: - type: integer - replicas: - minimum: 1 - type: integer - restEnableSchemas: - items: - properties: - enable: - type: boolean - pdbName: - type: string - schemaName: - type: string - urlMapping: - type: string - required: - - enable - - schemaName - type: object - type: array - serviceAccountName: - type: string - serviceAnnotations: - additionalProperties: - type: string + lrpdbTlsKey: + properties: + secret: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - secret type: object - required: - - adminPassword - - databaseRef - - ordsPassword - type: object - status: - properties: - apexConfigured: - type: boolean - apexUrl: + modifyOption: + enum: + - IMMEDIATE + - NORMAL + - READ ONLY + - READ WRITE + - RESTRICTED type: string - commonUsersCreated: - type: boolean - databaseActionsUrl: + modifyOption2: + default: NONE type: string - databaseApiUrl: + parameterScope: type: string - databaseRef: + pdbName: type: string - image: - properties: - pullFrom: - type: string - pullSecrets: - type: string - version: - type: string - required: - - pullFrom - type: object - loadBalancer: + pdbState: + enum: + - OPEN + - CLOSE + - ALTER + - DELETE + - UNPLUG + - PLUG + - CLONE + - RESET + - NONE type: string - mongoDbApi: - type: boolean - mongoDbApiAccessUrl: + pdbconfigmap: type: string - ordsInstalled: - type: boolean - replicas: + plsqlexemode: type: integer - serviceIP: + reststate: + type: integer + reuseTempFile: + default: true + type: boolean + sourceFileNameConversions: type: string - status: + sparseClonePath: type: string - type: object - type: object - served: true - storage: false - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .status.status - name: Status - type: string - - jsonPath: .spec.databaseRef - name: Database - type: string - - jsonPath: .status.databaseApiUrl - name: Database API URL - type: string - - jsonPath: .status.databaseActionsUrl - name: Database Actions URL - type: string - - jsonPath: .status.apexUrl - name: Apex URL - type: string - - jsonPath: .status.mongoDbApiAccessUrl - name: MongoDbApi Access URL - type: string - name: v4 - schema: - openAPIV3Schema: - properties: - apiVersion: - type: string - kind: - type: string - metadata: - type: object - spec: - properties: - adminPassword: + srcPdbName: + type: string + tdeExport: + type: boolean + tdeImport: + type: boolean + tdeKeystorePath: + type: string + tdePassword: properties: - keepSecret: - type: boolean - secretKey: - default: oracle_pwd - type: string - secretName: - type: string + secret: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object required: - - secretName + - secret type: object - databaseRef: - type: string - image: + tdeSecret: properties: - pullFrom: - type: string - pullSecrets: - type: string - version: - type: string + secret: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object required: - - pullFrom - type: object - loadBalancer: - type: boolean - mongoDbApi: - type: boolean - nodeSelector: - additionalProperties: - type: string + - secret type: object - oracleService: + tempSize: type: string - ordsPassword: + totalSize: + type: string + unlimitedStorage: + default: true + type: boolean + webServerPwd: properties: - keepSecret: - type: boolean - secretKey: - default: oracle_pwd - type: string - secretName: - type: string + secret: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object required: - - secretName + - secret type: object - ordsUser: - type: string - persistence: + webServerUser: properties: - accessMode: - enum: - - ReadWriteOnce - - ReadWriteMany - type: string - setWritePermissions: - type: boolean - size: - type: string - storageClass: - type: string - volumeName: - type: string + secret: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - secret type: object - readinessCheckPeriod: - type: integer - replicas: - minimum: 1 - type: integer - restEnableSchemas: - items: - properties: - enable: - type: boolean - pdbName: - type: string - schemaName: - type: string - urlMapping: - type: string - required: - - enable - - schemaName - type: object - type: array - serviceAccountName: + xmlFileName: type: string - serviceAnnotations: - additionalProperties: - type: string - type: object - required: - - adminPassword - - databaseRef - - ordsPassword type: object - status: - properties: - apexConfigured: - type: boolean - apexUrl: + status: + properties: + action: type: string - commonUsersCreated: - type: boolean - databaseActionsUrl: + alterSystem: type: string - databaseApiUrl: + bitstat: + type: integer + bitstatstr: type: string - databaseRef: + connString: type: string - image: - properties: - pullFrom: - type: string - pullSecrets: - type: string - version: - type: string - required: - - pullFrom - type: object - loadBalancer: + lastplsql: type: string - mongoDbApi: - type: boolean - mongoDbApiAccessUrl: + modifyOption: type: string - ordsInstalled: - type: boolean - replicas: + msg: + type: string + openMode: + type: string + pdbBitMask: type: integer - serviceIP: + pdbBitMaskStr: + type: string + phase: + type: string + restricted: type: string + sqlCode: + type: integer status: + type: boolean + totalSize: type: string + required: + - phase + - sqlCode + - status type: object type: object served: true @@ -10732,45 +10604,37 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.5 - name: ordssrvs.database.oracle.com + cert-manager.io/inject-ca-from: oracle-database-operator-system/oracle-database-operator-serving-cert + controller-gen.kubebuilder.io/version: v0.17.3 + name: oraclerestarts.database.oracle.com spec: group: database.oracle.com names: - kind: OrdsSrvs - listKind: OrdsSrvsList - plural: ordssrvs - singular: ordssrvs + kind: OracleRestart + listKind: OracleRestartList + plural: oraclerestarts + singular: oraclerestart scope: Namespaced versions: - additionalPrinterColumns: - - jsonPath: .status.status - name: status + - jsonPath: .status.configParams.dbName + name: DbName type: string - - jsonPath: .status.workloadType - name: workloadType + - jsonPath: .status.dbState + name: DbState type: string - - jsonPath: .status.ordsVersion - name: ordsVersion + - jsonPath: .status.role + name: Role + type: string + - jsonPath: .status.releaseUpdate + name: Version + type: string + - jsonPath: .status.pdbConnectString + name: Pdb Connect Str + type: string + - jsonPath: .status.state + name: State type: string - - jsonPath: .status.httpPort - name: httpPort - type: integer - - jsonPath: .status.httpsPort - name: httpsPort - type: integer - - jsonPath: .status.mongoPort - name: MongoPort - type: integer - - jsonPath: .status.restartRequired - name: restartRequired - type: boolean - - jsonPath: .metadata.creationTimestamp - name: AGE - type: date - - jsonPath: .status.ordsInstalled - name: OrdsInstalled - type: boolean name: v4 schema: openAPIV3Schema: @@ -10783,375 +10647,775 @@ spec: type: object spec: properties: - encPrivKey: + asmStorageDetails: properties: - passwordKey: - default: password + autoUpdate: + type: string + disksBySize: + items: + properties: + diskNames: + items: + type: string + type: array + storageSizeInGb: + type: integer + type: object + type: array + type: object + configParams: + properties: + cpuCount: + type: integer + crsAsmDeviceList: + type: string + crsAsmDiskDg: + type: string + crsAsmDiskDgRedundancy: + type: string + dbAsmDeviceList: + type: string + dbAsmDiskDgRedundancy: + type: string + dbBase: + type: string + dbCharSet: + type: string + dbConfigType: + type: string + dbDataFileDestDg: + type: string + dbHome: + type: string + dbName: + type: string + dbOneOffIds: + type: string + dbRecoveryFileDest: + type: string + dbRecoveryFileDestSize: + type: string + dbRedoFileSize: + type: string + dbResponseFile: + properties: + configMapName: + type: string + name: + type: string + type: object + dbStorageType: + type: string + dbSwZipFile: + type: string + dbType: + type: string + dbUniqueName: + type: string + enableArchiveLog: + type: string + gridBase: + type: string + gridHome: + type: string + gridOneOffIds: + type: string + gridResponseFile: + properties: + configMapName: + type: string + name: + type: string + type: object + gridSwZipFile: + type: string + hostSwStageLocation: + type: string + inventory: + type: string + oPatchLocation: + type: string + oPatchSwZipFile: + type: string + oneOffLocation: + type: string + opType: + type: string + pdbName: + type: string + pgaSize: + type: string + processes: + type: integer + recoAsmDeviceList: + type: string + recoAsmDiskDgRedundancy: + type: string + redoAsmDeviceList: + type: string + redoAsmDiskDg: + type: string + redoAsmDiskDgRedundancy: + type: string + ruFolderName: + type: string + ruPatchLocation: + type: string + sgaSize: + type: string + stagingSoftwareLocation: + type: string + swMountLocation: + type: string + swStagePvc: + type: string + swStagePvcMountLocation: + type: string + type: object + crsDgStorageClass: + type: string + dataDgStorageClass: + type: string + dbSecret: + properties: + encryptionType: + type: string + keyFileMountLocation: + type: string + keyFileName: + type: string + keySecretName: + type: string + name: + type: string + pwdFileMountLocation: + type: string + pwdFileName: + type: string + type: object + enableOns: + default: enable + enum: + - enable + - disable + type: string + image: + type: string + imagePullPolicy: + enum: + - Always + - IfNotPresent + - Never + type: string + imagePullSecret: + type: string + instDetails: + properties: + envFile: + type: string + envVars: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + fileKeyRef: + properties: + key: + type: string + optional: + default: false + type: boolean + path: + type: string + volumeName: + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + hostSwLocation: type: string - secretName: + isDelete: type: string - required: - - secretName + isForceDelete: + type: string + isKeepPVC: + type: string + label: + type: string + name: + type: string + pvcName: + additionalProperties: + type: string + type: object + swLocStorageSizeInGb: + type: integer + workerNode: + items: + type: string + type: array type: object - forceRestart: + isDebug: + type: string + isDeleteTopology: + type: string + isFailed: type: boolean - globalSettings: + isManual: + type: boolean + lbService: properties: - cache.metadata.enabled: - type: boolean - cache.metadata.graphql.expireAfterAccess: - format: int64 - type: integer - cache.metadata.graphql.expireAfterWrite: - format: int64 - type: integer - cache.metadata.jwks.enabled: - type: boolean - cache.metadata.jwks.expireAfterAccess: - format: int64 + name: + type: string + onsLocalPort: + format: int32 type: integer - cache.metadata.jwks.expireAfterWrite: - format: int64 + onsTargetPort: + format: int32 type: integer - cache.metadata.jwks.initialCapacity: + portMappings: + items: + properties: + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + default: TCP + enum: + - TCP + - UDP + - SCTP + type: string + targetPort: + format: int32 + type: integer + type: object + type: array + svcAnnotation: + additionalProperties: + type: string + type: object + svcLBIP: + type: string + svcType: + type: string + type: object + nodePortSvc: + properties: + name: + type: string + onsLocalPort: format: int32 type: integer - cache.metadata.jwks.maximumSize: + onsTargetPort: format: int32 type: integer - cache.metadata.timeout: - format: int64 + portMappings: + items: + properties: + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + default: TCP + enum: + - TCP + - UDP + - SCTP + type: string + targetPort: + format: int32 + type: integer + type: object + type: array + svcAnnotation: + additionalProperties: + type: string + type: object + svcLBIP: + type: string + svcType: + type: string + type: object + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 type: integer - certSecret: + grpc: properties: - cert: + port: + format: int32 + type: integer + service: + default: "" type: string - key: + required: + - port + type: object + httpGet: + properties: + host: type: string - secretName: + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: type: string required: - - cert - - key - - secretName + - port type: object - database.api.enabled: - type: boolean - database.api.management.services.disabled: - type: boolean - db.invalidPoolTimeout: - format: int64 + initialDelaySeconds: + format: int32 type: integer - debug.printDebugToScreen: - type: boolean - enable.mongo.access.log: - default: false - type: boolean - enable.standalone.access.log: - default: false - type: boolean - error.responseFormat: - type: string - feature.grahpql.max.nesting.depth: + periodSeconds: format: int32 type: integer - icap.port: + successThreshold: format: int32 type: integer - icap.secure.port: + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: format: int32 type: integer - icap.server: + type: object + recoDgStorageClass: + type: string + redoDgStorageClass: + type: string + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + scriptsGetCmd: + type: string + scriptsLocation: + type: string + securityContext: + properties: + appArmorProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + fsGroup: + format: int64 + type: integer + fsGroupChangePolicy: type: string - log.procedure: - type: boolean - mongo.enabled: - type: boolean - mongo.idle.timeout: + runAsGroup: format: int64 type: integer - mongo.op.timeout: + runAsNonRoot: + type: boolean + runAsUser: format: int64 type: integer - mongo.port: - default: 27017 - format: int32 + seLinuxChangePolicy: + type: string + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + supplementalGroups: + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + supplementalGroupsPolicy: + type: string + sysctls: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + serviceAccountName: + type: string + serviceDetails: + properties: + available: + items: + type: string + type: array + cardinality: + type: string + clbGoal: + type: string + commitOutComeFastPath: + type: string + commitOutcome: + type: string + drainTimeOut: type: integer - request.traceHeaderName: + dtp: + type: string + edition: + type: string + failBack: + type: string + failOverDelay: + type: integer + failOverRestore: + type: string + failOverRetry: + type: integer + failOverType: + type: string + name: + type: string + notification: + type: string + pdb: + type: string + preferred: + items: + type: string + type: array + retenion: + type: integer + rlbGoal: + type: string + role: + type: string + sessionState: + type: string + stopOption: + type: string + svcState: + type: string + tafPolicy: + type: string + required: + - name + type: object + sshKeySecret: + properties: + keyMountLocation: + type: string + name: + type: string + privKeySecretName: + type: string + pubKeySecretName: + type: string + required: + - name + type: object + swDgStorageClass: + type: string + tdeWalletSecret: + properties: + encryptionType: type: string - security.credentials.attempts: - format: int32 - type: integer - security.credentials.lock.time: - format: int64 - type: integer - security.disableDefaultExclusionList: - type: boolean - security.exclusionList: + keyFileMountLocation: type: string - security.externalSessionTrustedOrigins: + keyFileName: type: string - security.forceHTTPS: - type: boolean - security.httpsHeaderCheck: + keySecretName: type: string - security.inclusionList: + name: type: string - security.maxEntries: - format: int32 - type: integer - security.verifySSL: - type: boolean - standalone.context.path: - default: /ords + pwdFileMountLocation: type: string - standalone.http.port: - default: 8080 - format: int32 - type: integer - standalone.https.host: + pwdFileName: type: string - standalone.https.port: - default: 8443 - format: int32 - type: integer - standalone.stop.timeout: - format: int64 - type: integer type: object - image: - type: string - imagePullPolicy: - default: IfNotPresent - enum: - - IfNotPresent - - Always - - Never - type: string - imagePullSecrets: + required: + - instDetails + - securityContext + type: object + status: + properties: + DbName: type: string - poolSettings: + OracleRestartNodes: items: properties: - apex.security.administrator.roles: - type: string - apex.security.user.roles: - type: string - autoUpgradeAPEX: - default: false - type: boolean - autoUpgradeORDS: - default: false - type: boolean - db.adminUser: - type: string - db.adminUser.secret: - properties: - passwordKey: - default: password - type: string - secretName: - type: string - required: - - secretName - type: object - db.cdb.adminUser: - type: string - db.cdb.adminUser.secret: - properties: - passwordKey: - default: password - type: string - secretName: - type: string - required: - - secretName - type: object - db.connectionType: - enum: - - basic - - tns - - customurl - type: string - db.credentialsSource: - enum: - - pool - - request - type: string - db.customURL: - type: string - db.hostname: + name: type: string - db.poolDestroyTimeout: - format: int64 - type: integer - db.port: - format: int32 - type: integer - db.secret: + nodeDetails: properties: - passwordKey: - default: password + InstanceState: type: string - secretName: + PodState: type: string - required: - - secretName - type: object - db.servicename: - type: string - db.sid: - type: string - db.tnsAliasName: - type: string - db.username: - default: ORDS_PUBLIC_USER - type: string - db.wallet.zip.service: - type: string - dbWalletSecret: - properties: - secretName: + clusterState: type: string - walletName: + isDelete: type: string - required: - - secretName - - walletName - type: object - debug.trackResources: - type: boolean - feature.openservicebroker.exclude: - type: boolean - feature.sdw: - type: boolean - http.cookie.filter: - type: string - jdbc.DriverType: - enum: - - thin - - oci8 - type: string - jdbc.InactivityTimeout: - format: int32 - type: integer - jdbc.InitialLimit: - format: int32 - type: integer - jdbc.MaxConnectionReuseCount: - format: int32 - type: integer - jdbc.MaxConnectionReuseTime: - format: int32 - type: integer - jdbc.MaxLimit: - format: int32 - type: integer - jdbc.MaxStatementsLimit: - format: int32 - type: integer - jdbc.MinLimit: - format: int32 - type: integer - jdbc.SecondsToTrustIdleConnection: - format: int32 - type: integer - jdbc.auth.admin.role: - type: string - jdbc.auth.enabled: - type: boolean - jdbc.cleanup.mode: - type: string - jdbc.statementTimeout: - format: int32 - type: integer - misc.defaultPage: - type: string - misc.pagination.maxRows: - format: int32 - type: integer - owa.trace.sql: - type: boolean - plsql.gateway.mode: - enum: - - disabled - - direct - - proxied - type: string - poolName: - type: string - procedure.preProcess: - type: string - procedure.rest.preHook: - type: string - procedurePostProcess: - type: string - restEnabledSql.active: - type: boolean - security.jwks.connection.timeout: - format: int64 - type: integer - security.jwks.read.timeout: - format: int64 - type: integer - security.jwks.refresh.interval: - format: int64 - type: integer - security.jwks.size: - format: int32 - type: integer - security.jwt.allowed.age: - format: int64 - type: integer - security.jwt.allowed.skew: - format: int64 - type: integer - security.jwt.profile.enabled: - type: boolean - security.requestAuthenticationFunction: - type: string - security.requestValidationFunction: - default: ords_util.authorize_plsql_gateway - type: string - security.validationFunctionType: - enum: - - plsql - - javascript - type: string - soda.defaultLimit: - type: string - soda.maxLimit: - type: string - tnsAdminSecret: - properties: - secretName: + mountedDevices: + items: + type: string + type: array + nodePortSvc: + items: + properties: + name: + type: string + onsLocalPort: + format: int32 + type: integer + onsTargetPort: + format: int32 + type: integer + portMappings: + items: + properties: + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + default: TCP + enum: + - TCP + - UDP + - SCTP + type: string + targetPort: + format: int32 + type: integer + type: object + type: array + svcAnnotation: + additionalProperties: + type: string + type: object + svcLBIP: + type: string + svcType: + type: string + type: object + type: array + portMappings: + items: + properties: + nodePort: + format: int32 + type: integer + port: + format: int32 + type: integer + protocol: + default: TCP + enum: + - TCP + - UDP + - SCTP + type: string + targetPort: + format: int32 + type: integer + type: object + type: array + pvcName: + additionalProperties: + type: string + type: object + state: + type: string + workerNode: type: string - required: - - secretName type: object - required: - - db.secret - - poolName type: object type: array - replicas: - default: 1 - format: int32 - minimum: 1 - type: integer - workloadType: - default: Deployment - enum: - - Deployment - - StatefulSet - - DaemonSet - type: string - required: - - globalSettings - - image - type: object - status: - properties: + asmDetails: + properties: + diskgroup: + items: + properties: + disks: + items: + type: string + type: array + name: + type: string + redundancy: + type: string + type: object + type: array + type: object conditions: items: properties: @@ -11188,353 +11452,771 @@ spec: - type type: object type: array - httpPort: - format: int32 - type: integer - httpsPort: - format: int32 - type: integer - mongoPort: - format: int32 - type: integer - ordsInstalled: - type: boolean - ordsVersion: + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + configParams: + properties: + cpuCount: + type: integer + crsAsmDeviceList: + type: string + crsAsmDiskDg: + type: string + crsAsmDiskDgRedundancy: + type: string + dbAsmDeviceList: + type: string + dbAsmDiskDgRedundancy: + type: string + dbBase: + type: string + dbCharSet: + type: string + dbConfigType: + type: string + dbDataFileDestDg: + type: string + dbHome: + type: string + dbName: + type: string + dbOneOffIds: + type: string + dbRecoveryFileDest: + type: string + dbRecoveryFileDestSize: + type: string + dbRedoFileSize: + type: string + dbResponseFile: + properties: + configMapName: + type: string + name: + type: string + type: object + dbStorageType: + type: string + dbSwZipFile: + type: string + dbType: + type: string + dbUniqueName: + type: string + enableArchiveLog: + type: string + gridBase: + type: string + gridHome: + type: string + gridOneOffIds: + type: string + gridResponseFile: + properties: + configMapName: + type: string + name: + type: string + type: object + gridSwZipFile: + type: string + hostSwStageLocation: + type: string + inventory: + type: string + oPatchLocation: + type: string + oPatchSwZipFile: + type: string + oneOffLocation: + type: string + opType: + type: string + pdbName: + type: string + pgaSize: + type: string + processes: + type: integer + recoAsmDeviceList: + type: string + recoAsmDiskDgRedundancy: + type: string + redoAsmDeviceList: + type: string + redoAsmDiskDg: + type: string + redoAsmDiskDgRedundancy: + type: string + ruFolderName: + type: string + ruPatchLocation: + type: string + sgaSize: + type: string + stagingSoftwareLocation: + type: string + swMountLocation: + type: string + swStagePvc: + type: string + swStagePvcMountLocation: + type: string + type: object + connectString: type: string - restartRequired: - type: boolean - status: + dbSecret: + properties: + encryptionType: + type: string + keyFileMountLocation: + type: string + keyFileName: + type: string + keySecretName: + type: string + name: + type: string + pwdFileMountLocation: + type: string + pwdFileName: + type: string + type: object + dbState: + type: string + externalConnectString: + type: string + externalSvcType: + type: string + image: + type: string + imagePullPolicy: + type: string + imagePullSecret: + type: string + instDetails: + properties: + envFile: + type: string + envVars: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + fileKeyRef: + properties: + key: + type: string + optional: + default: false + type: boolean + path: + type: string + volumeName: + type: string + required: + - key + - path + - volumeName + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + hostSwLocation: + type: string + isDelete: + type: string + isForceDelete: + type: string + isKeepPVC: + type: string + label: + type: string + name: + type: string + pvcName: + additionalProperties: + type: string + type: object + swLocStorageSizeInGb: + type: integer + workerNode: + items: + type: string + type: array + type: object + installNode: + type: string + isDebug: + type: string + isDeleteOraPvc: + type: string + isDeleteTopology: type: string - workloadType: + nfsStorageDetails: + properties: + path: + type: string + readOnly: + type: boolean + server: + type: string + required: + - path + - server + type: object + oldSpec: type: string - required: - - restartRequired - type: object - type: object - served: true - storage: true - subresources: - status: {} ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - cert-manager.io/inject-ca-from: oracle-database-operator-system/oracle-database-operator-serving-cert - controller-gen.kubebuilder.io/version: v0.16.5 - name: pdbs.database.oracle.com -spec: - group: database.oracle.com - names: - kind: PDB - listKind: PDBList - plural: pdbs - singular: pdb - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: Name of the CDB - jsonPath: .spec.cdbName - name: CDB Name - type: string - - description: Name of the PDB - jsonPath: .spec.pdbName - name: PDB Name - type: string - - description: PDB Open Mode - jsonPath: .status.openMode - name: PDB State - type: string - - description: Total Size of the PDB - jsonPath: .status.totalSize - name: PDB Size - type: string - - description: Status of the PDB Resource - jsonPath: .status.phase - name: Status - type: string - - description: Error message, if any - jsonPath: .status.msg - name: Message - type: string - - description: The connect string to be used - jsonPath: .status.connString - name: Connect_String - type: string - name: v1alpha1 - schema: - openAPIV3Schema: - properties: - apiVersion: - type: string - kind: - type: string - metadata: - type: object - spec: - properties: - action: - enum: - - Create - - Clone - - Plug - - Unplug - - Delete - - Modify - - Status - - Map + pdbConnectString: type: string - adminName: + readinessProbe: properties: - secret: + exec: properties: - key: - type: string - secretName: + command: + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + default: "" type: string required: - - key - - secretName + - port type: object - required: - - secret - type: object - adminPwd: - properties: - secret: + httpGet: properties: - key: + host: type: string - secretName: + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: type: string required: - - key - - secretName + - port type: object - required: - - secret + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer type: object - asClone: - type: boolean - assertivePdbDeletion: - type: boolean - cdbName: - type: string - cdbNamespace: - type: string - cdbResName: - type: string - copyAction: - enum: - - COPY - - NOCOPY - - MOVE - type: string - dropAction: - enum: - - INCLUDING - - KEEP + releaseUpdate: type: string - fileNameConversions: + resources: + properties: + claims: + items: + properties: + name: + type: string + request: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + role: type: string - getScript: - type: boolean - modifyOption: - enum: - - IMMEDIATE - - NORMAL - - READ ONLY - - READ WRITE - - RESTRICTED + scriptsGetCmd: type: string - pdbName: + scriptsLocation: type: string - pdbOrdsPrvKey: + securityContext: properties: - secret: + appArmorProfile: properties: - key: + localhostProfile: type: string - secretName: + type: type: string required: - - key - - secretName + - type type: object - required: - - secret - type: object - pdbOrdsPubKey: - properties: - secret: + fsGroup: + format: int64 + type: integer + fsGroupChangePolicy: + type: string + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxChangePolicy: + type: string + seLinuxOptions: properties: - key: + level: type: string - secretName: + role: + type: string + type: + type: string + user: type: string - required: - - key - - secretName type: object - required: - - secret - type: object - pdbState: - enum: - - OPEN - - CLOSE - type: string - pdbTlsCat: - properties: - secret: + seccompProfile: properties: - key: + localhostProfile: type: string - secretName: + type: type: string required: - - key - - secretName + - type type: object - required: - - secret - type: object - pdbTlsCrt: - properties: - secret: + supplementalGroups: + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + supplementalGroupsPolicy: + type: string + sysctls: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + windowsOptions: properties: - key: + gmsaCredentialSpec: type: string - secretName: + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: type: string - required: - - key - - secretName type: object + type: object + serviceDetails: + properties: + available: + items: + type: string + type: array + cardinality: + type: string + clbGoal: + type: string + commitOutComeFastPath: + type: string + commitOutcome: + type: string + drainTimeOut: + type: integer + dtp: + type: string + edition: + type: string + failBack: + type: string + failOverDelay: + type: integer + failOverRestore: + type: string + failOverRetry: + type: integer + failOverType: + type: string + name: + type: string + notification: + type: string + pdb: + type: string + preferred: + items: + type: string + type: array + retenion: + type: integer + rlbGoal: + type: string + role: + type: string + sessionState: + type: string + stopOption: + type: string + svcState: + type: string + tafPolicy: + type: string required: - - secret + - name type: object - pdbTlsKey: + sshKeySecret: properties: - secret: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object + keyMountLocation: + type: string + name: + type: string + privKeySecretName: + type: string + pubKeySecretName: + type: string required: - - secret + - name type: object - reuseTempFile: - type: boolean - sourceFileNameConversions: - type: string - sparseClonePath: + state: type: string - srcPdbName: + storageClass: type: string - tdeExport: - type: boolean - tdeImport: - type: boolean - tdeKeystorePath: + storageSizeInGB: + type: integer + tdeWalletSecret: + properties: + encryptionType: + type: string + keyFileMountLocation: + type: string + keyFileName: + type: string + keySecretName: + type: string + name: + type: string + pwdFileMountLocation: + type: string + pwdFileName: + type: string + type: object + useNfsforSwStorage: type: string - tdePassword: + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: oracle-database-operator-system/oracle-database-operator-serving-cert + controller-gen.kubebuilder.io/version: v0.17.3 + name: oraclerestdataservices.database.oracle.com +spec: + group: database.oracle.com + names: + kind: OracleRestDataService + listKind: OracleRestDataServiceList + plural: oraclerestdataservices + shortNames: + - ords + singular: oraclerestdataservice + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.status + name: Status + type: string + - jsonPath: .spec.databaseRef + name: Database + type: string + - jsonPath: .status.databaseApiUrl + name: Database API URL + type: string + - jsonPath: .status.databaseActionsUrl + name: Database Actions URL + type: string + - jsonPath: .status.apexUrl + name: Apex URL + type: string + - jsonPath: .status.mongoDbApiAccessUrl + name: MongoDbApi Access URL + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + adminPassword: properties: - secret: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object + keepSecret: + type: boolean + secretKey: + default: oracle_pwd + type: string + secretName: + type: string required: - - secret + - secretName type: object - tdeSecret: + databaseRef: + type: string + image: properties: - secret: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object + pullFrom: + type: string + pullSecrets: + type: string + version: + type: string required: - - secret + - pullFrom type: object - tempSize: - type: string - totalSize: - type: string - unlimitedStorage: + loadBalancer: type: boolean - webServerPwd: + mongoDbApi: + type: boolean + nodeSelector: + additionalProperties: + type: string + type: object + oracleService: + type: string + ordsPassword: properties: - secret: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object + keepSecret: + type: boolean + secretKey: + default: oracle_pwd + type: string + secretName: + type: string required: - - secret + - secretName type: object - webServerUser: + ordsUser: + type: string + persistence: properties: - secret: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - secret + accessMode: + enum: + - ReadWriteOnce + - ReadWriteMany + type: string + setWritePermissions: + type: boolean + size: + type: string + storageClass: + type: string + volumeName: + type: string type: object - xmlFileName: + readinessCheckPeriod: + type: integer + replicas: + minimum: 1 + type: integer + restEnableSchemas: + items: + properties: + enable: + type: boolean + pdbName: + type: string + schemaName: + type: string + urlMapping: + type: string + required: + - enable + - schemaName + type: object + type: array + serviceAccountName: type: string + serviceAnnotations: + additionalProperties: + type: string + type: object required: - - action + - adminPassword + - databaseRef + - ordsPassword type: object status: properties: - action: + apexConfigured: + type: boolean + apexUrl: type: string - connString: + commonUsersCreated: + type: boolean + databaseActionsUrl: type: string - modifyOption: + databaseApiUrl: type: string - msg: + databaseRef: type: string - openMode: + image: + properties: + pullFrom: + type: string + pullSecrets: + type: string + version: + type: string + required: + - pullFrom + type: object + loadBalancer: type: string - phase: + mongoDbApi: + type: boolean + mongoDbApiAccessUrl: type: string - status: + ordsInstalled: type: boolean - totalSize: + replicas: + type: integer + serviceIP: + type: string + status: type: string - required: - - phase - - status type: object type: object served: true @@ -11542,33 +12224,23 @@ spec: subresources: status: {} - additionalPrinterColumns: - - description: Name of the CDB - jsonPath: .spec.cdbName - name: CDB Name - type: string - - description: Name of the PDB - jsonPath: .spec.pdbName - name: PDB Name + - jsonPath: .status.status + name: Status type: string - - description: PDB Open Mode - jsonPath: .status.openMode - name: PDB State + - jsonPath: .spec.databaseRef + name: Database type: string - - description: Total Size of the PDB - jsonPath: .status.totalSize - name: PDB Size + - jsonPath: .status.databaseApiUrl + name: Database API URL type: string - - description: Status of the PDB Resource - jsonPath: .status.phase - name: Status + - jsonPath: .status.databaseActionsUrl + name: Database Actions URL type: string - - description: Error message, if any - jsonPath: .status.msg - name: Message + - jsonPath: .status.apexUrl + name: Apex URL type: string - - description: The connect string to be used - jsonPath: .status.connString - name: Connect_String + - jsonPath: .status.mongoDbApiAccessUrl + name: MongoDbApi Access URL type: string name: v4 schema: @@ -11582,268 +12254,638 @@ spec: type: object spec: properties: - action: - enum: - - Create - - Clone - - Plug - - Unplug - - Delete - - Modify - - Status - - Map - type: string - adminName: + adminPassword: properties: - secret: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object + keepSecret: + type: boolean + secretKey: + default: oracle_pwd + type: string + secretName: + type: string required: - - secret + - secretName type: object - adminPwd: + databaseRef: + type: string + image: properties: - secret: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object + pullFrom: + type: string + pullSecrets: + type: string + version: + type: string required: - - secret + - pullFrom type: object - asClone: - type: boolean - assertivePdbDeletion: + loadBalancer: type: boolean - cdbName: - type: string - cdbNamespace: - type: string - cdbResName: - type: string - copyAction: - enum: - - COPY - - NOCOPY - - MOVE - type: string - dropAction: - enum: - - INCLUDING - - KEEP - type: string - fileNameConversions: - type: string - getScript: + mongoDbApi: type: boolean - modifyOption: - enum: - - IMMEDIATE - - NORMAL - - READ ONLY - - READ WRITE - - RESTRICTED - type: string - pdbName: - type: string - pdbOrdsPrvKey: - properties: - secret: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - secret + nodeSelector: + additionalProperties: + type: string type: object - pdbOrdsPubKey: + oracleService: + type: string + ordsPassword: properties: - secret: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object + keepSecret: + type: boolean + secretKey: + default: oracle_pwd + type: string + secretName: + type: string required: - - secret + - secretName type: object - pdbState: - enum: - - OPEN - - CLOSE + ordsUser: type: string - pdbTlsCat: + persistence: properties: - secret: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - secret + accessMode: + enum: + - ReadWriteOnce + - ReadWriteMany + type: string + setWritePermissions: + type: boolean + size: + type: string + storageClass: + type: string + volumeName: + type: string type: object - pdbTlsCrt: - properties: - secret: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - secret + readinessCheckPeriod: + type: integer + replicas: + minimum: 1 + type: integer + restEnableSchemas: + items: + properties: + enable: + type: boolean + pdbName: + type: string + schemaName: + type: string + urlMapping: + type: string + required: + - enable + - schemaName + type: object + type: array + serviceAccountName: + type: string + serviceAnnotations: + additionalProperties: + type: string type: object - pdbTlsKey: + required: + - adminPassword + - databaseRef + - ordsPassword + type: object + status: + properties: + apexConfigured: + type: boolean + apexUrl: + type: string + commonUsersCreated: + type: boolean + databaseActionsUrl: + type: string + databaseApiUrl: + type: string + databaseRef: + type: string + image: properties: - secret: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object + pullFrom: + type: string + pullSecrets: + type: string + version: + type: string required: - - secret + - pullFrom type: object - reuseTempFile: + loadBalancer: + type: string + mongoDbApi: type: boolean - sourceFileNameConversions: + mongoDbApiAccessUrl: type: string - sparseClonePath: + ordsInstalled: + type: boolean + replicas: + type: integer + serviceIP: type: string - srcPdbName: + status: type: string - tdeExport: - type: boolean - tdeImport: + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: oracle-database-operator-system/oracle-database-operator-serving-cert + controller-gen.kubebuilder.io/version: v0.17.3 + name: ordssrvs.database.oracle.com +spec: + group: database.oracle.com + names: + kind: OrdsSrvs + listKind: OrdsSrvsList + plural: ordssrvs + singular: ordssrvs + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.status + name: status + type: string + - jsonPath: .status.workloadType + name: workloadType + type: string + - jsonPath: .status.ordsVersion + name: ordsVersion + type: string + - jsonPath: .status.httpPort + name: httpPort + type: integer + - jsonPath: .status.httpsPort + name: httpsPort + type: integer + - jsonPath: .status.mongoPort + name: MongoPort + type: integer + - jsonPath: .status.restartRequired + name: restartRequired + type: boolean + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + - jsonPath: .status.ordsInstalled + name: OrdsInstalled + type: boolean + name: v4 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + encPrivKey: + properties: + passwordKey: + default: password + type: string + secretName: + type: string + required: + - secretName + type: object + forceRestart: type: boolean - tdeKeystorePath: - type: string - tdePassword: + globalSettings: properties: - secret: + apex.download: + default: false + type: boolean + apex.download.url: + default: https://download.oracle.com/otn_software/apex/apex-latest.zip + type: string + apex.installation.persistence: properties: - key: + accessMode: + default: ReadWriteOnce + enum: + - ReadWriteOnce + - ReadWriteMany type: string - secretName: + size: + default: 1Gi + type: string + storageClass: + type: string + volumeName: type: string - required: - - key - - secretName type: object - required: - - secret - type: object - tdeSecret: - properties: - secret: + cache.metadata.enabled: + type: boolean + cache.metadata.graphql.expireAfterAccess: + type: string + cache.metadata.graphql.expireAfterWrite: + type: string + cache.metadata.jwks.enabled: + type: boolean + cache.metadata.jwks.expireAfterAccess: + type: string + cache.metadata.jwks.expireAfterWrite: + type: string + cache.metadata.jwks.initialCapacity: + format: int32 + type: integer + cache.metadata.jwks.maximumSize: + format: int32 + type: integer + cache.metadata.timeout: + type: string + certSecret: properties: + cert: + type: string key: type: string secretName: type: string required: + - cert - key - secretName type: object - required: - - secret + database.api.enabled: + type: boolean + database.api.management.services.disabled: + type: boolean + db.invalidPoolTimeout: + type: string + debug.printDebugToScreen: + type: boolean + enable.mongo.access.log: + default: false + type: boolean + enable.standalone.access.log: + default: false + type: boolean + error.responseFormat: + type: string + feature.grahpql.max.nesting.depth: + format: int32 + type: integer + icap.port: + format: int32 + type: integer + icap.secure.port: + format: int32 + type: integer + icap.server: + type: string + log.procedure: + type: boolean + mongo.enabled: + type: boolean + mongo.idle.timeout: + type: string + mongo.op.timeout: + type: string + mongo.port: + default: 27017 + format: int32 + type: integer + request.traceHeaderName: + type: string + security.credentials.attempts: + format: int32 + type: integer + security.credentials.lock.time: + type: string + security.disableDefaultExclusionList: + type: boolean + security.exclusionList: + type: string + security.externalSessionTrustedOrigins: + type: string + security.forceHTTPS: + type: boolean + security.httpsHeaderCheck: + type: string + security.inclusionList: + type: string + security.maxEntries: + format: int32 + type: integer + security.verifySSL: + type: boolean + standalone.context.path: + default: /ords + type: string + standalone.http.port: + default: 8080 + format: int32 + type: integer + standalone.https.host: + type: string + standalone.https.port: + default: 8443 + format: int32 + type: integer + standalone.stop.timeout: + type: string type: object - tempSize: + image: type: string - totalSize: + imagePullPolicy: + default: IfNotPresent + enum: + - IfNotPresent + - Always + - Never type: string - unlimitedStorage: - type: boolean - webServerPwd: - properties: - secret: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - secret - type: object - webServerUser: - properties: - secret: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - secret - type: object - xmlFileName: + imagePullSecrets: + type: string + poolSettings: + items: + properties: + apex.security.administrator.roles: + type: string + apex.security.user.roles: + type: string + autoUpgradeAPEX: + default: false + type: boolean + autoUpgradeORDS: + default: false + type: boolean + db.adminUser: + type: string + db.adminUser.secret: + properties: + passwordKey: + default: password + type: string + secretName: + type: string + required: + - secretName + type: object + db.cdb.adminUser: + type: string + db.cdb.adminUser.secret: + properties: + passwordKey: + default: password + type: string + secretName: + type: string + required: + - secretName + type: object + db.connectionType: + enum: + - basic + - tns + - customurl + type: string + db.credentialsSource: + enum: + - pool + - request + type: string + db.customURL: + type: string + db.hostname: + type: string + db.poolDestroyTimeout: + type: string + db.port: + format: int32 + type: integer + db.secret: + properties: + passwordKey: + default: password + type: string + secretName: + type: string + required: + - secretName + type: object + db.servicename: + type: string + db.sid: + type: string + db.tnsAliasName: + type: string + db.username: + default: ORDS_PUBLIC_USER + type: string + db.wallet.zip.service: + type: string + dbWalletSecret: + properties: + secretName: + type: string + walletName: + type: string + required: + - secretName + - walletName + type: object + debug.trackResources: + type: boolean + feature.openservicebroker.exclude: + type: boolean + feature.sdw: + type: boolean + http.cookie.filter: + type: string + jdbc.DriverType: + enum: + - thin + - oci8 + type: string + jdbc.InactivityTimeout: + format: int32 + type: integer + jdbc.InitialLimit: + format: int32 + type: integer + jdbc.MaxConnectionReuseCount: + format: int32 + type: integer + jdbc.MaxConnectionReuseTime: + type: string + jdbc.MaxLimit: + format: int32 + type: integer + jdbc.MaxStatementsLimit: + format: int32 + type: integer + jdbc.MinLimit: + format: int32 + type: integer + jdbc.SecondsToTrustIdleConnection: + format: int32 + type: integer + jdbc.auth.admin.role: + type: string + jdbc.auth.enabled: + type: boolean + jdbc.cleanup.mode: + type: string + jdbc.statementTimeout: + format: int32 + type: integer + misc.defaultPage: + type: string + misc.pagination.maxRows: + format: int32 + type: integer + owa.trace.sql: + type: boolean + plsql.gateway.mode: + enum: + - disabled + - direct + - proxied + type: string + poolName: + type: string + procedure.preProcess: + type: string + procedure.rest.preHook: + type: string + procedurePostProcess: + type: string + restEnabledSql.active: + type: boolean + security.jwks.connection.timeout: + type: string + security.jwks.read.timeout: + type: string + security.jwks.refresh.interval: + type: string + security.jwks.size: + format: int32 + type: integer + security.jwt.allowed.age: + type: string + security.jwt.allowed.skew: + type: string + security.jwt.profile.enabled: + type: boolean + security.requestAuthenticationFunction: + type: string + security.requestValidationFunction: + default: ords_util.authorize_plsql_gateway + type: string + security.validationFunctionType: + enum: + - plsql + - javascript + type: string + soda.defaultLimit: + type: string + soda.maxLimit: + type: string + tnsAdminSecret: + properties: + secretName: + type: string + required: + - secretName + type: object + required: + - db.secret + - poolName + type: object + type: array + replicas: + default: 1 + format: int32 + minimum: 1 + type: integer + serviceAccountName: + type: string + workloadType: + default: Deployment + enum: + - Deployment + - StatefulSet + - DaemonSet type: string required: - - action + - globalSettings + - image type: object status: properties: - action: - type: string - connString: - type: string - modifyOption: - type: string - msg: - type: string - openMode: - type: string - phase: + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + maxLength: 32768 + type: string + observedGeneration: + format: int64 + minimum: 0 + type: integer + reason: + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + enum: + - "True" + - "False" + - Unknown + type: string + type: + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + httpPort: + format: int32 + type: integer + httpsPort: + format: int32 + type: integer + mongoPort: + format: int32 + type: integer + ordsInstalled: + type: boolean + ordsVersion: type: string - status: + restartRequired: type: boolean - totalSize: + status: + type: string + workloadType: type: string required: - - phase - - status + - restartRequired type: object type: object served: true @@ -11856,7 +12898,7 @@ kind: CustomResourceDefinition metadata: annotations: cert-manager.io/inject-ca-from: oracle-database-operator-system/oracle-database-operator-serving-cert - controller-gen.kubebuilder.io/version: v0.16.5 + controller-gen.kubebuilder.io/version: v0.17.3 name: shardingdatabases.database.oracle.com spec: group: database.oracle.com @@ -12441,6 +13483,13 @@ spec: catalog: items: properties: + catalogConfigData: + properties: + mountPath: + type: string + name: + type: string + type: object envVars: items: properties: @@ -12541,9 +13590,14 @@ spec: type: string pwdFileName: type: string + tdeKeyFileName: + type: string + tdePwdFileName: + type: string required: - name - pwdFileName + - tdePwdFileName type: object fssStorageClass: type: string @@ -12564,6 +13618,13 @@ spec: - value type: object type: array + gsmConfigData: + properties: + mountPath: + type: string + name: + type: string + type: object imagePullPolicy: type: string isDelete: @@ -12685,6 +13746,8 @@ spec: type: string role: type: string + ruMode: + type: string sessionState: type: string sqlTransactionProfile: @@ -12768,6 +13831,8 @@ spec: type: string scriptsLocation: type: string + serviceAccountName: + type: string shard: items: properties: @@ -12845,6 +13910,13 @@ spec: x-kubernetes-int-or-string: true type: object type: object + shardConfigData: + properties: + mountPath: + type: string + name: + type: string + type: object shardGroup: type: string shardRegion: @@ -12862,6 +13934,47 @@ spec: type: string shardConfigName: type: string + shardInfo: + items: + properties: + replicas: + format: int32 + type: integer + shape: + type: string + shardGroupDetails: + properties: + ShardSpace: + type: string + deployAs: + type: string + isDelete: + type: string + region: + type: string + repFactor: + type: integer + shardGroupName: + type: string + type: object + shardPreFixName: + type: string + shardSpaceDetails: + properties: + Chnuks: + type: integer + protectMode: + type: string + shardSpaceName: + type: string + type: object + storageSizeInGb: + format: int32 + type: integer + required: + - shardPreFixName + type: object + type: array shardRegion: items: type: string @@ -12883,7 +13996,6 @@ spec: - dbImage - gsm - gsmImage - - shard type: object status: properties: @@ -12965,7 +14077,7 @@ kind: CustomResourceDefinition metadata: annotations: cert-manager.io/inject-ca-from: oracle-database-operator-system/oracle-database-operator-serving-cert - controller-gen.kubebuilder.io/version: v0.16.5 + controller-gen.kubebuilder.io/version: v0.17.3 name: singleinstancedatabases.database.oracle.com spec: group: database.oracle.com @@ -12973,6 +14085,9 @@ spec: kind: SingleInstanceDatabase listKind: SingleInstanceDatabaseList plural: singleinstancedatabases + shortNames: + - sidb + - sidbs singular: singleinstancedatabase scope: Namespaced versions: @@ -13728,9 +14843,12 @@ rules: - configmaps - containers - deployments + - endpoints - events - namespaces + - persistentvolumeclaim - persistentvolumeclaims + - persistentvolumes - pods - pods/exec - pods/log @@ -13745,6 +14863,25 @@ rules: - patch - update - watch +- apiGroups: + - "" + resources: + - configmaps/status + - daemonsets/status + - deployments/status + - persistentvolumeclaim/status + - services/status + - statefulsets/status + verbs: + - get + - patch + - update +- apiGroups: + - "" + resources: + - secrets/status + verbs: + - get - apiGroups: - "" resources: @@ -13821,15 +14958,14 @@ rules: resources: - autonomouscontainerdatabases - autonomousdatabases - - cdbs - dataguardbrokers - dbcssystems - events - lrests - lrpdbs + - oraclerestarts - oraclerestdataservices - ordssrvs - - pdbs - shardingdatabases - singleinstancedatabases verbs: @@ -13846,14 +14982,13 @@ rules: - autonomouscontainerdatabases/status - autonomousdatabasebackups/status - autonomousdatabaserestores/status - - cdbs/status - dataguardbrokers/status - dbcssystems/status - lrests/status - lrpdbs/status + - oraclerestarts/status - oraclerestdataservices/status - ordssrvs/status - - pdbs/status - shardingdatabases/status - singleinstancedatabases/status verbs: @@ -13882,7 +15017,6 @@ rules: - apiGroups: - database.oracle.com resources: - - cdbs/finalizers - dataguardbrokers/finalizers - lrests/finalizers - oraclerestdataservices/finalizers @@ -13894,8 +15028,9 @@ rules: - database.oracle.com resources: - dbcssystems/finalizers + - lrpdbs/configmaps - lrpdbs/finalizers - - pdbs/finalizers + - oraclerestarts/finalizers - shardingdatabases/finalizers verbs: - create @@ -14076,47 +15211,6 @@ metadata: cert-manager.io/inject-ca-from: oracle-database-operator-system/oracle-database-operator-serving-cert name: oracle-database-operator-mutating-webhook-configuration webhooks: -- admissionReviewVersions: - - v1 - clientConfig: - service: - name: oracle-database-operator-webhook-service - namespace: oracle-database-operator-system - path: /mutate-database-oracle-com-v4-autonomousdatabasebackup - failurePolicy: Fail - name: mautonomousdatabasebackupv4.kb.io - rules: - - apiGroups: - - database.oracle.com - apiVersions: - - v4 - operations: - - CREATE - - UPDATE - resources: - - autonomousdatabasebackups - sideEffects: None -- admissionReviewVersions: - - v1 - - v1beta1 - clientConfig: - service: - name: oracle-database-operator-webhook-service - namespace: oracle-database-operator-system - path: /mutate-database-oracle-com-v4-cdb - failurePolicy: Fail - name: mcdb.kb.io - rules: - - apiGroups: - - database.oracle.com - apiVersions: - - v4 - operations: - - CREATE - - UPDATE - resources: - - cdbs - sideEffects: None - admissionReviewVersions: - v1 clientConfig: @@ -14181,14 +15275,13 @@ webhooks: sideEffects: None - admissionReviewVersions: - v1 - - v1beta1 clientConfig: service: name: oracle-database-operator-webhook-service namespace: oracle-database-operator-system - path: /mutate-database-oracle-com-v4-pdb + path: /mutate-database-oracle-com-v4-oraclerestart failurePolicy: Fail - name: mpdb.kb.io + name: moraclerestart.kb.io rules: - apiGroups: - database.oracle.com @@ -14198,7 +15291,7 @@ webhooks: - CREATE - UPDATE resources: - - pdbs + - oraclerestarts sideEffects: None - admissionReviewVersions: - v1 @@ -14220,26 +15313,6 @@ webhooks: resources: - shardingdatabases sideEffects: None -- admissionReviewVersions: - - v1 - clientConfig: - service: - name: oracle-database-operator-webhook-service - namespace: oracle-database-operator-system - path: /mutate-database-oracle-com-v1alpha1-autonomousdatabasebackup - failurePolicy: Fail - name: mautonomousdatabasebackupv1alpha1.kb.io - rules: - - apiGroups: - - database.oracle.com - apiVersions: - - v1alpha1 - operations: - - CREATE - - UPDATE - resources: - - autonomousdatabasebackups - sideEffects: None - admissionReviewVersions: - v1 - v1beta1 @@ -14304,27 +15377,6 @@ webhooks: sideEffects: None - admissionReviewVersions: - v1 - clientConfig: - service: - name: oracle-database-operator-webhook-service - namespace: oracle-database-operator-system - path: /mutate-database-oracle-com-v1alpha1-shardingdatabase - failurePolicy: Fail - name: mshardingdatabasev1alpha1.kb.io - rules: - - apiGroups: - - database.oracle.com - apiVersions: - - v1alpha1 - operations: - - CREATE - - UPDATE - resources: - - shardingdatabases - sideEffects: None -- admissionReviewVersions: - - v1 - - v1beta1 clientConfig: service: name: oracle-database-operator-webhook-service @@ -14433,14 +15485,33 @@ webhooks: sideEffects: None - admissionReviewVersions: - v1 - - v1beta1 clientConfig: service: name: oracle-database-operator-webhook-service namespace: oracle-database-operator-system - path: /validate-database-oracle-com-v4-cdb + path: /validate-database-oracle-com-v4-autonomousdatabase + failurePolicy: Fail + name: vautonomousdatabasev4.kb.io + rules: + - apiGroups: + - database.oracle.com + apiVersions: + - v4 + operations: + - CREATE + - UPDATE + resources: + - autonomousdatabases + sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: oracle-database-operator-webhook-service + namespace: oracle-database-operator-system + path: /validate-database-oracle-com-v4-dbcssystem failurePolicy: Fail - name: vcdb.kb.io + name: vdbcssystemv4.kb.io rules: - apiGroups: - database.oracle.com @@ -14449,8 +15520,9 @@ webhooks: operations: - CREATE - UPDATE + - DELETE resources: - - cdbs + - dbcssystems sideEffects: None - admissionReviewVersions: - v4 @@ -14496,14 +15568,13 @@ webhooks: sideEffects: None - admissionReviewVersions: - v1 - - v1beta1 clientConfig: service: name: oracle-database-operator-webhook-service namespace: oracle-database-operator-system - path: /validate-database-oracle-com-v4-pdb + path: /validate-database-oracle-com-v4-oraclerestart failurePolicy: Fail - name: vpdb.kb.io + name: voraclerestart.kb.io rules: - apiGroups: - database.oracle.com @@ -14512,8 +15583,9 @@ webhooks: operations: - CREATE - UPDATE + - DELETE resources: - - pdbs + - oraclerestarts sideEffects: None - admissionReviewVersions: - v1 @@ -14660,28 +15732,6 @@ webhooks: sideEffects: None - admissionReviewVersions: - v1 - clientConfig: - service: - name: oracle-database-operator-webhook-service - namespace: oracle-database-operator-system - path: /validate-database-oracle-com-v1alpha1-shardingdatabase - failurePolicy: Fail - name: vshardingdatabasev1alpha1.kb.io - rules: - - apiGroups: - - database.oracle.com - apiVersions: - - v1alpha1 - operations: - - CREATE - - UPDATE - - DELETE - resources: - - shardingdatabases - sideEffects: None -- admissionReviewVersions: - - v1 - - v1beta1 clientConfig: service: name: oracle-database-operator-webhook-service @@ -14747,7 +15797,7 @@ spec: env: - name: WATCH_NAMESPACE value: "" - image: container-registry.oracle.com/database/operator:1.2.0 + image: phx.ocir.io/intsanjaysingh/db-repo/oracle/database:orestart-operator-sa imagePullPolicy: Always name: manager ports: diff --git a/ords/Dockerfile b/ords/Dockerfile deleted file mode 100644 index 25ba08ec..00000000 --- a/ords/Dockerfile +++ /dev/null @@ -1,86 +0,0 @@ -## Copyright (c) 2022 Oracle and/or its affiliates. -## -## The Universal Permissive License (UPL), Version 1.0 -## -## Subject to the condition set forth below, permission is hereby granted to any -## person obtaining a copy of this software, associated documentation and/or data -## (collectively the "Software"), free of charge and under any and all copyright -## rights in the Software, and any and all patent rights owned or freely -## licensable by each licensor hereunder covering either (i) the unmodified -## Software as contributed to or provided by such licensor, or (ii) the Larger -## Works (as defined below), to deal in both -## -## (a) the Software, and -## (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if -## one is included with the Software (each a "Larger Work" to which the Software -## is contributed by such licensors), -## -## without restriction, including without limitation the rights to copy, create -## derivative works of, display, perform, and distribute the Software and make, -## use, sell, offer for sale, import, export, have made, and have sold the -## Software and the Larger Work(s), and to sublicense the foregoing rights on -## either these or other terms. -## -## This license is subject to the following condition: -## The above copyright notice and either this complete permission notice or at -## a minimum a reference to the UPL must be included in all copies or -## substantial portions of the Software. -## -## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -## SOFTWARE. - -FROM container-registry.oracle.com/java/jdk:latest - -# Environment variables required for this build (do NOT change) -# ------------------------------------------------------------- -ENV ORDS_HOME=/opt/oracle/ords/ \ - RUN_FILE="runOrdsSSL.sh" \ - ORDSVERSION=23.4.0-8 \ - JAVA=17 -#see https://www.oracle.com/tools/ords/ords-relnotes-23.4.0.html - -# Copy binaries -# ------------- -COPY $RUN_FILE $ORDS_HOME - -RUN yum -y install yum-utils bind-utils tree hostname openssl net-tools zip unzip tar wget vim-minimal which sudo expect procps curl lsof && \ - yum-config-manager --add-repo=http://yum.oracle.com/repo/OracleLinux/OL8/oracle/software/x86_64 && \ - yum -y install java-$JAVA-openjdk-devel && \ - yum -y install iproute && \ - yum clean all - -RUN curl -o /tmp/ords-$ORDSVERSION.el8.noarch.rpm https://yum.oracle.com/repo/OracleLinux/OL8/oracle/software/x86_64/getPackage/ords-$ORDSVERSION.el8.noarch.rpm - -RUN rpm -ivh /tmp/ords-$ORDSVERSION.el8.noarch.rpm - -# Setup filesystem and oracle user -# -------------------------------- -RUN mkdir -p $ORDS_HOME/doc_root && \ - mkdir -p $ORDS_HOME/error && \ - mkdir -p $ORDS_HOME/secrets && \ - chmod ug+x $ORDS_HOME/*.sh && \ - groupadd -g 54322 dba && \ - usermod -u 54321 -d /home/oracle -g dba -m -s /bin/bash oracle && \ - chown -R oracle:dba $ORDS_HOME -# echo "oracle ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers - -RUN echo "unset R1" >> /home/oracle/.bashrc && \ - chown root:root /home/oracle/.bashrc && chmod +r /home/oracle/.bashrc - -# Finalize setup -# ------------------- -USER oracle -WORKDIR /home/oracle - - -VOLUME ["$ORDS_HOME/config/ords"] -EXPOSE 8888 - -# Define default command to start Ords Services -CMD $ORDS_HOME/$RUN_FILE - diff --git a/ords/ords_init.sh b/ords/ords_init.sh index 0994dceb..fc13912b 100644 --- a/ords/ords_init.sh +++ b/ords/ords_init.sh @@ -35,6 +35,9 @@ ## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE ## SOFTWARE. +# not used, avoid messages +unset JAVA_TOOL_OPTIONS + dump_stack(){ _log_date=`date "+%y:%m:%d %H:%M:%S"` local frame=0 @@ -54,6 +57,8 @@ _log_date=`date "+%y:%m:%d %H:%M:%S"` get_conn_string() { local -n _conn_string="${1}" + echo "== Prepare connect string" + local -r _admin_user=$($ords_cfg_cmd get --secret db.adminUser | tail -1) local _conn_type=$($ords_cfg_cmd get db.connectionType |tail -1) if [[ $_conn_type == "customurl" ]]; then @@ -81,7 +86,7 @@ get_conn_string() { if [[ -n ${_conn} ]]; then echo "Connection String (${_conn_type}): ${_conn}" - _conn_string="${_admin_user%%/ *}/${config["dbadminusersecret"]}@${_conn}" + _conn_string="${_admin_user%%/ *}/${config["dbadminuserpassword"]}@${_conn}" if [[ ${_admin_user%%/ *} == "SYS" ]]; then _conn_string="${_conn_string=} AS SYSDBA" fi @@ -89,6 +94,32 @@ get_conn_string() { } #------------------------------------------------------------------------------ +function setup_sql(){ + local -r _conn_string="${1}" + + echo "== Configuring sql environment" + + ## Get TNS_ADMIN location + local -r _tns_admin=$($ords_cfg_cmd get db.tnsDirectory 2>&1| tail -1) + if [[ ! $_tns_admin =~ "Cannot get setting" ]]; then + echo "Setting: TNS_ADMIN=${_tns_admin}" + export TNS_ADMIN=${_tns_admin} + fi + + ## Get ADB Wallet + #echo "Checking db.wallet.zip.path" + #echo "$ords_cfg_cmd get db.wallet.zip.path" + local -r _wallet_zip_path=$($ords_cfg_cmd get db.wallet.zip.path 2>&1| tail -1) + #echo "wallet_zip_path : \"${_wallet_zip_path}\"" + if [[ ! $_wallet_zip_path =~ "Cannot get setting" ]]; then + echo "Using: set cloudconfig ${_wallet_zip_path}" + local -r _cloudconfig="set cloudconfig ${_wallet_zip_path}" + fi + + return 0 + +} + function run_sql { local -r _conn_string="${1}" local -r _sql="${2}" @@ -97,26 +128,13 @@ function run_sql { if [[ -z ${_sql} ]]; then dump_stack - echo "FATAL: Dear Developer.. you've got a bug calling run_sql" && exit 1 - fi - ## Get TNS_ADMIN location - local -r _tns_admin=$($ords_cfg_cmd get db.tnsDirectory | tail -1) - if [[ ! $_tns_admin =~ "Cannot get setting" ]]; then - echo "Setting: TNS_ADMIN=${_tns_admin}" - export TNS_ADMIN=${_tns_admin} + echo "FATAL: missing SQL calling run_sql" && exit 1 fi - ## Get ADB Wallet - local -r _wallet_zip_path=$($ords_cfg_cmd get db.wallet.zip.path | tail -1) - if [[ ! $_wallet_zip_path =~ "Cannot get setting" ]]; then - echo "Using: set cloudconfig ${_wallet_zip_path}" - local -r _cloudconfig="set cloudconfig ${_wallet_zip_path}" - fi + echo "Running SQL" # NOTE to maintainer; the heredoc must be TAB indented - echo "Running SQL..." - #_output=$(cd ${APEX_HOME}/${APEX_VER} && sql -S /nolog <<-EOSQL - _output=$(cd ${APEX_HOME}/${APEX_VER} && sql -S -nohistory -noupdates /nolog <<-EOSQL + _output=$(sql -S -nohistory -noupdates /nolog <<-EOSQL WHENEVER SQLERROR EXIT 1 WHENEVER OSERROR EXIT 1 ${_cloudconfig} @@ -143,6 +161,8 @@ function check_adb() { local -r _conn_string=$1 local -n _is_adb=$2 + echo "== ADB check" + local -r _adb_chk_sql=" DECLARE invalid_column exception; @@ -164,10 +184,14 @@ function check_adb() { if (( ${_rc} == 0 )); then _adb_check=${_adb_check//[[:space:]]/} - echo "ADB Check: ${_adb_check}" if (( ${_adb_check} == 1 )); then _is_adb=${_adb_check//[[:space:]]/} + echo "ADB : yes" + else + echo "ADB : no" fi + else + echo "ADB check failed" fi return ${_rc} @@ -180,7 +204,7 @@ function create_adb_user() { local _config_user=$($ords_cfg_cmd get db.username | tail -1) if [[ -z ${_config_user} ]] || [[ ${_config_user} == "ORDS_PUBLIC_USER" ]]; then - echo "FATAL: You must specify a db.username <> ORDS_PUBLIC_USER in pool ${_pool_name}" + echo "FATAL: You must specify a db.username <> ORDS_PUBLIC_USER in pool \"${_pool_name}\"" dump_stack return 1 fi @@ -193,11 +217,11 @@ function create_adb_user() { BEGIN SELECT USERNAME INTO l_user FROM DBA_USERS WHERE USERNAME='${_config_user}'; EXECUTE IMMEDIATE 'ALTER USER \"${_config_user}\" PROFILE ORA_APP_PROFILE'; - EXECUTE IMMEDIATE 'ALTER USER \"${_config_user}\" IDENTIFIED BY \"${config["dbsecret"]}\"'; + EXECUTE IMMEDIATE 'ALTER USER \"${_config_user}\" IDENTIFIED BY \"${config["dbpassword"]}\"'; DBMS_OUTPUT.PUT_LINE('${_config_user} Exists - Password reset'); EXCEPTION WHEN NO_DATA_FOUND THEN - EXECUTE IMMEDIATE 'CREATE USER \"${_config_user}\" IDENTIFIED BY \"${config["dbsecret"]}\" PROFILE ORA_APP_PROFILE'; + EXECUTE IMMEDIATE 'CREATE USER \"${_config_user}\" IDENTIFIED BY \"${config["dbpassword"]}\" PROFILE ORA_APP_PROFILE'; DBMS_OUTPUT.PUT_LINE('${_config_user} Created'); END; EXECUTE IMMEDIATE 'GRANT CONNECT TO \"${_config_user}\"'; @@ -247,10 +271,13 @@ function create_adb_user() { } #------------------------------------------------------------------------------ -function compare_versions() { +function apex_compare_versions() { local _db_ver=$1 local _im_ver=$2 + echo "database APEX version : $_db_ver" + echo "install APEX version : $_im_ver" + IFS='.' read -r -a _db_ver_array <<< "$_db_ver" IFS='.' read -r -a _im_ver_array <<< "$_im_ver" @@ -270,95 +297,287 @@ function compare_versions() { } #------------------------------------------------------------------------------ -set_secret() { +ords_client_version(){ + echo "== ORDS client version" + echo "ords_client_version : \"$(ords --config $ORDS_CONFIG --version 2>&1 | tail -1)\"" +} + +#------------------------------------------------------------------------------ +set_ords_secret() { local -r _pool_name="${1}" local -r _config_key="${2}" local -r _config_val="${3}" local -i _rc=0 if [[ -n "${_config_val}" ]]; then - ords --config "$ORDS_CONFIG" config --db-pool "${_pool_name}" secret --password-stdin "${_config_key}" <<< "${_config_val}" + echo "Setting ${_config_key} in pool \"${_pool_name}\"" + ords --config "$ORDS_CONFIG" config --db-pool "${_pool_name}" secret --password-stdin "${_config_key}" 2>&1 <<< "${_config_val}"|tail -1 _rc=$? - echo "${_config_key} in pool ${_pool_name} set" else - echo "${_config_key} in pool ${_pool_name}, not defined" + echo "${_config_key} in pool \"${_pool_name}\" is not defined" _rc=0 fi return ${_rc} } +#------------------------------------------------------------------------------ +read_passwords(){ + echo "== Reading passwords" + for key in dbpassword dbadminuserpassword dbcdbadminuserpassword; do + var_key="${pool_name//-/_}_${key}" + echo "Obtaining value from initContainer variable: ${var_key}" + var_val="${!var_key}" + config[${key}]="${var_val}" + done + + # Set ORDS Secrets + set_ords_secret "${pool_name}" "db.password" "${config["dbpassword"]}" + rc=$((rc + $?)) + set_ords_secret "${pool_name}" "db.adminUser.password" "${config["dbadminuserpassword"]}" + rc=$((rc + $?)) + set_ords_secret "${pool_name}" "db.cdb.adminUser.password" "${config["dbcdbadminuserpassword"]}" + rc=$((rc + $?)) + + if (( ${rc} > 0 )); then + echo "FATAL: Unable to set configuration for pool \"${pool_name}\"" + return 1 + elif [[ -z ${config["dbpassword"]} ]]; then + echo "FATAL: db.password must be specified for pool \"${pool_name}\"" + return 1 + elif [[ -z ${config["dbadminuserpassword"]} ]]; then + echo "INFO: No additional configuration for pool \"${pool_name}\"" + fi + + return 0 +} + #------------------------------------------------------------------------------ ords_upgrade() { - local -r _pool_name="${1}" - local -r _upgrade_key="${2}" - local -i _rc=0 - - if [[ -n "${config["dbadminusersecret"]}" ]]; then - # Get usernames - local -r ords_user=$($ords_cfg_cmd get db.username | tail -1) - local -r ords_admin=$($ords_cfg_cmd get db.adminUser | tail -1) - - echo "Performing ORDS install/upgrade as $ords_admin into $ords_user on pool ${_pool_name}" - if [[ ${_pool_name} == "default" ]]; then - ords --config "$ORDS_CONFIG" install --db-only \ - --admin-user "$ords_admin" --password-stdin <<< "${config["dbadminusersecret"]}" - _rc=$? - else - ords --config "$ORDS_CONFIG" install --db-pool "${_pool_name}" --db-only \ - --admin-user "$ords_admin" --password-stdin <<< "${config["dbadminusersecret"]}" - _rc=$? - fi + local -r _pool_name="${1}" + local -r _upgrade_key="${2}" + local -i _rc=0 + + echo "== ORDS install/upgrade" + + if [[ -n "${config["dbadminuserpassword"]}" ]]; then + # Get usernames + local -r ords_user=$($ords_cfg_cmd get db.username | tail -1) + local -r ords_admin=$($ords_cfg_cmd get db.adminUser | tail -1) + + echo "Performing ORDS install/upgrade as $ords_admin into $ords_user on pool \"${_pool_name}\"" + if [[ ${_pool_name} == "default" ]]; then + ords --config "$ORDS_CONFIG" install --db-only \ + --admin-user "$ords_admin" --password-stdin <<< "${config["dbadminuserpassword"]}" + _rc=$? + else + ords --config "$ORDS_CONFIG" install --db-pool "${_pool_name}" --db-only \ + --admin-user "$ords_admin" --password-stdin <<< "${config["dbadminuserpassword"]}" + _rc=$? + fi + + # Dar be bugs below deck with --db-user so using the above + # ords --config "$ORDS_CONFIG" install --db-pool "$1" --db-only \ + # --admin-user "$ords_admin" --db-user "$ords_user" --password-stdin <<< "${!2}" + fi + + return $_rc +} - # Dar be bugs below deck with --db-user so using the above - # ords --config "$ORDS_CONFIG" install --db-pool "$1" --db-only \ - # --admin-user "$ords_admin" --db-user "$ords_user" --password-stdin <<< "${!2}" - fi - return $_rc +#------------------------------------------------------------------------------ +function global_parameters(){ + + APEX_INSTALL=/opt/oracle/apex + # backward compatibility for ORDS images prior to 24.1.x (included) + # APEX_HOME is used only here and it is set on images <= 24.1.x + if [[ -n ${APEX_HOME} ]]; then + APEX_INSTALL=${APEX_HOME}/${APEX_VER} + echo "WARNING: APEX installation ${APEX_INSTALL}, ORDS image probably older than 24.2" + fi + APEXINS=${APEX_INSTALL}/apexins.sql + APEX_IMAGES=${APEX_INSTALL}/images + APEX_VERSION_TXT=${APEX_IMAGES}/apex_version.txt + + echo "== global parameters" + echo "external_apex : ${external_apex}" + echo "download_apex : ${download_apex}" + echo "download_url_apex : ${download_url_apex}" + echo "APEX_INSTALL : $APEX_INSTALL" + echo "APEX_IMAGES : $APEX_IMAGES" + } + #------------------------------------------------------------------------------ function get_apex_version() { local -r _conn_string="${1}" - local -n _action="${2}" + local -n _db_apex_version="${2}" local -i _rc=0 + echo "== APEX version check" + local -r _ver_sql="SELECT VERSION FROM DBA_REGISTRY WHERE COMP_ID='APEX';" + #local -r _ver_sql="SELECT SCHEMA FROM DBA_REGISTRY WHERE COMP_ID='APEX';" run_sql "${_conn_string}" "${_ver_sql}" "_db_apex_version" _rc=$? if (( $_rc > 0 )); then - echo "FATAL: Unable to connect to ${_conn_string} to get APEX version" + echo "FATAL: Unable to get APEX version" dump_stack return $_rc fi - local -r _db_apex_version=${_db_apex_version//[^0-9.]/} - echo "Database APEX Version: ${_db_apex_version:-Not Installed}" - - _action="none" + _db_apex_version=${_db_apex_version//[^0-9.]/} + #_db_apex_version="${_db_apex_version//[[:space:]]}" if [[ -z "${_db_apex_version}" ]]; then - echo "Installing APEX ${APEX_VER}" - _action="install" - elif compare_versions ${_db_apex_version} ${APEX_VER}; then - echo "Upgrading from ${_db_apex_version} to ${APEX_VER}" - _action="upgrade" - else - echo "No Installation/Upgrade Required" - fi + _db_apex_version="NotInstalled" + fi + echo "Database APEX Version: ${_db_apex_version}" + + return $_rc +} + +#------------------------------------------------------------------------------ +function get_apex_action(){ + local -r _conn_string="${1}" + local -r _db_apex_version="${2}" + local -n _action="${3}" + local -i _rc=0 + + echo "== APEX installation check" + + _action="error" + if [[ ( -z "${_db_apex_version}" ) || ( "${_db_apex_version}" == "NotInstalled" ) ]]; then + echo "Installing APEX ${APEX_VER}" + _action="install" + elif apex_compare_versions ${_db_apex_version} ${APEX_VER}; then + echo "Upgrading from ${_db_apex_version} to ${APEX_VER}" + _action="upgrade" + else + echo "No Installation/Upgrade Required" + _action="none" + fi return $_rc } -apex_upgrade() { +#------------------------------------------------------------------------------ +function check_apex_installation_version(){ + + echo "== APEX installation files" + + if [[ !(-f ${APEX_VERSION_TXT}) ]]; then + echo "ERROR: ${APEX_VERSION_TXT} not found, APEX installation not found" + return 1 + fi + + APEX_VER=$(cat ${APEX_VERSION_TXT}|grep Version|cut -f2 -d\:|tr -d '[:space:]') + echo "APEX_VER: ${APEX_VER}" +} + +#------------------------------------------------------------------------------ +function apex_external(){ + echo "== APEX external" + + if [[ ${external_apex} != "true" ]]; then + echo "APEX external disabled" + return 0 + fi + + id + df -h $APEX_INSTALL + ls -ld $APEX_INSTALL + ls -l $APEX_INSTALL + ls -l ${APEX_VERSION_TXT} + echo test >> $APEX_INSTALL/test.txt + ls -l $APEX_INSTALL/test.txt + + while true + do + date +"%Y-%m-%d %H:%M:%S" + if [[ -f ${APEX_VERSION_TXT} ]] + then + echo Found images/apex_version.txt + break + else + if [[ -f ${APEX_INSTALL}/apex.zip ]] + then + date + echo Found ${APEX_INSTALL}/apex.zip, extracting ... + cd ${APEX_INSTALL} + jar xf apex.zip + mv ${APEX_INSTALL}/apex/* ${APEX_INSTALL} + date + echo completed + else + date + echo "Missing ${APEX_INSTALL}/apex.zip, manually copy apex.zip in ${APEX_INSTALL} on the init container of the pod" + echo "e.g. kubectl cp /tmp/apex.zip ordssrvs-697c5698d9-q8gxf:/opt/oracle/apex/ -n testcase -c ordssrvs-init" + sleep 5 + fi + fi + done + +} + +#------------------------------------------------------------------------------ +function apex_download(){ + + echo "== APEX download" + + if [[ ${download_apex} != "true" ]]; then + echo "APEX download disabled" + return 0 + fi + + mkdir -p ${APEX_INSTALL} + rm -rf ${APEX_INSTALL}/* + cd /tmp + echo "Downloading ${download_url_apex}" + curl -o apex.zip ${download_url_apex} + echo "Extracting apex.zip" + jar xf apex.zip + mv /tmp/apex/* ${APEX_INSTALL} + + if [[ !(-f $APEXINS) ]]; then + echo "ERROR: ${APEXINS} not found, APEX download failed" + return 1 + fi + echo "APEX_INSTALL: ${APEX_INSTALL}" + + # config can be read-only, it will be set again at command-line ords start + echo "== Configuring ORDS images" + echo "APEX_IMAGES: ${APEX_IMAGES}" + ords config set standalone.static.path ${APEX_IMAGES} + + return 0 +} + +#------------------------------------------------------------------------------ +function apex_upgrade() { local -r _conn_string="${1}" local -r _upgrade_key="${2}" local -i _rc=0 - if [[ -f ${APEX_HOME}/${APEX_VER}/apexins.sql ]] && [[ "${!_upgrade_key}" = "true" ]]; then - echo "Starting Installation of APEX ${APEX_VER}" - local -r _install_sql="@apxsilentins.sql SYSAUX SYSAUX TEMP /i/ ${config["dbsecret"]} ${config["dbsecret"]} ${config["dbsecret"]} ${config["dbsecret"]}" + echo "== APEX Installation/Upgrade" + + if [[ -z ${APEX_INSTALL} ]]; then + echo "ERROR: APEX_INSTALL not set" + return 1 + fi + + if [[ !( -f ${APEX_INSTALL}/apexins.sql ) ]]; then + echo "ERROR: ${APEX_INSTALL}/apexins.sql not found" + return 1 + fi + + + if [[ "${!_upgrade_key}" = "true" ]]; then + echo "Starting Installation of APEX" + cd ${APEX_INSTALL} + SEC=${config["dbpassword"]} + local -r _install_sql="@apxsilentins.sql SYSAUX SYSAUX TEMP /i/ $SEC $SEC $SEC $SEC" run_sql "${_conn_string}" "${_install_sql}" "_install_output" _rc=$? echo "Installation Output: ${_install_output}" @@ -367,56 +586,110 @@ apex_upgrade() { return $_rc } +#------------------------------------------------------------------------------ +function apex_housekeeping(){ + + echo "== APEX " + + # check database APEX version regardless of APEX parameters + get_apex_version "${conn_string}" "db_apex_version" + if [[ -z ${db_apex_version} ]]; then + echo "FATAL: Unable to get APEX Version for pool \"${pool_name}\"" + return 1 + fi + + # check if apex upgrade is enabled + if [[ ${apex_upgrade} != "true" ]]; then + echo "APEX Install/Upgrade not requested for pool \"${pool_name}\"" + return 0 + fi + + # get suggested action + get_apex_action "${conn_string}" "${db_apex_version}" "db_apex_action" + if [[ -z ${db_apex_action} ]]; then + echo "FATAL: Unable to get APEX suggested action for pool \"${pool_name}\"" + return 1 + fi + + # upgrade + echo "APEX version : \"${db_apex_version}\"" + echo "APEX suggested action : $db_apex_action" + if [[ ${db_apex_action} != "none" ]]; then + apex_upgrade "${conn_string}" "${pool_name}_autoupgrade_apex" + if (( $? > 0 )); then + echo "FATAL: Unable to ${db_apex_action} APEX for pool \"${pool_name}\"" + return 1 + fi + fi + + +} + + + +#------------------------------------------------------------------------------ +function pool_parameters(){ + + echo "== pool parameters" + apex_upgrade_var=${pool_name}_autoupgrade_apex + apex_upgrade=${!apex_upgrade_var} + [[ -z $apex_upgrade ]] && apex_upgrade=false + echo "${pool_name} - autoupgrade_apex : ${apex_upgrade}" + + ords_upgrade_var=${pool_name}_autoupgrade_ords + ords_upgrade=${!ords_upgrade_var} + [[ -z $ords_upgrade ]] && ords_upgrade=false + echo "${pool_name} - autoupgrade_ords : ${ords_upgrade}" + +} + + #------------------------------------------------------------------------------ # INIT #------------------------------------------------------------------------------ declare -A pool_exit +echo "=== ORDSSRVS init ===" +global_parameters +ords_client_version +apex_download +apex_external + +# check APEX installation files version, downloaded or mounted by PVC +check_apex_installation_version + for pool in "$ORDS_CONFIG"/databases/*; do rc=0 pool_name=$(basename "$pool") pool_exit[${pool_name}]=0 ords_cfg_cmd="ords --config $ORDS_CONFIG config --db-pool ${pool_name}" - echo "Found Pool: $pool_name..." - - declare -A config - for key in dbsecret dbadminusersecret dbcdbadminusersecret; do - var_key="${pool_name//-/_}_${key}" - echo "Obtaining value from initContainer variable: ${var_key}" - var_val="${!var_key}" - config[${key}]="${var_val}" - done + echo "==========================================================================" + echo "Pool: $pool_name" + declare -A config - # Set Secrets - set_secret "${pool_name}" "db.password" "${config["dbsecret"]}" - rc=$((rc + $?)) - set_secret "${pool_name}" "db.adminUser.password" "${config["dbadminusersecret"]}" - rc=$((rc + $?)) - set_secret "${pool_name}" "db.cdb.adminUser.password" "${config["dbcdbadminusersecret"]}" - rc=$((rc + $?)) + pool_parameters + read_passwords + rc=$? if (( ${rc} > 0 )); then - echo "FATAL: Unable to set configuration for pool ${pool_name}" - dump_stack - pool_exit[${pool_name}]=1 - continue - elif [[ -z ${config["dbsecret"]} ]]; then - echo "FATAL: db.password must be specified for ${pool_name}" - dump_stack - pool_exit[${pool_name}]=1 - continue - elif [[ -z ${config["dbadminusersecret"]} ]]; then - echo "INFO: No additional configuration for ${pool_name}" - continue - fi + pool_exit[${pool_name}]=1 + continue + fi get_conn_string "conn_string" if [[ -z ${conn_string} ]]; then - echo "FATAL: Unable to get ${pool_name} database connect string" + echo "FATAL: Unable to get database connect string for pool \"${pool_name}\"" dump_stack pool_exit[${pool_name}]=1 continue fi + setup_sql "${conn_string}" + rc=$? + if (( ${rc} > 0 )); then + pool_exit[${pool_name}]=1 + continue + fi + check_adb "${conn_string}" "is_adb" rc=$? if (( ${rc} > 0 )); then @@ -428,51 +701,38 @@ for pool in "$ORDS_CONFIG"/databases/*; do # Create ORDS User echo "Processing ADB in Pool: ${pool_name}" create_adb_user "${conn_string}" "${pool_name}" - else - # APEX Upgrade - echo "---------------------------------------------------" - apex_upgrade_var=${pool_name}_autoupgrade_apex - if [[ ${!apex_upgrade_var} != "true" ]]; then - echo "APEX Install/Upgrade not requested for ${pool_name}" - continue - fi - - get_apex_version "${conn_string}" "action" - if [[ -z ${action} ]]; then - echo "FATAL: Unable to get ${pool_name} APEX Version" - dump_stack - pool_exit[${pool_name}]=1 - continue - fi + continue + fi - if [[ ${action} != "none" ]]; then - apex_upgrade "${conn_string}" "${pool_name}_autoupgrade_apex" - if (( $? > 0 )); then - echo "FATAL: Unable to ${action} APEX for ${pool_name}" - dump_stack - pool_exit[${pool_name}]=1 - continue - fi - fi + # not ADB - # ORDS Upgrade - ords_upgrade_var=${pool_name}_autoupgrade_ords - if [[ ${!ords_upgrade_var} != "true" ]]; then - echo "ORDS Install/Upgrade not requested for ${pool_name}" - continue - fi + # APEX + apex_housekeeping + rc=$? + if (( ${rc} > 0 )); then + echo "FATAL: unable to manage APEX configuration for pool \"${pool_name}\"" + dump_stack + pool_exit[${pool_name}]=1 + continue + fi + # database ORDS + echo "== ORDS" + if [[ ${ords_upgrade} == "true" ]]; then ords_upgrade "${pool_name}" "${pool_name}_autoupgrade_ords" rc=$? if (( $rc > 0 )); then - echo "FATAL: Unable to preform requested ORDS install/upgrade on ${pool_name}" + echo "FATAL: Unable to perform requested ORDS install/upgrade on pool \"${pool_name}\"" pool_exit[${pool_name}]=1 dump_stack - continue - fi + fi + else + echo "ORDS Install/Upgrade not requested for pool \"${pool_name}\"" fi done +echo "==========================================================================" +echo "Exit codes" for key in "${!pool_exit[@]}"; do echo "Pool: $key, Exit Code: ${pool_exit[$key]}" if (( ${pool_exit[$key]} > 0 )); then diff --git a/ords/ords_start.sh b/ords/ords_start.sh new file mode 100644 index 00000000..acdedd98 --- /dev/null +++ b/ords/ords_start.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +date +"%Y-%m-%d %H:%M:%S" +echo "=== ORDS start ===" +echo "ORDS_CONFIG: ${ORDS_CONFIG}" + +unset APEX_IMAGES + +# old path, until ORDS image 24.1.1 +if [[ !(-z ${APEX_BASE}) && !(-z ${APEX_VER}) && (-d ${APEX_BASE}/${APEX_VER}/images) ]]; then + APEX_IMAGES=${APEX_BASE}/${APEX_VER}/images +fi + +# downloaded image path +if [[ -d /opt/oracle/apex/images ]]; then + APEX_IMAGES=/opt/oracle/apex/images +fi + +if [[ -z ${APEX_IMAGES} ]]; then + echo "APEX_IMAGES not found" + ords --config ${ORDS_CONFIG} serve +else + echo "APEX_IMAGES: ${APEX_IMAGES}" + ords --config ${ORDS_CONFIG} serve --apex-images ${APEX_IMAGES} +fi + diff --git a/ords/runOrdsSSL.sh b/ords/runOrdsSSL.sh deleted file mode 100644 index 07e2b931..00000000 --- a/ords/runOrdsSSL.sh +++ /dev/null @@ -1,197 +0,0 @@ -#!/bin/bash - -cat <$TNSNAME - - -function SetParameter() { - ##ords config info <--- Use this command to get the list - -[[ ! -z "${ORACLE_HOST}" && -z "${DBTNSURL}" ]] && { - $ORDS --config ${CONFIG} config set db.hostname ${ORACLE_HOST:-racnode1} - $ORDS --config ${CONFIG} config set db.port ${ORACLE_PORT:-1521} - $ORDS --config ${CONFIG} config set db.servicename ${ORACLE_SERVICE:-TESTORDS} -} - -[[ -z "${ORACLE_HOST}" && ! -z "${DBTNSURL}" ]] && { - #$ORDS --config ${CONFIG} config set db.tnsAliasName ${TNSALIAS} - #$ORDS --config ${CONFIG} config set db.tnsDirectory ${TNS_ADMIN} - #$ORDS --config ${CONFIG} config set db.connectionType tns - - $ORDS --config ${CONFIG} config set db.connectionType customurl - $ORDS --config ${CONFIG} config set db.customURL jdbc:oracle:thin:@${DBTNSURL} -} - - $ORDS --config ${CONFIG} config set security.requestValidationFunction false - $ORDS --config ${CONFIG} config set jdbc.MaxLimit 100 - $ORDS --config ${CONFIG} config set jdbc.InitialLimit 50 - $ORDS --config ${CONFIG} config set error.externalPath ${ERRORFOLDER} - $ORDS --config ${CONFIG} config set standalone.access.log /home/oracle - $ORDS --config ${CONFIG} config set standalone.https.port 8888 - $ORDS --config ${CONFIG} config set standalone.https.cert ${CERTIFICATE} - $ORDS --config ${CONFIG} config set standalone.https.cert.key ${KEY} - $ORDS --config ${CONFIG} config set restEnabledSql.active true - $ORDS --config ${CONFIG} config set security.verifySSL true - $ORDS --config ${CONFIG} config set database.api.enabled true - $ORDS --config ${CONFIG} config set plsql.gateway.mode disabled - $ORDS --config ${CONFIG} config set database.api.management.services.disabled false - $ORDS --config ${CONFIG} config set misc.pagination.maxRows 1000 - $ORDS --config ${CONFIG} config set db.cdb.adminUser "${CDBADMIN_USER:-C##DBAPI_CDB_ADMIN} AS SYSDBA" - $ORDS --config ${CONFIG} config secret --password-stdin db.cdb.adminUser.password << EOF -${CDBADMIN_PWD:-PROVIDE_A_PASSWORD} -EOF - -$ORDS --config ${CONFIG} config user add --password-stdin ${WEBSERVER_USER:-ordspdbadmin} "SQL Administrator, System Administrator" < $ORDS_HOME/k.txt - - -export ORDS_LOGS=/tmp - - [ -f $ORDS_HOME/secrets/$WEBSERVER_USER_KEY ] && - { - WEBSERVER_USER=$(cat /opt/oracle/ords/secrets/${WEBSERVER_USER_KEY}|base64 --decode |openssl rsautl -decrypt -out swap -inkey $ORDS_HOME/k.txt -in - ; cat swap ;rm swap) - } - - [ -f $ORDS_HOME/secrets/$WEBSERVER_PASSWORD_KEY ] && - { - WEBSERVER_PASSWORD=$(cat /opt/oracle/ords/secrets/${WEBSERVER_PASSWORD_KEY}|base64 --decode |openssl rsautl -decrypt -out swap -inkey $ORDS_HOME/k.txt -in - ; cat swap ;rm swap) - } - - [ -f $ORDS_HOME/secrets/$CDBADMIN_USER_KEY ] && - { - CDBADMIN_USER=$(cat /opt/oracle/ords/secrets/${CDBADMIN_USER_KEY} | base64 --decode |openssl rsautl -decrypt -out swap -inkey $ORDS_HOME/k.txt -in - ; cat swap ;rm swap) - } - - [ -f $ORDS_HOME/secrets/$CDBADMIN_PWD_KEY ] && - { - CDBADMIN_PWD=$(cat /opt/oracle/ords/secrets/${CDBADMIN_PWD_KEY} | base64 --decode |openssl rsautl -decrypt -out swap -inkey $ORDS_HOME/k.txt -in - ; cat swap ;rm swap) - } - - [ -f $ORDS_HOME/secrets/$ORACLE_PWD_KEY ] && - { - #SYSDBA_PASSWORD=`cat $ORDS_HOME/secrets/$ORACLE_PWD_KEY` - SYSDBA_PASSWORD=$(cat $ORDS_HOME/secrets/${ORACLE_PWD_KEY} | base64 --decode |openssl rsautl -decrypt -out swap -inkey $ORDS_HOME/k.txt -in - ; cat swap ;rm swap) - } - - [ -f $ORDS_HOME/secrets/$ORACLE_PWD_KEY ] && - { - #ORDS_PASSWORD=`cat $ORDS_HOME/secrets/$ORDS_PWD_KEY` - ORDS_PASSWORD=$(cat $ORDS_HOME/secrets/${ORDS_PWD_KEY} | base64 --decode |openssl rsautl -decrypt -out swap -inkey $ORDS_HOME/k.txt -in - ; cat swap ;rm swap) - } - - -SetParameter; -$ORDS --config ${CONFIG} install \ - --admin-user ${SYSDBA_USER:-"SYS AS SYSDBA"} \ - --feature-db-api true \ - --feature-rest-enabled-sql true \ - --log-folder ${ORDS_LOGS} \ - --proxy-user \ - --password-stdin <${CKF} 2>&1 -echo "checkfile" >> ${CKF} -NOT_INSTALLED=`cat ${CKF} | grep "INFO: The" |wc -l ` -echo NOT_INSTALLED=$NOT_INSTALLED - - -function StartUp () { - $ORDS --config $CONFIG serve --port 8888 --secure -} - -# Check whether ords is already setup -if [ $NOT_INSTALLED -ne 0 ] -then - echo " SETUP " - setupOrds; - StartUp; -fi - -if [ $NOT_INSTALLED -eq 0 ] -then - echo " STARTUP " - StartUp; -fi - - diff --git a/test/e2e/autonomousdatabase_controller_create_test.go b/test/e2e/autonomousdatabase_controller_create_test.go index cc7fd288..bb63c8ad 100644 --- a/test/e2e/autonomousdatabase_controller_create_test.go +++ b/test/e2e/autonomousdatabase_controller_create_test.go @@ -200,7 +200,7 @@ var _ = Describe("test ADB provisioning", func() { Spec: dbv1alpha1.AutonomousDatabaseBackupSpec{ Target: dbv1alpha1.TargetSpec{ OciAdb: dbv1alpha1.OciAdbSpec{ - Ocid: common.String(*databaseOCID), + Id: common.String(*databaseOCID), }, }, DisplayName: common.String(backupName), diff --git a/test/e2e/util/oci_acd_request.go b/test/e2e/util/oci_acd_request.go index f0a3a841..9230f8f6 100644 --- a/test/e2e/util/oci_acd_request.go +++ b/test/e2e/util/oci_acd_request.go @@ -43,9 +43,6 @@ import ( "github.com/oracle/oci-go-sdk/v65/common" "github.com/oracle/oci-go-sdk/v65/database" - // "io" - // "io/ioutil" - // "time" ) func CreateAutonomousContainerDatabase(dbClient database.DatabaseClient, compartmentId *string, acdName *string, exadataVmClusterID *string) (response database.CreateAutonomousContainerDatabaseResponse, err error) { @@ -53,7 +50,7 @@ func CreateAutonomousContainerDatabase(dbClient database.DatabaseClient, compart DisplayName: acdName, CloudAutonomousVmClusterId: exadataVmClusterID, CompartmentId: compartmentId, - PatchModel: database.CreateAutonomousContainerDatabaseDetailsPatchModelUpdates, + PatchModel: database.CreateAutonomousContainerDatabaseBasePatchModelUpdates, } createACDRequest := database.CreateAutonomousContainerDatabaseRequest{ diff --git a/test/e2e/util/util.go b/test/e2e/util/util.go index b9e76aa7..38867395 100644 --- a/test/e2e/util/util.go +++ b/test/e2e/util/util.go @@ -44,8 +44,6 @@ import ( "strings" "time" - goyaml "gopkg.in/yaml.v3" - dbv1alpha1 "github.com/oracle/oracle-database-operator/apis/database/v1alpha1" corev1 "k8s.io/api/core/v1" @@ -144,7 +142,7 @@ func GetTestConfig(filename string) (*testConfiguration, error) { return nil, err } - if err := goyaml.Unmarshal(yamlBytes, config); err != nil { + if err := yaml.Unmarshal(yamlBytes, config); err != nil { return nil, err } From 860611b7e9d3695d2d1364e6e0f945ed49ec3f7b Mon Sep 17 00:00:00 2001 From: Saurabh Ahuja Date: Fri, 10 Oct 2025 13:29:58 +0530 Subject: [PATCH 2/2] Update oracle-database-operator.yaml (#2) Oracle Database Operator 2.0 --- oracle-database-operator.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oracle-database-operator.yaml b/oracle-database-operator.yaml index 98535d86..97ebd5af 100644 --- a/oracle-database-operator.yaml +++ b/oracle-database-operator.yaml @@ -15797,7 +15797,7 @@ spec: env: - name: WATCH_NAMESPACE value: "" - image: phx.ocir.io/intsanjaysingh/db-repo/oracle/database:orestart-operator-sa + image: container-registry.oracle.com/database/operator:latest imagePullPolicy: Always name: manager ports:

@)3Wiw9MahSO?t~GEIUMM};q1J!X-0-pYS9hZt+EoM8vwD^1 zp|HOIzbk6qX;SNWA!PMT{yDTCAWU4QZeJ)IfmZCqT{c8!1`y_r{n)rh)0ZY_JLkNO ze-=kCF$O9%YNkydxyCLn_t6$l;6rKp{>&!sKwwb_n1|9QWt1TFpL3t4)5F(TD|qrg zJXP-vzNS>YPt;FdmOFFeDguOvi6y6Cs$(p?C2`OoSqKpCuieJ ztA;_uszRrodmS-CGM-xS_EH)SXoBdH8gpD;RafPQ1?5-|XU~7`{mF#Jd`l%M+(?n@ z-N77+eMs7V`XfE)s2Zdg=|HN{BSG>_SB;Z@op8axb|~{<;Q0a7WQDPQg=n9Jfw)Pe zKCNh%!^T*_J}3q00C2U$u=;7P$=`$MRAjS7XM~W~&RkfW`4bh?ULZ(E@HkxxAOzU? zh1;NO2Nc7ZmXM;*voNLEaufyM_B(b&O{gCKR#Us0--|EL+yUPMi_uIpA~yIR?Bxb>sS!G3E;{v{faHCojXlLt}J;s)D6JQfLCRtlbc zufUIToca7U#xrOGJ5bE;*tLVy9d@keFR^5PQG)rUvuv2hecIYsD{$Uv_=_Oi91y2G zmcv!*W`}x51!aglsPD0u^du#79uA}4-l8OPj+-rYX|@mj?gV0ZrEjr^FZRYXb{b~t zsH<%TRY{MhT;Cdn1eHPGWl|tF2lc|j%6P5Se>8=MWn2S4K+o;!0DU{~*c;n?&P=V_ ziR`y_jcVxzcK98I><#|02qtp@9QdElAY&H-n$lseQ3)#0$9K5aDamdJuYosSX8F$> zuL9Iq9RMjr!gd47fDh9AH>I0S;+_`FJO&F`n*{SM-fWseV-;@)MCVXgDlv9azv%-| zD)u1K&7q|X{=ElLISRpojbu>(GS4xEK17|2tfPMq&X%#*D4-f|3NSkc$luy!;NJ)E zkt9#6RA7{qUkm5Vyi~BQcS;uQLQ!Hq zP0rl@EFe4;vDV4=EG#vE`8T|c1wG%XXG96%VTDDMW3jODG(ULH4M4>(@N7mq0VN_$ zZ)!vuCU$#@3tbTU8VSJ$thp8T8^JkXjE4~h-8c}fQ-8-FJX>R*ci{tWlrET+!39S& zNTKde4}nUgxSNEb#D|{Xa!N4Upg?57n^l85mCsD`u2Ib+4!`Cn;O92ogTV{qiqYEM zB^l4g7~WU~Ge(TyxZRXP3PD8wysbvc_#X05aB7DE?dO8RCbu8m6QDV^)Udj6ssvPr zgg{oj;GUt08?Y}5gmdLVrSt7xRpQk}@2@fSaE6G2`=rMfY4+3{ z&miEu?@k%JjDkM19GDp|P8(>5+@F0`ISO981mF}CAklRIGc+&;8G{{Qs@5$)KVKJ! zK+}L>2Tlu!v`_Yb!5oKuFm>cHD9CRB#H-c(3BBb?la0sAF&l|m(z&2MUz{2QF8tA4twH-Grd*-c1FFx( zU`mUS^PK2`_MgqN$}u26pX5&~;;hRO&>c%H^kP%@<;CBh!>_GCk@}||y6y797vNR& z+w;x(uWeU)-{@AtrAW!{!5szo$jScX09&!dZX$P-S_@ol$!QU-q6pvor#BBW01DmX zNKY3;K?}i_fC2}z_cm3}=_d;thJZqI&k};+EnsS}z+$nMJ3ml(z?O$828Vw(#|`}U z$s#N@#4O-%IR4E$vRM*dg%4g4kAgx~wq-PP>8`9X&`PD3dYM!T_#wc}b3|?goS&48 zFhQRqR8R%W87{54zh*ZryiwTfo4Ytc?kFU-{EjR1 z0H8w&(ig{82fsmrRRAnSW_G}d{ef5czyrTL2KXIxM{r2$YaYYTBY^U<4yNbm%lm=n zZ;Bwy0hpmY+5RF2&_cvc0e|UZw~*U%7hoL0Wzk=-%<$3dKi;bGJ~q3qfNJJOi!Hng zOYKl2J*G?Bm;;rtp?gV`5bPdc04q9$f*D)B3V&`SsoL4qLo@qT;Zfmo{{hUeTuia} zombeR`F#d?nI`k_b8wCeK#Wx!crhG!k*Bv;^)f!hH5-J6q+x%-7?%geb&g_QVsIFG z6>CY@px{-@o6mul0Y=qNU!SN39l~hBLM~VWwVvTJZoj6vAFY`!St9?EzqR+$CUN`k zj3BVQE~+M&+91)Fz4vHfd)rHMl%@%;@g=siA6_{RJuG^p3T;TxSE%<_wK5U+#Bhk- z_RoQ|JQEx^O_pI()$w zc9Oy`&MkQmh)7~a>C6!)bh!JkZ&i3(hW@;4jw)PiuY;_D`4Zm=9lGZ-fe~czKVuGN zpf$pBi?lxOiCcNP8DJR*fjFUQHN{w$P+H+0WVav{UK%EAZRM~J!C=x8ZGnLl9Pywu zFIHa4YI55M6^L%X!Bq$ftuXXA{s>oU$I>s|3l9@-mjsa3ftf?Y#T4fN&n-R2iEQQr zuKFZ8NTbQ%Af5xA*FzfB-V=jsdBOaP^n3j4=3^ofFTcAW$DXz;5f-o%6fR;xoEd}M z9}rxY3y6&sP+PK(XJHUJmiH3eL8xa!@;aZ7{Uk^{pX7&_YI&ur&(B3EEe_U&3iZd~ z1|%R8s-QRl%;tZH2udhP_1+f0<6%p<$FYF-DXN>dEk{|lKEajTIMK(Rg6iK#i16*& zzL$bm9+Xd8hBP&4np?`!JaP(pahq#4?*&`C^=NM8J1o@wI>* z6CLSc@uH&XLxRkJ8!afoS*#QzbZEmA@18S(;$3k~5kWBjo;Fp)+$`Xtt?J;_b5i(1 zg4=#%idEEEXeyv$Y;l7534q6U4dTYJSYw|Sb8|?A`V~R@4nl&iLp*G8Va&M}+WC0; zFn&zgYq%M!9!9U9DcJQoA`MW_J+EFO=)Go2EO!HRopOcFXQtKP_vKPJ6^2CS#-Sai z;Ceo{i7&0~xlD+azrA0Lshyo}1i}xU6j3*lQ{l_MY|KDTnA)vs79#tIbmT6Z=yF&l z8j~hmx!s?latt%piYj!XuClAY86~|xDeDz1yhZ~Rl}h4ui=Q28B<+V_WYRGM_T70X z&Mpl4ppKYsXCnV0`3pN81Pk|Z`&u*wbMUyYEvQjRz1&Ar5P}HD&y0Sgb0|EOA!Pfp zKE_# z@T&cZ5e3AN*&O6HWg>roV;lpNKUL6~ZerpH>(~P@?c+BrijEd{lK-+|F&67p&@e+d z;2YoT`J@0Lr1doDE23tTs6(wx1-Cw%7EyCzqH_JoJVL$uI{8O%Ld2eK@nd@fR~4xw zdo(zr2A{2J0VAy@zyLNbV64-LUH`?Oih%=KX2ZTpAo9hYuZ@6QdW>!c@cQfkRF6SQ z7L{Fk>ML$-0jKzlyabReLiNy_Ah(&HbKCQwMo^#oDu8?QDEN&V9uAxZwKh1i?e?we z9Q=X;;759h#{aG)Y8BfKXV(BBk9v*NLn)>AVAc-C;5m%EmrIvol`Jx-vh)BDqp#z9 z`25VS)8gwF=0WH&Y;Wv_2_ksYS9+1}#7K{9+7Rd&(_mrj7XwU;X7Tf>FJPl|td#+c z6&>Im$1^K2scG(mS9XBT*gaHoE1N7BB)2i2ZHF~GUy*2$r%U-Bg0BpnSVNQ>R-X{( zf8Xyv6G(gOkx$2{#{Foa2~_jv#y zVReO5voOx+CP-X@Nbx|5;k!o;x-+2b5EQ+%R%?_G^SzfD4l%sd&CIYujf@kEk6ZpP zzp6EdlEP;d2j))T=&_yW?IuEhjdl!B{cL}-OsaSg7~j_HPcIW-5{_c3eTp#f8(`>o zaa$u@A%=>t5ECgP$Q{;u2|y$T>Q#k$6-Hl(`rFYk-}t05=+f6D&cs~SIF~xpkKTA8 z=rOgL^X+~DxclLYz|o_RUZlqh7I&raaP0pv@*?_?B9X2VxZHbSW}SZ7o;C!VDF}?6 zac<&V62pUkun_>x_n~(hp+kG?Z9_N)P@1THrmv429aPhgwH_%3>vNwQ>zd;ih#XP? zrwO+a50NS9BLLWz0v2$;ekN5#D^g_lEO1d{U}9E}17U}n-Z*o&cdV>|_r|^Q>=z)K z@_kz3!3Krg!k(A!xLN#3JPdIZS(Qqr34!IsBaZ3X)}R2+T(V{_X%XyDtF|?Rw9f2t zOx=J4e|m~5dl&~2^HJa_cv)naS{-d~!AQZBF#=Hgic*})H24lU!D(ci)LUzy0K+N!sRgCF2o) z`6E%KpzBeSCm?1V0VNv@{80XRH6OPc{l~>REESu@a^O)|YI~M(56)tkBYk_8G_~O) z*vQ>pjWzZ^Zh+aTUofhm@&H6vWNZB-(Tor`Is@u38O#JkE}}K3`uV5o*}hAwKJho< z*JHB4iVWI1PylB|?6_*{cYYLF~3@ zs{R6J{sC}!$3QpTq|zLi+*V5NL|U11*H~CJN4ll%BA?k7nknsXG)S%oanoL#sxY3s zZZt4{f7;{$*qM90Qw!E`@r=QmN=kTBc)7&(%fWs?&n(sHd&5k0z#~#jpG?i3 ztWTmq(y(c(!T1@mD}C^C{Q*-`JBp0m4rEem*Y2>wASSZNXYeL|h`lyUg*+H+Jjkjt zbXDMBL?(nYhZGRbS9PL>1@Fug;FK_-Zt#KF^a1djxnNf32yi_ZghxOmB!~$X9>j=9 z!h=EPl(2GrFo8BeJ4eAVuwqcaclP4kHkOrzwBjb@M>C;>apb;ExgGv8tc@bG5CZ)Q zd9N4)-fd1*Yzg~~wKAdbapaZ?xKS#3xymTuJw5=LqBkH}Q~-2~BJdBne8?Pclg~z7 zOF->!@EbwM&o@$FCdWjH4SJjc_Q<%!FsU~fTsUeKFO{+u1|T*idXFSMpZ!tgNoE6k zibgp4`FJ48%iDf(HLwb?|279X2t7gZeDAKt_%!6530|9a+s~B|s0F={5xK9Nr)W|59ssUlTQavS>l`cLRI2 z_2?!=e`2usTxMeWMDJ6l8n74U(Xt7jaiGd+;%V<}XoHz{1w7FmI^TDgsQBfWdJz+N zRR9`Q)C&q#9v;b8d}X)%Av8>;hZ1^Tc-f~^v1wgIXK4MiPV>f_x&Z|w0bGfdmG1ty zR+obdDLOs1Dvvz-rNrV`1`~<{2ShCyIjUsyv6jK14KAh}ub}=iQmQ9UgM%^^bs}Ozru^u=@bTF5Fr+%Hj4G^I``r4OIOiJic^Ij@lY`9qOY{WJ0FTO zM+%@Dq!TkU-RjO`%N$~e`VX65@4}135wf$Wh#+UUJ8cfHF94RaX2e|uX%1$v*?+yD z2-~T-^wK0fX~IhOG?A07XPW-nzm83yY?>BepTvn%h!(hS!8fd3sLB3ROi5ORkDj)x z2D=BxtfyfPMpWe)H8OoqXDq@P>e^<3r#zqTW1lLBGv~7r{wdX#)0Tv&$fQ z7!E*}2tv_9vT@80NHI50Cpdnvx`9&>UtsR$V50PwPs9uo51x5t#v1o<5=$Ir>)o(X zR6SB^EO|0dr*%P-d3rmLz&#{WXDy^?gQaiiB|a2diKgTLeY+ zY{~~9TTPNTh0yI7dX)c>={*HDuL#dl@sxdXH=%M60iTt{&1{+-1It!cu;gQ-?@b!M z2?ob&_*V2}o2B&_d>TF2981$7B*+DYTeMDDB5S{*ZfozC8mmRl+18&mP6n;tJ${7@ zNReMwzISY>eVuReo&cNJ5IZcd>8Z{v9zG78H+4(_TIkLK`wtaL>^OwFCyz56_+aBf z{GfzeVcl%TuHsH*;xl*|mU&u5Qr`Q7gJv&uYw79rUFsR5I_qQJP5g`vdIDF9mlN&t zQTkHHgxPMZp1YDnAsoH8AlOG3BUll@wZXs3XY^^)PMfvVjFW(NLDVv9QULR}W=0`InDlf2*ltV^k$ z6xFR(-;?2ZnhibM89#(6)Xs;zJMQiCGje;?YZlL+rDt_1O4UbhxT?Z^VB^Se7HL|s zxx$3tSNZ3${Q1|EpC-p(%0BOaIQeQAGxziudQd+$rimCn(L`z@axtrK&kMQPBe2}6@( zb0cEwv_v<5IzC*qkVEqFS-FvBM|{z-&I}294gtZFLXIj-J5)o?6JOzUrh4jQ@gt$U zS;v^0X;osoyWe~N-mu$_Bj==f&{CuAV43Ii!BJ|8ClP@4fl#((n;M&r$CqmlUlLQ{ zfJ+w>ixx6zWyzy}WU`=|kjf6~(*q)QKMz@%8> zm|g-5otRoBimw&AVkkh#fw2MP#-e7x`*ZY7-CGa!cNj0rS}sDmT>j3SSXylS^^W@m zE`Z9uk*?^8t9A9S3y0rtKb~yE$i050f`3` z9yLFUkjj!W@?pRafRyMbk(%h{s=AulZDte)SQxVG^c6A7vJ5C-0%ZK~n62BwX#%a8HMagIi%m*j|Lx*-R)LoR9ct zHN-`#LUGkIOnzl#hqL&Sbj^>+Ah6glY33{0!@%!XGpNIDUY^v-ihShpaj>GT|& zZ?DNPM1Ji;DMK!R8iO6^y$Kk8v!8Pv2UcbH**X`DUB-lT#US9wy9~0*~!Sz9#j~b%>v)Bx=FOTZ47NU+PUb|nos|)s5tPGRmN}7LCUHCD-wTgm(_(9uu=AV>0L(rBF8nBw z$eb)_fBf_9!%FksRs=hpM!?WYUl;V<(Bn36s>{(rRh184uaNS;E;8{>lU!af*(gQ! zz}^}%@9Usgxk-d~vO#Sh6Yvi5sPN6gkgxymlBI6G3aQg}^JGE}#_~H4meyp@;M{hb z(1LFod`Yz}apgCJi0&nk^Cf=zzZa~POyB^){wxlSS~gN zFK}0KawlNyy&|NeOD87bQ{`F|@&>+WCVno~D0}VARM?_7b!3wDnE7pasam15Hw~wt z7b>bj3-aEgjZw@!43y6?WprEaKQC-?Rg_eq%0$n9VWssa=K)6|G1xJVCDv;3V5viA z9wFpN!;=AwhVkGOcFb*m|AgyY?IuusimE8KK)2;GeQ2 zj`1Dq-xd-rdE zV|Bl$DD)|(NSyn!%`X!ItCiI%Z`YnDZl=ULrZM$?Km5#rhm1@ zMHWsMJ_u|xAFoJ7n(J(b>?qzbzWr9C7LGIgz7q5)>VRZ1wlC3zs?Hj088A1z%i3lf zIC87SI&e7pzEO68_`K%q=P21nJ`Uv9>n7<5&a7pUnPP~DbN`ef7EAa|;cVCZ|4vTt zw<|M__vPr1U7Y_sx^Zliy8nzM7!0V^S>;MD8FeCBPcN(d?6C{7Q9IB9`Acv2F11UQ z_Gd(_GhEbx)Ukw;3VUhY$J+xfK(VC` zDy9-%>fZ#k-%FqVs>Tp?`T&~OaRS%Hlx|ZKU~w<+owo0ETF1Gs+LZSk4~{=fTr8Y< zE}ddq(yl8URn+8TkFNQ0f4&N-C2XwRRk{w|A_(lt_rMXgnrFXBU%uCI;cH5kmXtL= z`?R3Tum4nyB@i?|V@h7a)Gs{n{y+ybeW;~OJ06o{im;i!>wY|hhjbthRA<3!=YJvJW*A@LZjB%ah>CEteybscluYk#{P@t=3=w$+|--3 zEx<`GfVaGC1XZTnjR2*No6XT_WO)HmU%ul7?`GkVq?p=}4-(YU_)+jttm=E5Rd6LY zH<^Sgigr*nNy=i>f1U{uZ`}fQ)xH?U zt`9M(qu-#)q3fIr@tgy6FG5qR1n)>5EEI))vwG)4lk^ufl)MPwYA2n$Ci6`h@FI5H}!vO22k&N0xviEs z!#BHPb&P?@7;>c74E_tyo)VzL%bDJ;y1g-8gm)Panj$pctoch#XJ|`Kco4V)#v4Pu zYm$f;;`7kWxCoFG?eR(;MB^b&m-@i@Sdk+5Y7CMBj>Xyw?yB7mRrOk^-}$%a4sW(J zzN&%N80imAq;c+9)Gpfs;dvGmWF0y#f04B!A0>&n`L<7@pnlDf5B(3Jt}UEjuJQ7K z79P=0Me!Wh(>q2#p#?n0!i%y;g8Vs5d}#FGvoCG|j35RyINe|uVFx&NGsASo+tlK# z{v2Piy{G#Wdf?6gfM3x2M{P1#wQ14j$s7RW0hTe;uYRtp+;g>1p(_f*^;(XnnuhX9AI?IuAyv$)!D{?h%8iBRDsP@R3hcdes_W6wr5=Hv*LhkWhn! z3*+O(Pci*gj`S=HA)AGV-Cqt0Z@S!U!WH8H+|X93OQHu(I-&p^jz*GP+xiv%hFc48 zOQZ#!nk)eZ2F=qENWj!VS&4kA9{#eR;peej{K@tDsrd0xH9nn7&(*9Koby$Ao5Mh2 zCol9#<%hfUIeFtV&%1L{s%=YpYeb5XJ%XI)ZytDj(tdGL+gS}HZQ7qtKUw!cqN9jQuL)t)V`i450OsA^z{mcm3Xth}Eg7cb zp=Y6!9}BI%s6ECA=cK8p*-sW9ty^dN0fmu)tW?ih@1V~W#w7uZljRgnF5~w;CgZNP zDzo<>RFwOY^p{&v%D=~!`nkvX_z<}t`y0GOo;W=x7@ev1XfRPUlCXaJD$jEvISNMx z>~aVfU0B|s_cMIu&W`@~l^}SUV31+0ZTurtj;N7{`KOOfj)Fwux1E~APGBrhxyZn% zUDPcuW_!->29X^rtEJ@D@@c*IP>vr-zFV2+U%NTly%YN51Y7TCwd0H|_icaqBKMUK zA6()KcrvC(Q-=H}Q-l2WWZr9co{j;jpgZ3qCilm?wAOWZ5T5@1^(Q7Ss&Jf+sMOIP zlyUgVVwHqVgO5KV2xE{S43Z+j{DEkg#4uR=x$v62dmCIxo;bY_+^+B^cY1k(%Z`qx zXbuyWJmRu3tIkwyLkpcBjwJ4%ZXb0WRBsMCjE=r7upj)ySu*uNqOt8VU07x&RkiJy z&}n1tmFM)c-a)VN8f9nIw~9U7wy(PdrRAx?IoVdoWCxh#8qH_ zJNgEQsvGd5-V@wdDz5zwf_5STzRtQ3KFRwT7$-zTwC8ZuqV@t}Drr4;O7+CapJ2Ph zx0-Cupk3xo8_dZU{ZKXV7nFkWk}GQ{GkgkhBAlg>6Ra9Xqk=1)R)YAWGJ{{UmRvuP zQ|TCRm!8iPuY%!XXpzBpjqq`k$7cMDK5t&XucUmNNMrnkogWDhrHK-_XpGTQ&(^ZN zEa{vd)F34p72Tv<0e$OInoGs56Zy&Qz;HO?m}M7CWiOlL6#S1DjiB?`4brGdjs2z7 zR=b@K@%A8;Udvrsrz%DwHflC&Hym`FjS9|moH)0dlK1zH8$ATH0=R^plToBgg-;#P zEBds(Qx3OPN1(>c;B-ZwkKa9NSV&s7ph^KLxUMM8(_oj5b8=yPt?;5uADWb(1>>jT zn2KtXxBvdoC2Ikn{_?qFL~M=wKB0Irw^sE3tpp>UD5jR4vT7`vs!HS^7nj1Ne4})l z#d=Z<-XL1RARNvm?M7mXI3<~#Ym9G_9*=X=IdAPTp{r;u)VO(U@f9DY zyA;GD-tl;gP8itSB^v`hba>H}Sf3&4RdVX#)A-&inlMxGGInBg0&RZz+@Y(!xc0{n z=|9&ckB{lg4h`mITx|x!0v@f0)|a5lIg^%90kr~`&rWSO907IaAo#nTR?Bz0=RaFZ zU&jn(0@)OLa&r|%ygB5pyQIEiveqP|}-NRmGFKG7j& z^JR^PUV2y`<>kXdVUzh3XJD__xsN-VR$!yvs1Q@Gh5rzCIM0+vQaZtEYfIK2O zKb9g;ZXAlO_e=x!A7q#%^?s;XTK(*Zp{U1j>bgSZm3$r$0mATl$iWdF^KXduVfgg~Q9($4|p8O(3z0`@mVPXJUDqPLA z7_|t6Fb5U1+4?RYcE9LIF0xoHFueibH;gC`7el>TC{!K(_npkS!Ho$uM2;`KjkP zfhog#L&3t^H8nsGMac8lx3B~CawIU|$9T#b`?p(!Ujep{Pg*&~fWw_#9qtZDt_qQ8 zI%2Bb7+nX3mxnuA1!Yr(Mk2xaA9eWY$@deyRgn-v|Nhc`d{4c}tzjz0W0 zH@HjPWBj>G7HjtMu7o8p)!?@M=HK=!>uwWP>ue7S9}d(+BauTD#SfZqY=AsN)f*4A z5le7m=`&j#u$zdckNCf?Fek%!K5N`o^n?M;ZJ|s-5F7|F--^(?5+d3HznNd3kAz+` z@Ry7diN~hX`b5-B6*QJ{Dg%G5PaPx>H4x%|o0Bn8PaTidcpXjH$1(B7KX3SWt!6Y)n~?M)H17J0+qxJ%S_hok1t&*YlGOX=q0hyFRFy~*4nPd;j= z(acXFwGA|D8bRv}*ynE)#2t@w@CtK%6UD$wiW|1an{W?n6It(8;U#)sMRz2aR#Ckm z>#k5!WPX4v48;Z{nEJqeT9LQ*c%%0p)?iz28j zAtBYZJIvfJeG%i{6j6d60io)D(lHJWHc|JluorCC{MpU}vzaXr6(e6@qT(cqvk{%U z_E=|Yn469xV^5V#;ICqkn*pdX3gCy3^Mk&k!Rvv>XOtwP(C}4|Wv>nsXi}aGUWJj; z9F-!&l$BRu?E&FGw<1^$iFrP%<1K|OB*1+A$5+{ZpT0=$MeN7{-@`;lTeAN|M=T7# zGy@y9eEt829g#Nh$9;Vj)5;hic3%p~{V16}Zv3AiFx$KB>wgQcLg3J^jC@KIS4#@@ z<3orw_(NV7$)$1r69B(*f{4y1fqVkR#kRseSCdEhJp!4jxS*d~~%c*9#}rG3!+F07amcMmF-n@HPe| zyDM*Pav!YKcqC{NNPR_Il%rQ(`0}-p_Lt53occR{nGvjY2D0Xtt@AZ6XUL31ZE2pA z8NF%p+_76KZ7cI`*+dl}>j^`8D8lm?t_{7H04EC*+1kj`#cTX~PE(;3$X5-QR+slM zS#k5bhyI2CxkZkKE6>R=?$TUY6x0mK27?U?-Gq1$9>6wAzL13$rn>*UGTf&B81C}u z8-A*ESCR_iUwPr$Ib#1wee<7vY~{)=00&*(%1IB^)o(W*o+r#95H#6208XH~9tIo? zf}Go`0GsjX=_GK!`z2ka-s>~XGRYr}G92c=@!#3I^W*r&|1v<45C7j9pvC;Zruzd4 zyJXK@A`@rtk-LTQdH8@iPVdsu0zhNn%42qHsx`O@<(|?Qx3gjLxUcwrOC?o8>UN^G zU?*+l`3Woj*-48hk`v7vlrNo5^dmIW-ep09dyGt!34R7<78|`6nE@CP#^8*G?L70q z)e$W{RZQdNOQbmN(6N=F#QF~xAkY5Yi1(Fu36s5L)DC{JkTdCV=F?6YV~nq4Cc}hG z6Ebku&6$6Fi&#}?{M~=u#J?Bff4T{z?SH!osGru{LW5?*RkrZI?gRhU^A=8@C}9q` zYGhohpo@|jh@kbsnH32Yfg4lnWxsq~ZG4UiT7k&`b7VCJ9Q2)46@Yvv&LYo+w}q2| zSYI+o4_nYL_fDYPT7wxS6L%?t8I#hI+%g{^yE5MHlw+BMhatiU9tQ_q1;mPg7OC?f z2$S6j6jy_?IS_tjT4pYGc5pGl#2!ky|0+m)-&^FLxC+Ps$)5ky(8aArOd7e(YSm3~ z6?#Pf{~daSFfsHUZYwkf7%13|7vz!S;*alSI2UDk)v&9k#<8?nyd8duco&z%rEDxA zsiw@^!n1=gJ5lB9JN<$n*rekI;W>>|XmslV4TaBd4I>F}N%~fVYV^wu68iUKE(rO? zi~4uG;%+AAtp}q;8{VUDJ^cr(F?m%swk%jyG=L~od!LXW|77giccR7TKhom z*VxlnJs}nmP=`5zC%R;8yPtmSDBqbTiMf&m1+ulX3#FZ6!EFasJ4qKKa$MpzLiC)b z*;B-6x29h)4GFr;E2Mcj=bbSQ=bztq7;i7V{GcP|UP2Y=jdMAcJN!7iQo3NfVs?Cn zkh%nDz=lzU-tR0t-l%swP04s`J?b_t#@Zn#nl9P*Pr~(qFB-7UHALGpwLVj1-_C3e zqv+@x4@2J4mV-!FM@7#irL?r&+}hXO7=4 z=@goNpLEev9m;9Vb8lhL;Un<2k4*B$Y13|AOWIk%JNbJ+@}7qCNF+xmBJ^}JrhR6A zqrzj$ho0O;6;vl(Dahnr;!(xj*7^rWxbA~%=1n7sxvYPnyRBoGAijdtZW$Y=Y2{fc z1`n{C?DZ-+4qH-1U7mH*sRr&tO&88BfY>Mg?6*A%HF!!alJEY$PhUNnRa`fhX8YrG z!r^48a*_LL)kKb2vO376jscPLpWxljPYX1RgU{1U|pDdTPmF>hUKm`g}3qciz5lwO(eSbogD&wZS6j2LK`$9WIGF zCYGkY4qia}G~V$a;AjZQ?lwm@n#hPN+$bYXMF>CoGH4~}2y|qVk&8hTr!l-ALKqPb zW&;1F>5*i75N=sJKhbIOaE*UXc_F&ZEuP3;#v07nc*~5?{leuQM%q~5yfsuW-Y+#S zg=brU=Ii}o44NZ^Nv0HnfeRbfxx#WHfN-W(Xy^E+KVv(DptA_bvp_T?6)e$$bmA&9 z8rfPl*F7GRhUVCg+UlMZ?1X952QG|BF3o*6Z69tp;^R6Q3fypQRNbF$qb)lm7|76M zS8kKtI*rZ0raz_rt%#8V?18goCkBML8P}cjV#!X`?~HhK^a_b+0)ec(do~Z)2-hF( zm@H>Q=V3xu=iwTH#&`ExMCML^QSBEtUl!;eBU zrPj}Q%8ANiSE-r$zT&+*h8|%>9pj+xbP8Ie2G;n*r_W0OX`~Vu6Z#{m!avK(m)C;> zSXY)`p6w0!zzbt{~}}PIT$G1adHK)ak2$v+VFZ z6Wea%X3SXw(nJ%+(yrUDxwSg`)S3f;R5Gunc8(Md=2!+1sOe#m693xx5fxnM3ZjK1 zivN$D5kP==;)!x<|H~2;+K%FZ{)2eYa>>c~e3`qTyZeUM#-!r^0w`nRTpo@8=Y>Lh zf8>^!V1j=J`AvHa#rX=QlsurX%fcjg*VOBoes-zhjasNtED{P{rCw0K#bRWW6~<+& zO(mj`q5x`@Tb$(sQVsOlth6gvOZ7ZUbm`7?+U^uDxDA+4>mY^1#P>%Z=01GPi9}*J zAGYyW80`2eiVeHwd*W^R{>o3<;4YHai+m*i=K_l-U{X%*pvW7|!fL1_OjG+wXWFl!7iso4_5 z(Q{S>oZ$jq)25Mk_pJQtrm}W`xYf(Bj>leW-)r_Kd3xE z*b?0X{=Ee`9xsiFAZNsZ&XqElnZ}WP z=P?U!ij?nB(b$t`Iu*3Eplg4B1Dvyf3sNVes&^+xJJj{5fMQB-O2cI@s{^zqP=` zqZ0pG#$1P7_jFdIpOsRXHt~NlH}yZ7)9uzLeaI~u0T(RmG6%fXb+f+U`&%Qjv;V=XamLg5(Q8>RIm)>y2L7w^v^m>oB_CR( zo+eMhrTZR;(Xo|pDTaTt24x$tVt%Lmum7`Rf8Bct`WvoN9El@PJKvhBuwU+q@;rS} zZ~+v2ioJd>aMg*ADjDCx`oAQ>ngzIbgrh~g3{jV7a0x}=1^Syo>6V+Vb> zJ2dM$TR<8|1_;0!OCMx=)^|Mi)LCwl>lodGJ%0)B&hTC;*{U(04n`-I4?5$@O2*U_lAR8 z6fYRC&m(zo?SPxQP#n z1fYeEEG8Fptff93&aQYNBManP>`};I-9mM0N~>65dC@*+)o;{Y!5B%JLO|8GTV<~Y zXmS))_UAlYiO@3?XDjcLPgJFa_A)Doynft%1lRz5K!u66yz0**(D`1fU&%n5(FJ3G z@|ZLv05RVVhl2}DbJ^eHHx zg3lXB13lIQU{%@iAD&6!^I(TwzU*ZmF`z&q}>le1^ZCG3ran&1o*X4B#!ydG@QMUlK zCMC;~PP?TN;+vt<5jm0+aHFFlS$sc2Mnf!09)tIK4&?NHHuStD_FOok-|)-2L07eH zq8LXAyFwQbCsM`qeU+eZ+tdTU^9{$BR3y%_z!hlA>;na|>y*#AuaRdP$#F`)I9ucN zj;ch1>q$hi{xEK$3V8`+4|FAOrjpC$xdoX5itG~+JJP=oU#ej7b`sgoHMn6~SslRN zSV~FWP(YvOI6LYf;A8vv%Zg~JI36XqDb7O|ZlGjx37Wloq32zNP%Uz+n-Me?OZJb} zK%lqy$>N$6XyJ0O;_eBd9S71;8y}l@-n!Dqk*|E#I8KUm6wo(uQ?%=nVlr)cdH>+o zOybj_%ngB#p$w`x@t%e-TMpzD#1*5y1M!g!)+BB9rh$}u6Xi4L~_T$%kzeX~Zg z>`*ru(66D}^6~}aNd0ty>jVQ_u4FJd@AwZvjt8sb5F5PvBn{9A2dYJgc_m-$`Ph?? z3OYO^J?`aAI5GJs=zX#U;1DMP?23X%#8~i*&R%G9K6|i0z+%;`VYP~pl)|VqA z17uNSCnIJJVt}Np0wB*60IT4Tqe%|(3cICZPb%}7_jq()xO`9cHzp(e=D!HRyW`Bf zOTV*v*Y&(9IzK%sBIf`(BVt92V5oeI1)Y_K+MYaCi1xp>HC?IiGxCOmZUeHn+OG__ zKcu6-w>?BRiA0zptyanwTKy%QfU-7!+iG8O9<6gjutxT&cc>#q*xqSY_nc$Z1rnCm zz6;S2i+nsP_5rVf8h-?3gd?Zm%dQH^tn13)!BGvWy zb%qVla@B2cwGe4`EpS>Y=Kl0@wm6Comk&d@c340(mKj*EXX-R9Sl{55)SwiTo=!vR zpfP$2n+d9%Q?QZS=r^6T+{JX&8wR2B4tO~253p{8FlB>K8_}3dnihlQzQvmD4cc+n z{ts909ZvNh{*Tvj%AqndDr6>ml$AX)GEna% z_V0eZ-|x@o`?-GC)zu&MIPmYaMeBO@j^rV&bOuEK{E%LmU}fk7YKEQ4+U z#XIcu7}OOOkUmgvyDBvHk;wPDlzA(LYKPl7O4fz)LYyNSsn9GN8+YlItj?%^Az4H* zlXof1SA`sn#(p3_!IyBZ7+CPdEv&t&Lrf_os3#{p^?Gmw$c7nMi~Xf{)>KiG--?@gQ2*Bkr{>(;Aad02hLZhBQ5|b6_Xa-csbx;`VV^>P>m=_aIzA!ZbL>kf=T8zj4#e% zp}r>(ilkq>ldL&6?L}lfBVC-|G)A8|Km#<#z)c4ysf46oVcxOBNnVC*c{-;Ey+38Y#~&8L>AGICO(92x-Ag=++WT+8(lS+{TDa_Lk887!(S; zCDBis&nw?eDh$G&D4UbOPX!@`^VQHJ_ldyO%Zk&_8h6aN`|}tZsZX|!SZXG=SiRq#(ttY8By1EuRVa0?E0GzUJ&a{_b3`?$s_9&C zhi{WrJW<;!b$<22&)`(qZMh4E+^FzRN?H5xYDA(J$#H5^E!K26m#slvyXz*;DA0JO z_aK3FQS32mGRGZ7BAeAyga^voB&+4D+hRB2R=U5Cn(n>rl|LWEhDvCR;dpaxJb^tv zTnkvTqsmI9a4GSSlc2(XdktzL85czyi#)FPKVyVTpsofY+eJ+Fs5)1;pZ&L&A-2|0 zbqFh8F`70Nd$9$d!O^60ziQKFX*^`1F@Ec z1SN)qdwz$ToIKvqjX-f5Bo6*!1xH}8{1PH^*@VEg?FXc(MG<}(2qO$mE9FgFi3-{Jb@`|`NnVFFW;?@w;LEb3&+=oyi$v7FfS zG7Z!m6wp6POafNDMxZ3{tzPNNh!*}fYm-y{+_%CF>wi5}MM`7ZPFxKQy@a*N@(P7= zzG9M6xvYIgY7E;i;#4v$V~-<$d+?osYgV1PI<;qLcv!iema@LTH*1lJ)jUll z0c)3gX@*VcO|Pm(d7I)?wy3jQrhkq7h@e~l9Ml9xlX^{E|7ITLf}ZQm))duYcv27Q z9dBjvJlc(?Kbraiv4_bXFaMmiUONdoF1%?;cD@h}ZGe4qC`3VHa&ufa|HUS_PKirr zP6?ABq(Wog^_yPC7Z++|g}uN8EqSGbx)s&Nh0uAn%3Vtq+$*epsre}o`@VPh9SlQ` z#&PSIG03w)J9_J-Nmv+#!fIe5U71gi*MLyb9!MPw-fHhUm-BBF|LAs_cl1iZ-b>AwJ;Gf`gPtPZxsY5Vx}de4{$+y z9=Z4Th8m9Yj<}C`MTfV-AlkUiI(FJva9xG`lz(`#4@Urt6CP*wxeI*k!b@-}O$;`a z7}e;Qg|6^%lIgM{Gh;3&x`xyP&)>&VPSJ#O+e~T!`h9y^3*r(wNHp z&waoES78G&z9L%5!lzEkGXBL(Q3?sm&onGN31}B%Oo*|`A-r`0xs9w0Szjz%xe|XX z=%42t)Lni6aS>A8mW*8hk$YllRzf@oWL$mpvlf*IcmC&vpw^2SXN0|XkqWuVlmrU= z)q8T67cV`kSrLYf7>8znTmSQfMjkO7VfQMY@D!|?BmUx62&P|zKv{`XP; z4FVZj0u<=$+XGcSU>L~M(D5Xc?BYM5+&GOO={C{7b3hveBW3aW)>-7Ei!sZS4cVaG zcnewCpogDam~Ng5k732ifI=Dv6xao;BW0|2$RP3CsTPrm98wfmAY*bm$0hFrFRt{% z?Pv;cIew9Mr1D)xgitb-c}y0bw%;!POAX)jbB3o59xZc%T(8zsU=S)n16dN8h)~mt zQxI?FL(y`E^6vX^W1O>lSR7<4gfX^K#!z0><%CYBROfkGDm$~j7 zP>AcxlJps1?D|iT6XZ+?J^BR8WEdGy(q!MS1|cKDN3K`xT0q>j2n!fWdwaTy^Z4K% zI5I5!wjZhkd$!Uz?p&z`A=w2uS^oABFWw@19|yy;pZ0|c89{S2H8jJ3IQe^p~>Zv!PJ(rl|G^?OT!vD%?*|E@h`p;hbXIEj5ZPwHp-#s6dK6w7gvV)eb zg?*FesX*s>PPvfNw<6@yZ|w?RFH|ni<-4e55_CR~nOV?A`KtU&>15^brS9WD#$Is~ z&f5qerl6Jd{s59OHp2b~_+cUKl!(A|kSi4;J4sG<@-hnQ;y=I! z(f9VQt_lx?k>o||6zQ2=_WPUg5xite1;*Dz%2Vxs|8|JN_5bhB)k`Od4$9X12jQKF zlmicb{HlSTMb4;L4S9-xE>jkUcl#ndd$9!Gf*cEW;?s-}oN;BP7VVMI7 zhHH_g>d!?>2khLin7ilUrjg7g{x@aNoX;CUOl~~&Z(oi)*EsbB_;j+%X#98;xCP6g z4irHeWM!pI5#OlUX*=`+exUHrA2~Lsg`BWaXq2?_=2a~57CB&S%(rAv(l0%?oWQQ@ zu>|hC1Asypwy54U^=){MR-%*r3t28bVih2G*BbFz@Y)DQd5V23+OC#4z7-^KrwC|V zCPDhC24JVewBS%)6fzO<9cWe@rI)*qFx=J%GxbUOSG;@?R2ML_{PX`XE~@ZcvwR%_ zFq{95UHEzIY^-<>D6LzyUk>oit?qNV$!G}!#q{cX4BT{GCQxsrxmnH*!aIpHu1oM) zGr=dsv7n6d5fK5_;7&>Zq5yYIw>Gq(!m_gPgV+y zG!b65FDNK*w*J(uej1rX{;rF!FsvNdUg_iU@WgxneGS%nu+Dni4Cnt@)bPfj z0p%fpf4+H}5~~L{zN}i#WC&9#cZDBh1r6VuVkvjt@?H>BoVW_R;xy`#u)UM5V8HJ0 zRKf8))tihP@Bo%xJxO?kLPR4dM!iFNP%NoWz(0fsjzq9$J%9#I@4st^tBWcyTEhgl zci-(GgQ?TP#1n5mvnwY0tuyhzGp7P$%#?pGhyweI5izCuIyxzm(5mN)fN6~Oe@pEN zBfKV>)rc8J+z|cv@#E!4uHyHArH(=si$#~HKHM$d6%8#~&J}_JM}LeVL97w7VZ}wt z)L_G&z=FXaoesOqtB9;8t!cD|*zfa~gdQeEF5Sbcb({b@Q2+B3ykZs8d;ddkC?Sx_tiW9= z2}A}$rkl(Yq`^R^%!@Z8_8CkQY&gBNQ!qo!@LTq5-b8|yp|vS{<@LPY+D z3xKR)>bmP}R>x-k;!nFjG6b0k>3Abqd|Ex1UqY+#Odq8To`{YT9 zqA+)9VWNrUOJZ*_ej;*R9{vUnv(14noEtJ|ijYR|IvqJUICS786)rq&BM*Zin04y6 zEW@;lpKNv}K6AzaqVsjB^CNu!*){WivrM?8~alN1H!O@z!%Sal_srKLOQr_qg|t55`L zvApe)dE+Wxu43mVsgOH2BuDnz3q>SOrELJQkG3zAi%-u1WwA zuQbPBKaC8+)nplPc#)ab+MCo?S$v|5Yz*{0$vbF;7vYVtJ!7i&!M}=(MBbD2-2d3B zMC;m%5A4a|+IPG0LjSGl|5i3H?A+hY`59o6Z!ya-ENDvYwgR}tDT7=V{{c-*Ow27L zBxKuOg4Lx%jxmdptecEXMBD}+I*O}ih%=zS0f2ylOzm%E!PbV$@T0dU0|GqywgHwV znR{%+f-+-)btRnS9S{Gjp%H83yZ#wwU&LNG6L)@b{({m<*8LZuZ zBhrNIMo-j09j5gf7Lc*dt6yTPq>PX1%VK86hi+S#B!T#9u%#D#2Gq9sTkqEE$8` z!8i=-RV+00YeF2J7q7m*N2dCPFZ$~C4jfBk5J-3JFi!BGtiZmSoYt!bV`s{aMWpAG zZT~q#!ez+NIwi&qF_%Otz=WQ5{>V#?#laMlk}65wL_ZB}R2P}Bq@zPjgO!yP+t(@~ zEo50>l4Zb|vhUATaR;4?uszHKJIV^q8j~yctM$-NGi0DmpFIexZiB`es|Y5>82JYH z^M4kd9!q>?CQpf9k$U7ZvW1bcHivQ8x&;df%^LV-@`G*SV3Gcv+<$*6$0&1f*q^8R z4zxCypaqD+i)FaFt4GZvT&ACFxXda_bw#Jr(dd$tuV;U>ApsR~LnlhcWPY2}Bk*{` z_P=>U*>@0}1jfYhUlc^~M-=a6+dF15 zWDQP2kYNJn4W4MFudn~3^3i06$7_bCu`tQ~>Yq+OKZ}5Jj{v$2<+98JL;*2Y zK4?$@|0VUx#BXbG_?iawXGtuc3eUmeOlm@aZ1V&eGqZ4!C1LV#0@R*=RFDd1L&Vru zdy#8nX4d+@1D!vzPHa9Og-A))Kt%hRd0d@Q7tPlmd8Z_$45k8Eh{Oy#KG};t@4-W8Ia_(u2~7~)};h<(*fF^T^${W)zJW}!|pDt?Gm4Nw;%qGxhL^&g|hz7fcb(D+>y13!UTYRl{vk0OiB2s0P^mM)+ zK~kgUTaPI?kt@J;V;f8p8ty<&BsxP$eHf}I$b!b`#>Jk4xhWMOz5V`)Ya3)sO8fvA ze9O;p>gSif5VXHwcUfCnA9}7Q(@u%bIT%4Sa@zHMq4AW8(8L!pJ)(o$XFIBVbpwDn zn}Z$Ica~9UYb$~le0j}2Xk1--3;e)&5HU_>cFup_s z9B&6Gk+Yc?A-$`X!hYmdnCevf-q|npk169D`9zvMeHwAoEe>EhLRpm5|IhKKkZ{6y zA35qH_8~FN6)D+ntqbEW##hD}4KvY#xhBQ11=NWVa77%UWu?0>EjO1%3as94jhM5P z*Z_)-FJDT#yVY_8t`YgxohH(RubIxT$~_5X4&P3bGT0N+Imu|pngo%MA}{PTa7TgB z9F1FGwMcp1A9!mjRZ&?w&Ijh&Svuq*_*DNJAv0o7P^0U9?;N{Y8d>Y$7qB8t9hl@Wj zgw$HD)eCGY0$=Rn7ahqlTqid!{~?eR&85IMIrralR)Yt{vX}V2Ht|{c_~V1^9lMb- zt6hfNnnYy8`ZelKGuWX4wc*+^`w2bEYT(Q=|5}??uJo^~8Il24RYkOY-DL1t7C^!w zFh1Ol_9;1~h-*IVBqd0arYuF$E2*R)n<`m$hf~0{Bd9pC&8)>s6CVcI1g!+340J+ zB(asKE||fnJ-hw|c7>Mn=~n$-m{Vah!OH)22m&#pC>1^&2a%DMmMPeC(#2#cS8PgSmMR*jIax^sz6CIx{oFrAKh?>wZZJITOOq`{~!ym)R5Ef8K2a*J2dh z0|#h6bT3>epyV>FjUW&ytaz*St;ECua1{u}4%Q&Iwdf@R5;w6!&y15jYXCo4fd zLHsS|2-rd+AR}o9M(zCyNO-$XxD9>EE-m|6W~KVc90x+9x-@r=!Bed=!p^*_YMk$u zfem<;6n95c^7pbuejuXQHap=c50Q|(4~whrT}=FBTF8jLO(s?H5k)B_(SgGAdoi{& zU|Up<0Dz@?@=Crc6DN0L$5#DT?GbAKL7*^}NE#J=rB40W0)rMn#k6p;+&zn!~ zir1V#oJ65VKgSQx@*M)}qmvEi0w@SDVy=_uAN?^o=DatR$aM(ViZg>RHEkAQHvOsQ zC(x;2=E%~@YVjToHW)mp?DuyHv0ktgw|K$D2v&wi9|3^5h!!(;ni=W1K0DUQiPOJ2 z)dXVfbw1rJaS}5mm7&i<+RH;;QvZ^E)Snx%pTE{{{1b9sHX_g_O?qZ|cjuGKeD(VI zomH1LkXv3d#{u1Nfz0I01T_&V*a7L{$7}f1U>7VxYIaT**(^NX!uN?~7N(UK8%%>Y zex_E|wG3guJ1Bl8sZR0Tuj>0e6?aLbhsb9mr92Qvg$It7i}>mLG|Lqi&zw^b*LT%R zB2(>cQq}17Va`fh0rabypcWG_v+Z}vAKWBy)2{TB&3?2c2KHoAhX|gf>Vd?r!~C2* z@8GQ4Bud)NqYRI4w4uR6*E0`LGp&GtZgk~gc@({x4~X&Je;NrZNFuGv?W+k|xkX)h z6;hB)R}imCg#{-Tcx(IsUv|C*^`Zg~@U07p&hwpIC9a3Nb4{A6 zs^#*ek^}SyaOOCs8>5Zr^ijFuEL$959|xNuSd^Ba+e)InLdJyEWX3d$z2d;_I{p^d zK{YI|jS;awpQ6@&UV4WTYf8lWp^=pONihVe;nJ}3)c7+*Ts+=p$HCLq#V zdFKE;+7SRS?mw6VB+uxINLk4go{2Kb!||+{W*Me-5fVYpry&p7X$53F1q_qQ38xzg zX-_81OQs*pkk)h~t~|68c+(LScPCnEWW;)hHh+j*iEi4F9-4Zo63fae_JppxXiI*- z^(WEpeJXtrFdB-YPf*T2e(o23fM}r#oX)+i5CR9zUH-yEOV&mWVjC`y?97gCiNmL{ z;aFnz=0Y@lL%}^^L9ySFTYsTnbiM;u4k89z+jnKQl|i5BHE!NF%~{W$Jp>K>0?OH} z)U51%N<4NiIww7zgPF_&cGIk5r=?R7W#cu)y?y|1uK!SvvD=4#_TIDf{L5@s^j zAGW-AnexJ!crUW0UVUOo`rN^SuyN-^u{8Gbn_g2gw(ml|mG95Iyn<>%JfP60#Sp27 zi@|CW5MdL%I*yh*kN3oJbSpnlSNWRVh^Vb+vS(SI#~h3~)_lz~Qke{)x?;sp7g#jj%R~ZVI;XwNz ztSjy0EjWan(fb7kMk%Y*`;94ziP+ct$Cp*bIbM?Q8=qRS`kJ^qtmNw}pGRli=BK!Z z5(}XH6N9%q0eB6+X68ifho=O;-Bw7TJpg2D6g-`A7ts@A&~wWpKE;Tj*#&dKoM~DJ zEggm>e|z_lO&GR;aOrzZDj944&5dYFhur!rTfzk?5CYS}hx=dU&~)N2i7UpQFp+>f zplj*-G}|qhs#j=(iMw=IvQtvde?3Qai}a?OtuVc^fs`zoEDqPFB{43+!p3GK z?T;myy-Vu0A4g$2ow2b#QAvZTM8S#qUHEbTJ4TA_Ik*e{` zW(xlz;aeC#z1(AE8a3(MuiWo6SJk(_G!ib;{p=;FxYL~l|?fr5jH$geB}rc=1XNpvs3jOXr*-?ZCz%qlkgE-C#tKX zdBKHV*wA^z91p0b&J>rX6RIvxKr{(@jI`t>&f?MyKEsMApF5{U7T{#sLt^sgAQPoyyfZ%|1UPA|>3UKyroU%X zO+AhQdMwr!Abc~Z@Zvh+R3@`meOMgo?YcFE9QP0hR}0!bubw%%anr}@XV>HG;ur+>=_nmcvxZs^d(~UPbV%z`RPYfYhUD(Ejz1*~OH!fBMMZ|@(cv=x1DULdChww8ptB!&0LFKW{ zUe@#32QkLpyOz>44`L7I-M->SyN(Kf7&j56BLr`0BAk$`gW=nXLjU_AKAvHgG?`__a zJtW%iH`PO$O-!IyZVE7bXSriIDn3A7d>E=7iU5UBtm+19K1Jf|y#hMSA3@o?`c#t& zd*=D`WOP@#FCzUyD)kUtYMdD@PR(a*A3g#()f+iK|8r!GSaAhus#M0*RKub+J#?uP z-Iddy+3PHsy>QU~%_2QLy}0A$GYlTW_!19!^esjl&@)F6Ic;G9Bti>iS9%>1CL@NH zlB)u8W>4(TI-ju%HqXtVL5Q=lkKA1k@G5r7L3>@C&l6Z@nT_o7v%o+pkdAy3Lo3ic5yqamkMN-&cENbnVtkh(J$n(X0({!T62m|V2*5P{mdoUx$$m*rn?+xrv%JIq^L;RWqU2}#ctqAreJn$$)`B+9hhU0M{7c|nKD zCc9wXYa3iU@Xm1PkRBVYM8_vN&3~{1{3oXwTI}(#McfEL_Ot4x4#%J@ygQM^%6a7 zcD)?|i)_n0JdhL%(#};?y#TS_x^Mm*z9CvOzMD%Li^2HHJ;c>d0x?!&ijp`8$EQ9u zJJ@ca=IbYV36;H6)DlMO5yy5Nb33*&t6on6XsS(=guWlqAl4D}Xq8tcbWCq-V0B8qOP8236-wXH(=$+DhkN9qs zA7aI%mCD{fM>8|xzP!>9=6~1(UCBGW7RBKVlcON;!hQe#eL}Kz>#}nz`KmD-R{C{{ zXQigEd(QqDD4-meBFBe&xf4PjzziBue}H_y0VE$MruEHn(B5u7Sn@bjX_68zYKhx$ z(eeZ)ov_F1{T=Sfxl2I}UM`U>4Kl>KYT7+<5Pf`ce@lQ zx?>Q34Os@vGW~qIRT`yahOa;@wt;rJ7Tf)4&?fM|^ zxR3bj5k8#N^}rM8iCVKF9j$gN6qiu5sC=TBLf>e4DOF6`swRwMUqMBI;xkaa1M{GE`G6lA`$-wWl z(lSqEA>ls5#<@9eh5P4tJKL_jYmCd6W2~h_kU9Vke$unP3?y3Yg0np?{tPy0JFE0u zr!dDc+m%dy>e?L$;^f~LNAf=AN{mXQRW}!1Z;da-Dym&M){!WewLMf?nzxJx9^-3O zDkY1t3V^bCGwIgzn%ZI%JEbH=JmIaI%KMZ57X}SO@X))Dk8AmCL1vQ*P36I+TfS7hE`jiY1mvi7Ec6F?;PuxT_#_pW~1DoKGhv+W|m2S-e%j(HvdA<~Qx*ARj>S+4$2*m(%wAB=^o_Gd5;a%f zhn)HDfSXdgB)~kj^kXnq_6;RXjMtgL3eOr%R0A4&*)Bxp&9YLrcmPi7&dX`}9ec@VU=gUWzs|1vvD<_mHFShGrC$t3Yq32d>JXP5nt0UTJpmaU~utorSJ*s|66 zGUoD8pnEUYw_Mfu?9m0i(atGR*K|IQU~c_xcO=-c?y`D8+Q0Vq+*~zEf0^26Qodde zWJ7gh;>S%5ChPUL=Q3>F=QG8G-R(mQea9LCV(H!Wu!l!Jkz0$2w%Gw$)-?CyZ3=bs zk6MBzbL5M843ZoNhN^ex?$_XFBn->141)V*DKe2{x7LvTFxxN1IzKz>k>9?xp~15e z=mawy&y>Esm~X3-cPq!p$Dvs0QP%uk&ENh`Sh-#mp3c=1u6JV9v$M>6mTy0pZC`WR z*;q?tw;7I;em#6!iAt};ctodsu7}&X+&XWpJN-h1m1-m;SWS&;=Bg(`*6LM1Ryc6! zX9$0CRn*ZG%HO4gv3`qp#FSgY2O}qvB}0R6NNUQ{rQug z)La3ZncPB;hcao?v8#_^T!1+|GZ!dc@{MZy!SkG_h|QZGx>*(k;{S%%LAX zT(2xw?GJ}ebioML4IfoMPsnU;HWH+7LytfAZ#TW%=hAnDEtON~eYm6I>4hKOef>2n)3I_Q#bRWH zL)c6%l>V`D6ED+^cDg&JLnhgF7HyWJuhlt(ZAh3tazJ7=+5Y(O%tYN!mBda+1QZ`m9_NilQWXIIKGtn5 z%SuzCv}I%St7CP+Q$za?j+r(sVKz5TN;!_lGLugh8x{Yt=+8ZC)7u&$K5qWPs;jeN ze>DWM$70(XU5gd2w$EZNCOfz-;VTLE%?Muoc;~WGHA`&Y7|2~{B0ahH_lx@(hJbDK zzBm&kObhx;5*zt46N({M7cL8yS3s0QkfRpe$%K2A$wp%KHP#XIu*P?}CPN$B?bfV} zS&RgXcc2_tv_x{gb2#C(8}Oc>a$=d97%SLO2mNWb0dgD;CubS=8yOi{m5QW&x-EJ) zNA!W#>&-^ZY~Taw@_q1G{qp#U&-#pm`)o`yWckng%%!gs_s)Iwh_sxfJ@{GYQe@U5 zks)?%g29XRJA?PtF-bx!@XNe@)cM4|Ge~|2omR?ZC&r0IVsBXxUmj+!;4zVG z#if%w(i}1t&Ek{o|7?j`eEIDA+38*eYPpBskW94E&n1vy)@a<}mgUzEXm*r218O)1^oX;(oP7$?eJDi4)DPom@B*KevoCSTFpv8cpnzH%e2Non&&< z)84nZ>1&^rYTo~qxViE>^*dxzx!GT=FR|brRWS|W-jo<+JRCC8{c^*j=c8Tm7dJvz zqVnC{`Sa}E>)kGTI}5Koh!1k)%U9}l`k#mS_PwwN=1bZBF0`Wl^cj=oe{qN6V(ZJ9 z=xZAeO9N*!gn1?^9Mwy^)ye^hU1_`I5{333s}Mh~2hR}7Ek+>KQht2E-d-XZ?zwDz zlHX^$=Vt7qO=_j1<%pkrvEEM*b{zqExC^X6XDO_p3HpFMJg_lNF{KIuvT-QjGP^4= z2`=IN@SfXB^!Jj8VuQ)AFXf`k*tJf7Lh>5-?yx7`S9wmD z9y`oPb`pEJfZN$~R*cRE5;xWHm&}*NP9T{aF|6jy>6zcQo;wjO`Sq%dJ}9gBeaK80 z-I#|McNAb$Gk`@NKRE`HpsU}wyJ5=j-_B(2AJ1Ld4jY7~es9FKYWrx6(`>euR-&8c zaZAIc;`i@M66)5tGDTa>n|;418fsg7{KAN?ZIqYuSy(G%t6@sSs_r&t( z#jmg^=e@tpWZNf#fB8E1>_RmoM zC`;V3)02_x#`af7ONF+=&gP*G!|_WqIB~a5H|L$;-S>K)(3sLVGU3_;|!Y+6GNVJJg&-FN3PQPwDUwpdeBppR4A)%)}tLOUR(XvY4WM3LX z3ff;yQ`0pjDp;!siis2%O6fJFY!}ba7|cyIrAV3w{~mgGX`+eE=}|&jrDtOv?SW?+ z+lU@7prHqkw=Q5a$niGMokNWte_p~y)|K}V-@`Ua;T*fNxvOg&T00qJH?=>7)G`pYK1t+WSpBo5~1DD-Ozg%8L9CxmUJ^ zc=nqJdHh!Is~T2xSHRc(A}k!wjrd*h(5m2gCm^NTJmmM^KQ{UkHDj^tSx&ocoD;5z zhW5WcIy$7cgo&T5w-~Q#xanKtUK|&#lK5Ow{ECKa6ty*&&8y($=t3ane3=hxDn9q{+`$DS;Ul&xn$4HCd)Yx=Kt7&}V$>=u{LL z2mZcR3FUY*edS}L=C&MT4)MXQA)|Tp>h?(a@%mGyqBEqTjqYk`Q~o;UXOB7(QUW%< zq<~YHGT6{S+2^@Mj{hVwZu)6uRDWsoqoVYuOmV|ip`j{*YToX-`D~9XJ70S}!racp zN_pouUcNEV|I?>81Z_on(K0zh*iBl~XZ>^Lw9IGDNAKTsNY9?_O1m-GpRZn?^v*Em z7UL(F=+zjJXY4o^qVLmd86t+it-o@Ys8Du6ZsTBdmRjs2-jd|v`M#_d{&ocS71n7L z4xzdzx`-NZy&8v!>Orgbc!;fw%Gz^k=|49ff-A~TB*;_-$0SRau4WvPGn&qT#wV(L zZr7nea-MU=$a^vG#up?7(hJyyQq;$zZ>8u8+e;A4XitH5OjC_Z-j8Er!jrz~U-S=O*uXEn; zj%ffyWK&1FNjn>OMzdDm-u*8s5gG#qcUbGnBQ`_xoFz@0re z_LpCZaP={b{q@@9b^(gO3hx`EvCp$K1MmJw%(o7@I0;p%(!8R;ggKviI4~@9`uNSO z=eiO>vLdL35=Arnx?}H_nw?uo#NltB5M6SrCVSwCh23a_s@|( zA|$%4*RcJ|*e!XpZ+}*P@@SbUsX!uzZUEFHDuEZ#g6YkfE}ho|#>6s2-5zXHlzA%9 z4M^=Foj(G%0XN#P2Bc#q=1&xGJOa6y7mU(S!sVDBx7J=J+A zTk0mChp-vXk%26B)7=G=!YxdvbrVh5uv)h8ceUT^beDs-yY*>kR?zxs^m2dCm}S2Y6=lftmULjoK-BXS;3#7u~;Pr=YP-stnH=3 z6anX#d5**Huieqe*Wg{v;=fFN#5TooO?ch5|u7`N;+X{8K0A{COAn_ciP^Gdlsr%Kc)Ci&-+w{1cL zf=eNmc==PEo_3Jd7q%#?>_!r;#?ru^%Y~tQbekM+oPGBxQhO3Y>}$XHjiu|SpUBsA zx50Z}#V3*t>?vu5IKNx>YIrDSxoX$PFw}j&okx8UIka=8Bgc)_?_yXVpEsZXa#H}m zGWyBoYO!>D{Jt*d5p3!)D-5I{+@EA`tOh?j$6gbkjuh<(`|Y`k1KIdF zZVigbx=D;s($@Kkp&?N+XtY1%pJEs!M}PY7J8*Gxe}F7d$pdU0AtK}8KKNkQUKFaj zo{RWHs@$DrCTo4Ry{0#rfK;OIXfykG7#a*WbUgrf9`aP>P)0Wbl(C7A5HnOvD3qZ( zUmp!RiTXm3H7{D2?p#i|>0g5=YA^Tc5HI&}=Tp<3wOQnfC^0eX*>;=JB7O1ClJE4( zC2BS`cse_S^brDnv*)Ar_#A#IgE(TW%({F9lf=vBNBR}^>0Qs{ZSicXDa+c;6WPv4 zsrA0O(m^?aOw~n)UmXc;n`DQJxGGIY@IC8VIw;NPC zIs_4tx<`4VrBFxTlrDc{i4|==eU{b~$VYDCCmUsZC2}T%NN&7Y(Ai`AfH7?ThWQTi4`q`|r)HS|)4E=>Qc)B*w;=X=z8v75 zbF{b}a|E++=!mx$-%#dh>)fmX4nBjoS*X-jY5*v=r$y5Tpk*YPyFGb)G-T|*YlX0C?15FiYSX&oa(UYS!i5Vr!*5aGp`Ghf zNK<~rUsx>o-tQjTqfpIbl4Tu4XoI-;b$6z=ILwUw%@~SL^|5E?LdrtiV$Bxtk};0_ zUrKvh%J_NJDfWZ&c7e=fVkpl&v7|8EeAE~v9tx7(#|}LA$&WEuClR6pC(6N1^H}7~ zv+$6?gKWVcC?OdFz;DIT>hB%aeK>@@xm10dRM7|^oq>#p9+SrZk1U$`iSYMtWhx{cqxH5Ha!kaZPxn~lu=)XbZUzw@uP2GPt0%*KdNhn$<9 z-l;tdKL%QO^21`Ze?hpbY%L{3=cGxeN28kBxq9Ueu!Iawg!YdBi${Xf6@+EBzS%mz z=~MgQ8#9gj>=?|XM4bGm+&^rmvp=7j`KkL4f$Q_4G;mT~(B^;v_^YV}MU!Fs zh*}dHcSLTHob<_&`nAEnNg5M>dChbU@~*#XkBs5l4>P}II?FLCU;{u5M4V4DfNWr( zSrp1$j2@25*uEh8B3qTXb^4#&0Zs92$Zu|w`f>Wko6BpT=CgYCd)L4p_P2%J2)TSAVU&z3 zhV@A!U&H_;m%(#3;oHEw4ENeuZi-HSnqS5*eqVtsE)pXbOX=#K&h!PBNU@SgC+Ab1UK(%8 zZMVB^v*0l?MTUSys3dp^yz*Zlfj|5BFAT?K~SHG|DYvZfPB;qSsnpE1^ zWPf~M)+@0Yed&tvO*taW)-HtDT|H!~og`->1^5VB)B{op*~0*vyq(WOA#INa)wE ztJ7yE;qhle!6f2Q5_PgliU(hoIG#>WRk&PXH#qsG}h+N*xpCEIN;6DA} zJTX&SsW!^9hCC9ZSn_+qxBT}80{RhYe4-FS=N*r;o0{7=Nk^vlW?Vu$Ohawdo9LJpOIB)X)1Cenj z`Gg%3EMOo;H6Z>o-uqf3bBP2s9l_pp;!|Lq93#e)?AkH+1 zpP^$5QHr_WG;`AiC-UmIQtnmbnqP`_W{LD}^h!&U8#ldZ*D}Vgm9WV9NK9)E8kf9f zKjYi%SF4o`r#GQPA@6x?Jd9gdu(}8o>ZP%LXl%dqPcAJdjA7}P3#HCA{{zp|C#2X0 zV1rz`W*zNAid&_GeBBY?edS6FUacX&5bkKtE2VL%ux-S%sHB}D6~LcIH?_psOxhvT zQ8OtZe?}J4`t}|TP;qhlZCI-6f9%xOeOH#09pEngv@wnzn_cGq&dP21!7fCQyXw#M zni7<*Ow8-&qU)vO>E^cN{K$5nf6XB$UbwgN`H$v8_A^Xyh|JkD2S^^;FV0)s#8>*U z;|fw<*wxpy7q2`bf67EVbkDy%bx_!d4@D)*m~%?1v=V`{_BU?6NW7Fr%5?FvTILNVW=~V6M(;S=&{p4EjWS1O+MjAPo`K>AZ;zU0{#uf#YbQY|V38LPDIOeBo|u zSH4u2ETi}sHXknWNWK@6lNq#L9J z6+vnUX=Dgtkb2kr@B4X<GxtLa9ZgIoLV8>_&-wf6w?WUizL2+8ERfmKtOOhV)+e=6){e{VCBJ{AY{ z!};MpYY%Px4mdNtR$5-Z-KR+nc|eUtO)65pxHr8un8LvX&d`vKw+qA}+~+>%PL%JY z7I$-@9}w)9y}f;Y=erU7IN3g&fJByD!ob;mrnZmhT#>ha|6B^#zqmG~fyr+^h!c(Z zm_o@%2yB(bT0BZP? ztB7A74VB;({j;}ozXFs+|GT#4%s#Ro+gg8=Y;-EuO~GCR$L%#?pZ@{3B99cx9**Tg z;mAt)QWM(iLwG7x`+JseleinckeZXdrTyRK=>XlOv2X=ofE@@+zEwMV`f|fVfmn~8 z`5YLu7V>?FKdDU?EQVaobD@hKRa(qk6z`vf6Bc3v=w@EH}yC_u-*P!VNc;K68tf!vMZ7Z4CXxD~E1AWP`gHudS5@ybrFambr`y6Ad&uD=~X z&(Q&;J0 z!M`(%={HMHWj|`A-FCwZRlEf0l*M__zJO1${X|TuJlCi>a5&d^S0hioWH*Y!OR{K*J( z)tQBb?jW=;XuJHkt#m|jp*`*8aOMddctVz`&#f(Q?lHKxPPAi}iGkG5i}2-OG@7Y^ zeT;&PFzNzGuIzobzI)z*BI~$3gw5-b(`Qq#Q22?r^JX+hqQ@O*t4NsQv1(!Bi+CTf zEWZoc#E_V%afx1eI#p+Xz5Ow;5A{-gcytYQgB(jGa?c01fOz01l@tZzI@iC#OA2;d zO=z&CtQ-!D-`^QN9s%f`ryUZ|VBj_0iJ;W021#7gjn0R>ay!*Md5YyP_C7W)Y&D3) z+?fMMdA`ZOnUXI-gOtoG)dG8c^oqCehtx!CE>Lub&;w#|92m5P(e&PAm4 z6fOqZo)c~F8(gKV~R|-J~lQs6G~mcBhxaj6A=PZyttbdi2Yv5N-^nfP&y+^O0*gJ3Oa!}10)Zy z6DQY0Sjd2aH1{z*|NHMLQ}020M(Cq)kunXCpw9qT$Xm+wQj!m5pWYuja$w*sz4H$y z68VC`rQl033VlY05nJSO@D51Vj4MgYt%Ld$zM?-_HsJ=LDAU`fmnTLZf{}Nykedx3 zAGb`&7xv*@{l1@U>^Xjj4upgN!J1GwWDZGi31aq-fP0Ly@6tTlI{H^0npD*(>_$Uk zB2d#*Oi;@`ExP%66@yo^`_j^3a#Y3lQZt_4tasJ10d99ZDO ztawh@*TH(BxZ(&<8u0S5_UND|#s>#IJb$BZJO*k!HIQjIOQHNrx%KikmjA%9y>+!F zibq~w`;F0c$wf)Pyta;~5>Lq-{vRXR^>}AS51r@%Hx>(LJapi}uz1U!?RBgM!bI=B%hIV39Tf$Ln033CRRx#<0M1 zYoV7DgzmYAu`3bD^7M3?AlBkxSuT93(mlLnb?(g}sqsk3NDFOD?(f47+1F=%LVzfE zv8*HSxhb20y9XC=a#7M`s;Im@?CXmO055;aT3kZ-yY#i=n=|de!#$?0ulW_IWAonj z$0c;`O}vge3-N{WoQ!rEEn`iur5jmrP*mA?qV#D=2_1=^fyC7COKwjMEaZ~Bf8`Ev zTY~yO}CP|)@AY-LWzje6d{G4b*y0zfR~uv z=5s+dCpfq$?MjBGRGK&vBw`B&3Xk$5E+;gfqlvD1X?>DkgY=yuT?lw;c=T|~D0&16 zyLE9?eYuGqYDZR}h%#olTlF=PCS1SL+;*1*7?=GzkAa18rY}6nuVDRwAQ_nyp*0v{ zY73agjX>bC-u1@$I-7i104AKbQprV5bVq=`dqNHs@`bm&Np7Zh@`Wrj_(bMm)S^dc zI1gG+&m|PG|1a+jtHgkIGu=(|zwxW!@fS`X>hV1m{wqX8h&isKMRyc%pv3iIA?~PT z4^?neZujo6!lS8iuW@f8rACqlUCI}fN!4dd-1iw$yY8V7mLurY?E?xpRga+v%4qU8 zFc{liu&SH{#rh+>=HKn#uuT>8G@_KB`x`y!yQ4f*Lb<`Ef;OZ|YeE{>)n2;`!k>A; z_5h4&Me;xai7NQ~edq#AdcT^?`A2$7mn$BVj!|NTAWeyaFN7VX%!J$j#o)xl`wCGG zOn76oi(i>;@Gk8;5oU7Y?}$ypqE3H@AUb~qN*e5tePEabBgu8scL2U& zIs>OGfFVjr8#Qtjh3as^JpcN@HqhqTAFe0Fj1NhG#<1nR$a2J#*Y+A?<)^vb7!5BeN^qIBcZgw#B{VovX1&kp! z6g`B(gDMS$prNDvUC~8;ofs_d?Gp+Oa$gmWS#$7r;y-{owc1HdH2RwO)f=34*9v^l zE6>{PKmG?E03tuWhn#q|5p3t63Zmn5oi{D!ars^8Vo?tDc#o^ZD5j+jAj`;@pSuQO zahH4aZOHZ|Id{d&8(l}?o$Kdaz&2n}h!blJ$g+Sn{A6xIf{CIn)Ngh9s?q(nK{QbB%vDC2SNzFu0|vDyZGw2~{&2l$l7!d=*wylra6dZxYp8J>zkfG}e*$F2PyEuF zDrn;mMx!%I)NIsjxcu?1HaWPeC?_qSk<)NWeYpvT#eI2Xg}`0Ueflq&a1o+&Rx5}Y z5r)mIHA4*@ctSC*wX=#N(b(^O2AHfOXM&F)xX zvYGu0Y8&30gJ7rs-O6Q8U!*vNN63i2X;#Cv9e|lh48m!io{F|I_o@N8+_S9455QuJ zp`?Qk-Wh4&v!q2}hkonCbDNizD%zc;uNQ!E5N^*hU&{AB3M-bw8KWl*NOl8>4<%%#k;xhw$M5Q$V}_O-AXfk zaWt|i3Yfa_of3ts=T~}0h1O)p?3h!z6S;q306~|Ggr*vyDRVXdSl4p09s|j}J-CSS ze4rm!TcCoT0JZd=T0YiVt&Dy`;hLykGxnT!=zD#t`slPEYrwz}x_JPC@&;g3rDifY zu&i2!T!AFjFD?#PXU#zJ_1F{MDh%`fXEPy!i^fH4!aUI*!1gH#kfO!}egIwaCz*!I zKBmW>_|@5ID0hc9$aO6kZ^x1NGMK=sUt#Fg?gJ9_My0}LXIc}Sf^$}~m z1(9}(-do%)O{?g)f)tA60&o3f(4Q6i1KX<^I-Yh00aTSSj*$lTRiHJ>YXV5vhCt%M z_)z~pKs0wI8&*hm-;3l-vh}xrC!m0RE;%EgEa=og))5RB0vBv!uT%vmYFyd&zp!Q{ zU`ZvbaH z`tvs&H!4O2T=``mjn$5_2hY0hYdNU}c!X#|Jbtex4tSh3bNeVVTw^p>%I|6n#VN-F z{1|iB$qSA*&GxL?3Zc4NU4XO?^gxfW3eI z6VQQYY=_N90#8?qC%T5)BKm!T=ZwORw+AIt;?G4s?abjqMEH+wKT_V|c zh&Gp)p$L`&e>5+3*m^{d;dvX7<=tnxX;JgKGwRNhVHn$Ikjm$Wa{IE7bvTikH^7hw z65eESp}3aU{P5qrI~aNrnAFy6qx%E4tlogOJE^L-K>#yY_n}4x^7|I{k|(t?dk=ps zzUJE*X#2Hb^eFD2=hrK!6Xxw0H6{442TYrhezltDmhgGZfdm*%Y00jdL2x}N%_zN> zqGlSa=Q>kcb^%=RUQP?JNYZ+d_us3F%aEsKWiAUATOJJ;J`3z$Bma0;rBhd~BWqrK zlEv5D#a~EDHIvvS`q>l~p!XzxWoaGcZ!u4}3kt|&T9@rp*`6i*giY`cg#*YvUI$2~ zPi$~S{Lioa5LoBlkwcq4abUXJY29V)E~|-Vr3~}4&I$qWjh`gK+C}6{lW#oV#@=UG z79hGmC1DWwl2wB(c-`A!(Z1kunfT_Kh!2PwqVDi+YEZKxAYX z2P8(DRT;8K^D1jAdyjSOIo$HD>n{mEqUPfn!T#Ctu{-NuH1~COX;L40jHl@%Hrt6+ zTw&+9LZaNo&OKl;8fbpmx!#fmrP@g+d}_*c*W}kow$c)jy1%;N{gA7WD`QR!?+XxY ze3$4SQ3JPa=Xkx_1?G7U`uDHnCV=S{JRq`Q_VLR8HJl62)4E1G!Kt8rJYpkxKOy}q z%0M31vllvJ_=!GemnCcYhGRyu=N($XkiH38KXfXC$KpL-CLB6(#-KZe#{_*F<((+5 zaGZ-qk)j6=Xt?Q*SusTXzXRj0PN=Xb1Jx5SU&6T&HShALN3Yv;lO+(Q|9hrycYi@> zFOR7KIy;TW-AhA;fLs9gv=jrAKXvsIL>(%ulxkFFQ~H? z=xqvZ?~s@&U20$Z^-TNI=1)QMSSX^E3!|+@ZAkN9;XZwyR@sF|Wh_dDg~xuSd8c;x zOPi~iul93rj^p4f&jUU*-e3iSb?%5;HroE&>y6|q_Wm4sx9`7q0zKXA$5wLRBVKt) zp8!L>58GYNH;U-A8Pahepl-{bc5r&d^i|I87>dD)1qY@8VIlF(}Pl#;RX z{+IAxq+iY9ZkgY)@^eU8bibtUr+2jRD#XRQj&cTihGf?R2OWP_n2KllhtZ4d7-3(2 zvp#uJo^SLJpZgVEzj{9syA;*3XX)M;zGwT78K5og2K+UhQQrjr4zPUGJ^3S4pP6%a zI#wf3oO~x9qeIP!OsYFe+7-AP%YDk@W>wTol72gvDTpG{n&Cp6+aqif2NU@G1Q2kE zb8Q!hCN^j4oJ9Pe>yqFKAjLzlAGZ+oShdpqF7>s7`uh6&EahjatU{oEG|(q8GWB14J(-H93y_Z!dT#KY}+EWL*M7l&=jx zOfN!?E{HrX+N3>=yn9^!Bu}%}GG>5F65S5JC}S3Cy_4kTP0Q#!quZjjO2X?zbX7KnEA74lsS^7BI^ zky|hSZ6*ued5^t;^o6$8&k1~`m6arXidw6!mzas5s_TJp`@Y~gk3pa0NJ;vJPEXhb ze_zH$bjZ=H3XpwZ!N1!rBO|!MJ0Vd3rhx2g}u95sFo@NG(l*g8aU9RQ2 z=S7!$I@7?%yUeTDB4!@6gKEc|0-M>sMcuVd72QE{OPf5yJeJq$!(b<0XM8CA(Zn8l z53ajtIZ7*BR-fgV!xS;OLv+%D&tc$N_u)Znjfah>M9AfN2@4x$|1uEBcA4`CN>g5i z*9Gj{{c2(_DkVXEhxtU&+te(_h1%i75ef$H?sZQ1NxD5a^({YMV z8N99I>HobGdNMIwhVALOd1)Ze!7gL69Z(z>h-o8dYHjfkhF|#)2OxqBR8yM%DJGQO zikS3JSY}buxh{QKNI<}B7r{-y_+<4^qk>u=&ZLRv2n+wjZ6VwR9FX?*GYq1(5rLe{ z=X>pEW0Cw_>i`ENbbwx&aR+!p^ErP=;Za!x)<2HzQrnq|bm6?XhH~YHC`=-&tZc}U z21P)yWOCHQULqG&*3(y0Qm_)(!1k(VjLN~u^j}7&FtOlc9FbQX9C>z*eD<29{~3l| zBSw>TN%ik8*g=I&pF+xB9NS`Im^60a_f&Fpl-0lkBB7bkrG<%}MZc(2s`t81GA>IO z0Lw3i$d1bbJjDDsynQ;dXdN__A~vj9X3){)+!h36a%@sK{72k_lY>+5T|jzqc=%V1 zJWZyuiLh|}dhI~2P*XMWSAhBP8fUcwe-gNl9ALryqqdPA4E-7;h=vE9seKeA@}MMf zeZ)C};z8OvNbX6Tjlp+#TUT=CR9dzZ5?6;mGzad+^H}BlT6|ye;BCMqM1zEhaQvQe zKQD^BV-Z76xqJY^B$*^>Q%5;C1J4ifJC@{=bNcp_mJpeU^GuR?qCDU14d9-7VDDc_PbR-(*-RFcN z#yO~2ier=283}6#0vY~G{B_xTqwjN4voJC`mWMIH=y^XZsmRIhW1|Br9hQ20{Qm)= zj7pYoE&&NK)82V#2SQtW_Az)B#cqp|<$bi?&lu5#C{Q~obP}>I5a^YqO~y4LZYTz9 zR!7HhAd*S&vn;$~hAHm1wt=}oZf9Rk&{}Hj(dqKQr5%*w$DO{PB1M;^T0woA$U%q7 zhb-z1HkG}BLZ7TArSAi;ak^{GR24M1Vl;Y(NEXl*f~R+*_X%GR2FlE7Gs+N}KZ$5d z?kQBeX6acwtNIxyLw`U6Wat{7W#tucU9ujiSVa;M22b;x)!Z^#A4&;XjPpEnDbggA zc?J|n51W36hwnC+ICuRDk1DN~)?D28Fo|{5ZT#`-q z1JkBM`#aEUd0r4z=P?X%wI)SL%W)i)4q2;UP?g}lKqGqm3po+MJP>Eukx1D}^xFmvcFG0{ zu*+|M(!@DT;n}vf7pHD2=adD?DU= z8T|ax6751c6nl;HB{?eT?OKNhlTUiwJijhFP=Wg|O;jL~01=fOjkk)?0RoG;ZoECI z*$@k$d$LQbxVH7dL3N_9q@F@g1(tet! zV8Ek)E!o#w7q}2CoTQL%$Vr^?C-NV89rN4M8-0xn$Zx=gA(w@?6-6)e?}sgJ-xL_r z4kc5J5E4BG*bY_UNCa@~@ZJywdD{ssl#b;FY)LWU;G4g_7RMS@@p8I?aw7g6)Qz7P)uLPX@)SX3d&wf%R8q=vRMIo9}~O<>7r zs26-P*^aVW1AIVdMyW4JKR!F|C$A?@@3gE*xfS`Z8dgL_6U$iO;1~M%EcoJ|j7FZ> z4(_yWlM&@#C$ui2MTRh+13=eE!10rZ>#s$kPy9lnF%Z_$Hd`u-C=vBJzPLzIl zCb)Hc`$3d_ixM_)c~M(ziT@T0z&KIum59(L^mHKC4lwYxA?kBNiKGCE?DCe0bNJ2qd8GOD5u&1s)>R+hic42>vrY~ z6QsPo;W7?OHrM=P@RI%h*j}TxbZTXahmqINO6Y-)_346sjt<|4+T6eq(SauMrxAs% zplj9jJWb*!_C}wqTgQA*)_At~K##a91U$_%NGB}b;LGnz3@{Lc8;sRAsZP7xxRNlv(IG{7U`Jw?~(bD zZQ>^cMeXvdEPWQ6leVbIpp#B{BvQaIOJ-1pb4piZeyCOxrLO&D{Sx){};5sKp zJNOE8J>uf1n_ZkE8eb&bXuHM2k*3cR#{q^0Tr6;VJAMWw-}Hprh(d+lEy{BJOeUki zbGG(GG7+PB%YT|ivjEpP)7SIBAo?a(Nz5mXo_NqcnkEd(qLSy{lCd8amig~Gj#;aT zCWuzMgog?M0jzzHL4S&R8NVjOmS#u5<7--QjLhl|gqSkc`>S;5u#sKStB^kq11J*p zzFY-_1tG&fThznJMAf2hPQWg@69^IInH?}bumn3|t9PLU2iXHO;g>J{dIF0ox4M=w z+>0O!dclm0W$`2}yufwJyc_OY-ylO2I8XAe(5}di05slu;m9*&A?6}fCA#&{GKHT) z2>_HPWSabME^=G43DiabEY+Ycf5x*@x1)AhA}2OO2GYRk&9&2o#AA>)xZ97cwB$S= zwKTH{ImYBEBC!Fif!KheJxVK7i1nwZLO&#ry9pMgzk39yRCf<=W!3t8aG3MSDigGD z&dhll0Ce_l9l=6S8K5>3sCz7{9k?ey+_J@|eSuM_o11$qhm}!@GVAX|;WWL!b+T;< zxbv(x0qr1L{)NeLI`1DRUWH5Nfcej+Zb{6E4qLSjnE}89@$ZMRk;k-6rufrzs=T0v zveEo!*+600^{(7(OLou1tq_svIWC{M`1#Xs5*D}2LU+20GxL9G`et8PLQEFQvdqS& zYXCWQtsocSTj3B7Od<*w_5?sp@+X|k?6P^?>|X>?X21YbrnqgySu>!l%AD^AKL*~$ z0=j7ZC&!%s^S)>u5)`9i6~k~J*w>gJ)t9$HluMSL;R>dVx%p*}crk9Rd+?M@Q(`0R zYG21ovxr!S&X%cp0E5;CHlX%bHxQTMDYGv?4eg z@TtMr74xws&&-2)zf4i#jMkrNpBMZ0w=V2;;x9GyIH<78&QOFgNf zg2Ao2JDy~Ae}5oT(!Ia|j02c4EH_(oZ8YubxN;?+v9R7$iTZV%z#wRMab&m7{vjHjk|v)yR25Az@_zr07UaUj)3Mg4w!fIb?~cbd*4`7WF8RO2<82F z2C_a&)9SC>aa6!t2=&NU2y9XyFH}5Eci4)x+LroYWmCM{uo#>fvX-4GAJT5Bgen8F zU@GS4$&@`ATT?y-2fJ3tC1%rRBSHQOGanjM$HjKl{9jXJE7vE&t=6-_|Av55#T#^= z5tauea_hj2BfIcoSP(Kz$20O}d9@1Y<=V=|bAH>NHnV_VKHC%AUXGjZE-rF=JpAh!EfRe*7(uVQF&p* z$XNb7`%9+SBMs)7k(RxNoo4UHESEbrG#spjb>qj+$d0+Z9q$sIADs7ZS%R4T1}@^W z+4$jEkIPB$9UNZ2mvwrVBjx@_9o3l69uH6-6(0VhPhdUS!e0CpxTqPh z*dSd9>UI|c!)@xb#RVe+4-e+LyNS*tiSs)b8v^Pc-951wcAuLTgwpYyXCF+kK~BX{c{9o_`pmL057%@R_Ue$bq8>iyo{nJL$C=* zQ1Fqn`~g7Chd{iu7HpJC$bw4xc@7Hp4L}h=zaE3BU!mewT?29kzx|8eQ$h~R%!9X< zZJ%2d?yuz0H&DD+3A+JB46%?O_+w%|Xfzo4Kf#15Ttbjf=k$X{_c5l-Q_#ew(z2M+p8+(-*KCBzGD= z{_#Q3b#pSG`J9MSph}lPn?<}|?COwZy=xs2a_n)Njg_VK3aC|PRr?mhLHEmj%)SO7 z#f|U3slG3u%Ib-8-GEonyvd_DuIr>W4xKRYdXFJopWfbf_npBQo_Va;3pN1ZX+4a# zk5x?33B#`{y)6}{ZP;1pdetLJ{P!6S! zy_vm$nG-gt?(ExGIVH0v6V_%$pmz2x>lICBlmqC*WuI3cITiXuJu;MZ_l+`>3C;QW z>BFH5a57~r=X6{+()C1{cfmDw=))J^BplW|fqu(rdBM{eVj61DTU+hYKRtCC{=CIE z+_JCkXXThBl!>$A?edy8K#$OM*um9mP_#Gl_`;isa84#Bvpr#R9b+8cnx5$UOT*}w zCmX%(duytqrk=*?Ib^X9z35lSV7Eo4&{XKzIaMgncuv~O9dwQbRdhXIACi<_I^QtQ#S!4>Uzb2c=WY^F>0w+g!fPwuLootBG+KM)Q>42khew zJ{6x-xAk*{#qtH${P~$@RYCI4b}w2B$4~QyEw>-<{cGFaxd<#gIXN$gUWsuM65C|t z!e&e4z7UR8q-HCEp9|#dxU~I{CTh>+XIMQ4-ZiR^vYY@>c>J=vRS7o=rhbhB0a@r^ z-0|}ro-ye%L9^)Z<-a~Q=Hz8rie*T;OLFPv<~(j(czR4iOP;#nVMQKdXvVj)c83#@ z(jUIq!GKsUp?o5@ucLlF|20&hYv?&OQOp$8!hS2>U2{1^(?sRUZ=VHpb#J|qeQUNQ z(8w;&Y?s*>$2iFkJuu>bG9GtC&qCfRGphNzo)bwD5VKsd$@FUE{h@9*X}4GX4j zhN(BIj?>hY8`rM`kd_3{AeX9kbPF1uvi7;9Ytju&tFOkrrP6X&Esb`5WPj+LLe7Py zIU^es#=52_$&_W>QJh`GW z!B7)-HLlm@!+86@HJ0RXVUsm0hQPouHup@`$1Zo~o$WY8p8}7~85}vrzjp_QDCnP) zMGQN0v#nZhJlF7OsWi6{yWzDYqXT_gG7H>Tk!ZU+QUGl|@$N1IF#|HECc|!tW7v4F5mgpWEe(D2br@5A8R|L*ENJWO3s{6HblnYGJ zcCHlFB$?}vSHPAnI``ie_v6t}}FznULtZ@r~f6)y}r_(Cal%A2knP zY>j`zk$)oR3D**A`Ig|whLnAG#}*ngM-jC2cB9UO_^gM6briayZY)(njlY#@bMtb3 zFPNMDQOO}WveJDPoRqx`+1n+;zY9vhZ@12TQsjS@J3q?$F20Y{eD&E>Map{i%FO5M zlsCRqkLoHh(62Aq=q^e6TF(`jD$o!;2rFDZG1_fkhDRSBeq1Ij_y_0d(wFZaY7L_} zw=dcJSXGg+!J5SSt>C>sxhi{Qve^6KiDVN+NJ?bA+q~KP`4YV6{1n{*x9U548bKca zGB)(#G{?PdmnEoz85=o_*5GEWeNkb=h;sjglz}Nw4!<+tgwo)xvL>6r?uwD+L7R-J z=~Ta%!jd^3F?U7va^!Zg$UKG?-V1h?OW9P6IAtO+uC(Bdp!OP*n_i>tYRn+bhq5#i zw?H@4X5Muwg{e_+vUnwQh!cy^`-|?~>My#-K}tul0wFW^nI7(-9#Fw}riZ%N%{wxf zf0-VtIYT{S;=GHL;+6|b3XvFh1d+T$-gIwGGQR9sq3VP5`4+BI5>ml&u@-T;Iyr{c z{z|-r_sIg%%r7?BPUm;C|Ni%tSQeq&n&zu8>yonqKre|Ngg5_|V{cbInn0^SHnaL* zDbGtTlFuJfBCB7@Ex%9E;B#F>lC)c;wd7)05N_C5Ih zu)Qa?Ml76T@}jUEFtdv|I$soK3YcJseoo%9zmQ-`e45$~Ll}p)FKIo(OW)sF*|*bn;he^9m@77U_iF zP8+McsvTP!IntgPduz6KW$*!O3imU5g(mb4*~61l3e8lNoOWt)8Jfa=t{HjekeKROTyK<&9>`xxSkv@rqH zG{Tkwnf|wmdEK+Rce8b;F1YJhxUfU2hGz#{kb9UQ89}#joh_eR?x3Zc;sABr(g9qU!R$mEo;GwbM`Pgt0%k&)zTjf*}SCmIfuA zx&eEq%7^k3?%=+M`Pe8ddqef9XN!`uY6&NlSzZbrTym)TG$vl;3!{|RM2KhH-YbcPZ@uo+ZzDfcw#%O_* zpjlR|#cNPqJJYK5S2vNXL8ElIf0^Xfc+@`P+)Qfb^rTN^jzQOTsyM0=`L5-x$F9e% zXXO!>8V?R&LLK2<7kBe>OjKD>Pr*npi`g|w{~$-ow!MdWRZs=0w@(Y(m03xli<{7a zK*B$kzTC4h1VYZx>&bNo1wCWjd*)OQe;Hj1lHVT4^sjRiav_Bdn)Ox=3r_SAi?E~$%2BJZQ@8Pomx4nqPzWF(S8F_%lR%4)h3v2x&wL8a#+~+m%U~sZ?grSYeQ;ZR?wN;LJs=2z0EKigx;VwA7k73xph{Cd%a_zO2+-xi{{O{(G4F ziUO%Ho|q`e3&X~v4;RJ*$?+&UD3K)8gAV{+%>L;y7_`96^^xZFDpKfrc+;$clr8 zIBd*`q)-7migjTvOdKagTm`uIA!y%K1YJ2)iteK2b52YV{7(9>tZ%+Jb@1L|i+c5C zIE&%^z$7a+1Pogr@lQuz#VX$QyUq8}j4Q+Miu-)03GzA@?U-in_Y4 z(_yoj^aekX>R=lE`39D)2~DG#%yX?RemQ5NXBlN%w#WW7q@le9678gCYR-n8peH@2 zNExuL&I|Clw9^d0@nQ8O3zTWxJ!cM$qJ!yZqv+Y3U zd6l!|S6oHef{Z;eesYSNO@B%d@bM6muD9a3w`ZWpm$<>3RmMAm_mwnB(O$UvyS{PGJ57O0lI8kb{=c*_Y@9tjP?deirwhdd<$Sw(hb_v!cqIeP zVOUm45>!K5(wxAZa`xSu3Z)4`p`T7F@cU3mOen>LE56c%BL!+r=c><8)K?$U4z3V#yG(0p} zLQg%d7vrdeKl=$o!B7%`Fd2o%g5Xigkw_Z#JEytq`ilr%qjb{EJ%AxjM#Et7Z<(uf z;cqpe>1bole=l6s6yebfAPezSDd)aUfRZLc>flRn^a$_Rd+@6;--8AM=RW&p40YGKG=mJcOcp}(y04ko z>jaISu@W}AQHpb*oSGQ{=)qJcPk$A?m3VX=Ll^tY?d6C4SQlb$?8aZuO{E;W`NdRG zqg@qvG!3|@vt+y6qEK&)hTAOm=*Yck)k0@6Psnh{*hrYen3_BUJ2Di##p|r zb|qepKD~L*cLua>W!Lb>#E@^ALmI|nSZz)n%{h^C{givT z_dbsqh7}5x{=G}}5@e}`C&CK@(L5>!TG5~SF6Sx=_Cp5bqYl}=<7y4o6FZz(bXOq% zseJ2tMrLL#Cqc>hnb4k+^f&8Sor90Ls!{FLf4GJ+wkb~ERF`}sVP&RaYLp2_+nJ6! z!S~VxLOu3sP8@y$rg1;9 zp{(}~03QDlLazGZ3VsBSp@dcNpj<)Ut^8$^oslu=6%smpH_{Q7nET{@l|z{l4``eu zu5U{%G{x|VhBVmh^`D;4ORoES_?pz%MmFtYihqB9v`na5_86hq!$9PSM9UPyE54gB z?&I~dM`|+oK8>eCQvA(c!+%MZ)|I%0bi+0!l(QFXn^eAoDp1!SO)Ij4Gk|QhZL5e1 zPmT2*%pRQd)72UMe$Rc#@0OfMhOrD0PnTGMbZtaQj15Wd=JJI#vH`+3;l%zyHIZ$- z>3UwkTJ|JY2EMHC4(+c@dlyxb={KAZl`?}tQS&zdoldQ-QS!Z^PaCr$xC-rZjLU=} zpe$m0F65pSKc*p{Mk$ajFw;i2{N%?Ci2aAR!;&7G`a>fQo2g5u)lH?dBDkNYS?h5Y z^1u_Cdx^p}kWJ;HhY9m^#1sq{N$Qwg|7l-0Fx4viX(Nc$7?XG^lsTi-mH1n;0}3!% zzK7X~$A#mWa!uci0XRfCss&d&`txI4m#DqaNn;@M7WkgI3DkF0;e+Z>kh)RMN3z;3 z670V$Rh|;)N?Ik@QOCsG8E^-5*y-3yKHnHzWP7ZcFlLaMZr z{r+Gq&f_=qLRACWn3Q9WJqrcu82Et7=aklJCLlr_e|Z_wNhyEZywU}w&W=5A12YDd zM^fH5!I8e)(-4Q;?8xLg7wHzFyg&eo-#JlWWbP_2x)EbHQsrt%goqPr@MfE6=(~rB zsTN-pIClw08$t>qEjSCG*vrcqFD3L!wEP`gz7{7#jT&2fnKbL)IrIO#N%QPpzz`Nb zOiXMAQ>oc-k64<%tK4he+`Ww}O6fUU)u6Jo>Ryac9n53vb$}p!S5zt6X7$}an5a9J zVJv$p`peY+p2Osrhy6)s)hTG;UeD9UsF}Og?|?>%Jz&gRYJN5xNV=S`jrqg8%hB-~ z9*I=0oCx$n4C($3XqA;upj!v&D*Ke%D*rTf0@`k|Kk=sQZ2^)zZyJ8^w>XMz@a!4J z*$;;k!*a);*Eh2pBw40k=8clFH11>DFj*Am&iWDU%>Llv5eDD*d;JzFRvBcJh@n*b zF-p`MrQUa5P8`^j)=&`Ov)46jd}kjCoqSD>ePgl*T;OF>J@2J1;f< z&BNLsdsMSpi-!GKoU!G?3cN6>V3n#(h~!db*rbLkq=GA#ioujR2DUOF+*a7uyZ)aK zL?x3=)hH7Zz+v-^;#||RGlm?wj^6-A1vev7;m=p4sfvW1mXyM;u^-kL*^nbkPEzbC zHxtp^;pjAr*g1kI8a@*6PBl>#nMc89`_IwMBVj}#w!XJ^!XOY`F@ZRK%f^C!@_*IE2BfrE9>PxF&uxK#m-2RoF-OG|=^ zj$i_5&!vd~6O)7+VkCAxYeM_X;_VD|F`%ANYid1hk5;|HV@>cUe!b@_=>%nQx7YLs z293z)EzKLDRGhS?#6$h*EZk9$O_EY-DUOoEsgPIp_7p~*n?(Ax4vlEg8xiJ6(E<_d zKK;DhID;2k{{SOK?(?ZmU^Zp_$&O|uO}uB%fUDeb4zSo4jBpv$dGLRC9w!?Yk2A>S zMz@klxj$=s_6w1Y4h-NsdZoybd_7FRL|(QM%Fk}98g3)zg8U{!Bi+2;eD7M!$t;-i zMhkJMk*vZ-oWyNjmpz20Q@C2XyrXP;Sx^{&TaEGq<_Hf!gQt)I242;a`PNWMHmqts zTjBJbRA(9BeJ4jnVc4aVhV@Wzdf)h|U{M1o%aef;wYXA!O);+aCs@iA9muY$g11Dc_#cXZ7!C;`dFb%C*{f`aT-j}i zj3Zh}B%0}dtn~xyq@UANpWx@3)DuwFITz#%k;BjD0KIi^8P1F6V~}i{YOFeT3w6i0 zY;zqX zydLnkC6nBCr5Rf$29U%@2!ZHVk>)jy^aA%V;;!m{gO62MLHy1c&G}blZOASRqpavE zPiEPZJpyiqFz(Ki|Dy$ot8uq zGc*K%VF{X)Y&`-H58gWaOf%*qQeOR1wibIG)nPD9mv^Omq>EzBekHsjxrmxVpWerO zdLk5U7(ouvM-P9wU$fRf7yj5S8bQo{Fqs@>%d`L8P+!YNEhksdhn;q_IXeO5DkB9} zZL(^tcHrq1));<}a)fJ!qE#3_9@45Z#K0cd9c_FMsqyV1zb}w4*p*}t-yMM~TLQt< zIgd7GeI%RX!HXNmN}6mxj!3%(NKl1<>%{!-Nbj$I~B-mmKiKXvHE4WH+@dXsthrWQ2J+Pa4Inp!qrn%yefUH z>Zm8;7e@`G3V0*Y+zf@la~88h6eUT0-$Xxc)#VOWpweUW^%Q9;&)RhZcRwK$iTy~o zD=_Af&yWdG5cu8u7D$4K5cuS$eB*LP8FZ=Dsebzv357I%+CQ zd&RviQ&AsKH=ml7vRkw zExS$1In&^8@qEsaKCI9M*~DBaHPuIw#rh|1@rj97U0}E0FRm;LgE#&hsZ5-Yn)XC! zK&GJzURDl{ijxVn| zl8bq)%a0#TcFt%@Pw`6{|OoXqF+ILBPp+SfO|K&II?$Nm2_1E@6)Wju86w#4xlfqT9k5hsyYL)SE z1`Jn*mQ-{!_1SNqCoIS;??D*Lfkg^u`Hqd;BMu_%YPcQt115%m2{5wPRI+6CZ=dxa zX%pe0fy*(p*;(U*Gb~(5SQTf0JB)VKAeSr0r&NU? zt#DphAfrnE4iU1^PBDD&n`gqKymvO~aHqQ)I8~8edIZ z6}|T#KF3HLzL}x}HnkRK@pn)Yb|3D>SfYrraPQ$LfjybexqZKsbU>;0SG4uUz|QIJv`6^Pin zyFGU=qh$IhQ~#L*DF85)R%Y^L;vOB^#qEXuI5iH}P}y(PfmE%3A^jo`9Fw~HDs$so zPXo`{3!Z3>9S^@Oj(I()H!PT1?kLA!Fo;NQw7DgC2VFu7`{UoO1eM7+t~aEe_tY#3 zHI+@D)%QQy?QndFnv_2M%oyucVU-X!4Pka}%&K#^Rxg{U_wWzD`aJ`mN>fWF@x6gd z13G6}hRdq?=xg;Sy6Nt;DM_i4t~t1e0rGG4o5$f8*316XB|fZ=7(<^I7;u&9Z*sF3 z$R+NMa%QA3Go(U1+JyV&ukq^86Oj2@PsgbU+np=!)p2gC!XP(w)8%Yg2826X#*=4j zGPJG1H0^sps-nC3)tajhWqA~B6n}oUU1glNwOf=6tFIL;ynk`a-)kh}8{7Q0@#i&b z$HvWi2m$}NwsvNK&ix4a#j6CPUn@^1R(97|H5&0Np5sZ+ysY6H*;;CpNXMeO!TDg4f-+I zFAqsu?N?v__#FP_nfLY+qylY&=ERVCGi-rl(SDUQgH+%5Pzf25y5+D0}*-Ktl? z&*{8>z-AuX@gbLyT8Whn9+Q)7yZ+sK4u1Joxl}foFs#=iPhq8p9bGwki#__{J)>LV zk^42N>3?4Y88>6YijF^FmIy|-nJkMQyGhXJuMimsOjL=-F03|#J+I(kTirPbU$gR~ z*;GXDBcsqNP6t&;XFHzAeY%AIb39m7%GW|;!zR%@gV(;5o%3Y+b~Apv0A@B6}CUQhtG(5^eS=+>cul=_WuN#O(p6qeMCJvXqfU%f2F&7{}Zd3 zu;T5e&?Q+er@FTa-g7No5pX|;ZmZ_MDGC>16Up2;JY6_-eX?_x|B2{fsvSIzeo-SN z&(z_0tnt1yJG| zDl@I)OFm-5IO!NT!+e3+>U!jG?PkU44b!#h>}`Cv*euu@78k#9Z%b4U*jco@C3WVe zNeiUNJy{>GI;R%{2BIA>!Xc`LGB7Z}Gd160Qa}~0RE=sor=j;iLW{Gq?AiruaoroA z7IbBvy!a9#LEt;^V04f)qPMVAH^m&VyVA*E zS*G(cX-IDw!b%Q&Xxf?R`B^zA2NCslgJ)`Hyk_cWk4t3_avWb)VfJ{@vin_HaQ<$l zQHK;|iv!VOpWF7^uw zHcIgu{q?oAw{K@@GZqL&3z>NcUB>nGD?PZ)!mE5@efDD*xhEQ}*~X=zI#JoUYN|u) z<(7CkS4w~K+O4dt({7lDR^NHn-kW{O(p`1{)v{E>g52dep=uWLwVLQapvX5o`U{V2 z-UD17jlymFHQTp0ZaSsUtuPz_zu0s7jqWFnmcx&-sU9@InX%pnw7cwPYcY$)KGcBs zJr9#J4rKCUuKYt24P}1QF3HR}->UBRhW$f{3ELVBN17`9g%LK6^O1|S*LYkE^EZBG zNX39jgtwy7ms1~h7yZJ8&;PpOo}xsITputpvov&^G`FX$i zJG>~;o9{gZVwF|5o`)}v+*gLe{D1eUJT_^~B4(tz2KZk}j+_Dag{=Xw>lZC^YMSba z+z+T6t`Dd@SriD74!zx?(u%eZ`=o@D?y_h`dSX0EWR=&)fCnt|8hlbnPzB9_URMlm z_8)3#?1!Mql{%$g(Xs=gpGHkEzOa$ZITc8$@~H(fB6n(sI<%HKWx)a0j3`5O(EAhnzm<~4NSBU!^zjDEmsE8lz8s70zxhg0 zS|WAplLuL~MeL^L?j4(7=}gh@Oz|nh?jF?n+!~XBNZD@iTpc;|d#S}CY(8dsgYRwA zkEhAn27*!XTDSi?KusbhnNw6G&9UpLk9*Se`?Q$z)d2&pBnz4SR|?f@CI`Ry>AC!2}X6?m@4Iolu*LED#vcC+7|C)!m>!pg}>qVK7awpaZaIoMj6ZoTlHLlY&J1P21}-z zEw;j2nngH1r*BCAz^k{Y!+PHth1&vb{oAM^yFhW#n;RUc7WZ3<9ln2;@)VDhEXAFe zIvn+k#-5R54>zGuylLlnIZg$f>)U)8RgQt-?R0G%1J?l)CvbpQ0aumS1vW}Zl@ib| z)VuQcMa$k}-d0^ICiP6;KLz#DnWPBZmZV(bWcGc1*TU6rXI@@mwP)PZgnHhRzPYAa zDsvmu=5LDI@?Fj3*t3L^(pc18`YY<* z-&cSB4qbhh&qj{#vR=tBv|j>)n_JMmnQ>@{&&4Sj%bpG_QN?%&PGA511oA_9lh-|V zelO~-i>;+eO&Bh#aQU6*I@W*X_)d130Ce@$}*;y1lp1)uW0yq8?|Co^MIrio|Ma657{2pDkH=%Lz zN!Av@$@sf6S)Z!2J3@TO%yH?KlC;U6$8HK_qZ={mozEBF-iUKleK$Pi7*phT;X;M} z5B%K8;#L*RAe5>m0R1p9(KAFQFa}7bpd2rFdM^0XYKQjP*SMSL{sbo=slq0kaiQp3 z7yKs4HR4+K+~moa0$Q%y-@bi2alzLyoHPQEhM>VRw*YG}r$8N@B@SiPe@#-M`{*kJ z55T>vJ@@j(#){o&TFLa*h$!eFmkZhC&{iV7y%M49H)nj8HU6U+}hKxbK~{ zdM_=z@$^s#gLa#BppTFdILEiKkB)tN)ykGe=^m_6-I>CGK3TQGNX;@2rXw1D+Rsa_ znT!4;Bdos+RZgzdQQN{`L+o^G(+afejFo|t*HBp8md#*WJ+++9&ylMc-Tf1+8|mZT zceo@^;ePoZRKSesz6|NYCm0u@V$gpcIW?VL{Umem74yw_1BH4|W39~iZ)5Rfnp$Vi zJQIv#G3$xfkErY=+6p!4iJuKgXyelkB2mbW0F}LK)Zb$riut?j3hA zOgBHan3O8A)%PP$O(3*qp?N{Bf2|p8qD_(wn$J(XQZIT$Cr*{D{Tgk zvBVqlbFl^;5E8r^x4Tx8Q!glL@T+^t*kN0;J@)lXjs5k^$5yR0gQ|zDP6qb-Fy(<{ z+Nh>f&k_|Dr`>^tM#iM7G)M8m`RCGSe@RSUm4Wv?nXVng+}l6;JV!-`zFAX;J42fn zrPX-9@1OoJ*eBEbPSTJ?p}NX-YQspN1hNlna1MO*A)7pDH00x)R;~Lm{9x&~a$oC6 zemg~4m*(>i#vz_iX!jNHm!q!(Don5WA}Qjbbyk^dez4cMy7;{h>MTWD2ya0(nsEB=n-rLMMFYPMB1e z#{FE*nWPK#y}HhiNsO7?{B=hD`YE1=ozHJEtt0J#Ng#;qtt2y(=P?mkZWm5z-y<)9 z#W1;)nMu=%M`6nsWG&P`x^dn3mlm?dJx2~}nuP8wzA%u3h`D{+KjdS5^$g1}ywP1e z1uj)KOw9>KrYzn32=d(T`17_nRkWV=mpiL#Zex9_oVx&rUiwf$oeq5#TJx8R{_^E{ z?N9-;6j$nFgu-~u!q`(?nsSF8l|C*j43S|`Uyk}1hacw1KJX~V?Q*k4Fl3(dWSXWG5TFZS^Gd0Whlm@ZF6SujMmQQj;p zMz=+4m-gNgw!Ht$_sq6`$X73v7PI!ob!R|%v`(#+-m^tv^?}B_38h|B`+P^n1U|0n z8#HS!`p4mqK z<_A!;-uO-RvoestOw5+R@5xs?e92Hw3P9Uy;k2w4QwMV{Sl>rqpNj zCTOvXvgY6E;>4nD)ofAf2zbS29&&<&@FDLOfCubzyxh)F1~{qPAM(8xr>2k@SRpwr zQ%}!AjzYjETRY(^O*Xg9_oAbz>23!Bdqpv$MCr#I?--aN(c8 zH&@O-)?aMYHpGT0%`#ccz2q&4)5aQ{=(j^b614On}2w55BXtMSaRky{XRs$i=+OsL~g~XEH$phP6~uBJbkKrYkg`WtR^{M z=I+h)w|}qd=y-2PE=0i>tb7!fs^P2kO9nUtq|9Tj8hIRvS=L^j?eB;DUz=Q6(VN$E zyV3yd`m2cGi(*nzH%y=D_aRn~^Zrob%z$;S0-#&+Dk>^vpvagZGDxA%{DNU{CD84^ z{JORQK%ipe?vwx&o>xmQ`cyhKG4@@Z>da6AeT^cLK3@-k^WVp47&!C5leuSMr1q_X z=R57b8k%a{Yq0;i9wBb`#9Q_14|dgz`x}S^*EVo_n42su7JY)lc!JdR0pbcBuZ>^z z*GT*Z?oICd8$f}+M!pwjT==i5^DaYbj@_@@ZyKKJCs%MniaD1Vs%VEw0=kAK zk-F7C;NOPZvUE=MeAdL$rPAK|%A>D9#Z7Pv0a5I0FXoWdOF&;{rJq_)9gv)J)Mw-) zJ11v{{_?P}vn1l^<=hi=Ty>tnEp3}*uk1ZkMSkWzY&8?(;wf2o__|@7YoHx13qNm2 zgos6{Pmrk!Z&R7p5SJ6 zJ|{T_@VZj0W5%@B!6e{@@>QukKt4F7@2}aP_D(7Us9wqiurN*(OG=N=+!KHCX zG|!iFOWp>Pq*&vcEeh)@kHVzl8q!n&xGEw9S$@8}#{m@&e%zRG*01IrUuzIXeP|93 z4R}p2ng>Rx-LIqD@l&q&fd{q{!7~uC%`5LcBoqAk;NP?Rt6af|`*r+-o&^4o`;(h* zBr3a#qdZbQutYzuM5h_yoX+(hwYifoJS{J-u3bY9WYhw_!6B>R1n(>i=!dYg<}u8I zIp`qV*7o+N5*g;jc%HzUl+=Oc{`#yt@u#wL;8*mWFJK0N?OQ6(NLXjB4F_|y={f*1(D~tDiEsYtK`58HFX}IBFioUT z!BqqhB(luMuG{yjbPh-}aH01h(WM90+eO54-afX9O(!JXWa#G;wFH8jvW;ChMM{=Ty({KVbL1;W9b}NW^Nc)_aI#C*G;DB0=s!2?;O6r zRA-UIoWc>Mvj_wmNvfnbdcTrZ)p%yZpzyA~rQ;IuC3u!k9x~FlZx_Lq*XJ*+y09xV z;rzDd?J0YbyD1PZV)7N6chRngschHQ^sYSzrfDB z%!1sU;(K%-CH)vNjJBRcROAOB1zBZS8-7Dl%Xm@aCBpNoFD^jE&5xwF&eTpdvvdsJoE#2LUCj zjjPl|9VM{j7m>3Ei)RAK9ZQ&?z=OC&j7L{XTDm-^*_M6V)59*&o!7cF359c^LDYVn zg2rB?A!VLwu|WCkBjOe@MGM=@4v}yWD+;Od0|a_FCJM71 zzBubMs;ebI9;6eVVnt)f*ZOEqhoZTsxlK?gz9R~9xy!UGszCJ#`Hb!;^8({l8FH&lj`lCtm4E)<&ubf$8B7Peo&+=!g@KM=9@`_1xMF(9JcQb`@;t zk~Hp#Ezv75iHhr4;p3)p%|c7Cp3Snu?|KdoOs>Vz3eeLMGP|02$P?9WDHy9PsZ4)T z0XqkX}72`07Mia5T$TKWjoVp&*OF@vtSPGIcVIAnN<$iw%rWOK{oMVDpL*9lw zEQi9-l3A?N5=n|?h}i5M8B>rsBk9z5iqjTzo**w7OS-E}522d1!sjz+1y)&1M3g9W zdwF%=Ab#`q3o=S`KgLJE9N4Q$gvMc&ri=9sO9%A@*n}Xcn9wB`n5eOKZ2k5u%@NHY zdHzI|ex?)|C1OT;p+}TQ1~1c6KCPt<8qfm`P~?joHzO2;QF59%_Z8D+_3XKXcITdnx?6wt z<&}y$HvGWT!mS>@-&uXMH{cNE`Wux45yq*m|Hy7)*D_e>!L{0pT zaa!~4s#yRStR(T$&2#pGCboza($V2qtt@0Y_;PJom4Tw8jVgg~-a{}@q@~G~K&}MQ z#oZx}$53foS4%$?%@&pW5(?FDiy&8uMU{pbRJl9T!CDsd&DS2ScQNYWv{72EB$l?t zSg&PjSx-LK5lojcu}E<}ECgRkgFD#FNtKi_>4hh)YwVgXe@Lix>h?em0mASIr=JdQ z(ReL$rx4W18dN!@2|(jcwRzz&o39Dx28|ARnFpfZl^=<(4qQAvT;{sN+*`~^MQ@0Q z5$OwI+3(G^8KB;rI7|I`po9(H+`PV8(4ZBus6z#=cU~G*?cRInvInk$_6+{C`jtZ0 z4QpdPhUl#pOd@tyq1j}y>*6xjykIpx|_cY(NJ10rx{tWNWopis9jU_H^5P% z4lo4tbY+G%?Tyw-$gXrP0Uskn`Lyssoc8;(=-5OJ#nbFxJRwykU##^3bPjM)Xg{mx zi-+Vz)@%MucK%^5>7-+Zdy2j#$5$mtVubcD=DhXiyZl9)jQ|BC$dhX>eazxrU z9z-`6v~vEj0qQJ6G^*x(5q5X3zlERm5{l~2Hs_^}SGSV2<3Ja$<&69XCGb;j9hG(8 z4h&3!Gkg0CP$@b3U%*cDi=<=S35Mu{F2vJlefo&FNZNS{t2FH>v?kUjnJ4q-bi(h9 z$lX?T;A>`}T+4oKc2HunVk!6YLp$j;P<6#6`x)?Xskm%yv5^j3yeB8MzlAgs^Iw`8 zy8HVlDs?ZPSUNI1b&@C`L#+JIgD{Fc5S}e+q4?P#;k+r2=wtzQs4J0dmXbcW$86%coUf#B<_jgmdLBWG6 z(sf5-XE3`i85*ChwT1&p*j+TLNYKvKDC5TL-Df@yHEa7jHH*XVc5G2{{P3K)wc0~s z;P3E6;_81sX^3?efEL9{WdmsejI9Iw{mo&7vLr+@$3pt$oL~hn17-Q*(9_oS#ZkKl zo&Hj3U4E99nFob}e2isf?Iw9gwLYD`V51P{_yhomi1PE|1L0ks&sI)6&P{&w46M?- zq%Qe*A5WEqsIjatY4O?WTe4tSdcxq#>({z#G$3*AcZ??nB(}+-hJ!xbeoLQ zKhp0lJ1CE*<|NlRx6AgSHGMBZ6QKZsHCaEZ9CLRbK$BvVSI0awG^QX!CmoRy%fgX- z!T4+i$xIEBSW16*gG=7@wFmUp90wWtAy?p3hdMy&?}9O5RY-1!+aoBxQ&c~Dt83G1 z);RU?IQQ*tmjNETYJPos=H}Vo8i~VOq&W=C0rWa|+ z7f%)kfSD=7SD}kvUS9R$W^TI-MbmjRM@L8RC-pOj>?wG|*9Uav@MREm7sJaC903`u zh~|a;B}c)#`$F>IBia9gE_n8(B=go6h>!~SZ12&%4JPE(P||e=(y?I?+IMgTDT`?X zXjfmHy59&_jySR?L5im$A`KwI|IuzXD4(rnr<4x9Tp^U z!Y_d@p~(h%C~j#S~Sd+v5P9 z+^(#8?ELhSX$Fib+`nGTYVx}aZKdY6^H;8bSdv9$n&he1`(`j_QhpIso2y?3Z=Hk% z{8OH^g6w(YFZnRUb6*;ZBp9)1P4I+o48~rLpBtsU+W;u$2($F zsI%Cn4fB+=e*=W2Jxs6NO#L?Z#nD!*Hs=54<6bfIqEoNH_xC5#OK;h^kCdKjE)C9DG zl!oN#OY`_2$G%XYxW-jtbDQx$y0R~ERf9!`K{Hj-lf%S}nZgwpWgVB!SlNWZ6#7oO zZfi)!)_s3(Ik-9)8{sc$I#@IP;^$%V=4k3%5Id#89man;ftIB=ZyEL;q0S0iPc_0h zqb5dN(}4@aTKL5BGim5&&Pj)a5>$XwJ@O6X-$}z8oq-o?{Tzd1GD}n{uLO-aBj0~>Pb;G z9P<1)Fo|$2`a3G+#b`~{J#q})E$C6(?>zL#jCq7cKB&#E*HH#gJwHLIaW0_W!SOgn zf9$vbXp3U*l{rjNi8~W!CPy6&H^vrC8;iubMHSFq$hN+%FjwGC{%-qH#o{o4?Az0sc-VZwX9 z5;L$boLZ}Y^*U~1oS-_UR^(GRN4nz(y>$K2K!*Em{FVBhB5$)WJ;dbZjtl|mKJ9_! zbLlW#hgS{pRx`@r=QF)wOI#{a;)N=vfT4voY=i!I|8xRd*alTP(>39(r(a8rJTO$0 zf?G+&$BRYU3en91Hzzu#=*+J$k`XlW(Ck~2KTXp_@0_q#X~uJAut4=MMQE z!`}5*R7eQRbTqt9cVr&nxc2+JTSw5Eedr%F3wr|pPu*sQ-iL@VVfviSlUhR}e|*%R zYJMVY2nvW-ekH_~7(2`ekup&&?`L}B&)YqF|MKdwZcVQz$%V%^=$Q*GE`c_=jVWr; z?$N|0)gb6v+6*Xq1sw*w<+IH{1O71}Q_W#UiPd~`iOo5M^SvK^a|mVU@1pxCEyc$5 zIYwO^16ou6`sOEy!qGhE)#KXJIMI=v8v0(dnk_n&uoZFJbc6njgN}@`CYk+P)tBBd z3QJP#N;ZKux|5GeLZLBpJKr?kQzS`u7>&e%I~f*LIK6thmmp5gV3O8QjX_{Oqx9WR z$E)v5<}$tvZsLH>&lm*mNj?mw-tyX^^)nkZmHA7)7ZQV+VlIs}ML z>!(AV4JXD-v865!Lf$Bk_zmQW{oFz-#8abQ)#J?8`ay~@HE@{8{Cr;_j1 zzrfyB(DdiNECwZrY+4B03ZgdW|G3U;fLlhhbJ@um801WAUK~^`3tr30TQZ@K=pnU7 z66L#3h7pSjAS-WEWSns3y&SQ2Yr?pV|2=bXxq{mBbG#XYvY%K7CLLbz zW_2dcf#!HA(uh7HtZv?(J4*WyI->5aox;bei)~R+g2$ZL&R4<_T>}=vPj9rT@v($` z(R*k+>QM2)E9L4Vr=3qz?369|6Q}ZkO?xEO3Q>1$Z9*-8L3G$(#ljQ)-C|KCykH|R zt;b}o+S>RzXr@r)spZyzObcjbdym5TowqO5-)Ys4K1iiW*kX@A4F}ML;c7Q-flwt^{8nS+%*@Oo-iy91dx0MyM=@agdv2!! z7!bJ-%~+UaU>C@5RA{99^PPL;mUJa7riEbQq;)hace>(V1Wu0(C>Ao6qq-c?Z24-w zF!Yx!xbm?2Z9{INs!eImYtu~cFnSxrfG|sfcDpQ(HRl+9TH!%W1XLMTxF3juosBH| zAmf`W(sxkx@9Ws2a2&* zfCGDjH1!VVL?(RYJoC&*{`JvQC?V@B*szkc;NajgKz_4c#L9CsP!b+VN>;X~SO+d()-lX)y-H2=w z)OT-Qcn=F&?r=z)G$s_3oKixrso@3^>Q)eQR3d*t!s%!BK%add|MOWpnB-pL}4k>uz)F^g+W_t2hm1=p9ugaH>(@?>_!HSOfw-%O5`iICG zuA!E_t7;7U%5pkf%+jg(OiF`S)MRPNN4zgp#uPnm#tkRFRG!r``D3K2hZ2CX`l(x{ zFec*N>OoANgjzCddKA(RZ$Z{f3LAHy>qQmbce5uGZX=5sIP+c11-fMI69{ow&9(vnQz~boXxYZ`n>>7pLIw9AdAO{*}ks@k}J+whnUlK$bHjkD~dH zBpk!*A&TyFuD9oYkg71w3A#tO3X3E)-pv9hVn za)r^!n7%#OTNRO0k-VMC+1)XiHu-^$f##5uDhz&bBoy8PWuK&UYru`y!m2=BlY)S@ zx34jW9>Q(*E{xr}Z!Lc`-19L- z$CJ|S&VB&|(EhoJPi*vKtLK)FD?=pZXE$y>U9adAr_w=|aL- znzx;k)0_velecB~POc(?adpXBQ3KptWO#t(YsY&p*mZQr1|*#Vhu!4%PNlBwAiB5i zZ)clAYE?EqT9;xgzznIcw2ST+jU<{`d6HtdnQ*zwWO-$~^jMyZjw5wYS_=eD-0zt( z8o)HCYy2L%->&)2fs=zHiJGWrfXl{U1Y0Wi8hvg-3w6`6iR&8DBd`^t4^v%6rUDS% z&<*c!xh?zdP!IW z>9tY0{Ey&5TK>6^ZzB~j50!hr7;ays4RG0wljb-i*`Mfxq6viiS`w9F84P~0| z;HME#BHP1msh0zm-=8Stofe?~FA(be^Tmke^|=8(g0d0s<=Dn}qh+8_+>nD{o9_Z6 zN=-@WMNhr2fvz%1JY0WSjGg$2tF&%qi^-x|1A<$7fO#z8Rbq$*fb6~c`a8WDkfa?2 zrR34UGuLhhLowT4iyJvQRS>Fij1`TPlhm@uRJ&}Da~LuHN!C7zgF%e0IKaVb<`;VaZmFgzbb71~sm zyYtzFJ^BvvpgrZwrj8wgmU+J`^QdmF#NjRB3%S3|j1ZGJXWnQjA4E{WhfHN6yo0d| zGVw6bWNpgR&FJ@-Jf$_y)ORe)}sKkq9LOPN3K-iNHrDj|f?V6xBI{5dJ>YE{C8&h)+F^tEZphOjUZjaRm z&}fTKWIc4hJ;4bApkNmOF|c&XfqC5~_wX=JfF4vahjDy$(8PT|>fd+2BxQkPQ`NO1 z@ATx!+1;_}StPG6^`NZo#SZO-`Rwtl3#AvJ_{Rnu7=8B6HeUT_2#*`)u+$URa0_@p(ScAl2K-wwkZh)(wyw5q*`58yFoYX(jl z}W$AB0&l%wfqM zSs|m7xl+)8{figMQHMN1W)r)@414!k>#BJ}iTDc`@QD z?s>-%S;6Wx@4NQSlj5^+lXmOu^BWw|$l@2U`LIiymz?xb6H(g)B3naxej%$2N_=w7 z2dq)Ds0~@WvX85pbnQpe@gHUlO;!d*O9>?y?4my(x%HJUJ2%$@&ZZoHO2d(4N4hC` ziOr{U{`^;+7tRyAGzazGgX5-i|3hY_3;!HMMb8pU)T~g%cvz`-dh^ni+)?6DAjr*9 zil3*p;#(*jc$t0HuuY@UXROXra0?R2=?#`Ix9BA6NrBhp0@ah}0h|$(+MqSW?oz0isp=cxjv_JurY9p0Gcd;TWF(WgG@gND?83){?OzQ`v6B#< zZjj{)j@F*(sy`0P-j7<&mNdJkOgOX)E~?4rP=~@w6g`*6(VA+sk_6H;X1quxpsd2C zBCD=z;7i1g&%mHHV^#zN+gZDFQ8--SCRVstggVg`R+guB58~HQ zUzV?rq*S}Zr=KZBBKsN@2tZ+$bP2Arf}k|GD;N4)%U#mo9-=@&6f#F)7Yx_XP_1*t zO-4_0asE1XvGnWnyT{ZXaICO!>@eg(U0jm-b6C$LUicTHbViHqo$xX?SElFQu}6QzpRlnRW@leGX6!dm z+?IOqsr*ceTM0{qxUeex(2R$<#R-%ZqHMkX>cQo2=^Aj= zY(^SKqTrfXCQIf?9F+e&sOVsLB=k|`<>FNQu!AkJ9@datLrR9=fCtw~&?U%~$WlFy z(>{+fXR!kj5b9D}5Lg z{MbnB1w5%lr-Dz6su1pEiC#qGFdN)k%fzFQ+Kibtyk0yySR!N6+Gi7{3>#G72o1AX zuD~*yloDk&^)FYRa*#wt!qn_cw`h?9g3CH~WS}N~255hoj-sS?mE}6V3{|%!ek*dT zRiFovD5L%kxn!hqoSxt>f`jyh=g(Kj%5X+qT5kQ^433~1~;frXCYrudbJ3HG2;-EdG>L!WrPC@Ayy!Pj3B@dOS zkwEyf?J0r3p5Sr{j+VnN5F_eVRmQIs(P;nQ0`8@h4+MbEJniI{N1*cu4F%UgciQEJ z`3jMnrdl@N7lELkQ!oCE6dXtZuzuBT*4?Zuto4Z#xNtqee>BD8R|idsFzp|XtM%?e zbYRw;(!eqRDz`xMsqrI78R)~k%CViI{q^!Z!Sgn#*R0dIY3elRoHUd2HT#{GOd&v3pb8oRVvaAB9qmAxA8155Bes$&bUGqY9 zdrlWN(c{-E;Xgiksd2vS*0g4@CgNMXz&`X}Er4*pJq(DHV*Ozw?LnqdA;I>|uEqM& z#Mxijzt=xlFpygy@wpdih*2#dQ0Dlpb$x+Bf^4B!JW>Rw6#=Q_OyaE7z{G$b-F()U zE{kD1{v#V@JYgbm)JON=-`!cQTL3uIv$|7KR5T-0OOH_K9t?zOsgwa)s~ds;OuBk# zmEBksn*t{a!`+xFJsjRmzYJeRdINIlZo$^)upPho362?f5@`xlOnvG=Zs3!cr}?l4 z<|pa{%Il}I%s~ChW)8r14-CwC8m9A#{r`(|^k4<3TkwHJZRF1A#7*cyN7TU<$p04< z+W5?Q#9X9!H+42aAVQ|H`OgC~W9%C4r~VU_bXiqlRXYBlJN4T$x8j2OZz=WT?C0Sy zE(nFrqq_h}StWZ=cDzn#A4fcO>U<$=#5`ZFJq!j>bW7~RqQr*e`}GIxyDs;EvqUdI zI6Xc2@^J<7EEr#c2?*I=R5-kNa$%ymN#$!Sn>nDqu3uk$BVDd0d4773qaFY$(dpcZ z-g)X?seh#-w^JIwJoj-3fP~vyz^2$NhknnobG3XW-yu&=e!-yMpFK52oin);a*JSB&)7N{Gv zmlwzC*CPG|84FmBf(pnuee|hI0%;gV>fYKsK5e@{W4MfrjxyAX3ZAvGVTXqBxN*^^ zf&kvV>Pyj+7B>~T!+;u^Ns(?Q8KBiz_4Di<^@05Em(2_o{2}Rm&;c69(uWZ6Va+L! z8Uv(50k~!BbDBWs9*x-ph#GXzt}+4GFu^U%E@GU^$&c}CaM1aS*aWW!;0^Zv4e+V zeeJ-tM6Em(`u#gPF|k8z8xTLg=9cMv?f5}20@3eppR9iGA~9_Ozq}x$J%aBeMe^`@ zNZwekNp!L7pvqVxg&V66Y%bG&k<=%B#y!2P=xV)ywW)MEv)>~{(a+^i;7pp9sB*9= zq}3W;%k0_zm-b$&IHTt3^7Ji2dj}XklSaT-RjNjl;pcDfmVeZ>Gf6nDw$6h<+{fqM zjp%jZ`i%u~bDnoI>tmG?2ci9y%5*ryK=bU!ZW=8)At<5Ymi8DfRsW$-ttXfmz3C$Z z@F5fu_N4VVDwZva5u7-v=TpHD><5S`ss*IYmdVcnX#Z}wJX}g)hE_*L7;Jbp?DxrCT#0cjqkYX{NV6Vt@AzD`r zPTySQuGg7`MNzG98%}fO(LawHe?9XHi8?BlM$nXSkl23^ zzq@{{NPm7lrk&`361dgtIi(ZB5^W$y3=3LgxKsVJjJ`N+t zE=opXoAnn!g|mlm-YQ=W0|y`9C=e!X%Xm&CYd=T2;Zb*W28f?7@+-cH2W0i~9*w6_D6?`mwsDJ60li5e_`AERUlws=1}kM6xhG;#w4IS-jXcZQCVG$drAGo;(HC zo&i#-Azdn{B3VHmojf~J10Hw4KDAqpK)_tw{WvvMVEfnufCDc z^HtXWo_U$|vQdKHn#3Gib;idzP%SIa@m(QxT_*VsjDbBReT9d2k8p}S_UdCbmdY9m zXLHF*#vrsgzq4`10O?C&i;f*ZtxCi`-9nAlJi{2j&Tt-sA%+YdRSnt0XjI}P9nE|5 zLmqXsTG_vjj4pu_(||m%sJ|1|m}b z(fDvtK~0cWCr`48d4f5@uWC{KN37&-oHo$`QIyO?)Xm>s)cY66#5h8u@aUZwfIhTH zSMon~N?{|xK66XYP4msUwYV6{ZY#L8x;SkZfOu7&_l2kizqvtgiK@m%!(4dTojW(j zLQ^<}VZ`#b>=Y?eb1Nce&J*-TTvYh;8EKqxN{5tu9cKhk59N-auw%M7;0z*K2HF1C zY1*+aGr=Dz;VBMpAS9~1inj2r^0ae{;Yu20-?f>?b0i`H8MTi=MM81wdiNekd~!G&x_ETOO?2tN+hZ*X4|X&GGsOGtdrs+ECHIOJ z*+pEm={!?=(e}PCW&6{ruEwc!HFI!VI2IAHA7SujS7G0rKLrFzeNX8 za?94YlwL#vpVSBv(?)xUW8tKd?bvFs;REnm?vleQ(pYtIO!(uBD<`UTvFSa0ys zD4Wg&GmnXvS04^nor|#v+^`0V(y$KQX%l&dAGSnFb62?#X#D@u%$%A>G&2j1v(wYY!q&-VmIvp-@hj`K73R_ij1!bx zlx#0m&NZ~DyMNz!wX9Jw@M3q1(Scd!r#4;5kX7?~0ZFy+TGM0r1sN0R;7kN`}yZxh+A=>;$ zC*#hFi0ywoV$b;Ya*Qj-@m?)bST7^Pb&60UX6f2Fvh>smBzvgvY%SB+(^*jIAv)(C zo&a|VmKrZ+^6V*bdig@Q00W-%=0YmyUCJqJ7OkKo&H^f8clum~1`T601$Gw%`)3}I zut)vIU)EAEUx?NJcKW2Fqa=s*zsi?a|5Lt{Q5~^8=F@tZ^45h1-61}tYfs+($16<1 zxtIf=gUrsgHhK^D==^n{BKr+x8Du6I}8n@O9Ljv}r{0K}OXtDty^z3Cd& zCyb@iOzUlYOOvHHH(yb+bZwJd0y#<8acHH8CgsO(>&{?)N7zqB6=hFmpN&UtKm&KZ z9n;nT<0MuSBBx#O0}vFI{}2qdEjd}&_ux!4x$3|vwQ%T!PaVj^K|7)8j$}tX&W)qV ztFAf-#V0AkY?+&lLG@Q|aQvize;0lr{$6D{1;XL}UwtR#m61{e&7y_{Gp}u5`PB>& zh2R^YlVqz#54>Qirla(3SgJW_Wg0{A?V=*2)Z@pY9pl9Qn`*ESTZa0^9E=R0!Z3XK z`WcvfnGb+T2JD6Mhy&B?)xNhzgA%LGUj1b2FAYP*XoK0sdi_zzQmXV3bIrT|r&no0 zvid}qRxo91%u3Zz73we0cGq_jCa*0FL00I8j_L2;xr@I-$;BGfupI3ueYV-LEBF}{ zAYr2_^^gVWc9*=B4`P_LKtTN7YOKktu}kLR78GCqg`?>$6&V|5tktgg(DtE71Iy&u z3ae7jRzQTJU}R>OeX`^XGF?amr(O#&YVhqz_5WhPQ<8_bN?&)R_MlV<)68PY*4NU$ z@P6}5QXr6we;-pCCrk;4WH_QEreL%uIV|q}WpSR^o4S^yOI=c#o?X|Tt&kdEWJ4j7 zCv%kMDKZf5CQ7l*%<79iQo`@w^K(J-Qii-oALBR{^w+d07*0z4e#9RQ0cbyf&|f8E zHyVm674{Utk@fix!hd`tueD}`#9ple1OmnASSl|{S;rk6uIfo zDX{asY>*Gynz<>o^&iB^dM2B%Yh)oFQueD6j*eLehQEfWXBM_q*DFzzlkd z6-(-#+@xJF=+ilBKBQT_eT9%&PG4pOD`ird<8dkxZ^Nu|XZU1-LEgBXa{X?P3%@nB zLwGIUJL|Tf2(|5oHzaI+OUZDnA1eY(>4DUvUj=^+R%0Sgyx|`ZQGhty=-cgpn1CW% z2%f-DWYx{CD;QX!k}@N8*{A)cBZbZm{y?k`i2{Z#XNbLfSEiZ;0*7SV_W^MkVib0_ zYQ}BAEkh3@rbNZxs#*6{0ZZL*)>FQ^c2(m3(Gdhzc9x0KIe*+VL(Z2e19|jQtxt*F z<2Fb>=ocz!*IVFo+ro{s5_i9y%WwWT69^O`(oPr=KD}mB9iSN#sqcwhUOv??PAA-I zpp`+b8y#0mn7^yNpFo|U*3dUWZO~!WHciP z-yM^$<{177KDkr+!m8?z-3wXp44!?6aDb!3=IX*JBCES8N1BKd`2yaxC~)-JqF(-? zfKMIB02U5Ia!jzd8a%h(KD2tVP`%m`C1&ukJ-qX-JSmTG5I#w~zs7CG{KLr1PylQI zC+H8b45+eD1MKe_?C7ZgN5(ZJwx-pYwhYKO>Pi;5=IJQ+p0Tl1r`VhZ|FYprVM?bT zzs7~ksC+KRR~N4bmmI?|KZ45@yrZ^9KOW2=5`uRJYh7al%~J~d4T1q$lk$JVfhkA3l&pGRL+(S(Vc-z)EsKQLkvE=|TWc=tWsfmLrttRgXT&|zx6 zv6u(_1sWm(OIp7~-6r;H8jg$!_l)`OWNeuAvSW4Lyo}}gxSfu;9t}EcO+1%r4ssb# zJiG8ZiA%;|SNBBXKf7Gt#l{dCmM=4H)w$VQ_81DN%q9 zA+$Vr;AW4Pr^`IK`&vh#HlL#es6-@p>pUynkb;t`e}h2>yMx(@!iP5-RB{FB=qjV3 z8}&B%?5T|(wFGnBkT$lxESw_T(Q~H0J3DLGuyx!bo-FWu6B{X2ISbo(vLzU@o(B-q zdgkw3;_yVSr@b3+2KjNHsIIC<`Dovz@r#PuSQ;^*`gA+GZsmt9NdStcJ=FD#6>Zbx z$x}^}mH+i7DbpM@HGTY)W?$oEk1($Ae0W1JDFr!dOrP`#Y-QUQzE-EOSPJzt$uk=L z2k0DdkZhV~fc-@KkMOHsjyE4$VHbIR_M+kwzYnmX>ml-lM}eAg(|$qrtAka7x;4W^ z$wD2-*Qy>_)&5Xd_gf7Xf_)cbPi=#LD+u~^=6m)PItcrJL$4y2mu7{rG5k6*x%M+f zHZLEi{0f?}CshHo$dk!yAnGWw|GFwuHX@6C&?u{QYLf56w@JQB?+jT>P$*;M*d>rwyho4#wp6`ck<;IX)40$E=+ zSwA_E(qDwZ@u5H%bxx!4`+U6)`H|ZFRtl{_zZC<=!ZlVjP5wP_Jp7Vl$!{`nfpWuN z9Ie@O%4hFKSBl}9=pc(-woW0&zmk^)`nZ*8D!x@4aA-OA^_v2ezy+#I+AGvb%M$D0 z@-)0axpB-tGDyvzt$zs0hC-9G5NZD9+b|13rj+8u-s$wE964amNaaDG>6Ml5Caa zDJ$0v}*X8O^{ypyrGiE+B4rPT*DDktEb5y#@t*)GFcj`2tPz4s!9`us{hARW<8>_J=l4Y8db{Sc8p@MS6Lggm1X}{+}QLtR! zKEf%|xV^ctfWH{2jzZTfU%S;_0rz)y2QqWcK>nQjl}k!FV6LQb7@y|ZvHuB{3yY2( zzMsZo^y3lu^*uP(AiZV^A|@xkHLAGMt#&ls*N& zh6w79CFxR7-%E~+7JvhXlz!q(dNimJO#F=ak0^HM1%a{eWpoe%ZM5o6ij^Wlh@E_2 z$v#-HYOSQ4zHzFYgH6E%9KcB}Bic|0X*P7EfQdD0xh3&cI1W>8EW>4rzCIvvzVu=@ zSj|}D?oILS#kr9@reZVrxO`%Wd4BC1n?|9PY^9+N_B@6^2CoKZRLyCg4ab}WY;!XE zfZePB14lX6oPo~sOu2@$8FK7VHwMlweK{lGG%mQA2%K>!<*AWUzWM4uv(gCKBf&BB zTS+j`8LcCth7uVYz4D_BPztD29?w7DRTyIQt{ua%ussN+s`1w2K0BT+<))9rJNjm8 zf*!<`+2R>^10}v2pqgRJ8Os|_hfdU8F@Y4cgdd26ngg_3d5!sje3D^KRj^U=9}6ni z5(pmWj};&nLXKRB=GA{@l+en4buk?8uDBS3vO=eh&mkHovV5Bp$h068FI<=rX3!53 z8*m$+PFU#Y*1`C0Eeo^xEvN8oPoZ<}x}Li}S893_4%Ut3X(!c;A2NuCG$?tL!P2{9 z(I0ZfEc_A81DqZzHvb%LyC8Cf26r?j?XDad(+JvAfeuDmE(Ulx4-z;WJ zzY7Or-lj;J2j9&)K&jHbuo`JLY+lL=DL|$cT~mpUxN6m3r2H0Tmw#@iXP+)45k?6k zLqxL1&u zRXg?zR-z)3NWiPtxDDNfv4J`NwaC~ZlAQH{2}^L0Plp97W8xmRYV$P1IWJ}RfY)KP zGs66de8&5Qo&D7>1J9V?d-3~zLjQpVJ@faI@r~>x#}fuFZgm8$QnBWwN0f3C*iV|S zk64u-xDJBIJ%*B9wml;=B^eBE8CA|PK52|9f{5LrEh?2OM-tPryPJHjs%g|G@xfyw z;IY7<@85#f`~McSx`;WT>%n|}`}f4VIhcG>d-qGR`G@apFRUIfXOTW(`0=()jU`-z zNr?7SM7!YQn&^`eQQg>@05*LF=2?3>ULoBl1NxcT5`sazt13i#Gn&~KP^Z1$)C9FS z5%Xgfu-fPK7z$3Jql2X*;^k`BKdjt~%KPx@{fGA-UhaSQkZzgU>r?k1!Yu^C>dR-Z!|O15^$_EMK}Tj6{DUw>9RYm*m?*3CBtuijk7QhGfRd#bB3s>wpRZ%Y(uRP2d5L3 z)?@GmAH~JQ7^R{foi?-;?b0~HK4a*6uxOs%j<+4>w+zq^lXdy-_;PS8cK0>%k{)m4I#$z=y_nP8Z ze&tbOq{W?uAVu;RWJL@LH20`rK=jICVBvyKUq9hBw1Ro*0P zbizFJ-D)elt>f0!ntv9!eYby$y+50L%}L_EAWijzyJ1uHC9m`7Ow3ARd6~^%VSD}C zfv2iM3UPmbzWNH1O*wf5uywLBHG}8T?xcb0w0;3Un5iH_>@=jq&4C20MX>@tz{N(@ z4rc7--FRQ6MXXKCZJNF#_@Qp7+*A?=s!#)=EYC#!d;O@ygkzZKp3+Y3CVWMljMsXS z#u32n|7V9lT2&AOX5Pyf@ULvzpL7#;<=esiNsGUb8WLA3of_RATZ^kEq)o!0@=YWz zOpT>8oq0AdxO9Or@FR@a|1r_&<%^%)zScXF&-@%SD~}4~+p%a=f=f#C;TMF4)56VW z<1a#~j0DMK1BCZ~ja`lkw#NtBiGOvh+^sn=8i2Q1(jm@42}rGbQgHwl!zcnfO0BXH zFVd*@$D}nZRmtnk4)OXUjH;|1Zj-cpxj553>VSikI*F}y3$~uF`5o#Uw^r6Q7k+tp z`q40c{ebqZaU)Wv)--hKodU3tBi)&=>Non*E}#<+{JR!dHgSj`O$imqyQ6;=8uN?3 z{JW@@S2?2FU>g+E;%zpzT1_i4k!WE}>#E!T&CW9%Zy(|Rm`O}K#YK7t*IP!xcQ@zs z-wnQtjKG~A#co=;aZu~vb*pmANXlID1Yx_>n# z@UOS^jP{X2M7EsBQ&qn6jn`B*P3GOHlIQVL%c8=P<6d^9Su@SfbTJBSs!`WF-e~Q0 zSP3nG2A?_mcr@s%dS{Ssyv=&}@ywy&?QBLgnbVNYii3>qlxPxFrdR)>=|b0A`pS)> ztz3{cCiWClo%p5v_2E$w52+LnnODMsD`)ZwP$LByM@znbCakv8QUjd_BJaqLSlr43 z^-CRMpP&7Z`1Bs74%U(e!_a6647m0iCpWt7wY>?LE?vpUztxOmiFx+y_IRzgFJ&Z! zV#_uVGMKKUav$JH@}CMNu9D8N*e@?mEN!HM4SrrnR@!6Eh)}nLN>E z;xHN?*r$-nZ(%YG^=jIco8CI23I#xSWQy`eAox0!Dn1)DZ3M5Xi*0zsjeAXQiHT5D z1!6+D$ICz#-Hyh1Q$3`5Zi=120J0FbQH>QhDMG7>yZHLwsD!4z={mQEpwg3pe`^5< zCuE!GZoYoHn{E9A7+~hVOJawp(I}X@N5o&j;B@&6f^r5Ve|Eqjq5)B`?o++slCVd_ z9JaQvc?;gW97|tgjNfQnOK%{cdQo&Eub5rs)rVo+Kw9!;tBii@PpK_bz1}i)+D#5l zDHaMv-^mw+wz}GN9<4sM)PKz!DC7D4w>HNseClQF-Oy1Fqr~%oYD{)AP*ym{6Q4P~ z(pt3)uk0)eS|0BnpIz~oviNfMJem%n3N^(qQr*(RHlrVb)NSw`Qvrs8>G_o6Y`j*B zB<01}eU2Mr34Z0MkAA!Ei_bLs_2%Fa%JS((OCT0E`8Pb|pQP9oL`7zYYbAwqd%LcR zB<1PJp6z4MKR`CY-}m*elA1N7VOu)!6>3SuMyHy1Leak?Ks@-9gt32w^}l7;udbbePNJPCEM%&;!xJodW$j{`MY%4o%* zQh$z4DsLNi_TV@Lyyzz$xp)jjZv+>!<)M>a9^p8%J!hYsLs4&U`5v6^} z9ig{l52T>?^Z6`lQQi_?!si;g4-%HpzdFAtoLzV7Eh;$aEv8aU6`LnFpx7u?V?B>| z+SIgzlTYQ7ZIN*TEtwebj^B`pa+( z$hB3jZJhRo_9Z`koGI1h5^kDwW0j#rb1Pw>fB(~+pJ|v0P|Zm3c5F2yiUw~A5C z;Uo0{d<^B*XV=K6sHg~~K5%%5N#%`=T}@cUmuiW#vj5Ca8W6f@!o-XA@wU%Q%rHSZ2ze2e6eN)y6yTA2{xe9?!&rQ-LM zeN-}`yvETIOp^MWUaW^{nEn}6#tjFDOH)%ssXBh9if?662BfOr6!Z{L9G~1paXvRF z!Rw!3+)8+WBQW$NUAJTaX;24MA*-6`rvsj4f=wI>c$ z-^rp@*)OPM^U@&ceS4m>VknCp|E_B#n@x;?99qbIy+HB4X7XT8uBftdxc~F-el2Ro z8(Ct>2ITiy{R!YVR8HuqJm;lBPyl-v{uVx18)A{vw-n;jM!WOxt2e znL>4ih+pomQPe&Ar%g)7GJb=ri zId&=Fx7T}kj<)WJ3O8;jCO>lobBzSz9vXYxyE%>Z>grjEV>p;W^=@Rlac zeV}9bWAmUPdmxU?NhOZA6zTA8Mz(vyf?MqQ7;(_97jPf8d;T1Dh?~I=Ouo6HBn{($ zT?MXvmLO=R2tHeXcGqr?`Z}0~iV#l*KnDb`HcRxz+?|3}HGft9!C{x>(I6tBGN4?7 zu7@0=zkBwN#*mdVw7;GzEaV3Ba)y7*3@N(psn+UBWU!6kYeW;yR!Oe?$tU#*x4XPWx^z6e$lSgX@qhKLY(OgM;fJnYE z{#W<-ytyv8%26;h7VmS=aokQlSMdJ*m;`%eg{>t|&%dt1XM4jcN_`Ny3O2=WlA?dF zg3(;{Xvbz9m|x$AM@9^Vu1d`pXWdWdSOYVQB(y?5c!TocoQ6i=@M0Fo@DhdwB)0)5 zEa_Lz<-apE2aLYA4&zm?JzuMUq=>Tj9xRy^NJ<_nSWsmqd6~p-Izl0@T4B>h<KKOn>DP(4(T4^?++~Hs!|AFaDjFn1w8nAEQQsK+keKU4P)NFo*EUsRBXT z+BZ){5g;J#H>;Q=M0-4&;dy{=a|z|?dFQ-i`~sZ*g05I?&SgeFj;Ihv0Cd30!~ zKb__ET^OCXyt($H%Qo>=@UnD{UKjp&qgd^!K^t)IthpZG10%Fag7yH)O}1D}_(zBN z2G05258$c9awuGUzun}Pdcrlvm7^Od)6O#*Ur-t(B;y35UBS&;GzZW+N{1MQbU4nK z-$xev(S%^mq|C0C0lGd?~y-IyxM1K(U^Ny(uDEG+!qbA(lxW4fA z6v`W^d~0iZb{{4HQsLaWb&PI}6HWyCW%ya1jZ6B|(0&a?g9F-lJ5-_`G28Y>5Yn0Z z6Tb*+sdjw#Dv)`N;red+&zXU|0*X|StvTw=r8{ndhV$I5TX_efT#Y%vDvS4(T7z>W zZBXAVitg-Inw3&)R|`?lBgjk9Z#_T+zebwokyx9a*(N9Dj_>SdvR5nZdFa_&tHAis znTI!{ndOEZa_tyUVx28*bbf;LOw_S~UrCRv_uD{wh0Y-zu&~P#c=X)El{krJ<;T?r zW-KK1>M_Xqhg$H$%>Y=vMs#S~AHw=If27j8FS4P&($#sN_Co&4J9ocegC8Vv3KAN} ztAw4WqrqXM*Emr5dR523G-Hxdbj7P{&B2c6#?_I7;m08cmvFMT1* z%y%CyDm+pa*n!gXh-U;=Z)+ItTIR%L>S48EO%(4RmaponKlM_j>{c0)l24ozdLW9i0U4JvVkT!=GZz2;-SGRuX3WONL_b zNuRyg<0kt=MtX5`LTbB_9O+Hwf?~<|3Qa@}&~a~+LiO10UWk!m5-U4wMmZ(cqxz8W zd}tBsJr&B_4YH9t;AouLel~xwdgpFNMrS9JdzOliO#13fwUA#=-7dbOTT}8Zv@3NU zwTY%Y6Nyq?iFpJ{Rq3L}5eomNzQEtPm=MGynD@a}5wA^)p>5QAqbYf3zBfxiiNdYu z1e2KTqvQ3Q<0rf(WLg~$nwi^K3T*X)`?zIxs=wXOfDzz}Rm@Sc*frd=y2i=hQb+)4 z&^e8lln6trv$L}sCZv364s%VUzf5p2di1+IEGf4Zhgm zxXd}P`u!}VTTlOxZEGxh+CpaMOBTW}coJPFuydW-!oM`k5kMeAyfdqGxQ&FtQnH2` zi@>R4*?h*YQj!_a$YptXK64)w4jso&@xB@p$4-D4bZ+jtl<=C)uUaWDM+7yck?Z<% zxZTRjD3fKk8YzslI2{s!-SanvWx^S|!heA6#?_Hx)W$Xu+o>R{cQ50W>$C%%eC&;H zL;JAL?|wSkpCBg(TrC#5?rc6+%)aHJuZV zBnWN^1d^Tzt-@{E7WZk2$nNQZ0bT4W zd(z4>|A!FPSG1V6%AWIIz=_sXIda`2W;kp#OfcTV?}@_`O@c(};J6Axv3k(Ci&R{KBl$)kP2BshdYbKjIXtvFcv&xIOW@*!~ zB|#msdFG$}a3}RB3bN|kQ@<%#9vkDgK8(auHrHp7Nni9Vj1v6Bjzmtk@`h|Ocy2{#~M{yunJZcM+mLmM@M@hmJU z9bgD8l8)xSA)GT~8k5YxeuftlV*twEo&13w4-)vX?>xje&%$ws56|8xl;Pb>DTncH z;7*S9ILHf1Mc(T(+Y1SKTE?ry=5@7pfLU|B0GtB?eJC7jj8fw%D($RRu^K4}s&UT{NzGPP zX$#+j!n6Ra(?0aTe$);VN@%F_Y8Nn?@go6Bi^K+IT<#{xldQ{HQ;bw(=ugh=izRAO z$E}}v(zS*D{uGiQltnY`@+hvl$<8jXGaXscKX2jSl|FChgDNI8zw=KU0YH2=_buNozw{I zYd2Z(DG#&L<^l~>(N>HnrkxMtSCd-Bc!05tUo{>t0yzlrYcBZ(Ovu8%*uwdr5I^Ln z1&8Ih7>0C99L8KyBRTWiqT`Z-r3TgF-`ahTp+%X6&At5$mBQ#h36Yi%_Q@)Ri)goL z$J-2qF|wi4OWq~nsli+Bmr%x`wI2JFZIFq*OloKK4b`-<1e51F;ZefCy}jE-RaI#RCFj^}-g`}cVy7duB&{2dl)j8xl8tqI_~u{l zjkPJir2T+i44Z(@eKPNarrd(!1_ng)%y@EHG7DV~HD_X(UNH4;IR8uqMc!>4;)}65 zqAls^^q!lm)4(*7HcuMFJp*^}pkIiM#6Uo$bfndqMxf|9BkY#Gb9>#8Jh7PSD_k^* zb?iX|V-Sqz87a)9;&-qaHrCDPAW}*V5?xWLv}kHh#Y^S;RSR<0>P33j?+~kDyfoV4 z8PS^BD+#%4#x|cMFo6?^mn{k-Yh5JGa$7ryiwH|@^FDQC<9E0J9deMccT$b_#*T!U zS>OlYD;i+mjDL;qj4NA`l!{Y7cP>y^eDdwj@5LrI7m*XFzsy&4Q~i?^8!DO`s9{}f znhUyVomjHXw)Opjdh0+(QujwEgeJ(YBWie-+LtEI<>4w9U>L zA~fe~mD$Xoo0-1$($3#p+dJ1oHCwYJ2Q?BpNC>{>KvC&Qa`4-$Uc{Y)!z}`S{8GrU zv~2qO{^Tsp8%w>-^F}9F=;=A4B6g+7&LYkF=Bb^6m^VT0;hr20T3GE#mqp&v4r{|3 zbRyOIybe3)Mj-=h!nHmn39!m!*k@f$;w0aHM$TQwhu!geRMynxdz}xsZBh8PAo{fc z3ak@tKC{)BL_eVj$~yvTW#!t%-TNIpd;T7D<`gHMF9lj95eeN25@8t}1}ug}UW;FF zNE_lnyQfRz8sx*LFSn+%B@nO4o7BS6co+MLKdni1Ylq(fL&wI?vHjJK+k(^fS#o56 zv^A}tXt+TO4N#pEFI#PSJ@%ZU@8cbFGTQTYrNwYGwykre5nLo;H>b&dFdZosY-|{^ zK$M!iG!hI|JVHiO{dUK^Zv*;rouzPT{J2;*)z&g!1} z>~f!>_Gk{F?=Jkj#qXGsh!#jjE-M)cpm5wZw! zX$$5d2z_sK6|d?{E9*D$Mo-N|9GR$${+8skOMC5b-lzijjc&xND*x)DpQptTLYt@A zHN`Vpm+5zVJ1&l{QbxgezDMB4@}=?H zOD_%5aG{@H9gK{#OkHpb3Rcg>sMcr7P^3et{J$#cBMQ!t32X%zLaa}W*n`PQu4jOMn6Jo<^G{tFJHat0U?E0-!1dt zAc}a4RvfGI_LB^nc3#V9l%`EJIUYk5iu)? zf9jVQD1#Rf6^+iNm_mGw?Uw0h#y{iRbfr!!I#*LiQd;;$YmB|UE0jdyC@lma@);{$ z&E)T$E{;w-*3RY-D+F=l>LGJX0Q%)~|;Sx|)OjH@~2dT=FN;IVp3X$}X(9o7&_c zU|2*JWFq~FmQNSc=yk`U+4IIC*DHilUeea5h~nYhd0`zYP1%x7QP=t%b5eEh5 zEcOCvl~b0{US&P_k>TNXS>zm*W99hCzFWIrg@#>Rwe8E}J&JwD(ww*EpB_d=60-V9 zsp8bes+>f6p4q?DoxgIBv}!8Y6?B(y2FGqkoMSr()9Le3@69hv*T&!Gw{>pxE_GcR zEh})0%3>S4c+{t$KqbPArYv4aw3jxiux#^_ee(A^!4^i$4x%VKy5pq zp}d{6= zt#i@5SIU>-{q;{<+=Xt(YG_|OlG$zleV|}yCmMBa<0(VWAQ279NJDmw$uG`=Rz10C z72JJ^9o~b7w{XF-~>$3gUI`UtIxA2Js;XNLj7fO=yXi*zSXQ*B0qbKB7c%V^n zsxgYl!e5ir-@0BM9&oc^buZ7qCpy<%ZEdG}_&>hSy+9wLf*WFyz8yW)2iPHzKg)Z2 zJ{FCiF8AF%Z~5s1qmYd9N?G$Vi#wx0aP9?{)m^3^O~rkhJ>Xe$753RUUg*V*YY*I=~lM4L<46VQx~tnG&wa#>93OK z&m$PRPBoe=%Uyz7@?i0c0>V(w`T5yLU}sAif%f32{tVh{OanCTN{|W3HCzk}nN@DX zHkC+gS^Cx0y0`ZXYAs(d+XUt20aeMD7f!(Q%t@At5iAzI{!`dp1|7`<2c3`phd#%v zeGTOO#~MOj+MA8%sN}whOu&0Xy?r<|P#l0LUHrCr#aeh%`O#*r^8N_{`k&@fMGV65 zZ551Rqc->xUf5Blg0PBSMZ-i{8D*Spz-%YThgxi7aCC1?aDO`X#tW&g+W?V^wju4L z)ia}MeRkra6^@^8SoL!2>e61k{`Pt0_21)_i_rrLIMU`mU6%W|7NC3!cTt#w-{Y}1 zi9-_rl*I0bjf(QxuBasX5$vhP%5a~DSCuF=|K@mF>0*K#SvvHZ>xG631dz0j!n`5Q z=2+w>q+C0F*8g-Wpoarc?AB*qR8q|3^Dv^6I-tq4s*!jePv zMxS2n=EQT9nbD_Cigjw?bZMVYJfSrhfjpxz1W(YzgtN;BE21g#)KzjhZ zfU9ggQ<1T!seD@iOOU?1KA!>cCeX3^l9WGx8SPF3rL4jO)6K*!Z7fkxpO^R=18NBM zGfbjR|I6Gcl_+aaUtO7FsKvKpkKOhJzJl)$&A8jA^@cVeoWL^mx(?PA5WMz1_zygV z(3Ok|xB`oaug=+Oaxs)J4U#6H8%^t~!{;h(_+?K1a*)fm%N>#EXHv#aRl8U^4kM0} zU!DzcqjQ!Y%20l!JLp&as_4p$)Z_z`ABc2R>+}-E)hFBXNG^dEJsRZ9Y6Y%K9p!<@ z)6(~h?*wmPmeifX3Ys5aKN58E$RulwvlEHl6r{o-nL!J)2^%mp`7+_Z|I57-*69w^ zq3OZ{V`+`+!=<)Wfy6nnOY-kHA*knTO|Xx#4ptcXr?ak zNI&USQxuuPuiiwuio-A0h7{2%HON5#VRNUoUGGc1g}#X0LEN>Tbm8cNTlJtuFO)xH zkumcC4OHF9xEj2Ax#YJ7RuT~$8}IwCWc6U4xY!)NMETGop3Z{%0NF!sT{(b3!dXel zUZr8y(eun%6l(wYC{EHKCpxYqOCw-fgjUy@n>(-njLeWwglPrQ=g&p40q*neI)aIq>mv!hx4 z*o>#Gi;)FmN((u#u*!r=nNzGmjQ7HfJr(*LE-afVky|~8ImJCnxK8p~cAjZfX!ft;2ph<9oejGZ*IgdwKb0NcIiTjX*Tkc{KM$6QRNS!(T3gF}aD z*hed1`KM={gbA$(EBEy{^D%Aujx}cpT*6U|rYgMdKKPxzv|o`&((Qn3vKzlXKSs1{ zdmJZG-jV7~P9CNfUp3!+{C9otw$ekAgJM2#txK5FgQsh1gp99^R^=3AJ>!RaToh`Gu1Xr4_qBjXw|n$4;;>C*SpA3 z>ZAS2>SFkW)&!~4p8e+p7Rj4rAusBL?CDUXRp^|j5uKf9ccU4}`6V2+)Hhyy(Mt~& zU_FD;zt^y5-J(%Ds=V|qsQDB!xIMN`f|8wRGB2;=g5v;w^F1P7>;S~pr`=UCN6pk~qO5*`>bN-zS{4|ev|Fd1 z@6T<4JH9PcVG^JA2y`2>$GyDQ1g$(}GhquWQ9jSEF>somqfkZP^?8Q+Y3-3IIOU}( z{jSk^6Kv%deFefn{TVlF$`GWZQkAUCNw{!GSnuybKqqrg{5az<;0Wp=CfC}7y`mr9)&0Qyzv!kQB?x26gtFLjI+LnJZpc+ocT zJ8ISD(NxXvX*B9{77qhjk{ajdcj)N#J^TNFz{|ID)1w}4)*rcipW1%J`Xp0*mgFS{ zO-TUq6rw8Uz&Q{$Ukr{5%MKa^v_}oDX)U_wN$+sEDw0WT+h+BNY8^<=G6)abP06+IIn!`NDyPLhcmi!FfXU zywhxcQb)*fy*^5vq&SA|Z1CGnARB-I&$b_aQq(9Tbv zKDj%=2$=50Ips*>5(?G*qy)P)4FKz|&yl{EcM+9wTW{2{gAY&2;ntZTACwuVj8m#>`4I+UamFKEEb;bUlcm)`+(r*xht@}t{~fM?Y6OAHt)YC3DRyo*|i|A5sCFyfNdBDR5lSUoFgzvY%xFc)J$@YcD6YzUnc zf=&GD`{(18e<>*EsQ+&TCCM*>FRM6u!sjZk%V0Gs&2H#Z5xCSRP>jSet^?N@>c#aR)2aT~p zwvUcXve-ATwOhY-3;tN#Znik3$<2up7NMsYCv1(u&;P2(W6mDJ43Ef3zkU0G-_dD| zW$2@?P1DE{d!rp`+z+|rKk3A5F1JTOxK2vle=i)a=tf%sJM#Yi16BGqqp^y4DouOS zHha|<`TCDw{VjrzLvza^M8EauX#Y>#Z~y_HAOmjEX-GeFHlOI8Q~Nyx;~X*3=B$UJ zxofAChOOw^jo+Yww%PxC{~+@hj}Cn?MwQ+pc@G9B%I;41+emt-wV{{SV2yo7tq z!b})7_vufW-A51OL~BnxtSA24J8jYaTj3@N>Kwg}d9#t%;f*feISivE%34hLiO+BK zQkeXMo5*NI;P1gElgt^7dgYH9NXG3AR|*zq2Sy<-Lt`tB2~jl&kB~ADT%IJbCyx9;Rm9l>JFqsvN}Jlzh1lSXOtHNm9yLTs zR3)!32XlWRxzYBG0nvpMw~3#sTVeQ^+X1s1M2mI4&|XliTB2FJq4K&`6v^crJOjb- zH!UO-`}}uVPETIsA}L=h-}Bti<^ko9ro88I?04*YmVQKBCHuYZ^meK>Tmm*Ay~(^w&Y`Rd%j@7Py=bwt03+59JN z0EV4VMmdZWg8SGk&4ksIW3#kV4}Fe;t0r^pgmo-cM5U z9A2kwH$eYeRpRu&RV7)VDiQL#U4K7f4P{iU-KO9O*R~}1(~Tjp1=-oV?xLVarZMFQV)s=VZy3zFUX>H`TkT&eK40`Mz#94zRm zNx^_-Y)1aUhRux1e~2Cdu@wjBFQc>bh9kWj+%*u)cn;V}-C))>stmi8nc>`wK*2|& z*0zC~W{6}u@}Ci9OF9BYU~(Ptx!_f+P~&>OaktSf1N%F=+cN-vuC~4h=;cdDPU3^F zm#YmB=m}|1k!m9;swe{@VtKLJ{$7qw_VA;4v)}thz>lgQ=~=tO-4hEgw-@mp5D9l1 z!drUi64^Y1Oz0(HiEFJ9tUWFW%|YkP*?{xI9^8QMCL!(5!6zBhSPvCT2wr-XMbbKi z<^b%Hr?l(ir`RBw*7ch>=WE`0FL?faAFjP&c0`mo2!Emn#9O|mmX_jK_8ply0J3)% znDkqZc?Tly9wai2jEt-sqEodze!rX7{VmPuk>fqbs)hQ6~|^cL)?jzF^DVLj_Z{7dQXWjRjqM6^fnS3l%sekp=zly5(wF}!xrHsz5L zslSa)!HbfTCpOY)EXof`5d-@VKj!PMfpF?E?S{(3`SiP4I)~oRWj1;cRB+IGPuXz= z!3(?{@|H{JKJzUIDUw+I-jJ7Iq?Tm_kznL=Fm5^raTDE8`h(yC;5u)ECPDgU4?U#r zmPq=Z1U;_By?r9L;sS|RY|DD{c;t4*9l{K+^c~0=h?LlpB#F~gXrXn@6og2WCSXc( z72^*^%ImO(^Z(_yxDI_pN@gwOzS8vjq~m%1r@}=(-)Bmv#{tQGwOxFcrra1y5?F{p zNQ%JLMvS7mbqtbG0sd7$3p1b$wtRr9EY(pOSR2w;V7u~YZz!j;a0Y&XBob_+olmIo z5C{vQ8$*HHa4wPp^V4I6=?FfE661U3`v>83(Vuv;3T_3ad}_CQ?9}B<7)AL}s)n0f zid6tI`8D=Anp5eEG6WZvkHf;*4f$7sqmmu(V1U5S=PaP;XgF^v$QZo}=dZQQzY<1! zI?i6s$9QUP)$1$D_y9tqbLV0#VCHWW76JdMPMfXe^HQ2 zUh=$)$RH?Sy&?1v@x-R-f(nS+I%XBExK(6ff#EEyz-@a;&ZY&-CItQlLgG6R`TVep zzoYL5*}6ZD+f5-|w(#S|>O%iOBrs-fRPlM7tMWO53Qf&aaDaUgO1|5vrH zYRAGl*$iQct3?lf4 zB_-XC-)j{TzlQH5S`-nVKt*Lx5mnOoU5n@}QxLT9iEYI3+$839UzX*P*=cw1-~KR} z249@)kMPI;Kbk~`m9*4zX;M1NdcwUAL8&@>e0Jp{wr*Z_bF;_D>DTGqiy5-5;G#}l z(SR-vMH)ykPomNgo3P-(7Y`z`K$ymY!98-E63unvOh{U{9Kn>{hdLlbcJTA{y_=2_ z;l1jO$iGH@&Ctds>7q;}mW zrQk&p$B_W-L?=G_VK6AYlZ>SEI}GdgMsI*e!=>yd;4&!T-Q(fg8N=}V+t4}Nl^dp( zu?4ysjZ@gd!Li)Px97}=z81}RKiWMuPAcZ&Np<(zD{l95e{92e`qS3qXqB$M8qHii zZI`GK7hW)vqByYTd$a#~XKX0D;SYH;=&7{3-??}1t?0NSp&ikiB#irC{p6?A zb>suty;cIAQ>Yp@DL||8{I6bW7Kw~VqeQ2Ro|$FN65?lf?@|dYNqQ$3977nGgf9?p zp+rsJxO`cluK93(TMqgHMGDZ+`D9F)RSeS(A>CH3l(r-jRg2{oSzzUCxg!`yd0i{p zuoQ`N=Iu>MI>iNi`?yzvc2o)PE>P zZm(3es6PFd;BM@$DUPTyG(L%Fo*K<^(a=uqIs>+u1{dKzM4^g44yIO(rwXBQ8L!Gz zqMVA;T|po(72wnIX2EvZPmr3qVSmavH$Dw?8!h6Crj_bV?HHR8>OrGPf)MCzuc68b zdNjYL%+*^u)49P^iO%sjV8`Nivl>+yZcgQ@pI_br&8zl7!mivKDj?BoI2+}o)oA`X z!>O%^)V;=@o1;27Oux{J$e=NaEeu-KFUEZk!s`dXR5Q%tEbLc=dOGFO2$W{6}9}f$={b9m1pWO56%g=)^Qjb(z`tyBzMNmVET}5E~_p zYo|$Qi*IbprxcOF$XIv?HF5(bZim-KA#9FQNMthMdR%ni<#EGyB4ImADg$YX|L zaj~_1_CRm^e=&C6@l^l+-?!peIYzRwM25<=ow6%xrFhmd)Yy(*$1A;}(D zN7*Wb9FY;38A)8vcc1b5-hRK|?Ygf2`lsWJ_xt^Nj>qHvz{qIPH$kC}EpIL$|E>0n zLhtHho>B^)pK;&0UA8*nm)3jL)25WeF3=Wt!C^IH@&4seGHQW;dX`FX5WFc`xkhF7 z7&u+WsPFJnJS!7)HLq@6&#e&?#49}3=O1Y{xH?EYnXskuET16q@`&E)C;4wSWj>kf zGXYlNbb~;ZThZmf%8rs*6GZJ!Wc_-8?LA$?Ul&&N>BQFi+C1`C@6C;fPHWSI#pjZl zqNJ19Ysl;p<^{LG+?g1Ak|ThiF36tefRY!J!QWJk;`=D)qH&Q&(gv`QVe+Dvl>*rR zZ|Xws{{Nva{IN8CNm#*%H@y$-W!uW1jQjFHE7HJwg7vrX*44LNHkr-6bUl8l;>di- zWhL==((siw7CXA;*&|oU*AiG{{;2EXvXi)tm{gRl`{txcJm9i-d2;|X{Q6{3%LhO= zcIMLP*m<+1tKuK?b@SQPgZ$qdPDxhV)(}D-bn9`L?YNNiDH(ffFW#xT;m(45U0qgk z{h0%IzY7Il7txX1s+$vy*M4Y!a<%guAXDj3Y6Yq`n7;l`>r93B56^OcioK-BRlbIe zB~;1k*nBopLlO4C+xkd_2wHyY#3^EwR;$3al}JHuS^xVxm5VIjEN^qGIgvG`rxVYd zGE$J31NqI|ZM}{(88SYh?e|oCzrsN6Hyar3LLxQi2us;{ABIoK zYztaE9_PRE=G0e6Cp_$1boxyzC+9#9JjzEAZ`}3RW|G5l%6&Nc8EQ6-QoC632w{qh)Xm zBW=d%DJCEdlo@Q?`S7B^F{-qX9B*aS{r25Ehp#M}v{#NFKR*5}_2^lv=lZ0D2S#7J zr`ac$y-uWScKVRor84x!plE36YgKw2DAOR6N?r3)#$2iT)SDXiCy!G?6zV_1$S1IW z%#+K^pjcn)q0jtgHPVoc)fV|_M`Mg(#Gqc}R%Bzuj|z_^3Phe#F+Mqn2m!0l6$s-k z%0E-wQVXM`PtpmX81cXQh+vy9($P)FZm28(dsUyu<8Lh4s~KMz-~)FawZ?$sFv@~_ z<4iw}8g=u?)$)g(fnPw#W>?U0P!YP9mcV_lAJLc|gj|T6^|pYB19g5)RSMtT2TPqv zUNYOx9}rXLSSIvz%}_wS;i{MgYmbtOH7lBZj}TyR?7`DZ_IlrBgdmvD{4 zyNB3b$s*z}y0}3rcG6f0raCyDo=Xq%0q6MRn!flyT7XbzxZHRJGXcQS(Mq`nd?rR?aRwAEtQh7L2ZF;PkV0wP+BKT1lSKB zQRKxBCbMVwSoM|EE-81mj+O>$XC~be zCNt+=R|IT&((#^K0^7v9EGQ8^VSX0rJil*G+|nNB&7OdtQg)jp{5fOPMdv=lBsh@AAeh1 z(FJ@|SH5`9l*(L^2P$l8NGyy=MzIsQ#b488|!d>Zx{+UHd~6! zJkoFHeGSps<43<)Bz?z%aU^9~J z9&dghH4J0QOa?PpQ2lkJm2siKxl6@mpgx674ZfS5lLy3DMAQeiUk|JWk1FBVKzXHq zc=0c3Akv~4&R*mw=##qRaQ^xTo5-k=?Dib5I#7f?T<`m`%-%UO*!kf&0txf*CLs@q zc1e2kbDK$XpTJZh>m1g@L+}$17)H`>G*?Dg$3>^gP8*K3-%H37Ywqo+Eg2F=O_Mh3 z?z~a)2M4m~CHrrt`Tq@8qmvwql>C%=l!hxUeW-A8kN{>qI!dz!62LUtyWGlNj~_sN zLf3ysNo$wyRVvZ+%$@TQJ*BXOm7e(7bNiI69v^c-cG?LF&LRq~!@SiL##W3JUmB{$=z>os3a~$vx*Ngor5)~*|GMDO>r22< z@?%#~K3$S$Z+c3bl(ht1%yD0!?KqC?DP(k{!z!k&HPTZ?Son;U;SVI2 zaQmOGz0hwtNXCvnUNsWIJL;AdhKcBWij2Kx05Hz7W^#q8&5-3t*;QL=wUPsJnxVw} ziyeWXtBLf$j>i&RsZJ5(#c1)KuR+t_;OfaLV`L=929yfn!}uRy_tIZy-s`5(Rt-=R zN#?J6C|W_raD5HPA zXg-igrdc@+KUFEM~E`gMe9t$kz4;^0YL%#8C zHq&VX?;`^yl)oSnh9$20R@kJh%#Ja=TIOvIpD{=_KW7_LcjuwSrvQN~Po6!KZQO2? z6jU*8=j^2Eh&*L~MBg#@y816HxNz8ITCF_0aM#%5j%0Ex-NTJahRDHaSx` z(`xc~um4BGm7FU=_iZYNTv0kIO@TC2i|(I#U6(%-r0Z z$J^$dh$a6ttDAR;QEIte3gEYlkTQLdI zfV{d$?{KYNyzVAv5$BKXfK{oxLQP>gelm(%k+i8ZH}mzlZ@+O5!=%5N zTikg18#2PB<55$^Q86DVj??FIk=4k{JAKm166qdw5kkfGui8^*U$~z9{DYu$03mk@ zD!!-n0*v)U@&-Ah`(3K~-8_US8lDf83}O9dPvt z@0O7a?{`_pF7)UXvk<=d_m?aNDQCYo-6N|ODRC%{EoX>#u6oRvu3~G?bxl6BlYYQp z1+xRto$v$yO6FhxBdLkZMcRdwIl+M2-pls^O{OjJ>Nh|tA}-f#fxO=>d}G@}7Zh+N zAGs~~LUoS#e-!m>R}^WgaGqM891kFP;oZ7IA82BfTg`ruY*6 zj;YYLcQ76MV@724)Fw@D=#c&C`nraoDjO^QyKZno42PQ=dUtK@W(L>r^+u*$qUuxS zBqCDh`fQ!jEr1EtL3rpcOyOUn3NR}kTQ$(q98iun>7O(m%Pj8pI7Y#98K^x+904UOh9=Y8)j@tCZfqn$0a+ zm2>6Yb#i*OBSRiHRlr5Pz9~IG+iL=URm5ik%6qN@J>~6Fgae< zwYtGOqOGm{B{uAk3_W8a)fHy?Nu7%cuVf|^V$7^3NzVkqaH2oV>by_*0^tbx^Cmsgq{5Ng4xajJ4?hZ)9GlDXQ zvJ=y{nv(VrddzN9laoS{wWq=k^C{Q`QI0U%Jk1c?p07HnF5Pc`0HMt^H>fMu$nnE( zsSXRRM^lxESYld)?1cWxxSNo+3ewjMKOZC90AtsroEJ}? zFiKuYrBk`}P4Hc?IPDh}jG}t2_}em3JxyBbC&DqIdmd2AI8Fp^r7V?xtckJT1ExvR zBfV5%Q3=g3C9kf$qz+w;wW|h=|o*4*m=Qy;o6yW!zk4O7FAb~BF*~ZI7m__ z?BY3Tc`{E=-fR+#l&FL#BUz*Y5;j*z-!A1cBo~$X05S=W19tM9OBP4WA^x|kto%4LjW7c~;73Au8XfYvUVGk@ z5N3i*ycF^LPYb1uc8k4&5!BI4kSw|?c=H3u(KQTTcZ(v=UFV@^&==;C)w3az(zPU_ zmQVjLjcmSr=V`={wjTth(mO2qmn*p6z>Z1QE78H9AaA|-cu54dxsCcWNAj-Vo(6es zgH`q3{*)z#m76T#S`%!{_zUuSo!@9Ua(AoskZ`?r(TZnK+r|_(JI`n^3vagU#eScP zOIe?Q>Zg3bw&PkJ6FeyXy!CSwcV?K4K%IOZxh;<2YJ#2ri19Z4ZV{nP;Qd!z^64LO z31M2OS$9u!l7an3rhD<_Mw>bxz837vlQEGN_pEF9&_Ycfeum~*S?mdlnqRQ-*2hZr zU~b#MIF>y~n94kG5*t#`PFavEu4qigqBeP87qpU50gqjzMr>oPYrGQ6v^FhnMJbxa zHy@**H^#G_2s180dqmaIS2pJ~DKOssdI=KCvBpNJ28M>rLA+qKy)O!_(?$m25h=*EcbwugE07Y_0axnNGk}V+bClLl2Jun;J=rBc)nP-``u_R{$uN zRK9M)XIev)8WVG!KeZEOFvvj9eTO{INZm~VEe5`hZ?tzAO62=#oFIwykzvtUYKz;b zy`sFJV^e}(;yy41>_vE~wA9cl_3A2Wc~AU{tr00@e63X7n|8(4;){#kvzJrIh~@O$gk zABa;6{;+E`s?Dj6?D(5Vsq_l>FE;L^VE0B+UHh67V5k!9EZW{Sl$JuK zh->ti(wX0l^dhj5dH!5&hwahJH%+f{0oy0TjnzX~mw$v})}8rcvS>dxqiW_kx1>*N@z-#j9-r9|Mp%O@5uNF`%TnK#1^ zGU=$}!OVxPz0MY-0IS={e_9pkMZcV%J%%YRYZurKsDw#vqG%a^h!;UYb5d4u;)~C$ zm%uhq!z5ok{$W$ZwEN@BGhGG}vJ*xt^`Asu+V$K9QyaO-q02aonNo0CcmpIPafzGnZ}n%eJ~?_&c7aoX%XcQRbEJK&^K7xO}{K>yg`IeCIB3W=hcvkUz6uMUcLK)zP;v87v{3NB8;< zl3g20$fybp*C*Aq&YaXhBqoA-4~{$sZ4P^satolHrp^^bh;fvk`!;}2Eq@x3g;~v{=}S%6IeryFUmcNNGohyL`slHQL+1 zK@l0q*K(RUESt?|z~Z;b9scUCxX0X*HxS05ynMGnOS`K;3Z}$hCg)Qb=u&mFr1?v+(pc9fsP9+SU;ehR03tM=Udm71-Xq{3xLIHGf**YzUJmsB+8MDtE_*mB5PuaPc>Q7L@B}DF&$&C2=hKw` z@N()ijA9ol2i29noh3s+dkxy;0@P#E_-{t( zQqA{o;b62-xVwVW3 z{y&`MraIhBwyjRghrM9R3C|-R*I+~)fvZ>R_B*epyZrboIFrkVcdCiQI(;ce(&Yb8 zUkXAe0Y=t!CGv={+f8(kUlCf)_*9t5%TrdveZ>5)9sJ68nEhw`U1ouOGaSzV=hK$Wc(@GIEPq1m@rOFd zS53OxAv#;LP?BtGM*lS>-LUB7L9mJIROa>P zw0XCXrx4Mr*jhai_ z$ba7Re|!-v57hrdW6`SU^NKOb{mi#e7E%+W`=v;ByB7oh=xwWgI#htdQXv0j{**Ci zXaTeJ$x8lGWSqJ21RmsYR<5n3GWr0p;_LDAev@$f{6WYi`|wvV>M`A)mx#tBA@7Pw zY5e#ua$!$4>eZ8hphJ%ec_3jfz-yA^EL1jhoiTm{_j=Mwa$KamfnJp9HeQqAG1Z=d$dm@I-FMkt-rsxib1pxjz?~#` ze=V(1&O~+fR80ivu)T!coSmf*)54?6&417E@cq)$Z#w@z$E*zU)Z&EMf9Alu3}Aw< z02eYp6OD=fPqy%d=%#N=%@7;BXR}3k7AU7;;|G*bIpB=ceDBGWf5+w3rx7Z$?L{3y z9Mnj^_OSY51qzfXe`&U=|4XyA-FS_Z^8r(UTT}xH`KI}|8!Vw+$n#QGX~CjA3KP{Z z@t-KVESN~>&+JwV8Z)mn{G8$R1npB6#DEU5u&|`_?5#x300J+Lnvv z-+U{IfVvftF5ee^tnhFYp8bri2-O%0=N#2`#@)ByVH+aue*6)Wi z-!@b`9w&?3{#@88#D(Q>erTTH?ijt4r+%3Ge*)-e{x<-ffIlMJb;v?8XMf%?*ZN33 zEsFo~tfb~GvVKDehyT93zmtY0KrqE13}wgh^@tDMNai{)eAV&)yPC^4_>YB)JtyCh+%^L#LWUxNl8Tnsb@q|)eeQgr6{$6kFI}+i)gPKbAv?*v$N^n;j`6x z%LCj&9tne9thH?>TZb@ZYHy>Pypz~>&re0Vb|mmfCeyTUgEl`y9bkzivqH3uV6VzR zOI@+Wkss8GJebmXT0B5$1vd4f%*sogTkL?Pvn~<&NY4-75`a@1fp+;BSsuE(2ku$< zM4{ALIi{$+7EjAo;!o;1buQ(~0d$T`kFo|{Yz7ETsd->DUOj$kseJ!b?#%&xk?)TZ zbR+k6*L$-dyyBfV`fT}!_1RvNB(F(`?r} zJTj_)%~myaurnRT&%tT>C^1Ih%{CfIjI}2(hP``BndvXA5bKI`PFfFcN2(%D&D8zg z>=UNKYNhfwdcN-AkQs)4DT=Yd@|eCAV(7hoZQUxl~?Li8M0&ab9L}A<>m$RvwY5fR}}Kn*jsA;XztmU<`CdGDpBF0Gzm_m8^e;o&hoi|y}}7>zAMsxCnxv_*US zGzHE`CtLhvcxEHJ$Phdv;B@Lv5;E+9Y1ApFo?f8NO#Zk_nHZ%mN4I2Lkpzd3%=7ae<-}O@i%-}~2Li|?$4|&(;8~@6?`nzRa$^em* z1S(klk`~96pX$#9up@j63gF>T@9bkHgjQWf0=swferep0oSK~4+W5M@dXMP2m0w2! zw)6EDcWnF{kZ0!QB`@yFML?-Gt28vwJb%Hpo5P%Nq3kQi2kZjex`^}BtD@Q?GsUN4i0$naYb0jE)qiVZA2Y1KT6n~Zc2{lt*dAUsg zz0oeU*2DAk@+1jrJCFbW7<8r$7g>5AHoxwHyguJY*dNE6XZ;9kVH=2MEjMV#C2_Ut zz!ol(TKI$!osvXB-i277?W!Xe)wiJD1L)l6yi~SJGg&4#RGW^re{s;4X8NRYy_0Zk z6}W3hZ7pGg7Eos1?f6zvh?a_IDQd?FvvY1IhVr5^kY%eL^?TceT(N&9+qWP;)k-V9 zGk_T#RIyMe#urQQMBJ=VX51qL=vYPaG|upS*bL+l3+werJAO%+nVg`;?p*%8=gWmL z$8;+xVPVqihG5+J^)Np&TPuoP;O3zMbt8*RVs;u}QrdL!4p?{Wz$=Nd2!IYt5nSu(m0V2Ww&v zwM66ct;kYDpUb1*e5)=RQ-UmP!)}`g;--GFdWt1=-)LQQNDZxbTyc5-#>bh=6Wl&A`)gqKXbb!TKG`&!O!mPQuJ-&4aa9Yi zRQ-|2M~=cr#hqk>gvreZCW%tMYgh|2#|+j$k&m8Mil?z`BFxm$Z_OPY=ec^Zs!!O} z-}MB<#~m^3t`bLUtjMwcV|(i*su-H2I6&v&^pkRK{?K1;r?4&KK%DF{^De-e?@}`3 zX!%5Fdw5V%1*z>kfOw1GBcQ()vIn5{sCWW+=?!>4S3cuQI2f+Z(Tu<+yCKJDdLAd9 z?;&E|7W#Nx65#M{{;~0^b%j_c9{bF@Qrn!H1+_L&K9B8CQlK}Jxn9BK73%$v}RO^i8(#B zwB)N%a|OI58MUDiTsPyi4ldDQzd#m-O9UM|{~DXjwvrO@9!aS1wI#vrmI!+0OHy3a zeRWe6Ar);p#dJY+&%&yhV-Jk)g1JXqrqzICs;!>n+6hiQ$H$~>5QAu>knVMV!YPGB zpMHZpl_=0y*6pykS9KwvwqPapA1%PY?6#LUrrj>p7(Z5R-Yf7W{-aW@Mc)s4ej_i? z=w(h+U<(^~%rvUSH>i8+Or+Wh_3Zi{PEEz+=4jd!NC7lb>JC~U`K^~m;B_S9S`a3;v!{hc6?T0Ny=WVziPYm>D4nIW=}d{4!pN8qmkNC zeA^V8UBYz4qDsSn^J*F1UKQ;*ty_!+aZ#~tX;Nq_6{qaqQY!NYR}%F#q>s>bE{#5% z;N4wvru+xf_BXyC2=1vLkf(^C8vHy~c2&2vJYfpe+&g6w6qWhf}Pa`IR*d%qFC}xB z(8Xybr7viJ!w(^m(>`j0uM+h>Naw!J$ctznyT?}M4YwV;KP z4X})TcUL;rQ zxKy`iuwG$@&Mv5(Z-|-lKq#6i044n_2HWlGWU9AZcslD8Ym~gC*aIVyxu-Owx|z7s zz*QA4*ESP#<<)KtjRjR!*q%^!S|@4n;6)046W$N4N})T)gjvu&X4evrmVb%)E%oY> zSM=A}?6i`~)h{6aPdY?qcBixArA(K{<5jyaA;l3NECh3Zh=k5D2k3-Q*91|CVLip$q626lgTX<&E}6+2rtp^~y(oWO&l~?W4F7&ERVrWMgO) zdvix|NDv?14n$gV(}+mxzY*N@`_-|Pe+X_QijOmc+^h*Vgu)(sUYD6}o$JrPGsbi4 z{n@o(nH{v7n#jAJ{Y$T|3gtbRD7qM(a<-j=>egAj-!;>&pNXnTaUyuS$uXV!lv@U# z++y6#MYm}){Yyg2v`5v?26+9lg=0AH7c(IGN_j+%z`K=z2SMaQ(7bapaA7ChomV=8 zwD8j6rPCSDB6zjZ6KByZYYZH6dNL!R-ReTmUBG?hsY3$akVQY=o3O zn2v+@{cbnpR3*zF;rxBx*~|wBqNk21+&Tw_kG2HXRa8*TjlL;L1R7_s686jM8{wb) zS(1gxmkA)HQp;&96Nuooziz9q%pSn?&aTty2D3jR;X2DxV$)+IFkeMYux4XmrXh2Z zCo{AYK1T4CvJWA?tidcZ0=DKdV=P^9MHHRd3|)@O?bIpEO8;cXQvsOX#H$?nY<~>8 zD4x$n7j+Ta8r&X;*DwtEyNHB~hRr3qyDz!AFiWAF4H9L&{v^sSQjE}H*)B>~%=KVo z6AE+fEY!aMI6gI#L{D|?uo;^7H{Pc`YLG2C2?9h7ABamR7`p~Q*zeER907jGnC*Vz zrTRL6$$Y|3hOqkunW+NUH=*`OkNtnNIk#^)>E9h$8Z_j~PPiKn(V_q9%Wm?b&*2iZ z=;(j)_91Gmxpq-CYp6BRSEo4Oxw#?;Uu4fZ zgDEqXFEd;(SbYsK8%m>s{>JU);@St!y>$Ithf!tJSf@7D6+mYmyOQXbb@Tw)HW< z<)w@1564ee%(YG3HlFTN1ENns?K6% zoVUP*)pW)t?4Tv%3KAp1C%moUM)Agw^|zp5anJp3#o#{O9vD7|H=|N*S5N9lqJY}* zgfT~Gr_DG=`K~-$&PD3c3#>Qe+gIR(lAk+7y*O~=a#J1~r~zDR zL!f}=xRA2`7`jL8*LB2vGq0nU$mB)>K|HLNq{RD=p2HEfY<^}t^8BO0!jfU2JGRQE z=o8WW#P{6nSCq&a0O1|x6siEd&et&fNZR8*Z}j&YCyBFFM}`*_n3EzRC{4?1mvGYO zFJ6qvYKBN+yZDq zvA0bTD+_{MGgL$(w)c^?A#Qz-A9SXzU_B(Co;UxD=quV~*KhWUEs<=*DzpjHSxaOeOt3X0tge_T*ll zlH9QE2C0fXR=G32W1*_0DhK^@yzJEj4eB5^_lNN(P9WuBWY*Fn_*HbE*Wn>U1;Hoa z?Wl&d)bMagtExVt`!0+-VpJ((N^**3cH7%u_yGQvo>VyxM;z}e zU-I~S{I;QO5!&8N2Ydn=tj<*O7?n zm=v4xH=Gs3+K}5vCqxwL6pCp+y_)l33B@9leDRz5i?bD-bbbQp@=0teUl9ngG+RY^ z?}R7k%*~40^$=O?b5e}j_A)!zonrzVH z$6*SMbB>iL5N`I}OYV+LXJC`Ul!mXE#u~PuqqRG21*Cl!%R0g<);w*>u{)jKm-M4= zO=vhl)RDy@8ZYVs47T!T7Uo4lYAyunT_=C=89?hwGD;Uo?c(GQY0_VreHVYtmS5`( ziBStn`6W5gD`YLVzTER1zctjE#;?&$c20z#CZ=p_yFeos*0}Wowv?E<1~2pohL;+b zCjm0}muo#Dt9InF;lRmy>o1`v;SBM8%OVX^!|`lNH|`5YEN} zxSm}OFW=@g3b=70=GCX{xDLJEj6CEE=>2f%NgU?pM$flfZLk4pP~-89LCzza&oX2t z4qp}cCg*Qx2=s^DlA%cRJ6cnxe53<6jhoR~$@BR_&9_yI7C7vnof6K}$)PwaZ)RI& zD*WJ-(`aG6=`fF&6VvJb=>p$h;tO}2xJc=a(QJ)$$c=U?o3gdE&`QOoeemjn1(fIQ zL~;60zs*CfMZ}mnGLR>l3$dyo#L<<6gG|91kV9yl+vlsl*TT4M=b(*310?@=ylZ9qQ{eV+Hbmj z=|veVb_WIq?yH|vZa@qC6T2klJlLMTG|tJ~!132I2$7%&BliqKMhMk}->Y|fa1s~c zLGtQ6q_6l;@bBESmAV017|OQ?=(VW6z0OwNMgmV{HY=y9>rqBb&foNSSj98OvxkuA zz-iiN9H2Zp&0dqcbqd+Rh17OH16#z z+!@h$&#nK1s4^%!lH~F_zF>BrS07^RK$OhM@hf7Z(F6hSo@-3=dBdG60Ee_y1Q8E zJ3MVX|19Yh8*^Oh&%j`Sce{E3MUf>{epsO;q)kYItkT1j-$Y{ELi8$s7Lw*j10_s` zre$xkJVlb=wgu;lTysFE{Mn{9p@PymLd|g|iX*V_;M|QZqib2VtxVb9s(vOhyUk;Tqpe}1I+h_dW9%drn1KfX;W0YwL*cPk>l zaMJNULW6ib`XKBqg$y#4`LD6|)`PN&_t&!&-4pyD#$hsbg|@vW7JxJBWfeDc^bSsl zCMx~tcK{SDw(6$0lJfy?x z$bg%8dO?g_xMDOS-PGp?LK{J;1~tO`5XL5ZzhSWv6UUYrm^GPq!n3g1-$H(SGWbdO zJPkBB^KSFz$$wRp*AAWVy71v+fpNZyD7=0te4M{tf1hu=lJHTPpN?JT6hZlOyArzZ zJHu^?>q8($;r0Z*RTe^-fa!m{G;9$|c6IuavJOTYeS`YXtqtr7#=$! z(U>K^zua1tc-n=(a|S14y7YMh6z?89`qf_m@5~V}JIKtyqZ-snl%?iH_Gp8BeNZh- zUREFHJA{1SxZEX98Vb2Ep6Jnnu=5nSD9mT3-2iurFFGqzlUn}na@*J+e4Brbv~$=4 z)P4nT_K;gW{dTn_OBjt2Z^ij(94J8H3?z^7k4Mw&h&3~vJtKFN4RiLagk2D?RqSz% zGyPFy3QvVuF{F>I3NE(sJVFUq9Q_^qS$ormtuRe?wyStURkbO{vuAPryQfn_{pRb2 zJGX?}syL!CGN^lmUWV+3#^C)XUVO6uXF;C>TlLrbl5zh{?zIA>3`+ zacym_D}2GP;PEAix)n`NxMcO2b)rrVsoo%l{J6%a6VD~; z=B`lTd7UFyr5G>yi&Nr%HfhY@l!)ofqq$lGKIh6}(8)5U-JzQq* zkaio|jhT;^+*5?3#c4|I)V${KqLvkp`Lq&z$xoW{u98z7WEuS`VO>MGH35Ss3*PW| zL{GIC$}a9=mvFUstjvp?z{}Er*7umdpyGM!x0%s+oui=GxoKsp~)YB^!6`Al}>+ z5GF?BHMbJp92lgu8mj?|r@HP=CmDIxj5#W3jd$S?OX%V$UNPz*Z{w8DDg5{>WCz^s zJ5;M6%$n6@Q*UtK{;{LmLN3D%yK9E)up`fe`BGMlM2#L{56(47_FoW;odtR!s?X%f z2g-kdmrW7zJ;Q#C=I7pfSH2++Z{ycgH@V{ueYXSb@N#5rRJBs+5UIyScHs1&k zWt#IK^EU%jJP7KLtJc_;y*|F+mV`n)3=A zEmzlr_qT!{HtSvuqf}>VcIpVZLy?qS?(i-XvV&r3jbW|-wFV*O%q`59^xQhgt;%+E z=j#S&o!>nqUvT_DT#z)ao@auePwl<~k^D>`j@id@gGHOS4Xz^->HB^smih3Z%ZkgP_XW;T z++={mm7RAR2Jrrk&VC)A5qXT_thR0nqP{9Z8`a6j@DD+C zzWeL}>fNdF7Z~-};TmW`@7h5QaFXjNO_Uam}E&f#-;#EPb;hc8grX~I`; zBsH%Qq{B;zyKL@L;3*zD8i9zLw-J!i6_lF#L8s?wixohEDo1_$8I8G$99bM(8^^ej z|1$Bg&7Dk9%5x@IH9&TW;X6=cWDq@qudlCe{R!^h0Ej8~g$RMKvp$Q5C2hu#tkNY0 znTXp3`xtxY9Z2lrzqEyftSa&~EN^>Ie z>k|%Np0%0(-dCWXoFAc^EMFt74%&3P`pmpMcMgT?b9XPnTO-B2P;1Wh*}R30Zc5bN zootB)tw%llKI)Ec_OB9VS`jiM;nGpS@t45a)s_vWz}!CxlLZ5|vHp-S`8g_*xIA3e zf|x9fvX#z$ZjYf*|2P{`)5+;xcb3(&nPMM$&%?NV*jQzN`ojPPJB~aa$qJVfLEk?; zEWA%ts`mcyG_osj>wCelSLRg52*5f;gD{-Zb>r{nb(D-Y5JqVg zS!f-{Ay1R7`*ElEWlxes?e1tsv$|JQq2_^`u6olBY1WQN=}|3wY8CS1Cf|ZoF;|oZq10Gm`-i$fLSZRq{OP$c0g+^q%ht4`d9A!CMevC{o$P!hW zS3{||@xP{jrb9A?s#ABSe6o%U9JVq**ODRUsyFq?<8-UiSW5PRAk1(goT}q9@T6zO zL5`7wWFVQKd}^`K_9G5?8SON2#v7Z)d2R<4+;dsZ_?5$LQI&vM1x{GM#mlZqgOsjj zkTyL~zC{MMqUF7SIhC5&kOVNO9bTh&~J)ala8zoo}+a*WAdafEyV+-tX6$nedZk&Pg zS#WwA$zVkIWtfNLzLDXYJM&HcgQpcU__QL@K{JvGV@CZg8}M?FER}&6M>CTzy1#uL z{?jD@LA`%|NpU9f?iAlP__`{q!8p@7-DK+KSEOi8M5+l zB9~j#`@KTDu=3c_Y<9_7c(LBh_9Qlu4%XgwmxW%5Z?=m(oKt_`zDAf4Ag@kCC4IWy zZ^H-a8*OfdqN&YgG$a8zhx^R2p1?`Vt5eMa|mgfYkm2uqgg zPr~wQjASPH>#>J`i%c2QXwv-$NktStO7RVpbF4UROfI$F2iaa#0qpnG{XI7=&@^Ff zsiUqF7ovS1C5EgOk@E`#C^-(yp%2V5lHHL+4h9Y(F2_OkxegKNL(U8 zI_Bz~U1t8Kmp@*comO=(Vo~c$X9E?L&r7t1Wms!FJE3q`=%_{cG-JbUVuXa^t9Ygc$)x4Ab@yhx_%Kg&s1p?yX zxzWJvQn87aA;>17P|B9hSczuRRpqHzSwT<3(leN6J-sN9-Km(rJm?W7mGu1JVc5Y= z-^&RDe)nDOysaxDWN)Z}_Ak&G>ZJod-;Mk@kq;*MLff{Za4F&01H46>n8>=nywhKS zXVJBttz}6SHoeqz5kRiIVcgZ*BJ~g%J_Nob|IO`(AQ~3?D zAh(V7lA>@bIDr^`v=4=|b<)xDVUHT)E>~W+_(x~(gg)eqY$SdS5dQD%BCCPfBC7WL zhbb^yhR;c2;z)*URq@{K!{rW!qIiczwEs-jqDn8VWx$FfhzxQ$S+78~s_d6bntSr9 zT;K2Yu8}uRtTyl6OSM}0qZS*E@0?~0evy~=iap>3`HLy!aYaC#dX|3{_rm1NM*%c> z^YVvHV=39kD7}hB$y~&h9a83(O)@n_{hm=E?h5LVjS=9E2Ns6SqDfg zZ%4T)-94Bw=RBqVURz|Wz!KN{kW}V$ZmN;e2AOzLQ?@pPV8$;*px{j!F|!BcM%LT> zg32G6^Qv2H2H4R7pC!*>`Vtj2dJ@$q#|Ggh58*s$;fuAxj`dHD?$DG_kLX^l0H%`X zm%Ad-TvYR{jCYBid3e9}cB^uw{4+x}>nqB%B<-zak^FW}eBSLrSFC(Mks}eZ`1N(4 z4sTn1KauWh*!G}YyYZJ--^n7O?#L#9JaNh3JIdm213p z<%;y+vmr*O`!%;*8dw_<@}( zK}bsVFkvh3v8FG4O+h~MmmDaO@m{m_#05rz6!ZfbBs_s6kTbu<8A|LlOgwZ)-D@WQ zh(hoUsr%*z;2D0+7{#8d~WMWURn&v@h4vL%M1Q;-*q)qWF0`ZyvI@o*16t~>7jBhZcL}qXr zD;v)bg?1s0&OzL=*H_*!lTrWIoVG51K1`ou9YB2_HZQ}x&lhfsNBG;j+p7o+;k^!T zJ6ody*;!m(wIolVQyO{&Vu^7Eo&r~M>d=T*xC_qKYo7zRtX=BD%gAzfIw@+K!4OC* zch+FW(r>9BRbbz+8|z_!3JPivJzMAF?OjfvJ9aLPBjcfADj{M1M(yn#uZy!_xkkPd z>O!pvkW9qg>VIHCeOju5kzld5`wOfYOaT|w8-8@lgktC@8cR!^ZLtvCeXLGr1-c+H zmDlgDoP<_Jb7ej?)2K>+WvZ+;ej|6gSkm&x(5%qM^?_U2o~rZdFTCPL;}#0Ti45H0 z-P6%rGcIfPtt+I??$73iE@z#Kz2^xelq>3)#vBrJEUBL1nCCW}9Yh%L${m_bckBJP zGC`waS<`V-Vzm)01diW7y|Rl%LpWlqRjB9U#{9>(y>C;GUz^+eMpyo8_W`rSfU$BD z(_tH*2VNIWE>%qx)_f3gn@ zm>QoDS~NeA+S6dgtkR8)#P5k+?5X=XlXaG_)w%>w4WY)ZT|&$5D$Ijr=_M5uY;>@L zk`gLDz5$M`N6E+E-a$sIYnBD8;aM*phjkB--y_PFNe7T9&t4ADSYIbs*L>>v*f>W# z@YIh=7Sd&bkbw9cdDgMjaHd_Kz5)BdyBN9&cN>B2G;8k+v|ag({MGm9VZIMY#qaWN zX}tCic-MmD{0kdvlu|hR8$%2i(!|NywMI4Fn+(_CL2CC_)i{L4TPGPqzE@)QJQIO3 zc0mWkzEsyxGvvj2i*m=VGnRE{N$aaz$hgmx?DdWXY#VAkMMiOqH-H7A@}8G`ua7>=dn zwol>I%w51qy~3gBCQh3S6{-cRxA^s{#sjphY^=fk?aXcscDXC!DmVV=kDoog@PBxF z?|7>J|9?0fdylNFbF7q2_MS(C(x8w{A!KvN-s7NQ%ZQX@lu@FCjIzl{*`Y{gPElQt zSD)|q`}ju{<@+xb%BX8I_h3*-VVjX+QxB3qGShlG`PRO$79$LnP-=S#%advd5}95>$b2pc zUwgH7J+-9an6{$jOuKbI zWOS)pLoM!qU-C!c3^p?!3YbL(W$rbQ^hQ%sV9Q?K54Mon*3?ZF71* z%IuVb&=ZB&4xFynBaJ`0$f@H1YDBJZV{3F_H~*~`gd^}2I0AP;Y+eYcNyK{W8=B-u zsJM+T<}J)1zG_SQKvb;2UiZ-Y3b$0aFG%;_k(NPQ>GE}~lB6EXbe66Y;&0=gN9yCZ3~;=#(-G=I)zwQq!VtL1##U z!~D}Jr%k9QkkdV=LBhIMr4p_j2KUXM5_KKFz;B%_?o|A~{ZY(CfD> z8m-jh`zHphq6W)Ke!n@vYRHSsFuW-v9eaLnbsH~h%v|OaCT{fIL$1Y5E<&S1j?;6o z7tgpt{MMj1D|N~ro)*l|Zg-^C)Nf+?diu@@qc|1Ia-9I)DC1g>6Hy*YInF&|nV1A6 z@-n=4-RR**_C?b|H7l(0(<}5ETwf3yFi>+@(s*R_Yb^{fA!bPBPQ95f?;l;*zD1=$ zkXfBW_pf8_TijYLj(4gM;3mwz@PCYS=h%1;wDd%Z{J0a9#=coieS3?Hs2v4``1Q$R zyZy-5H*@tVQMcf-!}ymFg#MdyASzdQzAsqPRq;TbtMnQw;_W$$mLy~P4JoH6^roO; zntU>c6#FaX?R+@Ik|d`omI|UwdIG%fsxosFV9kyeJ?^=gR;Df~cF3#g! z!n$W_k8^nliFmdBZopp*ABc)J(VV(*(x`Srb@lh~AB!t?4^Cqvdpvd&BW?L5)FFe% zmo6p*3WaT(`A*S~mRaaArhug6W=eKh)VA2i)NIY=Byzg_L<>`xzLZI4-*+LYWOXXy z<(00XRe-3j1FH(utbWTYgU!)-)in|Di!Pe8iyGu$Ko#{d)YtDTZhA($cp5sRkV_gykCnp@wZHZVI~Uj<&3iBd9r z1K=+i(Ysxv|AkJL!()0uLkrDvtLFy3*MW8vf(nmlZ%)NIzvGTd-;li$74_lV`^U$? z#NOpQam(l3&3q*&;_g=Hd>0fam0>!xeHAWYAaR`d=AIo!Tbm(Dyep*qVA}vE?H0mV zBgi6Iv*zTg)A1K7zjWF7Nn-J6`XzpzuXT5k7=ZvHs@~=;2Mf6Y)6Ew`CEAE*2Oih( z;HR{H^4p94u#m#X1}Ts06-vp<=gY@}CWx15K({-=OBSU$-MWeYvZ;FXF|SM0)w9CH zKYvL_R1>10lA8U_Su(%8!qq8&{^P8HrVczYtvRVLrVMPoelTonJwb0PU&NXIZ;IU0GA zv{%5~(z0*&>xFZYXB#)rSqVt9aGUOr4+PCQWQp?ovT^;6K1}RzU`23o!~450&7=nu zpJ_CD_V~~XOu85pnfbE%Jy4Pc^ZpK#JcTp)T;7tH==4%lWQFWqCxz&uK1!YPf!MJf z)pP65C(7Dlt`rnQpK`rxFU$rZxwxCJK~9-13=%Zz=i4a-tPWYieq}>#%p>=AOTPCe z`_v<|nzpbgG;#9rytB#B118>ZKw{|%2a&XcSqJ%_PJ0G9NBqQ!1o z9k0iq@R?^~%Rd8Ha1cMyXeNy+Di?fqqJkLCwT#w7Sg-Eb4wF7cq{P`M@C@eIDlc`0VN&w;2WU)s-3?matvPUqKXDpT896E ztw`dEvW>A}3a331npb+b?iedxpwJCAb+i}CO}Og8b-_pZAITyJNET{tUur9nr+o)c z%d=G8dG!zG?5cvevN z-Hv(2lO1w`W_9Sk@WJy@q;K{^ks^l`)`;->f5>nN+@$Iq@2&_q&S5YQh;h*GLimz zx<|BNH4-(OtjbwV>UZ^!mWd=y5UrGO-YTUTfa4@D{g$zqgc0vquEX<+Gb5BX%v1(2 zu!;kZn{+ljiy-6aOp4W5P$O0H*v1Cw%aO63Gm~w z`#jf&F~SOUTZgtjRW6&0`X=f7yrk4jI4p7E>Me=qTI+115)J1o&t;|{(iSTa?)q^* zP^6sl1VHoyOth&8h7LVz2=^kJ#8QkQQxEF{RhkLNRFRq@c5?dlcw^YXk7Zzl9$iS6 z&sbNajrW{xFnYx@d*kYuI{#n0sy zQ~g1jGIjl+?^xnc;?X?EuGq(1=ZrUfPW!#4GL&bh2i}7U(u)g%;c?gYN}=4XTusOh zqomSM6?WS6&6p$8OuiqK^lo%(z5<*?AAa>$kNwwgZeGevou-K4aJQv8(aDjjM*oAT zSf`DZ+17F2qAh-qEZ(hK4R^hT1$cl-4}Ht;Q&o74YG}jp_XCyrPkQAMs~{@1bDbc0 zJg)UC_*Dq1^$A)3$eUWSR%f_u-;4V@T00K!z04}n3usgqlhp{Q*-wAaodk<$|(W^>C7FJ z_xZhyyAf0N4CW!aRt>aOI?`-OTs{;z($Samms$1$Of-5UEWYo+a0z>hp+|aZ6WTgs zien4Lc%$x5^oT*uDa0Trln#x5r9}1uhLtA0oApXs?NP%=GTD#ee;s^pn?>~NF3=Wa z?isRr3Cj(v)lS-_I8Ab?Iu+T(|EEyxsS}jBY2ck9LCej4O#WC(9OLiPl1+1@|Hy{I z88o#ufHLg0c^1AP+-8C#I%i5V>eh<#i)9#ed)l!$eNqO`42f=Yk0L*h7QzN4Ci3%8N{ zx{H%Kg9SJHSK;SZit%cR>ohcJSu&TNg!Ft2oNaCDeyTzA(JdE6BC9yyalgk}4at-p zjYLsvOnGC)mTsGj6_|&>?FUHZI1-V5W2)uGDI%giNAx-JYo)K*r|8~PyDjeQ z?3{s~Q)ra&i}221El)H9-^tT;OKgh^)*YtW@=<1kOwTr)K6s*~n+&+!;gV<3U|TN% zk^vc=7`sR*W!mSeJ>*FP6T(Q+2A7{fw$+{OimwhSj>uf943$m_#&fx={C9+tuIN0_2#3 z{#jJ(PQz%ReAM7pPjCaselt1Qc@)}HPeI-+HGdb)#W9f@Z=_AIM3>Gt7E7vF{mr{~ zRPiB7GwvFs5gS}bMJ2xm1!7t=hTQaUp6n>&Jj`+5#k=fqzMRXvrY(BSKMpr%Z`Bp= z`S9BS=M5vqvnU*RXX0kfif6z%6TZW+59?Q%uB8#i7mr@D zMssU5P+Z$)t{&DeGY|k@y9T`}1*a>w?WS2JFpITZxTNso*BwsQUmGot6h&JLGOc;_ z{ivc}?>R2L5S~~R{>!N0?9}BqKhEkhZz8@!zhx~{24WB>PTDV|1cP5gI8cUp*lu-? zrXkjIe$g&WW>;Hq9j9I-j8O@3MQQph=%M%7gwfqUfng%Ps7>ZZt?{7?MrotF>r--@ zACbjdBjIZWH>kUdVz_qNoY2=%DX&?x^*bN{F(gIPr~2>B)!(6r``Sz)Kd%i9a`KTf z?Xe_j-N4}MQT)l$J?gw)sbqf+EY?4x5HYi-~QgSNte&*L5#u@nPnetHPKIzl45$%4E;A_;J# zUyoJWToK`uQ1JoFghW1d>(XRws@6K|?c;>T702E;lWxDyaA;K|4?r#>x_}(9yxYR0 zjZ?+EA5QHbv(+o;sQ)bQg@}+ka*eXIqkw1yu_?)?3rt&^$VuC5>~t3sgjseq%d9@wOj%N zk}Lx>)}51MNh5o1hfkg#86*bn835e91?11v^N%l{Syw5Dr1%Tg?FEwo<6z{vL5Vdp z{|{-?1pV~;8aJtx$tGk9@mSO)s>rqMLrUV0u`kDhk3_b&JCt8~nkkzJ^c-QKws%%w zJ$*)7JJRZzTL#v$8i+~0Aj_i3hIgQM9mSG32fUyU;NoY_tZ>E% zKd0676H?d>5b6*MZv~^q3M3$1po#YBNQAwxU+R`!rP+P<*%}>~`Mmb3V?d*kRtl9@ zxFm`KC)D1%NETw$Ia}Mpb&-3*v2G~N%gW44;PZrnWC9s-zVO>`&|wAY(O1_GSl-p+8GN-Zz%>q5GJBwnls zqjk_u{4zgYYk<_zt4?!dLtg2^{MC+KDH)6gpcegK+mqy8+w3?XZ!ep1SM7GC?b}hl zkWrKk`fXaV?tF)_hi6BA-~A&^%Px-ZWYZ4gcy5p24?jUUMwiMzl_OHwc|)06UZ_@_ z2H&3ftfWgkla*)UK@r`o{k$wOf3PHyp$U%IKr!Sap_BPq-**Ewo(C~%vS8?9G`6^R zME|uEvX9@Hd{JUGK=_5=CDPk6`mh!{7R- zV$EpZ&qIDL@$E|i{lISvW#h@npL0MHsU07So*?Ce>JDUD3F#|ffT8#9)|B%-E^cce zqE8`is~)*$Ufsh}HJNO3k@?&7DBqL1dPDNXDh7Rkpi@rkUns0V+_-RIH`yXR5Goe@MO^h>AzNfNd{cf_lMs9nbI%b% zqxabggT}xz6|nWYue}D2P<}o+v}u4Jo~9@Ymr#gKKcGLC4yge4nxH5|0~za8`r0>s!@Lm?T6*X!;4*z8NMe#zTbuq&P43rR)!hl`vO zGQPJ577PtpY=JZDnmSW9Ma`?};haA~aMz`KP*x~Du5?9fP5AwZb8}Qb<0A>3~D6rQ~o!wl8Yt{ry(V~tlZk{$8pknm!ooI6g;)P9^>oxdk1Ha?PHCXR`OWh zBqNb-^RI9^~t^22Yb`^@E0dk6{`f#!aVmpGQlDdf}tbn{V!tRDuq0s zgVx^78C_u0X}XwDcknO79b?D_Y<%T^KaUwHJWnPnDC7|aF9i2-7RuagUjzB5^F-%R zek$H#xTiW1k834n7_Iq`mdBqYy#}XAcy7e|cOjJU_WzWh(KlHcc<`Y2LcI%RU#eo4 zSH()fG1G5YKy$@LpSxemx_2*dEcRNvKOA~_P|#ftH)VYS^?cs+jh7P7$*{xk-K58x zeO)0~W+j_XJtStGis#!C;0_D-zt$kmk?G#dT>BKV_GAPvZaw5{x3%zQWGICX6;8i# zXW8>it~|?zU($bb0hGV8ua>m94)P&xMMhq+VBtAhW3Ssbvn;a+Q`eRA5OSkLGTL!8 zQXW~eGq1lozlug6AJu4oSE1SuhH|<8!{lrH^uL&VV7gT{qMaQIrdut{jD*=*$9OK) zfT|oNpOThT4(jl#K@$Hgr1%zJj-vhpc`;I`zn}_}MNUa3AbM+R+PbNSwy@v^O%X|h zISK=x4^F97hQJW|JMGnW3RqRApet3-Ph!jH3x*ERDGA&}|DPFq(qRbU7B1dabmYZX zA^tlc92iChR6J@yL(kkN2JFtLL+nRAScmw6W)>T0;{ajU%i#Jg59s^7>X-TZ)3bjr z+D217g#kvj?xh)6Zt?*^_pos**BroWZDc10=>7nO&;+hX>tigzaA@X)ZZsQKQ=e_w z9WnkFr99DRB0vb@1;e zjWaBVs{$qpA7&_*S^!Co8bnk#=PCX|bdfwVsv_{#K*KvCoZU?3`-1t>qw~FN?Cf3o zn^VGe;(vclw4ofke24WpdV@57tqNKxc}&&4K?;NAXh$k`Yd;-Z5EK938GHvwrTG8C z;gf@pI`uy+J_>T)8C@=D#9MlNS2f8sHG=tI0 zyQ8e%-2pOQw5TM5^CeObsrIO@hon74=E3nxI<4qErwDX!y4rREQ&-#JXDJt$bLqrb z9npI9Pn@;WV_Ouup{!hPk|V1VjvmMRlLpfW@h0j6hcaeJdlHSu%gT;2SzchPw!6XI&Z>%SVZ{qNGvl+BEu3d3(c3=Mz5mgFXefC0C^zb!5Nro0wMWTj9uN4 zIX+dtRe>_D5>8LGzYtm&dp}wi810VXY|4C5w?K58hq#NifQ8P@4Y=2YX-Oxv!oM>R1KAs<&wq$+$&>R0aev0;~FIz-B9aBRpKT!AH7g>_GhHTg(@r-ZGW^1#T%#E0dEn1m4RMnGPXgrl*~3!87n=lSYgt z8vzlF^g%V@Oow+~(nY;gV)9?6T}LkjRN=jQfWp`&X%)(TPFxG>J6D9({{7v~Ytl$^ z3=w0bqw@xXuTono6mOGLT7e?y_9wcipFfePR|wJ;{kt-ouK6bjo5-N5*{3+Uy)Ju^ zy(}l?Y-yUDSDTG>+)%?elF_zX3)Pm8hN7JHYX)JPBQz1#I8(D-T7-_pWG4HZNioXA z7izW7vrJA@a@#zl4*CuJiR6&4LhdAY9>3=tIRx%srM7W>I#s=2KM3fa8J}>m2P?!h z!}7?D+}c_td)Fhfr|Js}=()9K{z%SZidA(%qz{!G}0J`rbGAE0)mH{AR-s2o?-Y*VuZembgj_<#ht zrCFD5u4Lgu@deAZ-chsuJ$v+@4B!HorKZ0e zO2xIrWrRZUO32IJn~Ml4@af{pY!bWA2oA;F$Yne@h+!*jYr1g89q zWpV2!bM^~N4nlI`G5BJLwgKI}uwyy}|2Cshxxx{hejc5-D}zN+$-K)HHeqGN-BC08 z@iIH(S4U2fT1P|79hW0uCl9plg6uEHtM0p+l-m{|Z%eB$_=aVSK8Cou8cY%Nqjxz$@z=YL{e|i6gCQ z#5_h;cXIacW5tb2?~)k*%*{m?GWzaDdtB+}p4xbK>?A`i58$xuw|6kMYJ>jH3hd>)M#CIbTVY5RG~?F@<@GYJ{I%z^TpZ$bJ9#)wrxx zd}10Y6Z zg$qasE59-1DBfV!C7z$G1L|=3&^FEE@2ni)Q&d%OSFRFHp%1XBh*5wkc&cVB3Vgy~ zm~k7d=LRRUjoDrQ#j@GDlDy8ggjlw9@y>v?eZdb0O7;6Zs^N!&M#`&DT*oz;T}^+_ ze|P%bsiL{_K)y`HCB$HecJC-f`Ng-G4n6iipZWasHp}7plFEr~Aq1ySXn!ZganHoA zE&RabWxxmG+brL_C~b20O$%?MS9)t*W3v4*$Pu~5k)}SILdDB2CYVhLmH`XDF zDMqKW4}C>l!_)zEp%t0K5f*Io=Tzd)Z@1^V>F)5YUP7@55MKszF!O)w&r4VG`RLt2 z0#d-lE8l(|ePPYn2y&rZ=a0TF{U=;*w8)%#dE*fwaOpy&S9`qQ)XjT;;13Tx8@u$e-80YRhp;e<9?gFwuwbfbxJj z5=EuaNNvO^J(u7qu(kcJt?5zszTlXOi@v}#(g-c2z+~@0gMhf1vw2Q6tg`veyL(V; z{OM{yQ>2sH{7|A$X59I_`%(Yv;hSUhd@~B|}{VgF{SbBnRrjRj*5wlfKozq8W@hg-gAtH5%NUer`F% zd5mswbD4>28x)y_t&`d%8WF_*HZnizxr2UDclu)))x%Par;;5(~pfnUr7)gQnt`X8Px?fjH72rQuf z+nLmgFrW;WsGVVb*@gSwbd-R)>UsZ@&YzwGV1ROXg(<>?f@Ova#R64k+x3#FEMm^r z*Xz{AfW?R6firTxD~O zeI&J)IqI-km6l|IvL8p4x^0VGOb_X2}%+p@7LV4QHC0L^eF-r%6RK4J@0=9P9CFhJ84_as;1rnKT>O*MCVq;!TjT?m zU0<5K>cVdBJY%8+cWy>s9T)C-q=5)7->=fYl)8ZB(9P^cYc&)Rg$UpzTA@#Q=QQe^ z-lm0enfF8;ioaPfCc7#adWa=&>>gM&>s8l=?%(*5{gyQ1)43ir!WA6)cAMdu->K)? zikE-&44qLJzi*-Z{oO)D+v5w5UBJ7OrdHQLgF5jOAh3R;&aTg)ImO&@V%F)t(xtyY z9!W;|mcbpYSz_ko*ljE1#JAtFnks2;+)HqZ>{DON@4`5KetUR5533ap2=`BLo1tE{3JA4PaOezs(16Xz5738T$BJC2|oH@ni{!Tiw@ zU%P3)Pe#pd!b;_M z%J#F0nzC(EH$wtE3%Dgxpy$PZMg4Gy*&8mjhx=MER~SlkL}j6$F=u6 zG8n{_)5v*~rP<9DItHsrR9WuMtvsTCmi2L3*}GITmgUg0C`jED4j(nJGQgRgiDwYu zr6NLxX-c`E;6#P>BjCM)v4#2Gd(H)Pg(q4$yqbouuIVWpE6iXFhQL&(QN~ zRgP5zQ1DMD$Z>7W9XZxz2?{uvc=7{(?T&j5Ey;L5Jo=&fnheIpU8PJ6flYqj=(wUM)7`YGR@K3|zps@=ZN{_){rl3R3-nmWqt1$NgBCc*y%S zlnKRe#0sgy^SU*muhm8xouTI`2i(I2bj~GPhSP(@-nn!h<#TD2C-YNo-uX`{(9Lxt zBXkBbJ5)6FcRwHvlmY@4(qDd6bBW12aW2a~U*Q`TeWUTl0qe~VKI~JO??qTW<#t!c zeWU~zQ)c+AC|UT7iNn6S4^LirB*gXAE{A5ypGxvgT_siB{OSjE5jIJnf>$+wjpfhh zdsp8Mddf)VT>D4Y`vZ#nH+NFS704TyX5H9(V8n`eGi72f)8NIdoe^$cyh-5q8JjZQ z1}07(OzJ7Ce@H!-AYp^-`*f9+T&4DXiioWmX@|3&I*C7BGxWA($w%Bar@q`bSLC2> ztsmy$yesBwXYR3J zExKfL?C6|IYL!Kd&qSV#xwCN2N%SzEoH8dI>jCn>&n;N?9&99DatjD}v#0G!kp6N& zcmAZ)RH9i^tq4fGOa!zY?8lpY9L1_1ghzO&??>{)|Dq7OAaVH_{-+RL-JZIilrdhY ztNX97gm-njO#H(u`*=X9^M=}$xjw?!C)zfDC8S?e>9QbGcw}#bofSKeN3%oGoA@Ii zI?wC9*tD<0QGpqSa^^MaM*7^O&>{CGxHxh9HRFMAnMfs>aL)+3aku3G=|3oiv&G(b z+H;Hbw_ZjG{^-K>muaFQyy{ZHDEzAY^)YOItUQZhgz1R*?J2Hzda$_6dw$H5dR}Pw z@CLE1c6$BI;6d|(%dEV#D-URkUT(aA#GD3~CiX)B$xK|bL#hpt8Z0Am$i9q`goCP; zS1kvZ>rnhAkV21t%2XwpG~>Q`Rib#DNwdK(rq>@bc{98tHwp_HYjf_^wNVDaS*?Kfjq5;IJLe~NTpk$ExWRJjxS#wu~)_oQD2aLIXo7(81+ zvk=|kC~K;Ac2ORNH1cx4dEK)d-Z@o|5l$p8LVc}#Uvgbkv zn%D3`f)}po{!q>RxqAuSc&-Q26}F=Cq)F+4h5Tjii`|A-i7wl6V9Aoas%!Uq2$_oy zHd_XsRW^I)mFukaW6Lm&J-kEo!*6gZS z)9e8s&0d92f<87L{4F-DY(=(TR-!bS$5_<5&0xsE@4s(IvCxL@Vz3)uR4@T$b)lCRvgcOWN2}c zhtD!%WfAm)h0T08c+hC`3r|A2WMTWj+5=X8jtZ;Ux5)VGCF)u0nPn!Z^cUG-z@~&g z4?|bwWxmZv@ow`wd^}q!W!k5={JKfv_d7ROuI@tc#D2H@lha4DQ~3Y>0*L>Ub@1mq z%fLgBq@kBz zNIJGu+vH?_5tvlTx!6I*!Lwi@eg?wbblXczgNuvUq0RWi{T3Iw}W zRGJMrhoqx;R5~tP=E-4~Lj7D5jOKbNr{Sh`fhynCROL(AkrCXM_a{5eUw#XV3-W7M zkPu4Ty@>I~ULI0v7F@y@2)94WeaH;WWw4LzaaA~TSKum?ceGQ^@;u;wXDm7jW+m@j zRaHsxA)vq00C{zppu~uYC_z+v7jVd2gdotP=|Z`FoGCvLy2d2jovmTJuP`W-&xUg; znC)-*wQ&&4izcGEC2O<2U%Wh1q+ydQnUV4f=F6Q7EIe)x_4hC5+f(I#dUWaOjl1Gj z4mGQt5ro#Oh;B!lV_!=;AiT?za%$Ooaefk<1*sFAJ%GhO|$9^?J1`SVSNnNEI5&Kj$jcR1eynxiLr zYVA}Y?W5zAQY%2-h7zl*t+cfw^DecCpY0h}OehJrRtf^?T#kW8=#dzxjw1dgd@dvU zW~r9Vs21{;1%$nan`6o-EOJ9#E!c)0eEVa_FHRT*28Xcmn`yR+@#lk&sO}?E}*l~0slLCu6{vwq|CUvH=&<%kd z)2Q?mitS!g$pSmHlSPH;9U-PAE(bcaD~YzjJqlx{Fsy8jEf!6C2a5NkXJnV;TRxzl z3*|?V=_CuwqB7StgtB8vjE-X!_vyE~8Mvv%Yy!WGy(J};`UBT2wmc_W{#vH?fV@K| z)eiVl#Pc$C>)a;rWX?8tUQwQP*$g&QA?kb$grJxGUY+)-JM1Q3@N2hMH)o_??PiKs zP38zF$5~O9-K+h*_w*JLEOMtjOK1Yy=A!>u&0p&w+e*l4UWV1wXt>4;7|*lz z#~k~lq2?6WrWt&B;k5T)3L-5EO9&h-KNCAt zXMUdqL$Lhv@dLO|0125p<3XKP2_xM~#^DCWIYnqNSzoq5Xc~0z>N?$ zgNYEKs;;K2@@!0C4jsk#Gv@qVslV7E~}JioZR{B(^AP{8RRRcR!*`rxQ^Dl^t!So}g3Yn~6WLtL?a*ObxEWXJk( z@+2EUG;bEF$KEd1$CsYoEyzq{6@B}=_ITYkrmL5@{&%=Zw~laEpH%gD&%=?UcjxM} zi{)hYdp78}am$%M>s19)B4b%Qi+GE%gJ#d@!e4B3f1kJz^?jdfZ0jVcGGZOhQ$VzS z;mOF1yIwKWl=X&zK}hB9{j*K9m8IrvUuaQ9CbCmEsXl?zk6x>eg9wzlOuoEzTLeOo zz?e9jhqsz6r;`!^yfRd+Hetr32O!t4jJH~wR(g{*q}c0CTVSFz3-iLj&WhW=6m~b= z)(ZBn)jDm~7Q1Y9z)qcIAa&^9pa}{5%06-w3ff`0%#-Z$eMf0o*duyUsdoR0FEvpH z1Yp!PVN{YQBTP)3FLy=U+~Sa7{yg!N&9LCoo(m#{iF`Q&cyG##I)fQ_z`3w1)j+#*z`@ z-7K0CCHV|FPBz2+c}Y)|Zvai7NW&e}qg+N`NtYAS<0Y*fy{hfj%=h$Q?(D7q<^q^O zr6=3u^celICr}{2$FOH3K5Nxm&B~SLMlfT71Pi0=UliDx#J8aboZk3!qwy8yel)|* z>pd>wXVVQfmH8jL1_&P431DwG;7<2XpuR(he<*mhggtU?p4(AayC>qfgMqPWLxDb3 zn!mUR)$%fMg>T=|?QG`2hCZWf^*_{R*N>FkarR$RNG?oBHf^Gu1ywu+%U}sjB17c) zgb-SGbN{bn%)P9%`EG-ZsG<*4uKCai)M3Y{kSKJ{LWY$`DnyRA!77Ys{>udIv-G!! z_yvx9#tRqw=GAO;>A%2K{mvc5nKm4@n4k()%?_NeGBE5fCD=!)L-3e6GNhok%k)d= zMF2yyjnq94=(ILfq0@5ACRkFpzG5TS@Hl&JljX$ZNT;yC#i%y#c^#`Sb$9dwqN=E@ zsOwmZNt31XeC(Y28aQcDKpD$}{-EegedRt2qKNrrhxi#r{bOyCb^qYF660;cDQnns!C#2xp0S0$`te%Zk@UKya23V*}K2&P4>Zl7l9y>C8 z;b|vHrqfZ~KqRPe2zxU*tXL-}{a-u=F289X}5 z7I_ehU?1{}m)qp+$+mfxrUC>|eEF@XwK#AZn3b6?=Mb?seQ(nDtOO z^;MR@SRpF?H%A?h!sYF`+n4@O`y%IQGeH7j`mTrO_$4M> z-9G&>1+`@vH~`9Ld0u&RA!pr|*bqq>LeTwUVEGh$=OmEVI6UMv-O#VL-6N<|c4;e@vkzbUx~*M{YL5~m2l=GWu*O@HpEG>`(oWd1%6O}OyOROXxR+= zyOFOs<74ac)__xw=ym>@GsF;6;@84~YgMwgLiPd%5|JxsZ2V;R9#?$N6Ng`ZmcIGm zz8a|!6E2`I<9>P4+?6WCGic9!KIPa*YUMG7A)FK@F$`jGbl(P*ep&=+@`+?J@Z7?W zOpGQ1WBJf8>d^$^1HOx}%YSpOsXB2Ybbd(>CKD4utcbR}CxuETy?mhi%c5|0pZC^D1GUnG|@t*nepp~27&~qix|g00$8{Wdf2N)aaD!_+EI*> z7e~1A3F%H`XTrF)--F8l0rIB1=y;UOtv}(PegMgXm5CioB<{A&0uWRZpRv(iS7`wT zQmG4!f*sW9Bb_I>T${juDch>vC6Si*$LQE?nGGZ%Za`4ho{YCT;2StX$n{*CI^$Zw zf6|>DC4M@eIcYAMME$zJo@&54K9=;Hb&p1q0M{}n`;m{TN<)+9beXYiunQA06)Q*~ zV1WEM&Jy?fKWPcjqcmZKDOgwgdnH+W&Li2=D0yyzXf&g?@Q%sv4-klW4bXK*XnQ0y zMcuug(iT^A)7?C~RKpZO= zrueRYyrEiU@zmheF;!AM5s&*F%(x_1tMyHlZOotWxk$>iFK1bk=IW)77zU6~y?8r4 zqae=m`s0r_`z_suB6kq1CnAW%*`-}IgmQ2kwz(WlTWj+%sYK^p7ffW-(wwPIvx5314Lnn?7RA8M zO8l;vq$3s}Olx9)lFQ)_vM@JO4;ZYj)A$aLmomq9e!$*F#QIYy+^0@U>i*zfFkR;R zq?yU%q{@b>@6al){w7pJeQebmgsW-b$V83qK9l~5T1b!J&j8Kf%cE>d7ojS1(ijsk z4>T{z>1^)e72n85zYgjqZ zoXWU^I1Vh3MrWN7I_9>KSG~@U+l*X&gZ=>AxI10xyD?veileon{o?5qG>GH1qEH}u zY8K3*>p&I6?%Dc0G8~GsK49@3pQyg{v`}eZ>qnx=q*lBj5m8L@)V_(sS*X6A6nhrC zx#LCJ=h?V}bC`~U}L2{NE%Q)VoP zKgGq*wvC-RH+J>&k+fHGWu3@B;YAE^_Wc?H5a+Un8vKc-Qf%A;A&pUb;hY z#szR~zIm+p0U!K~D!b1=U6n!ACCn*$Ol}^pOz*~z&mSkXLUu8#{I-X3fqi02i<($k zE$^CJL}X#w2_kZTSd&i92!#{pDRr4g0p&CSvy4v3cOTr3va_F#RsW#DZCmrku%7YJ zTB3Kqp}-I8EKf~bGIhhRd#d zGBes6)^q}Y52xALhie&J$^Mwx(_g&e*I4S^%Qc*v4o!m3ObK5#6_FGcyER*c*GU$6 zg%s-QmzaC5!6%DK|F%q=mya+edB24Z%ZRvhph{h{E^Ct%zbb`VheI#>SMxt_qz zsU!cxuD5%(EC~Hjwx0T%efVqpzuCUh>mej*`IudQ_^DGx!`l<$qxb0Cq%(M=`|kR) zjnN7-#s4w7?WXBNvZb|?EME9stCftNiy!GVbk~3|f<&V+i!fj|<7Q0@7}PGJG6jh1 zA{Kc^*N6GOJz@2C?zo)wOFp1HqgKIdMs4$=b=LPMIzlUU ztvvd2(?Ef09#rmzxjy8#q@IG!?BVw>Vs`WZ)2>&34>s!yY(h^ z(?Z;MGctmrh|&6kUn#-xT{>c-n?8(lk7UBVH2Ik(|C7{c6XdKhV}BxfG4=gA)zJUmNI_K zd9m85+#Av)g-kD!uZ?JnL~BK%W)+G~*tRsur?h{jcQCSROF4OL&kjYOC-4y$sORhK zcxf(GaRy%y36DEH)Y+RuSPm(s3Wu?Fnf*9eLLUF#d~H;Oi(@vA=?}QU7_nFY>Zy7n z+H5`eQ@#F-%B)_F|j~Xqwnaq~IEvrLDj3 zh&_pbf?YAEWP2aX(P^B^Mq*9Z^E2Xa@|b!vUBu)sv6dzv)=F3p5Jz@?LwH_HUn=Ev zm~repPOUyk(Qea{PX~6e=EF=U^Yti0uLY3yMAfhH_CHT^mvf;~nV?sAnjr7R!$@#t z_60QaZHb7|I6kAXF7z-Wh*#+L+%HN-?L0`MH*oxEI7Y?p#I*q zAuD@~#|0FaLDtag<_ySJ*8D53FnV|m=)?J}z0U_Kzoj3&zT&e!R{w55QvG|s&T(#d z&S+o0ee%ePnKZizxUxAnUp#4E{IFi*_P;K<6L7X&MvA73L3<0fNYTVk`T;N|U%0T| z@>bivy6jXA!~PWPCi)@UFh*D6Tv-5~_Fnl2;b#3dyx&O;AFG}HK5 zCZFQR{=neollp$)QRP?y*CEh3-d9=GcS&Dl*AZu#RObW$uM!|=mG}}Ul684Pt`zVH}uqT!bp2C@kzWtC4MO2Fzs2W|2LA@eZOZA*O2Y$S-0IVM zMeEoG&@~yzN2UKM@KEQvxu(^voMFAT5UDim7)`C=!tS;!Q(rS&X(1{HoibF5QB^C# zNQgn%+-TNO(6(B@xjD$T{j0Sa0RbxTtU0}8jA{ax{mM=q^53L4GK@DXKA9$o#dj)Cr8JIKt zJU&_4JnkdC-yc?-{PfaO+tU>i*>e^`DL0Bt_r^|`%N_Y4ZUQEq=T`R|WRN<|;Qp>O zvLx)VB(F~KtK`ZHAdki@Nd64#AkdA!?jc7lyLC~JVe3sWeVtj1uk)hH#k6P?1tn*X z`imf{D&aNaoUer6CJ#6ODk4eUKKD8x(T>7qt~OT^+;7D>j~~Vg_*Q$e^>Wt!S~_JN z;Q^IGhuYql#r7<=+P|d`4EmdLoQkz2NbC!?iHfSs75O)luzqn2yeF3az+{50Zjj&F zPo9mW5jN0wg$2G)O^xFq&A$#N{At`=X*4*ongZjRFk|f@&&E&7wW><8`EIF3AW+ho zcX`cm^p@uCkS@u$Yb}cGl^Jx4=ukgMx#4?G&-+cCSXX)=b2Zpw$ZKo=*}amDd1x7h5$JI`9+f^TWyOYlG(jES)FnjVEX$lT zbQg-Il%1|OEx7hfnQ)yxYOp=Jj}i436esBcmrYx%A%wX?z3~Pxk8kuh&OE{3bx^1O zM?7U88%_t_eRb-+KX|9{Oy069@{O1wF$R8(k2O!;2Jn#^$?w)^E2<@>KJ0Tr-WCj>7Lv3;ElJvZ^`H6k!h`?FNTLS=B*Qxnr;Rp!ERF*e#(ihf zI$+8)ns+Pbz;N`O#=P=wHXQE34c zq=eoCDHa3~sRAkrNL2z7iuA4m(os(rhUkA zVXXdE-m8Fzc9Dkh&ht0M(fC1gds;us$jjAAePT4Bi8iR+KFkhORc$XRDgJVT9%jF& zwS5ZlA>k3j6NXL9B=7iw|)j+Y&^C-XwV#s_Pw3nlcc$q1%D4dxKD_{X?Q0AIzp; z7JFqimM_dDxrC6UV~8j6p;8hGS%XASd`$wzAQG4NQCSSyRUq6<)1!+w01+?tq!~?f z*!~VZ% z+s`Nme~s1Ktrj@9Kqf*$V#dtc9M=XhvE+}qz}NZ;5IFtzAsJPqH#KR`db+7Ul|;H5 z)PLM@r3hWT{XH|bp7uZ>lvW-HJ1IAH2J!BEU3|{RgiIrgc7j98d8u6wS{d#}eAH4w z_>D!&?_Eq%Q~0?5kmX@SETx9_>Ft)ZgJ+KKCaOL1nrv&InRL2@G{1g2f`PsTfk^6S zjjmpl^sn}rDJMuPGXfc|rGs-9qT&1x$FP;okKX5rz*XFKLLz&`EjXp5P0a19a7Vu| z)il~hTAw!8mT7x;KH=rHk#oC(MP)|h2PsmGu3DcVN3zL2&(tV_A{@}2w07Vtd-wc2 z;g0$el=G?g(p`iUoKv;K;szs0!NTxIDM9ZvjU>FJb0 z5*D4d|4+w2%p+DqU;^mpA3|Guw6#qDfhD4Zu1fPCHeXX}NSi7mx;HY%G@KfnLa7Tp zY}v5V1FlL!w$_*D7~LLU`)(?2g?;gXqxd)0jK9j@a>`U~Lq7AEsB%J>~{7Xt%* zRVa)_-{+1q(FY)mHydIu?KXgwCEWO29Tfs@Wsn*|ETDz>z3B7hkMmy{?CraOpM97b zo;SJvQ!MGXdMIxyB6zODOpg%o4qg?%r!NFIye0Kt>pf`dv_8 zm1PsH3SDfF6Acd_AHcls`u~-L}-gd9wUa0P)khq72G+_=el*C4GKku@BY4N!l_+2;aq0kMZu?56s zOHPHZ8%&6Blo;=rPaYW@)6Qw>TrWe}p+8fq&D@{ugwzE6d>=Y)QK_WIU&%<`IsNmwWb^~e z=g^IF(lyeUXKcit<9&eR8##4-R!_vQZk;AFhrS*mH-?nlw{uq*Lxl_e+JYWO@zglA z$nIN~nca~L`;G?e3uSm(DX0PlY#_cLjdB&>?F8X!)6H>2FXfot32TYwh5nR}K%dpZ-ROVYwpeI-s^V+#fc9q` zp2Ft7NAgS2WBSwQ@ING}4Y9xhC?>>X%Lk$NrNi z;?sf_tpqUTi*W-$z@?Ss=0wy^iPZoh^DB^tD6l%uF80f?PHW>0Gb8O45hBMO+pB&j z2cJv)G<$55AepRtoo;?!*5LJ0%XPV%aUvK*-D>MR!e=6fnA!K2K54Zh;YMy1`m8AG zfOue+>7f)eh4+{b(Eh0V^4KnfbdT;u^B?z1Pkv>8Kg5B;`UYR;;3M%&TWHS5)2J!P^)TP?#-LaHVaq^#yOrv602+V24@rbB$gKBOd5k&T*7X}TB3~Wj;5>k0 zq;$^39zlBcZu2v6svj-2bFpYBpm{1asZ+25je;=;Xsb#0NbL$a0RsH$mT;*rdO{GQ zY$S10mvxliM-4&8oYXX3MqKif<+S^}^kbd9O@cVOJ)FNKbBVlf^zC9Jf$oNp2jb+l zK&z$R&Df-fX)E4jG+yD%)*Uh(n~5&Z6*c+WD)-h!V@)dzr|x{#r7@6iG%)QnrSbKe zcT)py_fXL`CgmyNPx)N&BNm#W&GkcXNTb6qFNs#~PYU$(f(N-Rbzc4F<{QLq{dV7@ z)<#e03IZq|IrUVDjhT2DLoi{Jrzm}Uy?pUAy_UIXhSYBF*Rpca<3E7ZQmdAX zGL#8EelE}Ls?awxm|SRXJ~!VQD$D=OA?g~q=|j{!H!ss=aqk&A`A+83OKf~&&j|Ju z`nadFFguvgMVHcJiY6*DmrEYrzycY>QyroAJrgk|b&6q&ZGUD0rx2&u`%2x~HQ4!( zM)rP_N27*3=wM}DyJ9}!B8)|ry{BvQmwjAWZYV*nBUybN-%=mt$UB5O?+&pFIk5;E z6lnL#oV%$K^b47Z7wn@f~ zhsC_Rg@00H`TXYR-)>@p7o`<@oeRECQR6>pQDQkC5;kb*ob;4#{HM3OF&WZ4;wgcn7|gEeBf=M;SqmM8KNuW4Eh@=1e&?6nc4JH`72`d~ervd(@%TMub#g@BziStfRO^w- z!3%x2Xr{RlrcE*)ZmDgSsAMCszGs{s&?)TWVpmuRrq&SBs*252TIQbN;>Y{^5(O7>`WHk zm{C8ngAfPp_2iEd;eOB}E{i&X+Od=fzqm|~-zOubCNkV*DMs1rVw0#Zau|~z6-h&3 zdq$X)bauy&zC%&$%hAsySDy!fGkHA9bT8J@zr7y8x8e_sB}~keN1HaKh6i+5ch-Im z#Gaz4ydr)2qg&r>CAgXvkrC?WfOWX#VhD4ny}aMN)shY^Z)G4*Zp(~NFWI;}8{l-d zm}wCaI!!BRBD~S@p!7!bn$|x=kn4MVbNo_`1Fi_qO&{`vu9A4}k#zjZ+TTJ4G?VTa zZN{ay;a<12|62>N{RNP%U}x2Bt?QQxiF>j(4>0G@hpy2e{Job~krID0RiV1lfb4~u zfRiXeB^rgrZ^j@-LS^aROS=WNTrtn66PWJ3toW$)taI<`A&+kiZV>`}Ujw zlL)goP&#JwYIg>KCWw#Y!+~x-SYe8hwRIK7aQFP_-GX@agtjqK$+;fKIpnUz@ZI*^ z!Vy*}(T$NaCv6kOZXX@H_b|25;MoPJR7$D{(V^TA)qn#^5Ph*_9Dgfn5$e$%p+g<< zR{EUhY&6GUQF7yB(Yz0&$+$3%_62!RAs>#sYyiRKh*{Wr$qUq2pm%}jaU2zXE7*7` z`zO*>Eo1~Df4O2IOIZuj<*ih4CNDpHe2{NYsLjb+tIozRrgo1kDnq1z27e>z_Rxz5 z0rjcTI%QVoUL=y0SY8_}wGZE|hIr}#qt){r4c-a36fz;UF;0l}s@4iKqr{bqO&Pgu z_L7E~f+26S?Q=gagtz@<7N4<$M^S0t&)3MkSjP?c`*0JPFgu3#n!l;{8O;kP$2mUF z=*$at@5xa=85j{H8UGNH)lxbW+jkb_*&Gpv;0-M1);q!@jq8J;vV3Qus`uw1Dht>^ z1@{+x<|m15Ahg*MLK{prI9N#@+<|XMIWW)2h_#@~%x`&P=B6VyT{&b(W31i>#z0y^c?A zDL*`>xRK+FF>vsJ*W@2zaTds>ADqlT15`p8t$_ltcgY zJ#f?WY{*U!ZpM8ypwX_rHliZjDlKNEtocK@a`kM!MC`~09lJHe3Q4{e7-ikQ$BNuamC44>WVBfD}DU4w;TK%qmbJg%G&nLR7XF8|&}at0#D8JLk*=M*22!5OV~;7dU=Fk7*4WDMY$(DXXhI`i&=zOXK0KR zo1`$%yw7{&vRJrMM#I&FRvIzx8%!Bqe$q4hn?os&U3n7aT-_BKNt^PzpLJ!rC+(a( zr)~`wBZG4uicyMFwNls#dRjw610P#j}PzMwIeGJ8X z6=So#ce*+5jN&2=s;raJ5}d2!dTD6c?zL2G;(%_obzrUQ#o7949O7sbjTRJjuUbMs! zX*y%V|1HJjSIKUqSLj0k{hc_NzQ}i^2PdZ0Au%!a6to9t+tf zCS6eS2ZNVe?ft?ZQhJEBsLR3g6Qq%*TH*AK&rW7cxIYkOw7kX29J}eOFpN3tEOPjQ z58DUNd(FN>qFg+~ z(Gng)si((epO8*L*{Ex-+m+1s!>;Fd&TgjP+rYxdu7%sHHdo5*)7gu1ioCn4{eKmm zaNy$ii_zK;$)ewMNW)HSFIlqp^f-R9PfHI)@0|AQH40Di!F%JgHg zVZV&QAkG=K0R&gr24T+N()c4+AO4aqzSutrmBq^um4UxrrP=YyX}O$%i8uVN;l7n9$p{1V5`QCekes&%%3X?Lq}=OvL!Xh z+3vorhSxDDF=$kM{HZ)L1Qq;y_3q_^RNVTSR2t1Hx?fl+S*5U)^gE$meHPXx@Mf&4 z*%>ZkpSTdw?(L4RKP!gF0blSKZTk2k3w&;V_RyEpNM$4{__l9UktmgjxjYry2F>Kd z2j%JTCZ~T+ku23CV@;}hpUs~OB0^LL;7ZI`vrOL$z~_cOoh^Ebc1FID=_lrl0bIX5 zTpeSZk-@n4xg)d{NnT}MD2xbgS%h2 zC;@(4BQh0nsR{2K;Z5srH;Ue(@5mAvMD?vAmcKtYxd8voT8g(;|D;KWvq(@4oq!)% zy>0eNj)F31jDf-^GAaZ;WuPfQh-ZW!Ta0|&-I2oMT({vG9NcED;7utwH4iiV7HcCo z_)ztUyRuAhu}vTQ%HT~HqlW(tnZnp`p!u5Fad>e+gkOkCBuRtv2Kk7Bt|qEH1x{z_ zmwC_@3kO8xKXQWKHq|(Q#Za<#_`zF4qhUq2PQnW#)>#Tx400ST9H*@dC)3S-S~z8< zNo6OhgVGh!qhjbaf(d1ZTW1RgJbG{EFGmS4#3`(+Fhoz7Zx+}3qBA0xoA74-qT>;B zDmy35e~x6^I1u{j5?t;7+#zvIxK${jpPV8XQ~3D!{MlE>QQGij8mqR&w8u$Tm|%V$ z8p7Xto+>Bce)+(1NRz!aQj3C_Por=i(MB~P(~|Y4$TC`U21X<6V!vq86XGfVwRoaK z;I_b=^?iR)okjsK*g}eJqBNC}k5|}zImn7uhc`d^m+?puU*M3b6M?@u8fOo~N&dSU z*V%+%SyCnWGP0s2Vc7HcvvcB9`I16J!7W)!YVp5Q_TLpF0c-OuEE?mq`h^Qr$P0?{ zYr<6HaEArEgU>wE6-WLB3F$bA#L9)P6Sj#sBk<*e97(E3nh& z`j`i1@PGfo5lIa_uVpPF;P(FSKOtY3{ihY5GT`R?@4qB3xJ1PgmSNnff`R9s=FWEOhxk@9o!A#ZC)*Q@y7#L<(bJr&a|JspOFy{3e+&_^G?0?Mu z-yw0>@$18hKmFiOaoE4=Tatg?{yX#tYS;Z&=ptYP<=F7q z(tn4pB16yHPusyz6XLl@XW4%bf!}UN ztp(?qhp%fMZN8lQk7aFefj^H3ft7ATcK3Pp;@_~5fh{-uyDv_0fW^(4yLQC#uf+u? z0;Yjj$_`s_Aj`9$zb9Y*|M)QaQA0f`l(87Zy!-%X*UKJT0S=Ahk*3XEzbi{vv0ho&#ldUc@82DexEH2hM*Pl zXT9;4Ff)Ecg(L9K+Q3ziL|ZxvG+rO$(eDr?YG5aM1Ltn z)S-DEIv$prK?RQ`H)!zkv*$4_u0SDtJ1^oppb=N=|NPGUkfGQU-Um_F3%iTDz9;&-1L_z1pJ&2d4VJwJ8Sx+M zH=$6OL9FfRFvOKkfMXu18Qt6Lxplt7{A-oRq6h)h1Ajxv^tIo{<0>FqD`UOC0blyO zyR%~E`eE?Wx7THR*Pz4EEgPI+F!SWpqjJ) zi0|2!upW1<#nD<@x8-dBEWQGBRtwb{SI#gHtA5x{uJ7CK-o;s6Z+9G6Ox4c6awESm%Y#5Z(Cw=q8 zczSq`x|6+@V1hT6a&PUv={b*Dj0qqhqFn)tH?Cd&QuKX&d`JUiWp~Ex2ul$flAj}7 z-8abNyUYb2({|MC8z=;wkeu`gXlr+?SZ9EWcJBKMV+|ZL%(bA#dIL4f>iQ{j6~FnJ z#dB?vL5C*(%mjuowX(8Akk zZfuWB9Mq!YcZLXh2@?ev$yZ>NBuzV@;`omB=>-YBoP&%gbg`cNs}ONrdOf;JouGd3nr%GGUeIxpQ|S?=9&B&0L9lMa`_- z0C$gBX@l)96m4uk)2Rc8khx&sJdW!J2TJkR43x&`o~IEYgwF%YWGf|FHySXxPv^Wv zNzL-{$*<=<;LGvMn9HCbka${BgJ_q8V|$?|YC3Tkv6HV&H@ z-6p+A#5uL?Fj6}0Z$4^aH({v!{m!YHI?o^6J3ycJ9O=7lS9JG6eFFl`-EIb#>ugK; zDN!5n{t%$2O3xJa_tC2BL+ikUWPgz!x^)NX6={9~xp4tC=r?>-blv247D<_88)DHk z_((&xdR`$DAM}>jFHV(#3B7+C9;#wkg7~9A8fL|{rmU+x4LR4unnO5LK(~{Q4;!qP zIZ!eG3Ti$iCuXliTz5drrZ6$LSZOT~**93x&l?N^@Z0khvVNE?Eh7mst7*W|18C=P zS5nzjb9_~FsCn~>$-J8OK8v0|QiMAh3M^{FJ9ckr@S!X()_`$!CkJ8rg(h}?+1H^- z`HwAf*as*Gka+<4G3Z9H^s|7aI9E`)~)I zJGxFsEnFpj8s|`t(l@5J&SGxDo)SoZqC#d_L(JR93d^_S#mnQxsUkAC88Q>#yu zLG0bGHmP}`w3*qvnVp(8tk8S9TXqY1!Xsuc@gIUXH6EpXh|D?{@FFL8>(cs5I`NDK z118pcI$9>Uk=TLQrx$~{qR!zRqqu4;0}f^Aw^d+YJhUNAjx@zWs}$Fk%-;6c-djyw zl4uOMR75CDJAf`gaJbmf(W75`pF!tkIkT;bP-YAB*v5M3-caVH<7UrGJH@z?wf%7i;RuXp&>14AewB zpdDv2=NiKWhZ5HCt*Nt-JE-7tTvL}erj2&HLD2+@Z5x5Dg!j$3OurcIXNYQ_AfdIx_^(Ja^9lML-9Hfxt}da|99liw>3hvj3Tn&rs5FLb%>QeT_isd*;-#bqZ-VSQ z;c|LtRkSBVxGK%!Zm)qLe{`cOk21Ms()x#vE}x{pFwoS0(V=xTE;e9UN_?UvM(e;i z%enEH+RI>DDqsiwMU$kl)WW_X!`!62%=_>u!w%^j;bF!H&7CC&IR-%#K8s#7M#kiq z=rw90|Ms>nmBt0zRP~qG+X0bbO;!wQ6L7>JF0@n|`^gn6k0vl^OTCq6C#DG`o1Ry3 zVtpWs*$5AZ7H~#}ru5Jy%$oDxMxf`BGfNv{l@b8QAsP_qT;5I&?D$O(1Q3gf?kU!01 zUAhO-n4NHK47C|{zaveV)}C0=6$^po(=@@Dm4ry1=0t8T?I$y$$JK~ESO&Ep*i+LW zn^nQAHiz|#y5^Y9S_eS>TIox-M7Ln7dLbt<4O6TJ^y@B{#kt;@W{{J);HRr5Fb9O? zZCni#n-}-dC@MDoE?wG+<)&$)oEACC8j;ofm7(NBz`ni0Mb^~fV|hKPOAazjWfQBs zx0R@JZ;F!H?KfV8EOriyqS>Ibo&Pn@YV1n8KIvc#|o;ZDNytV6_xX*Cpnon*_we;pM#`lid% zUbB=o#8+Gk%S9{QhN|$vdSV-}g3(*XRP|CGN3y}}Vf3l;xi$iXt&49{GZ{DH|UtOWeQgj6jbvdSZ$h*Tf2N!kwSam z7otw1*l;^=2|C=;+EaVvRaVjLCuE1h?tnV0)ku=wYOg87!1ROTPf&sc&MCIs`^EU^ zh3Jr}$@l6{<@7PCEHpV)r#HpeHUe0zrP>$tzX?*)PfA-EYwn3`k=;Zk&|X@aV$ZG= zv3M6z=(D&EiAh}LNnpcRaLm%1c;_&bzB`{&<@xNz>jwE!Ju&<4JQV^i~pZgU5> z`U%)d>AGp|A3-5iYUwzWqdAjc%?P1a*oV8dZ!&2OI(##iSD2C1J(hm}82+8aM)7Be zJ-*|H9n2L6Ys@x}sqnEm$)dfOMqf30Yt}2s9!neC65@*+oBT5E`n78w<8V(4eXeFV zrNL}1zHkKzYL5%uq0%VMzWc;BfRTP0?fA(%nfO(jDnpZknAq5=y?0)=t5iO){Gyqo5c67~FdJQ2x;>=t zJVGwCQO{j}hg(;f90FvZU$iI@0`a#9%9!a!e<2aWqAg;@*zg(uX`pwQ;-38(pF`jX z?XhQ&JPii+jx;(_lfJd`7@N(^zP2DJ_KWCo2rVB9vXcwQKTMor3U zuuT~Vd4YH)zpJ|!H%EDNN`1(m(V@)j=NdzG$wA9Q?~?v}nzT*pDn4%}u7(QrXEORS zNQ`_g6DA`cW?6n$ZunZtv#?)&2Wr|2)^k}BzW-_MujQz;zQ|~N-Kv!QO6+*B@Ata- z-+IFVJ>_RL)|_p60%8@+OKp&gX3YzP8*@j3XwE=rQ8n`4m?@(;Vo9oKrcqTT z3){Z+dAhjogcd24c(1m4h?1JP2GQb{AifSuJHJ*eLKTfJ9n3gSDdxiP4qUnU?HbXS zgNj+TQ$u^1xOeqKTv@dfL%dm2u2BNlaq_`9DX{VLt~kKg3vND>5au{B(G^(M3j3=(>?WPq4w0AUQ#Ym|y7Ec1B+=`U zQk^4LjwN=z5F=}yHzK(;J6GBLvPG;57wdOyB!>%|(dG8n4uRhWhnjV%a^1__AHDCk5!-$S<%Xc1|IH`h*huM!@){ zx}a%FZ_*_N@=Fo2(QHDgw24oTwude1JIu3kA%ZvX*mAU(pd(eh%#cY4xL*)&l=T?Drg zb+E9E{)4bsXp8wyCX1AyMq&IWh47C}?6}cBM{@Lm_)%8@Giln87&NdsO9EAhSI;@- ziDg#BJ8r~!zhCEg)o;F>R!Rq^vL(fwZiwaet~FwJAQw%e?u>Gz!B@l$U#-fHME66h z%fJ=+6&*KIsOJ3>q^B0FK)f~LJ1_(!WWN>_n{K7ErmOf~F6)auI{-bN0fVghmv=I) z`$=ya-ZUvTzLo>_)rY~@!&s?VX~=m=p0G8Bhn$@rdpdPPKE-d_KLcn{;p6kWT09#cETt+N|FN}(q-)p(vO zZOB|Q*bvTF!0v0cuq?J>ttu&&5t6HGIIE2{{)sG{SwfuS< z0>7LxJ=V&RGyrl~vWsDE#)M0hhKcqAuxf@Mz0`RkMa}5nH)=u_}vE_H_di za@vu59IbaXq}-W3_x`MJU$X~KdKVy|>58=QR#XJPJ9&=1Fm>ViS;muRo8sLDfc1e8 zL~p}canBx_o_}Nb{k}LX-7rtLXeAlBS}+BxT$(!v^Os>LE2Je{z4t72Sii@D)ME_dSyR4>FEyTNU|frww2l3J{E&K^^h2 zkSwS#GF0;Zv;z!jBgnXzJiqMUp%G-LdhaxX98AIMRK-5lzn38_0x8ad?$f7Wvs;Et z{tKe;-$PEp$GnTGzgU6LJk>E_G6Z^o+cOUm2`raViueHk$@z9%57l=fq;|Hmq7=x( zdg?W=MF4qq;r7wD7ozBszOR8&ZWSOvx(Wj>Hr_)ldr!x?j{?S132?XQ$^^6~+S^=vbFq8hlk?ZeoszlkP?uN*+>YHi zHIR*$?FW!XF;msQ#CLIYq;?BJ$(%oc_2{<^!;$i{8_@zipe;~Qy;wJl0AbH;sut>9 z9>Vop1k#6?E_2)iGR*syI<&WrXWR#N+&k|_?vz~>l(!EF+Wem8_u&ih;mRipcYAWY6b>Js%O983d47jb`h$*W`SPv1}P&{%ULm z%?3VOtFy9mn@J)aZ+&J{N?9)4oE7J~l4WqypDF4@=FvADd%&V+J19Ic_xb6h$ihb* zmRG<6bJwzduI`a$h*vwd@ySE^ZnUvILHa_?;C&^J1;0tW)9Zv)ZW`Gyy^)&0Q8 z@+PC9y*UB%fb0(fhVfXOh{G*}C4B+c`d;5bg`a2#+Ch7pV8deq<~`ng=d>wzj&65+ zHpNT?$bG~$7X3aSqf-kQHCXNUOfMw#1s!v}-av`nGiaODZE5Azh?@pjNVnVR8*KXT zfAr+$o+dekv+WV!1_cJ8_w2Jk{7=+hMcmlt{s*dD&n%>Y8yfMP5WKfX8TY_QZ5JMm zbn>6`Cr8AIb|)YD3c|D6CxTHSA_P}8FcCfos9OM5=Nh>eJBVXhAfaufoqw z+*2cLc-mp~9rB06>DhP2T26ry$xyfQWZbQ_FM!JjwUHnVuu>|dYmIEM&4nqdC~`lG zfZ8DXOFpR)md+n_KG()jntsqKQrZbU`9Oc)HsGM~*r|X?$6AHH`hP9HkMsn!MHXT739%`v5W^ zNI#v50kU1_b+))xv&8P9=B}G;4@8lPjjqLXRDz;hu7(Bc-mFRc=T6h_Xpi?bf+?{^OnRU zxDTLq^R&YFl{j3l1v$mcCFT)$coOEl)X>R$o3x{8jJ|X2KfnV zVOWn6-I_k#DK0gSvpVV5+sOOgcc_h7PTDb{Nn{wPB-+)VVY!K}M}}UlZF^!G@|^wo z`h(^BH!`wyF&pjt{#m@)0rRY15wS$MniEHZLzlXkPhju0^g3aM$n`Fg ztVgRmM|;G-82)?7fxYVseYzc^sYZn+{H8S^bG*V-Br0(bUqD{iJOH-Y2-iYI*!s$2 zA;|MvOS_!;2m(^~#LlMS0#5Lw*gkK*LDIPc1|eVNsK$}gsApJ zVbjcq{TG9jW;rZ6yfXVI!E3JwfAU~pnK&Rv{T8s?{u@t1mKdqlx*h|kF!inC`MY}y zvHYzo8l;~JZ*a8`>AW2pMDFy-e0PB)cFp1E`<>ra8t|O|0;~30hGi%?HK_f}_1Fe@ z9;Y*|7@3|)PwC#+Y5Jx#ROWm=82peF{(Jpnd*Eo-#k(e@%ifKxpxcIg*>uK}18Oha zaXjw)JV7VQSyBmcSEUq%n-?L;IhFD9sRtwsMQoM|oAM9$2xltQbiS?uMwuI#_StsP z!5V3&v&H|lUZ5CHYZvx}DllSd4pfIy?2`=v_QBZeTz4x(6H@5QdV(z&wtpyH>T9V; zzUM6b=|BMnXI19dsy((fsIn^$kcRRe*^c!8jwjt!!3M4k7FCSajY4Hzt62u5My>s(vc&z5j1_S5R)_YU3Sa zOjLrGgUxB@^{DA(e{O;O2|^xKl+?6?z(Y2tXb7HDwbye*!w%W@%-+6o?qnM-bqG_3 z6z}c!JKBCfK(z3twsJ~Ca$J;k$s^;mx9i(*Pz6-jZkF-+gQ)FBJ^Pnw;3WQyGKjl@ z1(mhd+F2T$GKn%4=+!&%dpq7QjLif3OWyq8@nBq&+?Fd)e{0W|q|8K2^wQRnpKN>q zmDb*^ayn#AQ7+erA*0Girv)UG6-6(E>znP*E_e4IIl9uv=hEX?&JNjov>17kWLp$ zEY3yWnf>&*`iX&*uqH;N^Brvbw&vft3fQwUa~mK5TrGJ_`|} zdv&`@7$ZaGz7=;2rTQn!gd})#CSN~*b|Ss2BK$B);9{FpMsjD;4)|CB{mvz4L8XC( z?kjNI%#*@c{D^gsp?NDLuDCMO`(Wht2E3@~@_{W>Ajs28|`{Yx>uyaCoY zO<*5#1-hqQE|TtAKJ(}JpTe(~NMNX|%#GeS>Kv7Kzkl@V9Cs1!Nhd{Ie&|NLtXi6L zF>`q%Z9T#c$}7DYG2>~XMojDtn<>Prt*_GcTr;{0nj(z}$`k0HKSUQ z=tWJRYC`?e;RJA5E*TUaBrlD!FE`WzHG9%3UPVZi9z`YCvk6vuR^WO~+Z|`vjc9e1 z91egKXZh92p|)FX58eleo2sQ>6ge6QU`sz}w0D$!I}~+{WkwhTAHxmI<$2OOc@$kt z#6HKBD!zm0(zb)v7eaZQ6x$b9Xxsrmw`xcY1jwbz|Dl`KhSS<1ZvL8}&-(4n2n zqxR`DO^?b&xMoNeDT?a4R*m=#&vQOyAfOI9C}7WG^~J3*9gs|T$!m+fK6D!Efh~%w zfbOKxg}Uu)O6TZn9SPz$!Lq3S+p@q;#3g%v=G3>AM!E?3Fz9pC?l}7un?Xo32KGpEMl8iv-Or`> za=1tI4hzLKE`2+c)366V5qp2+l_hDMw^pE4S2q*4A_I*~8I_<@bSaOnqj~z%JQ-qo zyH1xob$i>r(=>R+AndMm&NTjfW=8mg>^F9=#HBOPjWbba`}IX4UG7JzHn|9HaVE=P zX6>Yj+T2eUGL*UQG{_z3dyBxImsru+4eig`PkTb{#yeqMX7Fwph9BS8B7KU=3{RC6 zinRm%z`g2|kB(Qa_1hS1Nt_`Hj=X{Hy8OtWn9D>%3wjnx6uv2E!@EVPNg>Kc^88~6 zq>}ZX1t{W{L$QQ7BgY=rCL&#KE_XfD5c&1E1`FE~8(eGJZ{B35C)5-Nv_6PWn#qAB zks)k+I(OzuLzpi*yo1&9^v9I0FtbeDSNf45$5{yS4rH%F-@7$XSp*d;=1>!RyeZ%7g4bA7jtVwP z?hk@Z@_4M~3<^JfG{7QSUEdi|)LnSm62{}68oeGh=){oE`d{bj7xhH&5(isiYzN6{ zf3=NiFhEzlOz6TwoKRFoFtiSc#PX+(TG2Vv5NEIil)9o(+7Y*WeC30HC8(B;+HY$Z8$H1O&~$sY<20z(;lh6E))!vf5@ zb`5Hoj1>=ykEwuL93+Ew$~?mKkIUooZlu(%427PL0n<4``P#Yj|FUK#M_?b2qmgWKe^ zexcqVMtvc_Op_)z&~6=P+{eule(N7Uo7wvF2`O3!@iWo}2%IO8)Ux}HuXz^nc)B()?!SG__G>`y42xu<+HTyjF)I!N9gv@+tj|kWZ9rgliR~(z_!*9?iMLB%vKgisH&uQ50qKxN^?r zL%?Xj(ToqGI}!W=>9+aIYH3|}Pz5)4cQ!2{rhB2ZRv>_-*&%$?qTBB-|Im|zmd00y zKuhx=DI?nbP!|PGf0I`yxm4yq&ZFtSXY#+!uE@-)yYW*>=&E<{E%#GF{zRs%9+Kt%l3yo_- z9`&Z#D#|!IFNKrFX=L21U%k!kt?iae#>O!Bc0y`GLW)h?aHdcm#?@vP}d(j?#Ij=?0 z3ov31ki#)1Z3lnfyl5mdZy%#x_qrtW$%Wo^F>bcbhu2S#9*3!JH>KX?5`8Mh*g1)b zh-w5NiF2zT`nHRwbECRHs=9p?8e2z@?|sY0K4)flACIHvOiEC{cO)Gy0Bk^Dxe<5Q z{gCrdiD-KOe$=h8w>q9CnUL&m&OXYC$ilafuMU)CuY-2T8X#d;F;D6ctm!z!Z+n1a ztc4%m;lBfNzk!@KY$Hk7C|xb2q*9VB%>5p!jJTFC$tTzrxrfA{7|{bV)TNRoO@S?u zx977E!1u-HmT2~?uuVcaq?Dcaha;5$eD|2gPVckO5*}jg4657P4eWW+X|VI;LBjHw zULd(sHfA7UYsJCXvf~@KGxZ3FCVX4r65B=^{?(f$qlt!S-2!f`xnZfU>XY{XLHvqN zfVkhuQ{)nc{;uJ?&JB?EorjocxkZ;FML2osPG5l@zK zO?k-e4JWtV^oPu8rxbhym!jS(yZ9*98uCej_1s+0TfOYWZ7g>CxTWA$?5-Wi@bM;y zt!n3SwuFS!Wf}UkTA|ynlusX;F^i*47iPwpO8wbd^QLC;kUxI|@6wY~JA{E9h`RAZ zMJQMHLmMc2GV|Iec+2ngS^1R))hVdBj8*e!^_x6wm%h%Ik%42r(cZ?{CLKMDWmlW_ z{2pX{>ywAr4aPRSQwbnWyV8<@bXk@6&z_E2s$A}b5!CixWMy!{y@-m zGZ0E~uPZrr5Z=AQs6`nn1S&i-9nX0g%~XQa5XRmObw;u!i#z%}?mQYHM) z-T54>T2yo&ndC$eKFK=oc|w9r-OS;&14WIm9XP_{8mRGc!fo>O$OT$GqgA$~0?3VYO4ZzYp~i5IH2W67miuwIF%grg@IRB21IP1 zJv7Tf%H{Hr;wx9L!o@|XD7>Xa+aujW$4oKWO!P`@aQ$%`*Ah@@kCso>sa(=sBsEo9 zP}P^V_|$;OsYLuH$=my19HH6?p=S2}o=grq1Cwrl!1>*>ZOc%ConsjssrkPt84fd!2W{aFtosuoj8~|69GE zbQJ-gmQJoDw^x{0Q3+EABw@6pZnRL3OA^Tq*oIV&fT;8mN$JSY^@K#}3+sb47orRj zTB#$_oxbKKTA7m{E(f+8{PYn7bk0oq6JBE0ALj!x)1bH~rv#-=~yZ>Pv} zp9+NtbY?h#ZIX)gwH8kY36V;n!i9XV~Gv4fLTtgN>+WF%@RD zjE2R*vImI+d7kl59nRxm6VUab`3&}bSBiWBrgBxWKniBEBpHgS{<#bJ=c4DDYjJrQ zZ}QYvj|rz*{=HoNygv1SOJRxFJHVm|zhk`Q zFMocL^7`V}g~siOb?jtXQy^8?-P{{2!h?w8*zqS_TwFH^*S$z@AbOgue@DN-V$r{rFz8 zInUSqHp;Y<6ytH|#;~?H?UxO4_(&G{Um~9+>O&j7o9HE9bJYV;QnIad`f*8jiGDPF z32{)uIhrNFz3~(sBZK08unWI$i8_E;BI+7 z8UL_842fohrPFsiYjd1Mx_Tt7)n4o1VL#9>5vcuOa$o=R6syNkS+mf5d>)rknZkAN>mzQ z=!T&izI*HYuJ8Muv)1|Ztn zXmdFqaAPqd)$=0F+6Ilve%pDX1TDDTZ}HtkgK>KwLNQoK?4yRE3h|otvs}emQ>k>T z#PH}FMF!-lII^y5c?3L4d}ad8JzBvYYd5)N_L08(zVPZ@boZ@80h>Lar)Xv>$LfHb zna7SIdrzs%)Jdhs33>u8V#I5;L$RiJ4N|<;Jnytb+^lOyPFV%2ZZS}B=Gb4TUKl!- z+jn3#-A9h~qbHTAl&2$?65_-*Px3WikhddD*i$r;ysM2a@fS4uOd)dEQ8n+acocaH z^NUcb!bg&ogn~9tZ^cf=173|{lTKnJ)`3+4CJKup93A-vC<-nrluNyqKbzSlb}pfP zFFs!4d3NU5LC)rLu5#`oZue+*+nP}#xG13^dDm~U-MFx8pu#!g0{7W}qj&v(KlN01 zq)@({D<@S7zC%=4xEtGK2!n(B?tk$IiBSj^u=Pa8>7O?q9C+hmx; z@eG~xwrMbOGgTWn<*CKt2xHbyW_D))kkMg70GXC?oEE&>PfyiHH3hij?&VIN;qHon z8{&&&hE?e}Vd!m@XpT2)ylZvxVQl5Zp4Yc+qSw8XjJHoEUz2ZS6HdF9d-?`i zfT@C9W~**M4*xvw?|5^{39s2$=X)&%k60!q1ANW6sqxRTbQsJ=0(xWq%b~bD@DBkI zp4S~tI*L!XG|`Jh?*f(;E$am9+S|uaLTBeF{BEiK)^A&B3&Qb=(PF~K4g7L?Qk8${ zCaVLBOOQYi``L3wD2f+NnXozwa*?Xk-$-N08+8QL=_WVf1)*u`5W^!pHUSjp*(@W$ zdvHSHVxIG@r0~5a47ekR&*vL+ zIzha$CX;x6q^!WUZFSEmD#)fY=(^P8GH8M9IH<4TSP3P(bSO1)@Rcx_6MI|#7Aq~e3a+3Ul7lN7 z=8WSwq-ZNSgtqBf>Ry=Cc@3MuE-!e`U+q6?YYR@`|0D)8B}@Ek#oi-Y>v!Xs;AaRL zT=@Q{agOAF9>Rqm?|EcvgIx9f1^Aw_DJ%9LFYLW2@|!C-fArzTEQMZ$R?JWrzDK&T zPtc8-J$RKS49jE_qJO4KT6hQF!@n3%uFKjUC|?0}^>sw>zG?mYA@U>`C-^{f7iIT8 z9fY)D!tl`ho_WQ85;EnxH z^_s_jWX)=b;P~5$1|m6!Xa7faj*^7e$=%5v;{MAG`0uRZ|Kq=mOuLb*8oY9z((2mX zP2RWKoa@vAQ#v^728AR%iz7hJ1$8(w#-Y$_;mW@KwtZ)Q_R%;#PZ=e(ZVe}qM`ka z9RBLqh41WP*N`4Qt)TY-MeAK?;O{c&|8FH67N|0%N8srFwB~UAW&m)Vw|F{d*#rc- zzr$E0iB}4Mo^9|{zKNmrk0JxfIEK;nv3SX_Uf{DIDqcJhDd+X6Qw>=y_mfw!UYkeY6UWg z478qM@Xj=rT*|%XxkzP&A!iWnNO--#bFu|+g&}<0MOno|>DOJ9K~E0_>&qyZX_Y|X zVkK(U#c0(JlW9wcb=ZoDgtB-Trbd?F_MI?BZs^{2{)3}CtSg--Rxm^^2E(2`7?WJ- zC8~fzqIk^hU=SK0D;NQ9I7?$|@idAp20wqev@;Q?VK{f^VufR#<@1q&zCSj8mPSDq zLnbstq`5DtxHLzBX7DxsRq=WRm*q^|m~}I~#&$t>W`|kgU~_`BC6kEslkb|5Ob)rh zDojr_KD~)_pQ0EpJXk5-&TF}3l^Wk6YLWZ)=s>%yE#iooAymA%?`T%mbo#s2G#*** z?Sm!bX^3h$97=S1{$#eRsOVSf@K@>W-1SgtQyS%vLg6tNE0&jb zhRih#u?81`&=B<~BPy$AigMU`-1oSGw_@da?Dn7jb%#H_-_@fH7|10%`lJtYr!Di8 z>i2i;C)b2-Y9@Zh*n6NFfhp7<1|Skj((}&gLj-D$#X254`)^LqU1QnW656 z=MGSv!_c$TL6|@3T5vSOjb{c!7y(KapMC6j!!kl5f~xe2(d9a_PxEj_cN`G*j9Ujs zsMG{|te58+sMC(xy%W>2m7LP(Wq(;zqoyWOtL&$SfX>tDuWCg%{!|RVVV1UlD^X*uawD@N(ftJ%cH{Z)T>DplrWHy? z3c81e%o6f~Er%aRmfLs2XyuGIq1NchQR2g;;eJt_mjPJ};y=HKGMUpHZ02oosC>-Q zHK)lgYwnZTO|o1X>g8pz3)70^<=?-kV;`C2x~`frP*IY_a=BHM>81a}7lS`V?UtW2 z>~9yng?S-i{l|X21C_@9Ec4jy;clR4SWE*0>U$`|Y)!+dw4X_yk$Y<@+U`v>ri=aB zzT*u?rCZnDaAvBr%RDjbR?|#O*-Y)~yp3RmgR9RWVvExA2Z@W%ED%o2!G!-C+om`4 zix3XgR5eD8FI-QBqbemUa;mZCz924E3Ro%)a9aw|ZjFR`iS5In<*j4!UX6)Yc`oxI zRQTonuc4uI>(0n7|6YBtB1u~>-%2um5y8%e;g(&kuYnBmWB0a&h_P!}AmI*p5;4!! zCuoK7VZR^eQ2Ua{2{HPo&NeXU3O#tfHMw<TH!VScTQSfz{QRP}h;#8+&4TD~DMjA)T1%%!cA>C& zpQawHZKi8R8Ts8Z{FVLsPG3$R-u7cMWdLb&aNtAPFr@b>m*Byp8ECTF`1v+yd4Qs(`Dmccket&jlX0SDs9oZ zn8S^nCpzMQh5PT1f0vaZW+-9@x>;vEb=c95JwxwsM;!p0(m~xBO7ufm2>TmeUT{p; z3X*`yu_SO?g2xC_&Qhqio=H9jmfNYF3DU#Yu1rKC=LZ+z*R5-LpFW{Iw+0C+{W8b9 z#cq6LHxMVQ+wRSbUguUNn2+;WT&I{1^WiQmw&B3C2ijBYGK5O~6un~g0&c&8haj;{aX0?J zuQ5}xnkkk1)HAQ>8I?v#mg@q~@IZEar1SiF@}s(^thb&T`;iq#DRn)}IEO21<;OG< zmi%Dwl|FfAoulf5L`4_g1Y5QGea=O7kQW(2)n|SSN-Cdtxu*m&DH{m84Uv@t{$z&K zuGNkAZ(Sv*kN0|Y0%W|9Mhw+kD#wYnOZx6dTgYZw-r@iZB!m!mWPa-wj7X(-{{l#C z#{qsSoP>FUZTR+v1TtG`x)sGGp$fY{0N~d7D+tqL35s((ujTsgjEm7E$iW;Tg{Yp$ zliaM{->4n#F>~YQpa9g;z!*2>Ze3m;$hbvGubE1E)oZ51Q2!)4y*9^sqm!AvU}pM* z<*#JFa_;IS=heYxD)mB=e*KeBBKH-Y_KZi}Rnv~0pI-G0S}%MnhS{r>M)i6f(eVE9 zktK-Ei3!}MMJ+QSvZwgXoxDW%;_p7ahgM<@~gBgvB$$F zmv;XSk;c<4p(P|xzAQEe<(>IcQBhlx|HN|ZaPMRt!<`OS`qG8>xiW1M4{B&V5ig^2 zlu$Msa>JF*0wcWCSf!*!hx@qlE)8eI24HvBy~@t-tyJvehKV=z-r(mCD*1he_L1O z#A;E=&rw)D5#nQ$pnKw}5|6!LgdzU)xWF{na-_89fcvg);QAfF1lrx!d>@XwyANNt za88ub82^4zT7!vA=S4H=PKwoBUuV^LWOU@-!D^>5wq)pgUoi8LI_dFyp5mD=AEX+& zfO#(rPWJjk=O(702wHcA(fK;@44l!+C|`v;XCK8Eor))Oa15wSz$D*5RR9 zqU(&!g3ls;GCxYI-g;MBAp!edE{(%F2V~j@P(nzJGIg)NxSLFevTEdIF9Ihv<_2{` z+Os}92bjsdR6P{Ju;UunoM2o)7#q7UL{Xznfwaz&JUT2nGLkq35t3s61eeMZQZ{SlMtK}7fjLM_Q}-o* z84y$+4S==5mi+C97+YB%aj%l4cjr%*QbZgwTS^c3C6*i-_$6+~?yr6yvklGSQe%<- zQ~0fAAT<#3*WTWcxo{@O%fXJ6);<57O1%Y@`|CX@6K?}&r8j%T;q z!F0mw@3gH^y5+xL&1ktV7ZvBZI=5&XdlDTU3b#icQ3blgZFT(7{CKc;)}2klXa0Sx zXs7XEzw~a>^uvr9$?e9B?&b)!x7|bz#0wG6^%p8f;Cv)jxaLF!%4MU$*P}bKEAR!k zp2gWH!o2yiRyj+4S6qJVtC60*TZwx6k$2B1vXbEtyPQD~3dA*}`zN!hxmq6`=E$@; z2}b(WS!R%dTTrN!?m&lXu<~2KlMN}JzYa2J4Yhle+S4iJp50gX!O$t_{d?NT%ST_7SU?w3LnkZ^echGAPL#wPGpXMfs-w(gah5tv{VG>>-p3o7W&BwkPi9eU#0}CdZ7wt`+993{=*g6Y{Qha;iNV zub+_J((O1X~U4L#rPh27uu&yiz3c%5>sK< zmO%KjC5v*A>EOI~Pc`@yaX#9NbBct7Pz!WFQU?NU<*oHMQv@jet~~uwt3*csvchiO z2x)dWp8(Y@A8NI9^?QqNG9^#j+&ugAT|A#=ZY@7pLa5)P<+T4K-|*(lhDEQxOFmu8 z*=8o~ws(yD<$}l82Z{|F2q95nb_t~M7aU~PMrDKcfHgJ*+UsXP0=Fl^TV*Ew<535N zxc<^syDtM=3km`m!I1ufufT&{%iQo>8ZOC!9%4ZsxJfdDPsdy~Ztv_Od72cS>ZXso zW-H;H{Q&~^742%5vPP402@#il{ITE)mMzXUu8vg)qGxARrS)L=0ECiZD|V7aD%lC+WZ9 ztkG%^Pd231BQ!MsT3Hx6>JF4V`_;S+{PIO`vEWn$c700lm?JJrHlpGAK_ZpsGmAEj zOss7&4^***wv=*LX)z>ITvB%RN`a)mQ^NAz#%#sxF?gS>Yn1{k46C2850UJR)^vEB z!7w=t)A!b@ncxi=mF-~0)Xg$cYfsgwTZIBR`5`qMZ0f?CkdGOfq*=Wkpmj6+8V9#k z|3(bQ!rlPY#|Aqw7RTe;QUVhm=s2)W=x?yWUV!XCL3V6FXs{ZmJ@A6dagy!{;=<&| zY%)Am{|zbqM7{Kd9%h%uelTg7!d(H?#pdL1ZUW=PPL*ly7sQDiw-yJCLyHhWL&Gaa zJuUMMpp_2){&ru?{G}Ui;#C(Qu|Pi$XZ6Sedh=p9Eu&R?O%Exn+);NYUF$!dGDXLH zlk?9fBp^g6(op#*r}qKu;0#VYff?z^5c%VlR`Dnpa5TY!A0&t1qS8}U=YkIS#v);t zCXm1UD>d{8?!%Qq{TjEbmi7FwEZ~Dscy_l?3kchcgKtTM)b6v_4=9OUUU+iTBx}^T z9d+Wn<+c_BgR&2FOk%j2*5B+Yo-IU^*4%#a* z)88_XtB4s|YnQWWFsJBx)nT~Ll*2ME?zn>cPqO6~l4snaj4z|-eD;q`5Wd?!p=CO* ztfmQrHWdJeTerFNc{E1cKrK+s8mnssI>FTYvb#*N+rb6bNsSl?Te}}TqK2vVC0wwSNou~>Qv9KD zGNLT(*$W@}TwAhV`^b3AZ=TV0vxhmQfuEmRu^Ks~O!%TX^D{C9Dqvc5>4a@q30fd^ z_yv4QA+FCeZ-yRdK4vS{mS{o5Py zpye<#NKLx|+v782uhobvk4B#4FZUJMsW+ML9l<907A!(*((vu8|2?FE0JP~V_nshr zr>tSWa8E!sk_&j9!bdc4h>6z{lz7`C)Fz3e(1#Y}8zPmW z>?_Vb36Lcmj)BAR-XraSt`A3tuoSPfu3n!|su*Mz-9HjBZYBkuOk!Pt^kEs%uCC2n7dU56V_>;vYjt zkOcr_f^H#f{SHkukLU)kt6qRgmVnKADX--<*TV79fiv!fv zW*%@th9f@h!)c|CLe#nwd4PDWEDr+*))HWkaK)>f+UzlB68NRQ`*&)R9|OR#L@zA9 zgIMu)6odJAe%0Z6`(Fz%SRPNG$NtwbEN}=`qQUbYbc`UUIeDiSC>utcwc|qoyS~Kp zni8cJUKK9z5DS?cDz;oqkfb!Pf{YYIiL7HD8eb}h3LqoL&yev#rEm^#R@cD7V!+lJ60o- ztctmmz+-#1W|3821F4Ns*oT8Sx0{j0uLgq@m6@Ke3$H+?MhMF_b zO_s}M6AEHP%!UxTm;z2udkYyktHMwe=zXLaY*aHVor}ftdIstz>Q~cKFH^vO54Cq*W5EURh zOJ2lvj4in-b+kN2@Av6R4hHG^Z`-P(@nQhtkOwxP&wpBkMQzOSYmCWh*?O>Dv>X}2h;%jp(9jmYOcFaZ7oUeFFNKxm?plM z;svOqUX6SCx8NNCy`?l75iU3n64Ru({+H99x(C$or`i^IN>ihBl#T~L4zmQ!rym0; zNR~_BqS6B9Se3NnZnWx!05p>Vr9V+fHSGxT`J`YJy74yp>yL7K^TZ}OlwmF#c3zTX zS*fxiY=6?eY;ap)mfrD3U-8xW2R&JCp5J(OiO z`uc0F*j6u>^>RWj`LS#%3!9whT=V@~OG{uzrW?kpLlNy#84#~jNmNg%ewqE0faK`Y zN*DQPRXgpgWS?+Ck3XwMh?<+_YDS0B5v)qHZ-dETJ^-v`VNW!YKZB)<;f#k2F$r_? z+GGo;Hmq~6qpdgydP2>pu94;(K|yYY7!T$M$o)3_!MYUlOdPTzg9#emBegs@@`XLX zOtzWiT|V?uNHj+A-s0ZSb92OM$w`0;vDLA;blMExMwCWsH|SBb7((G++rjNtcAJbb zlZ~l08?j~VK7!ftl8E6|8a>yoUVkFl*WrBKGTqwz0_Bml-Ct9c?SZ6s-4|hl>#c}d z4h=kfq&uA;sCYS<7Ny#FibX~yyD^UvFQi3E12f4sb9G`RprnOE0|X+oh>wt$`Xy=i zs7qQCDN)vVmEn#nptjp*6wtYYKJeFTJb+bm_SgI&%l#F{G~!-jd^gx8kI$dN25p3M zcS$`yH7?#bcj1|*`USFaWydo-bg~_stT$jpon?KV(Nh71V|dz8jJUm4uGahJ0Ip@y z7<$6z7%L`nmU=&y&RA-f?g`z!M$7wxq;s!U+Nz>0>*yY|x!`uBQRFM@8%-JJvSWp2 z=wt^UM0c{^5)`~avAc9h3H8`*lDvkP1lD ze{3qd`GJ2+kY1|RajJ~DSrK}*iJ!9^?U^_VjNUjo95$<{TMmTtJ)ivCqqXn@U$Jxx zvwIk$u3s_)*7on>n9rRNRW8(4FJ#&JLUDwtc1izA*&xz`j<3E4S)b#lk={vfp* zyORcn3s>s@`i0&?7$4-Cjh6y%28g#b__Z(!i2I&glbamm=8DtRM314Aj+Hk-I9&fq zvo27b@uJO={J>3%mT^OsN-kcPr&=fw`5-8=T?6oTRr)V zJ98Sb*LwCU$yrz$S14m#17&#%IKs_?*5XN8$9n% zP$0;fNl3w*sU(Lfc#6ge5j4xfm1Me$7V9{LDdV1cOHlNi#)e2%D)|yK*T^Xj|3TvH z+sQ{uqYeDxs0;fvlObMrYrEJ{Y^0|g)D&)gGvUH#6_Vd|n62X)M4; z!`KmU)r0mAH{PMW8N*U1DsgT%jX>woKS>PYy_yTl7F@X#%U zT`@2HdH)~j<1_NmW19sdI3oJ%tlaoA!$SG};;P{TDesQH7#EC~8jSg*-{AaH4kexR zH*)9Af?v18n65rJ^U(Nh@Tr5Uh#CxFt?N=WgO9K0x16nlns>*d^DvfA-}rQ3vuZhQ z1-V;sv1G&opJ$?KRdPAhz0^c-q!UT$<}Z{zsi@&Q-#c(r`Jd=dUk>v$@Hh5#C=Ya%s z0#i^YLd{AelbXg!+T}X48hc^McZy$O2$3LP1je+g75=v}9mfC>LBv`cCRY$7EK$Yu z_PZ`jr1?N@a$p!=$%yd$72EMsQsJC&UgEx-#+x|AZPyhZg4gZNN<`yZMQKhNj&Bsc zK_ykrJH=o$;N*@h*9plVaLp+r!@ooip2{b)l~Q$^V`%&h9E(HE<5d9cf}v1kkD-qV z{e~A15j}plO$wJ2ZY>V_bKNZNW*N0LyAy%Z^0cd|1NDwv-u8kCs!1velF(hta-IJ_ zTUiiy1|lj$Ekg=N6URe91UT3v97Ed}1Jk${jl+0l?)GCWxh6ShEs-|RbF}bT#A#K< zCr}Ppg4&B;gCfbqd>QP7O}89)dbtFP1q;0tX0#Ny?y4|N$A}U?Wc3z^c}gMbXwjx{ zYg^l=8`0G%pChCf!QDhC`lfW`bcCp6ummn`%2Mq3%EZQ(mMCYD<0} zQHuc#ZVdpSZoGzPe4gcI<2IbhksNg=8Fe*11=K*P`i@eBdg3+;ZNt%P)Cp8;>FvVN za>b`znvaTyUzj6KM1vedA||cqS0sW7HfYjwmoEq>#4Lkh*onvcT6Nj`x_1HpEkHq4 zBLJ?)v_1LJA{St%$w#z&QoD{lUTAb9;$E;kYVsYK^8=@3%N;&}aZ$s1{F5C(VhtdF z$06^Wwm*gGU-JT6bw*`E_J=Gp?$_CL3>3lX2Gv6kLfpXD_{U(vG9vhib$`ubqPNAo zo3tIkC{sYVix9t&Ib$DJm?OJ>ovPgE+SqtebL60%YaoBmGjv?Z-Efy+8f34VVa&es zCVi)Qi$w?du}(XJdh}z|qp~0n-8SQ>S?jR)G|Dm05&>wBS^7ofjGaY_V5w5bUjb9& zP&200HE4?{D0TrP4R)NsQdTEtcA;&+#j2gLL}ZL~;$)!$3igP& z5++}c(L_Rfbi_X4a}9^x1}%>DcZ@9Y3Gn>GxEfCzUPT=uLgxlZUeVY*ZS(@nLmcpM zyn_bmDia%5I2wtM##igSi4L3`Wnd8bh;|o-g?TU;Zee!8%AYVTUP2wPnFKl`X4az6 zvUj{b_b~P|e!ebdlJzRI!yL*yxGZM)Y)|)s%}CX&RNj#VDzLzq=&6YW;&9%MoIa`- zztZkcyF0qiZu&<>;R)?{jMrc|vCp54w~SdnAX;vBG748J0mHyIOiy$_{|Np-(<1CyHgE&Bohsai{$+(VUjo8fwfwzo+`4@N+xgKu5}EIK((F) zmtpmy%3G-z5>@@fl~u{^c&R z0&CJG_`fqIaHcntKS1wh>ersuf6wlzPyJfr5&lag9B5^;=rJ#D84_Ug$XUjF2zEL7 z_~dkdIWlA?_f*rMQetfZh--HD)SV54O$Y{_@YCbaj4JjpXM_}&)7@+ISJ?IprbOa@ zj-FSdrto2djGHsPa_=8T1{p3ndH>Je3Sm^ZiAQ~^rqrC}?4G2~>9oa{9v8Om6n6`F z(j3?o^h+M(7VPY6Xj?uUX!qN96#n_{)?1%PzC4Jh)R!TB-!gT>kS`~yi1f{jW~Pvx z<>uEh#Nymm{PR7!#3d&6pT$XGSFW}SGP-u&V(13M-iup&a*th{Ui?C`p(fE*Si$%mZ}10JcLfQq9}NqV%koxh?Z`m4I1n7CHF$n*}-8G!Pj;{k>k z87p<4IjMHn_D|z2zP*@jZG5IKsPuf$7XU5YbenN@Y9}jTNLrDrj8C;)YXfk6$)`}6 zA%xzwO0LN1D`bPv?m(1goanM15brn<;HCyzucI9hNxrmUWJrm~l#yJmEa;3A|8<7E zxd%pmtH#Mm(;$f}o(z$EA>s+nJaR1egKjsjY4#k!x?}xaAwiWWKM}s})yfj8H-IBsGu8ehsd7_e2P<|N z!MVYLR0eXNA?jlAlnhW|5it$&%f16{CC^zl#73;IO#wG5v=~6Cy(J|()rXi5Gmg#C z4YetupIYu1YDM*}L9knnWyNx_sLg~Tl!@`7>dj}BIYayrL?5O3;{#nIlpL#kFe4J_ zrGaHs_^%?KJyiq^5II#1{Z8`7d?9RU3GG->1ZI zJ^1`t{tM%m3nVlFgc8(W(l~E?e2}8(O&D^Gs`K&%6~E46zmu)9_*X|C{*0P08!lUB zy>eY$d}-;Nvn8{-;j%ibyXZXDz2me3=Tn70LU8u?|$_ zH8q)Ca#*cDhEGm;&$0JT?0^xx?Jv4*Pi>B_(xt0DpT1`pt)!rQanL<$aE#KF6-6mD1RFX?nedh z51dveYQk?YduDohUhBa0a>BOA5bL#Q1{ikvW4K41*mnb39`KRiu!ZTMd_uqrRf(3x z1ltxD!ROn7Psw!sVbXmv;XO3g1C0=ZPC}z3Kl-2jfKjP9L=+HZWIxG;o#u_uhd_xL ztaRGY8J2nW^K0T(e%42-j)~>E%SkA82aTn-mUc5z^NJGgf9@pWuM#Y0VK&?NvAwCX??wrIItB#(-51nO*SAht4dlw>*HFjtdFhiRrK_^B;=d#D z{6XO|B^p=i$>} zL$y<$cK%Cdkh^OE)ZOeRHQLjccM<0Z-8HZ%Tk%xG;Q3$Gva?$<5o4>ysS{|`Z@nBg zo21oDj#xF`V`lVx%;=@={v)TLti-H4L2a{)D8|O_n2v#s`1tBeN*?lysJRW}Ex%gN z?XT*IE3vlp`XKNw=Ge86GNt0mxKo(ezs|86CTGI9#AeWNDMKw&E}WRFl>jlmQxg5D zT+(Kfjo;APkGRyuIud86@X^nROisi&O$2bexO}s~&tIy*aN8EaC^}qPZi$#JBJ?+@ zk-EY6=kJ7f19nPLZWBTe0~O>@oV2*f9J{scw492>!52+}e%)wgENG)xNAH7p|M12NB_3s;<9NZZhp29Vgn` zB~u0b&<=_%#cEuisr;Gm9J!D&ZrY3|r>{Fr^$5S!A> ziti(L312}KJN*)_Cm#1@*BP*_G{`>QH2=#5@S)REZtX?b*Hq)7|6ll6&^NrsQq@@?={rCd z5Fw@YvE8-FW$>TNh21vudOyufCs8+_v;@$Y><(mUU22_W z6BFx)i1J5i{GdK)NpRiPqBvAY{vrk!<|(-9J`%x2sRRmi}S;kgG&n;B3YKh7VT|943fq9Rr}8ghTTtehVA2io zv}JGlONWJTgWzo%qfBZy^sG1YVTOhAZcgRBo^zrdd4>(en%;zEQSp`zCr6RXStY!h zFR};MTFkUhj$@P`2~?M+Gg-@pRPE>yd|DQ$xn!m9t~<)?VjZ|#W){*Juie9?o^2v~ zqSJLwtSc|pYG$=eruqC$-hpf65A#>6FIqwR?vN9}h+XjoheaY?X)~*1mL)pS_q^=` zrO_W7m9Gyo&Q{3k0K1mJc`E$I_Am8mNalLE^oBvWDpWJ`n7aQ%Iln6`fa>~V=2Hr+ zx@gpuI)p$Lf9u-oP%sGFc_!sW&VxAhEg@8MX6e_OPyPOwyc0p#1a`YLEaaQo!mk}t45GyPvK09QGb8*mBaoWJBP3mgpct8?AJ z5JQlFK7b=X&?2#A%sSRJaMDMZ9WglWEdBUrb7c3VPl1zW$qL9|kP6>~LAV+r*YTvr z-D7uQT|i_82zd`$u(azx@E!1Pg93WZCQH9&bW8-o+qehmaGIe_*!V!|mRTJ&HEw$R zgT>``?siGdQRhm{i5vN@(O|p4qe2P}UP(jXM`VdQSe%D@u(C5Q#*oG3&ghGZthe@a zVNY5ZBbHZ(OY=q?DC0j~v0eTeD;z(^uX(3VIb_60g*j%~=cB>+yPNrto20`aU7jER zN-s*+{^8dutb!85mGpdF`(BOqj?1xv$?wiES2lUSWtL7qm&fy&<({-r<5>oY!hBtq zH4Vu_EaT#ziu5AO*eA3)Vbh+(IQ6AAu^gPoN!~BP54U(oa`izjt$yZQ4#g?E6`f8H zF2&VEMLq$yzR1H9>DLy)gNZEem(2c@KP-siD}e6$QpJgzSJF#S*Vk`c*Od06k9Y!P z?JdDH+bc1_tv}6IPJ#{n*o%D41=hJ`@TC^PfrifY=!t*j!s$r38YM{JEIVZj(Kl-B$cy2r}i0xYX0E5+o0VtcF_+cWRtqvBX1CKRA)7G zx#i=P_HE2WQNQTm^oK`9RBEl;9^)})yiTzuz-Gz8tU9f?b%OFw976qiGc~ew;85EE z{waRFRs3W-k6$<4N$`}A*^`v}sn698TSCVe3uM)$AGYBI`!Nq$_%THpkGboQR-aVI zU9n17mRHC;e&3i_E-H=Oxau|LzZF>H(I~z;zqC)&3F5Y2&4%y-Rj{z2OMjR)Fi9T_ z+Rt%6Y6qTz)MSyzZ-t5Q5!PNhXi8jx*vQ{|p>={{A%*e~Y2Y87$u3{ut(m??E^Tr% z>XMDu3AhuMOMQ9qr#yVcVj#Ef2|o;|^xR3#5#gMf{K97RVW zuEp=r5~>YjjJIi~M-uV&RtYFv^e(35tT9__D3VFcCAAKRafW|uG3rdzTqeYK-<;!9 zt7XHY_q44#?Yu&RW_llq4XR1TP`TrJ%-t?F>2uy5dA`^eSy0fGJ|mA?hl}q%=4u<{ zkQc&~K|g3Oe0Wjg{SN7I$yn5@{3`nlCDBC%tQOAIsW3){tGRDGV@uljs|wSZUg;Dc z?VF1`Z}q;_$Qml9uvw1cGu;^}XbO?w9X>0NjrqyqMQEasDUmCDr)WXctEQd5Ozn5c z_`}XG3itwf``4ViW%YZ$s&(eRIdkG(E633AhE1QhRCev~TfeUys1>kRxc`-b$>(>& zx)4|d@f?EwBR}3i%`|~WSq;agrF8{pPV*FJWbVbWQmLEn!DucY@F~Y;2Sjnm@S=2F zz}5)(xCwRcFlswi@qej9POeH?IEdU=#4<-|`Zd*|@`lc7iYkvmHVO0ID2TJM;H@tM|Uf-Ht6D?8{NXy?jLeH)|L;lz?#Fq1bl){YTMIlWj z<{E~r+T>eXS3$prvm6$jreCd+*O55m4hVJ}K*po)-0_sy8nGkmB)Iu)ei-~K#*wT` zWoc^SK3iv|j{lRR`Mc?3P6_-c^2$t3)aM*c9>j`S6)!DW?5HDKPBQ;9LY6Z!uQ&Zz zvQ27^%Wr#w@$Iy9;ApjL6qBo6@gHX^8d+wMj~I>yHser2mUUT&B~w6Fxw-0Umh#j2 z?dh*PFIXI#?aY{?cN{9)9^xf1#o`6aOzn4wlt={V7rk{TZgVSl$a>BsX*#UR{lux( zrdvAFa_$FTU-^~&z30`qwa@Wl4pdmOsEK)vwq|$6+O>989=h639&Qv^jFjGJ9z#*P z9olH$iO_eiRpP>bi9;<+Z=7nZ*#^KDDI(0ml;s2Fy3>Gqg#&XomD(1{KKO4 z2=e8aOJwt2++YdEw^0|3ro^{dzXNo&!~BT}sz40Khceilj_f#A)IAif&n}&>twFnq zUM|>+In#rTCTheq^aBr)qTXXTiVPe0=&>@c!dbc%mZ@_=^aab*@p$ANTT~{d9cfQR zOieVEmBqP#KVA92pJg!S7O2R5@g5j(M`)4v?fylY z>sWCs_t8p{Y*+n)ua%3~LAqO5&(4J4_g~)~t=|;ypC~A=y<)P07oc{uUsh~wNYKAa z&K&pbWXHXXUx_zO79)pIkVD6%c)J8R8|Urc*U?`n--P=>*3~s9{IsW?JnMUWS=~5V zX|?1%W$Fl8^%aq>s7NEjygJHg#@M|SkN?ZljaA-ui zDLaKN0zZil8q00R(s+|GN$ln&0N2xyep{i0_Y&^BBKDKm;Mub*RqCks=b^QD5>UKy z9^a=nL{1LhT_q_Y$@%4DGGVrD+&JrcV*YSqwOG|3e7|qOl(bGA9rj!q#d!*c;t<~> zjSJSo>(60Tj2Mz$h=aBnS`^N6@R%Y;`Zn^IDDqe*$3p18$KLb8t9>EKIY$eJN{swD zT<)~f|C-}F4shrnnEJ{i;CCen5j5!SIU9>fK+danDf=LPWYXmTpx!R`*-X2T+Npt7 zfcBNcWDy4NEhpYSBSw+7ylAKgp@yy=>~;+m4Jn<{ysc${U%Ll_k-hfV+7F-Pc}SYY zk34NiL6GJKC%(g)<*sV9mm%Wk!y)ud`_7jeG<%%4xkeG2G?heQB5){sBsm*rTgj~O z@W$+M_f@XQ$rs;xtUB(qa61E?whGQav4Cl*oF$*@r*YZXLs)JtO_Jjcc0;TMKcw}Zu46K+^>Bh zD9~Q7fjw=n)&r4WG%d02QH!^mcSx#zOmR>w6go+p4U3F?0{r!2;9yt(UgMeD@Dm3b zff%C9$#mM7Nie_^_{oXY8mvt;eHpE>EA>RVNjPd5Q{&DD%UvLriLtL{1?~20Nzlig zC}7)`?q@y+Z~uffgxJlFpIHA;r`!I4TRi0QJgTIr6WH^9$5NO2@U6$)@UupQU18Zo z%#O8Ibhoj4@wZOEilXv)uFJibiJuitJGCt%h*ac9#GC(m_!T$5THAc;rlp1coSHvB zNZ66B^A$Vj3RHlyvi1FQh@yO=EItc$Ifr-X8lzxy6rc8647Xzne4)E@1t1-{pUCM@ z-#&uWfK&uZQ$u|c*)H;w;3A8XPTZ{1sK`58ri; zd7)%V!Dcp`KZ}(rP>MS(B=!R)qUijmJ!p3VF&{dZcx>(62F*2Iv2I8WL#MOTaRkXB z(;3ze1iNCt!FGFJ%*(*zBPAj-6xH0i$QQV^*RX&??;_*9{t*ztEq|1o(b@xZFVAmIF2{L-rpg6@-^4F$3=jE+qc4Sx&UrxD;?qUuyw z7+kv!A?;TBR2U7JCmiyli0~GHlF`>0R6DgP#4=MEFr-t%AZF*Q5$Yss&oT=zfBw+DvO?-yQL~ z#jBVFZtlYrhXxTy%5c?V0r^u5{O5aA!;Ifm#vGz#Y<(U|kW{j@a9mjZQ&A$+!rHR5 zkp?%5iOJp*Y)|?wQ;}wLWquvX}~1s>JZ{ac(;hVhh3LKU6RuvIC3s zi=Tkf6UAC=Y%Ai++ElB~M6JIYhIjo_%*1w~6Xp{~Cr|r{_XP_VGnedEN2R&y%o;@R z{vP(sbSmJjJ3ZFB19A^Lg z`~&b?DcZ>F$`j)d^g5NlkDftX>Jv98U1U!GSaF9UaQI9@6;oW1dX}#uLyL3s{-I$} z_^;0cDss7=n{!`dU#y=w>7i5H38eaRb^VdXW!S9(-+UP5gOy+bSeRakuletULRkbG zThb-y!vt;?+6%zv>VOaXm?WQC2WB3L{@QR zpCgk^qlI_=o*CHy{jvZcgr88JoWLNIC5}(w2aqoe+PYQVi~i zJ2*Zb3p0moOiP`>UU~IN%g0P6+B$WO?lgTk1WpoUmuLItK%xjF7Pou=3}4&#Ac0zt znSS=uIUWLE69rag+y@>=iXUVUbrY|`>T@p&;|^K~2WWfF;jBWlY604fb&;KK`SJaV z*vZbIi5h+{eU~M>FYwWQ^Mvi=7~mXZSR;$fnl5-;YZj33u0aqXL?nUOxWt29i{8{* zc8N8MrGY%GOOt&oTN?YCvI~`UB+}z46dBuClEk1wmXSP# zlr?R#lw^x2q0Lmbij1X<66byQe1F&PT;Fr9^Vd23HC-di?e6}(-mmRlb{DrmhXvEW zxOBclI&m#_O-l59@&o~B!7UB_{SFHp?#^Bxa+KsyZdazqzk~bs?NEm$p?VSWVS^U20H+RCr;XuE>N3>FY9eN$bJJT|cfmw0BODa54-sEtUpffO@fO!Ii}wnZbPn3V^D3}8X#pv`TV2^(v{gES<>V~6*> z0A$ZQplP$jB%Ex5Y|_{eS^BG;7^#zNDJeH*_ZFeK44mJR#nVU?N` zk**VA_XuY0FW#TppSw)Ja!c710L=PH4p)GGMBE;RxZd906I9=u-aFewyf}2W{BXzK z&50uZ%FcNg%!!xnAS(1{^c5RMtl!Vfc6()*+j{Ahx)*hmmK+r--L50y28t z*fR#ElaOrVrS#3syPvWe0onfC9E9}TSLp4`8)-?iE)Y!ys1>s<3(t{TtvP+n*6d(A zW=p)EZDc8B$&UCPyN;xTIdu7l9S?(5!YkIp6DBJ`Ro~VYb=bzp)b4BrMZeW$7$MRB zg6gqm=*GE=rnmDY;8>hU&^mv`_FCRSIj5M|yf0|_pzO5jvec*LU{J<4!Y1|UUdLX} z*nXU;_%B70{5NFX7`9yWexvbEnyoB(b0&<#vOS&Lg%rK}{>3lrkAJe>On_{eyyP!r zg~T7)FuNr?Q_~XJ)z^$uu)f>Rv6p0q(h-W&4lkNRJEHC9&bMO{X*IQ+=%B1zs{bNlh0Z?Lk9@h=xZ~*4B+?_)dWsLA5tFvUQReUlgK8Nsv z6P|Gu;)v$jeN4b`5}TiZpcYuJG&Sx`mg&$gq+H=5-ldsY8=;P;2Zi-cuH*xN>Vx%O zpDTpV3i!5?Q--G7AkHuAN&9@JU7jPS5QfiJx4(s5Fb1Wmgw`0&?j^w05uAz^68QX- zNdD**V`jv0%SJY)qPFJmc?ix+U9Hy_NhGhF;qE+#$c7((-q^mX+#9=S@}vhw8w`khcHxA8f>)?fo&e1K^%*b<2rk>tfbtD^F zU&KZO1)OgrZMwc7)E-w6;r!_~tFq{`wZqZ)TBtLil}Q8 z74r_ao&<*Ep0%vtVpV|Jc!ew}0{^1#G&%uR1~x;)-y8Ce=UxW|&WVU$^^|f?LUypd zfK51|=v$m#I{m@b7Oo{fk$7gnVT7%-e;MAgXq|I>Y)B09M%+GcGw?@|4glCLFb-qx z)JTyP69{tPJI>txrikzz2+KmxoJ88U$+uwWDV;wzz;Jas8!=iX6a$7d!_QStSIiv* zP3Hg`cn{t;JFTS>25#n#H;3}&2;MM0fB*dA>5Zo0>xh{AzBaPq2sPUT*;QBs=idJ1 z{#b{@kR>l}Ij%vHfB-@{N?n!16DAnM7*!A2tEY#@rTx8!;DH7Ix%zI5l>(pcBN_>t z{WkQg#gP^dq`zNk*U1_gkVfARK-~ZHMcUdfO9l)jS#I|pen^utZoitVBI?dFNdiMz zx+>on!SI5eM=!mDWdWD+@&e(oWX5pov z4D_jAYM*iS`P7)&`-X3;8j$8vKi`i13tt#y+`0>!`(DJkQnq zrKw`Jk$4QasG?jpSKBt<;ho6CAID*J!Jz@Cm;4*h=XV6zh?jS|AxHmRWL~`kJw5D1 z3yIkGmAwOLP%24mg#|OE9i1O>>(q6t#3-mvimv1GPkSpL_!;okojzlRESZ9co5WW% zKeAxU0!(F1$hcDqHt_#_wtTKP4`TCw;22iwvOt8Ux&Ov z@O`U#+3(3i|Md549l@I@ua8Ll81~{|B@P)=eQ9mo)D!Be^5SM3@%$B12^^Z`;eV{fG!NGSY;Fj3xH4*}p2V%RA->XKIt2Oa^4m6OUEbX<(2)LkT zhlmaZjA2n^MFDBXCQ}?262Hz``~er9^~I`5G@r51z2=~0lbD))b;ilL{;l&uICQ!r zl`$|q94;&@B$*T*K<6mRgQ9dQKasOl5f9IhmOF5A{m+NinTmR4VHt{_Ef#i}HUjie zlN8#LIrk0n8^;hw?L-a?Qt4r{aDFJvi{vwq8!W0sUFbaH+=IJPN_j_rinQv}-T<&0 zEK?vt#ns8lkJ0Ehe1chY&X^mlXL=)V;n-E$Z_pJ50Qgtm7k(J5WjwgF(luPv<9%VA z31)iD^Mt{qL2`sl>0E-!_GGvkO^$4j?Ae#DLjQ#)I4bzI1jM7k5d1TXJr<=^VQrh{ zdGAp{EP27J9yN1BRik6Vd0FBul5@|k!IvrTh_+j@o>mORlqO7zs%{5HhsK>Sm&b_8 zyeK}M38<@tNTo+RjFiKn(F5623`xQ>a?uQcm*Kcew*!7C$&J%!3ZEy)E{GWdsU6sZ zdsojMInEQ`)CXP&i~O(n-}t&r^bWCh@X?V z;7a5yPI^Z!u1S!2H-(s12h^yJ9`qW6>$gP|uc)MhSJmO#jPf=0_9HUW zi(r=fxJOOgB!^m;jsIdl{eX>4?1G)8nOiOLn@?ZM)R3_gX^@_Fsh0eOLCzz>B9@Yi z>*w@HWMzJA!e=^cz`YAoZpS!`$ZXY@5tLGepcI?G9-o!!HtW7?t}^t=;tPs8D5i4# zZ@*$6cAkJLOjp6T>0ps}`Ry5pOHih|{*3*K{)^`^-akP06a z1yy2R7IM3Z>3LQ*PxqY^{wRfo2S};|M~uMbpmnX7B{FfU16TQkz*^z{ZB!H+3nZ8& zJ(eb6Gm~A9%VlIM5ObF5sEH&Kj|7bLk#Ja3+iIA<_4-B~;aFsm^o+e$Cgmm^Os!}087i-H{YknSrG-ek z=4pPfHNLNvFJ5Q=o7+dl#yL7mu{vwE&+tFc*J`#YC)1;|&oPLyuD7=XE;xaZ6oL?~ zHox%vu6dWi&6IxaB56W4_83MFeJ0NhA8Kp!QKIlUVrO^sPntU(z&9y#K7vL_#hT}` zmaMlW{UtSQ-)?sJ;xAg2>VV<;;?Mm4S{r&f#|QG&Z7wo(D7l79v)P0l|f-3R{`8^cwj$o|^1Z6yw zE5=_qwa}UMEkXR7CRL70Gn5~oRHr!{^S&DYQYXNdV1a|VECp|*$N+$=i}n2UAI4=K z@YiSx!r9D$7IEg?V@Wy!(M;hl|95SEA$ysICus_zwRb=Any4%TIlQV;xLy*-v8+1) zV^%-1X4)unme#PG{E1chQ&PlIWj?6|O!o1m#=);6c1)UVt~o+Z(m!OvWBv?3d-acI zaEsqycYQ2Bk{P(y@KOF{9_bkSMU7C2$zT}J%0?H`N!;|WMNofhvuT~M@m!X3qVkUE zRKOQ`v`zZ-O_7q&f{WG(Ksa!?-$n=r9dJ(i+ENkb68PlcfksDbDm^dHwQ_mAFcD6h zdffqos}2~_cP?Zzp%rEO_O5$#kcSAW81vNGjs3D`NgiZ_sh#YI>*MNnHx(huK+EQr z8?~8@zm*wQC7D3bt_`+q-OE(2z7zS`1u1FCiw`wHqv?`Z<3Tr@Ygy=>`gfl~)Vc*o zg0_JW-1=nPz=$pZj}}Ts9)IqJRj2Lr&QUGl`Bk`||DRknC{dM*wQ|-uM7}^MiqT~O zda4z}g~GI+fJ4H3)}qc0X=K4F18)(kV~#qnp;;)>U6p!vdwZ($%UJ~VHuucpOr1Up zUF{Z9cdZ?FQ z7lbxcO`qZdiHnY^SW)I_Sd#qPU8NR)T5B_9?Sv+&OU2$P=Ohr)t+_%Xm&#q#HV~*T zCMabbT_+~?7}K=qHH?$^&5b`uWj<|95$R>m0694&jR87|w;$$Po&O=Bz@K8;ue_f;(Wj&Jz` z+o<$`_oHK}rl;}nta1E4WFcVK3C_K#iY?`RVhkAR1Hkvwl2B-VdadCql~_BY!~)nJ zq4R8FEjQKJHR_^fFEX{ZT{vgXl-e0OX~wOX@MCV==Yt-8k~V_G167O7|GG!~J+-mE zrF@T)`=xh;`v@yomb_{{23YwhTuAzUy{SE4seBrBN_#Fvi;fPI?D};ZjU}(%BNm!s zEI|u+Xs?zM0Nqp>*sS4#A8v%y0$efLn!YH=E_`=5T;+j_%+~%M5725RVD+*!k>r-H z6rEJjC~rFAL9t;nA=hSv3q>rEl}8xoSa6)dWDnUTy%4?&wI;Y^ncZASho)hs<# zDk_ABEoQ|g7K$RnBQXDsEKAKJJ)qP^jD56e1NEQZn}) ziP3<+{YRS&NKq$Y6xwx`$ng+1cN3S9ZgsU?E`&?V`e4Fz)$_Yx4JcN49lM%WNU2Gl>PmvYjSOub&ijMVD=18@k$9 zj`rS-$f}XX`ROJkx96|*&zP0<1e2C)x)fNHS6Hj^ znG<#@ObOa8xHoT=j^0f*{fS3BZH3c(C!lw)^usq;=_-tFWuXH4T=Y6MG&^P%$muzK zv}44tqG1^IbOv{V;57lEvc$-|D9c?BPJa&_BwHLL6i{M>Gfm%f1lVnpT-tM~By0tm z_!Sy*XG3~^VI;d}k-*NIXJdYiTD}3|r8pMh`bS^dm3_cCHrlp7QcZo`Mt1?zzBUP+3~P} zQL?!M}m30dex@t zeKX__sN^||(}V>uJS|N{Zgl1Dk_fFZV(+kNrscEIBxd%=x>#CkY5fqvMqb$2VUv;k zHob;m&3tT+sz6H!1aj_Amr6@Xe3ovDd#);s$Ge+1w~I5SETumSnt0W>cT>u>wsbr$|-2IFTBl*t$+RKJ*U@_LOY4C)PnjKzuai0^J98=X^ESTM+I%5et{!k-*ZV% zxGwFwvK`z~R5{yS)-IpG#~QVTq*L5d6lTSIHkC=)_AkZbZ-s9{w?P4QskZe*hGnXz zS%rFcRCk31DkroqvINxHL&?&)IE8Zp^@k%gmgLAfk8rSid62oW?92MYE(h?G_|V> zGm=!^jUO-*o_>Wb&m?on7!_srWaW(qD_z;a|0wB|_&8Q7nJ^ia79=f>PVwW;MX?j{ z3c`-e$Gm(S3Rn(>TTi&rm`pyoys8w;m6_md&ekAZZA-3uqD8qp;!OQ{3#^w?47B!@ zVCwA?P4EXBCT;K+by*75nM~9CL50%QBYnH*jz*sqC&IDW4241e3ET~KTQ(l(Az53; zlxhYs@6bo(Sv{M@H_ia)xxkaoU=gicutH$CCJt4`gn_1lB6v z{AaW#v4-}<=4nROZra1i)FS>}lRGahTAe#waGu(2K8B5jhj(lOz6i|+!~XY7p(T-q z1@h~h+ol-LlQVI&z@I%w>oKG_v#iI}Y-T(~$DynpCzweZ(!0Syi~IZRtHt~<@891; z_wfWgRujkHNJ`<3#o<|V*@=l3{W<%O597G*`Ew@r?~W&1sK=#FCd(~J>j^68<1qnI z)Mo82$3&av3nSQd`@hfNME-8TSf;Zj=H=mC|6Cd#Qj&6n@#$J_9!`(%n3lr9{=F-U zih{OS?AX#hOL>$+L69RayCGe*2J?6I*0XFLtb(Vsxar%N_>E;Rw8DTe*m# zcCc%Y?R~X1Tq485>G@QCr>B>{Ym_N-&+w&NkRZXs7C6S4(L0ibfG6J|E>1zc@!p}H zY}0byMbLuVYDX*xG=BIXVzSsvZooM9&_B68Y`OoK7$UTpcT^xT02&^cLt%8qWgL7; zSjH>U;pkm4q75I8FpRwLX5OPMUsB*}{DXLcl&Lqa{VQIAG!4umiC$>~zgNYr@EJQl zR=xVed79&U5CZZ9cU3I%I~Rxdge)q#{`1~N=`c`|6(mA8 z0v4pDH^e_vcdV+(e(~C{v3{#ooN`Yr?>R@iZ@4&h_o&6Eh-Jmm~9T0{5gy}yRMwSFNl=)oeV5%<*@n&!LRQ3lRbGip%%(I>R2e* zsdBH!3HS;3WL@z_5c)kFT*d2>D!y4~NQ{g zMZ3N-f($4p$|8il_M*k}*5xditzrpT_cRbP{ zu(7`E_<%v>gGY30G*q)GCusUMxLv#njgkBPQ!ltAU9!)yBCu3FYM2@KDEBPdd&s9? ztNKDmzL75jDP3`|@f-r-Xlg`_L*d0LRKC9k!lVYiq+a}b^x$kzP*9B0SSp{`&z+!z z-3w+>kLPM%f4=|l;cLP?)g9j;f$=^}jba~|ae}m4GzezkW8tMLk3?>JL+9=4F^Dyv za;S9KqqMJ@OLyDc|EF|X1=JzHv;(L7jT00PxMVd zSH{Ecg?gs)7XFuq7jb2mRWH)2OwTV$mqHT7N7Qg+E=71>U9x~tH+UL?;da1W<&45QW?#tIt9$eB7 zdf$UGZy`LO`6?P+NG3ku$K7%5crtfai0hi=*BWNc|^< z1^lf}vQ?#~BV^l@iR(3odZ_)~r=LhtVTwPKX9??R#=6k8^3O!(#dtKb+Qsl2+P65_k!Gc3HSt+jGz3Y1CW%_c z_ZMjYQRG8l2|K? zmHw`fH=DizfUHzIjbUL)C?e-+D;h$w{nt5>vPv{vO;?qZ7S(WShzoaSR_KUd4HjJ( zO2wrcD2gR_ntwd?I)u3S8wk;CTc7eb5a@=NZxA7L5G?(r^Jm5&=`+6RaS2WD2ZH`v z=;Jej*^$B~(AX){`h{(p_MMG`Vvc=z#e&n{jDIHk@V6jFhuOW8#Ivjn^@mIV?i52 zB-q}7I)z;t)Ha2o(TDJ4+s=m`&SF5K>3vt)!)<#n8?=Vi1zlA~)9Kv?)Mt&f5R8)4 zL^*IVj#~?-BKWJGfyY!KAOH9seCuJ*d$FOPex$BSti1R4T`mh3j?WL_0W?<}F#x9X zUDg#mF+{X43n;)w;AxJn3VF>FEYb`NF-T2-h8GXnD9DaU&=?;dm-XaKvz!5%Gtxz{ z<4hfA(KZPa7Hx!h)Fg7&X)vt8s2ulNi8#;fkgYvmEj;q@x zQQeWuokB)Utg0~1Ag0%cSk{L#aYq)~He2_Mcs}dB(Ge_%z2Wsb@<59h%;m;!BjLe} zs!BF}4IZaPV&6M)v@nYI$|SMqHJHoO3S|4U8R6p%Pn7h;CG;kh*2clXF?*r}>7S9@ zaRfW=cBV2kuRMeVNX4slFFlbylZ<9n*977zOnKSWW;-r5iTM4}}2TVusZ4^kh#94V^ z-&&vDPcc1@a%^Nj-w(VMWR=R6B+5qNCe=8V&4eRS7fOXd_l@TU$*QYC7Mke){D*Ih z#?+mDtowM{h1#a&8IPu;*{^fr3YOP}o@1Xk=v%=L?gLI6wbIbNhU_(^gY3$8{@K4{nfztK(|;7sMn zRVp@-QCE($&CWO{LdHl4pnbO?SZF&2;=Rwdz`gUIcyBT4Q!*pMh}VmiTY!nTd;a3K zIX(`-Hgdj4D zI}s7_&Setdlg?Q~c_JdRX)9@Ih{OlmQG&L;uF}tA9O^7TX9vX3LZp1>pH3foKB_s*mqt5x z2f2xqWpBV+%D6i(p~OHT#6un+wk%D ziF)_9>j4E<7f-M-zALIF&x8eR6JDLX zIeSCfbulA5{=u$NoVobegV&`(>;fJMYA=16UL|oW4GWN)HGUo9Hn^GI5q)Rh{OuDh zktnScmckNp5Ywv@CS&z=0gF(R7uh;7Ec5~k3xy@}OE*3rgG~I2RMbqB`|SGr0FLXFv=>;e2P&|Ex|=ym zt}O<=D4|#<&PpR*CnasU;c!(TKy8+(=2B6Bk^|#&qK0=w^+fEm%(sHrE|aRTG5ol& z`a4G5sWa)QusPo=ir?aL<^$V|%4TGkGj?-?h zJl(q@(_$8P&8D?&OFs@&KF9Wq%E5%dr*Uv z?^0jP3MqS8syvhPNS&AybnjK66iwP^6^$G(b@DuBSE`Xv1{vr}(*XeiD#$0dSEOl~ z1JCzV_ZarfT^=5@)!l$zfKp9|ut@p+Qhua+i#&`xIOtRB50e<xLVp}RNVq%JBXyuJC=>CL?#nLoOJh!4t^aZV(y zD=OXX?$qmex~RD$w!=vF!9-#E$6e(z4n2;_@4MgIzf;Ny+~c|zans1EP&HmPQ?+_f z9voQqm0!>Et5bwi>5k(PV^^y>@krd7;*#o^a(B*yT(MylnuO2ca&dC7F7s$?|XJhTShiX-7dZ(>^}B#k-E;0Hy2sSA}zNQw;K57u<|e%WF5k@XRi z;h6mB++v-l6DZd-;rGvMT`Zjs z=T{avTnpTpocLYjoH*Urj0be)3VI8=`)*nAp)32clO*b%)tS{YIUlu-3_hLS|ALu) z9_1P6NxHlbUxhnPb546r$7A1MQE;5sil>8E-t!a>29F)Fe6iHh-f^q-*7aD!QA5>Y zD#OhNgL*CR>fr#|6Dkj7#joPCjz^Bd4@D1)_v|T}FI@uJQm)KdI(sDP z_PtJ1n_X)rms(8gNE=Lh(6ZbzI2&{Q+1*EGvK_6;naYlqYPT*gyx+L&O>K9xQQ!o9 zG=4O<+r2CiUh+wsW`N~Qs6LY?$Bw~wt>Mue(gE1O&Pvs-rMRDQlX19G@zPhN4UWzW zn(YRmV_|f^gdWV^G`X2aB|~?}J6`#YZQ%A}iS51@Q6IYL40V3ga+nUR{^|;?4UK$Y z{LLil+CAuUuw#BrW@pmk&f<*}A$y@s5GtxEO5&~q74?;rtePz9tgEoPEbMFMn_ibg zKeXPlSIJ$`(3RAEHHqIV*-O~l4jT^(R`4-bU4B!V_sqb@_*37vCHcC$wBLN*Y^ug) z!DAv`2mKh~e_f?Kn)FJRBn#uDb|kdZXp37fTc_+ciD5P>D$}n&IVRhsNz{Daz1@9e ze9!BiJs8Q^pn=icFElSM(!W^v#&D==^c`eoGC==qDhTrtGdM;loAH7U99J`%2vsav zp_i8T{T9v?4H{J&1)3}}F>Rmi2T-3IH^$AXtq`zc&`DtahtUteKBNlm3K|jmmf_zv z+p2%qu8yzOxS|Bt1-Gk2kz!S>+^P?|96a$f=rzKt z@7DbAjGRk>c#Fr9$=kec+f<*hsvOcoTt_S>(@P{eyH#imiumyE>-!<+t4PrceW`w9T|wA`s^gUCpLC+xp=jd48aDFx@C{T?Yh=|s-|ZrP8qGGncKlb;mbhu|t>-E&dbARmUJb{-MAagr#)rIP{oJ<<+ZwcR<(rXW9ng=S+FKuwa}G;K z7xIjHeIYBZ>tfUBA$P)?#VArj{V82jr!W5b7u@%qio0St$bxd|%hQ#oVQFznLiOhmxuO7Y{k~xK)MIY8OK%Vq=P>j94QqA|mB$RI!U3{x7JN-dvbrVBaSm)J;JKI#*7JutuiGWv$zvka9KqN;MWm8O?5CKjSJ zp~LUP?*Cd?u>W#&!-AwaHf4Le6t=z1H}bj^_f>d#dRSlL_Hb)Qq5_NPKerEWCyOT= z-Bx>YX5@8(CcDpBLVBiucFCoJZt#8WPz;doYJ#=jm@6t0aRToz6I}?gBDw^;y8yfx zfEN+b#c#nxq`>c+!0Tli@qez8x~EhwvJk*_(iQJs=L}w-7z|5CPsoz%IsY9uQkQXAuvvJOBEI2=M;= zW4=3V|N4rHjo2M+MP)W=dnYiPAnzmIM|Z@@*x1;fJDHh_sJ@i>_wB%6Vs|WETpUFB z_}tyydEKAz+B;eBJr))g=6l4?$Is6Le1pf?)6T`%gU8O9{l6aapXa;;JDWOLIk;Ha z+p(QL*Vx3~)kW;io%4bI``>@f6YOF2KO@;W|9e@$0{PCb@IB^z#P{FN25xY@JYxP6=Ma>M%!^@pl7)E5{J3`-qoM72xCY>x6h8mhPscwhY@9xn+XR;3~5 z0A%x8afNyH>3hl3-9(Rh^9hQ?FYmu^9&*FIyOQD&@yELA4!ZdAc~p@V z(}(x;AS+4X>2R}E=L#<^yBVtojj5K=Mv35TpR)cgF|ppdaD4Yza@2jsZ*DqMPlj-o zyKt(pLvcRcCU$6YpQnE8!&Ou!w4;0bMj^!!#B%}bY}=Oi<7Bkp>W0cATz%xSUT-zk zMiPQn_g8kdUhx$he%q)aqZjQ?n#EbT2Zfd8$9>4mH|eg}(w$AvKwHjQ7#^V>iV>MM zqH`?nDa-cLFCAmm`Z;*e@3xvEI694V%6q>3lWH_ep3Zx3Kz7K;$Y=YNl16Hw*FVU; zf5`d9`I=73#_Cu+W&;}RT1kE{P-@zM8gADg>W*-eEq;8y%By@;!2<*MXViXL`XBL}9ZBZoAQ0l9FOQiZGSCijqYaD4dj(;PqIw% z`axe6@?f}B4L-P|pw%O}Y7a1?=s}h_h;D;dWy9W}{Ad|iK3_dom6ti%`o?cRTwpif|4`e>M8r|)%Ve!c{|bF*EGd|jV?)SG_*~0|DlNUdea0t7z*SWT~4kQ ze>N}VcE%ySH(5-pliF#+W4v+A+jq~pX>E;DyqHE>^noRKSP)+@Wn=SF8l1x)Q0Xz}xjmeRO}THt|Hfi!5xE7X0A2ss>sY zE?=%Fq1+L2jb|PbBpNxujT01?>6u=6A{E4mAUHHqB*ZV{yZw$$`Didg*2? ze!F0afpwCPBUMQc-EO%u-r&8ZI^@eaq-lqX&l%FX3wB7vz?K^4cR%>^_A-n#6|pU3Lgv zq2kxJ)OFrdmjG0D067!sSoPYqh8VkBCF3%1Z)s&0qfbwz#dFl~Yb+`nW692|vt?ha zPoXS9-f^(hzJlQb?ZIOMO@vpt-a_HinZcrJdbd1A%H6SzEnHB4Rla!m#bk@{8~d+d z=^JDb@oEb~IedfoA8(?3lr`eZB~A}B_cu`}`_b&Ttnvy96+$x}t+afGwnH$*@>nC^ ziHhkQmCO@CWVL-Y4&{fcbK6*peVdvRvvh{vY(hFC#4O%Q5GwXJkYnGXGEs;?L5*U= z={)sZ6GlPX5)e7_Bo#Nv1(mPgOLxaqXcK5DYj zT4VAoagb}_;_rhR=rx;7i@{jw14Irum2bv`ooX;JP|E1cApnMKGO}uNwX@u{v$vL} z5<=1qjUm58Q1o_1mpl{_EQ688l#1|5KEGpJj@rnKPh3x?w^E}%TO|Jew7Y%6ESBKD zlm-^uSv!t#F>+3I^}^3qNUpx7`|0H}EQj&*WfslQ)G?C%7RsBv0ShiPs{LVrf+av4 zThb&>uQi7m#hYd7H(SKmAmX|)df5T;1aUsNRa7>ivUPOI`6ESLS)4n?>vvLQr!zAj8Dosqn#wh$`U;g%CRK<&bNxBTlG!Di0UTZF(7}4 zJ!a(hj%rH13JCgmA3Aq?o4zQ^bo$xmDvU(yjbp$7`axki)tc7jP1T>9>9{Z@LhSf= z41?~@MV@0Odetx{+w*89^&si}Ish&2+&3G3#X~GoZP3qjb zzCScQBW(I@)M1KtU9A>;3C9zK)iZ0cHVwqBU%RGioe6chYT1)xMt+cJF0HDdYa$)g z0;ibb3f5PaO^S(v6K&!2+10F1%)<(G7R*0+tlain2Xcx*`zIb-??g`z7YL=1Vh7qo zZhuI4$?_y)g5X(G!(FDlHt*1W4R@tu(ur$(^=A*Dbn2_vlzrW9=;=5}l&dB;NrSV_ zKOwPltjOuYfW1LgPKt(JI|xb{$_MsR!YEg-=e5oko`vln^s8toLp`6s_S!^Kg%T#iVmE*To}$q6fkcyO${ALs)@CaaBXIY=fdu`iu>7>$QmdY zDCj@V*Q>3VUJ5Ln%X<0grnrrQj!ihDP!*;4A)60XD4*IteRiq?jgA+rCGPFk_m0kO ziDBwc`xql0PGkV@iiTiOO|%w zhITZJ_i(?7Di5jXM>-fpVM5P(kEt`<&gW_>e8Si5LUDV9XtPX>Re^@M;`sbk9MmUi zI4uy0y^1|@%OzLeED?4Wph$>2^<7C1Nl1}kU}d$Zy07pWL3CB{<0xHt`DT&bJE3a^ zc7AnNce49lGe(^`o9@!1VYWkb-!&Qusm@0wj2Kcao{em7ze>9^pr_4&Sm2fRaCS+V&GUO4uk3s~L3ons9-Jx_P$wGdkOS`iJU zXElM9P7+`Gg~gB~rT>7eTIGSbv@z|Ddg|$OvfW+rIw7z zF7nebJQ?~v&CttHF8F~}f&fp%oDIM@2mF@W!i%H{TZ^h= z4-8Uuc(Mia-qF>vKCc&cs}x%aL@^sQ%;8Gn)?DZe-w3e;!)76SYOGE zJTPrC>-^@{^)UNVA|Cgo?^&z;89LQZ_t$5djcSzdE`7yufxm5QFa@M7j9&avz9a^U zn?~b?w2!x1DG8JyC9!>V(Y2gR{$WNRm0;}pL^-rXJHQa+7D^ZCTYU9k2p-r?#&otf z^!BuT@&}t!xp}XTOmUf4G{sc9bV&d7Vc2i0M#+GiA2*7ZjSW=O_70>MHf{@5Ix2rN zn63EM?UJus_1-UeE>O0*wpqhtpxnGkw`AmPv5#Bpj$08qEG(h=SEFNhAB4dk=?<&o zmZhs9z`ygX0<<1Su4I;6YaTBbF9)x`)8^xmg0k+|WRF?5U%;i+Wtd%Ih(+4-3WzWi{7`uk4SXW?DH z!0?uxG#=3;joPbdQ^Y9LjQ2}fx}B$vK}&lmE&$b_a}eUO{>AsW2k zE|wxSSPf;wCg$*23s{A>u{yre#oQjwdPm(wZO-m;;x`Y%@xHjJH(DuH7z2B+@r9$p zY&3R*x5kq9nM5y0{%Kb6XV|jcCUJ1$lw*IwKKxFky)R>MyX3r$G*ZRAYBQS5K#O0D; zJF4T_j3*l_*f$C0cHxh+txi)=22EkqoF|)5JIBuHSTjH6KuZV3ogOZ^x3Gr0P zvsN+KeKwDtP07Kfpoh!yj1lZCtv^`=-(9YH>Hk^a+F#i@z2_R-K!M!&4USz2|XUKceX$Cb!mWmCm zu+b0JMQ7$s@bLDR&w$ECLG+(_efQm`1}(0q08m9LVV_U5Td~#KZ9#5yNGzxd9Ifg* zO<$CP<$Yagu$oh}=ZffoV2FS5RxO6gkK?mz__UZ+Mo8RpEZnad*~tr@P^}+ZWQs`` z%ts8ggVNLmvYc>VdS>6*fZX+}?I2`VnSiQ$wA8fyoklRuEkwP_NZ)NWv8k{!(I8Do zRYk^HdCzeS^>yu#-%)S|B&DIBG)Ypst{%NDU!`Ls%6}smvYp|BaHZfMgbL&uJ_+EXRw{`o0g_nAA*=S#KK3xC4t!(U3L0R#F zv`j17wklD7%ic0OSp2%Egl(Rc80B7sD0b_Dq9PBlyy-N?Q!!m5>gN)bs5^{aR$_2O zC2lz&lyD%XU$WinY=bwA(QKoaryjQ@WG;*Ou2lY5a<8?@K*iOJ%D`8V zZ(VU=1+-#Fc%^qrwK<7Ln}Ij9FCWr6Mfuj}N^J;Ob5qq_>2!a|1T*hDn>InlP~)8p z%N!Fc4eI`O_Oq8kxzaYt*Yv9|>#bSh!AO=H_Bgo0oar@Ai>BJyEo{@~jk)%&Puq-! z7Av^>iV24WS|EE@%3*=-^m`o{BW?+tIv3OxJqr-XD##PX{es-2%9A!m`@Mj98ZOO4 z^q!#Qthvv#GavX$MyP^Ltv450+gkh6gg~7O$dUY^6kMU(L%2{qhp6_3n)lw|W4UO~ zXmLy+b}7M5B*rvktk?bb$ly+>WSe?DcZFQQKXbevU9qB9e2aJWM>k&MlBW5K`zmC3 zJsiJuS24n726?2&!o z1H(<&V&UN$x9Ipt=@1C!TeJMGiFZ;WZ`TpWjqIM@H8XOPif^D16RK%Nlk;Gsbu@>- zS%z4CyKXM&XFoVz_XFrk(+`b&&D%EozUs?MG`yx;&U7zDH;-lLwPX%Q+r$|6P*~yO z1n~nNp%Gi2Se;xq0f+dI_PXYLIb_|E?Pv0v%m<_TsIwvcX7nq%3a@66-yIr|8kL=; zC7n-I2Mc3UTS9GEi?ChZ+FRb7g^SDS_^XamV^POE71{a@V#dy&#awMQSbJ279;xc_ zIv@laekPBRVe8j?nC4Qv8Re~X$_vES%kqqFMaLY)MQ=N1k#DU{w64VxBMeCR%8QG0 z(3J4vg#~G$tnG9tdIHnu98O+ls0~kIR(Z1EtWGJsc-@a%@d{(^OH6kwdyI}KAo21< z^|C0+B1@W?I4i=bwuL*~s_8AJx?i`hU^m##3-e6K@Tp?49AU@qcvA>HE`{})T=DIH z)F;L&q&pxiGM2m$so$@HaAjWU1Mj9dPdnRnC#HbBBk{(+3Q^pphIH9Ft`795*3-uO z`S9)OQL&KfsKg@S6VHi(2)ZzDu!jsMqL$S|e>J62@Aw0~+YfKvy-iHih8)h62*m#s z;`9R_P}as?`DS01!S8DXBR6?N%nZZ`Az|coI%?q7KDWIpL2{*vV#d!YfKXF36jZEP zVC#s_mA-gpFwumDAJ`PIF8SK>cY^Yo;CFlZPgr6M1MpuAv*C2=PHIx_jxGCVqNTwo zGk^Ttr=se~B5W^~^k#9gW-Ic2RYMd$pldaiyX5w6Ro&xFY%G>#yMmDwQHNLeSe=F4 zM~!VW4o?7O-Bq1B9^HzP{Nl#Rn^aUA6BemY6}t;;j5j7LSg&8guNtViV&aFpdU&*o zk~DIZTS%Rw_fG-eF<#@iVA~$Sd>V3tY|Yx*dnC8V2;6GqxfGo~@`_N+bKm z`#=CQJvdw}I%Q)&l!Gla2bBqYuvxNwF636o?Z%)(dp#r5&R&&1nceRrmgqCO+6<~8*(1(FJ9R~`c)GMB z+~Ys2`B{D%rY+Q3F6t{Jeur1xX+2a{U9`5yf7ryXn8|E&RyIhNO!ubyZH$TES_qBi z00>7@fr|TXLR#aT%=oHZ?Y?F0Wwr0zxTS=I~W7*&`;59IK^5$ z>jb@za8XOM{;Wqv?8#GNt5z26aG@f>jseouc%@*feh5}Hj)iR(Svnd|tl%}B2atM* zB%sS6g>q%Ir~zk%4NbW&Ci7eV<=xp8>WNwGDLcC3_xS$5=_Scg(80!qJRJ!Z?>`ex z|GH`>?LTWL*gp>XZSM7-3mrB9LFLKQxBYhl$QGcedPx}Ie;?nIN76y4_?Ixt?|1v( z3;sd@3_l@hO818y`7a2ctr&C}yIlFKUHZ53{Lh85+raRQmg|APt+W1fVX=~^WxAdg z?eTZ{3j#Em`NeR9zssqfmaQgnY@UhoKQjI;2hkA>3~!rOO8$2_jRCaZu5Qx8-zmol zfcQ(K5O9jKJ{a0d2g0my;nmpd~hr5h4GP@o&}T|L;nA zJvJPAZlRy!nu94AZCe6CRVKg4@>R1wGrROlX#%M_lg4e~ zd(;Blw-}U?DHHy=ZWB}7WM|*79}~@^H#*~shu8o~?{U?YD_72miNq%s5tM0yw&VT9 zO(#3ug#hZdiUhGz_iqejhEUM+|D^Vz;Nj7$s_;9*qud7HJov!dw}T!YR>!ZGBi0Ex zR5cKzEykPlYn|NoN3@Utm}w1oWBO6o_3?rGN=b3XIu(4IjrgKupPf#6P5a4;PJrmJ zHf;+lt+W|4*Ul!|1kf2fI)Eo-;RWED#@<9B#Ekc5g95(MX-Q6W>ARXw0~r%(<8+;C zId&Ru@yu|AZeJ~6w;UmkEP`W2xYS?@$7}Dd{^09aY@G{XwKIb5Yy9MK+nj;K8o0%; zo-_g6;f-qJ+kY^|MG3%N9c?D{-?@}Q#&JEbS-)aur<+f&*s!7C#RoE&|MFK^miWe1 zV19OQpKrfMHR)Db)6&sd0BF)~@hJh1Y-jaLy8WdFz=Yw;2{yL92~S6B98EV3a@wvX|?CEPyB4k`hvpvQdmTQ6)TD{>#I9RfpC{rvf| z$qqn@4+dmej|4+Bc__g*LP&k3s|PGR-^WOtoeYt}M%wc<@{b!I-bzSHvQ^7gss!p< z-B@0O6ZoW7fp+QMv}@07bmOBgQV3nHrRP#tG&I=Z@ECwK;wOcm$1aS|JxVD1gGoFp z?K{8SYfw9EOh?hwglh&WQqfs@%F z%Jq`gYw{9p8LvXubF))+q`Jloy?ZAsZO~)AagSR8NJ5G=N26*JK;B{-5t)(VYZu{N zNzk3n2Sc%#*1lxXLJLHScs?$Gl%-@KQ#P^8wpzILC=e^h;ekf!JP|6&o?H_C)p^uJg(JAG?Hh{+! zTNV@T8z)F87|aqFblW>tohGfaM zc-TZZNJMQUIrnv5AE#JTeYX$jVj|U2uMkQA-=FW(Bsu?sARQyZ^Ef(-B;LxqMY0go@3!jJ(E+%ZC@43=C_En5plpJ;EK1P zfty|=C1q~awi7qGy9V)qha(&$+!4_go_kMqQqeildG%Vc^XbWv;Jap`vlFB@w)TOK zYw%`vidP=9bI)Vn&z9hR7O$0b`cd=&9mr&#dme(U+SpB}-qU&~!I5wvQjQiRMQ;B| zQIF)=yk6e(tBe{2))MDW4EG8yBK8Lav>#y5Aj_FRtUb{H_@Zq!z7P_%pj9ZqxbE#4 z&~Ox~WCmN2EdbUy^LACg(dTGyza=EUsIF_T%T9RyBl~(cU(+5UM&rN^aHhB%ImFS( z_W@y*`wDThhvVasUrmTA(|)DIJN^Ix#T8(ne#paz)M=Kx2zQQFS38=#_rU&}8(CbF-P z9?sW7VC#34&XV*MTc=7*Wrv2l&ixag;zEH1GMH8jy)SWg6zPX82>gfaI#*~FU?K4= zTon-w(9z-6gaeP!0-e*tgljtV<+7~M3O{p)cz*DK<_AQ9L8-~B;SF`!Xikk2FR@mw zEUO{dsaVr#R9xIj_JpSY(RVeaiOhJo+eTT>aRKwS=UbBG6!V)ZSIGYmveKHbQ0LBK z!!+0-$9YMfb`}AmLq|0&HdRIZ$%(X2`}*1odn6Rq-f1DyHQEXT{kmqMt68z@t&|cy zE@WUacCD7)+tR_>TLpUia`d%c*9rpz(w7G1`6|1jxz6!0+vT2kUlCJ9s{&QH8$@*V zN7Q;3SMeTVgJPPGD-|dytL=^ad`85;UDRi)%w*!#Rzq!l;&@w3e|iuQDFsbNy0}NV zbcF%j6soL;dCF5Rmn`CXfr2pf`-nv)k3~GPcd_TXbn|vmL-MoT$T- zMcoAzg{CrPDdBi5oL#fbYUuuXXVE#yC~ldz|f1Eef8{wF892!cGqNyDKQ2=D3ewiBHV4}jy@0Nm`B6dLeJiNvX799)+R zwGb&{8{tATTY$SNu6Y$o8?RJ4RR-?fGYCYJZBErbflizrr&+}Y<;JpCO6d#&Nb~24 zF4DSvg_$0?4`ej_dc7il$T~UviC^8X!7l{5PrLMllFvTmWKbNf1#py&*~Czp5L{_& z0>csDe4l_D#R+>u;yL+5IVp&|hvBW=Nc*OZi|}@4_ar}GNUhV-pwJP3Wv{1)umDQR z>A_gXJD&3<>OO1?hyiH2-V`z9F>ceI6}tBoir=UfS1ushXru;T$J92pl=}f5$pi?a z<*u7E(|~8RsGfF9%q>hX5vg!l8}8(-UHsPnN=0I<*a!vS;&iFZjINnqXcA7f;|BrH zhH%>w`B8V3l}12IQna4ipl;Y=og}`etKwR!VXgH@o_MB+k5ME0@B95E=V&<$Yv8e? za79r~WXDfj=nE}!7gpB@mm-dM@|B~~gf8s>gJ|bI;dWm5I=>p*&MAHqhe)F+o&#dw zPUtDF>1^XhJp|R*K$*jt_%yCa2jRmEq!)#T%tt%x-sRC0D zc>v#DwgL?F#HGLMokj^Rgw^+Bg;h^?U~>uoSJQ{pQ%<3&Zn^>MX_tni?U7YH+SJ=( z+Qk;9oX?mHT}~Ni8AnNx9L$D8iev#2nQg2S+z7FT@`rHYKBE` z0u7`*E{4@}@vATVv1!}*w|nyZ)3Pl)fYUmHM2H=X??C|Geno<7D?vx6-lNp0(7)_B zAzHQ0#flteFf%4@u~cIl`lk@Dm)1|}PFIj>xe#-;6J-%Fnnk{%znY{{c0bL3ldHIX zK6_KcEuY^zD8u!s^>E1svKl{YU-Enp3dFioo2{_1S>ej_?jR5%mttD{u;5);U>bls zR{%*RDAd$Ti>Mr!YvUY*}=1F*#%JSpPg zHI%tMiV}!0O`UbFn1iZ^nBVsTN~V3h{F7$cS4sak`d(3@VH{kW0}peCIlJ#kF_VGM zoXXXNrd^_}x{Sp{wE(8~QFLv-RU*xEjef_zBw@Ev9Sa>FpZfE}l@HnAU5%2VZ{#F1 zY$O~YPjwnEl3o)4Z>2yFCSVVP7v|?}fu4{9fIgov@*^yz(lfFUg;z!b5n~5(>(M*^ z)D)eTrO`q?z5TTjf;uAVzT(b?CO*Yu{))|L!32xOIU6T_v=}886Tk~sPzK6!=xq3LW=qR8bV8Cu`{E9Y*bJY~vHEMyGY`mvZx?I?0Jyrw(;!us)UcdAx z+})n>srC0{kNHn-{Zgby+()j=0sq8No3?1Wyd?6hirw7fZMpXo_EHhF)>`=bOrMcFl}Di7@g# z0<(5d_z5}!o8z;EtKaPlt?*XpUM#-4Fi~!S_xG`Ur#JD>P`1TsD~%Xw{GZUK5Atbo z-YMqRb8D))_QCh+tx*Vk%_FzMj=j|$8Rm`p`#8Gz(`60W{rU-TcgzG3Y6!#KKo&KL zNss#W&8Mm;Fo%qdi>pE^h(r@;>}$X>Vn}jxb75iXU{JA6d7A)FX_;~Kuf)Vx?FN9S zx_AFRpaUa5{hB(xrL-WG5%YNKrsI7KUXbgN{2y^4C)G4%5_c{8cniK0Cmkgbl`-F& z9)~~q3cWta;8$0e1oMf34y|xcIz{)SXg2E}n({xD%WTT8L1arw)$C|jt{>U?E3WeB zvV|MPbPW-7{VcfEp$E$`tUx(%$=!6+MEVcufMjz)KN#D0f+6)j!<{Wm(cTGQjP#jE zVD<32y>`Rkk1M(^$p%ot<&E;EGp37EEJyHw3f^wwq#^F8(m(V$0;!nfZE6nnmg&dj z-pFsgR-QqtozS!zb}kIxjD|6uI{O zbzx5fh=Je{zon!iYVoSEDQWkP#3kT3$8VhnkP`fm47i4kQ1BTVwKdYX`&M5CThUkB zC-xUSJ0Rn(^1C3oO%+esjZe_eKn78Kn^*+LaPbd4ZvU!0K7W=BpuqaZ_BZ4tu^-dF z@_&3C6#VPYviP8yCQ0IsE~1A-X@+rrfAC)E7jN3*v{K2x3jQ*6$1Ha1C(En3ss8p` zmu`_iW-Gn&hv*Aza{6@+hZf*00n{v1@GdoQG_!P*n1P&u>Te4pgG&aGVefBDnX#iA z^u`fa2;CX}ZSUJi{wB$PxRuia;eg?m^K|Nw?H;AsPh9Vh>mUCU2JkQKj{}h@IJmFE zBsS0=2;{r9Y4&)ww9@p)Fa9Q$5;E$zS!_qGnK|vKRGb>YBy92f-Tgls+oEg$@r@~h z>5_6{skw>oUZ&#FYf00WrXT)hG{)2%*ITCNu85Imib1v4 zS(o=KM)YFQ^nCb6^Vw#?${S6E@|zOFIw;_nA6JLF3hr|ClKn?I=r{ z%9?)st59xqRR2pTWTB4<<8O(>(g~Lk#(zkgfLS`dVlyIfZ|7UJ*d0UkL|;?08o1Ga zYfwAmZ%a6S~WHQVAgqM>Hl8mKFG@Nc2gZ`QSJG&TLYBk8{t@(dbNwA(7Zn z=@qFUuNSd2KM`aszm8VMX#SO+q; zz20Gh@!D|iX*pK7h4S@_SbX%v$pcYbU_X>!6sKdAxmF9n6dM30bpVwBV7_yltjE0A z1j(~^nmDrk6hQjm3lX9R&y+2by$epfrB*z{swleuPi5p-AwIfaBg&m_Ayxf@aHOA> z7FjwxJZyV~>(R;nn307I3Oa2C^l}RNo*V*#Yx48M3}r`BT2nAR>re4Lw!)7!oi1Gr zBW3k*P}pZaFDaYi1p8XT4@N}cXkk+LY`&`UDbP4trDX&i0Wj*=jb>vvWV8`e8S*q~ zicd3CyPDiy&#ic9%d2eHV9d)fw)qV%TKbysPu5cax|XBF2J@m~tH5Z(>y2;wN4MO} zvciFywH7GldV;SpTLVNybZ+rEU+^$Ws9J7?ASAlLrP?Ho2^+=s@7{ti#{8ckfpR}+ z=amDl9ke<5nJ1LI?%oChP0V1*U;}F47+MDwUM6POzq9<*ChZBKTy3}g|sXG6*`2~Sc4zmr;5kinw$B0{M>2r^=7`C1YlV)6BX?u z_FesI65uc2zb_&MAd>>;?2M0Is@K{>ztfY?C4R$*5EieF>P-MkI)G@=8UfZ%IJztd z;O$67`XH&JBXsHpfUs8p3|1fMFijdOz?89|m50o^)UX+$ygNE0WS6-(oC*75iGk{z z=Pa8mCPr|n@t9LS`t?G8amQk+!&?J#0GgLOW$*op1FpladhPD00IYEs>0p(pXkCo1mKoq@5>xDV*AR?FUAR2sXAEt z`StG?*hr&wpIo$LT4}tYj~*x*vuu-En!^$EM7r^H+Oiggx?qC@W$HFb_d%NgWwnP> zm=@BYzDtqJED2-KIzDRy&L*+0FK>>->N|aE!jayXk02Trki^ zb$LzwBK!1EE5g)E_VE9N!X$4~jc>M0{}%-2cN(YN;j^kFPSvkH6vx#82D)5F0f3hE zPgsX&IEMF9$l^H0$Lm~en)IbqKq1oI08eENuvCSV z3g@(=Zz{k$A;(Kip@{%{xe`R$HKo?9rDkW{1qNXN%552$&&{nUTgaAE_L_!U)nBcf zMp>0Mft&i}=`KP-nsPG;Ce^H@M$Xld1qO$lp%1S6vbDm1``R9WpaFPFaJYiSR7D2nooSMxAmq^09+;-)8uOwT@%2}XW$=QniI++8ZexxQTFW5z<ulpB@^H?7!KoB*E~}O_AfWh~KG?gv|azG+;eE4RSnVjrR51 z!A&?TD9{;BIM7X**k|Z{mh=3?@XJKQ-AQp>mW?S=NTdsja*cfE-0p@d9Rbn`k8d(} zdIq%j7dS0-30m|qVP+cOfVOm#j+pS+~N5_hj;WDz<5E zAs@iOSJhr%#Pje?rxG{iudEGMeZ{WMp&!SHOpyFv{M`_oub7+OW8#7 zH2=1QHhn3+P?Lfr`UhDG|c90g{Vd&UK}x>!<1?Z7ESgx>39#Sr;eK zGy9o(m&ux%XIut#7514k8q~AO%?d3hBU#OflW9a@fgxR|oO)FSD9w$Le2?vU-}net z>Pjj4w8?|!iHu;Nl#la}I2bpDlH~LI(0tTPds4xHDFy&X*oF%e$c3h0*aJHRq)h|rGe$^h+6gamt z;HfgJgD3{bkpN0JoA|(LYwsT!j~!38YX&Hq{_}qUwBw=CwFR?rf$3c@G`1A(2}FtH zNJECS2M)>l3`%$_B*;9tw2O$~lQ5!E{x%wab-|Rtw&NB_fOLmymh&4`@|FY2`(V<0 zWjhl>Rrg$%F;W!!?Cc^5;iFW_ap?=oD}fsB)2^`+Xy9nk0eu()j`M3#OFtnqxyce5 zDM+G`T@ zcCz+bYtA*s9AlcA(uJL-T+PN7PMfTnQ!3->Nd^_xhNWh#vYqzms@%Y5c>F$nj!=VT zI9aS4|Cv|V5N{_7z_p~kn7^n>M7Ia}cpYD=Y~83iUw?~7M8UCJr(~4Wq(%Di`f`t` zxk7i^3rEu!O*ze+GuG~J^v_KR@)TBzWlxDq2c6l~3G@~y{7y+mOxDuhC@8+pePfZb zAY-nLSA=6|G%C7Td#O4x*)bz`b3@{Ey6x=@bJEwTs=_VL<0VEhn0_cWYXTK=yFF1pATGZv6_y_I>WGzF)_}o?`+kPp4J^}OddEXgqF}E|FL>5iJowE2tX3P>v%mM_$hF_>jn0C zQ!}{l@AS1puVfH+&yyA6j=(;;gYe9B=1{QxZ&o20igodL!#-txtzJTB zCU-#MG-_Qws!LWWGS?P)Yj?c6Bzj3}GQ1;;*>WHkOE8EjXsAT7&}Ut6 zEB-2qKQZ#4xQoy(Yg|J7c^#RNqmsanNS6y*CrAeJ?ck*c4V`Zv6L)>ussmujJfASKQ1;N`Utt{&N z-s^=BU^0LcHHo^+XK!$7&2w6~AOComKV+6S>ShOtjArP4;MzV1KmM&f ze0QT&b2q(HrE}~d&Z~H+KizF!-?$HE9%CY6q1vU}Pj|eh*|(47oAIT|ZmUWSfts(F zyAA63geRtAMwrXqsTXv*y!yHE21rR2-T4}ee7xOR-Kz6&N(dEMcs4-V94N$BFp_>D z3+D$RN7lJpf|aS#M@b1iIdufHG}*AqId5<_Qtg7`=wf;Jb_?6>EECw}iRnA_5>nYC zUn|Hy_V0w=Co_^@RZ)+2GXEmo{XoQ8`65it8PP7f$Ew`3BRQ9H^_9``GP+jcL6VQ4 zTbgl-NrzMxYcIEm*I_kgeaHJQo|?SS(qHH8%CPJ=HLDJ~CGslH7SdK^cp>+KCFP>y z!+U`q!iys;H8<%?n*4Rno{QPTFfryN@p=#e} z){HwRWp_`a+&YmSyQ}D=*Z)0L7MrjAJy(vvsqIO2(4pW6T+G3+Lo3N7omE@?RIl2j zoH&amDw|Gp@%bI0jbq!8rgTs0l(`sw%{E6d%i;WFGif8T@pEp}{FBg}uH>1O0LFxe zBF^e!ygfWsj@Iv0cO@Cd6@0$78;qZ;a--7y6PjNfyiF3F_~*lT3r^!$m|Ldnxc7J_ z^Zshr0{S?m#1(ED;xAqy9?m_mDwck==x5{ps5N!(=w}r=(#meSj&RNFO#b_1c%*z) znP>0w#1Pi#LWVRGS{9Uy*DGSqo(O+yiX{|IQR{nE`WG;DMIkNJ{k1&g3h`+Vchi#t zr8k-q>T)!cFn4suwND8bGZ&h(;supR{~EI8P2HLZ)amcqgq7&*PosN3EvrE6ZK~hy z5$-#!kD(QUDcRcAQO94C3VhBAB345hUhLs_?FW-H^@@TVb|-1&nIGYs=d7)-`=eT< z&wVQH#n-eSC!lX%T^@@(&^V5ZU*QIyWzS1*BrzIlJmi~ucbtN(xZV6m>mH@dxS%U% z&sP6N)%Zb4{CdgpCm&<$D>Yr0`#Aj1ZJvpZeR5sg?ayM3u#g{p#(FFfQW&r|^JXZ75$KsusF}vaNP(V@Dm@T^1w=3Jpj`|-HiR~is1bk<) zvl-pkLIVd+{$$uQ*(ArDFF9B?s6e+5`j1!r`}=$QJWzbhN%eRqkM7FqJcB@U;7f5a zP~D#L2aNG!BHlJH08p($Pb?)y1M@E!7F|j7sQ7g~6_$m$;r*CsJw$VOG{RC>QC!2+ zSc#;2xc~Mq5Iij8{r9Q$qaNP&Uu7sh*Z6CR2fChoNzf}U7lW08)qbNi`TNo%C3*z_ zwBJH$_I9ov6`U6ncsu;xUdT~ORpQd1igDbk9TSzU&ZyBSFn|>4z=WBq%hen=jAS5% zcBw4dKYpgdp0|?ANTV%BRgfc1DQZXq`>*Vil`T%|(w!8r^qXcll-bV*s)v`=tnhx% zL|ni*lBOR5N0wWth3xAK4u-7}Y$iDt@m4VN(jvF5=Ng%1+gx<__#8`f(8!kg6TYT2 z#BJl9p^ie;gURX$Zd;?(@5hl%s&eMb98`SgMNvq07Amv$2@le~_Y~nfa$^6usr(mN zpOw8|ljXo@CRuT%|jF#giENK-Rw zPholb-RSs%5vnuGC<{Gfa&n{Z4CS;@jPFN7)`xs%_AOgm)av=5m0nndtlsZ#ei3)O zTs&74kl6LYN0k^&>jZ8`PBYIu5sy}S3$>LKYNFLFhVa(@{G!+~!*dbG_cZEx@noD4 z+3Acc@tjZGNt+6^edXz(&wfXI5vGdgJ|lkqpxVp=na84PTEfbo|6Km8NnZ5uGKEV= z)twyH2+`VHffG&C&T#3Ny?YxOOM_4EonUCN{vT)BGkSF&lv~K^`|qMhsiA_sA(Gck zjSi^K3%YVUJ`oybq$rgjow{t{2)k$&F3XMwg6v6LhyvL^5I6K`q33m~f520WO9ty9F4%t|pQ1dOeHlnhy*6V*iq|HowX-@^l_ zs}TJE=GSbXldm4Hf{lXB{`B=R+E{&21Bv_U zhUnblR-VvLFDtFt7<8_^`m(%?vpbHX_g=dPH#-p(s#N{jNEqR!9b2&+8FirF5O=eC*h*#P9;-^B za;NGG_CHWvua)SV(~M{Z^I~DPnpy8s!$MdMVdi?ID$Y)*sWnEG^DZOj>l63$sd9We ze#F9t$0qnq2km?K1IkV^D%S3EnCPz)np#?XQ2F=P|4;cU_`hL29Kl;-(26AmjH~|n z>43kEpw+mv96~j~j%z>!lh?ys9 zVEVxV@yFZHVQ)htSh{J%3t?Aho!|MC?_}9NDm7yB)WBM0-Ed&xWsLpKfc zJeg^OgsNU^+Xl=S)G*;sM3pWPcwc9iCta0R_6gLS)$QI~bm`{k;UbT3YB6O_9>r?z zYM_wJW_oI;`UQt89Zv$G*sWv+bFI^wIZ9ht>CeoUg zdz)lu{}X>)rAL%|gq3C2;rPd57G*NFw)+=@U4u{isK!4%e?I&050fU!NVp8;iLs-K zIFv}{8;jaiZ--l`q*nz)cf)gr3;&vj*FVxJH1lRZ+L_yI^yo^*p~U1^l$Np>SuFIC z_p<%|8`MAexCUrt*Kted<@NgcPZNo9nk^yhdWD7K&Oa&kp$5bl*XhWJxWfBYw4$W) z7lYx@OX}47ItdOoGie?ChMgXx#YQp-)Oi`SDa!8%2fu1c2IZ-8-IGufC5}d?$VZuv zZk#2io_xB|lf%UJLHKJx_}YFMnQ)q#(jo`^m*$IrUTstc|BBpJU00eky9k_(9ZPXa z;L&A;gL{ZX_FR`Gwi01Mqii`{uHiHeIx7N;baZY%RC7d(kv~@}Ek?w%iI_{R#Nt5S z{nfA%7pzUnop8)nme@jduS`UJcJp%q$$7pIA8~QaK=zzXhbje2zTKXdHgcO&_-yh1 z6N+>%j@E^{Jh8B5UFDQZsJaVEZ-AZMC7-;?e_&Gi}HU{(#efiHZMVI&LK+6#a`Z)}IQ*pK-$ zzMesn)sBdjvx%2OPh&^ca#RBfF(_tgx?k*H|H@~QZV*jh0Mq1uL5x$#?-+76BQIFjt7UcTZ-Qj19MQ#Cr zAl3@v@X8rC2h{a6+alOu6P2z?#GqcCTo{zA-p@S;jMy5G%KXS*?^6&f!FmGKXaOq_ zpm_d#?g5lSuZKCrJTrHvcOb4kp%B*}T@a&%_TsK}_eR>#ISuG1^#m3F3Rvk;@##fy|tr6;o&!T6hp5dJc39iMY}=V8Ea!F9D=yG;)b?lteM z?U;oQBL>7YTGwZFV0M|e=2k2Ct+jFp9qso7R1-ulLa8dG_MMplxjx~T&EE_oHCrOhRaEO2gXSwsEz8_>haC#hBaX1gak4QtnBV& ze+cRvkL9J7Mseu?5d9i{yjuV$=RR!B>}aKH5h&y~9IS@BqX6v<1GTrFDg&8s7O-%5 zk#ec^2n3MPkJLzA^G9Pg zZ^dff;uG&LXt$N*=q4|~t(#ky_8Mo}v{Q8zG*9}i5xtMbbDmaFp4}kBh+3yA2-N?4 zUTvKkVx}Kn?J{mM2RW;%QKKB|lcpIT`&omjW$B_cpQBq|Jz$2P{YXz%i!{I(s-+9#XNIos`bAxEX0i2g11oZWX;HU8Emw}x~L$P#PBayUw5 zF=nxL=haP|GjzWW!26}*?R8MTT?D8cPE`&+aE{&S*!rUU*>dc2@_y%c%&ORAyeXV< z680sMxHu(DuDn+Y&&67(B0Zd4lS6$uy?2#hF{ZoZlU^WB3L$+KQ(1cL5>ThbPCUjt zBNaFSeq3un1yY11-TkiKG04#>*{+kqvsVLhJPWao9Z*WF56X=9FE%VBxOajQY|TE> z?qs8xreJL8bGA--A@MxR$E;&gjrHMzZ{t{2cZV_2qZglQK-B1t#j-x0v7XB`p8MI+ zLXOw@83b?z=TKoL0hD2L$gU(-e(MF<+{ad007`eVQ)q<^tG$8Wa0Z&5!8AE4_xWfO z5BdXuB=S^+p@Bc`-g^~Dx_ZJ}I)}%2`9RHJ4k)1=bV-)yNU_;IK*Y2U*2m@g`28>E zI*O8W=qz^Cf%+UH!l-7gVrwvUPhHiMAl}+!cnfMKHJ+!k3-Wcdd=`Vlz)|p|>|^O* zFs;Z)1iLo*%CwyWiqKlbh}n>Fva%a|&pvfpw*i**R|Y}9UC61ns}8_|EiRJ*_~HOFOb8B#guZQK~784%P}-0w&Q zoB0$u+PcGZng!o#;#-`G2M(~= zNIM{f!c2{)$;Da%D@;-GY;ki%(p9wx3&5YUiHc5OMWS?5SiiUdAdka(FcJ=8$!3#_ zp|`o}@eM$o2uTeT6jiSRX$@hPj8~p^X>Oj(#ST!&gWi3tX*3L$`p{r5)PjhdE#ELi zu!$DFt3<~H=~l1Pl;c6HDy`{#eM&$_4WT-K006g7Ubtf@8$MvY<^^g(FydRgOMUQ& z4d6#Qe+ifvg;ERJSr4r59M8_aR&hcUX3VLk%g66#M$g&xJfqMA)I9=t*oo59P%Ewg z3HNKj25(8&8F4pjS(VR`qt=k4m8tFFf;soJ2Q;%(nX@&E(&C+UN5WcwR{T+kN_0=_ z8^}YAwe}Td#A^ap+qxy=Eiw-$%du&>4s+^PwsO73OfTGWN*NSl%rb>l*c=-k$Q<8W zJ4uyIgfUNkD2BET4X>9_b)0D}_@Ioq`Mumf!#0Fl+B@8;b}$8I5_+YLO*>Y|uuPtu zRlYUEt{R0_LExOuDTvQR-4*evxEI(X@5b?`!kkw0Yv2^78mZ+=F)a$IfZcZL6kGrWdkvO9zXy0q zPjv4ylRz{RB$RG&w}hw-=HVpCM6hjntHZuAzhhO+cm-;=c2TCWXcIRU_}zsjZ8y7F>A=mTZ94pdPq8Rz<_`ES0h5* ztlT6TH)8JhlBFF>U4#)V7ieGK$@Vl_W#Qq^zZ!C9#41PS)gz|l#J5I06F_t_mG3hf zg7K9;W3l0? zsYJ`42411ryZa2MJKv&T7&hwIJW>ug-*%LN7EKtfgdl0)_!>*7ym7-KgY&KlcbPc&*6Dsm|qvT+S&6f{m~uU6>;6VVCx=g$yHyI(8a zMO?ei{EdWpP&~pzU%Dz(Or4uwPIhN%) z^1&fwN_z8;Kji4UlUm0SVsA0jA4PVZMR3#P63OXKcOheY6L_}0B-)Lc3YN+@I}>R2 zLR`Pm+IKyEi9*^pEsLCQ&}Q){OtZ={C?XQ^o{0L58Xb;bymYy}wkJ=oMpd&l$k#wM zT3JbE^wryIKbO)IuV*;|g-_ouWuJ;sMF;0e>5nGCYtMEe^l z2eff#+|~tRKNUv~3D#+z6=S$&tp_NNOG^&VEN1Y1{lb(|@}4nn z5qdT@kI&FKd+}pSg!Hv0Z@Qjv@%yM{=@VSdob7-CIi?;pQ0EnZ8asS7B*^OmEU&!s zGoRNjSB>6Z?780|MejbW@82jdycV|Zk7}Z{NSWeyj#i_i1EqszG;E>t_Tb6LlNGb( z6e|;I-NA{`Pb3f{c$_)UaAh5MY7jC2v%+G4DmqUH%Jl(RE`1#MHSl4s^o(xB7;y1R zK!GB?EWPql&e8L9u~TLkRbHP<)yH7>#akJ9pxBTH;g=jmpyYp@CMGMQbi!?=IOi&3 zJReK6t#Qqb^}?z>*ws>x?>C>Pz?(jHxhQqCRU23Np5ku%R;j{B^6f)k4?cUg18l$z z&QH&s6-JJ8dGz|^hBp?Yc4BzGo|bLhEGiB9ab78K?MqzV+yaNmvG0$q(Nl%*W1QpU zIa24;HKGSv#adQBOiKp?xpMBkwsKyT;8OQT8?GkG2cR$K(#Gr62&bJ32z~w9dk`5< z`~w;mN~S1&5s&nrewdPb2Y%v6R|{k1s|-#M;FdqzAg{ae9jQrTN|s~LFo9}pf)i(6 z=`z0h(_CwNF<|ohR_L|d{k7b+g#g1rFT}_}R`ubEwaQF4{;?Vyz2JDIICog#d5*~A z3Wr7A=CC7IOkHzgz}{fXhNr=zfzRae_4Bq}+_##ImT=?;_Zs7ddnSD(f7yxYu6M*z zJwDe`N`2YLEGfRg???WCzO%~OW*vhzWKoN*S$4opooNnDG%H7YsCkKQNbI_VWWOf_lC1%BX6PbuCa~VUViz;H8(Gi9C9Fs&;9&N#JWR4 z)K*`UD(SK)JW4&a8<3y;NTCf3)lc^ApVf}y<~RkpZXIyFOz9= zz+I~NXUlJves8-^mpN2iBF9^GPwmCQJdDTv>aqv|`I2=q7Io?&QEOVJ`?5EMSYSVv zWthF+PmO|Y5Si;I-&L4(8C5A2r2|cpJ=EYEHgfa3GoPL-rKkgbr+w179HBk5O?V`F zfHR+70U764eFPmCvO7>fTKX8UID?bY&LJpjRX&WzL$&M4a>$Xb7LM1P?$)>;Z@U~H zE^l=PqWmr(5HPpPm&87EN5M@73LDcM0u{ zi8$Wl+?Zz=>xek-_E&E-7jga|U(^*@r8=`Qm^1QytjIZ_=Zh!8mU5EmL#>CslR~qn ze#E*mSxe%D1D@)ifvi8l(tIh&qy7ltcs4ly`5$NSzh-q(H_jf_rLqX!tLuG_56lp$ zGDW+C!IjpSxP`l}Uh!uZ9;Z8_dphnZt>bPAtG=!zre@6^6R&g>l?ZKWrX2@0 z(?>956h{7QVgDGI{uQzMnI5)HuM-kk-fDAL(VHQZUJEYx;+1*(ukx5!PDs&R87B<1 zeK2i(+8{f+q$$!f;RG7Ip$4B>c)3UXHM?_Vr-{G@Vc$Obo9}{MtET4On%A9|Hj}FN zPzshR);flR&H3$yj9z+qnxiH0JdmYoiK?`mji);fi<)^a8Rbs}L7ar-nZ+}C;jk}h zbR`DLDF15F|2<@;gx;0DC=nrcsui{hn96SH%fYSAQsNqN%iYytm#XwUSiSt3 zQ1e$PBop?z^`M&STyxHljyL#1vp}<(eX%@^a8t%cWsP0(8dpJ74!7BT3h@L#=>lmv|bmnFU;j_1PxG3l<2M`=E(UE zI}gbGM5H@^0MjIE=<$GDKT>UU*sA;+i=V5N-uNg_)&5|{dHZe*C#igrAu6PfYeEMW z%|IUNEPBX0ff-h;`G|S5Nxl-JCAi@~Vks2oEGtXx^hDNu=3C67*2xMkN?`cc4}vL0 zJR4}O2R1Gj?JYJIfy%kj$y0TaG`97ZTF30334%yTti=uqGsP0^X*K(+c_<0OlpQ}uj_m^xfA zugmN9fsqH_{!S%FQ6>d)t=IoMCDF!Uf3_q1mzmPP{-8=Y%Qt3}xgc6&=)sk?n_W7% zQDSddJ$o};aSy&Z&}H2`lc+Nwz`uB%s z_3B)1KZm%Cdou#>MhdI?mO%|V1L>U$I8Iqu-l86vqGcaf-FsMl8t=vHB5Gn9MZco0+qwg{{M4Q}aA(S>~k6%u{863EWb>vz#rH(lIW+cN(IRS zg55TH?hPkU+3M+>k0g^Fbj1%MMt3@f4`zHQ_Jadd7riUvsM2+#{(t6 zn{rU`XBo^n%sX*=ua=E7yP7C!d&ReGWt?PY z)#TuVtxD6NGk<#9TX7j7r=8mAd%s?Ve}@|Jq@?L}mhsX+22_p?ZftZS#|iIJbJ=qE z{h##|PN8{TuEJtOImXO=p0U)NUhwB9I{_Ut0bN!>OMO8z2{i)_CC{qAbF~#%sQiBR zCPm?nkXP_f?TJ2|IM>g6l9%=}@_d@u`umc?8s+$c)?de*#b$`&?uef9QyegSN`_(| z7HTR5b&r?NZf{gMnxKmch^C@Sew{S`{n!;PadH4E0og3<7M>6hO}Jz0TmW;QIPPQ7 zYV;wKu2L{HH@v0x(P2m}8j}0sW^QT=RRk zx#n{YJ$6xCPOY3gvMXQGC^YP+*$rb;Lt)2cD5+mZ@V^Ir`G)Cd8|Ra-#GkY2{CQV+ z83%jfBMzKmAuxjw^!$7NoY5vP=sY?b$xJ*#F?5~5yuKxD@ zy(9io>~>!_R`fM`>%R6yggtw*2BNwg63@-wBufd)*PeAxFzkO-+T#3q{I}1hRU_$T zJ=9}J%b{ygaLB+&b}fyf=tA>@=8Qj1O)}F$8;Um)aAGD1K1OXGhc?#8^FrCt@Ag%3 zf-AgcWvJcCONOaNpA_P<;A-rP4#2W_wsz;G`iHsWGYtcENOwLP`_ zpB{qr%OuSPXwv09T5@1A%*w!n(W=LxwxG8EdXrN+TwU<0`JGHh=UGJ5h34DMF@GEvQl>x~in3tLs$QVyez`C1 z<0ffS6SUgz7gaQF;Da%!@tH)y*oj4Zzl5G5Jsl3UHnrg&zo1%@X+87;{=pqcR>(f8 z1@AF3&;uVW^84Ef;RUjcL6c@~aoH5mtgIH3Fm>O!7{VOF{-+6=%GPZTQ0~4UvrB0w zLR|8$c72CNtx*Qc5pZTDZ)|G$X=RLJZN!18hy75m)SsA6xA(MkhmbzMkzD&q@^7MU z5q-B4`8<0I!qj&71-pnHcX>#xEs zX{@-?wYIov#Gw6xMrB(sTE&aCo9SZ6-4KR9eLHrEB&G)FEBph&B!T#o zbG3e(mC$eCJfm9QU#@UNj2M2aw9Y4DBfc0y6LR;DcYBs(+5mlnzeh<9(<(7z9<+K+ zLSWNwv-+D1_FswqOPpsk>rPAEB9{Se!r+Qxm!q%cYA7)L?S4zDn8ZLp>R3j!TdRv{ z_OPeyFG+cu`*F0spq}fGSEdq>!pn#br$qf6+1F}W`}bWCq|$?Pzs-3Fc}vAd=hwyI zgEjRyvERAJ*F&5=T{1<#2+Ktivi0S7*&x6-O)?^sjT7Jk4oB*1O+cB<4=6O{HTUX_ zU(OYAnVU=jHWaH8aDO#l6snZex%Arw9P0bX3OoSmMY;e6(h!KI3%=iy`Tm56UG+fL zHQ^@^CVwcOk6{&ZUVkhjL(66GR7>75SyyzB%d9u$5C~eo0!t&VW@`KX>^vYngB4p( ziq0pvue-@jl9J3dd}Th?91_xXTS|2~oc8Z~y&4PJHl>|*5dk>Rni?3rI9DR;UN46E&e8RSSARhH3yr76l{b*^PiQzipBT zcVa`W7IJ}BAA|09aNXO*T(@WY&$D>ubH6RSmv?biEPg!%jcxjl?e!M60O>_HYK4Ni z<$+EgYLf{|N5B$_9K4asV_?cwfR!2Vda6_@I>s(rD7Bo;&D_>~{pp#(RIT??I?(%a zIjs&009jz)4nSr}3o#tpzg#WeUvDr)w?d0SEG5tT;K9@+m+7g|`SnH#i5y3F0kqk{ zlJ_W3#_}C#l=L2$OmXv%*)$viFRAD!JwPw{2-v??ECQD{OC;J@4*5)7$M#uP@#}Z@ zpND(^E}w$$I>Ce2n>hZ63K=1nW>^I6H>ri!%M4*(2972`b3Dp}ac4bHT`#%?-m<)N ziAYdl0G5^a>@G%i(TGQfXY>@572rwo=2a|b6#(2eW#9~Od0U4wq0LykSC%Zy!3G_0 zm{Yn+`&d9Kl$PfFsLRWsKSOCr#K-2z>))*QMkYn>aMV-@z=Db`#`z~O(?lm7+>OHVX+kEZ*?-&jTzN0=R<`%!aG$24Df}K@G!T z)ke2A_R^<7@;qbhh0#8iyVC3-cDk#g1LWX!`E^&lWcN1J)S=-K5?-dN`w6y9cX|M^ ze#cMfK|X4;p2+YW86*Q~cK{r|ySaFub9l1L&hgieTy2 z^J1Wm;|sAQArCHp1Ad&X5W?S z3QRlCc+Z75DoFsR=cdJvNd^A6%wd0(+xF_h{xnzw3KW+SV24;T$_mfC3|zhT3=>@EUp6ycEYi=kXI(fQ`rBe0?zlFN8pQ9LM)#5Ia&s$L}?Y;+|qUrgcM2&-86M=Eq@PfVMs^z$vSCO38>587p8`PLxTk4yz+Bj*YEH5+MAlx$C zL2b)|XO!EnGr^DKCHPkuP16e4Ruh2}G`&u9z>Uu~vHFEu$&8ep&G-yDVv>}r`hLS395-ze`8n&5xa=l7wQn2P$6UX$f+oh_14zCBxgImG5mg3+ zp&OVjyvV9&hysRlaC;PF#xN_PNI#N5B=gAw+)0bPs?Rf87CGVd+a&S=ch%;V+1o3y z9uLD{?R>fK%^tkD3-(Uw(IyLEp8Zm-)wf~y2!s&n@N(6QDsw?1-VA-%0-j7l6o`QZ z{mL?_7vhusRz@DwyR(E5Y@6X$`o{P!uBuRyYg?5YWi8KZ=nor&d@-={-5HZP2A}yQ z*_5hyUq)`4c^nZzVbe2QRn5LDKLpP2qv-e7kWRbWHm3%=#R_sv zwOq9Yud)+sf$yElyXCw7)St5+Wg;2aQaREmgagz{EkrMRMd2QW^rm^aY1mPtdB5`G}0_P2Pr*} zh3gq?hWkcyhPKW(e}K;i@vK8)3efgAc9abdRm|XPxgF}1x{_HHLcW+~|2|NhOk3*w{ZdAmMRF$@!W>+>9B z$&OepOYY8k)CHbLv$G{*O($2$bP}cB&G}JTX_R}em&njjjV@Azw*co&2g3C$&Y4fD zf!`_>s>QA2x#^3C@uM#Cc(#KXO)%LR+NLqb>~*p#|X^F zTOX7d+`wL(+HEmbW^0x){Y61kFvqtxJlQ?k6NG77kVZ9vwG#1!E6XMG0_dF?s^Zcl zqPsGh0ZCQrk$%SzoE%NCc-uJgArjdnRIXsTPJB^!Fhjt2SRY4m9oBZE3&DfRV+lnk zWJy@^dl`CJ7yfv#Bw7I|XB9#sI^Z~?%GJJ7G$Iv39gX9N^lw7;Wh|!-Amy$G2eYd6 zjD<$&m)V+>Ei1!B-1o0~mN9bavrn_D_4!Vz%7Tr0Vkx0{#?0Wby*O=fm^LZ#NUZYOb{V$69>+1q>F?4XRjtl zDIbWtw85?K__+GhIKALeQIZ+{v043i9`2XHfJo(PXP4ycyL}wl?Er$&i{xQYkLZ*C zLF(ZlHU~0}g+;8xq~Elhny&QSUXI43V!N7~jRPY=IsF}wXNNseA5_-{d-Z}(;+RxM zBwv;%=yQBt2HFg$G;-+EN$l~DT7G$O*HUD!Uq$6Y+_*nqtTV*Z9=-6G=-*xdoZ31B z%>*ahICZ0%sNDPQ0@c=@1x&Yxuhpe4bM`Y2VDt7vQP(Xk|Vc5Zr+B#+M5-t(GNSzH%QLx@>J@P ze*At44>az37>(mbg0eDcweV|Dh+|*TiKWKuswWcdFlG@2%i8GfL3`{Sbrfb68&yU)(gKMpc9nL7WHNp00hnx1of zb_J=laf!8lyecr?#EE$lXVpTxjXcTCdHNkihzv)0ik-`1L{*~|G#O7I?RMbkuOWN! zOrqlVHqO(XtW7aQ)_1x-l%e_${7>_qy@9t^BTFnVt`D_^{(20s=apAd-h(`%?Bga> zScu7FMX`eFRhI?XtI5Jp*sqhizuAJYZ1Erh=LO3lzRL$A={g67L6cVd?3m$Sf8M`O zw&LZ4#ZfkqG-ZQd-KDz9$ytZsXoJ#Ncx&|^vF1)3c9t=t=hv6r#RWII$jjH(`^d|+ z$C-YQ`CKL}E(4Z+uJ}#TM7iVQml-)B7xx)3{eS$NMBute8ljA#e7ng!c4N$VB)hSC z#ogZzOA%A|y@v54q5Z&XZfpEOyqyzcIIEKTslR^=0L#Cp1=?o7++=(YVaLS+M?ZoM zcT?!Jf6kX2!j=IGCtx0r|J5-Y$?mDWQsfO*Twd2}`ES3(3b21ryoZC$OW(SW7<}P* zQU?eaibqv&ySGYl$wzp@&X-X<>;6q1wI(_~UBAu<|0XRYk_z6z+3hsuE&X>l3ZT2| z6*~LzrU7Laq1jb%b~2z`1-p2)Hy6CRQgFR9C8cwL;epSN`ZI~u`|U= zuNY*b+>E5CqN0f~KX2INPrx8)lu}ZoKgx1FX`Xj5jDKNj zwe2`YH`kqF;MIZMHXG5U6Y7D|{|^@yqF30F)@;MJ)6sYysZ6-Tk5=#hCIkG}JW}OU zcFSDQs8hQ1Dye z5IL=qty2B*#{F!HkZF#q?g$6Z4$A7^&m`>dkYF6+VtOxq5A~9vZIE0_0KO=On)$VX`GX57yt-;2d7M#%+Y$oq`jxhjx1m~#Stlu4}^}aL%ls<{mr(=x3cF+`2 zGN`o$?8+h49-y(sci^xXsk=}GP}A?giH-(10m{V8zrP=nu_`$yOGcd4*<5*Yb|^!mH$WRsHi2H?Fh?T1nlEVyum@X!3aakI0BFc^SYe@E2U@Pg zSy};0L;=u5rPss^K+9aPkzxUGV6uoUq2Jirr~~Sq8Q`-N5^Gh#EdO}xdy;j@yJNhi z_e5&{L&hh0*4>J$LntwgcMFf1T_XKkb; z=$T31XP%s8Q$BT#gaD$p_&_7fe;vT|r1TK^xCMOb6)aM%1JGMrfXOt1 zR!j*jMv+?PnWVb&u8w=UW1x7C1X$=R+a%*%JO&g53GN&gx6_g`hHT)?>(>KE(}z%v z`gha-`FlkTiei`U1p`F-F|myE&HvdK2}z}rmdo2fGuC(x*||p)tmN7=l_|^RF3HEk zVJj^C{AD`-In+^n-pC?;l>GvsB7-xQTL91!4Ho?)bcz5phiXU5it3%-1c2RX=)WT~ z03#vSmH8NpB^m-9je=U}wH(nDZfhWsDd?4EZNz#4v2geKWS{uIDg~-5W<>UOghHG6 z8%`>0?es9f`;;&Icwh~n`+@<8c541EvDcxbj^ltDmwvr3>ArG~+uP$Jj^TKn_^ z*G2zpR1wJNN#7>x>LX(;y3G7={vJL@89g=hl{cRG+9RuCgj<5>8hGm-a;?V7?9CvSQa$)))@Mp=|mL*ygdEr4&D?% z@B+az5)nBN`;Uh{E16VbPtnO!_Hi404!P{96W<`ENpMWtb>>~T0*q5_k5Mr}nn$D2 zb2b-Q7npTE{9uxdb568tWyX?SDMa)`01AJFn*h23H4kcALtU-x!ShYGcR!oQwsiQL zDi8{t1h`{g#xp>*$Wq8}JBvw5*;m)QUh1>!Vf@B$^C@Rx%RSrLi})!)7SaAU-wPZh z+1?cR=BSif3DleEM3zUGL`R^EVZ`=VbD0ZQ?=^ju9`B^3CBI%#mVfeDhU4WV-_(~; zQLx4E`Vi}W7=C(QzSb#rwiW8&&ABqob)SC=jze*3r(1v2Zl0YJ!Etp?*ca@4obim) z4UKirX&hCB6lN+{z}EEvEC3Pc)%Fa9_O^EoF1NbvMH`6o>}@#)I)JV$kiB$_JD$~6 zwCP>_2Z1Ixfzt2uA9y-nU|a!y`;wy5&Zj7z~x2T6vU}2hs(TU$2M1bv9S|1wE_s5zsZ{_sX1#&DsK+P=T{T4UArI& ztA%l)qQ^|}Ww#1%Qra8DzH2YZVT=v^qU$N3UmT7Uhe*V{Lt0*A9DKYW3i17>8e@mk z_0oOtiZg*lwx{ODszgletB;)5Z`a2XI?7icJxS#X=DY(WPa~~?jv@9Oum0xrY~=-7 z7d{Xg9?O=s@^>HJyMJ(2=rP@Z?5zPC=>xfj1-|Tzg*FNwiwFtD4vMQ-600}z5f-Vr z+9u7TzC(wjSp8;tr~c!LYUR#50zKNELvCM3YNuiS%+>@)lGfN^e#X_+h(&^ zVW-uoYBqh{eWJR@!D}Yvj(pW8iy)x31FDioww2<6j)QVv1;MAIQbLky7oY@v+jruD z?yYh^c-{ zSzC3+E}9^(b5zbnC{%WpUn)(@tl;3No+TGz<1Dr1Xj+tsGt3u>yL96^5c~z4e zPgx!nP%@y+y2@7%V*QFO@*JIoqoBf)}gs)4Q*@wR4YhicBI3- z%NJh3o4d%E-}H{tEaL(_ZhD{#5O2%&Z3@o8t9hrF9dmsdN#_F{j@yvilp6yBWy%R~ zb?8jh$Y96gvAz!vV>q)8>;1hf7OC<~&ZW4X@^}3whBK>@lx9!C@nqINh^_|Mt*Z|F zXzU;J^sUhr6lO6%2&lX5APkfGbctw z2W1|K#;u3Mw;$3AP)vP&-Wgxiv}RrgB|81-qb0TS^;US(6(P3BnmpG;dm0>#`d-UX zK@eYWG)9nYDoQy009+Ixfh@*2;-CkOmW?6fhj=TmPQ`LaW-Vrm%D!vhDnF zUoKop9ftOo*DqdGk2hvy!%|X0N`@~~aIz5FA)7%Lf?QofKQd+?<|vewxrMt%B__Tj z*J6S)$hb((;C^w(Y&ZP)t6{*HQMZI$%vp54BWE806P;w$^=37@`(YL+i(Yt5!&IuaKDROA1-xK&IjCF;8_h@{$>7k)cv>aWTfHx0fioaA_fPy24vpUKLGH-Onp!)k1&Q;MG(0VIT|E5}E*QRkNNws+LlPGD; z>i!?ql|L%Eaz`}iq{`W;27@^z4nO-#LperXo#)3tUXTf`pp;)F_Ozo?pW1n*3R^Kg zwq!D)?Q>2`zo|&FtX$Q$f@uB^kW5?@FH6{Y`sYhOjt8slQcHJjQnnuPdezu*ISv-? z+~~YxiryKkv3C{GzI>O_3sZDeYHJ$jxfJH^Fv6{l(JRs3N?FBF6Fqr^r~y50X*-); z5^J&vl7Uw0Z2iGhiqZWoRp;pkEBa_Ozwv3E{~k%)cEgc~q*kB6qp#c1b4o`tGt>dy z`#XWjxp(B4h%(DHM0WIrI?19VgOoF8jb`t0Xe~UwG_b$77EUnkcR=C*JwJ^Mr6-1FdVU(Iwk*$z4wf2YF)dAw+I%npi%@Buu}t41*FLa3(^Ta z^w2{ONEcMFp;+iuklq6Xl2AoKdaof=0U;1Nq4VB3XPxU6VXKU_4-tivC;XYO(s-8?yUU7FdP~w%x{JiGtud2 zCK}nQm0YhaIkh_YhT?-^)a%d5xLU(F4g;sjwSlgTHimg@32pOJ-ySa_>(DdpbN*G2P2-&5&{uIsT;TlIEcGXT zf9d0Ig#d*+m1~xm75IWn8wb@@LtENK8tUzs?Heu&msHV5-C1VsL0_+J3To_2#^RJ^ zVqN&`kcBNM6{udDn)wp9Pk+aCpF2-%dSl#J)mK7(g6hzxrmRrGNT12m&8|KT?nvF!1HLYwDl#`bg9K8~x`z7Q%|;MbLj$_L)0res zo>)^7Od!rxkI-^V`iQ zNwJl#*ZCNm?kk5ig>^k9m0g!kp>r`4Aa6|?g+7N>#*QXyrol&CAhj5pu07ydnQyOU z{UFgzG@?m@Z#r-qbbcLM*#7}V%k}Q@Li+}N6YV9jVwajj z_%`UO?|~Xc`WJktI~7a{Hm%IE)VSmN;yqgf&hu*q8V9d7ZhcCbGP7L+{%tiBu@n^kUjiC$VpN2p~7UA-7ee^p}vZo`0cdI;EGMrBK zs3j)Mg;HyNALGE@+cdIf#MSsD0x25IYb6QsM%ITRpJ~kalkrk=Dedv&tk4Nf%^QUpMT19zvxBp9N-va(WX&XZ zVYlielzWhUqH2D9U%XWOf?&Oo;HjIAJ7gPjbg6e3!LnyA>QW8r+xE<->+2`1{7?Z= z&jKk{#B4&*2>eQWf42BNmS5OC&G;2LI?WE3R_BNQkvIMx`x~>cn8(LN&5?CY zVt@iqhB5*_m;VDSA9~7$5EUJF``>f^XI~}R(j4=Q?Zy9H?GJ$Np{E6h5Dc-xKVX?Z zf#Owo0jgd+ocF^v{*%wQc@K{Nl>WH8Kl%9|{a4_pHUEAk1~$CARu80UP64(d|MR{6qcqR&ngZE*f0^gDA^prh z>>@t|2cVhm9{8|(v>aUR%QQg5KK-nuyG>5n`13-*`&0sIFynx>+Dr@v%&>VEFz3Y| zM1u^~Xca`Zy_=-6iC20+8N@udq&I7401zXrVx|F4^aX$b?9=+}y~hB$`w~FTRcWPc zJ6rF$wakH)RQL+T#l3Jc!mxF*2rxFGl_rFPR2yg1ZeAA~uzV*nqHt1LTVeo+Au$3x z*VdtYsgB~HQ4au{E*}am2}}=8*b*oRHpN+bX6?7k^{3OcGr`r9T z0A?QG(|7z>MQqfK0CI$Jg39;<_MmltFHkqgT9#(lE*%;=ng{%d`psYPcdo~SCukQ4WJruE!fL?-}|DauS=M&sZ3^IyJ7lAswBn{I>wvXMyqZwE#})Om)2vTjuX zz8eL;WTnuin*r^z5Xi1fr3wQ?B)}IyVYcUw>zgvE_lmYH_>*z03SS(I(V?cr7ji3! z`G{!nH1@Pj9Dog`y&0{7`+D)kXTZ6QmJS8vNhzgJR_JCq_VD8j3b+q~A?YQM;~z2; z4E#m`vYUn5?r-M-u(S~98l~Cx0?z3T0FXS|1c>_?pjoCF2LMEu=Iiy;V?6SLW^)Hm z-V95#knQOPMTcw#yUw=Z3LjlnB^&<{Kk^sAB8=FQH?069@YJwk*W~AiDL5wmo@;rB zLFwKI@^?{wah28m{T1MfPCZ7Vu!s?ObSRM3y7Xj^y6f>G(8n^p2>>|moR;`}nb!dF z9(Vw>usS~iDJ545oO2zBjIYM+wmqS`VRnZN5a?;Wfhkaf9>9UR?Du}Y#|2I?6u6d3 ztbw6d;aUXtG#?uksl>A+Gmlh&>|5&ydA@X{4q)K#sEm0SJpn|1W4^dcbMjivD3+KP z8?ZREGsqt(L;ech~}c%|lw*>%PoU}WQx75s59BI^#%Wn1ku zb^wtV0vV@|Ja`}}1*vEaIC3?!HOGlf*58+w?66>FMhljHTw4XKvpjBzAgj~YW0bGtLae)^Z?I@V^^9Acx^XlrU3wd>>R}g zp^=>JBbU^KjrIhRKA}L6?8ZM(POyS3D%N^mP#Dp`rHg~YMBf2q{^LPil#0)3Wxh9` zw9`JAN7YGEXrQZ6^k)+XKg~%@MP1SX$RoqK^yt0)0B4!ehX;emz{#7H!VkJ2;8av~ zBY@s#gQUR1>Du^85aTa( z+~%M;zzIzb*laSOX&gD@d+I4At#B2=dnsGu_F(?GWudP; z&C5JBg!%`;9G=SCr75a1aqB>Y+TgA0s!c_Tp*X}7vCQk8eNYx}I=%_Yzu zaUO*6##xrRtxp8S?X`EttJT{v&)nvhEJM~nW`$#C1~pyo zwbvCi9E;y>xR&^F5m4h>0*)Bj0$IJ> zw5A&fp5|XfU<{8yICp)HW;;pl%n|34y{QofJP=?8xdAK^!?P?io8!e01`b6nV&T$X zc^HmrfG}q1tdxfX?Q#V0%N98&K=$?nEghYL)xdM?^DzbLBKoOoz8@t$LG9*5kO5Uy zE_#qBW5_bfm`av(vPt9^^1kU;^%ngJ0FkCHt7G>yrPeA;DzR4a?|!sIl7J*2D@A>Xl7+B1g7Q>CfhqGhfxd(c+aUR zXi_TK!pACFd>@m+rt7;y$t;igdR3$$SB#ONSth|ttvp7VLsoa-tgl&K#R3Qi3QrNNd=bHna=$oJ9twwtiZQiHJNd;RnJk zUDaERB7t6xP}j*=nSTepfBh}b$f)T;-X>Z?+g%m9cc@EdzoZ2fq22)vcaLmpOI4QlVK5IzYbsac#YrW>t;dGzz z)4yBXV_c(<6VUJWtV6fQgnk$5sTx|jVXu9+NY>84Qw-KK^0>A(`1lKo$W?VT?P&8F zpirF5a*$9$aYk2a=r{X?x$zcdLn1F=KvC+he|;iiy&vO72>s^zBP{=a zWex{u3?5T5{}8z2DcndXNF`#Zx5x`7U51KF-frvp5`8d+4Ubzt&ENu0pRs|N@0Q^H zb8hoP5Fn=y^0A0XN6(a>KGPp}medV$JY~e|0h6EQJO8>JE@hDSpoZohi~lamdLIKe zugv-D+dos9`7?=xQ}|^OI6|n-UxNL=ejBKHMsSF~MA|CgZy$&c;JEW|3CiG`&95i`XmqWU-uD%o4%(4a@OwksNElUG=F`Te++0KiUB-O6M>}k z(>CZoMoBL7D0r8H>_t`o@!tL%OF)y201pJm;0ZQ=`z}SnyVS&6tn2>k?f!Lx{MSu4 zYJvyi^Te_J{51dLTu;3U-ld&S^WG0w&<|7chtvPRlk>lm^A|Ie>%Wup|7GWFNAr~q zhUz3y(*y&JhM)>`)35i3&C@obfCV_SX%N-)bCl&@>z;w@<-=Mb=DMELzgx}^sk|5k zhgx{k>3)9x|Ldx>m%tuzRyQ*1-@Cz2zWN5Ob{5Z%gWUhS4T^H3V~%DCiWU6DF_^F&RIBr%NXFmPqr2n7a27aPsiT@qY|9B?+ zdlvk6K>s^K|89N$Z8iNjDgAnsFNcz4mDSkQj2ddjrr4&4Ky%5`Zy317mYmLqrLb zbosbaxcOI<Di z8f)(}+fj5XS^*_j&ng;jjz^TLBrZ_6)x%&w%D%l-<`JI-SZC-y03Bv_DLf~BCyF=+ zaPY2jWo5q~E5=Xhf?Zt!?g6&}N*gpI4p21h>l7DnmbB^NPXOEX0UYXU@=y4gU*`aJ zxb63EK(E6s*w%XT=&oh|6eu&u(v1Pt z_V(g^;f-I2bJM<$m#e6SS$eO}u>g#E;{F}&N+7pk>{5GN{P&(DtF&i+@}QVz^srzZ zTw9F@-!LPc+6LtRH0KD;6+q5@P0?MZ$pl^sp#Z^vP)bVQfvd+H0Pn8@J^SzN9~5E& z08nkqWf2^lW*$I+I1UIjaqduxRu*p*neKL-Ze@3_KClEz1HXgTKg~N0s9`|xY()Np zh{O)GPc-5HQF}&Wj8}dU=+Z=O-5XV|I+C{1QFh~r&qAlbE#1Ly4ez=n`$@B(2mnF4 zQ6UTH);%mF8NfhUimckXQn|mk&G|lUgqjHI8v1kKt+;&u5TfIFsB4*ZY?%U@QyTWW zmL>P|mK73zEb{(!v8oSK2}d-Gb_TVZ1Jvif{9V$;N}z1bdF;b%sa=X>*u#+7n?Y$= zp?0!7vl$*ui2KSnv`el_6C3;z>#5uHDK&_4@cWsAH+=ZX;_?i&MV+Ntq3}X}Z2?WF zwN?^_`bKLNfRqgaHf~zZQ@T!SNK|xu^rb_t%H^OKF*6O;ylM?Mw4_HgYx$@Z1-iQd zhHe5N)c4+jci}aYQ1!S<`{0=&&5kXA_lf$UDU~|vY7r{S;dI&!uuafr`E9DGD=Si} zKv6Q&_nW3kpw#q?0?hU`3@=Y9X|UW$84QyPmO1sQfIW+JBu{^zhS`Xo(g8Ba9-IN!2eqWzF@5`7 z?eD2lltQ0VG=+Pi%Pe|8sikXr4iwYwcNv4$jv3*GGdzx9VAeRb&w-L|Ns~AD&@S(d z@@22&>+XVy)-tyN|Jo|cdlET30!ocRW>51$*_Xjw=%X;Sv=|f(B{Jw+2SM$V8K7>; z7X%WKTcC!Nc&MistR}U@S_gwY*p1nYl<~)BRHJ6hUpvIa_$PV{+6rrLp>_ajdj+O< z%cK;XN)$0B`{kU0Ig>l!-V?^8kVz;4rKX-GiI z+4V{+odFN??7?50oLp=aabfM$Q$vE~+>CjYVkxaTC~PHig2Q3u2H0RVIYj`IFWpY! z){C6u=WYl>OI_4PD8)PeY0%a5b@&yiT3t{DibIsXA|8|M`I>;el|)njPghx2PK%tn z0}mCP42pk1`!6j3AT$&TN}hrgl@*3_XNoODd0;%GBk`cFIkmApvY!wH8@`tFVRnJi z9tGfgtD+6Vz^kWBAAAGb7p@nnv3oCNF#N^2BU+gLWINF1u-0iE*`|k_2j%SSV;)|K zbQ~G9!(hO99}$K&H{wo_ge-+$V>p8XPEsTX+?VQf3=!cx9?>BehTTHE(qFH}%0*R& zzqp3_@RU*=)kenQ^0sdJ1N{#BxR>o`Z{O018*_`Ml%P_~n|>ocS+P9(o(~vZk;vkBjv$ecJ@paC6YBm3>U}C~wC5 zmLP(IK`?V_Ojva==d(uGCpuPgTl69syC3{*zy)$L!` zY=^WW>XraglYL-_LtLuYEq%;w0LVfOMRraUWN^d zbX1KRaCPdmer*~~CxvP|)l+qBzq#uAF^-m#3gY>wMjr0VR%gElpGa(0@AUO|V^b*6 z3cvr|w_W})O;5!@l`N)9bp5(QgWFjBzQqFMw{O74`DK;MNEuG*Ld{)g&m)c@>@~2H z6^|C2Z) z6^p1E8c9u=Tn`wYV|`VQsXsBJjmx;7aC6{r_NVPJjlf8+m8k&`c8%iR>J;3S9|wJK z++uV>_v?xJ{JhR=FC0fRlOv%Ub@ zdn|zT(lb9$hZ3{v=Hj@lqvQ+BihM`KsRd3-%?VaXtFP|@O(ov~Etb43twWW2?-6m1 z8lzbKA}hm}dGlai0e>tk86khj^AQFOpEZ|+cHH0@+^YaBNtM%7qGB3-OO0=P6NB2Z z0r}X%v|so*SEKpEZjQf8dm+@^y}JYgpovE~T88srWy?qjxG-I){y3Vk$0T4p_edPf z^4K7@(=<#apc*>P#mZBHdBa-FWwh#C33pk`ct1xXR2#u{# z3ig{0-h@(vf%@4XeL`UsIA8U#{z1#(-n(v{!h$5>N*3g*T4E(pUSMgf63%r#>%zB07&4@4lLNPLS#TN>3+0!5mzw(`Xk zpiOL=I-E^Nyp3S%exQIqwoW(FzT3kFWfiG5(FILG`0+n zpE1FwX4p%yAy!6WhDu7o954b(W`Ad;<1S*VIl5BW8abA=y}%Qe{m_AnHkm?d8iS*6 z)4a9o&A}*!P4f1w*@ClvrwpWXXi$md#Je{#9UBNVFfI$RIeYiOIhr7@BMjCD;5FbD+C^2(l}BvHrL^^vqXGf0YspCDmElf0y&1F76F_W{whuWn}6G~hYq z02&k%mEf$>76?62fb>=G{#unrkD6M7JctAWi5r_YsDY>LH9< zk-tDgMDF(F9_Y7SdK7+P3utmhV{Z+Ew9=1U32dASL82Ywf3h(__t8uD=Rw&(hUs0K zao>U+=wwE*OM6FQd3&W@!>?3ergL;Cre1ov<3q*Do9D3)KntW@2kmrYOc+IrdPNjX7hFYtT&oI zThJ|+i6wTbCl`{El7b%2Fo0V$*69mU+_?riyLwkV4e-qA&6K4>m z?%2g{ug|j$FCQ=p-3HmB;n%qyIT|lYuPzQqTcQYBQud!7Uz=spSFC};>#y>H#4Yrs z5bs$wtZknwQWim25L>0^Wac;2M_b1uBVeE{d#_}QjWsxf4o>j7$&gC~GGDepaWaUT zljAwIvDcLhy6G1qOkSb9!pCwT9~3miim-5<-V#&+_s>1*I13PT40g_*_%!(ZDXs)6 zY*h|QQQ^^Zz>VyFe>b~%aM2hms(XnJ{we!SFiK^DRpqf)rjI%NOFj(OdzeR|^t;)E zRgD8W$of26hJNTldj1ry7gz-yP9yz2JNmbY$x0UtJagT?(@Hw}+nFXWF^uLKS74oN zlDDaT8EhOlN!b>?_SdfHST#po`X!D&SC$RC$2h* zrZHk}R?CN){_9=2f#O-$%{n$G5kjqjBH=jEIB)irz9KpNSLZiy)@5)yAmsUiJgqQK z|FyIln6A!&eUn^cL^y~us|`k5Hk|JAdjLGuMnhmAH`+8lA^cB!+p6A1=9ZI8Uol+2Ehhx!NS7Qn+4@1f*q#>*mdZ2zq$8|*aFu_f7lBD>K_I=@SFd==>NxK8n`^uYhE4s zsOxCfMOJw*+*lB_~!ds7wIvZFOF_4R-yU@vr4F6lcBLqDRYJX6%Zv*0^( z>*-%V80~%2t!4?COsCTGKHt0(+?(S5xCq7OyYxFj|KhKnLA{;o`R2EhQk@_i&91GW z?9V-9|5*M>^wdX4OjJBYKebfq^}2 z6^WJh`_h~Fn{tarl17o;y3Pm_+OTPsyB=0&DH-p^`VxMdjU=VXpnf{Q6AW&hPfe<-hjxvn1+E zCcYE$AmTu>jPd>&Y_7dPg_5klL3HM9L! z`1LDlrgHU|rCy5vdI5on9muIJbF*Q=VspE`U$-8OlF7}Q^wgn|*=@*IE8I(ojDilG zYw~zKjchCk;23Uh4CjC6?uCzfhe9 zov%@d>3_2yYd*4H;@7)yy!+J~^7u9W_gz;aVyW;_@+(Bt82mOQW{I#8c! z)S3pvw;1eL{EjB8^=IZudm8Z?mbv<&$+5dWanaky9YWvgrHAGfegUCyQI)R{dY(u!@_#0t@@_z9tDa0Y|MhU{>GL_gtS5cC0$Wmoh}!2Yf$01#qC?Yh4L%-fK1 zKIa;iwobLi+k@&?iG~#qBsXmrvCy2Pff0h|m}7)`KX@o^JAXPYm1EJe19>%b>W=d0I&5~}(nw1Q zou_S>P+4sG%p-l2*}*#8NKX-j)9B?rv?OL^=+SM_QXjINWnZ~4zb$`>KTW~WJCgfB zXVq~Y2qMS{42lMey&m;u>k7zL2H|MwnUT)AbPK~3IB32xq0gcx3o7Qj;hb$!RSC^d zuc@k+{l)f8FQ7hJL%5)&+rJG_mq0d1`uNSd;?w=%J`bX zue6TZi!xsVZ}=ksN~WPYoHHLYas zwLi=bC!RIlw;wr>J@*hT>JVh;SoCsPC*Qz|bE5n_abTn>LB&}-_J#nhN;N!tKh+v$ zKZBaTZPec|E1qq&l9s>t!hSe8MmpekVe)Q{4Tp-}EE~sTZ?1#ugVL<}lgseAXtR}C z{EVy)uaf|K*C%hWOgja_GE?rLsqTqrYdloI$)_~Ku7t3Fa#^GG*xL^1KXY5GlwhD; zI>MY=m@*PGTjYNdU-@dcc6TZpJF-a*;43W}c3!~A&F{Z*f=jdN7Bbq44?+`Li{(kB zF{rYUoscU2B4yY^!^xwiev(e3d_^?T0}Y9=_Kp(|d~dDmi{zMdU>-Ub#W2ptayQK- zd+rGuR&_W^f6R<;&qbf4k|lTf;_{;`H+h`5fh^C!@?=x)y>K3#JR^y0gOV(hYZW7f z7Pr|z`p8`%Y5w^L5oji_uknA<%}Nvs1uhKpk8l+KX<#uC6V!2>Rit35B^D3L8Hl}| zO|-|nfg#!Xn3f#xTZ>g%ovAykW1K@pdv0{xrSS4ykex6aL1)F z;fMt)F>%ZiWZyYBews>|jLQqxg>?+*N2QllFwHODg7rWY}QUrYPd`H9uy(y{Y0u=$Z<4Ned`b_j6>dL;uhmx&HiE5Q_O& z93rI+VGrebU!-6XXNj2AF20)=I}jHk?K^dks9l+d25T%@`|sYiTK)OM08ci$cMzSPIEd|-KP-DV`>u6;jY*Kxrkuw8wM&(Qj$ z?{)mJi+p#Rk>}U+s+$eo=#~PrY0u)r0S2bWvm!hP=0fK=65CK!0aAU3?PZ;h|K92b zi(2TuP#c`sGwh9#TWx|oH6&JcSULJEg&FR>fe@9`uv`YoLY%CWDEkP;UQb$I(d&H?${c1%Kiltzr)VPl1`D8 z3ULTil&e?x>98~7H|?W=_h}yVaC zBc}VfbS_f*6HWMQV%z{>dEQ67 zSlY0!;287ov9m&_@iI4tTRjdGZ^a5fV9qg$3~!0eft0_N^QVCjN-9;{$?b%$-C>5a zyUGI0pOFJ#-~P?Byvc||*f!eaGg~cF|H5Ib;lpdI5Ejr$fkMKRDjT0b1ie2`EtCXY zT5h|=EV4HUUkpLntQ|@;!v$=|c&xfrZCAbuNGR(LMzk{ZKFDfIfjlyaF1w9s-mdA2 zz<(i;lRFE7@%J%^dS=I%HrV2NK%UP^(;>|EDE8RKI*A>doD z0uG^z4lymCp>Yq-%$_rn)@_F(HG1=?DvGSFho|*w?BH`^4*l(ivlZ40+1C}rl;+uzm z8lDE4UsmU;A=tg)D=WyG-75>C3%ksVu+v?Bq?12l8r~F+gk6hV$zR(jqnpbzoc^Ng zQK$xM_k2jyT#Ex;vf|byCBu0>leUqb#jooGO6p;YB@4JN#NK3lZUmdGLF)O|12jG0%E?L|@%#q|r-XCE9pmOcy6e`7^r~^S?9OR93^u zvJ+T8MA?d|*~o#DxuXJP}$+a}tIf$D^8F$0Eb?PrI;VURN6m^ z>H&oRz@{Y3;R~%68}iYGaoeAVp6-*M$m9xRy(bb;#}jX0G4WGaz1=z*4bqPHtK(4x&8>vY1V49gwh`$p zta5ASs;%aiJ!k!=w3m4=Js5u8{POe3s}5dOt$r}2MPKH22|SWNSCadT%LkwMRusN3 z&!)=hgi(p#gCLdR9p7@SzsHI#H2rOCpSK%h1MSX~wrB9AFx^1=p@nZ(`jCrPoK#!3 zaGNX99&d;-UW**O&7ODXDvNlK1gFAm;;i1}nZ74m+|owqiK#iVe}#}R$8NRzo(s7a zS~_#MY`ZJ#yG)go5?0ojBd8K$vr|O(`IWmqW`EnPvaM5k0O>ckrB>QTsBo??dpR87 zFVqFub;h3W&I^W(qkLX3yf@3=y)mNdz2~W=@DiJcUx{l}?^E=4WB#tAKkgs0-Q5(I z<`&DU(?XM+GU*d(W>kyZ-K*+&;TP}kf+S9QU;REsO57bCYnh`X?=d>56zxH3O~}i` zgNlLo;ki6dGg1D#-W=U8vm=G9OFcAx(;?%ay*b#oI)35d_zaCp`$qX)+-4QiEwR(L zb=7qbeUP#P`^TxAW+VRF6QH&Nx7Tl6?%B9t6W1!l@{6^f%*6G+nNSw4&fJ)Pu4`ye zMxsN=Xz`mn(q$GauFAJ&#Qa47lY^~$y)%ow)q1S5+{MU^gSuiqdq8L3WFqGo-D8N0 zeA2#D(wkg&`IJ^j#!CJNI)_Rh|KHzb^`>Y{ejadK!ag%xe+{Wl`Iyh^k?$1Ban>o` z=%IC6T69g$cj}Nbl94PDR;lA}np2XiIUED!b>hIup|CowwgZQ;YrX@>ONOoRgO#17 zn38~TaiVc{a;U5R+l9pbP9!6&SE-j8YF+mmQ=;-1IQ>Fs2{;9YMO@mOLdu8RMwudHO^;*M$L zshhjK>|?0`4(l8Fz1uwvVxN)Oz9aG~4yf4uzITy>eB{{qD8&6DKchb4NMh$;ME-zH zxRK!Ilz>$N!m>WrFOuSl7K9*p=tLA`6SvRmfV>Ief*ZH{a0&g$=3M6WWm>TcLyb{x3 zoUgV-?nU6#F0pyQb(uMhouGu>G6C1X>14iH^9@iLYC?YnRiq7{udmp|TRp{d?i8V$8g+=kC#AS&K^Pn}1Seg27?NAs{kwXie0I(t1cu!ppC)pUv7lQwZ7t!tMes=` z-lS~DNGL;lkF-b0OLbteu^y1xiE4~ppu_oMk*78s-!#$&o7`B zyA=zmM0EXxnueuHkU62J8zaiL1Fw4Im-oX5a`6S%i83WIdK0b4=u)VWvm)WzbSfV* z@Z)ome`2_}_0_r-1(;aAOF+-1Xg>*lL!!ZEO`+HbO>=iiv5zgb)Um8BFT#z4@0z2I zeCC@(m!Lua^vv1CNmD6@ed&oq@Cx0jtPlEaD{G%=Zie5@dpOIaSap&tb)D?ffEcJn z#_xoZrg50;=?jtWC;|t2B>y8>{LLOwAM^9wlek&*5PUF%KXR7HY7~Wi-l|ZsBUGXq zQ;M&=%y00jX_R1ouIF|aj?)m8(vNcy1)xLv}U+!nO)Q#l!$!>D@a~d+o7eSBvxUDA39xOK7>!>f2NP# z5_T={A{{BqL!lMnG@@cSW-c5%S69KdEoYrz2m5qY(kBFa7gq>fB=<}hye|+<#vb`* zJUSd$G&ijABDWNmzEJy!;HLqLY-ac+z8kno^IW8iI3CrDgO{w@>)xx=_HYXEHXL5j z%+|#dY9oWH#V!e2hadL{_?rt2(S4ggRDv-%q4o2J`e#80yyR~R01A~^jAHm{WcIHg zaPa`-Q1MEOD?OKGpi&FE0@6koU94`E*gE|lps+251!d|$pD8+vJxfhB^+u0GI(EwX z0hWDJ|4`S(KR@rUw-k2&AzS6ucVm9k_oAu|{S5U+O4A1BW_a)EwUoxs@)TsZai@1y zxJj{zIwq^cNlK?{(D{`Gq5qd3G3qG9GJHGf@zJ?n6t)GMtb89{;g^r*KgXRepGq>Ex-3{8`m?q1=R-1Fqm;B}x{@G2zuF%IA$K~k_z`Y0 z|9pZa)BnSjIo12AY!7?V;Vw3F{OiU100#c0V#A36xa>EvWAcCb|Nr^z^HA`c<|`WN zr+zs}DIfX2xv1Qxxz-sTut{^%$eg>uHR*il=QDvadhZ>xZ-RIgurk$fj%6n43pk2;X=fWMnQ1HgCh&}_^za(e~} zyll&_z8if3Tu<--`q7oSgDF_>XDV{|f^r5dy!8cZ*I%dMpRexEA1LwxNd0Mz{<)tA zuYbJUFlKPmUK&mHB4?O$(6UVV&*E!4McL*f^|Ku&()w%4pZ(p0!57E{65L96j?2a( z`h|EfdkMyd@t%v_jOe~gXHb1923|PtK5vgcc@|l2nXhcK#_!GB_3t6-))--G^sz zJt%p|=($F5HywOXG;gkL=yUY#`eQh+cB3HNfsosOH?0<_Xjq338Yw{p^hiW>!EEqG zxA=a+g(BSneVF|qhjO1Gc`JOblQ66A6q)JWqKcfas^UW(G|(xDCEHp?`U=(>$Cfq6 z066pHLf_X{bDB+c&HC{4irtV<*Fg1b%~sMtMI~$?Jus7ZqG&kAZb8+^QKxQh6(Txc zcq!I!a+#!fYv6+mqm+FJc8?GWG4<(@>OzoE$o1Z+FCG}WRex_yUSf9=ia>5T=d&@b zxK4a>zNp3Q5m8|CRlXH|zLt5D>@)+#_mmd$yVZ=l&pIz;D568?E{nUXI+ejQbC3hA zP>yr<;^*4Gdf4gosJgCIeGj!2gLkmnLve38LIQpc#r(0t1F4RaRNW5rZ|$>2rh2C} z-KrpmrFj_b+pc%L3|}AWXhD3WTa=NqtPGzwZla)r!s+?NyhhdQg3PP)gyfj>D^=WL z4{BI_)}E&I1~i{0Ztq-X^MYIB9IEfLi8_^pVv{N=zEjoo`HR-S@(5I?u^K@J_LBi7 z*aS-=fAdVo`{!okZKX?_IoYR3*4y3fn=)?X*#%hXS0wLdGXb#dwO=z5#kR0&hN#vm zo!@R4kUUsQN&~LfvrD6*TTC!Dj+-{YJ)Q)qeCHZM+-#DISPRVJ{2=^yYgehad+ZIX&$v_k;c;B6pRBA21#;{a5|ZB@$@s}WiOh7pjNYLRRX1MMiC@i zy!9a?4rg*fXv7(GJ|h3zNGDUCfzvh2{o#4S&EOV2IMOIM53@1bbctC%768)OaOJr& z@x20zLEX}^X`u0sz%tYZUY9} zA#_Y5e$HojO~iz|%Dj}U{OUjvA;kS_;k}ptb3^*eQG$P3o%p7XO3#eOfI}i#A1#sV z31a~vbR1)LUMRn?yqJAC334O{)d?@na&TrTVl*-;O`uB{b~e3)C%{+PyDQVqkCCFc z_cl^J`cBf#~tEGpzZiwYlKxdKTPL*mJSy& zU4J5o@WFSPDAXsgq9j!Z=L|vJ^;5=;$A5~w`RikW^I^4^fO=95!IrdF60mQsBi5b0 zB~lE-_Z8?G1%nmKI6plAar5}H*DaZg;{mJ=+nI5YD)c#z zpB{Jy(&0NJQY9tl2R{U#Cl6hYog*nrjh0^Vn0~4#w!L%0Bef|B;ahLB?f0SSt%^n? zh9+T@Dr*5Fxm#&kvS6T}BD>#oi$l+USDe{BlsmmN+f3kuq0M$lOTiXo(RX-Tcy1_x zREaxiJ*5|-1exgLi7b+h*ujtz)jY^YJ)*IVawp(fIN8E`O)V@9V}9(XzUEesUr|P^ zZahOAe_wh4-BUZktfy0~1`eV=kEQEkMB*P?oPBAY!1E z@W)lR=Sd|0%G;iq@D<&hqT5Dwj+dCb*SO1&JcCFdOOJQw#P=7Cx4okWK2>2yNuoX? zD7(lK9{pVf8$|5XQ_jtyfk80x&pnSRX4jk(z;eN zbRB|QkAjr682#oZ75M>QL!&3!g7n#A@ty7Y_&m1?o83~Lip#|yq;SNftCb!h?isfB zj&oM;@@LH5h6?1Ti;HF%n+&oC!yU#Z;}OAZVryFvbgK#;BOmTy-;INdR-HpOoNGLx zlD$QC#OFVQx~=HDDq6>SxQQFOxp}fZP%@Og595~gbf7Z~@U*JVPQb}7lsaPQ_J(Vj za8>!Oj@FD#;dir77?4VE-9?EoxxSHrz6#?2gl_BumwNRp`y0()zw|aD6$s_Mi38;W zN~1JXwzOOp;_=maR_RNf4T~yn9Ro^+7}r!vBNTe#mDC{M96EtUxJS~9)d%$_ZUTKh zG1KaYIeiTYfUJCyLRB09g|&B~<0Dl|b!@o`A)Q{ev#Rlu`gk;AH!*<&_#UhSUyRh% zuePS$vQ-{{Y`vV=0XVVucCFvMxv0ovPQzll<1(qMV zx!!LvfQAC^_ZOf+1ukaY8du)js`jp|rEx(lEoqt=R`_{D-wcs`Ccb39eLc4ffln5@ zDPR-`SiJ&xCxN)g??(A$5kS_iy-&ZY;WNs4>=rQZNklFT6F_f=2@$k1NH2fA^ekm^ zL$XK6HSPiL%L2O|9y}=CR4CtY(h?mG*!SBX20efLRnt!l%}VqxgWxkPMQ;3hH?WSe z22R$`cL8ruLe-G|(nlP^4>(270d20;7jaUac_U)`fb7ftlt=I+H9Z%ciY68lOKqLs z+cTe`n?9IbRhDDuxj=|Fsv|=ESTD0kt;`=H8Ka*m4pg@EAl>fUo)z|Qw&5PJ)4b$A z{OnARJIfcBE9sJP!$q&w*aW+bcf3BpD(Vvt%nRl>(Dn77?SrluTBDCTfd%`F`N3+( z?Rnt*QM$ifwJ!|Q+XJd{M70>?1y$xMV32)O3Ox2arF#dHC)wYrc=h}PxaCKZjKP4` z6KFRI^jM6jN^D!alBnZ>@@yPpH4uQyP>iqB?%TkOaG*c#AEXHRxc7`NSkA7=0tZxz z`tCCp-ip%qg-j6Y*;^L=(~-lPEOZB`$Ek}ej^IBnJ^)&4SXI@=4FpP+2{_9jt|dGe zXL@HZl~i|WWx5S?q|!mkst+A!wS+gNja(HJ$ItQegiW!TRdk-KP``}>nwl!thTEjc zap3|c!;Zi=0|&fV=Rvy^DhvAJwiDm7T}Sd0icWhP9S(Hzy@dg#t+W@cz{;G|FN|Gy z`snz`1<^%HBipWj03b~Z?`qJ0&6nGlC(q;iV#J*sLDOdmAjnsPepCcdVJw8^L=8*8 zirJsxbAU_J!se_kWzyx^KVbK_eo~Tqq7?{T=E(-1Y{Gz!0+pJ?aTJ1%{U%doHBj}O z_c3P`bBL0K{2$8xIxOmK?H|VlK}iJxk;WnglxCz;k?tBoK)PgxZb?a%knT=thLn`< z98!?(4uS8Qea=4T{GQKq_I`fXHGhnv%zIYc>t6T$3KW0s@+uv$2>cp%(RFSK*NyE z68?nYcy~i%SZ#J>CacL^(CxXOoOtrC)yd%5kEj{1+#NnkPoU?XTl#?pQ9Mj$6#S0Z_7ue<~Oy- z^2``P=3M~Y`qFKL{r54_rfPjOA1QQwCLjOYzz4oLF$m;d?+bZEN@<$m&;|57!Gxl; z6o=aY_PSvb@H&5fATiG^T!X(*C|rPju6^5v1E4DLGeDSqtb8q5^BQN$+!-;DX`Pjx zsaB|U4EUTR()v8lq(~`P5vz}`mC#6y6>|J)*bk$h*`3fjevb$GVA_CMeCcFvP#nTu zI0rDf=8)&VyY;T#0`;=?`&awaytXO*C2DG8r+bT+ZFPYDH+<;KI4nP@i?Gd0sTV}L z9IZQ}%)2^?rxlz55#TrGO)~#87QKe)Z6LUcvcjo5Ae5q9*vPhi)6cg;X))JOf1LpT zmIFBFf;TmiUtpqP;K=;}t4C%7fzlSB)yhG^_uk0TR=v^QicOVHHg1W{p)a1N|LVA^ z@eJ4NeCry}qmRLFo&r|&8vz?UMNKpKJk$E90l$y5ylnqsbu&vgez~fmi(riV7jPW< z2B?Jtx3d?QQG6R8F~$_W&I|z2Xg~`qMUII1u|7ZnyBm`&1ILAr;g-x9RDfMD(u1#dQ+d^>eTpPznw zdulxIb{IGv+O;Yz%?901P%PEz2aUy8-HMmxJ9;Jh-ZX61H17dpEPI-iVNv?RZEIzu z>9v5z>f5@~%p>TShr)rn8sgXyfQ*o7a&?O57_et3)!1{AVH>efH9#bJtlYE)XwAU_ za=#CU<+lI~i~EQ>t*InTXjGqdkM{r&O!ttFjg#nK$_<`8n^_!N{hcKf`*R~pzdUR_ zk}qecbUkD45XE;0uQ=2~T8+LadV*BT*R?}xa|s7otB+`~8*TY5S{Sp^-REk@>jSPl zyYS%`Nr;qdV7jvqo_$mYnz0_*MO$K}M+4l79b@LFxtBg+x$wF(5b!T%pA5}JKOOrt zF7Y@ec!=q|Fn2Jfq3Rs3Vs7kM^Wr(z@d)3?_R(YAix7Z%Iq%o(z@DO6eIPVx!-=4S z(DZCM$_#H@WXzRYV`kV#R__DqQ(V|fm~~EjDO1-(BZ^Akg7)fId$hVU`qSob4(sHC zGt1C4`Ccfd%Opsmbms53L3=x|b`(?S>GKkr)1bu`M+& zKWj`&f9(l^GYeFyAhX!xqp;arLjh>_FgHup_-GpP)mByWSzuUHS_c%8JbYAb8!FQs zf|Od1%OJ-t(ygCLu3{|7->`w)R{Ah)bU~L$WL5`wI%7P%(W#aF=H;>?H@t_K6^sz>F9CUW6ELHbWs?PG^k~PuV|)miAR;4v&oLR`@=uJV)^11_!wndD|y z)L(WO(=<85jRP9f5jL=N_pg-YHm@qE*;Vr`Nian!3RQ)r>cv-U(BsTU(w)t;DJs(u zDMdF*b?K4gZH2KuyXTgT0Q5K)@p>^wyH^E1awVKxIJOocc4zM*dbQ!DH6PBc{L~Q; zAm%w7NyLZ!!i+8m-0yzg!fo~=WRbey8P(&)S-#ruX1huY@{vab76cz5Udc`#V;mxt z6`Nko9aJxhV^S5@ZbLjN^9&zY*$@+lA>XY593y>{(OfSqT)P4v??BmB8OeWHbQr%4 zus1^7Lwnk%Gl0T6+@MY|Dv#VyxT_cYL#VTIw6n6x;L4yjt!Hj^uVS4{z4=(QBivj# zNAeTo{`j{9_AGcpuAwJZ zVhh!8I^qW{j5?1$nNMUJvL{OAvEoo$xaLW^#{x8nB%Y*r70Eq-muAy?qx+k;xdXlOiyj>KK`!n%1WkR=PZL_L3O1iM6ie;K z3msdf6UHvsfOqH}2bjn!(yuUY^!jlPS zJ=D4Y~(-K3<@~%sX@+Fv%>U z8Ewkjk9pttrxt+2*3^|NQPthSw!2Z)?%u3Qs~W4oGO71 z{*Fvc`)payCDSq7<)w1>4o*aXc5co?KUQJ$#1Yvt>95I29fbjXn2~k`R#_i)Hx86u zJ4{nou^bTX%6}tNXwks5I+__fkzWyCClr;qfV(HFHrJ#yoY*xgCU&ol38XIldYR>3 z;&B_&KkS^yYR#O^`Hi7PP2!@=6noBKOZoGq8rQW^H^cUvVWP>}1JUGQ^n)pNrZ4Kq zbfc$}b|HA9)zbLF<%L6g$>hJ8nkYOrx}@JYu*O+B*M);xK5>OGug!oUEjZI6pQmZh zt=36P;GZ-LN*1SgK&PZ6-b@QBt?)w}M$W>El+8WKr1KVEJsrr&Il<#<9ji9)$vLR5 zn=XCw>!R*-hKN-M$Q6UCB6D_(BOPo-i;dC5qSio4jwKZN-Lx-`_e5?iDiUrJamQ^Btm zp?F&XL6CC8rt}_qoi=kxe(*A4b*2j%G4dMm5j|H+JyD}uT*RKrGA|7BLq*c>4lCT7 zBReg`0#iffFvpo$p`i*lkok-FjH92Ib)NGY$3UAf^Z}m4ftms@G1O8OyL-5l0UY=x z=u-{uzsS9FC`663#67;`Y#WTuP7HT~_aixE>10ddo0vB^q|!$dX3n`J5;K{#(2 zK8PZPxX=e)r1}W)MOE*-Lv$1lItT37P3W$><}Wh3$A~FD#*DI+Ar9MX-raprRVY_c zk3a0UCGC_roQ}S*a8h7sZ%k>yw;n1LDluaEWZPrPZX*>K(G9#$ZycSr?0%oe(GsjC zEaG7m5EWF|E`S0}35+T1$|u|B&6$3c1NMPHt3Gun)52MUyAaR=)9D9-pdQ z{_fmx^_+R0G4aaIx5}qexAGF!`aDPVr5k@0PuqQO_QSoru)EHHNGd332#A^q6-&;T zuV(2NlfPfPPsHRbKpL8lyYmp#6fs#+ZkbOxY&>A64F9!^i0eCRt|?g0y7cce6MGGx zCaDU?8*UTGn&R=>TMW}|c}`6zoN@?Ql`QVbj26Da-|i_edt%CwOsKXO%2C7Lk|A!6 z`rW7q8S{H+thDM=!HQTjvIVef&5g7@6=F+;2qpSW2-&E>C2ThX7dwbFX%t89VBj^P z6c6@E^R|4~Gv#@;nV*ci1;OSJ#?hc0Rwc&Z*HCjt(}ELa5=^IHc0+X$3{2w@s_gBm zoH4hqsUb|xSSB&)jl7in($_Hk>`ko19~*)#=50kcM3?8FyME#`r2rEh(uw)X59p)k3%7Cr1^g_(14A>P$Cpt#>-4N+YvbNZ z=J=E&SjJcEZjvrSs#IM37>7qXPiNc%+K1Z=N!+EBw#0tFdT|W{^`sMWl%qp8Med*m zLtfRf**cK4zqx)Xa*>(PuxQ{y{if2(#c%L38r~=apPTC9a!@W&Ym5Rpl5w+nwFg^Q z!f2t`$kSY(+J%!8tLU)IAx1#?mt4s-Y_#DC>adDQxxhb^ZRYUkI%U$-Hk7$8dyP zc2TTY5VfP>9hsYk!|_W9*B}GYb}+uE1b9$ z6{D=}3Sef#?`g*lBg(lcj|m?k31L;C^SHmBCtEnL%Hw|f*kQ7<%QhY4be$vvZ4gZI zqns~tRySeabe|%6)rxUI>Vos$6q)II%w(Ff70k|GUN6se{WBx$GI;xOE^bC@zO&WM zQCA;UhmA+J?DSi%<=g9(8p!v(%$nH#U7tUNv-X)cX5Da}ZeX2s`#IZR2#3w3vQmRx zJgq`Jpm>tkdHm2L<16s4b?L*zBd$|+Oh&90;j$xEQ{53hYh96Qi%&7*V5$D4R#?6} zNvy4mrul{H_i!dQ-|MjY3^bxN@ z|JS_L=3E~^oDgO)$z<)-Xue51*SxbA1*cd!-#>v<>pI2Vl_51>+TG;va&U(eT(I|k zKV+MG!J;?3<|OWk^Rt~~ODxI#Skp847!(|vZ~Im>4!YNj4u6p@oy!Yz{>+lj>lsec zqdK_vq0d#dd~U=g?_o)0fZfy47e3^KXDj)X5R2HalQAZDHgvD17AgVKfG?y?ALJo;-e`JvDqD`{IMmCo zZ0A#SWWONyRAZ-Q(hK}BaqpiJ)wdEk!ntz2lJEEuyQy&Ju{Yl1@Qd8b-&W$R_g|}6 zCaX8SA5iXg_TKYzF43{+V7d8ZVX*t4v6j3BT0GRZdxN|yKoK%8-(VRcJlJu{O> zrP29v4PCGNiFFy*vr%qgq!HKp>t~(I$-KG-b$x|Zm1SMcZ+b#hXFeAOw2$lktmKH!I3LL-c*4Ho$zp%TY@uwGD7o0TL&kfGOxshiOTzS!DbSIOx(%3+k zKZ`9Qc`k`P7`8#h)vDN}f*-Fk;w6H#HtuLoEPHwpDru33+|l@|c7c&NmucUZI`@tI zx_zsbtI?=&RHd6?eaFs2uu(sH*w8wR@ZJi^@ul5LTF~M?IcRJB2o@Hxxoz(Xl?@$N z8JgTNtxGumG-bWc&)`y!wjj_K(rmaq}ekTPnHTL;mvx+E&=s~@OZ)K%Xj6~|hn zkEgR1Y}=}@#-GRCu>%=>H`p66`#+vs*OdApw@qXGc~6#cxn<8m8R9<(-8 zkX}Z}C8Gnju#e`%kp)k*??P8c^bk*D4MaApK7(?abUuk%v*UF=0aDHf?%iFHZ! zD%AmrJI4G0O-1d>izP;^27>(7uIQa%MDT*xiOYQ0&q__c++WMpTz0y-m83z7%fH}= z#vm{;Ir5xh9Cb14-$2n#uvS;#hpBc7o}BS}$u}aF-DwAl&I`CX>JMi;r0Q?^HCe-Q zJ3WFsXT#;|KHKZwI#@oGL{|2%FRwl!H6qr!15B=}y!PB?|)4la%{|7Lm1S^1S z{pi%B{d4I4YtuZ!6?}XRk0H+A!IFeieqaL1Js%(L-&5}2vkBYsoV=6q{yVrA7BLE! z_lVxa`>*@@s4mPlW>W4IlmGc%Ys=#S}G@g*