Statistics
| Branch: | Tag: | Revision:

root / lib / cmdlib / backup.py @ 5cbf7832

History | View | Annotate | Download (18.5 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 qlang
33 7ecd5e87 Thomas Thrainer
from ganeti import query
34 7ecd5e87 Thomas Thrainer
from ganeti import utils
35 7ecd5e87 Thomas Thrainer
36 5eacbcae Thomas Thrainer
from ganeti.cmdlib.base import QueryBase, NoHooksLU, LogicalUnit
37 5eacbcae Thomas Thrainer
from ganeti.cmdlib.common import GetWantedNodes, ShareAll, CheckNodeOnline, \
38 1c3231aa Thomas Thrainer
  ExpandNodeUuidAndName
39 5eacbcae Thomas Thrainer
from ganeti.cmdlib.instance_storage import StartInstanceDisks, \
40 5eacbcae Thomas Thrainer
  ShutdownInstanceDisks
41 5eacbcae Thomas Thrainer
from ganeti.cmdlib.instance_utils import GetClusterDomainSecret, \
42 5eacbcae Thomas Thrainer
  BuildInstanceHookEnvByObject, CheckNodeNotDrained, RemoveInstance
43 7ecd5e87 Thomas Thrainer
44 7ecd5e87 Thomas Thrainer
45 5eacbcae Thomas Thrainer
class ExportQuery(QueryBase):
46 7ecd5e87 Thomas Thrainer
  FIELDS = query.EXPORT_FIELDS
47 7ecd5e87 Thomas Thrainer
48 7ecd5e87 Thomas Thrainer
  #: The node name is not a unique key for this query
49 7ecd5e87 Thomas Thrainer
  SORT_FIELD = "node"
50 7ecd5e87 Thomas Thrainer
51 7ecd5e87 Thomas Thrainer
  def ExpandNames(self, lu):
52 7ecd5e87 Thomas Thrainer
    lu.needed_locks = {}
53 7ecd5e87 Thomas Thrainer
54 7ecd5e87 Thomas Thrainer
    # The following variables interact with _QueryBase._GetNames
55 7ecd5e87 Thomas Thrainer
    if self.names:
56 1c3231aa Thomas Thrainer
      (self.wanted, _) = GetWantedNodes(lu, self.names)
57 7ecd5e87 Thomas Thrainer
    else:
58 7ecd5e87 Thomas Thrainer
      self.wanted = locking.ALL_SET
59 7ecd5e87 Thomas Thrainer
60 7ecd5e87 Thomas Thrainer
    self.do_locking = self.use_locking
61 7ecd5e87 Thomas Thrainer
62 7ecd5e87 Thomas Thrainer
    if self.do_locking:
63 5eacbcae Thomas Thrainer
      lu.share_locks = ShareAll()
64 7ecd5e87 Thomas Thrainer
      lu.needed_locks = {
65 7ecd5e87 Thomas Thrainer
        locking.LEVEL_NODE: self.wanted,
66 7ecd5e87 Thomas Thrainer
        }
67 7ecd5e87 Thomas Thrainer
68 7ecd5e87 Thomas Thrainer
      if not self.names:
69 7ecd5e87 Thomas Thrainer
        lu.needed_locks[locking.LEVEL_NODE_ALLOC] = locking.ALL_SET
70 7ecd5e87 Thomas Thrainer
71 7ecd5e87 Thomas Thrainer
  def DeclareLocks(self, lu, level):
72 7ecd5e87 Thomas Thrainer
    pass
73 7ecd5e87 Thomas Thrainer
74 7ecd5e87 Thomas Thrainer
  def _GetQueryData(self, lu):
75 7ecd5e87 Thomas Thrainer
    """Computes the list of nodes and their attributes.
76 7ecd5e87 Thomas Thrainer

77 7ecd5e87 Thomas Thrainer
    """
78 7ecd5e87 Thomas Thrainer
    # Locking is not used
79 7ecd5e87 Thomas Thrainer
    # TODO
80 7ecd5e87 Thomas Thrainer
    assert not (compat.any(lu.glm.is_owned(level)
81 7ecd5e87 Thomas Thrainer
                           for level in locking.LEVELS
82 7ecd5e87 Thomas Thrainer
                           if level != locking.LEVEL_CLUSTER) or
83 7ecd5e87 Thomas Thrainer
                self.do_locking or self.use_locking)
84 7ecd5e87 Thomas Thrainer
85 1c3231aa Thomas Thrainer
    node_uuids = self._GetNames(lu, lu.cfg.GetNodeList(), locking.LEVEL_NODE)
86 7ecd5e87 Thomas Thrainer
87 7ecd5e87 Thomas Thrainer
    result = []
88 7ecd5e87 Thomas Thrainer
89 1c3231aa Thomas Thrainer
    for (node_uuid, nres) in lu.rpc.call_export_list(node_uuids).items():
90 7ecd5e87 Thomas Thrainer
      if nres.fail_msg:
91 1c3231aa Thomas Thrainer
        result.append((node_uuid, None))
92 7ecd5e87 Thomas Thrainer
      else:
93 1c3231aa Thomas Thrainer
        result.extend((node_uuid, expname) for expname in nres.payload)
94 7ecd5e87 Thomas Thrainer
95 7ecd5e87 Thomas Thrainer
    return result
96 7ecd5e87 Thomas Thrainer
97 7ecd5e87 Thomas Thrainer
98 7ecd5e87 Thomas Thrainer
class LUBackupQuery(NoHooksLU):
99 7ecd5e87 Thomas Thrainer
  """Query the exports list
100 7ecd5e87 Thomas Thrainer

101 7ecd5e87 Thomas Thrainer
  """
102 7ecd5e87 Thomas Thrainer
  REQ_BGL = False
103 7ecd5e87 Thomas Thrainer
104 7ecd5e87 Thomas Thrainer
  def CheckArguments(self):
105 5eacbcae Thomas Thrainer
    self.expq = ExportQuery(qlang.MakeSimpleFilter("node", self.op.nodes),
106 5eacbcae Thomas Thrainer
                            ["node", "export"], self.op.use_locking)
107 7ecd5e87 Thomas Thrainer
108 7ecd5e87 Thomas Thrainer
  def ExpandNames(self):
109 7ecd5e87 Thomas Thrainer
    self.expq.ExpandNames(self)
110 7ecd5e87 Thomas Thrainer
111 7ecd5e87 Thomas Thrainer
  def DeclareLocks(self, level):
112 7ecd5e87 Thomas Thrainer
    self.expq.DeclareLocks(self, level)
113 7ecd5e87 Thomas Thrainer
114 7ecd5e87 Thomas Thrainer
  def Exec(self, feedback_fn):
115 7ecd5e87 Thomas Thrainer
    result = {}
116 7ecd5e87 Thomas Thrainer
117 7ecd5e87 Thomas Thrainer
    for (node, expname) in self.expq.OldStyleQuery(self):
118 7ecd5e87 Thomas Thrainer
      if expname is None:
119 7ecd5e87 Thomas Thrainer
        result[node] = False
120 7ecd5e87 Thomas Thrainer
      else:
121 7ecd5e87 Thomas Thrainer
        result.setdefault(node, []).append(expname)
122 7ecd5e87 Thomas Thrainer
123 7ecd5e87 Thomas Thrainer
    return result
124 7ecd5e87 Thomas Thrainer
125 7ecd5e87 Thomas Thrainer
126 7ecd5e87 Thomas Thrainer
class LUBackupPrepare(NoHooksLU):
127 7ecd5e87 Thomas Thrainer
  """Prepares an instance for an export and returns useful information.
128 7ecd5e87 Thomas Thrainer

129 7ecd5e87 Thomas Thrainer
  """
130 7ecd5e87 Thomas Thrainer
  REQ_BGL = False
131 7ecd5e87 Thomas Thrainer
132 7ecd5e87 Thomas Thrainer
  def ExpandNames(self):
133 7ecd5e87 Thomas Thrainer
    self._ExpandAndLockInstance()
134 7ecd5e87 Thomas Thrainer
135 7ecd5e87 Thomas Thrainer
  def CheckPrereq(self):
136 7ecd5e87 Thomas Thrainer
    """Check prerequisites.
137 7ecd5e87 Thomas Thrainer

138 7ecd5e87 Thomas Thrainer
    """
139 da4a52a3 Thomas Thrainer
    self.instance = self.cfg.GetInstanceInfoByName(self.op.instance_name)
140 7ecd5e87 Thomas Thrainer
    assert self.instance is not None, \
141 7ecd5e87 Thomas Thrainer
          "Cannot retrieve locked instance %s" % self.op.instance_name
142 5eacbcae Thomas Thrainer
    CheckNodeOnline(self, self.instance.primary_node)
143 7ecd5e87 Thomas Thrainer
144 5eacbcae Thomas Thrainer
    self._cds = GetClusterDomainSecret()
145 7ecd5e87 Thomas Thrainer
146 7ecd5e87 Thomas Thrainer
  def Exec(self, feedback_fn):
147 7ecd5e87 Thomas Thrainer
    """Prepares an instance for an export.
148 7ecd5e87 Thomas Thrainer

149 7ecd5e87 Thomas Thrainer
    """
150 7ecd5e87 Thomas Thrainer
    if self.op.mode == constants.EXPORT_MODE_REMOTE:
151 7ecd5e87 Thomas Thrainer
      salt = utils.GenerateSecret(8)
152 7ecd5e87 Thomas Thrainer
153 1c3231aa Thomas Thrainer
      feedback_fn("Generating X509 certificate on %s" %
154 d0d7d7cf Thomas Thrainer
                  self.cfg.GetNodeName(self.instance.primary_node))
155 d0d7d7cf Thomas Thrainer
      result = self.rpc.call_x509_cert_create(self.instance.primary_node,
156 7ecd5e87 Thomas Thrainer
                                              constants.RIE_CERT_VALIDITY)
157 1c3231aa Thomas Thrainer
      result.Raise("Can't create X509 key and certificate on %s" %
158 1c3231aa Thomas Thrainer
                   self.cfg.GetNodeName(result.node))
159 7ecd5e87 Thomas Thrainer
160 7ecd5e87 Thomas Thrainer
      (name, cert_pem) = result.payload
161 7ecd5e87 Thomas Thrainer
162 7ecd5e87 Thomas Thrainer
      cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
163 7ecd5e87 Thomas Thrainer
                                             cert_pem)
