Statistics
| Branch: | Tag: | Revision:

root / lib / cmdlib / backup.py @ 2a02d6fe

History | View | Annotate | Download (17 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 query
33 7ecd5e87 Thomas Thrainer
from ganeti import utils
34 7ecd5e87 Thomas Thrainer
35 5eacbcae Thomas Thrainer
from ganeti.cmdlib.base import QueryBase, NoHooksLU, LogicalUnit
36 fbeb41e6 Helga Velroyen
from ganeti.cmdlib.common import CheckNodeOnline, \
37 1c3231aa Thomas Thrainer
  ExpandNodeUuidAndName
38 5eacbcae Thomas Thrainer
from ganeti.cmdlib.instance_storage import StartInstanceDisks, \
39 5eacbcae Thomas Thrainer
  ShutdownInstanceDisks
40 5eacbcae Thomas Thrainer
from ganeti.cmdlib.instance_utils import GetClusterDomainSecret, \
41 5eacbcae Thomas Thrainer
  BuildInstanceHookEnvByObject, CheckNodeNotDrained, RemoveInstance
42 7ecd5e87 Thomas Thrainer
43 7ecd5e87 Thomas Thrainer
44 5eacbcae Thomas Thrainer
class ExportQuery(QueryBase):
45 7ecd5e87 Thomas Thrainer
  FIELDS = query.EXPORT_FIELDS
46 7ecd5e87 Thomas Thrainer
47 7ecd5e87 Thomas Thrainer
  #: The node name is not a unique key for this query
48 7ecd5e87 Thomas Thrainer
  SORT_FIELD = "node"
49 7ecd5e87 Thomas Thrainer
50 7ecd5e87 Thomas Thrainer
  def ExpandNames(self, lu):
51 fbeb41e6 Helga Velroyen
    raise NotImplementedError
52 7ecd5e87 Thomas Thrainer
53 7ecd5e87 Thomas Thrainer
  def DeclareLocks(self, lu, level):
54 7ecd5e87 Thomas Thrainer
    pass
55 7ecd5e87 Thomas Thrainer
56 7ecd5e87 Thomas Thrainer
  def _GetQueryData(self, lu):
57 fbeb41e6 Helga Velroyen
    raise NotImplementedError
58 7ecd5e87 Thomas Thrainer
59 7ecd5e87 Thomas Thrainer
60 7ecd5e87 Thomas Thrainer
class LUBackupQuery(NoHooksLU):
61 7ecd5e87 Thomas Thrainer
  """Query the exports list
62 7ecd5e87 Thomas Thrainer

63 7ecd5e87 Thomas Thrainer
  """
64 7ecd5e87 Thomas Thrainer
  REQ_BGL = False
65 7ecd5e87 Thomas Thrainer
66 7ecd5e87 Thomas Thrainer
  def CheckArguments(self):
67 fbeb41e6 Helga Velroyen
    raise NotImplementedError
68 7ecd5e87 Thomas Thrainer
69 7ecd5e87 Thomas Thrainer
  def ExpandNames(self):
70 fbeb41e6 Helga Velroyen
    raise NotImplementedError
71 7ecd5e87 Thomas Thrainer
72 7ecd5e87 Thomas Thrainer
  def DeclareLocks(self, level):
73 fbeb41e6 Helga Velroyen
    raise NotImplementedError
74 7ecd5e87 Thomas Thrainer
75 7ecd5e87 Thomas Thrainer
  def Exec(self, feedback_fn):
76 fbeb41e6 Helga Velroyen
    raise NotImplementedError
77 7ecd5e87 Thomas Thrainer
78 7ecd5e87 Thomas Thrainer
79 7ecd5e87 Thomas Thrainer
class LUBackupPrepare(NoHooksLU):
80 7ecd5e87 Thomas Thrainer
  """Prepares an instance for an export and returns useful information.
81 7ecd5e87 Thomas Thrainer

82 7ecd5e87 Thomas Thrainer
  """
83 7ecd5e87 Thomas Thrainer
  REQ_BGL = False
84 7ecd5e87 Thomas Thrainer
85 7ecd5e87 Thomas Thrainer
  def ExpandNames(self):
86 7ecd5e87 Thomas Thrainer
    self._ExpandAndLockInstance()
87 7ecd5e87 Thomas Thrainer
88 7ecd5e87 Thomas Thrainer
  def CheckPrereq(self):
89 7ecd5e87 Thomas Thrainer
    """Check prerequisites.
90 7ecd5e87 Thomas Thrainer

91 7ecd5e87 Thomas Thrainer
    """
92 da4a52a3 Thomas Thrainer
    self.instance = self.cfg.GetInstanceInfoByName(self.op.instance_name)
93 7ecd5e87 Thomas Thrainer
    assert self.instance is not None, \
94 7ecd5e87 Thomas Thrainer
          "Cannot retrieve locked instance %s" % self.op.instance_name
95 5eacbcae Thomas Thrainer
    CheckNodeOnline(self, self.instance.primary_node)
96 7ecd5e87 Thomas Thrainer
97 5eacbcae Thomas Thrainer
    self._cds = GetClusterDomainSecret()
98 7ecd5e87 Thomas Thrainer
99 7ecd5e87 Thomas Thrainer
  def Exec(self, feedback_fn):
100 7ecd5e87 Thomas Thrainer
    """Prepares an instance for an export.
101 7ecd5e87 Thomas Thrainer

102 7ecd5e87 Thomas Thrainer
    """
103 7ecd5e87 Thomas Thrainer
    if self.op.mode == constants.EXPORT_MODE_REMOTE:
104 7ecd5e87 Thomas Thrainer
      salt = utils.GenerateSecret(8)
105 7ecd5e87 Thomas Thrainer
106 1c3231aa Thomas Thrainer
      feedback_fn("Generating X509 certificate on %s" %
107 d0d7d7cf Thomas Thrainer
                  self.cfg.GetNodeName(self.instance.primary_node))
108 d0d7d7cf Thomas Thrainer
      result = self.rpc.call_x509_cert_create(self.instance.primary_node,
109 7ecd5e87 Thomas Thrainer
                                              constants.RIE_CERT_VALIDITY)
110 1c3231aa Thomas Thrainer
      result.Raise("Can't create X509 key and certificate on %s" %
111 1c3231aa Thomas Thrainer
                   self.cfg.GetNodeName(result.node))
112 7ecd5e87 Thomas Thrainer
113 7ecd5e87 Thomas Thrainer
      (name, cert_pem) = result.payload
114 7ecd5e87 Thomas Thrainer
115 7ecd5e87 Thomas Thrainer
      cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
116 7ecd5e87 Thomas Thrainer
                                             cert_pem)
