Statistics
| Branch: | Tag: | Revision:

root / lib / cmdlib / backup.py @ 1c4910f7

History | View | Annotate | Download (16.4 kB)

1 7ecd5e87 Thomas Thrainer
#
2 7ecd5e87 Thomas Thrainer
#
3 7ecd5e87 Thomas Thrainer
4 7ecd5e87 Thomas Thrainer
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 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 7ecd5e87 Thomas Thrainer
34 64981f25 Helga Velroyen
from ganeti.cmdlib.base import NoHooksLU, LogicalUnit
35 fbeb41e6 Helga Velroyen
from ganeti.cmdlib.common import CheckNodeOnline, \
36 1c3231aa Thomas Thrainer
  ExpandNodeUuidAndName
37 5eacbcae Thomas Thrainer
from ganeti.cmdlib.instance_storage import StartInstanceDisks, \
38 5eacbcae Thomas Thrainer
  ShutdownInstanceDisks
39 5eacbcae Thomas Thrainer
from ganeti.cmdlib.instance_utils import GetClusterDomainSecret, \
40 5eacbcae Thomas Thrainer
  BuildInstanceHookEnvByObject, CheckNodeNotDrained, RemoveInstance
41 7ecd5e87 Thomas Thrainer
42 7ecd5e87 Thomas Thrainer
43 7ecd5e87 Thomas Thrainer
class LUBackupPrepare(NoHooksLU):
44 7ecd5e87 Thomas Thrainer
  """Prepares an instance for an export and returns useful information.
45 7ecd5e87 Thomas Thrainer

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

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

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

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

103 7ecd5e87 Thomas Thrainer
    """
104 7ecd5e87 Thomas Thrainer
    self.x509_key_name = self.op.x509_key_name
105 7ecd5e87 Thomas Thrainer
    self.dest_x509_ca_pem = self.op.destination_x509_ca
106 7ecd5e87 Thomas Thrainer
107 7ecd5e87 Thomas Thrainer
    if self.op.mode == constants.EXPORT_MODE_REMOTE:
108 7ecd5e87 Thomas Thrainer
      if not self.x509_key_name:
109 7ecd5e87 Thomas Thrainer
        raise errors.OpPrereqError("Missing X509 key name for encryption",
110 7ecd5e87 Thomas Thrainer
                                   errors.ECODE_INVAL)
111 7ecd5e87 Thomas Thrainer
112 7ecd5e87 Thomas Thrainer
      if not self.dest_x509_ca_pem:
113 7ecd5e87 Thomas Thrainer
        raise errors.OpPrereqError("Missing destination X509 CA",
114 7ecd5e87 Thomas Thrainer
                                   errors.ECODE_INVAL)
115 7ecd5e87 Thomas Thrainer
116 7ecd5e87 Thomas Thrainer
  def ExpandNames(self):
117 7ecd5e87 Thomas Thrainer
    self._ExpandAndLockInstance()
118 7ecd5e87 Thomas Thrainer
119 7ecd5e87 Thomas Thrainer
    # Lock all nodes for local exports
120 7ecd5e87 Thomas Thrainer
    if self.op.mode == constants.EXPORT_MODE_LOCAL:
121 1c3231aa Thomas Thrainer
      (self.op.target_node_uuid, self.op.target_node) = \
122 1c3231aa Thomas Thrainer
        ExpandNodeUuidAndName(self.cfg, self.op.target_node_uuid,
123 1c3231aa Thomas Thrainer
                              self.op.target_node)
124 7ecd5e87 Thomas Thrainer
      # FIXME: lock only instance primary and destination node
125 7ecd5e87 Thomas Thrainer
      #
126 7ecd5e87 Thomas Thrainer
      # Sad but true, for now we have do lock all nodes, as we don't know where
127 7ecd5e87 Thomas Thrainer
      # the previous export might be, and in this LU we search for it and
128 7ecd5e87 Thomas Thrainer
      # remove it from its current node. In the future we could fix this by:
129 7ecd5e87 Thomas Thrainer
      #  - making a tasklet to search (share-lock all), then create the
130 7ecd5e87 Thomas Thrainer
      #    new one, then one to remove, after
131 7ecd5e87 Thomas Thrainer
      #  - removing the removal operation altogether
132 7ecd5e87 Thomas Thrainer
      self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
133 7ecd5e87 Thomas Thrainer
134 7ecd5e87 Thomas Thrainer
      # Allocations should be stopped while this LU runs with node locks, but
135 7ecd5e87 Thomas Thrainer
      # it doesn't have to be exclusive
136 7ecd5e87 Thomas Thrainer
      self.share_locks[locking.LEVEL_NODE_ALLOC] = 1
137 7ecd5e87 Thomas Thrainer
      self.needed_locks[locking.LEVEL_NODE_ALLOC] = locking.ALL_SET
138 7ecd5e87 Thomas Thrainer
139 7ecd5e87 Thomas Thrainer
  def DeclareLocks(self, level):
140 7ecd5e87 Thomas Thrainer
    """Last minute lock declaration."""
141 7ecd5e87 Thomas Thrainer
    # All nodes are locked anyway, so nothing to do here.
142 7ecd5e87 Thomas Thrainer
143 7ecd5e87 Thomas Thrainer
  def BuildHooksEnv(self):
144 7ecd5e87 Thomas Thrainer
    """Build hooks env.
145 7ecd5e87 Thomas Thrainer

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

148 7ecd5e87 Thomas Thrainer
    """
149 7ecd5e87 Thomas Thrainer
    env = {
150 7ecd5e87 Thomas Thrainer
      "EXPORT_MODE": self.op.mode,
151 7ecd5e87 Thomas Thrainer
      "EXPORT_NODE": self.op.target_node,
152 7ecd5e87 Thomas Thrainer
      "EXPORT_DO_SHUTDOWN": self.op.shutdown,
153 7ecd5e87 Thomas Thrainer
      "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
154 7ecd5e87 Thomas Thrainer
      # TODO: Generic function for boolean env variables
155 7ecd5e87 Thomas Thrainer
      "REMOVE_INSTANCE": str(bool(self.op.remove_instance)),
156 7ecd5e87 Thomas Thrainer
      }
157 7ecd5e87 Thomas Thrainer
158 5eacbcae Thomas Thrainer
    env.update(BuildInstanceHookEnvByObject(self, self.instance))
159 7ecd5e87 Thomas Thrainer
160 7ecd5e87 Thomas Thrainer
    return env
161 7ecd5e87 Thomas Thrainer
162 7ecd5e87 Thomas Thrainer
  def BuildHooksNodes(self):
163 7ecd5e87 Thomas Thrainer
    """Build hooks nodes.
164 7ecd5e87 Thomas Thrainer

165 7ecd5e87 Thomas Thrainer
    """
166 7ecd5e87 Thomas Thrainer
    nl = [self.cfg.GetMasterNode(), self.instance.primary_node]
167 7ecd5e87 Thomas Thrainer
168 7ecd5e87 Thomas Thrainer
    if self.op.mode == constants.EXPORT_MODE_LOCAL:
169 1c3231aa Thomas Thrainer
      nl.append(self.op.target_node_uuid)
170 7ecd5e87 Thomas Thrainer
171 7ecd5e87 Thomas Thrainer
    return (nl, nl)
172 7ecd5e87 Thomas Thrainer
173 7ecd5e87 Thomas Thrainer
  def CheckPrereq(self):
174 7ecd5e87 Thomas Thrainer
    """Check prerequisites.
175 7ecd5e87 Thomas Thrainer

176 7ecd5e87 Thomas Thrainer
    This checks that the instance and node names are valid.
177 7ecd5e87 Thomas Thrainer

178 7ecd5e87 Thomas Thrainer
    """
