if not backup_with_pod and not retry_backup and not self.backup_clone_compatibility:
# It is important set to True before recall the process.
self.backup_clone_compatibility = True # We only try once
-
self._io.send_info(RETRY_BACKUP_WITH_STANDARD_MODE)
+ logging.debug("Sent info to joblog: " + RETRY_BACKUP_WITH_STANDARD_MODE)
status = self.process_pvcdata(namespace, orig_pvcdata, backup_with_pod, True)
return status
ImagePullPolicy,
prepare_backup_pod_yaml,
get_backup_pod_name)
-from baculak8s.plugins.k8sbackend.pvcclone import prepare_backup_clone_yaml
+from baculak8s.plugins.k8sbackend.pvcclone import prepare_backup_clone_yaml, find_bacula_pvc_clones_from_old_job
from baculak8s.plugins.k8sbackend.baculaannotations import BaculaBackupMode
from baculak8s.util.respbody import parse_json_descr
from baculak8s.util.sslserver import DEFAULTTIMEOUT, ConnectionServer
if namespace is None or pvcname is None or capacity is None or storage_class is None:
logging.error("Invalid params to pvc clone!")
return None, None
- pvcyaml, snapname = prepare_backup_clone_yaml(namespace, pvcname, capacity, storage_class)
+ pvcyaml, snapname = prepare_backup_clone_yaml(namespace, pvcname, capacity, storage_class, self.jobname)
self._io.send_info(PVCCLONE_YAML_PREPARED_INFO.format(
namespace=namespace,
snapname=snapname,
prev_bacula_pod_name = self._plugin.check_bacula_pod(namespace, self.jobname)
if prev_bacula_pod_name:
logging.debug('Exist previous bacula-backup pod! Name: {}'.format(prev_bacula_pod_name))
- # TODO: Check if we remove or not the bacula-backup pod
- self._io.send_info('Exist a previous bacula-backup pod. Name: {}'.format(prev_bacula_pod_name))
- response = False
- for a in range(self.timeout):
- time.sleep(1)
- response = self._plugin.check_gone_backup_pod(namespace, prev_bacula_pod_name)
- if isinstance(response, dict) and 'error' in response:
- self._handle_error(CANNOT_REMOVE_BACKUP_POD_ERR.format(parse_json_descr(response)))
- return False
- else:
- if response:
- break
- if not response:
- self._handle_error(POD_EXIST_ERR.format(namespace=namespace, podname=prev_bacula_pod_name))
+ self._io.send_info('Exist a previous bacula-backup pod: {}'.format(prev_bacula_pod_name))
+ response = self._plugin.remove_backup_pod(namespace, prev_bacula_pod_name)
+ if isinstance(response, dict) and 'error' in response:
+ self._handle_error(CANNOT_REMOVE_BACKUP_POD_ERR.format(parse_json_descr(response)))
return False
-
+ self._io.send_info('Removed previous bacula-backup pod: {}'.format(prev_bacula_pod_name))
poddata = yaml.safe_load(podyaml)
response = self._plugin.create_backup_pod(namespace, poddata)
if isinstance(response, dict) and 'error' in response:
return False
return True
+ def cleanup_old_cloned_pvc(self, namespace):
+ logging.debug("Starting cleanup the old pvc from other previous job")
+ # List all pvc names from namespace
+ pvc_names = self._plugin.get_pvc_names(namespace)
+ # Search old cloned pvcs
+ old_clone_pvcs = find_bacula_pvc_clones_from_old_job(pvc_names, self.jobname)
+ # Remove old pvcs
+ for old_clone_pvc in old_clone_pvcs:
+ self._io.send_info(f"Exist a previous bacula-backup pvc. Name: {old_clone_pvc}")
+ response = self._plugin.delete_persistent_volume_claim(namespace, old_clone_pvc)
+ logging.debug(f"Response from pvc `{old_clone_pvc}` delete request: {response}")
+ if isinstance(response, dict) and "error" in response:
+ return response
+ self._io.send_info(f"Removed the previous bacula-backup pvc: {old_clone_pvc}")
+ logging.debug("Deleted request launched successfully")
+ logging.debug("Ended cleanup the old pvcs from other previous job")
+
def create_pvcclone(self, namespace, pvcname):
clonename = None
logging.debug("pvcclone for:{}/{}".format(namespace, pvcname))
+ self.cleanup_old_cloned_pvc(namespace)
pvcdata = self._plugin.get_pvcdata_namespaced(namespace, pvcname)
if isinstance(pvcdata, dict) and 'exception' in pvcdata:
self._handle_error(PVCDATA_GET_ERROR.format(parse_json_descr(pvcdata)))
def persistentvolumeclaims_read_namespaced(corev1api, namespace, name):
return corev1api.read_namespaced_persistent_volume_claim(name, namespace)
+def persistentvolumeclaims_namespaced_only_names(corev1api, namespace, labels=""):
+ pvcslist = []
+ pvcs = corev1api.list_namespaced_persistent_volume_claim(namespace=namespace, watch=False, label_selector=labels)
+ for pvc in pvcs.items:
+ pvcslist.append(pvc.metadata.name)
+ return pvcslist
def persistentvolumeclaims_namespaced_names(corev1api, namespace, labels=""):
pvcslist = []
if (pvcdata.status.capacity is not None):
pvcsize = k8s_size_to_int(pvcdata.status.capacity['storage'])
pvcstotalsize += pvcsize
- # logging.debug("PVCDATA-SIZE:{} {}".format(pvcdata.status.capacity['storage'], pvcsize))
- # logging.debug("PVCDATA-ENC:{}".format(spec))
+
pvcslist['pvc-' + pvc.metadata.name] = {
'spec': spec if not estimate else None,
'fi': k8sfileinfo(objtype=K8SObjType.K8SOBJ_PVOLCLAIM, nsname=namespace,
#
import os
-import string
+import json
+import logging
from baculak8s.util.token import generate_token
DEFAULTCLONEYAML = os.getenv('DEFAULTCLONEYAML', "/opt/bacula/scripts/bacula-backup-clone.yaml")
+PREFIX_CLONE_PVC_BACKUP_NAME = "bacula-pvcclone-{job_name}-"
+CLONE_PVC_BACKUP_NAME=PREFIX_CLONE_PVC_BACKUP_NAME + "{job_id}"
+JOB_NAME_MAX_CHARS=25
+JOB_ID_MAX_DIGITS=12
CLONETEMPLATE = """
apiVersion: v1
kind: PersistentVolumeClaim
storage: {pvcsize}
"""
+def find_bacula_pvc_clones_from_old_job(pvc_list, job):
+ name_for_search = PREFIX_CLONE_PVC_BACKUP_NAME.format(job_name=job.split('.')[0][:JOB_NAME_MAX_CHARS].lower())
+ num_hyphen=name_for_search.count('-')
+ old_pvcs = []
+ for pvc_name in pvc_list:
+ if pvc_name.startswith(name_for_search) and num_hyphen == pvc_name.count('-'):
+ old_pvcs.append(pvc_name)
+ logging.debug(f"Found old cloned pvcs: {old_pvcs}")
+ return old_pvcs
-def prepare_backup_clone_yaml(namespace, pvcname, pvcsize, scname, clonename=None):
+def get_clone_pvc_name(job):
+ # Get job name and id, and limit to not exceed 63 characters in pvc name
+ job_name = job.split('.')[0][:JOB_NAME_MAX_CHARS].lower()
+ job_id = job.split(':')[1][:JOB_ID_MAX_DIGITS]
+ return CLONE_PVC_BACKUP_NAME.format(job_name=job_name, job_id=job_id)
+
+def prepare_backup_clone_yaml(namespace, pvcname, pvcsize, scname, jobname, clonename=None):
""" Handles PVC clone yaml preparation based on available templates
Args:
pvcname (str): source pvc name to clone from
pvcsize (str): k8s capacity of the original pvc
scname (str): storage class of the original pvc
+ job (str): Job name to add to cloned pvc name
clonename (str, optional): the cloned - destination - pvcname; if `None` then name will be assigned automatically. Defaults to None.
Returns:
with open(DEFAULTCLONEYAML, 'r') as file:
cloneyaml = file.read()
if clonename is None:
- validchars = tuple(string.ascii_lowercase) + tuple(string.digits)
- clonename = "{pvcname}-baculaclone-{id}".format(pvcname=pvcname, id=generate_token(size=6, chars=validchars))
-
+ clonename = get_clone_pvc_name(jobname)
return cloneyaml.format(namespace=namespace, pvcname=pvcname, pvcsize=pvcsize, clonename=clonename, storageclassname=scname), clonename
+
+def delete_pvcclone(corev1api, namespace, pvc_name, grace_period_seconds=0, propagation_policy='Foreground'):
+ return corev1api.delete_namespaced_persistent_volume_claim(
+ pvc_name, namespace, grace_period_seconds=grace_period_seconds,
+ propagation_policy=propagation_policy)
from baculak8s.plugins.k8sbackend.pods import *
from baculak8s.plugins.k8sbackend.podtemplates import *
from baculak8s.plugins.k8sbackend.pvcdata import *
+from baculak8s.plugins.k8sbackend.pvcclone import delete_pvcclone
from baculak8s.plugins.k8sbackend.replicaset import *
from baculak8s.plugins.k8sbackend.replicationcontroller import *
from baculak8s.plugins.k8sbackend.resourcequota import *
else:
return response # it should be a dictionary with an error
+ def get_pvc_names(self, namespace):
+ return self.__execute(lambda: persistentvolumeclaims_namespaced_only_names(self.corev1api, namespace))
+
def get_podtemplates(self, namespace, estimate=False):
return self.__execute(lambda: podtemplates_list_namespaced(self.corev1api, namespace, estimate,
self.config['labels']))
lambda: self.corev1api.read_namespaced_service_account(k8sfile2objname(file_info.name),
file_info.namespace))
+ def delete_persistent_volume_claim(self, namespace, pvc_name, grace_period_seconds=0, propagation_policy="Foreground"):
+ return self.__execute(lambda: delete_pvcclone(self.corev1api,
+ namespace, pvc_name, grace_period_seconds=grace_period_seconds,
+ propagation_policy=propagation_policy))
+
def check_file(self, file_info):
"""
return {}
def remove_pvcclone(self, namespace, clonename):
- logging.debug('remove_pvcclone `{}`'.format(clonename))
- response = self.__execute(lambda: self.corev1api.delete_namespaced_persistent_volume_claim(
- clonename, namespace, grace_period_seconds=0,
- propagation_policy='Foreground'))
- logging.debug("Response: {}".format(response))
+ logging.debug('Remove pvcclone `{}`'.format(clonename))
+ response = self.delete_persistent_volume_claim(namespace, clonename)
+ logging.debug("Response from delete pvc: {}".format(response))
if isinstance(response, dict) and "error" in response:
return response
r1 = self.get_pvcdata_namespaced(namespace, clonename)