117 7ecd5e87 Thomas Thrainer
118 7ecd5e87 Thomas Thrainer
      return {
119 7ecd5e87 Thomas Thrainer
        "handshake": masterd.instance.ComputeRemoteExportHandshake(self._cds),
120 7ecd5e87 Thomas Thrainer
        "x509_key_name": (name, utils.Sha1Hmac(self._cds, name, salt=salt),
121 7ecd5e87 Thomas Thrainer
                          salt),
122 7ecd5e87 Thomas Thrainer
        "x509_ca": utils.SignX509Certificate(cert, self._cds, salt),
123 7ecd5e87 Thomas Thrainer
        }
124 7ecd5e87 Thomas Thrainer
125 7ecd5e87 Thomas Thrainer
    return None
126 7ecd5e87 Thomas Thrainer
127 7ecd5e87 Thomas Thrainer
128 7ecd5e87 Thomas Thrainer
class LUBackupExport(LogicalUnit):
129 7ecd5e87 Thomas Thrainer
  """Export an instance to an image in the cluster.
130 7ecd5e87 Thomas Thrainer

131 7ecd5e87 Thomas Thrainer
  """
132 7ecd5e87 Thomas Thrainer
  HPATH = "instance-export"
133 7ecd5e87 Thomas Thrainer
  HTYPE = constants.HTYPE_INSTANCE
134 7ecd5e87 Thomas Thrainer
  REQ_BGL = False
135 7ecd5e87 Thomas Thrainer
136 7ecd5e87 Thomas Thrainer
  def CheckArguments(self):
137 7ecd5e87 Thomas Thrainer
    """Check the arguments.
138 7ecd5e87 Thomas Thrainer

139 7ecd5e87 Thomas Thrainer
    """
140 7ecd5e87 Thomas Thrainer
    self.x509_key_name = self.op.x509_key_name
141 7ecd5e87 Thomas Thrainer
    self.dest_x509_ca_pem = self.op.destination_x509_ca
142 7ecd5e87 Thomas Thrainer
143 7ecd5e87 Thomas Thrainer
    if self.op.mode == constants.EXPORT_MODE_REMOTE:
144 7ecd5e87 Thomas Thrainer
      if not self.x509_key_name:
145 7ecd5e87 Thomas Thrainer
        raise errors.OpPrereqError("Missing X509 key name for encryption",
146 7ecd5e87 Thomas Thrainer
                                   errors.ECODE_INVAL)
147 7ecd5e87 Thomas Thrainer
148 7ecd5e87 Thomas Thrainer
      if not self.dest_x509_ca_pem:
149 7ecd5e87 Thomas Thrainer
        raise errors.OpPrereqError("Missing destination X509 CA",
150 7ecd5e87 Thomas Thrainer
                                   errors.ECODE_INVAL)
