Statistics
| Branch: | Tag: | Revision:

root / lib / cmdlib / backup.py @ 809a055b

History | View | Annotate | Download (21.9 kB)

1 7ecd5e87 Thomas Thrainer
#
2 7ecd5e87 Thomas Thrainer
#
3 7ecd5e87 Thomas Thrainer
4 363f43eb Hrvoje Ribicic
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 Google Inc.
5 7ecd5e87 Thomas Thrainer
#
6 7ecd5e87 Thomas Thrainer
# This program is free software; you can redistribute it and/or modify
7 7ecd5e87 Thomas Thrainer
# it under the terms of the GNU General Public License as published by
8 7ecd5e87 Thomas Thrainer
# the Free Software Foundation; either version 2 of the License, or
9 7ecd5e87 Thomas Thrainer
# (at your option) any later version.
10 7ecd5e87 Thomas Thrainer
#
11 7ecd5e87 Thomas Thrainer
# This program is distributed in the hope that it will be useful, but
12 7ecd5e87 Thomas Thrainer
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 7ecd5e87 Thomas Thrainer
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 7ecd5e87 Thomas Thrainer
# General Public License for more details.
15 7ecd5e87 Thomas Thrainer
#
16 7ecd5e87 Thomas Thrainer
# You should have received a copy of the GNU General Public License
17 7ecd5e87 Thomas Thrainer
# along with this program; if not, write to the Free Software
18 7ecd5e87 Thomas Thrainer
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 7ecd5e87 Thomas Thrainer
# 02110-1301, USA.
20 7ecd5e87 Thomas Thrainer
21 7ecd5e87 Thomas Thrainer
22 7ecd5e87 Thomas Thrainer
"""Logical units dealing with backup operations."""
23 7ecd5e87 Thomas Thrainer
24 7ecd5e87 Thomas Thrainer
import OpenSSL
25 7ecd5e87 Thomas Thrainer
import logging
26 7ecd5e87 Thomas Thrainer
27 7ecd5e87 Thomas Thrainer
from ganeti import compat
28 7ecd5e87 Thomas Thrainer
from ganeti import constants
29 7ecd5e87 Thomas Thrainer
from ganeti import errors
30 7ecd5e87 Thomas Thrainer
from ganeti import locking
31 7ecd5e87 Thomas Thrainer
from ganeti import masterd
32 7ecd5e87 Thomas Thrainer
from ganeti import utils
33 f7b0366c Hrvoje Ribicic
from ganeti.utils import retry
34 7ecd5e87 Thomas Thrainer
35 64981f25 Helga Velroyen
from ganeti.cmdlib.base import NoHooksLU, LogicalUnit
36 f7b0366c Hrvoje Ribicic
from ganeti.cmdlib.common import CheckNodeOnline, ExpandNodeUuidAndName, \
37 38519f22 Jose A. Lopes
  IsInstanceRunning, DetermineImageSize
38 5eacbcae Thomas Thrainer
from ganeti.cmdlib.instance_storage import StartInstanceDisks, \
39 f7b0366c Hrvoje Ribicic
  ShutdownInstanceDisks, TemporaryDisk, ImageDisks
40 5eacbcae Thomas Thrainer
from ganeti.cmdlib.instance_utils import GetClusterDomainSecret, \
41 047f59ce Hrvoje Ribicic
  BuildInstanceHookEnvByObject, CheckNodeNotDrained, RemoveInstance, \
42 047f59ce Hrvoje Ribicic
  CheckCompressionTool
43 7ecd5e87 Thomas Thrainer
44 7ecd5e87 Thomas Thrainer
45 7ecd5e87 Thomas Thrainer
class LUBackupPrepare(NoHooksLU):
46 7ecd5e87 Thomas Thrainer
  """Prepares an instance for an export and returns useful information.
47 7ecd5e87 Thomas Thrainer

48 7ecd5e87 Thomas Thrainer
  """
49 7ecd5e87 Thomas Thrainer
  REQ_BGL = False
50 7ecd5e87 Thomas Thrainer
51 7ecd5e87 Thomas Thrainer
  def ExpandNames(self):
52 7ecd5e87 Thomas Thrainer
    self._ExpandAndLockInstance()
53 7ecd5e87 Thomas Thrainer
54 7ecd5e87 Thomas Thrainer
  def CheckPrereq(self):
55 7ecd5e87 Thomas Thrainer
    """Check prerequisites.
56 7ecd5e87 Thomas Thrainer

57 7ecd5e87 Thomas Thrainer
    """
58 da4a52a3 Thomas Thrainer
    self.instance = self.cfg.GetInstanceInfoByName(self.op.instance_name)
59 7ecd5e87 Thomas Thrainer
    assert self.instance is not None, \
60 7ecd5e87 Thomas Thrainer
          "Cannot retrieve locked instance %s" % self.op.instance_name
61 5eacbcae Thomas Thrainer
    CheckNodeOnline(self, self.instance.primary_node)
62 7ecd5e87 Thomas Thrainer
63 5eacbcae Thomas Thrainer
    self._cds = GetClusterDomainSecret()
64 7ecd5e87 Thomas Thrainer
65 7ecd5e87 Thomas Thrainer
  def Exec(self, feedback_fn):
66 7ecd5e87 Thomas Thrainer
    """Prepares an instance for an export.
67 7ecd5e87 Thomas Thrainer

68 7ecd5e87 Thomas Thrainer
    """
69 7ecd5e87 Thomas Thrainer
    if self.op.mode == constants.EXPORT_MODE_REMOTE:
70 7ecd5e87 Thomas Thrainer
      salt = utils.GenerateSecret(8)
71 7ecd5e87 Thomas Thrainer
72 1c3231aa Thomas Thrainer
      feedback_fn("Generating X509 certificate on %s" %
73 d0d7d7cf Thomas Thrainer
                  self.cfg.GetNodeName(self.instance.primary_node))
74 d0d7d7cf Thomas Thrainer
      result = self.rpc.call_x509_cert_create(self.instance.primary_node,
75 7ecd5e87 Thomas Thrainer
                                              constants.RIE_CERT_VALIDITY)
76 1c3231aa Thomas Thrainer
      result.Raise("Can't create X509 key and certificate on %s" %
77 1c3231aa Thomas Thrainer
                   self.cfg.GetNodeName(result.node))