179 da4a52a3 Thomas Thrainer
    self.instance = self.cfg.GetInstanceInfoByName(self.op.instance_name)
180 7ecd5e87 Thomas Thrainer
    assert self.instance is not None, \
181 7ecd5e87 Thomas Thrainer
          "Cannot retrieve locked instance %s" % self.op.instance_name
182 5eacbcae Thomas Thrainer
    CheckNodeOnline(self, self.instance.primary_node)
183 7ecd5e87 Thomas Thrainer
184 7ecd5e87 Thomas Thrainer
    if (self.op.remove_instance and
185 7ecd5e87 Thomas Thrainer
        self.instance.admin_state == constants.ADMINST_UP and
186 7ecd5e87 Thomas Thrainer
        not self.op.shutdown):
187 7ecd5e87 Thomas Thrainer
      raise errors.OpPrereqError("Can not remove instance without shutting it"
188 7ecd5e87 Thomas Thrainer
                                 " down before", errors.ECODE_STATE)
189 7ecd5e87 Thomas Thrainer
190 7ecd5e87 Thomas Thrainer
    if self.op.mode == constants.EXPORT_MODE_LOCAL:
191 1c3231aa Thomas Thrainer
      self.dst_node = self.cfg.GetNodeInfo(self.op.target_node_uuid)
192 7ecd5e87 Thomas Thrainer
      assert self.dst_node is not None
193 7ecd5e87 Thomas Thrainer
194 1c3231aa Thomas Thrainer
      CheckNodeOnline(self, self.dst_node.uuid)
195 1c3231aa Thomas Thrainer
      CheckNodeNotDrained(self, self.dst_node.uuid)
196 7ecd5e87 Thomas Thrainer
197 7ecd5e87 Thomas Thrainer
      self._cds = None
198 7ecd5e87 Thomas Thrainer
      self.dest_disk_info = None
199 7ecd5e87 Thomas Thrainer
      self.dest_x509_ca = None
200 7ecd5e87 Thomas Thrainer
201 7ecd5e87 Thomas Thrainer
    elif self.op.mode == constants.EXPORT_MODE_REMOTE:
202 7ecd5e87 Thomas Thrainer
      self.dst_node = None
203 7ecd5e87 Thomas Thrainer
204 7ecd5e87 Thomas Thrainer
      if len(self.op.target_node) != len(self.instance.disks):
205 7ecd5e87 Thomas Thrainer
        raise errors.OpPrereqError(("Received destination information for %s"
206 7ecd5e87 Thomas Thrainer
                                    " disks, but instance %s has %s disks") %
207 d0d7d7cf Thomas Thrainer
                                   (len(self.op.target_node),
208 d0d7d7cf Thomas Thrainer
                                    self.op.instance_name,
209 7ecd5e87 Thomas Thrainer
                                    len(self.instance.disks)),
210 7ecd5e87 Thomas Thrainer
                                   errors.ECODE_INVAL)
211 7ecd5e87 Thomas Thrainer
212 5eacbcae Thomas Thrainer
      cds = GetClusterDomainSecret()
213 7ecd5e87 Thomas Thrainer
214 7ecd5e87 Thomas Thrainer
      # Check X509 key name
215 7ecd5e87 Thomas Thrainer
      try:
216 7ecd5e87 Thomas Thrainer
        (key_name, hmac_digest, hmac_salt) = self.x509_key_name
217 7ecd5e87 Thomas Thrainer
      except (TypeError, ValueError), err:
218 7ecd5e87 Thomas Thrainer
        raise errors.OpPrereqError("Invalid data for X509 key name: %s" % err,
219 7ecd5e87 Thomas Thrainer
                                   errors.ECODE_INVAL)
220 7ecd5e87 Thomas Thrainer
221 7ecd5e87 Thomas Thrainer
      if not utils.VerifySha1Hmac(cds, key_name, hmac_digest, salt=hmac_salt):