164 7ecd5e87 Thomas Thrainer
165 7ecd5e87 Thomas Thrainer
      return {
166 7ecd5e87 Thomas Thrainer
        "handshake": masterd.instance.ComputeRemoteExportHandshake(self._cds),
167 7ecd5e87 Thomas Thrainer
        "x509_key_name": (name, utils.Sha1Hmac(self._cds, name, salt=salt),
168 7ecd5e87 Thomas Thrainer
                          salt),
169 7ecd5e87 Thomas Thrainer
        "x509_ca": utils.SignX509Certificate(cert, self._cds, salt),
170 7ecd5e87 Thomas Thrainer
        }
171 7ecd5e87 Thomas Thrainer
172 7ecd5e87 Thomas Thrainer
    return None
173 7ecd5e87 Thomas Thrainer
174 7ecd5e87 Thomas Thrainer
175 7ecd5e87 Thomas Thrainer
class LUBackupExport(LogicalUnit):
176 7ecd5e87 Thomas Thrainer
  """Export an instance to an image in the cluster.
177 7ecd5e87 Thomas Thrainer

178 7ecd5e87 Thomas Thrainer
  """
179 7ecd5e87 Thomas Thrainer
  HPATH = "instance-export"
180 7ecd5e87 Thomas Thrainer
  HTYPE = constants.HTYPE_INSTANCE