78 7ecd5e87 Thomas Thrainer
79 7ecd5e87 Thomas Thrainer
      (name, cert_pem) = result.payload
80 7ecd5e87 Thomas Thrainer
81 7ecd5e87 Thomas Thrainer
      cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
82 7ecd5e87 Thomas Thrainer
                                             cert_pem)
83 7ecd5e87 Thomas Thrainer
84 7ecd5e87 Thomas Thrainer
      return {
85 7ecd5e87 Thomas Thrainer
        "handshake": masterd.instance.ComputeRemoteExportHandshake(self._cds),
86 7ecd5e87 Thomas Thrainer
        "x509_key_name": (name, utils.Sha1Hmac(self._cds, name, salt=salt),
87 7ecd5e87 Thomas Thrainer
                          salt),
88 7ecd5e87 Thomas Thrainer
        "x509_ca": utils.SignX509Certificate(cert, self._cds, salt),
89 7ecd5e87 Thomas Thrainer
        }
90 7ecd5e87 Thomas Thrainer
91 7ecd5e87 Thomas Thrainer
    return None
92 7ecd5e87 Thomas Thrainer
93 7ecd5e87 Thomas Thrainer
94 7ecd5e87 Thomas Thrainer
class LUBackupExport(LogicalUnit):
95 7ecd5e87 Thomas Thrainer
  """Export an instance to an image in the cluster.
96 7ecd5e87 Thomas Thrainer

97 7ecd5e87 Thomas Thrainer
  """
98 7ecd5e87 Thomas Thrainer
  HPATH = "instance-export"
99 7ecd5e87 Thomas Thrainer
  HTYPE = constants.HTYPE_INSTANCE
100 7ecd5e87 Thomas Thrainer
  REQ_BGL = False
101 7ecd5e87 Thomas Thrainer
102 7ecd5e87 Thomas Thrainer
  def CheckArguments(self):
103 7ecd5e87 Thomas Thrainer
    """Check the arguments.
104 7ecd5e87 Thomas Thrainer

105 7ecd5e87 Thomas Thrainer
    """
106 7ecd5e87 Thomas Thrainer
    self.x509_key_name = self.op.x509_key_name
107 7ecd5e87 Thomas Thrainer
    self.dest_x509_ca_pem = self.op.destination_x509_ca
108 7ecd5e87 Thomas Thrainer
109 7ecd5e87 Thomas Thrainer
    if self.op.mode == constants.EXPORT_MODE_REMOTE:
110 7ecd5e87 Thomas Thrainer
      if not self.x509_key_name:
111 7ecd5e87 Thomas Thrainer
        raise errors.OpPrereqError("Missing X509 key name for encryption",
112 7ecd5e87 Thomas Thrainer
                                   errors.ECODE_INVAL)
113 7ecd5e87 Thomas Thrainer
114 7ecd5e87 Thomas Thrainer
      if not self.dest_x509_ca_pem:
115 7ecd5e87 Thomas Thrainer
        raise errors.OpPrereqError("Missing destination X509 CA",
116 7ecd5e87 Thomas Thrainer
                                   errors.ECODE_INVAL)
117 7ecd5e87 Thomas Thrainer
118 363f43eb Hrvoje Ribicic
    if self.op.zero_free_space and not self.op.compress:
119 363f43eb Hrvoje Ribicic
      raise errors.OpPrereqError("Zeroing free space does not make sense "
120 363f43eb Hrvoje Ribicic
                                 "unless compression is used")
121 363f43eb Hrvoje Ribicic
122 363f43eb Hrvoje Ribicic
    if self.op.zero_free_space and not self.op.shutdown:
123 363f43eb Hrvoje Ribicic
      raise errors.OpPrereqError("Unless the instance is shut down, zeroing "
124 363f43eb Hrvoje Ribicic
                                 "cannot be used.")
125 363f43eb Hrvoje Ribicic
126 7ecd5e87 Thomas Thrainer
  def ExpandNames(self):
127 7ecd5e87 Thomas Thrainer
    self._ExpandAndLockInstance()
128 7ecd5e87 Thomas Thrainer
129 d6e1e696 Hrvoje Ribicic
    # In case we are zeroing, a node lock is required as we will be creating and
130 d6e1e696 Hrvoje Ribicic
    # destroying a disk - allocations should be stopped, but not on the entire
131 d6e1e696 Hrvoje Ribicic
    # cluster
132 d6e1e696 Hrvoje Ribicic
    if self.op.zero_free_space:
133 d6e1e696 Hrvoje Ribicic
      self.recalculate_locks = {locking.LEVEL_NODE: constants.LOCKS_REPLACE}
134 d6e1e696 Hrvoje Ribicic
      self._LockInstancesNodes(primary_only=True)
135 d6e1e696 Hrvoje Ribicic
136 7ecd5e87 Thomas Thrainer
    # Lock all nodes for local exports
137 7ecd5e87 Thomas Thrainer
    if self.op.mode == constants.EXPORT_MODE_LOCAL:
138 1c3231aa Thomas Thrainer
      (self.op.target_node_uuid, self.op.target_node) = \
139 1c3231aa Thomas Thrainer
        ExpandNodeUuidAndName(self.cfg, self.op.target_node_uuid,
140 1c3231aa Thomas Thrainer
                              self.op.target_node)
141 7ecd5e87 Thomas Thrainer
      # FIXME: lock only instance primary and destination node
142 7ecd5e87 Thomas Thrainer
      #
143 7ecd5e87 Thomas Thrainer
      # Sad but true, for now we have do lock all nodes, as we don't know where
144 7ecd5e87 Thomas Thrainer
      # the previous export might be, and in this LU we search for it and
145 7ecd5e87 Thomas Thrainer
      # remove it from its current node. In the future we could fix this by:
146 7ecd5e87 Thomas Thrainer
      #  - making a tasklet to search (share-lock all), then create the
147 7ecd5e87 Thomas Thrainer
      #    new one, then one to remove, after
148 7ecd5e87 Thomas Thrainer
      #  - removing the removal operation altogether
149 7ecd5e87 Thomas Thrainer
      self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
150 7ecd5e87 Thomas Thrainer
151 7ecd5e87 Thomas Thrainer
      # Allocations should be stopped while this LU runs with node locks, but