151 7ecd5e87 Thomas Thrainer
152 7ecd5e87 Thomas Thrainer
  def ExpandNames(self):
153 7ecd5e87 Thomas Thrainer
    self._ExpandAndLockInstance()
154 7ecd5e87 Thomas Thrainer
155 7ecd5e87 Thomas Thrainer
    # Lock all nodes for local exports
156 7ecd5e87 Thomas Thrainer
    if self.op.mode == constants.EXPORT_MODE_LOCAL:
157 1c3231aa Thomas Thrainer
      (self.op.target_node_uuid, self.op.target_node) = \
158 1c3231aa Thomas Thrainer
        ExpandNodeUuidAndName(self.cfg, self.op.target_node_uuid,
159 1c3231aa Thomas Thrainer
                              self.op.target_node)
160 7ecd5e87 Thomas Thrainer
      # FIXME: lock only instance primary and destination node
161 7ecd5e87 Thomas Thrainer
      #
162 7ecd5e87 Thomas Thrainer
      # Sad but true, for now we have do lock all nodes, as we don't know where
163 7ecd5e87 Thomas Thrainer
      # the previous export might be, and in this LU we search for it and
164 7ecd5e87 Thomas Thrainer
      # remove it from its current node. In the future we could fix this by:
165 7ecd5e87 Thomas Thrainer
      #  - making a tasklet to search (share-lock all), then create the
166 7ecd5e87 Thomas Thrainer
      #    new one, then one to remove, after
167 7ecd5e87 Thomas Thrainer
      #  - removing the removal operation altogether
168 7ecd5e87 Thomas Thrainer
      self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
169 7ecd5e87 Thomas Thrainer
170 7ecd5e87 Thomas Thrainer
      # Allocations should be stopped while this LU runs with node locks, but
171 7ecd5e87 Thomas Thrainer
      # it doesn't have to be exclusive
172 7ecd5e87 Thomas Thrainer
      self.share_locks[locking.LEVEL_NODE_ALLOC] = 1
173 7ecd5e87 Thomas Thrainer
      self.needed_locks[locking.LEVEL_NODE_ALLOC] = locking.ALL_SET
174 7ecd5e87 Thomas Thrainer
175 7ecd5e87 Thomas Thrainer
  def DeclareLocks(self, level):
176 7ecd5e87 Thomas Thrainer
    """Last minute lock declaration."""
177 7ecd5e87 Thomas Thrainer
    # All nodes are locked anyway, so nothing to do here.
178 7ecd5e87 Thomas Thrainer
179 7ecd5e87 Thomas Thrainer
  def BuildHooksEnv(self):
180 7ecd5e87 Thomas Thrainer
    """Build hooks env.
181 7ecd5e87 Thomas Thrainer

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

184 7ecd5e87 Thomas Thrainer
    """
185 7ecd5e87 Thomas Thrainer
    env = {
186 7ecd5e87 Thomas Thrainer
      "EXPORT_MODE": self.op.mode,
187 7ecd5e87 Thomas Thrainer
      "EXPORT_NODE": self.op.target_node,
188 7ecd5e87 Thomas Thrainer
      "EXPORT_DO_SHUTDOWN": self.op.shutdown,
189 7ecd5e87 Thomas Thrainer
      "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
190 7ecd5e87 Thomas Thrainer
      # TODO: Generic function for boolean env variables
191 7ecd5e87 Thomas Thrainer
      "REMOVE_INSTANCE": str(bool(self.op.remove_instance)),
192 7ecd5e87 Thomas Thrainer
      }
193 7ecd5e87 Thomas Thrainer
194 5eacbcae Thomas Thrainer
    env.update(BuildInstanceHookEnvByObject(self, self.instance))
195 7ecd5e87 Thomas Thrainer
196 7ecd5e87 Thomas Thrainer
    return env
197 7ecd5e87 Thomas Thrainer
198 7ecd5e87 Thomas Thrainer
  def BuildHooksNodes(self):
199 7ecd5e87 Thomas Thrainer
    """Build hooks nodes.
200 7ecd5e87 Thomas Thrainer

201 7ecd5e87 Thomas Thrainer
    """
202 7ecd5e87 Thomas Thrainer
    nl = [self.cfg.GetMasterNode(), self.instance.primary_node]
203 7ecd5e87 Thomas Thrainer
204 7ecd5e87 Thomas Thrainer
    if self.op.mode == constants.EXPORT_MODE_LOCAL:
205 1c3231aa Thomas Thrainer
      nl.append(self.op.target_node_uuid)
206 7ecd5e87 Thomas Thrainer
207 7ecd5e87 Thomas Thrainer
    return (nl, nl)
208 7ecd5e87 Thomas Thrainer
209 7ecd5e87 Thomas Thrainer
  def CheckPrereq(self):