181 7ecd5e87 Thomas Thrainer
  REQ_BGL = False
182 7ecd5e87 Thomas Thrainer
183 7ecd5e87 Thomas Thrainer
  def CheckArguments(self):
184 7ecd5e87 Thomas Thrainer
    """Check the arguments.
185 7ecd5e87 Thomas Thrainer

186 7ecd5e87 Thomas Thrainer
    """
187 7ecd5e87 Thomas Thrainer
    self.x509_key_name = self.op.x509_key_name
188 7ecd5e87 Thomas Thrainer
    self.dest_x509_ca_pem = self.op.destination_x509_ca
189 7ecd5e87 Thomas Thrainer
190 7ecd5e87 Thomas Thrainer
    if self.op.mode == constants.EXPORT_MODE_REMOTE:
191 7ecd5e87 Thomas Thrainer
      if not self.x509_key_name:
192 7ecd5e87 Thomas Thrainer
        raise errors.OpPrereqError("Missing X509 key name for encryption",
193 7ecd5e87 Thomas Thrainer
                                   errors.ECODE_INVAL)
194 7ecd5e87 Thomas Thrainer
195 7ecd5e87 Thomas Thrainer
      if not self.dest_x509_ca_pem:
196 7ecd5e87 Thomas Thrainer
        raise errors.OpPrereqError("Missing destination X509 CA",
197 7ecd5e87 Thomas Thrainer
                                   errors.ECODE_INVAL)
198 7ecd5e87 Thomas Thrainer
199 7ecd5e87 Thomas Thrainer
  def ExpandNames(self):
200 7ecd5e87 Thomas Thrainer
    self._ExpandAndLockInstance()
201 7ecd5e87 Thomas Thrainer
202 7ecd5e87 Thomas Thrainer
    # Lock all nodes for local exports
203 7ecd5e87 Thomas Thrainer
    if self.op.mode == constants.EXPORT_MODE_LOCAL:
204 1c3231aa Thomas Thrainer
      (self.op.target_node_uuid, self.op.target_node) = \
205 1c3231aa Thomas Thrainer
        ExpandNodeUuidAndName(self.cfg, self.op.target_node_uuid,
206 1c3231aa Thomas Thrainer
                              self.op.target_node)
207 7ecd5e87 Thomas Thrainer
      # FIXME: lock only instance primary and destination node
208 7ecd5e87 Thomas Thrainer
      #
209 7ecd5e87 Thomas Thrainer
      # Sad but true, for now we have do lock all nodes, as we don't know where
210 7ecd5e87 Thomas Thrainer
      # the previous export might be, and in this LU we search for it and
211 7ecd5e87 Thomas Thrainer
      # remove it from its current node. In the future we could fix this by:
212 7ecd5e87 Thomas Thrainer
      #  - making a tasklet to search (share-lock all), then create the
213 7ecd5e87 Thomas Thrainer
      #    new one, then one to remove, after
214 7ecd5e87 Thomas Thrainer
      #  - removing the removal operation altogether
215 7ecd5e87 Thomas Thrainer
      self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
216 7ecd5e87 Thomas Thrainer
217 7ecd5e87 Thomas Thrainer
      # Allocations should be stopped while this LU runs with node locks, but
218 7ecd5e87 Thomas Thrainer
      # it doesn't have to be exclusive
219 7ecd5e87 Thomas Thrainer
      self.share_locks[locking.LEVEL_NODE_ALLOC] = 1
220 7ecd5e87 Thomas Thrainer
      self.needed_locks[locking.LEVEL_NODE_ALLOC] = locking.ALL_SET
221 7ecd5e87 Thomas Thrainer
222 7ecd5e87 Thomas Thrainer
  def DeclareLocks(self, level):
223 7ecd5e87 Thomas Thrainer
    """Last minute lock declaration."""
224 7ecd5e87 Thomas Thrainer
    # All nodes are locked anyway, so nothing to do here.