152 7ecd5e87 Thomas Thrainer
      # it doesn't have to be exclusive
153 7ecd5e87 Thomas Thrainer
      self.share_locks[locking.LEVEL_NODE_ALLOC] = 1
154 7ecd5e87 Thomas Thrainer
      self.needed_locks[locking.LEVEL_NODE_ALLOC] = locking.ALL_SET
155 7ecd5e87 Thomas Thrainer
156 7ecd5e87 Thomas Thrainer
  def DeclareLocks(self, level):
157 7ecd5e87 Thomas Thrainer
    """Last minute lock declaration."""
158 7ecd5e87 Thomas Thrainer
    # All nodes are locked anyway, so nothing to do here.
159 7ecd5e87 Thomas Thrainer
160 7ecd5e87 Thomas Thrainer
  def BuildHooksEnv(self):
161 7ecd5e87 Thomas Thrainer
    """Build hooks env.
162 7ecd5e87 Thomas Thrainer

163 7ecd5e87 Thomas Thrainer
    This will run on the master, primary node and target node.
164 7ecd5e87 Thomas Thrainer

165 7ecd5e87 Thomas Thrainer
    """
166 7ecd5e87 Thomas Thrainer
    env = {
167 7ecd5e87 Thomas Thrainer
      "EXPORT_MODE": self.op.mode,
168 7ecd5e87 Thomas Thrainer
      "EXPORT_NODE": self.op.target_node,
169 7ecd5e87 Thomas Thrainer
      "EXPORT_DO_SHUTDOWN": self.op.shutdown,
170 7ecd5e87 Thomas Thrainer
      "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
171 7ecd5e87 Thomas Thrainer
      # TODO: Generic function for boolean env variables
172 7ecd5e87 Thomas Thrainer
      "REMOVE_INSTANCE": str(bool(self.op.remove_instance)),
173 7ecd5e87 Thomas Thrainer
      }
174 7ecd5e87 Thomas Thrainer
175 e8dd6643 Ilias Tsitsimpis
    env.update(BuildInstanceHookEnvByObject(
176 e8dd6643 Ilias Tsitsimpis
      self, self.instance,
177 e8dd6643 Ilias Tsitsimpis
      secondary_nodes=self.secondary_nodes, disks=self.inst_disks))
178 7ecd5e87 Thomas Thrainer
179 7ecd5e87 Thomas Thrainer
    return env
180 7ecd5e87 Thomas Thrainer
181 7ecd5e87 Thomas Thrainer
  def BuildHooksNodes(self):
182 7ecd5e87 Thomas Thrainer
    """Build hooks nodes.
183 7ecd5e87 Thomas Thrainer

184 7ecd5e87 Thomas Thrainer
    """
185 7ecd5e87 Thomas Thrainer
    nl = [self.cfg.GetMasterNode(), self.instance.primary_node]
186 7ecd5e87 Thomas Thrainer
187 7ecd5e87 Thomas Thrainer
    if self.op.mode == constants.EXPORT_MODE_LOCAL:
188 1c3231aa Thomas Thrainer
      nl.append(self.op.target_node_uuid)
189 7ecd5e87 Thomas Thrainer
190 7ecd5e87 Thomas Thrainer
    return (nl, nl)
191 7ecd5e87 Thomas Thrainer
192 7ecd5e87 Thomas Thrainer
  def CheckPrereq(self):
193 7ecd5e87 Thomas Thrainer
    """Check prerequisites.
194 7ecd5e87 Thomas Thrainer

195 7ecd5e87 Thomas Thrainer
    This checks that the instance and node names are valid.
196 7ecd5e87 Thomas Thrainer

197 7ecd5e87 Thomas Thrainer
    """
198 da4a52a3 Thomas Thrainer
    self.instance = self.cfg.GetInstanceInfoByName(self.op.instance_name)
199 7ecd5e87 Thomas Thrainer
    assert self.instance is not None, \
200 7ecd5e87 Thomas Thrainer
          "Cannot retrieve locked instance %s" % self.op.instance_name
201 5eacbcae Thomas Thrainer
    CheckNodeOnline(self, self.instance.primary_node)
202 7ecd5e87 Thomas Thrainer
203 7ecd5e87 Thomas Thrainer
    if (self.op.remove_instance and
204 7ecd5e87 Thomas Thrainer
        self.instance.admin_state == constants.ADMINST_UP and
205 7ecd5e87 Thomas Thrainer
        not self.op.shutdown):
206 7ecd5e87 Thomas Thrainer
      raise errors.OpPrereqError("Can not remove instance without shutting it"
207 7ecd5e87 Thomas Thrainer
                                 " down before", errors.ECODE_STATE)
208 7ecd5e87 Thomas Thrainer
209 7ecd5e87 Thomas Thrainer
    if self.op.mode == constants.EXPORT_MODE_LOCAL:
210 1c3231aa Thomas Thrainer
      self.dst_node = self.cfg.GetNodeInfo(self.op.target_node_uuid)
211 7ecd5e87 Thomas Thrainer
      assert self.dst_node is not None
212 7ecd5e87 Thomas Thrainer
213 1c3231aa Thomas Thrainer
      CheckNodeOnline(self, self.dst_node.uuid)
214 1c3231aa Thomas Thrainer
      CheckNodeNotDrained(self, self.dst_node.uuid)
215 7ecd5e87 Thomas Thrainer
216 7ecd5e87 Thomas Thrainer
      self._cds = None
217 7ecd5e87 Thomas Thrainer
      self.dest_disk_info = None
218 7ecd5e87 Thomas Thrainer
      self.dest_x509_ca = None
219 7ecd5e87 Thomas Thrainer
220 7ecd5e87 Thomas Thrainer
    elif self.op.mode == constants.EXPORT_MODE_REMOTE:
221 7ecd5e87 Thomas Thrainer
      self.dst_node = None
222 7ecd5e87 Thomas Thrainer
223 7ecd5e87 Thomas Thrainer
      if len(self.op.target_node) != len(self.instance.disks):