210 7ecd5e87 Thomas Thrainer
    """Check prerequisites.
211 7ecd5e87 Thomas Thrainer

212 7ecd5e87 Thomas Thrainer
    This checks that the instance and node names are valid.
213 7ecd5e87 Thomas Thrainer

214 7ecd5e87 Thomas Thrainer
    """
215 da4a52a3 Thomas Thrainer
    self.instance = self.cfg.GetInstanceInfoByName(self.op.instance_name)
216 7ecd5e87 Thomas Thrainer
    assert self.instance is not None, \
217 7ecd5e87 Thomas Thrainer
          "Cannot retrieve locked instance %s" % self.op.instance_name
218 5eacbcae Thomas Thrainer
    CheckNodeOnline(self, self.instance.primary_node)
219 7ecd5e87 Thomas Thrainer
220 7ecd5e87 Thomas Thrainer
    if (self.op.remove_instance and
221 7ecd5e87 Thomas Thrainer
        self.instance.admin_state == constants.ADMINST_UP and
222 7ecd5e87 Thomas Thrainer
        not self.op.shutdown):
223 7ecd5e87 Thomas Thrainer
      raise errors.OpPrereqError("Can not remove instance without shutting it"
224 7ecd5e87 Thomas Thrainer
                                 " down before", errors.ECODE_STATE)
225 7ecd5e87 Thomas Thrainer
226 7ecd5e87 Thomas Thrainer
    if self.op.mode == constants.EXPORT_MODE_LOCAL:
227 1c3231aa Thomas Thrainer
      self.dst_node = self.cfg.GetNodeInfo(self.op.target_node_uuid)
228 7ecd5e87 Thomas Thrainer
      assert self.dst_node is not None
229 7ecd5e87 Thomas Thrainer
230 1c3231aa Thomas Thrainer
      CheckNodeOnline(self, self.dst_node.uuid)
231 1c3231aa Thomas Thrainer
      CheckNodeNotDrained(self, self.dst_node.uuid)
232 7ecd5e87 Thomas Thrainer
233 7ecd5e87 Thomas Thrainer
      self._cds = None
234 7ecd5e87 Thomas Thrainer
      self.dest_disk_info = None
235 7ecd5e87 Thomas Thrainer
      self.dest_x509_ca = None
236 7ecd5e87 Thomas Thrainer
237 7ecd5e87 Thomas Thrainer
    elif self.op.mode == constants.EXPORT_MODE_REMOTE:
238 7ecd5e87 Thomas Thrainer
      self.dst_node = None
239 7ecd5e87 Thomas Thrainer
240 7ecd5e87 Thomas Thrainer
      if len(self.op.target_node) != len(self.instance.disks):
241 7ecd5e87 Thomas Thrainer
        raise errors.OpPrereqError(("Received destination information for %s"
242 7ecd5e87 Thomas Thrainer
                                    " disks, but instance %s has %s disks") %
243 d0d7d7cf Thomas Thrainer
                                   (len(self.op.target_node),
244 d0d7d7cf Thomas Thrainer
                                    self.op.instance_name,
245 7ecd5e87 Thomas Thrainer
                                    len(self.instance.disks)),
246 7ecd5e87 Thomas Thrainer
                                   errors.ECODE_INVAL)
247 7ecd5e87 Thomas Thrainer
248 5eacbcae Thomas Thrainer
      cds = GetClusterDomainSecret()
249 7ecd5e87 Thomas Thrainer
250 7ecd5e87 Thomas Thrainer
      # Check X509 key name
251 7ecd5e87 Thomas Thrainer
      try:
252 7ecd5e87 Thomas Thrainer
        (key_name, hmac_digest, hmac_salt) = self.x509_key_name
253 7ecd5e87 Thomas Thrainer
      except (TypeError, ValueError), err:
254 7ecd5e87 Thomas Thrainer
        raise errors.OpPrereqError("Invalid data for X509 key name: %s" % err,
255 7ecd5e87 Thomas Thrainer
                                   errors.ECODE_INVAL)
256 7ecd5e87 Thomas Thrainer
257 7ecd5e87 Thomas Thrainer
      if not utils.VerifySha1Hmac(cds, key_name, hmac_digest, salt=hmac_salt):
258 7ecd5e87 Thomas Thrainer
        raise errors.OpPrereqError("HMAC for X509 key name is wrong",
259 7ecd5e87 Thomas Thrainer
                                   errors.ECODE_INVAL)
260 7ecd5e87 Thomas Thrainer
261 7ecd5e87 Thomas Thrainer
      # Load and verify CA
262 7ecd5e87 Thomas Thrainer
      try:
263 7ecd5e87 Thomas Thrainer
        (cert, _) = utils.LoadSignedX509Certificate(self.dest_x509_ca_pem, cds)