225 7ecd5e87 Thomas Thrainer
226 7ecd5e87 Thomas Thrainer
  def BuildHooksEnv(self):
227 7ecd5e87 Thomas Thrainer
    """Build hooks env.
228 7ecd5e87 Thomas Thrainer

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

231 7ecd5e87 Thomas Thrainer
    """
232 7ecd5e87 Thomas Thrainer
    env = {
233 7ecd5e87 Thomas Thrainer
      "EXPORT_MODE": self.op.mode,
234 7ecd5e87 Thomas Thrainer
      "EXPORT_NODE": self.op.target_node,
235 7ecd5e87 Thomas Thrainer
      "EXPORT_DO_SHUTDOWN": self.op.shutdown,
236 7ecd5e87 Thomas Thrainer
      "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
237 7ecd5e87 Thomas Thrainer
      # TODO: Generic function for boolean env variables
238 7ecd5e87 Thomas Thrainer
      "REMOVE_INSTANCE": str(bool(self.op.remove_instance)),
239 7ecd5e87 Thomas Thrainer
      }
240 7ecd5e87 Thomas Thrainer
241 5eacbcae Thomas Thrainer
    env.update(BuildInstanceHookEnvByObject(self, self.instance))
242 7ecd5e87 Thomas Thrainer
243 7ecd5e87 Thomas Thrainer
    return env
244 7ecd5e87 Thomas Thrainer
245 7ecd5e87 Thomas Thrainer
  def BuildHooksNodes(self):
246 7ecd5e87 Thomas Thrainer
    """Build hooks nodes.
247 7ecd5e87 Thomas Thrainer

248 7ecd5e87 Thomas Thrainer
    """
249 7ecd5e87 Thomas Thrainer
    nl = [self.cfg.GetMasterNode(), self.instance.primary_node]
250 7ecd5e87 Thomas Thrainer
251 7ecd5e87 Thomas Thrainer
    if self.op.mode == constants.EXPORT_MODE_LOCAL:
252 1c3231aa Thomas Thrainer
      nl.append(self.op.target_node_uuid)
253 7ecd5e87 Thomas Thrainer
254 7ecd5e87 Thomas Thrainer
    return (nl, nl)
255 7ecd5e87 Thomas Thrainer
256 7ecd5e87 Thomas Thrainer
  def CheckPrereq(self):
257 7ecd5e87 Thomas Thrainer
    """Check prerequisites.
258 7ecd5e87 Thomas Thrainer

259 7ecd5e87 Thomas Thrainer
    This checks that the instance and node names are valid.
260 7ecd5e87 Thomas Thrainer

261 7ecd5e87 Thomas Thrainer
    """
262 da4a52a3 Thomas Thrainer
    self.instance = self.cfg.GetInstanceInfoByName(self.op.instance_name)
263 7ecd5e87 Thomas Thrainer
    assert self.instance is not None, \
264 7ecd5e87 Thomas Thrainer
          "Cannot retrieve locked instance %s" % self.op.instance_name
265 5eacbcae Thomas Thrainer
    CheckNodeOnline(self, self.instance.primary_node)
266 7ecd5e87 Thomas Thrainer
267 7ecd5e87 Thomas Thrainer
    if (self.op.remove_instance and
268 7ecd5e87 Thomas Thrainer
        self.instance.admin_state == constants.ADMINST_UP and
269 7ecd5e87 Thomas Thrainer
        not self.op.shutdown):
270 7ecd5e87 Thomas Thrainer
      raise errors.OpPrereqError("Can not remove instance without shutting it"
271 7ecd5e87 Thomas Thrainer
                                 " down before", errors.ECODE_STATE)
272 7ecd5e87 Thomas Thrainer
273 7ecd5e87 Thomas Thrainer
    if self.op.mode == constants.EXPORT_MODE_LOCAL:
274 1c3231aa Thomas Thrainer
      self.dst_node = self.cfg.GetNodeInfo(self.op.target_node_uuid)
275 7ecd5e87 Thomas Thrainer
      assert self.dst_node is not None
276 7ecd5e87 Thomas Thrainer
277 1c3231aa Thomas Thrainer
      CheckNodeOnline(self, self.dst_node.uuid)
278 1c3231aa Thomas Thrainer
      CheckNodeNotDrained(self, self.dst_node.uuid)
279 7ecd5e87 Thomas Thrainer
280 7ecd5e87 Thomas Thrainer
      self._cds = None
281 7ecd5e87 Thomas Thrainer
      self.dest_disk_info = None
282 7ecd5e87 Thomas Thrainer
      self.dest_x509_ca = None
283 7ecd5e87 Thomas Thrainer
284 7ecd5e87 Thomas Thrainer
    elif self.op.mode == constants.EXPORT_MODE_REMOTE:
285 7ecd5e87 Thomas Thrainer
      self.dst_node = None
286 7ecd5e87 Thomas Thrainer
287 7ecd5e87 Thomas Thrainer
      if len(self.op.target_node) != len(self.instance.disks):
288 7ecd5e87 Thomas Thrainer
        raise errors.OpPrereqError(("Received destination information for %s"
289 7ecd5e87 Thomas Thrainer
                                    " disks, but instance %s has %s disks") %
290 d0d7d7cf Thomas Thrainer
                                   (len(self.op.target_node),
291 d0d7d7cf Thomas Thrainer
                                    self.op.instance_name,
292 7ecd5e87 Thomas Thrainer
                                    len(self.instance.disks)),
293 7ecd5e87 Thomas Thrainer
                                   errors.ECODE_INVAL)