224 7ecd5e87 Thomas Thrainer
        raise errors.OpPrereqError(("Received destination information for %s"
225 7ecd5e87 Thomas Thrainer
                                    " disks, but instance %s has %s disks") %
226 d0d7d7cf Thomas Thrainer
                                   (len(self.op.target_node),
227 d0d7d7cf Thomas Thrainer
                                    self.op.instance_name,
228 7ecd5e87 Thomas Thrainer
                                    len(self.instance.disks)),
229 7ecd5e87 Thomas Thrainer
                                   errors.ECODE_INVAL)
230 7ecd5e87 Thomas Thrainer
231 5eacbcae Thomas Thrainer
      cds = GetClusterDomainSecret()
232 7ecd5e87 Thomas Thrainer
233 7ecd5e87 Thomas Thrainer
      # Check X509 key name
234 7ecd5e87 Thomas Thrainer
      try:
235 7ecd5e87 Thomas Thrainer
        (key_name, hmac_digest, hmac_salt) = self.x509_key_name
236 7ecd5e87 Thomas Thrainer
      except (TypeError, ValueError), err:
237 7ecd5e87 Thomas Thrainer
        raise errors.OpPrereqError("Invalid data for X509 key name: %s" % err,
238 7ecd5e87 Thomas Thrainer
                                   errors.ECODE_INVAL)
239 7ecd5e87 Thomas Thrainer
240 7ecd5e87 Thomas Thrainer
      if not utils.VerifySha1Hmac(cds, key_name, hmac_digest, salt=hmac_salt):
241 7ecd5e87 Thomas Thrainer
        raise errors.OpPrereqError("HMAC for X509 key name is wrong",
242 7ecd5e87 Thomas Thrainer
                                   errors.ECODE_INVAL)
243 7ecd5e87 Thomas Thrainer
244 7ecd5e87 Thomas Thrainer
      # Load and verify CA
245 7ecd5e87 Thomas Thrainer
      try:
246 7ecd5e87 Thomas Thrainer
        (cert, _) = utils.LoadSignedX509Certificate(self.dest_x509_ca_pem, cds)
247 7ecd5e87 Thomas Thrainer
      except OpenSSL.crypto.Error, err:
248 7ecd5e87 Thomas Thrainer
        raise errors.OpPrereqError("Unable to load destination X509 CA (%s)" %
249 7ecd5e87 Thomas Thrainer
                                   (err, ), errors.ECODE_INVAL)
250 7ecd5e87 Thomas Thrainer
251 7ecd5e87 Thomas Thrainer
      (errcode, msg) = utils.VerifyX509Certificate(cert, None, None)
252 7ecd5e87 Thomas Thrainer
      if errcode is not None:
253 7ecd5e87 Thomas Thrainer
        raise errors.OpPrereqError("Invalid destination X509 CA (%s)" %
254 7ecd5e87 Thomas Thrainer
                                   (msg, ), errors.ECODE_INVAL)
255 7ecd5e87 Thomas Thrainer
256 7ecd5e87 Thomas Thrainer
      self.dest_x509_ca = cert
257 7ecd5e87 Thomas Thrainer
258 7ecd5e87 Thomas Thrainer
      # Verify target information
259 7ecd5e87 Thomas Thrainer
      disk_info = []
260 7ecd5e87 Thomas Thrainer
      for idx, disk_data in enumerate(self.op.target_node):
261 7ecd5e87 Thomas Thrainer
        try:
262 7ecd5e87 Thomas Thrainer
          (host, port, magic) = \
263 7ecd5e87 Thomas Thrainer
            masterd.instance.CheckRemoteExportDiskInfo(cds, idx, disk_data)
264 7ecd5e87 Thomas Thrainer
        except errors.GenericError, err:
265 7ecd5e87 Thomas Thrainer
          raise errors.OpPrereqError("Target info for disk %s: %s" %
266 7ecd5e87 Thomas Thrainer
                                     (idx, err), errors.ECODE_INVAL)
267 7ecd5e87 Thomas Thrainer
268 7ecd5e87 Thomas Thrainer
        disk_info.append((host, port, magic))
269 7ecd5e87 Thomas Thrainer
270 7ecd5e87 Thomas Thrainer
      assert len(disk_info) == len(self.op.target_node)
271 7ecd5e87 Thomas Thrainer
      self.dest_disk_info = disk_info
272 7ecd5e87 Thomas Thrainer
273 7ecd5e87 Thomas Thrainer
    else:
274 7ecd5e87 Thomas Thrainer
      raise errors.ProgrammerError("Unhandled export mode %r" %
275 7ecd5e87 Thomas Thrainer
                                   self.op.mode)
276 7ecd5e87 Thomas Thrainer
277 7ecd5e87 Thomas Thrainer
    # instance disk type verification
278 7ecd5e87 Thomas Thrainer
    # TODO: Implement export support for file-based disks
279 43b1f49f Ilias Tsitsimpis
    for disk in self.cfg.GetInstanceDisks(self.instance.uuid):
280 a09639d1 Santi Raffa
      if disk.dev_type in constants.DTS_FILEBASED:
281 7ecd5e87 Thomas Thrainer
        raise errors.OpPrereqError("Export not supported for instances with"
282 7ecd5e87 Thomas Thrainer
                                   " file-based disks", errors.ECODE_INVAL)
283 7ecd5e87 Thomas Thrainer
284 8cd365d9 Hrvoje Ribicic
    # Check prerequisites for zeroing
285 8cd365d9 Hrvoje Ribicic
    if self.op.zero_free_space:
286 8cd365d9 Hrvoje Ribicic
      # Check that user shutdown detection has been enabled
287 8cd365d9 Hrvoje Ribicic
      hvparams = self.cfg.GetClusterInfo().FillHV(self.instance)
288 8cd365d9 Hrvoje Ribicic
      if self.instance.hypervisor == constants.HT_KVM and \
289 8cd365d9 Hrvoje Ribicic
         not hvparams.get(constants.HV_KVM_USER_SHUTDOWN, False):
290 8cd365d9 Hrvoje Ribicic
        raise errors.OpPrereqError("Instance shutdown detection must be "
291 8cd365d9 Hrvoje Ribicic
                                   "enabled for zeroing to work")
292 8cd365d9 Hrvoje Ribicic
293 8cd365d9 Hrvoje Ribicic
      # Check that the instance is set to boot from the disk
294 8cd365d9 Hrvoje Ribicic
      if constants.HV_BOOT_ORDER in hvparams and \