264 7ecd5e87 Thomas Thrainer
      except OpenSSL.crypto.Error, err:
265 7ecd5e87 Thomas Thrainer
        raise errors.OpPrereqError("Unable to load destination X509 CA (%s)" %
266 7ecd5e87 Thomas Thrainer
                                   (err, ), errors.ECODE_INVAL)
267 7ecd5e87 Thomas Thrainer
268 7ecd5e87 Thomas Thrainer
      (errcode, msg) = utils.VerifyX509Certificate(cert, None, None)
269 7ecd5e87 Thomas Thrainer
      if errcode is not None:
270 7ecd5e87 Thomas Thrainer
        raise errors.OpPrereqError("Invalid destination X509 CA (%s)" %
271 7ecd5e87 Thomas Thrainer
                                   (msg, ), errors.ECODE_INVAL)
272 7ecd5e87 Thomas Thrainer
273 7ecd5e87 Thomas Thrainer
      self.dest_x509_ca = cert
274 7ecd5e87 Thomas Thrainer
275 7ecd5e87 Thomas Thrainer
      # Verify target information
276 7ecd5e87 Thomas Thrainer
      disk_info = []
277 7ecd5e87 Thomas Thrainer
      for idx, disk_data in enumerate(self.op.target_node):
278 7ecd5e87 Thomas Thrainer
        try:
279 7ecd5e87 Thomas Thrainer
          (host, port, magic) = \
280 7ecd5e87 Thomas Thrainer
            masterd.instance.CheckRemoteExportDiskInfo(cds, idx, disk_data)
281 7ecd5e87 Thomas Thrainer
        except errors.GenericError, err:
282 7ecd5e87 Thomas Thrainer
          raise errors.OpPrereqError("Target info for disk %s: %s" %
283 7ecd5e87 Thomas Thrainer
                                     (idx, err), errors.ECODE_INVAL)
284 7ecd5e87 Thomas Thrainer
285 7ecd5e87 Thomas Thrainer
        disk_info.append((host, port, magic))
286 7ecd5e87 Thomas Thrainer
287 7ecd5e87 Thomas Thrainer
      assert len(disk_info) == len(self.op.target_node)
288 7ecd5e87 Thomas Thrainer
      self.dest_disk_info = disk_info
289 7ecd5e87 Thomas Thrainer
290 7ecd5e87 Thomas Thrainer
    else:
291 7ecd5e87 Thomas Thrainer
      raise errors.ProgrammerError("Unhandled export mode %r" %
292 7ecd5e87 Thomas Thrainer
                                   self.op.mode)
293 7ecd5e87 Thomas Thrainer
294 7ecd5e87 Thomas Thrainer
    # instance disk type verification
295 7ecd5e87 Thomas Thrainer
    # TODO: Implement export support for file-based disks
296 7ecd5e87 Thomas Thrainer
    for disk in self.instance.disks:
297 a09639d1 Santi Raffa
      if disk.dev_type in constants.DTS_FILEBASED:
298 7ecd5e87 Thomas Thrainer
        raise errors.OpPrereqError("Export not supported for instances with"
299 7ecd5e87 Thomas Thrainer
                                   " file-based disks", errors.ECODE_INVAL)
300 7ecd5e87 Thomas Thrainer
301 7ecd5e87 Thomas Thrainer
  def _CleanupExports(self, feedback_fn):
302 7ecd5e87 Thomas Thrainer
    """Removes exports of current instance from all other nodes.
303 7ecd5e87 Thomas Thrainer

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

307 7ecd5e87 Thomas Thrainer
    """
308 7ecd5e87 Thomas Thrainer
    assert self.op.mode != constants.EXPORT_MODE_REMOTE
309 7ecd5e87 Thomas Thrainer
310 1c3231aa Thomas Thrainer
    node_uuids = self.cfg.GetNodeList()
311 1c3231aa Thomas Thrainer
    node_uuids.remove(self.dst_node.uuid)
312 7ecd5e87 Thomas Thrainer
313 7ecd5e87 Thomas Thrainer
    # on one-node clusters nodelist will be empty after the removal
314 7ecd5e87 Thomas Thrainer
    # if we proceed the backup would be removed because OpBackupQuery
315 7ecd5e87 Thomas Thrainer
    # substitutes an empty list with the full cluster node list.
316 7ecd5e87 Thomas Thrainer
    iname = self.instance.name
317 1c3231aa Thomas Thrainer
    if node_uuids:
318 7ecd5e87 Thomas Thrainer
      feedback_fn("Removing old exports for instance %s" % iname)
319 1c3231aa Thomas Thrainer
      exportlist = self.rpc.call_export_list(node_uuids)
320 1c3231aa Thomas Thrainer
      for node_uuid in exportlist:
321 1c3231aa Thomas Thrainer
        if exportlist[node_uuid].fail_msg:
322 7ecd5e87 Thomas Thrainer
          continue
323 1c3231aa Thomas Thrainer
        if iname in exportlist[node_uuid].payload:
324 1c3231aa Thomas Thrainer
          msg = self.rpc.call_export_remove(node_uuid, iname).fail_msg
325 7ecd5e87 Thomas Thrainer
          if msg:
326 7ecd5e87 Thomas Thrainer
            self.LogWarning("Could not remove older export for instance %s"
327 1c3231aa Thomas Thrainer
                            " on node %s: %s", iname,
328 1c3231aa Thomas Thrainer
                            self.cfg.GetNodeName(node_uuid), msg)
329 7ecd5e87 Thomas Thrainer
330 7ecd5e87 Thomas Thrainer
  def Exec(self, feedback_fn):
331 7ecd5e87 Thomas Thrainer
    """Export an instance to an image in the cluster.
332 7ecd5e87 Thomas Thrainer

333 7ecd5e87 Thomas Thrainer
    """
334 7ecd5e87 Thomas Thrainer
    assert self.op.mode in constants.EXPORT_MODES
335 7ecd5e87 Thomas Thrainer
336 d0d7d7cf Thomas Thrainer
    src_node_uuid = self.instance.primary_node
337 7ecd5e87 Thomas Thrainer
338 7ecd5e87 Thomas Thrainer
    if self.op.shutdown:
339 7ecd5e87 Thomas Thrainer
      # shutdown the instance, but not the disks
340 d0d7d7cf Thomas Thrainer
      feedback_fn("Shutting down instance %s" % self.instance.name)
341 d0d7d7cf Thomas Thrainer
      result = self.rpc.call_instance_shutdown(src_node_uuid, self.instance,
342 7ecd5e87 Thomas Thrainer
                                               self.op.shutdown_timeout,
343 7ecd5e87 Thomas Thrainer
                                               self.op.reason)
344 7ecd5e87 Thomas Thrainer
      # TODO: Maybe ignore failures if ignore_remove_failures is set
345 7ecd5e87 Thomas Thrainer
      result.Raise("Could not shutdown instance %s on"
346 d0d7d7cf Thomas Thrainer
                   " node %s" % (self.instance.name,
347 1c3231aa Thomas Thrainer
                                 self.cfg.GetNodeName(src_node_uuid)))
348 7ecd5e87 Thomas Thrainer
349 d0d7d7cf Thomas Thrainer
    activate_disks = not self.instance.disks_active
350 7ecd5e87 Thomas Thrainer
351 7ecd5e87 Thomas Thrainer
    if activate_disks:
352 70b634e6 Thomas Thrainer
      # Activate the instance disks if we're exporting a stopped instance
353 d0d7d7cf Thomas Thrainer
      feedback_fn("Activating disks for %s" % self.instance.name)
354 d0d7d7cf Thomas Thrainer
      StartInstanceDisks(self, self.instance, None)
355 7ecd5e87 Thomas Thrainer
356 7ecd5e87 Thomas Thrainer
    try:
357 7ecd5e87 Thomas Thrainer
      helper = masterd.instance.ExportInstanceHelper(self, feedback_fn,
358 d0d7d7cf Thomas Thrainer
                                                     self.instance)
359 7ecd5e87 Thomas Thrainer
360 7ecd5e87 Thomas Thrainer
      helper.CreateSnapshots()
361 7ecd5e87 Thomas Thrainer
      try:
362 7ecd5e87 Thomas Thrainer
        if (self.op.shutdown and
363 d0d7d7cf Thomas Thrainer
            self.instance.admin_state == constants.ADMINST_UP and
364 7ecd5e87 Thomas Thrainer
            not self.op.remove_instance):
365 7ecd5e87 Thomas Thrainer
          assert not activate_disks
366 d0d7d7cf Thomas Thrainer
          feedback_fn("Starting instance %s" % self.instance.name)
367 1c3231aa Thomas Thrainer
          result = self.rpc.call_instance_start(src_node_uuid,
368 d0d7d7cf Thomas Thrainer
                                                (self.instance, None, None),
369 d0d7d7cf Thomas Thrainer
                                                False, self.op.reason)
370 7ecd5e87 Thomas Thrainer
          msg = result.fail_msg
371 7ecd5e87 Thomas Thrainer
          if msg:
372 7ecd5e87 Thomas Thrainer
            feedback_fn("Failed to start instance: %s" % msg)
373 d0d7d7cf Thomas Thrainer
            ShutdownInstanceDisks(self, self.instance)
374 7ecd5e87 Thomas Thrainer
            raise errors.OpExecError("Could not start instance: %s" % msg)