222 7ecd5e87 Thomas Thrainer
        raise errors.OpPrereqError("HMAC for X509 key name is wrong",
223 7ecd5e87 Thomas Thrainer
                                   errors.ECODE_INVAL)
224 7ecd5e87 Thomas Thrainer
225 7ecd5e87 Thomas Thrainer
      # Load and verify CA
226 7ecd5e87 Thomas Thrainer
      try:
227 7ecd5e87 Thomas Thrainer
        (cert, _) = utils.LoadSignedX509Certificate(self.dest_x509_ca_pem, cds)
228 7ecd5e87 Thomas Thrainer
      except OpenSSL.crypto.Error, err:
229 7ecd5e87 Thomas Thrainer
        raise errors.OpPrereqError("Unable to load destination X509 CA (%s)" %
230 7ecd5e87 Thomas Thrainer
                                   (err, ), errors.ECODE_INVAL)
231 7ecd5e87 Thomas Thrainer
232 7ecd5e87 Thomas Thrainer
      (errcode, msg) = utils.VerifyX509Certificate(cert, None, None)
233 7ecd5e87 Thomas Thrainer
      if errcode is not None:
234 7ecd5e87 Thomas Thrainer
        raise errors.OpPrereqError("Invalid destination X509 CA (%s)" %
235 7ecd5e87 Thomas Thrainer
                                   (msg, ), errors.ECODE_INVAL)
236 7ecd5e87 Thomas Thrainer
237 7ecd5e87 Thomas Thrainer
      self.dest_x509_ca = cert
238 7ecd5e87 Thomas Thrainer
239 7ecd5e87 Thomas Thrainer
      # Verify target information
240 7ecd5e87 Thomas Thrainer
      disk_info = []
241 7ecd5e87 Thomas Thrainer
      for idx, disk_data in enumerate(self.op.target_node):
242 7ecd5e87 Thomas Thrainer
        try:
243 7ecd5e87 Thomas Thrainer
          (host, port, magic) = \
244 7ecd5e87 Thomas Thrainer
            masterd.instance.CheckRemoteExportDiskInfo(cds, idx, disk_data)
245 7ecd5e87 Thomas Thrainer
        except errors.GenericError, err:
246 7ecd5e87 Thomas Thrainer
          raise errors.OpPrereqError("Target info for disk %s: %s" %
247 7ecd5e87 Thomas Thrainer
                                     (idx, err), errors.ECODE_INVAL)
248 7ecd5e87 Thomas Thrainer
249 7ecd5e87 Thomas Thrainer
        disk_info.append((host, port, magic))
250 7ecd5e87 Thomas Thrainer
251 7ecd5e87 Thomas Thrainer
      assert len(disk_info) == len(self.op.target_node)
252 7ecd5e87 Thomas Thrainer
      self.dest_disk_info = disk_info
253 7ecd5e87 Thomas Thrainer
254 7ecd5e87 Thomas Thrainer
    else:
255 7ecd5e87 Thomas Thrainer
      raise errors.ProgrammerError("Unhandled export mode %r" %
256 7ecd5e87 Thomas Thrainer
                                   self.op.mode)
257 7ecd5e87 Thomas Thrainer
258 7ecd5e87 Thomas Thrainer
    # instance disk type verification
259 7ecd5e87 Thomas Thrainer
    # TODO: Implement export support for file-based disks
260 7ecd5e87 Thomas Thrainer
    for disk in self.instance.disks:
261 a09639d1 Santi Raffa
      if disk.dev_type in constants.DTS_FILEBASED:
262 7ecd5e87 Thomas Thrainer
        raise errors.OpPrereqError("Export not supported for instances with"
263 7ecd5e87 Thomas Thrainer
                                   " file-based disks", errors.ECODE_INVAL)
264 7ecd5e87 Thomas Thrainer
265 7ecd5e87 Thomas Thrainer
  def _CleanupExports(self, feedback_fn):