295 8cd365d9 Hrvoje Ribicic
         hvparams[constants.HV_BOOT_ORDER] != constants.HT_BO_DISK:
296 8cd365d9 Hrvoje Ribicic
        raise errors.OpPrereqError("Booting from disk must be set for zeroing "
297 8cd365d9 Hrvoje Ribicic
                                   "to work")
298 8cd365d9 Hrvoje Ribicic
299 0894ac48 Hrvoje Ribicic
      # Check that the zeroing image is set
300 0894ac48 Hrvoje Ribicic
      if not self.cfg.GetZeroingImage():
301 0894ac48 Hrvoje Ribicic
        raise errors.OpPrereqError("A zeroing image must be set for zeroing to"
302 0894ac48 Hrvoje Ribicic
                                   " work")
303 0894ac48 Hrvoje Ribicic
304 a81e84ff Hrvoje Ribicic
      if self.op.zeroing_timeout_fixed is None:
305 a81e84ff Hrvoje Ribicic
        self.op.zeroing_timeout_fixed = constants.HELPER_VM_STARTUP
306 a81e84ff Hrvoje Ribicic
307 a81e84ff Hrvoje Ribicic
      if self.op.zeroing_timeout_per_mib is None:
308 a81e84ff Hrvoje Ribicic
        self.op.zeroing_timeout_per_mib = constants.ZEROING_TIMEOUT_PER_MIB
309 a81e84ff Hrvoje Ribicic
310 a81e84ff Hrvoje Ribicic
    else:
311 a81e84ff Hrvoje Ribicic
      if (self.op.zeroing_timeout_fixed is not None or
312 a81e84ff Hrvoje Ribicic
          self.op.zeroing_timeout_per_mib is not None):
313 a81e84ff Hrvoje Ribicic
        raise errors.OpPrereqError("Zeroing timeout options can only be used"
314 a81e84ff Hrvoje Ribicic
                                   " only with the --zero-free-space option")
315 a81e84ff Hrvoje Ribicic
316 45c044f4 Ilias Tsitsimpis
    self.secondary_nodes = \
317 45c044f4 Ilias Tsitsimpis
      self.cfg.GetInstanceSecondaryNodes(self.instance.uuid)
318 43b1f49f Ilias Tsitsimpis
    self.inst_disks = self.cfg.GetInstanceDisks(self.instance.uuid)
319 e8dd6643 Ilias Tsitsimpis
320 047f59ce Hrvoje Ribicic
    # Check if the compression tool is whitelisted
321 047f59ce Hrvoje Ribicic
    CheckCompressionTool(self, self.op.compress)
322 047f59ce Hrvoje Ribicic
323 7ecd5e87 Thomas Thrainer
  def _CleanupExports(self, feedback_fn):
324 7ecd5e87 Thomas Thrainer
    """Removes exports of current instance from all other nodes.
325 7ecd5e87 Thomas Thrainer

326 7ecd5e87 Thomas Thrainer
    If an instance in a cluster with nodes A..D was exported to node C, its
327 7ecd5e87 Thomas Thrainer
    exports will be removed from the nodes A, B and D.
328 7ecd5e87 Thomas Thrainer

329 7ecd5e87 Thomas Thrainer
    """
330 7ecd5e87 Thomas Thrainer
    assert self.op.mode != constants.EXPORT_MODE_REMOTE
331 7ecd5e87 Thomas Thrainer
332 1c3231aa Thomas Thrainer
    node_uuids = self.cfg.GetNodeList()
333 1c3231aa Thomas Thrainer
    node_uuids.remove(self.dst_node.uuid)
334 7ecd5e87 Thomas Thrainer
335 7ecd5e87 Thomas Thrainer
    # on one-node clusters nodelist will be empty after the removal
336 7ecd5e87 Thomas Thrainer
    # if we proceed the backup would be removed because OpBackupQuery
337 7ecd5e87 Thomas Thrainer
    # substitutes an empty list with the full cluster node list.
338 7ecd5e87 Thomas Thrainer
    iname = self.instance.name
339 1c3231aa Thomas Thrainer
    if node_uuids:
340 7ecd5e87 Thomas Thrainer
      feedback_fn("Removing old exports for instance %s" % iname)
341 1c3231aa Thomas Thrainer
      exportlist = self.rpc.call_export_list(node_uuids)
342 1c3231aa Thomas Thrainer
      for node_uuid in exportlist:
343 1c3231aa Thomas Thrainer
        if exportlist[node_uuid].fail_msg:
344 7ecd5e87 Thomas Thrainer
          continue
345 1c3231aa Thomas Thrainer
        if iname in exportlist[node_uuid].payload:
346 1c3231aa Thomas Thrainer
          msg = self.rpc.call_export_remove(node_uuid, iname).fail_msg
347 7ecd5e87 Thomas Thrainer
          if msg:
348 7ecd5e87 Thomas Thrainer
            self.LogWarning("Could not remove older export for instance %s"
349 1c3231aa Thomas Thrainer
                            " on node %s: %s", iname,
350 1c3231aa Thomas Thrainer
                            self.cfg.GetNodeName(node_uuid), msg)
351 7ecd5e87 Thomas Thrainer
352 f7b0366c Hrvoje Ribicic
  def _InstanceDiskSizeSum(self):
353 f7b0366c Hrvoje Ribicic
    """Calculates the size of all the disks of the instance used in this LU.
354 f7b0366c Hrvoje Ribicic

355 f7b0366c Hrvoje Ribicic
    @rtype: int
356 f7b0366c Hrvoje Ribicic
    @return: Size of the disks in MiB
357 f7b0366c Hrvoje Ribicic

358 f7b0366c Hrvoje Ribicic
    """
359 43b1f49f Ilias Tsitsimpis
    inst_disks = self.cfg.GetInstanceDisks(self.instance.uuid)
360 43b1f49f Ilias Tsitsimpis
    return sum([d.size for d in inst_disks])
361 f7b0366c Hrvoje Ribicic
362 03d56d40 Hrvoje Ribicic
  def ZeroFreeSpace(self, feedback_fn):
