Statistics
| Branch: | Tag: | Revision:

root / lib / cmdlib / backup.py @ 06c2fb4a

History | View | Annotate | Download (18 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 5eacbcae Thomas Thrainer
  ExpandNodeName
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 5eacbcae 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 7ecd5e87 Thomas Thrainer
    nodes = self._GetNames(lu, lu.cfg.GetNodeList(), locking.LEVEL_NODE)
86 7ecd5e87 Thomas Thrainer
87 7ecd5e87 Thomas Thrainer
    result = []
88 7ecd5e87 Thomas Thrainer
89 7ecd5e87 Thomas Thrainer
    for (node, nres) in lu.rpc.call_export_list(nodes).items():
90 7ecd5e87 Thomas Thrainer
      if nres.fail_msg:
91 7ecd5e87 Thomas Thrainer
        result.append((node, None))
92 7ecd5e87 Thomas Thrainer
      else:
93 7ecd5e87 Thomas Thrainer
        result.extend((node, 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 7ecd5e87 Thomas Thrainer
    instance_name = self.op.instance_name
140 7ecd5e87 Thomas Thrainer
141 7ecd5e87 Thomas Thrainer
    self.instance = self.cfg.GetInstanceInfo(instance_name)
142 7ecd5e87 Thomas Thrainer
    assert self.instance is not None, \
143 7ecd5e87 Thomas Thrainer
          "Cannot retrieve locked instance %s" % self.op.instance_name
144 5eacbcae Thomas Thrainer
    CheckNodeOnline(self, self.instance.primary_node)
145 7ecd5e87 Thomas Thrainer
146 5eacbcae Thomas Thrainer
    self._cds = GetClusterDomainSecret()
147 7ecd5e87 Thomas Thrainer
148 7ecd5e87 Thomas Thrainer
  def Exec(self, feedback_fn):
149 7ecd5e87 Thomas Thrainer
    """Prepares an instance for an export.
150 7ecd5e87 Thomas Thrainer

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

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

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

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

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

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

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

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

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

355 7ecd5e87 Thomas Thrainer
    """
356 7ecd5e87 Thomas Thrainer
    assert self.op.mode != constants.EXPORT_MODE_REMOTE
357 7ecd5e87 Thomas Thrainer
358 7ecd5e87 Thomas Thrainer
    nodelist = self.cfg.GetNodeList()
359 7ecd5e87 Thomas Thrainer
    nodelist.remove(self.dst_node.name)
360 7ecd5e87 Thomas Thrainer
361 7ecd5e87 Thomas Thrainer
    # on one-node clusters nodelist will be empty after the removal
362 7ecd5e87 Thomas Thrainer
    # if we proceed the backup would be removed because OpBackupQuery
363 7ecd5e87 Thomas Thrainer
    # substitutes an empty list with the full cluster node list.
364 7ecd5e87 Thomas Thrainer
    iname = self.instance.name
365 7ecd5e87 Thomas Thrainer
    if nodelist:
366 7ecd5e87 Thomas Thrainer
      feedback_fn("Removing old exports for instance %s" % iname)
367 7ecd5e87 Thomas Thrainer
      exportlist = self.rpc.call_export_list(nodelist)
368 7ecd5e87 Thomas Thrainer
      for node in exportlist:
369 7ecd5e87 Thomas Thrainer
        if exportlist[node].fail_msg:
370 7ecd5e87 Thomas Thrainer
          continue
371 7ecd5e87 Thomas Thrainer
        if iname in exportlist[node].payload:
372 7ecd5e87 Thomas Thrainer
          msg = self.rpc.call_export_remove(node, iname).fail_msg
373 7ecd5e87 Thomas Thrainer
          if msg:
374 7ecd5e87 Thomas Thrainer
            self.LogWarning("Could not remove older export for instance %s"
375 7ecd5e87 Thomas Thrainer
                            " on node %s: %s", iname, node, 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 7ecd5e87 Thomas Thrainer
    instance = self.instance
384 7ecd5e87 Thomas Thrainer
    src_node = instance.primary_node
385 7ecd5e87 Thomas Thrainer
386 7ecd5e87 Thomas Thrainer
    if self.op.shutdown:
387 7ecd5e87 Thomas Thrainer
      # shutdown the instance, but not the disks
388 7ecd5e87 Thomas Thrainer
      feedback_fn("Shutting down instance %s" % instance.name)
389 7ecd5e87 Thomas Thrainer
      result = self.rpc.call_instance_shutdown(src_node, instance,
390 7ecd5e87 Thomas Thrainer
                                               self.op.shutdown_timeout,
391 7ecd5e87 Thomas Thrainer
                                               self.op.reason)
392 7ecd5e87 Thomas Thrainer
      # TODO: Maybe ignore failures if ignore_remove_failures is set
393 7ecd5e87 Thomas Thrainer
      result.Raise("Could not shutdown instance %s on"
394 7ecd5e87 Thomas Thrainer
                   " node %s" % (instance.name, src_node))
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 7ecd5e87 Thomas Thrainer
    for disk in instance.disks:
399 7ecd5e87 Thomas Thrainer
      self.cfg.SetDiskID(disk, src_node)
400 7ecd5e87 Thomas Thrainer
401 1d4a4b26 Thomas Thrainer
    activate_disks = not 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 7ecd5e87 Thomas Thrainer
      feedback_fn("Activating disks for %s" % instance.name)
406 5eacbcae Thomas Thrainer
      StartInstanceDisks(self, instance, None)
407 7ecd5e87 Thomas Thrainer
408 7ecd5e87 Thomas Thrainer
    try:
409 7ecd5e87 Thomas Thrainer
      helper = masterd.instance.ExportInstanceHelper(self, feedback_fn,
410 7ecd5e87 Thomas Thrainer
                                                     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 7ecd5e87 Thomas Thrainer
            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 7ecd5e87 Thomas Thrainer
          feedback_fn("Starting instance %s" % instance.name)
419 7ecd5e87 Thomas Thrainer
          result = self.rpc.call_instance_start(src_node,
420 7ecd5e87 Thomas Thrainer
                                                (instance, None, None), False,
421 7ecd5e87 Thomas Thrainer
                                                 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 5eacbcae Thomas Thrainer
            ShutdownInstanceDisks(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 7ecd5e87 Thomas Thrainer
      assert len(dresults) == len(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 7ecd5e87 Thomas Thrainer
        feedback_fn("Deactivating disks for %s" % instance.name)
454 5eacbcae Thomas Thrainer
        ShutdownInstanceDisks(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 7ecd5e87 Thomas Thrainer
      feedback_fn("Removing instance %s" % instance.name)
473 5eacbcae Thomas Thrainer
      RemoveInstance(self, feedback_fn, 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 7ecd5e87 Thomas Thrainer
    instance_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 7ecd5e87 Thomas Thrainer
    if not instance_name:
512 7ecd5e87 Thomas Thrainer
      fqdn_warn = True
513 7ecd5e87 Thomas Thrainer
      instance_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 7ecd5e87 Thomas Thrainer
    for node in exportlist:
519 7ecd5e87 Thomas Thrainer
      msg = exportlist[node].fail_msg
520 7ecd5e87 Thomas Thrainer
      if msg:
521 7ecd5e87 Thomas Thrainer
        self.LogWarning("Failed to query node %s (continuing): %s", node, msg)
522 7ecd5e87 Thomas Thrainer
        continue
523 7ecd5e87 Thomas Thrainer
      if instance_name in exportlist[node].payload:
524 7ecd5e87 Thomas Thrainer
        found = True
525 7ecd5e87 Thomas Thrainer
        result = self.rpc.call_export_remove(node, instance_name)
526 7ecd5e87 Thomas Thrainer
        msg = result.fail_msg
527 7ecd5e87 Thomas Thrainer
        if msg:
528 7ecd5e87 Thomas Thrainer
          logging.error("Could not remove export for instance %s"
529 7ecd5e87 Thomas Thrainer
                        " on node %s: %s", instance_name, node, msg)
530 7ecd5e87 Thomas Thrainer
531 7ecd5e87 Thomas Thrainer
    if fqdn_warn and not found:
532 7ecd5e87 Thomas Thrainer
      feedback_fn("Export not found. If trying to remove an export belonging"
533 7ecd5e87 Thomas Thrainer
                  " to a deleted instance please use its Fully Qualified"
534 7ecd5e87 Thomas Thrainer
                  " Domain Name.")