294 7ecd5e87 Thomas Thrainer
295 5eacbcae Thomas Thrainer
      cds = GetClusterDomainSecret()
296 7ecd5e87 Thomas Thrainer
297 7ecd5e87 Thomas Thrainer
      # Check X509 key name
298 7ecd5e87 Thomas Thrainer
      try:
299 7ecd5e87 Thomas Thrainer
        (key_name, hmac_digest, hmac_salt) = self.x509_key_name
300 7ecd5e87 Thomas Thrainer
      except (TypeError, ValueError), err:
301 7ecd5e87 Thomas Thrainer
        raise errors.OpPrereqError("Invalid data for X509 key name: %s" % err,
302 7ecd5e87 Thomas Thrainer
                                   errors.ECODE_INVAL)
303 7ecd5e87 Thomas Thrainer
304 7ecd5e87 Thomas Thrainer
      if not utils.VerifySha1Hmac(cds, key_name, hmac_digest, salt=hmac_salt):
305 7ecd5e87 Thomas Thrainer
        raise errors.OpPrereqError("HMAC for X509 key name is wrong",
306 7ecd5e87 Thomas Thrainer
                                   errors.ECODE_INVAL)
307 7ecd5e87 Thomas Thrainer
308 7ecd5e87 Thomas Thrainer
      # Load and verify CA
309 7ecd5e87 Thomas Thrainer
      try:
310 7ecd5e87 Thomas Thrainer
        (cert, _) = utils.LoadSignedX509Certificate(self.dest_x509_ca_pem, cds)
311 7ecd5e87 Thomas Thrainer
      except OpenSSL.crypto.Error, err:
312 7ecd5e87 Thomas Thrainer
        raise errors.OpPrereqError("Unable to load destination X509 CA (%s)" %
313 7ecd5e87 Thomas Thrainer
                                   (err, ), errors.ECODE_INVAL)
314 7ecd5e87 Thomas Thrainer
315 7ecd5e87 Thomas Thrainer
      (errcode, msg) = utils.VerifyX509Certificate(cert, None, None)
316 7ecd5e87 Thomas Thrainer
      if errcode is not None:
317 7ecd5e87 Thomas Thrainer
        raise errors.OpPrereqError("Invalid destination X509 CA (%s)" %
318 7ecd5e87 Thomas Thrainer
                                   (msg, ), errors.ECODE_INVAL)
319 7ecd5e87 Thomas Thrainer
320 7ecd5e87 Thomas Thrainer
      self.dest_x509_ca = cert
321 7ecd5e87 Thomas Thrainer
322 7ecd5e87 Thomas Thrainer
      # Verify target information
323 7ecd5e87 Thomas Thrainer
      disk_info = []
324 7ecd5e87 Thomas Thrainer
      for idx, disk_data in enumerate(self.op.target_node):
325 7ecd5e87 Thomas Thrainer
        try:
326 7ecd5e87 Thomas Thrainer
          (host, port, magic) = \
327 7ecd5e87 Thomas Thrainer
            masterd.instance.CheckRemoteExportDiskInfo(cds, idx, disk_data)
328 7ecd5e87 Thomas Thrainer
        except errors.GenericError, err:
329 7ecd5e87 Thomas Thrainer
          raise errors.OpPrereqError("Target info for disk %s: %s" %
330 7ecd5e87 Thomas Thrainer
                                     (idx, err), errors.ECODE_INVAL)
331 7ecd5e87 Thomas Thrainer
332 7ecd5e87 Thomas Thrainer
        disk_info.append((host, port, magic))
333 7ecd5e87 Thomas Thrainer
334 7ecd5e87 Thomas Thrainer
      assert len(disk_info) == len(self.op.target_node)
335 7ecd5e87 Thomas Thrainer
      self.dest_disk_info = disk_info
336 7ecd5e87 Thomas Thrainer
337 7ecd5e87 Thomas Thrainer
    else:
338 7ecd5e87 Thomas Thrainer
      raise errors.ProgrammerError("Unhandled export mode %r" %
339 7ecd5e87 Thomas Thrainer
                                   self.op.mode)
340 7ecd5e87 Thomas Thrainer
341 7ecd5e87 Thomas Thrainer
    # instance disk type verification
342 7ecd5e87 Thomas Thrainer
    # TODO: Implement export support for file-based disks
343 7ecd5e87 Thomas Thrainer
    for disk in self.instance.disks:
344 7ecd5e87 Thomas Thrainer
      if disk.dev_type == constants.LD_FILE:
345 7ecd5e87 Thomas Thrainer
        raise errors.OpPrereqError("Export not supported for instances with"
346 7ecd5e87 Thomas Thrainer
                                   " file-based disks", errors.ECODE_INVAL)
347 7ecd5e87 Thomas Thrainer
348 7ecd5e87 Thomas Thrainer
  def _CleanupExports(self, feedback_fn):
349 7ecd5e87 Thomas Thrainer
    """Removes exports of current instance from all other nodes.
350 7ecd5e87 Thomas Thrainer

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

354 7ecd5e87 Thomas Thrainer
    """