266 7ecd5e87 Thomas Thrainer
    """Removes exports of current instance from all other nodes.
267 7ecd5e87 Thomas Thrainer

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

271 7ecd5e87 Thomas Thrainer
    """
272 7ecd5e87 Thomas Thrainer
    assert self.op.mode != constants.EXPORT_MODE_REMOTE
273 7ecd5e87 Thomas Thrainer
274 1c3231aa Thomas Thrainer
    node_uuids = self.cfg.GetNodeList()
275 1c3231aa Thomas Thrainer
    node_uuids.remove(self.dst_node.uuid)
276 7ecd5e87 Thomas Thrainer
277 7ecd5e87 Thomas Thrainer
    # on one-node clusters nodelist will be empty after the removal
278 7ecd5e87 Thomas Thrainer
    # if we proceed the backup would be removed because OpBackupQuery
279 7ecd5e87 Thomas Thrainer
    # substitutes an empty list with the full cluster node list.
280 7ecd5e87 Thomas Thrainer
    iname = self.instance.name
281 1c3231aa Thomas Thrainer
    if node_uuids:
282 7ecd5e87 Thomas Thrainer
      feedback_fn("Removing old exports for instance %s" % iname)
283 1c3231aa Thomas Thrainer
      exportlist = self.rpc.call_export_list(node_uuids)
284 1c3231aa Thomas Thrainer
      for node_uuid in exportlist:
285 1c3231aa Thomas Thrainer
        if exportlist[node_uuid].fail_msg:
286 7ecd5e87 Thomas Thrainer
          continue
287 1c3231aa Thomas Thrainer
        if iname in exportlist[node_uuid].payload:
288 1c3231aa Thomas Thrainer
          msg = self.rpc.call_export_remove(node_uuid, iname).fail_msg
289 7ecd5e87 Thomas Thrainer
          if msg:
290 7ecd5e87 Thomas Thrainer
            self.LogWarning("Could not remove older export for instance %s"
291 1c3231aa Thomas Thrainer
                            " on node %s: %s", iname,
292 1c3231aa Thomas Thrainer
                            self.cfg.GetNodeName(node_uuid), msg)
293 7ecd5e87 Thomas Thrainer
294 7ecd5e87 Thomas Thrainer
  def Exec(self, feedback_fn):
295 7ecd5e87 Thomas Thrainer
    """Export an instance to an image in the cluster.
296 7ecd5e87 Thomas Thrainer

297 7ecd5e87 Thomas Thrainer
    """
298 7ecd5e87 Thomas Thrainer
    assert self.op.mode in constants.EXPORT_MODES
299 7ecd5e87 Thomas Thrainer
300 d0d7d7cf Thomas Thrainer
    src_node_uuid = self.instance.primary_node
301 7ecd5e87 Thomas Thrainer
302 7ecd5e87 Thomas Thrainer
    if self.op.shutdown:
303 7ecd5e87 Thomas Thrainer
      # shutdown the instance, but not the disks
304 d0d7d7cf Thomas Thrainer
      feedback_fn("Shutting down instance %s" % self.instance.name)
305 d0d7d7cf Thomas Thrainer
      result = self.rpc.call_instance_shutdown(src_node_uuid, self.instance,
306 7ecd5e87 Thomas Thrainer
                                               self.op.shutdown_timeout,
307 7ecd5e87 Thomas Thrainer
                                               self.op.reason)
308 7ecd5e87 Thomas Thrainer
      # TODO: Maybe ignore failures if ignore_remove_failures is set
309 7ecd5e87 Thomas Thrainer
      result.Raise("Could not shutdown instance %s on"
310 d0d7d7cf Thomas Thrainer
                   " node %s" % (self.instance.name,
311 1c3231aa Thomas Thrainer
                                 self.cfg.GetNodeName(src_node_uuid)))
312 7ecd5e87 Thomas Thrainer
313 d0d7d7cf Thomas Thrainer
    activate_disks = not self.instance.disks_active