363 03d56d40 Hrvoje Ribicic
    """Zeroes the free space on a shutdown instance.
364 03d56d40 Hrvoje Ribicic

365 03d56d40 Hrvoje Ribicic
    @type feedback_fn: function
366 03d56d40 Hrvoje Ribicic
    @param feedback_fn: Function used to log progress
367 03d56d40 Hrvoje Ribicic

368 03d56d40 Hrvoje Ribicic
    """
369 a81e84ff Hrvoje Ribicic
    assert self.op.zeroing_timeout_fixed is not None
370 a81e84ff Hrvoje Ribicic
    assert self.op.zeroing_timeout_per_mib is not None
371 a81e84ff Hrvoje Ribicic
372 03d56d40 Hrvoje Ribicic
    zeroing_image = self.cfg.GetZeroingImage()
373 03d56d40 Hrvoje Ribicic
    src_node_uuid = self.instance.primary_node
374 38519f22 Jose A. Lopes
375 38519f22 Jose A. Lopes
    try:
376 38519f22 Jose A. Lopes
      disk_size = DetermineImageSize(self, zeroing_image, src_node_uuid)
377 38519f22 Jose A. Lopes
    except errors.OpExecError, err:
378 38519f22 Jose A. Lopes
      raise errors.OpExecError("Could not create temporary disk for zeroing:"
379 38519f22 Jose A. Lopes
                               " %s", err)
380 03d56d40 Hrvoje Ribicic
381 f7b0366c Hrvoje Ribicic
    # Calculate the sum prior to adding the temporary disk
382 f7b0366c Hrvoje Ribicic
    instance_disks_size_sum = self._InstanceDiskSizeSum()
383 f7b0366c Hrvoje Ribicic
384 b8dd2853 Jose A. Lopes
    with TemporaryDisk(self,
385 b8dd2853 Jose A. Lopes
                       self.instance,
386 b8dd2853 Jose A. Lopes
                       [(constants.DT_PLAIN, constants.DISK_RDWR, disk_size)],
387 b8dd2853 Jose A. Lopes
                       feedback_fn):
388 f7b0366c Hrvoje Ribicic
      feedback_fn("Activating instance disks")
389 f7b0366c Hrvoje Ribicic
      StartInstanceDisks(self, self.instance, False)
390 f7b0366c Hrvoje Ribicic
391 f7b0366c Hrvoje Ribicic
      feedback_fn("Imaging disk with zeroing image")
392 f7b0366c Hrvoje Ribicic
      ImageDisks(self, self.instance, zeroing_image)
393 f7b0366c Hrvoje Ribicic
394 f7b0366c Hrvoje Ribicic
      feedback_fn("Starting instance with zeroing image")
395 f7b0366c Hrvoje Ribicic
      result = self.rpc.call_instance_start(src_node_uuid,
396 f7b0366c Hrvoje Ribicic
                                            (self.instance, [], []),
397 f7b0366c Hrvoje Ribicic
                                            False, self.op.reason)
398 f7b0366c Hrvoje Ribicic
      result.Raise("Could not start instance %s when using the zeroing image "
399 f7b0366c Hrvoje Ribicic
                   "%s" % (self.instance.name, zeroing_image))
400 f7b0366c Hrvoje Ribicic
401 f7b0366c Hrvoje Ribicic
      # First wait for the instance to start up
402 f7b0366c Hrvoje Ribicic
      running_check = lambda: IsInstanceRunning(self, self.instance,
403 f7b0366c Hrvoje Ribicic
                                                check_user_shutdown=True)
404 f7b0366c Hrvoje Ribicic
      instance_up = retry.SimpleRetry(True, running_check, 5.0,
405 f7b0366c Hrvoje Ribicic
                                      self.op.shutdown_timeout)
406 f7b0366c Hrvoje Ribicic
      if not instance_up:
407 f7b0366c Hrvoje Ribicic
        raise errors.OpExecError("Could not boot instance when using the "
408 f7b0366c Hrvoje Ribicic
                                 "zeroing image %s" % zeroing_image)
409 f7b0366c Hrvoje Ribicic
410 f7b0366c Hrvoje Ribicic
      feedback_fn("Instance is up, now awaiting shutdown")
411 f7b0366c Hrvoje Ribicic
412 f7b0366c Hrvoje Ribicic
      # Then for it to be finished, detected by its shutdown
413 f7b0366c Hrvoje Ribicic
      timeout = self.op.zeroing_timeout_fixed + \
414 f7b0366c Hrvoje Ribicic
                self.op.zeroing_timeout_per_mib * instance_disks_size_sum
415 f7b0366c Hrvoje Ribicic
      instance_up = retry.SimpleRetry(False, running_check, 20.0, timeout)
416 f7b0366c Hrvoje Ribicic
      if instance_up:
417 f7b0366c Hrvoje Ribicic
        self.LogWarning("Zeroing not completed prior to timeout; instance will"
418 f7b0366c Hrvoje Ribicic
                        "be shut down forcibly")
419 f7b0366c Hrvoje Ribicic
420 f7b0366c Hrvoje Ribicic
    feedback_fn("Zeroing completed!")
421 03d56d40 Hrvoje Ribicic
422 7ecd5e87 Thomas Thrainer
  def Exec(self, feedback_fn):
423 7ecd5e87 Thomas Thrainer
    """Export an instance to an image in the cluster.
424 7ecd5e87 Thomas Thrainer

425 7ecd5e87 Thomas Thrainer
    """
426 7ecd5e87 Thomas Thrainer
    assert self.op.mode in constants.EXPORT_MODES
427 7ecd5e87 Thomas Thrainer
428 d0d7d7cf Thomas Thrainer
    src_node_uuid = self.instance.primary_node
429 7ecd5e87 Thomas Thrainer
430 7ecd5e87 Thomas Thrainer
    if self.op.shutdown:
431 7ecd5e87 Thomas Thrainer
      # shutdown the instance, but not the disks
432 d0d7d7cf Thomas Thrainer
      feedback_fn("Shutting down instance %s" % self.instance.name)
433 d0d7d7cf Thomas Thrainer
      result = self.rpc.call_instance_shutdown(src_node_uuid, self.instance,
434 7ecd5e87 Thomas Thrainer
                                               self.op.shutdown_timeout,
435 7ecd5e87 Thomas Thrainer
                                               self.op.reason)
436 7ecd5e87 Thomas Thrainer
      # TODO: Maybe ignore failures if ignore_remove_failures is set
