github.com/openebs/node-disk-manager@v1.9.1-0.20230225014141-4531f06ffa1e/pkg/cleaner/jobcontroller.go (about) 1 /* 2 Copyright 2019 OpenEBS Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 16 This pkg is inspired from the deleter pkg in local-static-provisioner 17 in kubernetes-sigs 18 https://github.com/kubernetes-sigs/sig-storage-local-static-provisioner/tree/master/pkg/deleter 19 */ 20 21 package cleaner 22 23 import ( 24 "context" 25 "fmt" 26 27 "github.com/openebs/node-disk-manager/api/v1alpha1" 28 "github.com/openebs/node-disk-manager/blockdevice" 29 "github.com/openebs/node-disk-manager/cmd/ndm_daemonset/controller" 30 "github.com/openebs/node-disk-manager/pkg/env" 31 batchv1 "k8s.io/api/batch/v1" 32 v1 "k8s.io/api/core/v1" 33 "k8s.io/apimachinery/pkg/api/errors" 34 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 35 "sigs.k8s.io/controller-runtime/pkg/client" 36 ) 37 38 const ( 39 // JobContainerName is the name of the cleanup job container 40 JobContainerName = "cleaner" 41 // JobNamePrefix is the prefix for the cleanup job name 42 JobNamePrefix = "cleanup-" 43 // BDLabel is the label set on the job for identification of the BD 44 BDLabel = "blockdevice" 45 ) 46 47 // JobController defines the interface for the job controller. 48 type JobController interface { 49 IsCleaningJobRunning(bdName string) bool 50 CancelJob(bdName string) error 51 RemoveJob(bdName string) (CleanupState, error) 52 } 53 54 var _ JobController = &jobController{} 55 56 type jobController struct { 57 client client.Client 58 namespace string 59 } 60 61 // NewCleanupJob creates a new cleanup job in the namespace. It returns a Job object which can be used to 62 // start the job 63 func NewCleanupJob(bd *v1alpha1.BlockDevice, volMode VolumeMode, tolerations []v1.Toleration, namespace string) (*batchv1.Job, error) { 64 nodeName := bd.Labels[controller.KubernetesHostNameLabel] 65 66 priv := true 67 jobContainer := v1.Container{ 68 Name: JobContainerName, 69 Image: getCleanUpImage(), 70 SecurityContext: &v1.SecurityContext{ 71 Privileged: &priv, 72 }, 73 } 74 75 podSpec := v1.PodSpec{} 76 mountName := "vol-mount" 77 78 if volMode == VolumeModeBlock { 79 jobContainer.Command = []string{"/bin/sh", "-c"} 80 81 // fdisk is used to get all the partitions of the device. 82 // Example 83 // $ fdisk -o Device -l /dev/sda 84 // Disk /dev/sda: 465.8 GiB, 500107862016 bytes, 976773168 sectors 85 // Units: sectors of 1 * 512 = 512 bytes 86 // Sector size (logical/physical): 512 bytes / 4096 bytes 87 // I/O size (minimum/optimal): 4096 bytes / 4096 bytes 88 // Disklabel type: dos 89 // Disk identifier: 0x065e2357 90 // 91 // Device 92 // /dev/sda1 93 // /dev/sda2 94 // /dev/sda5 95 // /dev/sda6 96 // /dev/sda7 97 // 98 // From the above output the partitions are filtered using grep, 99 // 100 // first all the partitions are cleared off any filesystem signatures, then the actual partition table 101 // header is removed. partprobe is called so as to re-read partition table, and update system with 102 // the changes. Partprobe will be called only if the device is a block file; else if its sparse file, wipefs 103 // will be done. 104 // wipefs erases the filesystem signature from the block 105 // -a wipe all magic strings 106 // -f force erasure 107 108 args := fmt.Sprintf(""+ 109 "(pvs -o pv_name,vg_name|grep %[1]s|awk '{print $2}'"+ 110 "| xargs -I {} sh -c 'dmsetup info -c -o name --noheadings|grep ^{}- "+ 111 "| xargs -t -I {} dmsetup remove {} ') && "+ 112 "(fdisk -o Device -l %[1]s "+ 113 "| grep \"^%[1]s\" "+ 114 "| xargs -I '{}' wipefs -fa '{}') "+ 115 "&& wipefs -fa %[1]s ", 116 bd.Spec.Path) 117 118 // partprobe need to be executed only if the device is of type disk. 119 if bd.Spec.Details.DeviceType == blockdevice.BlockDeviceTypeDisk { 120 args += fmt.Sprintf("&& partprobe %s ", bd.Spec.Path) 121 } 122 123 jobContainer.Args = []string{args} 124 125 var volume v1.Volume 126 var volumeMount v1.VolumeMount 127 128 // in case of sparse disk, need to mount the sparse file directory 129 // and clear the sparse file 130 if bd.Spec.Details.DeviceType == blockdevice.SparseBlockDeviceType { 131 volume, volumeMount = getVolumeMounts(bd.Spec.Path, bd.Spec.Path, mountName) 132 } else { 133 // for a non sparse disk, mount the /dev directory so that wipefs inside the container 134 // gets reflected outside the container also. When this is done a correspnding change 135 // event is also generated from udev 136 volume, volumeMount = getVolumeMounts("/dev", "/dev", mountName) 137 } 138 139 jobContainer.VolumeMounts = []v1.VolumeMount{volumeMount} 140 podSpec.Volumes = []v1.Volume{volume} 141 142 } else if volMode == VolumeModeFileSystem { 143 jobContainer.Command = []string{"/bin/sh", "-c"} 144 jobContainer.Args = []string{"find /tmp -mindepth 1 -maxdepth 1 -print0 | xargs -0 rm -rf"} 145 volume, volumeMount := getVolumeMounts(bd.Spec.FileSystem.Mountpoint, "/tmp", mountName) 146 147 jobContainer.VolumeMounts = []v1.VolumeMount{volumeMount} 148 podSpec.Volumes = []v1.Volume{volume} 149 } 150 151 podSpec.Tolerations = tolerations 152 podSpec.ServiceAccountName = getServiceAccount() 153 podSpec.Containers = []v1.Container{jobContainer} 154 podSpec.NodeSelector = map[string]string{controller.KubernetesHostNameLabel: nodeName} 155 podSpec.ImagePullSecrets = env.GetOpenEBSImagePullSecrets() 156 podTemplate := v1.Pod{} 157 podTemplate.Spec = podSpec 158 159 labels := map[string]string{ 160 controller.KubernetesHostNameLabel: nodeName, 161 BDLabel: bd.Name, 162 } 163 164 podTemplate.ObjectMeta = metav1.ObjectMeta{ 165 Name: generateCleaningJobName(bd.Name), 166 Namespace: namespace, 167 Labels: labels, 168 } 169 170 job := &batchv1.Job{} 171 job.ObjectMeta = podTemplate.ObjectMeta 172 job.Spec.Template.Spec = podTemplate.Spec 173 job.Spec.Template.Spec.RestartPolicy = v1.RestartPolicyOnFailure 174 175 return job, nil 176 } 177 178 // NewJobController returns a job controller struct which can be used to get the status 179 // of the running job 180 func NewJobController(client client.Client, namespace string) *jobController { 181 return &jobController{ 182 client: client, 183 namespace: namespace, 184 } 185 } 186 187 func (c *jobController) IsCleaningJobRunning(bdName string) bool { 188 jobName := generateCleaningJobName(bdName) 189 objKey := client.ObjectKey{ 190 Namespace: c.namespace, 191 Name: jobName, 192 } 193 job := &batchv1.Job{} 194 195 err := c.client.Get(context.TODO(), objKey, job) 196 if errors.IsNotFound(err) { 197 return false 198 } 199 200 if err != nil { 201 // failed to check whether it is running, assuming job is still running 202 return true 203 } 204 205 return job.Status.Succeeded <= 0 206 } 207 208 func (c *jobController) RemoveJob(bdName string) (CleanupState, error) { 209 jobName := generateCleaningJobName(bdName) 210 objKey := client.ObjectKey{ 211 Namespace: c.namespace, 212 Name: jobName, 213 } 214 job := &batchv1.Job{} 215 216 err := c.client.Get(context.TODO(), objKey, job) 217 if err != nil { 218 if errors.IsNotFound(err) { 219 return CleanupStateNotFound, nil 220 } 221 return CleanupStateUnknown, err 222 } 223 if job.Status.Succeeded == 0 { 224 return CleanupStateRunning, nil 225 } 226 227 // cancel the job 228 err = c.CancelJob(bdName) 229 if err != nil { 230 return CleanupStateUnknown, err 231 } 232 233 return CleanupStateSucceeded, nil 234 } 235 236 // CancelJob deletes a job, if it is present. if the job is not present, it will return an error. 237 func (c *jobController) CancelJob(bdName string) error { 238 jobName := generateCleaningJobName(bdName) 239 objKey := client.ObjectKey{ 240 Namespace: c.namespace, 241 Name: jobName, 242 } 243 job := &batchv1.Job{} 244 err := c.client.Get(context.TODO(), objKey, job) 245 246 err = c.client.Delete(context.TODO(), job, client.PropagationPolicy(metav1.DeletePropagationForeground)) 247 return err 248 } 249 250 func generateCleaningJobName(bdName string) string { 251 return JobNamePrefix + bdName 252 } 253 254 // GetNodeName gets the Node name from BlockDevice 255 func GetNodeName(bd *v1alpha1.BlockDevice) string { 256 return bd.Spec.NodeAttributes.NodeName 257 } 258 259 // getVolumeMounts returns the volume and volume mount for the given hostpath and 260 // mountpath 261 func getVolumeMounts(hostPath, mountPath, mountName string) (v1.Volume, v1.VolumeMount) { 262 volumes := v1.Volume{ 263 Name: mountName, 264 VolumeSource: v1.VolumeSource{ 265 HostPath: &v1.HostPathVolumeSource{ 266 Path: hostPath, 267 }, 268 }, 269 } 270 271 volumeMount := v1.VolumeMount{ 272 Name: mountName, 273 MountPath: mountPath, 274 } 275 276 return volumes, volumeMount 277 }