314 7ecd5e87 Thomas Thrainer
315 7ecd5e87 Thomas Thrainer
    if activate_disks:
316 70b634e6 Thomas Thrainer
      # Activate the instance disks if we're exporting a stopped instance
317 d0d7d7cf Thomas Thrainer
      feedback_fn("Activating disks for %s" % self.instance.name)
318 d0d7d7cf Thomas Thrainer
      StartInstanceDisks(self, self.instance, None)
319 7ecd5e87 Thomas Thrainer
320 7ecd5e87 Thomas Thrainer
    try:
321 7ecd5e87 Thomas Thrainer
      helper = masterd.instance.ExportInstanceHelper(self, feedback_fn,
322 d0d7d7cf Thomas Thrainer
                                                     self.instance)
323 7ecd5e87 Thomas Thrainer
324 7ecd5e87 Thomas Thrainer
      helper.CreateSnapshots()
325 7ecd5e87 Thomas Thrainer
      try:
326 7ecd5e87 Thomas Thrainer
        if (self.op.shutdown and
327 d0d7d7cf Thomas Thrainer
            self.instance.admin_state == constants.ADMINST_UP and
328 7ecd5e87 Thomas Thrainer
            not self.op.remove_instance):
329 7ecd5e87 Thomas Thrainer
          assert not activate_disks
330 d0d7d7cf Thomas Thrainer
          feedback_fn("Starting instance %s" % self.instance.name)
331 1c3231aa Thomas Thrainer
          result = self.rpc.call_instance_start(src_node_uuid,
332 d0d7d7cf Thomas Thrainer
                                                (self.instance, None, None),
333 d0d7d7cf Thomas Thrainer
                                                False, self.op.reason)
334 7ecd5e87 Thomas Thrainer
          msg = result.fail_msg
335 7ecd5e87 Thomas Thrainer
          if msg:
336 7ecd5e87 Thomas Thrainer
            feedback_fn("Failed to start instance: %s" % msg)
337 d0d7d7cf Thomas Thrainer
            ShutdownInstanceDisks(self, self.instance)
338 7ecd5e87 Thomas Thrainer
            raise errors.OpExecError("Could not start instance: %s" % msg)
339 7ecd5e87 Thomas Thrainer
340 7ecd5e87 Thomas Thrainer
        if self.op.mode == constants.EXPORT_MODE_LOCAL:
341 896cc964 Thomas Thrainer
          (fin_resu, dresults) = helper.LocalExport(self.dst_node,
342 896cc964 Thomas Thrainer
                                                    self.op.compress)
343 7ecd5e87 Thomas Thrainer
        elif self.op.mode == constants.EXPORT_MODE_REMOTE:
344 7ecd5e87 Thomas Thrainer
          connect_timeout = constants.RIE_CONNECT_TIMEOUT
345 7ecd5e87 Thomas Thrainer
          timeouts = masterd.instance.ImportExportTimeouts(connect_timeout)
346 7ecd5e87 Thomas Thrainer
347 7ecd5e87 Thomas Thrainer
          (key_name, _, _) = self.x509_key_name
348 7ecd5e87 Thomas Thrainer
349 7ecd5e87 Thomas Thrainer
          dest_ca_pem = \
350 7ecd5e87 Thomas Thrainer
            OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM,
351 7ecd5e87 Thomas Thrainer
                                            self.dest_x509_ca)
352 7ecd5e87 Thomas Thrainer
353 7ecd5e87 Thomas Thrainer
          (fin_resu, dresults) = helper.RemoteExport(self.dest_disk_info,
354 7ecd5e87 Thomas Thrainer
                                                     key_name, dest_ca_pem,
355 258de3fe Thomas Thrainer
                                                     self.op.compress,
356 7ecd5e87 Thomas Thrainer
                                                     timeouts)
357 7ecd5e87 Thomas Thrainer
      finally:
358 7ecd5e87 Thomas Thrainer
        helper.Cleanup()
