"Fossies" - the Fresh Open Source Software Archive

Member "freezer-10.0.0/freezer/job.py" (14 Apr 2021, 31837 Bytes) of package /linux/misc/openstack/freezer-10.0.0.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "job.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 9.0.0_vs_10.0.0.

    1 # (c) Copyright 2014,2015 Hewlett-Packard Development Company, L.P.
    2 # (C) Copyright 2016 Hewlett Packard Enterprise Development Company LP
    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 
   17 import abc
   18 from concurrent import futures
   19 import datetime
   20 import os
   21 import sys
   22 import time
   23 
   24 from oslo_config import cfg
   25 from oslo_log import log
   26 from oslo_utils import importutils
   27 
   28 from freezer.common import client_manager
   29 from freezer.openstack import admin
   30 from freezer.openstack import backup
   31 from freezer.openstack import restore
   32 from freezer.snapshot import snapshot
   33 from freezer.utils import checksum
   34 from freezer.utils import exec_cmd
   35 from freezer.utils import utils
   36 
   37 CONF = cfg.CONF
   38 LOG = log.getLogger(__name__)
   39 
   40 
   41 class Job(metaclass=abc.ABCMeta):
   42     """
   43     :type storage: freezer.storage.base.Storage
   44     :type engine: freezer.engine.engine.BackupEngine
   45     """
   46 
   47     def __init__(self, conf_dict, storage):
   48         self.conf = conf_dict
   49         self.storage = storage
   50         self.engine = conf_dict.engine
   51         self.client = client_manager.get_client_manager(CONF)
   52         self.nova = self.client.get_nova()
   53         self.cinder = self.client.get_cinder()
   54         self.glance = self.client.get_glance()
   55         self._general_validation()
   56         self._validate()
   57         if self.conf.nova_inst_name:
   58             self.nova_instance_ids = [server.id for server in
   59                                       self.nova.servers.list(detailed=False)
   60                                       if server.name ==
   61                                       self.conf.nova_inst_name]
   62         if self.conf.cinder_vol_name:
   63             self.cinder_vol_ids = [volume.id for volume in
   64                                    self.cinder.volumes.list()
   65                                    if volume.name ==
   66                                    self.conf.cinder_inst_name]
   67 
   68         if self.conf.glance_image_name:
   69             self.glance_image_ids = [image.id for image in
   70                                      self.glance.images.list()
   71                                      if image.name ==
   72                                      self.conf.glance_image_name]
   73 
   74         if self.conf.glance_image_name_filter:
   75             self.glance_image_ids = [image.id for image in
   76                                      self.glance.images.list()
   77                                      if self.conf.glance_image_name_filter
   78                                      not in image.name]
   79 
   80     @abc.abstractmethod
   81     def _validate(self):
   82         """
   83         Method that validates if all arguments available to execute the job
   84         or not
   85         :return: True or raise an error
   86         """
   87         pass
   88 
   89     def _general_validation(self):
   90         """
   91         Apply general validation rules.
   92         :return: True or raise an error
   93         """
   94         LOG.info("Validating args for the {0} job.".format(self.conf.action))
   95         if not self.conf.action:
   96             raise ValueError("Please provide a valid action with --action")
   97 
   98         if self.conf.action in ('backup', 'restore', 'admin') \
   99                 and self.conf.backup_media == 'fs' \
  100                 and not self.conf.backup_name:
  101             raise ValueError('A value for --backup-name is required')
  102 
  103     @abc.abstractmethod
  104     def execute(self):
  105         pass
  106 
  107 
  108 class InfoJob(Job):
  109 
  110     def _validate(self):
  111         # no validation required for this job
  112         pass
  113 
  114     def execute(self):
  115         info = self.storage.info()
  116         if not info:
  117             return
  118         fields = ["Container", "Size", "Object Count"]
  119         data = []
  120         for container in info:
  121             if self.conf.container:
  122                 container_name = container.get('container_name')
  123                 if container_name != self.conf.container:
  124                     continue
  125 
  126             values = [
  127                 container.get('container_name'),
  128                 container.get('size'),
  129                 container.get('objects_count')
  130             ]
  131             data.append(values)
  132 
  133             if self.conf.container:
  134                 break  # values for given container were found
  135         return [fields, data]
  136 
  137 
  138 class BackupJob(Job):
  139 
  140     def _validate(self):
  141         if self.conf.mode == 'fs':
  142             if not self.conf.path_to_backup:
  143                 raise ValueError('path-to-backup argument must be provided')
  144             if self.conf.no_incremental and (self.conf.max_level or
  145                                              self.conf.always_level):
  146                 raise Exception(
  147                     'no-incremental option is not compatible '
  148                     'with backup level options')
  149         elif self.conf.mode == 'nova':
  150             if not self.conf.no_incremental:
  151                 raise ValueError("Incremental nova backup is not supported")
  152 
  153             if not self.conf.nova_inst_id and not self.conf.project_id \
  154                     and not self.conf.nova_inst_name:
  155                 raise ValueError("nova-inst-id or project-id or nova-inst-name"
  156                                  " argument must be provided")
  157         elif self.conf.mode == 'glance':
  158             if not self.conf.no_incremental:
  159                 raise ValueError("Incremental glance backup is not supported")
  160 
  161             if not self.conf.glance_image_id and not self.conf.project_id \
  162                     and not self.conf.glance_image_name \
  163                     and not self.conf.glance_image_name_filter:
  164                 raise ValueError("glance-image-id or project-id or"
  165                                  " glance-image-name or "
  166                                  " glance-image-name_filter "
  167                                  "argument must be provided")
  168 
  169         elif self.conf.mode == 'cinder':
  170             if not self.conf.cinder_vol_id and not self.conf.cinder_vol_name:
  171                 raise ValueError("cinder-vol-id or cinder-vol-name argument "
  172                                  "must be provided")
  173 
  174         elif self.conf.mode == "cindernative":
  175             if not self.conf.cindernative_vol_id:
  176                 raise ValueError("cindernative-vol-id"
  177                                  " argument must be provided")
  178         else:
  179             pass
  180 
  181     def execute(self):
  182         LOG.info('Backup job started. '
  183                  'backup_name: {0}, container: {1}, hostname: {2}, mode: {3},'
  184                  ' Storage: {4}, compression: {5}'
  185                  .format(self.conf.backup_name, self.conf.container,
  186                          self.conf.hostname, self.conf.mode, self.conf.storage,
  187                          self.conf.compression))
  188         try:
  189             if self.conf.mode == 'fs' and self.conf.sync:
  190                 LOG.info('Executing sync to flush the file system buffer.')
  191                 (out, err) = utils.create_subprocess('sync')
  192                 if err:
  193                     LOG.error('Error while sync exec: {0}'.format(err))
  194         except Exception as error:
  195             LOG.error('Error while sync exec: {0}'.format(error))
  196 
  197         mod_name = 'freezer.mode.{0}.{1}'.format(
  198             self.conf.mode, self.conf.mode.capitalize() + 'Mode')
  199         app_mode = importutils.import_object(mod_name, self.conf)
  200         backup_level = self.backup(app_mode)
  201         level = backup_level or 0
  202 
  203         metadata = {
  204             'curr_backup_level': level,
  205             'fs_real_path': self.conf.path_to_backup,
  206             'vol_snap_path': self.conf.path_to_backup,
  207             'client_os': sys.platform,
  208             'client_version': self.conf.__version__,
  209             'time_stamp': self.conf.time_stamp,
  210         }
  211         fields = ['action',
  212                   'always_level',
  213                   'backup_media',
  214                   'backup_name',
  215                   'container',
  216                   'container_segments',
  217                   'dry_run',
  218                   'hostname',
  219                   'path_to_backup',
  220                   'max_level',
  221                   'mode',
  222                   'backup_name',
  223                   'time_stamp',
  224                   'log_file',
  225                   'storage',
  226                   'mode',
  227                   'proxy',
  228                   'compression',
  229                   'ssh_key',
  230                   'ssh_username',
  231                   'ssh_host',
  232                   'ssh_port',
  233                   'consistency_checksum'
  234                   ]
  235         for field_name in fields:
  236             metadata[field_name] = self.conf.__dict__.get(field_name, '') or ''
  237         return metadata
  238 
  239     def backup(self, app_mode):
  240         """
  241 
  242         :type app_mode: freezer.mode.mode.Mode
  243         :return:
  244         """
  245         backup_media = self.conf.backup_media
  246 
  247         time_stamp = utils.DateTime.now().timestamp
  248         self.conf.time_stamp = time_stamp
  249 
  250         if backup_media == 'fs':
  251             LOG.info('Path to backup: {0}'.format(self.conf.path_to_backup))
  252             app_mode.prepare()
  253             snapshot_taken = snapshot.snapshot_create(self.conf)
  254             if snapshot_taken:
  255                 app_mode.release()
  256             try:
  257                 filepath = '.'
  258                 chdir_path = os.path.expanduser(
  259                     os.path.normpath(self.conf.path_to_backup.strip()))
  260                 if not os.path.exists(chdir_path):
  261                     msg = 'Path to backup does not exist {0}'.format(
  262                         chdir_path)
  263                     LOG.critical(msg)
  264                     raise IOError(msg)
  265                 if not os.path.isdir(chdir_path):
  266                     filepath = os.path.basename(chdir_path)
  267                     chdir_path = os.path.dirname(chdir_path)
  268                 os.chdir(chdir_path)
  269 
  270                 # Checksum for Backup Consistency
  271                 if self.conf.consistency_check:
  272                     ignorelinks = (self.conf.dereference_symlink is None or
  273                                    self.conf.dereference_symlink == 'hard')
  274                     consistency_checksum = checksum.CheckSum(
  275                         filepath, ignorelinks=ignorelinks).compute()
  276                     LOG.info('Computed checksum for consistency {0}'.
  277                              format(consistency_checksum))
  278                     self.conf.consistency_checksum = consistency_checksum
  279 
  280                 return self.engine.backup(
  281                     backup_resource=filepath,
  282                     hostname_backup_name=self.conf.hostname_backup_name,
  283                     no_incremental=self.conf.no_incremental,
  284                     max_level=self.conf.max_level,
  285                     always_level=self.conf.always_level,
  286                     restart_always_level=self.conf.restart_always_level)
  287 
  288             finally:
  289                 # whether an error occurred or not, remove the snapshot anyway
  290                 app_mode.release()
  291                 if snapshot_taken:
  292                     snapshot.snapshot_remove(
  293                         self.conf, self.conf.shadow,
  294                         self.conf.windows_volume)
  295 
  296         elif backup_media == 'nova':
  297             if self.conf.project_id:
  298                 return self.engine.backup_nova_tenant(
  299                     project_id=self.conf.project_id,
  300                     hostname_backup_name=self.conf.hostname_backup_name,
  301                     no_incremental=self.conf.no_incremental,
  302                     max_level=self.conf.max_level,
  303                     always_level=self.conf.always_level,
  304                     restart_always_level=self.conf.restart_always_level)
  305 
  306             elif self.conf.nova_inst_id:
  307                 LOG.info('Executing nova backup. Instance ID: {0}'.format(
  308                     self.conf.nova_inst_id))
  309 
  310                 hostname_backup_name = os.path.join(
  311                     self.conf.hostname_backup_name,
  312                     self.conf.nova_inst_id)
  313                 return self.engine.backup(
  314                     backup_resource=self.conf.nova_inst_id,
  315                     hostname_backup_name=hostname_backup_name,
  316                     no_incremental=self.conf.no_incremental,
  317                     max_level=self.conf.max_level,
  318                     always_level=self.conf.always_level,
  319                     restart_always_level=self.conf.restart_always_level)
  320 
  321             else:
  322                 executor = futures.ThreadPoolExecutor(
  323                     max_workers=len(self.nova_instance_ids))
  324                 futures_list = []
  325                 for instance_id in self.nova_instance_ids:
  326                     hostname_backup_name = os.path.join(
  327                         self.conf.hostname_backup_name, instance_id)
  328                     futures_list.append(executor.submit(
  329                         self.engine.backup(
  330                             backup_resource=instance_id,
  331                             hostname_backup_name=hostname_backup_name,
  332                             no_incremental=self.conf.no_incremental,
  333                             max_level=self.conf.max_level,
  334                             always_level=self.conf.always_level,
  335                             restart_always_level=self.conf.restart_always_level
  336                         )))
  337 
  338                 futures.wait(futures_list, CONF.timeout)
  339 
  340         elif backup_media == 'glance':
  341             if self.conf.project_id:
  342                 return self.engine.backup_glance_tenant(
  343                     project_id=self.conf.project_id,
  344                     hostname_backup_name=self.conf.hostname_backup_name,
  345                     no_incremental=self.conf.no_incremental,
  346                     max_level=self.conf.max_level,
  347                     always_level=self.conf.always_level,
  348                     restart_always_level=self.conf.restart_always_level)
  349 
  350             elif self.conf.glance_image_id:
  351                 LOG.info('Executing glance backup. Image ID: {0}'.format(
  352                     self.conf.glance_image_id))
  353 
  354                 hostname_backup_name = os.path.join(
  355                     self.conf.hostname_backup_name,
  356                     self.conf.glance_image_id)
  357                 return self.engine.backup(
  358                     backup_resource=self.conf.glance_image_id,
  359                     hostname_backup_name=hostname_backup_name,
  360                     no_incremental=self.conf.no_incremental,
  361                     max_level=self.conf.max_level,
  362                     always_level=self.conf.always_level,
  363                     restart_always_level=self.conf.restart_always_level)
  364 
  365             else:
  366                 executor = futures.ThreadPoolExecutor(
  367                     max_workers=len(self.glance_image_ids))
  368                 futures_list = []
  369                 for image_id in self.glance_image_ids:
  370                     hostname_backup_name = os.path.join(
  371                         self.conf.hostname_backup_name, image_id)
  372                     futures_list.append(executor.submit(
  373                         self.engine.backup(
  374                             backup_resource=image_id,
  375                             hostname_backup_name=hostname_backup_name,
  376                             no_incremental=self.conf.no_incremental,
  377                             max_level=self.conf.max_level,
  378                             always_level=self.conf.always_level,
  379                             restart_always_level=self.conf.restart_always_level
  380                         )))
  381 
  382                 futures.wait(futures_list, CONF.timeout)
  383 
  384         elif backup_media == 'cindernative':
  385             backup_os = backup.BackupOs(self.conf.client_manager,
  386                                         self.conf.container,
  387                                         self.storage)
  388             LOG.info('Executing cinder native backup. Volume ID: {0}, '
  389                      'incremental: {1}'.format(self.conf.cindernative_vol_id,
  390                                                self.conf.incremental))
  391             backup_os.backup_cinder(self.conf.cindernative_vol_id,
  392                                     name=self.conf.backup_name,
  393                                     incremental=self.conf.incremental)
  394         elif backup_media == 'cinder':
  395             backup_os = backup.BackupOs(self.conf.client_manager,
  396                                         self.conf.container,
  397                                         self.storage)
  398             if self.conf.cinder_vol_id:
  399                 LOG.info('Executing cinder snapshot. Volume ID: {0}'.format(
  400                     self.conf.cinder_vol_id))
  401                 backup_os.backup_cinder_by_glance(self.conf.cinder_vol_id)
  402             else:
  403                 executor = futures.ThreadPoolExecutor(
  404                     max_workers=len(self.cinder_vol_ids))
  405                 futures_list = []
  406                 for instance_id in self.cinder_vol_ids:
  407                     LOG.info('Executing cinder snapshot. Volume ID:'
  408                              ' {0}'.format(instance_id))
  409                     futures_list.append(executor.submit(
  410                         backup_os.backup_cinder_by_glance(instance_id)))
  411 
  412                 futures.wait(futures_list, CONF.timeout)
  413 
  414         elif backup_media == 'cinderbrick':
  415             LOG.info('Executing cinder volume backup using os-brick. '
  416                      'Volume ID: {0}'.format(self.conf.cinderbrick_vol_id))
  417             return self.engine.backup(
  418                 backup_resource=self.conf.cinderbrick_vol_id,
  419                 hostname_backup_name=self.conf.hostname_backup_name,
  420                 no_incremental=self.conf.no_incremental,
  421                 max_level=self.conf.max_level,
  422                 always_level=self.conf.always_level,
  423                 restart_always_level=self.conf.restart_always_level)
  424         else:
  425             raise Exception('unknown parameter backup_media %s' % backup_media)
  426         return None
  427 
  428 
  429 class RestoreJob(Job):
  430 
  431     def _validate(self):
  432         if not any([self.conf.restore_abs_path,
  433                     self.conf.nova_inst_id,
  434                     self.conf.nova_inst_name,
  435                     self.conf.glance_image_id,
  436                     self.conf.cinder_vol_id,
  437                     self.conf.cinder_vol_name,
  438                     self.conf.cindernative_vol_id,
  439                     self.conf.cinderbrick_vol_id,
  440                     self.conf.glance_image_name,
  441                     self.conf.glance_image_name_filter,
  442                     self.conf.project_id]):
  443             raise ValueError("--restore-abs-path is required")
  444         if not self.conf.container:
  445             raise ValueError("--container is required")
  446         if self.conf.no_incremental and (self.conf.max_level or
  447                                          self.conf.always_level):
  448             raise Exception(
  449                 'no-incremental option is not compatible '
  450                 'with backup level options')
  451 
  452     def execute(self):
  453         conf = self.conf
  454         LOG.info('Executing Restore...')
  455         restore_timestamp = None
  456 
  457         restore_abs_path = conf.restore_abs_path
  458         if conf.restore_from_date:
  459             restore_timestamp = utils.date_to_timestamp(conf.restore_from_date)
  460         if conf.backup_media == 'fs':
  461             self.engine.restore(
  462                 hostname_backup_name=self.conf.hostname_backup_name,
  463                 restore_resource=restore_abs_path,
  464                 overwrite=conf.overwrite,
  465                 recent_to_date=restore_timestamp,
  466                 backup_media=conf.mode)
  467 
  468             try:
  469                 if conf.consistency_checksum:
  470                     backup_checksum = conf.consistency_checksum
  471                     restore_checksum = checksum.CheckSum(restore_abs_path,
  472                                                          ignorelinks=True)
  473                     if restore_checksum.compare(backup_checksum):
  474                         LOG.info('Consistency check success.')
  475                     else:
  476                         raise ConsistencyCheckException(
  477                             "Backup Consistency Check failed: backup checksum "
  478                             "({0}) and restore checksum ({1}) did not match.".
  479                             format(backup_checksum, restore_checksum.checksum))
  480             except OSError as e:
  481                 raise ConsistencyCheckException(
  482                     "Backup Consistency Check failed: could not checksum file"
  483                     " {0} ({1})".format(e.filename, e.strerror))
  484             return {}
  485 
  486         elif conf.backup_media == 'nova':
  487             if self.conf.project_id:
  488                 return self.engine.restore_nova_tenant(
  489                     project_id=self.conf.project_id,
  490                     hostname_backup_name=self.conf.hostname_backup_name,
  491                     overwrite=conf.overwrite,
  492                     recent_to_date=restore_timestamp)
  493 
  494             elif conf.nova_inst_id:
  495                 LOG.info("Restoring nova backup. Instance ID: {0}, "
  496                          "timestamp: {1} network-id {2}".format(
  497                              conf.nova_inst_id,
  498                              restore_timestamp,
  499                              conf.nova_restore_network))
  500                 hostname_backup_name = os.path.join(
  501                     self.conf.hostname_backup_name,
  502                     self.conf.nova_inst_id)
  503                 self.engine.restore(
  504                     hostname_backup_name=hostname_backup_name,
  505                     restore_resource=conf.nova_inst_id,
  506                     overwrite=conf.overwrite,
  507                     recent_to_date=restore_timestamp,
  508                     backup_media=conf.mode)
  509 
  510             else:
  511                 for instance_id in self.nova_instance_ids:
  512                     LOG.info("Restoring nova backup. Instance ID: {0}, "
  513                              "timestamp: {1} network-id {2}".format(
  514                                  instance_id, restore_timestamp,
  515                                  conf.nova_restore_network))
  516                     hostname_backup_name = os.path.join(
  517                         self.conf.hostname_backup_name, instance_id)
  518                     self.engine.restore(
  519                         hostname_backup_name=hostname_backup_name,
  520                         restore_resource=instance_id,
  521                         overwrite=conf.overwrite,
  522                         recent_to_date=restore_timestamp,
  523                         backup_media=conf.mode)
  524 
  525         elif conf.backup_media == 'glance':
  526             if self.conf.project_id:
  527                 return self.engine.restore_glance_tenant(
  528                     project_id=self.conf.project_id,
  529                     hostname_backup_name=self.conf.hostname_backup_name,
  530                     overwrite=conf.overwrite,
  531                     recent_to_date=restore_timestamp)
  532 
  533             elif conf.glance_image_id:
  534                 LOG.info("Restoring glance backup. Image ID: {0}, "
  535                          "timestamp: {1} ".format(conf.glance_image_id,
  536                                                   restore_timestamp))
  537                 hostname_backup_name = os.path.join(
  538                     self.conf.hostname_backup_name,
  539                     self.conf.glance_image_id)
  540                 self.engine.restore(
  541                     hostname_backup_name=hostname_backup_name,
  542                     restore_resource=conf.glance_image_id,
  543                     overwrite=conf.overwrite,
  544                     recent_to_date=restore_timestamp,
  545                     backup_media=conf.mode)
  546 
  547             else:
  548                 for image_id in self.glance_image_ids:
  549                     LOG.info("Restoring glance backup. Image ID: {0}, "
  550                              "timestamp: {1}".format(image_id,
  551                                                      restore_timestamp))
  552                     hostname_backup_name = os.path.join(
  553                         self.conf.hostname_backup_name, image_id)
  554                     self.engine.restore(
  555                         hostname_backup_name=hostname_backup_name,
  556                         restore_resource=image_id,
  557                         overwrite=conf.overwrite,
  558                         recent_to_date=restore_timestamp,
  559                         backup_media=conf.mode)
  560 
  561         elif conf.backup_media == 'cinder':
  562             res = restore.RestoreOs(conf.client_manager, conf.container,
  563                                     self.storage)
  564             if conf.cinder_vol_id:
  565                 LOG.info("Restoring cinder backup from glance. "
  566                          "Volume ID: {0}, timestamp: {1}".format(
  567                              conf.cinder_vol_id,
  568                              restore_timestamp))
  569                 res.restore_cinder_by_glance(conf.cinder_vol_id,
  570                                              restore_timestamp)
  571             else:
  572                 for instance_id in self.cinder_vol_ids:
  573                     LOG.info("Restoring cinder backup from glance. "
  574                              "Volume ID: {0}, timestamp: {1}".format(
  575                                  instance_id, restore_timestamp))
  576                     res.restore_cinder_by_glance(instance_id,
  577                                                  restore_timestamp)
  578         elif conf.backup_media == 'cindernative':
  579             res = restore.RestoreOs(conf.client_manager, conf.container,
  580                                     self.storage)
  581             LOG.info("Restoring cinder native backup. Volume ID {0}, Backup ID"
  582                      " {1}, timestamp: {2}".format(conf.cindernative_vol_id,
  583                                                    conf.cindernative_backup_id,
  584                                                    restore_timestamp))
  585             res.restore_cinder(conf.cindernative_vol_id,
  586                                conf.cindernative_backup_id,
  587                                restore_timestamp)
  588         elif conf.backup_media == 'cinderbrick':
  589             LOG.info("Restoring cinder backup using os-brick. Volume ID {0}, "
  590                      "timestamp: {1}".format(conf.cinderbrick_vol_id,
  591                                              restore_timestamp))
  592             self.engine.restore(
  593                 hostname_backup_name=self.conf.hostname_backup_name,
  594                 restore_resource=conf.cinderbrick_vol_id,
  595                 overwrite=conf.overwrite,
  596                 recent_to_date=restore_timestamp,
  597                 backup_media=conf.mode)
  598         else:
  599             raise Exception("unknown backup type: %s" % conf.backup_media)
  600         return {}
  601 
  602 
  603 class AdminJob(Job):
  604 
  605     def _validate(self):
  606         # no validation required in this job
  607         if self.conf.backup_media == 'cindernative':
  608             if not self.conf.fullbackup_rotation:
  609                 raise Exception("The parameter --fullbackup-rotation "
  610                                 "is required")
  611         elif not (self.conf.remove_from_date or self.conf.remove_older_than):
  612             raise ValueError("You need to provide to remove backup older "
  613                              "than this time. You can use --remove-older-than "
  614                              "or --remove-from-date")
  615 
  616     def execute(self):
  617         # remove backups by freezer admin action
  618         backup_media = self.conf.backup_media
  619         if backup_media == 'cindernative':
  620             admin_os = admin.AdminOs(self.conf.client_manager)
  621             admin_os.del_off_limit_fullbackup(
  622                 self.conf.cindernative_vol_id,
  623                 self.conf.fullbackup_rotation)
  624             return {}
  625         if self.conf.remove_from_date:
  626             timestamp = utils.date_to_timestamp(self.conf.remove_from_date)
  627         else:
  628             timestamp = datetime.datetime.now() - \
  629                 datetime.timedelta(days=float(self.conf.remove_older_than))
  630             timestamp = int(time.mktime(timestamp.timetuple()))
  631 
  632         if self.conf.backup_media == 'cinder':
  633             if self.conf.cinder_vol_id:
  634                 old_backups = self.get_cinder_old_backups(
  635                     timestamp,
  636                     self.conf.cinder_vol_id
  637                 )
  638                 self.remove_backup_dirs(old_backups,
  639                                         self.conf.cinder_vol_id)
  640                 return {}
  641 
  642             else:
  643                 for instance_id in self.cinder_vol_ids:
  644                     old_backups = self.get_cinder_old_backups(
  645                         timestamp,
  646                         instance_id
  647                     )
  648                     self.remove_backup_dirs(old_backups,
  649                                             instance_id)
  650                     return {}
  651         hostname_backup_name_set = set()
  652 
  653         if self.conf.backup_media == 'nova':
  654             if self.conf.project_id:
  655                 instance_ids = self.engine.get_nova_tenant(
  656                     self.conf.project_id)
  657                 for instance_id in instance_ids:
  658                     hostname_backup_name = os.path.join(
  659                         self.conf.hostname_backup_name, instance_id)
  660                     hostname_backup_name_set.add(hostname_backup_name)
  661 
  662             elif self.conf.nova_inst_id:
  663                 hostname_backup_name = os.path.join(
  664                     self.conf.hostname_backup_name,
  665                     self.conf.nova_inst_id)
  666                 hostname_backup_name_set.add(hostname_backup_name)
  667 
  668             else:
  669                 for instance_id in self.nova_instance_ids:
  670                     hostname_backup_name = os.path.join(
  671                         self.conf.hostname_backup_name,
  672                         instance_id)
  673                     hostname_backup_name_set.add(hostname_backup_name)
  674         else:
  675             hostname_backup_name_set.add(self.conf.hostname_backup_name)
  676 
  677         for backup_name in hostname_backup_name_set:
  678             self.storage.remove_older_than(self.engine,
  679                                            timestamp,
  680                                            backup_name)
  681         return {}
  682 
  683     def get_cinder_old_backups(self, timestamp, cinder_vol_id):
  684         path_to_list = self.get_path_prefix(cinder_vol_id)
  685 
  686         old_backups = []
  687         backup_dirs = self.storage.listdir(path_to_list)
  688         for backup_dir in backup_dirs:
  689             if int(backup_dir) <= int(timestamp):
  690                 old_backups.append(backup_dir)
  691 
  692         return old_backups
  693 
  694     def remove_backup_dirs(self, backups_to_remove, cinder_vol_id):
  695         path_prefix = self.get_path_prefix(cinder_vol_id)
  696         for backup_to_remove in backups_to_remove:
  697             path_to_remove = "{0}/{1}".format(path_prefix, backup_to_remove)
  698             LOG.info("Remove backup: {0}".format(path_to_remove))
  699             self.storage.rmtree(path_to_remove)
  700 
  701     def get_path_prefix(self, cinder_vol_id):
  702         if self.storage.type == 'swift':
  703             path_prefix = "{0}/{1}/{2}".format(
  704                 self.storage.container,
  705                 self.storage.segments,
  706                 cinder_vol_id
  707             )
  708         elif self.storage.type in \
  709                 ['local', 'ssh', 's3', 'ftp', 'ftps']:
  710             path_prefix = "{0}/{1}".format(
  711                 self.storage.storage_path,
  712                 cinder_vol_id
  713             )
  714         else:
  715             path_prefix = ''
  716         return path_prefix
  717 
  718 
  719 class ExecJob(Job):
  720 
  721     def _validate(self):
  722         if not self.conf.command:
  723             raise ValueError("--command option is required")
  724 
  725     def execute(self):
  726         if self.conf.command:
  727             LOG.info('Executing exec job. Command: {0}'
  728                      .format(self.conf.command))
  729             exec_cmd.execute(self.conf.command)
  730         else:
  731             LOG.warning(
  732                 'No command info options were set. Exiting.')
  733         return {}
  734 
  735 
  736 class ConsistencyCheckException(Exception):
  737     pass