437 7ecd5e87 Thomas Thrainer
      result.Raise("Could not shutdown instance %s on"
438 d0d7d7cf Thomas Thrainer
                   " node %s" % (self.instance.name,
439 1c3231aa Thomas Thrainer
                                 self.cfg.GetNodeName(src_node_uuid)))
440 7ecd5e87 Thomas Thrainer
441 03d56d40 Hrvoje Ribicic
    if self.op.zero_free_space:
442 03d56d40 Hrvoje Ribicic
      self.ZeroFreeSpace(feedback_fn)
443 03d56d40 Hrvoje Ribicic
444 d0d7d7cf Thomas Thrainer
    activate_disks = not self.instance.disks_active
445 7ecd5e87 Thomas Thrainer
446 7ecd5e87 Thomas Thrainer
    if activate_disks:
447 70b634e6 Thomas Thrainer
      # Activate the instance disks if we're exporting a stopped instance
448 d0d7d7cf Thomas Thrainer
      feedback_fn("Activating disks for %s" % self.instance.name)
449 d0d7d7cf Thomas Thrainer
      StartInstanceDisks(self, self.instance, None)
450 c28d15f7 Petr Pudlak
      self.instance = self.cfg.GetInstanceInfo(self.instance.uuid)
451 7ecd5e87 Thomas Thrainer
452 7ecd5e87 Thomas Thrainer
    try:
453 7ecd5e87 Thomas Thrainer
      helper = masterd.instance.ExportInstanceHelper(self, feedback_fn,
454 d0d7d7cf Thomas Thrainer
                                                     self.instance)
455 7ecd5e87 Thomas Thrainer
456 7ecd5e87 Thomas Thrainer
      helper.CreateSnapshots()
457 7ecd5e87 Thomas Thrainer
      try:
458 7ecd5e87 Thomas Thrainer
        if (self.op.shutdown and
459 d0d7d7cf Thomas Thrainer
            self.instance.admin_state == constants.ADMINST_UP and
460 7ecd5e87 Thomas Thrainer
            not self.op.remove_instance):
461 b1b4b282 Hrvoje Ribicic
          assert self.instance.disks_active
462 d0d7d7cf Thomas Thrainer
          feedback_fn("Starting instance %s" % self.instance.name)
463 1c3231aa Thomas Thrainer
          result = self.rpc.call_instance_start(src_node_uuid,
464 d0d7d7cf Thomas Thrainer
                                                (self.instance, None, None),
465 d0d7d7cf Thomas Thrainer
                                                False, self.op.reason)
466 7ecd5e87 Thomas Thrainer
          msg = result.fail_msg
467 7ecd5e87 Thomas Thrainer
          if msg:
468 7ecd5e87 Thomas Thrainer
            feedback_fn("Failed to start instance: %s" % msg)
469 d0d7d7cf Thomas Thrainer
            ShutdownInstanceDisks(self, self.instance)
470 7ecd5e87 Thomas Thrainer
            raise errors.OpExecError("Could not start instance: %s" % msg)
471 7ecd5e87 Thomas Thrainer
472 7ecd5e87 Thomas Thrainer
        if self.op.mode == constants.EXPORT_MODE_LOCAL:
473 896cc964 Thomas Thrainer
          (fin_resu, dresults) = helper.LocalExport(self.dst_node,
474 896cc964 Thomas Thrainer
                                                    self.op.compress)
475 7ecd5e87 Thomas Thrainer
        elif self.op.mode == constants.EXPORT_MODE_REMOTE:
476 7ecd5e87 Thomas Thrainer
          connect_timeout = constants.RIE_CONNECT_TIMEOUT
477 7ecd5e87 Thomas Thrainer
          timeouts = masterd.instance.ImportExportTimeouts(connect_timeout)
478 7ecd5e87 Thomas Thrainer
479 7ecd5e87 Thomas Thrainer
          (key_name, _, _) = self.x509_key_name
480 7ecd5e87 Thomas Thrainer
481 7ecd5e87 Thomas Thrainer
          dest_ca_pem = \
482 7ecd5e87 Thomas Thrainer
            OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM,
483 7ecd5e87 Thomas Thrainer
                                            self.dest_x509_ca)
484 7ecd5e87 Thomas Thrainer
485 7ecd5e87 Thomas Thrainer
          (fin_resu, dresults) = helper.RemoteExport(self.dest_disk_info,
486 7ecd5e87 Thomas Thrainer
                                                     key_name, dest_ca_pem,
487 258de3fe Thomas Thrainer
                                                     self.op.compress,
488 7ecd5e87 Thomas Thrainer
                                                     timeouts)
489 7ecd5e87 Thomas Thrainer
      finally:
490 7ecd5e87 Thomas Thrainer
        helper.Cleanup()
491 7ecd5e87 Thomas Thrainer
492 7ecd5e87 Thomas Thrainer
      # Check for backwards compatibility
493 d0d7d7cf Thomas Thrainer
      assert len(dresults) == len(self.instance.disks)
494 7ecd5e87 Thomas Thrainer
      assert compat.all(isinstance(i, bool) for i in dresults), \
495 7ecd5e87 Thomas Thrainer
             "Not all results are boolean: %r" % dresults
496 7ecd5e87 Thomas Thrainer
497 7ecd5e87 Thomas Thrainer
    finally:
498 7ecd5e87 Thomas Thrainer
      if activate_disks:
499 d0d7d7cf Thomas Thrainer
        feedback_fn("Deactivating disks for %s" % self.instance.name)
500 d0d7d7cf Thomas Thrainer
        ShutdownInstanceDisks(self, self.instance)
501 7ecd5e87 Thomas Thrainer
502 7ecd5e87 Thomas Thrainer
    if not (compat.all(dresults) and fin_resu):
503 7ecd5e87 Thomas Thrainer
      failures = []
504 7ecd5e87 Thomas Thrainer
      if not fin_resu:
505 7ecd5e87 Thomas Thrainer
        failures.append("export finalization")
506 7ecd5e87 Thomas Thrainer
      if not compat.all(dresults):
507 7ecd5e87 Thomas Thrainer
        fdsk = utils.CommaJoin(idx for (idx, dsk) in enumerate(dresults)
508 7ecd5e87 Thomas Thrainer
                               if not dsk)