375 7ecd5e87 Thomas Thrainer
376 7ecd5e87 Thomas Thrainer
        if self.op.mode == constants.EXPORT_MODE_LOCAL:
377 896cc964 Thomas Thrainer
          (fin_resu, dresults) = helper.LocalExport(self.dst_node,
378 896cc964 Thomas Thrainer
                                                    self.op.compress)
379 7ecd5e87 Thomas Thrainer
        elif self.op.mode == constants.EXPORT_MODE_REMOTE:
380 7ecd5e87 Thomas Thrainer
          connect_timeout = constants.RIE_CONNECT_TIMEOUT
381 7ecd5e87 Thomas Thrainer
          timeouts = masterd.instance.ImportExportTimeouts(connect_timeout)
382 7ecd5e87 Thomas Thrainer
383 7ecd5e87 Thomas Thrainer
          (key_name, _, _) = self.x509_key_name
384 7ecd5e87 Thomas Thrainer
385 7ecd5e87 Thomas Thrainer
          dest_ca_pem = \
386 7ecd5e87 Thomas Thrainer
            OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM,
387 7ecd5e87 Thomas Thrainer
                                            self.dest_x509_ca)
388 7ecd5e87 Thomas Thrainer
389 7ecd5e87 Thomas Thrainer
          (fin_resu, dresults) = helper.RemoteExport(self.dest_disk_info,
390 7ecd5e87 Thomas Thrainer
                                                     key_name, dest_ca_pem,
391 258de3fe Thomas Thrainer
                                                     self.op.compress,
392 7ecd5e87 Thomas Thrainer
                                                     timeouts)
393 7ecd5e87 Thomas Thrainer
      finally:
394 7ecd5e87 Thomas Thrainer
        helper.Cleanup()
395 7ecd5e87 Thomas Thrainer
396 7ecd5e87 Thomas Thrainer
      # Check for backwards compatibility
397 d0d7d7cf Thomas Thrainer
      assert len(dresults) == len(self.instance.disks)
398 7ecd5e87 Thomas Thrainer
      assert compat.all(isinstance(i, bool) for i in dresults), \
399 7ecd5e87 Thomas Thrainer
             "Not all results are boolean: %r" % dresults
400 7ecd5e87 Thomas Thrainer
401 7ecd5e87 Thomas Thrainer
    finally:
402 7ecd5e87 Thomas Thrainer
      if activate_disks:
403 d0d7d7cf Thomas Thrainer
        feedback_fn("Deactivating disks for %s" % self.instance.name)
404 d0d7d7cf Thomas Thrainer
        ShutdownInstanceDisks(self, self.instance)
405 7ecd5e87 Thomas Thrainer
406 7ecd5e87 Thomas Thrainer
    if not (compat.all(dresults) and fin_resu):
407 7ecd5e87 Thomas Thrainer
      failures = []
408 7ecd5e87 Thomas Thrainer
      if not fin_resu:
409 7ecd5e87 Thomas Thrainer
        failures.append("export finalization")
410 7ecd5e87 Thomas Thrainer
      if not compat.all(dresults):
411 7ecd5e87 Thomas Thrainer
        fdsk = utils.CommaJoin(idx for (idx, dsk) in enumerate(dresults)
412 7ecd5e87 Thomas Thrainer
                               if not dsk)
413 7ecd5e87 Thomas Thrainer
        failures.append("disk export: disk(s) %s" % fdsk)
414 7ecd5e87 Thomas Thrainer
415 7ecd5e87 Thomas Thrainer
      raise errors.OpExecError("Export failed, errors in %s" %
416 7ecd5e87 Thomas Thrainer
                               utils.CommaJoin(failures))
417 7ecd5e87 Thomas Thrainer
418 7ecd5e87 Thomas Thrainer
    # At this point, the export was successful, we can cleanup/finish
419 7ecd5e87 Thomas Thrainer
420 7ecd5e87 Thomas Thrainer
    # Remove instance if requested
421 7ecd5e87 Thomas Thrainer
    if self.op.remove_instance:
422 d0d7d7cf Thomas Thrainer
      feedback_fn("Removing instance %s" % self.instance.name)
423 d0d7d7cf Thomas Thrainer
      RemoveInstance(self, feedback_fn, self.instance,
424 5eacbcae Thomas Thrainer
                     self.op.ignore_remove_failures)
425 7ecd5e87 Thomas Thrainer
426 7ecd5e87 Thomas Thrainer
    if self.op.mode == constants.EXPORT_MODE_LOCAL:
427 7ecd5e87 Thomas Thrainer
      self._CleanupExports(feedback_fn)
428 7ecd5e87 Thomas Thrainer
429 7ecd5e87 Thomas Thrainer
    return fin_resu, dresults
