Generate a shared HMAC key at cluster init time
[ganeti-local] / lib / backend.py
index 01fd28d..9cce065 100644 (file)
 # 02110-1301, USA.
 
 
-"""Functions used by the node daemon"""
+"""Functions used by the node daemon
+
+@var _ALLOWED_UPLOAD_FILES: denotes which files are accepted in
+     the L{UploadFile} function
+
+"""
 
 
 import os
@@ -145,6 +150,32 @@ def _CleanDirectory(path, exclude=None):
       utils.RemoveFile(full_name)
 
 
+def _BuildUploadFileList():
+  """Build the list of allowed upload files.
+
+  This is abstracted so that it's built only once at module import time.
+
+  """
+  allowed_files = set([
+    constants.CLUSTER_CONF_FILE,
+    constants.ETC_HOSTS,
+    constants.SSH_KNOWN_HOSTS_FILE,
+    constants.VNC_PASSWORD_FILE,
+    constants.RAPI_CERT_FILE,
+    constants.RAPI_USERS_FILE,
+    constants.HMAC_CLUSTER_KEY,
+    ])
+
+  for hv_name in constants.HYPER_TYPES:
+    hv_class = hypervisor.GetHypervisorClass(hv_name)
+    allowed_files.update(hv_class.GetAncillaryFiles())
+
+  return frozenset(allowed_files)
+
+
+_ALLOWED_UPLOAD_FILES = _BuildUploadFileList()
+
+
 def JobQueuePurge():
   """Removes job queue files and archived jobs.
 
@@ -174,10 +205,10 @@ def GetMasterInfo():
     master_node = cfg.GetMasterNode()
   except errors.ConfigurationError, err:
     _Fail("Cluster configuration incomplete: %s", err, exc=True)
-  return master_netdev, master_ip, master_node
+  return (master_netdev, master_ip, master_node)
 
 
-def StartMaster(start_daemons):
+def StartMaster(start_daemons, no_voting):
   """Activate local node as master node.
 
   The function will always try activate the IP address of the master
@@ -187,6 +218,9 @@ def StartMaster(start_daemons):
   @type start_daemons: boolean
   @param start_daemons: whether to also start the master
       daemons (ganeti-masterd and ganeti-rapi)
+  @type no_voting: boolean
+  @param no_voting: whether to start ganeti-masterd without a node vote
+      (if start_daemons is True), but still non-interactively
   @rtype: None
 
   """
@@ -217,8 +251,17 @@ def StartMaster(start_daemons):
 
   # and now start the master and rapi daemons
   if start_daemons:
-    for daemon in 'ganeti-masterd', 'ganeti-rapi':
-      result = utils.RunCmd([daemon])
+    daemons_params = {
+        'ganeti-masterd': [],
+        'ganeti-rapi': [],
+        }
+    if no_voting:
+      daemons_params['ganeti-masterd'].append('--no-voting')
+      daemons_params['ganeti-masterd'].append('--yes-do-it')
+    for daemon in daemons_params:
+      cmd = [daemon]
+      cmd.extend(daemons_params[daemon])
+      result = utils.RunCmd(cmd)
       if result.failed:
         msg = "Can't start daemon %s: %s" % (daemon, result.output)
         logging.error(msg)
@@ -337,7 +380,7 @@ def LeaveCluster():
 
 
 def GetNodeInfo(vgname, hypervisor_type):
-  """Gives back a hash with different informations about the node.
+  """Gives back a hash with different information about the node.
 
   @type vgname: C{string}
   @param vgname: the name of the volume group to ask for disk space information
@@ -609,7 +652,7 @@ def GetInstanceList(hypervisor_list):
 
 
 def GetInstanceInfo(instance, hname):
-  """Gives back the informations about an instance as a dictionary.
+  """Gives back the information about an instance as a dictionary.
 
   @type instance: string
   @param instance: the instance name
@@ -712,7 +755,7 @@ def InstanceOsAdd(instance, reinstall):
   """
   inst_os = OSFromDisk(instance.os)
 
-  create_env = OSEnvironment(instance)
+  create_env = OSEnvironment(instance, inst_os)
   if reinstall:
     create_env['INSTANCE_REINSTALL'] = "1"
 
@@ -744,7 +787,7 @@ def RunRenameInstance(instance, old_name):
   """
   inst_os = OSFromDisk(instance.os)
 
-  rename_env = OSEnvironment(instance)
+  rename_env = OSEnvironment(instance, inst_os)
   rename_env['OLD_INSTANCE_NAME'] = old_name
 
   logfile = "%s/rename-%s-%s-%s-%d.log" % (constants.LOG_OS_DIR, instance.os,
@@ -764,7 +807,7 @@ def RunRenameInstance(instance, old_name):
 
 
 def _GetVGInfo(vg_name):
-  """Get informations about the volume group.
+  """Get information about the volume group.
 
   @type vg_name: str
   @param vg_name: the volume group which we query
@@ -931,7 +974,7 @@ def InstanceShutdown(instance):
   # test every 10secs for 2min
 
   time.sleep(1)
-  for dummy in range(11):
+  for _ in range(11):
     if instance.name not in GetInstanceList([hv_name]):
       break
     time.sleep(10)
@@ -963,9 +1006,10 @@ def InstanceReboot(instance, reboot_type):
         instance OS, do not recreate the VM
       - L{constants.INSTANCE_REBOOT_HARD}: tear down and
         restart the VM (at the hypervisor level)
-      - the other reboot type (L{constants.INSTANCE_REBOOT_HARD})
-        is not accepted here, since that mode is handled
-        differently
+      - the other reboot type (L{constants.INSTANCE_REBOOT_FULL}) is
+        not accepted here, since that mode is handled differently, in
+        cmdlib, and translates into full stop and start of the
+        instance (instead of a call_instance_reboot RPC)
   @rtype: None
 
   """