509 7ecd5e87 Thomas Thrainer
        failures.append("disk export: disk(s) %s" % fdsk)
510 7ecd5e87 Thomas Thrainer
511 7ecd5e87 Thomas Thrainer
      raise errors.OpExecError("Export failed, errors in %s" %
512 7ecd5e87 Thomas Thrainer
                               utils.CommaJoin(failures))
513 7ecd5e87 Thomas Thrainer
514 7ecd5e87 Thomas Thrainer
    # At this point, the export was successful, we can cleanup/finish
515 7ecd5e87 Thomas Thrainer
516 7ecd5e87 Thomas Thrainer
    # Remove instance if requested
517 7ecd5e87 Thomas Thrainer
    if self.op.remove_instance:
518 d0d7d7cf Thomas Thrainer
      feedback_fn("Removing instance %s" % self.instance.name)
519 d0d7d7cf Thomas Thrainer
      RemoveInstance(self, feedback_fn, self.instance,
520 5eacbcae Thomas Thrainer
                     self.op.ignore_remove_failures)
521 7ecd5e87 Thomas Thrainer
522 7ecd5e87 Thomas Thrainer
    if self.op.mode == constants.EXPORT_MODE_LOCAL:
523 7ecd5e87 Thomas Thrainer
      self._CleanupExports(feedback_fn)
524 7ecd5e87 Thomas Thrainer
525 7ecd5e87 Thomas Thrainer
    return fin_resu, dresults
526 7ecd5e87 Thomas Thrainer
527 7ecd5e87 Thomas Thrainer
528 7ecd5e87 Thomas Thrainer
class LUBackupRemove(NoHooksLU):
529 7ecd5e87 Thomas Thrainer
  """Remove exports related to the named instance.
530 7ecd5e87 Thomas Thrainer

531 7ecd5e87 Thomas Thrainer
  """
532 7ecd5e87 Thomas Thrainer
  REQ_BGL = False
533 7ecd5e87 Thomas Thrainer
534 7ecd5e87 Thomas Thrainer
  def ExpandNames(self):
535 7ecd5e87 Thomas Thrainer
    self.needed_locks = {
536 7ecd5e87 Thomas Thrainer
      # We need all nodes to be locked in order for RemoveExport to work, but
537 7ecd5e87 Thomas Thrainer
      # we don't need to lock the instance itself, as nothing will happen to it
538 7ecd5e87 Thomas Thrainer
      # (and we can remove exports also for a removed instance)
539 7ecd5e87 Thomas Thrainer
      locking.LEVEL_NODE: locking.ALL_SET,
540 7ecd5e87 Thomas Thrainer
541 7ecd5e87 Thomas Thrainer
      # Removing backups is quick, so blocking allocations is justified
542 7ecd5e87 Thomas Thrainer
      locking.LEVEL_NODE_ALLOC: locking.ALL_SET,
543 7ecd5e87 Thomas Thrainer
      }
544 7ecd5e87 Thomas Thrainer
545 7ecd5e87 Thomas Thrainer
    # Allocations should be stopped while this LU runs with node locks, but it
546 7ecd5e87 Thomas Thrainer
    # doesn't have to be exclusive
547 7ecd5e87 Thomas Thrainer
    self.share_locks[locking.LEVEL_NODE_ALLOC] = 1
548 7ecd5e87 Thomas Thrainer
549 7ecd5e87 Thomas Thrainer
  def Exec(self, feedback_fn):
550 7ecd5e87 Thomas Thrainer
    """Remove any export.
551 7ecd5e87 Thomas Thrainer

552 7ecd5e87 Thomas Thrainer
    """
553 da4a52a3 Thomas Thrainer
    (_, inst_name) = self.cfg.ExpandInstanceName(self.op.instance_name)
554 7ecd5e87 Thomas Thrainer
    # If the instance was not found we'll try with the name that was passed in.
555 7ecd5e87 Thomas Thrainer
    # This will only work if it was an FQDN, though.
556 7ecd5e87 Thomas Thrainer
    fqdn_warn = False
557 da4a52a3 Thomas Thrainer
    if not inst_name:
558 7ecd5e87 Thomas Thrainer
      fqdn_warn = True
559 da4a52a3 Thomas Thrainer
      inst_name = self.op.instance_name
560 7ecd5e87 Thomas Thrainer
561 7ecd5e87 Thomas Thrainer
    locked_nodes = self.owned_locks(locking.LEVEL_NODE)
562 7ecd5e87 Thomas Thrainer
    exportlist = self.rpc.call_export_list(locked_nodes)
563 7ecd5e87 Thomas Thrainer
    found = False
564 1c3231aa Thomas Thrainer
    for node_uuid in exportlist:
565 1c3231aa Thomas Thrainer
      msg = exportlist[node_uuid].fail_msg
566 7ecd5e87 Thomas Thrainer
      if msg:
567 1c3231aa Thomas Thrainer
        self.LogWarning("Failed to query node %s (continuing): %s",
568 1c3231aa Thomas Thrainer
                        self.cfg.GetNodeName(node_uuid), msg)
569 7ecd5e87 Thomas Thrainer
        continue
570 da4a52a3 Thomas Thrainer
      if inst_name in exportlist[node_uuid].payload:
571 7ecd5e87 Thomas Thrainer
        found = True
572 da4a52a3 Thomas Thrainer
        result = self.rpc.call_export_remove(node_uuid, inst_name)
573 7ecd5e87 Thomas Thrainer
        msg = result.fail_msg
574 7ecd5e87 Thomas Thrainer
        if msg:
575 7ecd5e87 Thomas Thrainer
          logging.error("Could not remove export for instance %s"
576 da4a52a3 Thomas Thrainer
                        " on node %s: %s", inst_name,
577 1c3231aa Thomas Thrainer
                        self.cfg.GetNodeName(node_uuid), msg)
578 7ecd5e87 Thomas Thrainer
579 7ecd5e87 Thomas Thrainer
    if fqdn_warn and not found:
580 7ecd5e87 Thomas Thrainer
      feedback_fn("Export not found. If trying to remove an export belonging"
581 7ecd5e87 Thomas Thrainer
                  " to a deleted instance please use its Fully Qualified"
582 7ecd5e87 Thomas Thrainer
                  " Domain Name.")