359 7ecd5e87 Thomas Thrainer
360 7ecd5e87 Thomas Thrainer
      # Check for backwards compatibility
361 d0d7d7cf Thomas Thrainer
      assert len(dresults) == len(self.instance.disks)
362 7ecd5e87 Thomas Thrainer
      assert compat.all(isinstance(i, bool) for i in dresults), \
363 7ecd5e87 Thomas Thrainer
             "Not all results are boolean: %r" % dresults
364 7ecd5e87 Thomas Thrainer
365 7ecd5e87 Thomas Thrainer
    finally:
366 7ecd5e87 Thomas Thrainer
      if activate_disks:
367 d0d7d7cf Thomas Thrainer
        feedback_fn("Deactivating disks for %s" % self.instance.name)
368 d0d7d7cf Thomas Thrainer
        ShutdownInstanceDisks(self, self.instance)
369 7ecd5e87 Thomas Thrainer
370 7ecd5e87 Thomas Thrainer
    if not (compat.all(dresults) and fin_resu):
371 7ecd5e87 Thomas Thrainer
      failures = []
372 7ecd5e87 Thomas Thrainer
      if not fin_resu:
373 7ecd5e87 Thomas Thrainer
        failures.append("export finalization")
374 7ecd5e87 Thomas Thrainer
      if not compat.all(dresults):
375 7ecd5e87 Thomas Thrainer
        fdsk = utils.CommaJoin(idx for (idx, dsk) in enumerate(dresults)
376 7ecd5e87 Thomas Thrainer
                               if not dsk)
377 7ecd5e87 Thomas Thrainer
        failures.append("disk export: disk(s) %s" % fdsk)
378 7ecd5e87 Thomas Thrainer
379 7ecd5e87 Thomas Thrainer
      raise errors.OpExecError("Export failed, errors in %s" %
380 7ecd5e87 Thomas Thrainer
                               utils.CommaJoin(failures))
381 7ecd5e87 Thomas Thrainer
382 7ecd5e87 Thomas Thrainer
    # At this point, the export was successful, we can cleanup/finish
383 7ecd5e87 Thomas Thrainer
384 7ecd5e87 Thomas Thrainer
    # Remove instance if requested
385 7ecd5e87 Thomas Thrainer
    if self.op.remove_instance:
386 d0d7d7cf Thomas Thrainer
      feedback_fn("Removing instance %s" % self.instance.name)
387 d0d7d7cf Thomas Thrainer
      RemoveInstance(self, feedback_fn, self.instance,
388 5eacbcae Thomas Thrainer
                     self.op.ignore_remove_failures)
389 7ecd5e87 Thomas Thrainer
390 7ecd5e87 Thomas Thrainer
    if self.op.mode == constants.EXPORT_MODE_LOCAL:
391 7ecd5e87 Thomas Thrainer
      self._CleanupExports(feedback_fn)
392 7ecd5e87 Thomas Thrainer
393 7ecd5e87 Thomas Thrainer
    return fin_resu, dresults
394 7ecd5e87 Thomas Thrainer
395 7ecd5e87 Thomas Thrainer
396 7ecd5e87 Thomas Thrainer
class LUBackupRemove(NoHooksLU):
397 7ecd5e87 Thomas Thrainer
  """Remove exports related to the named instance.
398 7ecd5e87 Thomas Thrainer

399 7ecd5e87 Thomas Thrainer
  """
400 7ecd5e87 Thomas Thrainer
  REQ_BGL = False
401 7ecd5e87 Thomas Thrainer
402 7ecd5e87 Thomas Thrainer
  def ExpandNames(self):