355 7ecd5e87 Thomas Thrainer
    assert self.op.mode != constants.EXPORT_MODE_REMOTE
356 7ecd5e87 Thomas Thrainer
357 1c3231aa Thomas Thrainer
    node_uuids = self.cfg.GetNodeList()
358 1c3231aa Thomas Thrainer
    node_uuids.remove(self.dst_node.uuid)
359 7ecd5e87 Thomas Thrainer
360 7ecd5e87 Thomas Thrainer
    # on one-node clusters nodelist will be empty after the removal
361 7ecd5e87 Thomas Thrainer
    # if we proceed the backup would be removed because OpBackupQuery
362 7ecd5e87 Thomas Thrainer
    # substitutes an empty list with the full cluster node list.
363 7ecd5e87 Thomas Thrainer
    iname = self.instance.name
364 1c3231aa Thomas Thrainer
    if node_uuids:
365 7ecd5e87 Thomas Thrainer
      feedback_fn("Removing old exports for instance %s" % iname)
366 1c3231aa Thomas Thrainer
      exportlist = self.rpc.call_export_list(node_uuids)
367 1c3231aa Thomas Thrainer
      for node_uuid in exportlist:
368 1c3231aa Thomas Thrainer
        if exportlist[node_uuid].fail_msg:
369 7ecd5e87 Thomas Thrainer
          continue
370 1c3231aa Thomas Thrainer
        if iname in exportlist[node_uuid].payload:
371 1c3231aa Thomas Thrainer
          msg = self.rpc.call_export_remove(node_uuid, iname).fail_msg
372 7ecd5e87 Thomas Thrainer
          if msg:
373 7ecd5e87 Thomas Thrainer
            self.LogWarning("Could not remove older export for instance %s"
374 1c3231aa Thomas Thrainer
                            " on node %s: %s", iname,
375 1c3231aa Thomas Thrainer
                            self.cfg.GetNodeName(node_uuid), msg)
376 7ecd5e87 Thomas Thrainer
377 7ecd5e87 Thomas Thrainer
  def Exec(self, feedback_fn):
378 7ecd5e87 Thomas Thrainer
    """Export an instance to an image in the cluster.
379 7ecd5e87 Thomas Thrainer

380 7ecd5e87 Thomas Thrainer
    """
381 7ecd5e87 Thomas Thrainer
    assert self.op.mode in constants.EXPORT_MODES
382 7ecd5e87 Thomas Thrainer
383 d0d7d7cf Thomas Thrainer
    src_node_uuid = self.instance.primary_node
384 7ecd5e87 Thomas Thrainer
385 7ecd5e87 Thomas Thrainer
    if self.op.shutdown:
386 7ecd5e87 Thomas Thrainer
      # shutdown the instance, but not the disks
387 d0d7d7cf Thomas Thrainer
      feedback_fn("Shutting down instance %s" % self.instance.name)
388 d0d7d7cf Thomas Thrainer
      result = self.rpc.call_instance_shutdown(src_node_uuid, self.instance,
389 7ecd5e87 Thomas Thrainer
                                               self.op.shutdown_timeout,
390 7ecd5e87 Thomas Thrainer
                                               self.op.reason)
391 7ecd5e87 Thomas Thrainer
      # TODO: Maybe ignore failures if ignore_remove_failures is set
392 7ecd5e87 Thomas Thrainer
      result.Raise("Could not shutdown instance %s on"
393 d0d7d7cf Thomas Thrainer
                   " node %s" % (self.instance.name,
394 1c3231aa Thomas Thrainer
                                 self.cfg.GetNodeName(src_node_uuid)))
395 7ecd5e87 Thomas Thrainer
396 7ecd5e87 Thomas Thrainer
    # set the disks ID correctly since call_instance_start needs the
397 7ecd5e87 Thomas Thrainer
    # correct drbd minor to create the symlinks
398 d0d7d7cf Thomas Thrainer
    for disk in self.instance.disks:
399 1c3231aa Thomas Thrainer
      self.cfg.SetDiskID(disk, src_node_uuid)
400 7ecd5e87 Thomas Thrainer
401 d0d7d7cf Thomas Thrainer
    activate_disks = not self.instance.disks_active
402 7ecd5e87 Thomas Thrainer
403 7ecd5e87 Thomas Thrainer
    if activate_disks:
404 7ecd5e87 Thomas Thrainer
      # Activate the instance disks if we'exporting a stopped instance
405 d0d7d7cf Thomas Thrainer
      feedback_fn("Activating disks for %s" % self.instance.name)
406 d0d7d7cf Thomas Thrainer
      StartInstanceDisks(self, self.instance, None)
407 7ecd5e87 Thomas Thrainer
408 7ecd5e87 Thomas Thrainer
    try:
409 7ecd5e87 Thomas Thrainer
      helper = masterd.instance.ExportInstanceHelper(self, feedback_fn,
410 d0d7d7cf Thomas Thrainer
                                                     self.instance)
411 7ecd5e87 Thomas Thrainer
412 7ecd5e87 Thomas Thrainer
      helper.CreateSnapshots()
413 7ecd5e87 Thomas Thrainer
      try:
414 7ecd5e87 Thomas Thrainer
        if (self.op.shutdown and
415 d0d7d7cf Thomas Thrainer
            self.instance.admin_state == constants.ADMINST_UP and
416 7ecd5e87 Thomas Thrainer
            not self.op.remove_instance):
417 7ecd5e87 Thomas Thrainer
          assert not activate_disks
418 d0d7d7cf Thomas Thrainer
          feedback_fn("Starting instance %s" % self.instance.name)
419 1c3231aa Thomas Thrainer
          result = self.rpc.call_instance_start(src_node_uuid,
420 d0d7d7cf Thomas Thrainer
                                                (self.instance, None, None),
421 d0d7d7cf Thomas Thrainer
                                                False, self.op.reason)
422 7ecd5e87 Thomas Thrainer
          msg = result.fail_msg
423 7ecd5e87 Thomas Thrainer
          if msg:
424 7ecd5e87 Thomas Thrainer
            feedback_fn("Failed to start instance: %s" % msg)
425 d0d7d7cf Thomas Thrainer
            ShutdownInstanceDisks(self, self.instance)
426 7ecd5e87 Thomas Thrainer
            raise errors.OpExecError("Could not start instance: %s" % msg)
427 7ecd5e87 Thomas Thrainer
428 7ecd5e87 Thomas Thrainer
        if self.op.mode == constants.EXPORT_MODE_LOCAL:
429 7ecd5e87 Thomas Thrainer
          (fin_resu, dresults) = helper.LocalExport(self.dst_node)
430 7ecd5e87 Thomas Thrainer
        elif self.op.mode == constants.EXPORT_MODE_REMOTE:
431 7ecd5e87 Thomas Thrainer
          connect_timeout = constants.RIE_CONNECT_TIMEOUT
432 7ecd5e87 Thomas Thrainer
          timeouts = masterd.instance.ImportExportTimeouts(connect_timeout)
433 7ecd5e87 Thomas Thrainer
434 7ecd5e87 Thomas Thrainer
          (key_name, _, _) = self.x509_key_name
435 7ecd5e87 Thomas Thrainer
436 7ecd5e87 Thomas Thrainer
          dest_ca_pem = \
437 7ecd5e87 Thomas Thrainer
            OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM,
438 7ecd5e87 Thomas Thrainer
                                            self.dest_x509_ca)
439 7ecd5e87 Thomas Thrainer
440 7ecd5e87 Thomas Thrainer
          (fin_resu, dresults) = helper.RemoteExport(self.dest_disk_info,
441 7ecd5e87 Thomas Thrainer
                                                     key_name, dest_ca_pem,
442 7ecd5e87 Thomas Thrainer
                                                     timeouts)
443 7ecd5e87 Thomas Thrainer
      finally:
444 7ecd5e87 Thomas Thrainer
        helper.Cleanup()
445 7ecd5e87 Thomas Thrainer
446 7ecd5e87 Thomas Thrainer
      # Check for backwards compatibility
447 d0d7d7cf Thomas Thrainer
      assert len(dresults) == len(self.instance.disks)
448 7ecd5e87 Thomas Thrainer
      assert compat.all(isinstance(i, bool) for i in dresults), \
449 7ecd5e87 Thomas Thrainer
             "Not all results are boolean: %r" % dresults
450 7ecd5e87 Thomas Thrainer
451 7ecd5e87 Thomas Thrainer
    finally:
452 7ecd5e87 Thomas Thrainer
      if activate_disks:
453 d0d7d7cf Thomas Thrainer
        feedback_fn("Deactivating disks for %s" % self.instance.name)
454 d0d7d7cf Thomas Thrainer
        ShutdownInstanceDisks(self, self.instance)
455 7ecd5e87 Thomas Thrainer
456 7ecd5e87 Thomas Thrainer
    if not (compat.all(dresults) and fin_resu):
457 7ecd5e87 Thomas Thrainer
      failures = []
458 7ecd5e87 Thomas Thrainer
      if not fin_resu:
459 7ecd5e87 Thomas Thrainer
        failures.append("export finalization")
460 7ecd5e87 Thomas Thrainer
      if not compat.all(dresults):
461 7ecd5e87 Thomas Thrainer
        fdsk = utils.CommaJoin(idx for (idx, dsk) in enumerate(dresults)
462 7ecd5e87 Thomas Thrainer
                               if not dsk)
463 7ecd5e87 Thomas Thrainer
        failures.append("disk export: disk(s) %s" % fdsk)
464 7ecd5e87 Thomas Thrainer
465 7ecd5e87 Thomas Thrainer
      raise errors.OpExecError("Export failed, errors in %s" %
466 7ecd5e87 Thomas Thrainer
                               utils.CommaJoin(failures))
467 7ecd5e87 Thomas Thrainer
468 7ecd5e87 Thomas Thrainer
    # At this point, the export was successful, we can cleanup/finish
469 7ecd5e87 Thomas Thrainer
470 7ecd5e87 Thomas Thrainer
    # Remove instance if requested
471 7ecd5e87 Thomas Thrainer
    if self.op.remove_instance:
472 d0d7d7cf Thomas Thrainer
      feedback_fn("Removing instance %s" % self.instance.name)
473 d0d7d7cf Thomas Thrainer
      RemoveInstance(self, feedback_fn, self.instance,
474 5eacbcae Thomas Thrainer
                     self.op.ignore_remove_failures)