@@ -1241,7 +1285,7 @@ def BlockdevAssemble(disk, owner, as_primary):
 def BlockdevShutdown(disk):
   """Shut down a block device.
 
-  First, if the device is assembled (Attach() is successfull), then
+  First, if the device is assembled (Attach() is successful), then
   the device is shutdown. Then the children of the device are
   shutdown.
 
@@ -1347,7 +1391,7 @@ def BlockdevGetmirrorstatus(disks):
 def _RecursiveFindBD(disk):
   """Check if a device is activated.
 
-  If so, return informations about the real device.
+  If so, return information about the real device.
 
   @type disk: L{objects.Disk}
   @param disk: the disk object we need to find
@@ -1367,7 +1411,7 @@ def _RecursiveFindBD(disk):
 def BlockdevFind(disk):
   """Check if a device is activated.
 
-  If it is, return informations about the real device.
+  If it is, return information about the real device.
 
   @type disk: L{objects.Disk}
   @param disk: the disk to find
@@ -1412,20 +1456,7 @@ def UploadFile(file_name, data, mode, uid, gid, atime, mtime):
   if not os.path.isabs(file_name):
     _Fail("Filename passed to UploadFile is not absolute: '%s'", file_name)
 
-  allowed_files = set([
-    constants.CLUSTER_CONF_FILE,
-    constants.ETC_HOSTS,
-    constants.SSH_KNOWN_HOSTS_FILE,
-    constants.VNC_PASSWORD_FILE,
-    constants.RAPI_CERT_FILE,
-    constants.RAPI_USERS_FILE,
-    ])
-
-  for hv_name in constants.HYPER_TYPES:
-    hv_class = hypervisor.GetHypervisor(hv_name)
-    allowed_files.update(hv_class.GetAncillaryFiles())
-
-  if file_name not in allowed_files:
+  if file_name not in _ALLOWED_UPLOAD_FILES:
     _Fail("Filename passed to UploadFile not in allowed upload targets: '%s'",
           file_name)
 
@@ -1569,9 +1600,9 @@ def _TryOSFromDisk(name, base_dir=None):
     # push the error up
     return status, api_versions
 
-  if constants.OS_API_VERSION not in api_versions:
+  if not constants.OS_API_VERSIONS.intersection(api_versions):
     return False, ("API version mismatch for path '%s': found %s, want %s." %
-                   (os_dir, api_versions, constants.OS_API_VERSION))
+                   (os_dir, api_versions, constants.OS_API_VERSIONS))
 
   # OS Scripts dictionary, we will populate it with the actual script names
   os_scripts = dict.fromkeys(constants.OS_SCRIPTS)
@@ -1628,11 +1659,13 @@ def OSFromDisk(name, base_dir=None):
   return payload
 
 
-def OSEnvironment(instance, debug=0):
+def OSEnvironment(instance, os, debug=0):
   """Calculate the environment for an os script.
 
   @type instance: L{objects.Instance}
   @param instance: target instance for the os script run
+  @type os: L{objects.OS}
+  @param os: operating system for which the environment is being built
   @type debug: integer
   @param debug: debug level (0 or 1, for OS Api 10)
   @rtype: dict
@@ -1642,7 +1675,8 @@ def OSEnvironment(instance, debug=0):
 
   """
   result = {}
-  result['OS_API_VERSION'] = '%d' % constants.OS_API_VERSION
+  api_version = max(constants.OS_API_VERSIONS.intersection(os.api_versions))
+  result['OS_API_VERSION'] = '%d' % api_version
   result['INSTANCE_NAME'] = instance.name
   result['INSTANCE_OS'] = instance.os
   result['HYPERVISOR'] = instance.hypervisor
@@ -1759,9 +1793,9 @@ def ExportSnapshot(disk, dest_node, instance, cluster_name, idx):
   @rtype: None
 
   """
-  export_env = OSEnvironment(instance)
-
   inst_os = OSFromDisk(instance.os)
+  export_env = OSEnvironment(instance, inst_os)
+
   export_script = inst_os.export_script
 
   logfile = "%s/exp-%s-%s-%s.log" % (constants.LOG_OS_DIR, inst_os.name,
@@ -1903,8 +1937,8 @@ def ImportOSIntoInstance(instance, src_node, src_images, cluster_name):
   @return: each boolean represent the success of importing the n-th disk
 
   """
-  import_env = OSEnvironment(instance)
   inst_os = OSFromDisk(instance.os)
+  import_env = OSEnvironment(instance, inst_os)
   import_script = inst_os.import_script
 
   logfile = "%s/import-%s-%s-%s.log" % (constants.LOG_OS_DIR, instance.os,
@@ -2064,7 +2098,7 @@ def RemoveFileStorageDir(file_storage_dir):
   @param file_storage_dir: the directory we should cleanup
   @rtype: tuple (success,)
   @return: tuple of one element, C{success}, denoting
-      whether the operation was successfull
+      whether the operation was successful
 
   """
   file_storage_dir = _TransformFileStorageDir(file_storage_dir)
@@ -2250,7 +2284,8 @@ def DemoteFromMC():
   if utils.IsProcessAlive(utils.ReadPidFile(pid_file)):
     _Fail("The master daemon is running, will not demote")
   try:
-    utils.CreateBackup(constants.CLUSTER_CONF_FILE)
+    if os.path.isfile(constants.CLUSTER_CONF_FILE):
+      utils.CreateBackup(constants.CLUSTER_CONF_FILE)
   except EnvironmentError, err:
     if err.errno != errno.ENOENT:
       _Fail("Error while backing up cluster file: %s", err, exc=True)