403 7ecd5e87 Thomas Thrainer
    self.needed_locks = {
404 7ecd5e87 Thomas Thrainer
      # We need all nodes to be locked in order for RemoveExport to work, but
405 7ecd5e87 Thomas Thrainer
      # we don't need to lock the instance itself, as nothing will happen to it
406 7ecd5e87 Thomas Thrainer
      # (and we can remove exports also for a removed instance)
407 7ecd5e87 Thomas Thrainer
      locking.LEVEL_NODE: locking.ALL_SET,
408 7ecd5e87 Thomas Thrainer
409 7ecd5e87 Thomas Thrainer
      # Removing backups is quick, so blocking allocations is justified
410 7ecd5e87 Thomas Thrainer
      locking.LEVEL_NODE_ALLOC: locking.ALL_SET,
411 7ecd5e87 Thomas Thrainer
      }
412 7ecd5e87 Thomas Thrainer
413 7ecd5e87 Thomas Thrainer
    # Allocations should be stopped while this LU runs with node locks, but it
414 7ecd5e87 Thomas Thrainer
    # doesn't have to be exclusive
415 7ecd5e87 Thomas Thrainer
    self.share_locks[locking.LEVEL_NODE_ALLOC] = 1
416 7ecd5e87 Thomas Thrainer
417 7ecd5e87 Thomas Thrainer
  def Exec(self, feedback_fn):
418 7ecd5e87 Thomas Thrainer
    """Remove any export.
419 7ecd5e87 Thomas Thrainer

420 7ecd5e87 Thomas Thrainer
    """
421 da4a52a3 Thomas Thrainer
    (_, inst_name) = self.cfg.ExpandInstanceName(self.op.instance_name)
422 7ecd5e87 Thomas Thrainer
    # If the instance was not found we'll try with the name that was passed in.
423 7ecd5e87 Thomas Thrainer
    # This will only work if it was an FQDN, though.
424 7ecd5e87 Thomas Thrainer
    fqdn_warn = False
425 da4a52a3 Thomas Thrainer
    if not inst_name:
426 7ecd5e87 Thomas Thrainer
      fqdn_warn = True
427 da4a52a3 Thomas Thrainer
      inst_name = self.op.instance_name
428 7ecd5e87 Thomas Thrainer
429 7ecd5e87 Thomas Thrainer
    locked_nodes = self.owned_locks(locking.LEVEL_NODE)
430 7ecd5e87 Thomas Thrainer
    exportlist = self.rpc.call_export_list(locked_nodes)
431 7ecd5e87 Thomas Thrainer
    found = False
432 1c3231aa Thomas Thrainer
    for node_uuid in exportlist:
433 1c3231aa Thomas Thrainer
      msg = exportlist[node_uuid].fail_msg
434 7ecd5e87 Thomas Thrainer
      if msg:
435 1c3231aa Thomas Thrainer
        self.LogWarning("Failed to query node %s (continuing): %s",
436 1c3231aa Thomas Thrainer
                        self.cfg.GetNodeName(node_uuid), msg)
437 7ecd5e87 Thomas Thrainer
        continue
438 da4a52a3 Thomas Thrainer
      if inst_name in exportlist[node_uuid].payload:
439 7ecd5e87 Thomas Thrainer
        found = True
440 da4a52a3 Thomas Thrainer
        result = self.rpc.call_export_remove(node_uuid, inst_name)
441 7ecd5e87 Thomas Thrainer
        msg = result.fail_msg
442 7ecd5e87 Thomas Thrainer
        if msg:
443 7ecd5e87 Thomas Thrainer
          logging.error("Could not remove export for instance %s"
444 da4a52a3 Thomas Thrainer
                        " on node %s: %s", inst_name,
445 1c3231aa Thomas Thrainer
                        self.cfg.GetNodeName(node_uuid), msg)
446 7ecd5e87 Thomas Thrainer
447 7ecd5e87 Thomas Thrainer
    if fqdn_warn and not found:
448 7ecd5e87 Thomas Thrainer
      feedback_fn("Export not found. If trying to remove an export belonging"
449 7ecd5e87 Thomas Thrainer
                  " to a deleted instance please use its Fully Qualified"
450 7ecd5e87 Thomas Thrainer
                  " Domain Name.")