475 7ecd5e87 Thomas Thrainer
476 7ecd5e87 Thomas Thrainer
    if self.op.mode == constants.EXPORT_MODE_LOCAL:
477 7ecd5e87 Thomas Thrainer
      self._CleanupExports(feedback_fn)
478 7ecd5e87 Thomas Thrainer
479 7ecd5e87 Thomas Thrainer
    return fin_resu, dresults
480 7ecd5e87 Thomas Thrainer
481 7ecd5e87 Thomas Thrainer
482 7ecd5e87 Thomas Thrainer
class LUBackupRemove(NoHooksLU):
483 7ecd5e87 Thomas Thrainer
  """Remove exports related to the named instance.
484 7ecd5e87 Thomas Thrainer

485 7ecd5e87 Thomas Thrainer
  """
486 7ecd5e87 Thomas Thrainer
  REQ_BGL = False
487 7ecd5e87 Thomas Thrainer
488 7ecd5e87 Thomas Thrainer
  def ExpandNames(self):
489 7ecd5e87 Thomas Thrainer
    self.needed_locks = {
490 7ecd5e87 Thomas Thrainer
      # We need all nodes to be locked in order for RemoveExport to work, but
491 7ecd5e87 Thomas Thrainer
      # we don't need to lock the instance itself, as nothing will happen to it
492 7ecd5e87 Thomas Thrainer
      # (and we can remove exports also for a removed instance)
493 7ecd5e87 Thomas Thrainer
      locking.LEVEL_NODE: locking.ALL_SET,
494 7ecd5e87 Thomas Thrainer
495 7ecd5e87 Thomas Thrainer
      # Removing backups is quick, so blocking allocations is justified
496 7ecd5e87 Thomas Thrainer
      locking.LEVEL_NODE_ALLOC: locking.ALL_SET,
497 7ecd5e87 Thomas Thrainer
      }
498 7ecd5e87 Thomas Thrainer
499 7ecd5e87 Thomas Thrainer
    # Allocations should be stopped while this LU runs with node locks, but it
500 7ecd5e87 Thomas Thrainer
    # doesn't have to be exclusive
501 7ecd5e87 Thomas Thrainer
    self.share_locks[locking.LEVEL_NODE_ALLOC] = 1
502 7ecd5e87 Thomas Thrainer
503 7ecd5e87 Thomas Thrainer
  def Exec(self, feedback_fn):
504 7ecd5e87 Thomas Thrainer
    """Remove any export.
505 7ecd5e87 Thomas Thrainer

506 7ecd5e87 Thomas Thrainer
    """
507 da4a52a3 Thomas Thrainer
    (_, inst_name) = self.cfg.ExpandInstanceName(self.op.instance_name)
508 7ecd5e87 Thomas Thrainer
    # If the instance was not found we'll try with the name that was passed in.
509 7ecd5e87 Thomas Thrainer
    # This will only work if it was an FQDN, though.
510 7ecd5e87 Thomas Thrainer
    fqdn_warn = False
511 da4a52a3 Thomas Thrainer
    if not inst_name:
512 7ecd5e87 Thomas Thrainer
      fqdn_warn = True
513 da4a52a3 Thomas Thrainer
      inst_name = self.op.instance_name
514 7ecd5e87 Thomas Thrainer
515 7ecd5e87 Thomas Thrainer
    locked_nodes = self.owned_locks(locking.LEVEL_NODE)
516 7ecd5e87 Thomas Thrainer
    exportlist = self.rpc.call_export_list(locked_nodes)
517 7ecd5e87 Thomas Thrainer
    found = False
518 1c3231aa Thomas Thrainer
    for node_uuid in exportlist:
519 1c3231aa Thomas Thrainer
      msg = exportlist[node_uuid].fail_msg
520 7ecd5e87 Thomas Thrainer
      if msg:
521 1c3231aa Thomas Thrainer
        self.LogWarning("Failed to query node %s (continuing): %s",
522 1c3231aa Thomas Thrainer
                        self.cfg.GetNodeName(node_uuid), msg)
523 7ecd5e87 Thomas Thrainer
        continue
524 da4a52a3 Thomas Thrainer
      if inst_name in exportlist[node_uuid].payload:
525 7ecd5e87 Thomas Thrainer
        found = True
526 da4a52a3 Thomas Thrainer
        result = self.rpc.call_export_remove(node_uuid, inst_name)
527 7ecd5e87 Thomas Thrainer
        msg = result.fail_msg
528 7ecd5e87 Thomas Thrainer
        if msg:
529 7ecd5e87 Thomas Thrainer
          logging.error("Could not remove export for instance %s"
530 da4a52a3 Thomas Thrainer
                        " on node %s: %s", inst_name,
531 1c3231aa Thomas Thrainer
                        self.cfg.GetNodeName(node_uuid), msg)
532 7ecd5e87 Thomas Thrainer
533 7ecd5e87 Thomas Thrainer
    if fqdn_warn and not found:
534 7ecd5e87 Thomas Thrainer
      feedback_fn("Export not found. If trying to remove an export belonging"
535 7ecd5e87 Thomas Thrainer
                  " to a deleted instance please use its Fully Qualified"
536 7ecd5e87 Thomas Thrainer
                  " Domain Name.")