diff --git a/freezer/common/config.py b/freezer/common/config.py index d9bf3b76..341cf881 100644 --- a/freezer/common/config.py +++ b/freezer/common/config.py @@ -507,6 +507,15 @@ _COMMON = [ "this time it will raise a TimeOut Exception. Default is" " {0}".format(DEFAULT_PARAMS['timeout']) ), + cfg.IntOpt('fullbackup-rotation', + dest='fullbackup_rotation', + default=1, + min=1, + help="Keep the last N fullbackups of cinder-volume, " + "the parameter should be greater than 0. " + "If set action to admin and set the parameter, " + "it should keep the last N fullbackups, " + "other backups should be deleted"), ] diff --git a/freezer/job.py b/freezer/job.py index 90accbd1..ea453e26 100644 --- a/freezer/job.py +++ b/freezer/job.py @@ -27,6 +27,7 @@ from oslo_log import log from oslo_utils import importutils import six +from freezer.openstack import admin from freezer.openstack import backup from freezer.openstack import restore from freezer.snapshot import snapshot @@ -408,12 +409,24 @@ class AdminJob(Job): def _validate(self): # no validation required in this job - if not self.conf.remove_from_date and not self.conf.remove_older_than: + if self.conf.backup_media == 'cindernative': + if not self.conf.fullbackup_rotation: + raise Exception("The parameter --fullbackup-rotation " + "is required") + elif not (self.conf.remove_from_date or self.conf.remove_older_than): raise ValueError("You need to provide to remove backup older " "than this time. You can use --remove-older-than " "or --remove-from-date") def execute(self): + # remove backups by freezer admin action + backup_media = self.conf.backup_media + if backup_media == 'cindernative': + admin_os = admin.AdminOs(self.conf.client_manager) + admin_os.del_off_limit_fullbackup( + self.conf.cindernative_vol_id, + self.conf.fullbackup_rotation) + return {} if self.conf.remove_from_date: timestamp = utils.date_to_timestamp(self.conf.remove_from_date) else: diff --git a/freezer/openstack/admin.py b/freezer/openstack/admin.py new file mode 100644 index 00000000..9af194f0 --- /dev/null +++ b/freezer/openstack/admin.py @@ -0,0 +1,98 @@ +""" +(c) Copyright 2014,2015 Hewlett-Packard Development Company, L.P. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Freezer Admin modes related functions +""" + +import time + +from oslo_config import cfg +from oslo_log import log +from oslo_service import loopingcall + +CONF = cfg.CONF +LOG = log.getLogger(__name__) + + +class AdminOs(object): + def __init__(self, client_manager): + """ + :param client_manager: + :return: + """ + self.client_manager = client_manager + self.cinder_client = self.client_manager.get_cinder() + + def del_cinderbackup_and_dependend_incremental(self, backup_id): + """ + :param backup_id: backup_id of cinder volume + :return: + """ + cinder_client = self.cinder_client + search_opts = { + 'parent_id': backup_id + } + backups = cinder_client.backups.list(search_opts=search_opts) + if backups: + for backup in backups: + self.del_cinderbackup_and_dependend_incremental(backup.id) + LOG.info("preparing to delete backup %s", backup_id) + cinder_client.backups.delete(backup_id) + + start_time = int(time.time()) + + def wait_del_backup(): + timeout = 120 + del_backup = cinder_client.backups.list( + search_opts={'id': backup_id}) + if len(del_backup) == 0: + LOG.info("Delete backup %s complete" % backup_id) + raise loopingcall.LoopingCallDone() + if del_backup[0].status in ['error', 'error_deleting']: + raise Exception("Delete backup %s failed, " + "the status of backup is %s." + % (backup_id, del_backup[0].status)) + if (del_backup[0].status == 'deleting') and (int(time.time()) - + start_time > timeout): + LOG.error("Delete backup %s failed, In a state of" + "deleting over 120s") + raise + timer = loopingcall.FixedIntervalLoopingCall(wait_del_backup) + timer.start(interval=0.5).wait() + + def del_off_limit_fullbackup(self, volume_id, keep_number): + """ + :param volume_id: id of Volume + :param keep_number: int keep number of fullbackup + :return: + """ + cinder_client = self.cinder_client + search_opts = { + 'volume_id': volume_id, + 'status': 'available' + } + backups = cinder_client.backups.list(search_opts=search_opts, + sort='created_at:asc') + # Filter fullbackup + fullbackups = [backup for backup in backups + if not backup.is_incremental] + if len(fullbackups) <= keep_number: + LOG.info("The numbers of %s fullbackup is %d," + "but keep-number-of-fullbackup is %d," + "don't need delete old backups." + % (volume_id, len(fullbackups), keep_number)) + return + for fullbackup in fullbackups[:-keep_number]: + self.del_cinderbackup_and_dependend_incremental(fullbackup.id) diff --git a/requirements.txt b/requirements.txt index 05576836..c9c352a3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,6 +16,7 @@ oslo.log>=3.30.0 # Apache-2.0 oslo.config!=4.3.0,!=4.4.0,>=4.0.0 # Apache-2.0 keystoneauth1>=3.1.0 # Apache-2.0 os-brick>=1.15.2 # Apache-2.0 +oslo.service>=1.10.0 # Apache-2.0 pycrypto>=2.6 # Public Domain PyMySQL>=0.7.6 # MIT License