Project: Rook (Runtime / Cloud Native Storage)
File: resource-dependencies.md

Targeted for v1.7 Custom Resources in Rook-Ceph can act as data/storage "providers" for other Resources in the Kubernetes cluster which may be "dependent" on the data/storage provided to them. This dependency relationship is not codified in Rook, and it is possible for Custom Resources which have dependents to be deleted, removing the underlying data that dependents need. This leaves those resources orphaned and could destroy valuable end-user data. An example of this is a user needing NFS storage from Rook-Ceph. The user needs to create a CephCluster. They also need a CephFilesystem. Finally, they need a CephNFS which references the CephFilesystem. In this example, the CephNFS is dependent on the data pool created by the CephFilesystem which is in turn dependent on the underlying CephCluster. It should not be possible for an administrator to delete the CephFilesystem until the CephNFS resource is deleted because a user may be using the CephNFS resource. Similarly, it should not be possible for an administrator to delete the CephCluster while the CephFilesystem exists because the filesystem could be in use. It is up to the administrator to ensure users no longer need storage before they delete resources like CephNFS or CephFilesystem. The goal of this design is to add safety measures into Rook to help prevent accidental destruction of end-user data. This design proposes to enable this by tracking resource dependency relationships and block deletion of any given Rook-Ceph Custom Resource when it is referenced by another resource in the Kubernetes cluster. Block deletion of a CephCluster when there are other Rook-Ceph resources in the same namespace Block deletion of a CephBlockPool, CephFilesystem, CephObjectStore, or CephObjectZone when a CephNFS or CephClient uses one of its pools. Block deletion of a CephObjectStore when there is a user bucket present in the store. This allows Rook to block deletion when lib-bucket-provisioner or COSI Bucket resources exist without having to probe an external API. This already has an implementation, but it blocks for unrelated lib-bucket-provisioner OBs. Block deletion of a CephBlockPool or CephFilesystem when there is a PersistentVolume reliant on it. Block deletion of a CephObjectStore when there is a CephObjectStoreUser reliant on it. Block deletion of a CephRBDMirror if a CephBlockPool has mirroring enabled. Block deletion of a CephFilesystemMirror if a CephFilesystem has mirroring enabled. Block deletion of object "multisite" resources with their hierarchy: CephObjectRealm -> CephObjectZoneGroup -> CephObjectZone -> CephObjectStore All Rook-Ceph CRDs: CephCluster CephBlockPool CephFilesystem CephObjectStore CephObjectStoreUser CephNFS CephClient CephRBDMirror CephFilesystemMirror CephObjectRealm CephObjectZoneGroup CephObjectZone Resources for which Rook-Ceph acts as a driver/provisioner: ObjectBucket (lib-bucket-provisioner) Bucket (COSI) Kubernetes resources which can depend on Rook: StorageClass (Kubernetes) PersistentVolume (Kubernetes) A graph of proposed dependency relationships is shown below with more detail to follow. A CephCluster does not create pools itself, but the Ceph cluster it represents houses pools, and users can manually create pools using Ceph tooling outside of Kubernetes manifests. It is useful but not critical to understand which resources interact with pools and in what

