LUVerifyCluster: update _ValidateNode description
[ganeti-local] / lib / rapi / rlib2.py
index bef6bb0..955a6f2 100644 (file)
@@ -45,6 +45,7 @@ from ganeti import opcodes
 from ganeti import http
 from ganeti import constants
 from ganeti import cli
+from ganeti import utils
 from ganeti import rapi
 from ganeti.rapi import baserlib
 
@@ -57,7 +58,7 @@ I_FIELDS = ["name", "admin_state", "os",
             "network_port",
             "disk.sizes", "disk_usage",
             "beparams", "hvparams",
-            "oper_state", "oper_ram", "status",
+            "oper_state", "oper_ram", "oper_vcpus", "status",
             ] + _COMMON_FIELDS
 
 N_FIELDS = ["name", "offline", "master_candidate", "drained",
@@ -86,6 +87,9 @@ _NR_MAP = {
 # Request data version field
 _REQ_DATA_VERSION = "__version__"
 
+# Feature string for instance creation request data version 1
+_INST_CREATE_REQV1 = "instance-create-reqv1"
+
 # Timeout for /2/jobs/[job_id]/wait. Gives job up to 10 seconds to change.
 _WFJC_TIMEOUT = 10
 
@@ -127,7 +131,7 @@ class R_2_features(baserlib.R_Generic):
     """Returns list of optional RAPI features implemented.
 
     """
-    return []
+    return [_INST_CREATE_REQV1]
 
 
 class R_2_os(baserlib.R_Generic):
@@ -308,8 +312,10 @@ class R_2_nodes_name(baserlib.R_Generic):
     """
     node_name = self.items[0]
     client = baserlib.GetClient()
-    result = client.QueryNodes(names=[node_name], fields=N_FIELDS,
-                               use_locking=self.useLocking())
+
+    result = baserlib.HandleItemQueryErrors(client.QueryNodes,
+                                            names=[node_name], fields=N_FIELDS,
+                                            use_locking=self.useLocking())
 
     return baserlib.MapFields(N_FIELDS, result[0])
 
@@ -337,11 +343,11 @@ class R_2_nodes_name_role(baserlib.R_Generic):
     @return: a job id
 
     """
-    if not isinstance(self.req.request_body, basestring):
+    if not isinstance(self.request_body, basestring):
       raise http.HttpBadRequest("Invalid body contents, not a string")
 
     node_name = self.items[0]
-    role = self.req.request_body
+    role = self.request_body
 
     if role == _NR_REGULAR:
       candidate = False
@@ -383,12 +389,32 @@ class R_2_nodes_name_evacuate(baserlib.R_Generic):
     node_name = self.items[0]
     remote_node = self._checkStringVariable("remote_node", default=None)
     iallocator = self._checkStringVariable("iallocator", default=None)
+    early_r = bool(self._checkIntVariable("early_release", default=0))
+    dry_run = bool(self.dryRun())
 
-    op = opcodes.OpEvacuateNode(node_name=node_name,
-                                remote_node=remote_node,
-                                iallocator=iallocator)
+    cl = baserlib.GetClient()
 
-    return baserlib.SubmitJob([op])
+    op = opcodes.OpNodeEvacuationStrategy(nodes=[node_name],
+                                          iallocator=iallocator,
+                                          remote_node=remote_node)
+
+    job_id = baserlib.SubmitJob([op], cl)
+    # we use custom feedback function, instead of print we log the status
+    result = cli.PollJob(job_id, cl, feedback_fn=baserlib.FeedbackFn)
+
+    jobs = []
+    for iname, node in result:
+      if dry_run:
+        jid = None
+      else:
+        op = opcodes.OpReplaceDisks(instance_name=iname,
+                                    remote_node=node, disks=[],
+                                    mode=constants.REPLACE_DISK_CHG,
+                                    early_release=early_r)
+        jid = baserlib.SubmitJob([op])
+      jobs.append((jid, iname, node))
+
+    return jobs
 
 
 class R_2_nodes_name_migrate(baserlib.R_Generic):
@@ -486,6 +512,106 @@ class R_2_nodes_name_storage_repair(baserlib.R_Generic):
     return baserlib.SubmitJob([op])
 
 
+def _ParseInstanceCreateRequestVersion1(data, dry_run):
+  """Parses an instance creation request version 1.
+
+  @rtype: L{opcodes.OpCreateInstance}
+  @return: Instance creation opcode
+
+  """
+  # Disks
+  disks_input = baserlib.CheckParameter(data, "disks", exptype=list)
+
+  disks = []
+  for idx, i in enumerate(disks_input):
+    baserlib.CheckType(i, dict, "Disk %d specification" % idx)
+
+    # Size is mandatory
+    try:
+      size = i[constants.IDISK_SIZE]
+    except KeyError:
+      raise http.HttpBadRequest("Disk %d specification wrong: missing disk"
+                                " size" % idx)
+
+    disk = {
+      constants.IDISK_SIZE: size,
+      }
+
+    # Optional disk access mode
+    try:
+      disk_access = i[constants.IDISK_MODE]
+    except KeyError:
+      pass
+    else:
+      disk[constants.IDISK_MODE] = disk_access
+
+    disks.append(disk)
+
+  assert len(disks_input) == len(disks)
+
+  # Network interfaces
+  nics_input = baserlib.CheckParameter(data, "nics", exptype=list)
+
+  nics = []
+  for idx, i in enumerate(nics_input):
+    baserlib.CheckType(i, dict, "NIC %d specification" % idx)
+
+    nic = {}
+
+    for field in constants.INIC_PARAMS:
+      try:
+        value = i[field]
+      except KeyError:
+        continue
+
+      nic[field] = value
+
+    nics.append(nic)
+
+  assert len(nics_input) == len(nics)
+
+  # HV/BE parameters
+  hvparams = baserlib.CheckParameter(data, "hvparams", default={})
+  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
+
+  beparams = baserlib.CheckParameter(data, "beparams", default={})
+  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
+
+  return opcodes.OpCreateInstance(
+    mode=baserlib.CheckParameter(data, "mode"),
+    instance_name=baserlib.CheckParameter(data, "name"),
+    os_type=baserlib.CheckParameter(data, "os", default=None),
+    force_variant=baserlib.CheckParameter(data, "force_variant",
+                                          default=False),
+    pnode=baserlib.CheckParameter(data, "pnode", default=None),
+    snode=baserlib.CheckParameter(data, "snode", default=None),
+    disk_template=baserlib.CheckParameter(data, "disk_template"),
+    disks=disks,
+    nics=nics,
+    src_node=baserlib.CheckParameter(data, "src_node", default=None),
+    src_path=baserlib.CheckParameter(data, "src_path", default=None),
+    start=baserlib.CheckParameter(data, "start", default=True),
+    wait_for_sync=True,
+    ip_check=baserlib.CheckParameter(data, "ip_check", default=True),
+    name_check=baserlib.CheckParameter(data, "name_check", default=True),
+    file_storage_dir=baserlib.CheckParameter(data, "file_storage_dir",
+                                             default=None),
+    file_driver=baserlib.CheckParameter(data, "file_driver",
+                                        default=constants.FD_LOOP),
+    source_handshake=baserlib.CheckParameter(data, "source_handshake",
+                                             default=None),
+    source_x509_ca=baserlib.CheckParameter(data, "source_x509_ca",
+                                           default=None),
+    source_instance_name=baserlib.CheckParameter(data, "source_instance_name",
+                                                 default=None),
+    iallocator=baserlib.CheckParameter(data, "iallocator", default=None),
+    hypervisor=baserlib.CheckParameter(data, "hypervisor", default=None),
+    hvparams=hvparams,
+    beparams=beparams,
+    dry_run=dry_run,
+    )
+
+
 class R_2_instances(baserlib.R_Generic):
   """/2/instances resource.
 
@@ -509,13 +635,16 @@ class R_2_instances(baserlib.R_Generic):
   def _ParseVersion0CreateRequest(self):
     """Parses an instance creation request version 0.
 
+    Request data version 0 is deprecated and should not be used anymore.
+
     @rtype: L{opcodes.OpCreateInstance}
     @return: Instance creation opcode
 
     """
-    beparams = baserlib.MakeParamsDict(self.req.request_body,
+    # Do not modify anymore, request data version 0 is deprecated
+    beparams = baserlib.MakeParamsDict(self.request_body,
                                        constants.BES_PARAMETERS)
-    hvparams = baserlib.MakeParamsDict(self.req.request_body,
+    hvparams = baserlib.MakeParamsDict(self.request_body,
                                        constants.HVS_PARAMETERS)
     fn = self.getBodyParameter
 
@@ -541,6 +670,7 @@ class R_2_instances(baserlib.R_Generic):
     if fn("bridge", None) is not None:
       nics[0]["bridge"] = fn("bridge")
 
+    # Do not modify anymore, request data version 0 is deprecated
     return opcodes.OpCreateInstance(
       mode=constants.INSTANCE_CREATE,
       instance_name=fn('name'),
@@ -559,7 +689,7 @@ class R_2_instances(baserlib.R_Generic):
       hvparams=hvparams,
       beparams=beparams,
       file_storage_dir=fn('file_storage_dir', None),
-      file_driver=fn('file_driver', 'loop'),
+      file_driver=fn('file_driver', constants.FD_LOOP),
       dry_run=bool(self.dryRun()),
       )
 
@@ -569,7 +699,7 @@ class R_2_instances(baserlib.R_Generic):
     @return: a job id
 
     """
-    if not isinstance(self.req.request_body, dict):
+    if not isinstance(self.request_body, dict):
       raise http.HttpBadRequest("Invalid body contents, not a dictionary")
 
     # Default to request data version 0
@@ -577,6 +707,9 @@ class R_2_instances(baserlib.R_Generic):
 
     if data_version == 0:
       op = self._ParseVersion0CreateRequest()
+    elif data_version == 1:
+      op = _ParseInstanceCreateRequestVersion1(self.request_body,
+                                               self.dryRun())
     else:
       raise http.HttpBadRequest("Unsupported request data version %s" %
                                 data_version)
@@ -594,8 +727,11 @@ class R_2_instances_name(baserlib.R_Generic):
     """
     client = baserlib.GetClient()
     instance_name = self.items[0]
-    result = client.QueryInstances(names=[instance_name], fields=I_FIELDS,
-                                   use_locking=self.useLocking())
+
+    result = baserlib.HandleItemQueryErrors(client.QueryInstances,
+                                            names=[instance_name],
+                                            fields=I_FIELDS,
+                                            use_locking=self.useLocking())
 
     return baserlib.MapFields(I_FIELDS, result[0])
 
@@ -781,6 +917,69 @@ class R_2_instances_name_deactivate_disks(baserlib.R_Generic):
     return baserlib.SubmitJob([op])
 
 
+class R_2_instances_name_prepare_export(baserlib.R_Generic):
+  """/2/instances/[instance_name]/prepare-export resource.
+
+  """
+  def PUT(self):
+    """Prepares an export for an instance.
+
+    @return: a job id
+
+    """
+    instance_name = self.items[0]
+    mode = self._checkStringVariable("mode")
+
+    op = opcodes.OpPrepareExport(instance_name=instance_name,
+                                 mode=mode)
+
+    return baserlib.SubmitJob([op])
+
+
+def _ParseExportInstanceRequest(name, data):
+  """Parses a request for an instance export.
+
+  @rtype: L{opcodes.OpExportInstance}
+  @return: Instance export opcode
+
+  """
+  mode = baserlib.CheckParameter(data, "mode",
+                                 default=constants.EXPORT_MODE_LOCAL)
+  target_node = baserlib.CheckParameter(data, "destination")
+  shutdown = baserlib.CheckParameter(data, "shutdown", exptype=bool)
+  remove_instance = baserlib.CheckParameter(data, "remove_instance",
+                                            exptype=bool, default=False)
+  x509_key_name = baserlib.CheckParameter(data, "x509_key_name", default=None)
+  destination_x509_ca = baserlib.CheckParameter(data, "destination_x509_ca",
+                                                default=None)
+
+  return opcodes.OpExportInstance(instance_name=name,
+                                  mode=mode,
+                                  target_node=target_node,
+                                  shutdown=shutdown,
+                                  remove_instance=remove_instance,
+                                  x509_key_name=x509_key_name,
+                                  destination_x509_ca=destination_x509_ca)
+
+
+class R_2_instances_name_export(baserlib.R_Generic):
+  """/2/instances/[instance_name]/export resource.
+
+  """
+  def PUT(self):
+    """Exports an instance.
+
+    @return: a job id
+
+    """
+    if not isinstance(self.request_body, dict):
+      raise http.HttpBadRequest("Invalid body contents, not a dictionary")
+
+    op = _ParseExportInstanceRequest(self.items[0], self.request_body)
+
+    return baserlib.SubmitJob([op])
+
+
 class _R_Tags(baserlib.R_Generic):
   """ Quasiclass for tagging resources