430 7ecd5e87 Thomas Thrainer
431 7ecd5e87 Thomas Thrainer
432 7ecd5e87 Thomas Thrainer
class LUBackupRemove(NoHooksLU):
433 7ecd5e87 Thomas Thrainer
  """Remove exports related to the named instance.
434 7ecd5e87 Thomas Thrainer

435 7ecd5e87 Thomas Thrainer
  """
436 7ecd5e87 Thomas Thrainer
  REQ_BGL = False
437 7ecd5e87 Thomas Thrainer
438 7ecd5e87 Thomas Thrainer
  def ExpandNames(self):
439 7ecd5e87 Thomas Thrainer
    self.needed_locks = {
440 7ecd5e87 Thomas Thrainer
      # We need all nodes to be locked in order for RemoveExport to work, but
441 7ecd5e87 Thomas Thrainer
      # we don't need to lock the instance itself, as nothing will happen to it
442 7ecd5e87 Thomas Thrainer
      # (and we can remove exports also for a removed instance)
443 7ecd5e87 Thomas Thrainer
      locking.LEVEL_NODE: locking.ALL_SET,
444 7ecd5e87 Thomas Thrainer
445 7ecd5e87 Thomas Thrainer
      # Removing backups is quick, so blocking allocations is justified
446 7ecd5e87 Thomas Thrainer
      locking.LEVEL_NODE_ALLOC: locking.ALL_SET,
447 7ecd5e87 Thomas Thrainer
      }
448 7ecd5e87 Thomas Thrainer
449 7ecd5e87 Thomas Thrainer
    # Allocations should be stopped while this LU runs with node locks, but it
450 7ecd5e87 Thomas Thrainer
    # doesn't have to be exclusive
451 7ecd5e87 Thomas Thrainer
    self.share_locks[locking.LEVEL_NODE_ALLOC] = 1
452 7ecd5e87 Thomas Thrainer
453 7ecd5e87 Thomas Thrainer
  def Exec(self, feedback_fn):
454 7ecd5e87 Thomas Thrainer
    """Remove any export.
455 7ecd5e87 Thomas Thrainer

456 7ecd5e87 Thomas Thrainer
    """
457 da4a52a3 Thomas Thrainer
    (_, inst_name) = self.cfg.ExpandInstanceName(self.op.instance_name)
458 7ecd5e87 Thomas Thrainer
    # If the instance was not found we'll try with the name that was passed in.
459 7ecd5e87 Thomas Thrainer
    # This will only work if it was an FQDN, though.
460 7ecd5e87 Thomas Thrainer
    fqdn_warn = False
461 da4a52a3 Thomas Thrainer
    if not inst_name:
462 7ecd5e87 Thomas Thrainer
      fqdn_warn = True
463 da4a52a3 Thomas Thrainer
      inst_name = self.op.instance_name
464 7ecd5e87 Thomas Thrainer
465 7ecd5e87 Thomas Thrainer
    locked_nodes = self.owned_locks(locking.LEVEL_NODE)
466 7ecd5e87 Thomas Thrainer
    exportlist = self.rpc.call_export_list(locked_nodes)
467 7ecd5e87 Thomas Thrainer
    found = False
468 1c3231aa Thomas Thrainer
    for node_uuid in exportlist:
469 1c3231aa Thomas Thrainer
      msg = exportlist[node_uuid].fail_msg
470 7ecd5e87 Thomas Thrainer
      if msg:
471 1c3231aa Thomas Thrainer
        self.LogWarning("Failed to query node %s (continuing): %s",
472 1c3231aa Thomas Thrainer
                        self.cfg.GetNodeName(node_uuid), msg)
473 7ecd5e87 Thomas Thrainer
        continue
474 da4a52a3 Thomas Thrainer
      if inst_name in exportlist[node_uuid].payload:
475 7ecd5e87 Thomas Thrainer
        found = True
476 da4a52a3 Thomas Thrainer
        result = self.rpc.call_export_remove(node_uuid, inst_name)
477 7ecd5e87 Thomas Thrainer
        msg = result.fail_msg
478 7ecd5e87 Thomas Thrainer
        if msg:
479 7ecd5e87 Thomas Thrainer
          logging.error("Could not remove export for instance %s"
480 da4a52a3 Thomas Thrainer
                        " on node %s: %s", inst_name,
481 1c3231aa Thomas Thrainer
                        self.cfg.GetNodeName(node_uuid), msg)
482 7ecd5e87 Thomas Thrainer
483 7ecd5e87 Thomas Thrainer
    if fqdn_warn and not found:
484 7ecd5e87 Thomas Thrainer
      feedback_fn("Export not found. If trying to remove an export belonging"
485 7ecd5e87 Thomas Thrainer
                  " to a deleted instance please use its Fully Qualified"
486 7ecd5e87 Thomas Thrainer
                  " Domain Name.")