Dependents which can create/delete pools: CephBlockPools in the same namespace CephFilesystems in the same namespace CephObjectStores in the same namespace CephObjectZones in the same namespace Dependents which can consume arbitrary pools including user-created pools: CephNFSes in the same namespace CephClients in the same namespace Dependents that do not interact with pools: CephRBDMirrors in the same namespace CephFilesystemMirrors in the same namespace CephObjectRealms in the same namespace CephObjectZoneGroups in the same namespace It is most safe if the `CephCluster` treats all possible Rook-Ceph CRs besides itself as simple dependents. If a dependent exists in the same namespace, block deletion. In this way, CephCluster is the most protected provider resource. It also acts as a root for preserving deletion ordering. Dependents which can consume this provider's pools: CephNFSes in the same namespace that have `spec.pool == <a provider pool>` CephClients in the same namespace that have any `spec.caps` value with the string `pool=<a provider pool>` Dependents via CSI: StorageClasses that have: `provisioner == <operator namespace>.rbd.csi.ceph.com` AND `parameters.clusterID == <ceph cluster namespace>` AND `parameters.pool` OR `parameters.dataPool` references a `CephBlockPool` pool PersistentVolumes that have: `spec.CSI.Driver` == `<operator namespace>.rbd.csi.ceph.com` AND `spec.CSI.VolumeAttributes["clusterID"] == <ceph cluster namespace>` AND `spec.CSI.VolumeAttributes["pool"]` OR `spec.CSI.VolumeAttributes["journalPool"]` references a `CephBlockPool` pool OR `spec.storageClassName == <name of StorageClass which references the CephBlockPool>` NOTE: dependents should continue to be ignored if `spec.cleanupPolicy.allowUninstallWithVolumes == true` Dependents which can consume this provider's pools: CephNFSes in the same namespace that have `spec.pool == <a provider pool>` CephClients in the same namespace that have any `spec.caps` value with the string `pool=<a provider pool>` Dependents via CSI: StorageClasses that have: `provisioner == <operator namespace>.cephfs.csi.ceph.com` AND `parameters.clusterID == <ceph cluster namespace>` AND `parameters.pool` OR `parameters.dataPool` references a `CephBlockPool` pool PersistentVolumes that have: `spec.CSI.Driver` == `<operator namespace>.cephfs.csi.ceph.com` AND `spec.CSI.VolumeAttributes["clusterID"] == <ceph cluster namespace>` AND `spec.CSI.VolumeAttributes["pool"]` OR `spec.CSI.VolumeAttributes["journalPool"]` references a `CephBlockPool` pool OR `spec.storageClassName == <name of StorageClass which references the CephBlockPool>` NOTE: dependents should continue to be ignored if `spec.cleanupPolicy.allowUninstallWithVolumes == true` Dependents which can consume this provider's pools: CephNFSes in the same namespace that have `spec.pool == <a provider pool>` CephClients in the same namespace that have any `spec.caps` value with the string `pool=<a provider pool>` Dependents which reference this provider by name: CephObjectStoreUsers in the same namespace that have `spec.store == <CephObjectStore.metadata.name>` Dependents via lib-bucket-provisioner: ObjectBucketClaims that have: `spec.endpoint.bucketHost == <provider's service name>.<ceph cluster namespace>.svc` AND `spec.endpoint.bucketName == <a provider bucket>` Dependents via COSI: Buckets with a reference to a provider's service and bucket (specific paths TBD by COSI design) These resources are all part of Rook-Ceph's multi-site object storage strategy. `CephObjectZone` creates pools. A zone can be effectively thought of as the "object store" itself. Dependents which can consume this provider's pools: CephObjectStores in the same namespace that have `spec.zone.name == CephObjectZone.metadata.name` CephNFS in the same namespace that have `spec.pool = <a provider pool>` CephClient in the same namespace that has an `spec.caps` value with the string `pool=<a provider pool>` `CephObjectRealm` has dependents: CephObjectZoneGroups in the same namespace that have `spec.realm == CephObjectRealm.metadata.name` `CephObjectZoneGroup` has dependents: CephObjectZones in the same namespace that have `spec.zoneGroup == CephObjectZoneGroup.metadata.name` CephRBDMirror has dependents: CephBlockPools in the same namespace that have `spec.mirroring.enabled == true` CephFilesystemMirror has dependents: CephFilesystems in the same namespace that have `spec.mirroring.enabled == true` We can identify some common metrics for determining whether a resource is a dependent of a given "provider" resource. Not all metrics are always applicable, but each these metrics appear more than once. It should be possible to design reusable patterns/methods for reusing logic. Is a dependent in the same namespace as the provider? Does a dependent reference the current Rook-Ceph operator? Does a dependent reference the Ceph cluster namespace of the provider? Does the dependent reference pools created by provider? Does the dependent reference the provider by name? (e.g., CephObjectZone references CephObjectZoneGroup) It will be important for the user to understand why resources are not being deleted if Rook is blocking the

This design proposes that the Rook operator report to the user when it is blocking deletion of a resource due to dependents in two ways: report it in a status condition of the resource object being deleted report a Kubernetes Event () Using both of these methods will maximize user visibility. Status: Reported statuses will be modified as follows: Object's status.phase should be changed to "Deleting" as soon as a deletion timestamp is detected and never changed A status.condition should be added if the operator is blocking deletion: Type: "DeletionIsBlocked" Status: "True" Reason: "ObjectHasDependents" Message: "object deletion is blocked because it has dependents:" followed by a full list of which dependents exist of which Kubernetes Object Kinds (e.g., CephBlockPools or PersistentVolumes) or of which Ceph kinds (e.g., pools or buckets). Event: Reported events will have the following content: Type: "Warning" Reason: "ReconcileFailed" Message: "object deletion is blocked because it has dependents:" followed by a full list of which dependents exist of which Kubernetes Object Kinds (e.g., CephBlockPools or PersistentVolumes) or of which Ceph kinds (e.g., pools or buckets). Rook currently inspects Kubernetes PersistentVolume (PV) resources when deleting CephClusters. This provides protection from deleting the backing Ceph cluster when user applications are using it. With the changes proposed here, it would be more specific to block deleting CephBlockPool resources when there are PVs referencing the specific CephBlockPool. Similarly, it would be more specific to block deleting CephFilesystem resources when there are PVs referencing the specific CephFilesystem. Kubernetes APIs are quite stable, and it is unlikely that the methods Rook uses to inspect PersistentVolumes will require changes with any regularity. If changes are necessary, Kubernetes will likely give well over a year of time to migrate away from deprecated API elements. Removing a Ceph cluster that is hosting PVs in use by Kubernetes applications could be disastrous for users. Therefore, this document proposes to continue checking for PersistentVolumes that depend on Ceph storage. The document further proposes to increase protection of user data by detecting dependencies for specific CephBlockPools and CephFilesystems when they are deleted rather than checking when the CephCluster is deleted. Detecting the existence of PVs when the CephCluster is deleted then becomes redundant and should be removed. As a note, StorageClasses (SCs) are only used during initial creation of a PersistentVolume based on the StorageClass. It may not be a good idea to block deletion when there are StorageClasses that reference Rook-Ceph block/file storage. An admin may at times wish to leave StorageClasses (a more user-focused API point) and replace the Rook-Ceph resources providing the storage represented by the SC without disturbing users' ability to reference the SC. Any user that tried to use the StorageClass while the cluster was down would merely fail until a replacement cluster came online. This document outlines the steps needed to treat StorageClasses as a dependent but proposes not to implement the dependency at this time. If we get more information in the future that provides a compelling use-case for treating StorageClasses as dependencies, this functionality can be implemented at that time. Detecting lib-bucket-provisioner ObjectBuckets that are dependent on a given CephObjectStore requires inspecting ObjectBuckets for a reference to a bucket found in the object store as well as a reference to the address of the Kubernetes Service created for access to RGWs (`<service name>.<namespace>.svc`). Detecting COSI Buckets will be similar. This detection does require accessing external APIs (lib-bucket-provisioner and

This is non-ideal for COSI whose APIs will be progressing from v1alpha1, through beta stages, and then into v1 in the coming months/years. Rook can merely check for the existence of buckets in order to support lib-bucket-provisioner and COSI simultaneously with the same code. This would also remove any need for Rook to update its COSI API for dependency checking though it will still need to update its API to continue operating as a COSI driver. This would allow Rook to block deletion of CephObjectStores without care for who has "claimed" a bucket. Given that a Kubernetes cluster might have many hundreds of ObjectBuckets, or COSI Buckets, having a simpler way of querying for dependents can lighten the load on the Kubernetes API server and reduce Rook's resource usage. Since OBs and COSI Buckets both result in buckets being created in a CephObjectStore, Rook can query the object store and block deletion if buckets exist. This is an elegant solution for blocking when buckets are claimed by these outside resources. It does mean that CephObjectStores that have had buckets created in them directly (by admins or users) will block until the buckets are manually deleted. An admin may need to request that users remove unneeded buckets or may instead remove the buckets themselves. If the admin wishes to preserve the CephObjectStore's pools on deletion along with their data, the admin may merely remove the finalizer on the CephObjectStore. While this requires more steps to delete the CephObjectStore, it provides additional safety for user data by requiring users or admins to inspect and clean up unneeded buckets. The main downside to this approach is that Rook will not be able to report if there are specific OBs or COSI Buckets consuming storage from the CephObjectStore. An admin would need to examine the resources in their cluster to determine if there are claims to the storage manually. A diligent admin will likely have done this work beforehand. This document proposes to implement this strategy to avoid reliance on external APIs. Rook developers should revisit the decision at a later date to discuss whether the strategy is continuing to adequately meet users' needs and whether the drawbacks noted are causing any issues. This design will result in changes to every major Rook-Ceph controller. However, it should be quite easy to tackle these changes in stages so that changes can be more easily implemented and reviewed. Stages by priority: Block deletion of a CephCluster when there are other Rook-Ceph resources in the same namespace Block deletion of a CephObjectStore when there is a user bucket present in the store. This already has an implementation, but it blocks for unrelated OBs Block deletion of a CephBlockPool, CephFilesystem, CephObjectStore, or CephObjectZone when a CephNFS uses one of its pools. Block deletion of a CephBlockPool or CephFilesystem when there is a PersistentVolume reliant on it. Block deletion of a CephObjectStore when there is a CephObjectStoreUser reliant on it. Block deletion of a CephRBDMirror if a CephBlockPool has mirroring enabled, AND Block deletion of a CephFilesystemMirror if a CephFilesystem has mirroring enabled. These are such similar checks that they should be simple to do at the same time. Block deletion of object multisite resources with their hierarchy: CephObjectRealm -> CephObjectZoneGroup -> CephObjectZone -> CephObjectStore Block deletion of a CephBlockPool, CephFilesystem, CephObjectStore, or CephObjectZone when a CephClient uses one of its pools. CephClients are seldom used, and few users will be affected if this is not in place. Immediate feedback is always more helpful to users when possible.