Return more data in rpc.call_volume_list
[ganeti-local] / lib / backend.py
1 #
2 #
3
4 # Copyright (C) 2006, 2007 Google Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 # 02110-1301, USA.
20
21
22 """Functions used by the node daemon"""
23
24
25 import os
26 import os.path
27 import shutil
28 import time
29 import tempfile
30 import stat
31 import errno
32 import re
33 import subprocess
34
35 from ganeti import logger
36 from ganeti import errors
37 from ganeti import utils
38 from ganeti import ssh
39 from ganeti import hypervisor
40 from ganeti import constants
41 from ganeti import bdev
42 from ganeti import objects
43 from ganeti import ssconf
44
45
46 def StartMaster():
47   """Activate local node as master node.
48
49   There are two needed steps for this:
50     - run the master script
51     - register the cron script
52
53   """
54   result = utils.RunCmd([constants.MASTER_SCRIPT, "-d", "start"])
55
56   if result.failed:
57     logger.Error("could not activate cluster interface with command %s,"
58                  " error: '%s'" % (result.cmd, result.output))
59     return False
60
61   return True
62
63
64 def StopMaster():
65   """Deactivate this node as master.
66
67   This does two things:
68     - run the master stop script
69     - remove link to master cron script.
70
71   """
72   result = utils.RunCmd([constants.MASTER_SCRIPT, "-d", "stop"])
73
74   if result.failed:
75     logger.Error("could not deactivate cluster interface with command %s,"
76                  " error: '%s'" % (result.cmd, result.output))
77     return False
78
79   return True
80
81
82 def AddNode(dsa, dsapub, rsa, rsapub, sshkey, sshpub):
83   """Joins this node to the cluster.
84
85   This does the following:
86       - updates the hostkeys of the machine (rsa and dsa)
87       - adds the ssh private key to the user
88       - adds the ssh public key to the users' authorized_keys file
89
90   """
91   sshd_keys =  [(constants.SSH_HOST_RSA_PRIV, rsa, 0600),
92                 (constants.SSH_HOST_RSA_PUB, rsapub, 0644),
93                 (constants.SSH_HOST_DSA_PRIV, dsa, 0600),
94                 (constants.SSH_HOST_DSA_PUB, dsapub, 0644)]
95   for name, content, mode in sshd_keys:
96     utils.WriteFile(name, data=content, mode=mode)
97
98   try:
99     priv_key, pub_key, auth_keys = ssh.GetUserFiles(constants.GANETI_RUNAS,
100                                                     mkdir=True)
101   except errors.OpExecError, err:
102     logger.Error("Error while processing user ssh files: %s" % err)
103     return False
104
105   for name, content in [(priv_key, sshkey), (pub_key, sshpub)]:
106     utils.WriteFile(name, data=content, mode=0600)
107
108   utils.AddAuthorizedKey(auth_keys, sshpub)
109
110   utils.RunCmd([constants.SSH_INITD_SCRIPT, "restart"])
111
112   return True
113
114
115 def LeaveCluster():
116   """Cleans up the current node and prepares it to be removed from the cluster.
117
118   """
119   if os.path.isdir(constants.DATA_DIR):
120     for rel_name in utils.ListVisibleFiles(constants.DATA_DIR):
121       full_name = os.path.join(constants.DATA_DIR, rel_name)
122       if os.path.isfile(full_name) and not os.path.islink(full_name):
123         utils.RemoveFile(full_name)
124
125   try:
126     priv_key, pub_key, auth_keys = ssh.GetUserFiles(constants.GANETI_RUNAS)
127   except errors.OpExecError, err:
128     logger.Error("Error while processing ssh files: %s" % err)
129     return
130
131   f = open(pub_key, 'r')
132   try:
133     utils.RemoveAuthorizedKey(auth_keys, f.read(8192))
134   finally:
135     f.close()
136
137   utils.RemoveFile(priv_key)
138   utils.RemoveFile(pub_key)
139
140
141 def GetNodeInfo(vgname):
142   """Gives back a hash with different informations about the node.
143
144   Returns:
145     { 'vg_size' : xxx,  'vg_free' : xxx, 'memory_domain0': xxx,
146       'memory_free' : xxx, 'memory_total' : xxx }
147     where
148     vg_size is the size of the configured volume group in MiB
149     vg_free is the free size of the volume group in MiB
150     memory_dom0 is the memory allocated for domain0 in MiB
151     memory_free is the currently available (free) ram in MiB
152     memory_total is the total number of ram in MiB
153
154   """
155   outputarray = {}
156   vginfo = _GetVGInfo(vgname)
157   outputarray['vg_size'] = vginfo['vg_size']
158   outputarray['vg_free'] = vginfo['vg_free']
159
160   hyper = hypervisor.GetHypervisor()
161   hyp_info = hyper.GetNodeInfo()
162   if hyp_info is not None:
163     outputarray.update(hyp_info)
164
165   f = open("/proc/sys/kernel/random/boot_id", 'r')
166   try:
167     outputarray["bootid"] = f.read(128).rstrip("\n")
168   finally:
169     f.close()
170
171   return outputarray
172
173
174 def VerifyNode(what):
175   """Verify the status of the local node.
176
177   Args:
178     what - a dictionary of things to check:
179       'filelist' : list of files for which to compute checksums
180       'nodelist' : list of nodes we should check communication with
181       'hypervisor': run the hypervisor-specific verify
182
183   Requested files on local node are checksummed and the result returned.
184
185   The nodelist is traversed, with the following checks being made
186   for each node:
187   - known_hosts key correct
188   - correct resolving of node name (target node returns its own hostname
189     by ssh-execution of 'hostname', result compared against name in list.
190
191   """
192   result = {}
193
194   if 'hypervisor' in what:
195     result['hypervisor'] = hypervisor.GetHypervisor().Verify()
196
197   if 'filelist' in what:
198     result['filelist'] = utils.FingerprintFiles(what['filelist'])
199
200   if 'nodelist' in what:
201     result['nodelist'] = {}
202     for node in what['nodelist']:
203       success, message = ssh.VerifyNodeHostname(node)
204       if not success:
205         result['nodelist'][node] = message
206   return result
207
208
209 def GetVolumeList(vg_name):
210   """Compute list of logical volumes and their size.
211
212   Returns:
213     dictionary of all partions (key) with their size (in MiB), inactive
214     and online status:
215     {'test1': ('20.06', True, True)}
216
217   """
218   lvs = {}
219   sep = '|'
220   result = utils.RunCmd(["lvs", "--noheadings", "--units=m", "--nosuffix",
221                          "--separator=%s" % sep,
222                          "-olv_name,lv_size,lv_attr", vg_name])
223   if result.failed:
224     logger.Error("Failed to list logical volumes, lvs output: %s" %
225                  result.output)
226     return lvs
227
228   for line in result.stdout.splitlines():
229     line = line.strip().rstrip(sep)
230     name, size, attr = line.split(sep)
231     if len(attr) != 6:
232       attr = '------'
233     inactive = attr[4] == '-'
234     online = attr[5] == 'o'
235     lvs[name] = (size, inactive, online)
236
237   return lvs
238
239
240 def ListVolumeGroups():
241   """List the volume groups and their size.
242
243   Returns:
244     Dictionary with keys volume name and values the size of the volume
245
246   """
247   return utils.ListVolumeGroups()
248
249
250 def NodeVolumes():
251   """List all volumes on this node.
252
253   """
254   result = utils.RunCmd(["lvs", "--noheadings", "--units=m", "--nosuffix",
255                          "--separator=|",
256                          "--options=lv_name,lv_size,devices,vg_name"])
257   if result.failed:
258     logger.Error("Failed to list logical volumes, lvs output: %s" %
259                  result.output)
260     return {}
261
262   def parse_dev(dev):
263     if '(' in dev:
264       return dev.split('(')[0]
265     else:
266       return dev
267
268   def map_line(line):
269     return {
270       'name': line[0].strip(),
271       'size': line[1].strip(),
272       'dev': parse_dev(line[2].strip()),
273       'vg': line[3].strip(),
274     }
275
276   return [map_line(line.split('|')) for line in result.stdout.splitlines()]
277
278
279 def BridgesExist(bridges_list):
280   """Check if a list of bridges exist on the current node.
281
282   Returns:
283     True if all of them exist, false otherwise
284
285   """
286   for bridge in bridges_list:
287     if not utils.BridgeExists(bridge):
288       return False
289
290   return True
291
292
293 def GetInstanceList():
294   """Provides a list of instances.
295
296   Returns:
297     A list of all running instances on the current node
298     - instance1.example.com
299     - instance2.example.com
300
301   """
302   try:
303     names = hypervisor.GetHypervisor().ListInstances()
304   except errors.HypervisorError, err:
305     logger.Error("error enumerating instances: %s" % str(err))
306     raise
307
308   return names
309
310
311 def GetInstanceInfo(instance):
312   """Gives back the informations about an instance as a dictionary.
313
314   Args:
315     instance: name of the instance (ex. instance1.example.com)
316
317   Returns:
318     { 'memory' : 511, 'state' : '-b---', 'time' : 3188.8, }
319     where
320     memory: memory size of instance (int)
321     state: xen state of instance (string)
322     time: cpu time of instance (float)
323
324   """
325   output = {}
326
327   iinfo = hypervisor.GetHypervisor().GetInstanceInfo(instance)
328   if iinfo is not None:
329     output['memory'] = iinfo[2]
330     output['state'] = iinfo[4]
331     output['time'] = iinfo[5]
332
333   return output
334
335
336 def GetAllInstancesInfo():
337   """Gather data about all instances.
338
339   This is the equivalent of `GetInstanceInfo()`, except that it
340   computes data for all instances at once, thus being faster if one
341   needs data about more than one instance.
342
343   Returns: a dictionary of dictionaries, keys being the instance name,
344     and with values:
345     { 'memory' : 511, 'state' : '-b---', 'time' : 3188.8, }
346     where
347     memory: memory size of instance (int)
348     state: xen state of instance (string)
349     time: cpu time of instance (float)
350     vcpus: the number of cpus
351
352   """
353   output = {}
354
355   iinfo = hypervisor.GetHypervisor().GetAllInstancesInfo()
356   if iinfo:
357     for name, inst_id, memory, vcpus, state, times in iinfo:
358       output[name] = {
359         'memory': memory,
360         'vcpus': vcpus,
361         'state': state,
362         'time': times,
363         }
364
365   return output
366
367
368 def AddOSToInstance(instance, os_disk, swap_disk):
369   """Add an OS to an instance.
370
371   Args:
372     instance: the instance object
373     os_disk: the instance-visible name of the os device
374     swap_disk: the instance-visible name of the swap device
375
376   """
377   inst_os = OSFromDisk(instance.os)
378
379   create_script = inst_os.create_script
380
381   os_device = instance.FindDisk(os_disk)
382   if os_device is None:
383     logger.Error("Can't find this device-visible name '%s'" % os_disk)
384     return False
385
386   swap_device = instance.FindDisk(swap_disk)
387   if swap_device is None:
388     logger.Error("Can't find this device-visible name '%s'" % swap_disk)
389     return False
390
391   real_os_dev = _RecursiveFindBD(os_device)
392   if real_os_dev is None:
393     raise errors.BlockDeviceError("Block device '%s' is not set up" %
394                                   str(os_device))
395   real_os_dev.Open()
396
397   real_swap_dev = _RecursiveFindBD(swap_device)
398   if real_swap_dev is None:
399     raise errors.BlockDeviceError("Block device '%s' is not set up" %
400                                   str(swap_device))
401   real_swap_dev.Open()
402
403   logfile = "%s/add-%s-%s-%d.log" % (constants.LOG_OS_DIR, instance.os,
404                                      instance.name, int(time.time()))
405   if not os.path.exists(constants.LOG_OS_DIR):
406     os.mkdir(constants.LOG_OS_DIR, 0750)
407
408   command = utils.BuildShellCmd("cd %s && %s -i %s -b %s -s %s &>%s",
409                                 inst_os.path, create_script, instance.name,
410                                 real_os_dev.dev_path, real_swap_dev.dev_path,
411                                 logfile)
412
413   result = utils.RunCmd(command)
414   if result.failed:
415     logger.Error("os create command '%s' returned error: %s, logfile: %s,"
416                  " output: %s" %
417                  (command, result.fail_reason, logfile, result.output))
418     return False
419
420   return True
421
422
423 def RunRenameInstance(instance, old_name, os_disk, swap_disk):
424   """Run the OS rename script for an instance.
425
426   Args:
427     instance: the instance object
428     old_name: the old name of the instance
429     os_disk: the instance-visible name of the os device
430     swap_disk: the instance-visible name of the swap device
431
432   """
433   inst_os = OSFromDisk(instance.os)
434
435   script = inst_os.rename_script
436
437   os_device = instance.FindDisk(os_disk)
438   if os_device is None:
439     logger.Error("Can't find this device-visible name '%s'" % os_disk)
440     return False
441
442   swap_device = instance.FindDisk(swap_disk)
443   if swap_device is None:
444     logger.Error("Can't find this device-visible name '%s'" % swap_disk)
445     return False
446
447   real_os_dev = _RecursiveFindBD(os_device)
448   if real_os_dev is None:
449     raise errors.BlockDeviceError("Block device '%s' is not set up" %
450                                   str(os_device))
451   real_os_dev.Open()
452
453   real_swap_dev = _RecursiveFindBD(swap_device)
454   if real_swap_dev is None:
455     raise errors.BlockDeviceError("Block device '%s' is not set up" %
456                                   str(swap_device))
457   real_swap_dev.Open()
458
459   logfile = "%s/rename-%s-%s-%s-%d.log" % (constants.LOG_OS_DIR, instance.os,
460                                            old_name,
461                                            instance.name, int(time.time()))
462   if not os.path.exists(constants.LOG_OS_DIR):
463     os.mkdir(constants.LOG_OS_DIR, 0750)
464
465   command = utils.BuildShellCmd("cd %s && %s -o %s -n %s -b %s -s %s &>%s",
466                                 inst_os.path, script, old_name, instance.name,
467                                 real_os_dev.dev_path, real_swap_dev.dev_path,
468                                 logfile)
469
470   result = utils.RunCmd(command)
471
472   if result.failed:
473     logger.Error("os create command '%s' returned error: %s"
474                  " output: %s" %
475                  (command, result.fail_reason, result.output))
476     return False
477
478   return True
479
480
481 def _GetVGInfo(vg_name):
482   """Get informations about the volume group.
483
484   Args:
485     vg_name: the volume group
486
487   Returns:
488     { 'vg_size' : xxx, 'vg_free' : xxx, 'pv_count' : xxx }
489     where
490     vg_size is the total size of the volume group in MiB
491     vg_free is the free size of the volume group in MiB
492     pv_count are the number of physical disks in that vg
493
494   """
495   retval = utils.RunCmd(["vgs", "-ovg_size,vg_free,pv_count", "--noheadings",
496                          "--nosuffix", "--units=m", "--separator=:", vg_name])
497
498   if retval.failed:
499     errmsg = "volume group %s not present" % vg_name
500     logger.Error(errmsg)
501     raise errors.LVMError(errmsg)
502   valarr = retval.stdout.strip().split(':')
503   retdic = {
504     "vg_size": int(round(float(valarr[0]), 0)),
505     "vg_free": int(round(float(valarr[1]), 0)),
506     "pv_count": int(valarr[2]),
507     }
508   return retdic
509
510
511 def _GatherBlockDevs(instance):
512   """Set up an instance's block device(s).
513
514   This is run on the primary node at instance startup. The block
515   devices must be already assembled.
516
517   """
518   block_devices = []
519   for disk in instance.disks:
520     device = _RecursiveFindBD(disk)
521     if device is None:
522       raise errors.BlockDeviceError("Block device '%s' is not set up." %
523                                     str(disk))
524     device.Open()
525     block_devices.append((disk, device))
526   return block_devices
527
528
529 def StartInstance(instance, extra_args):
530   """Start an instance.
531
532   Args:
533     instance - name of instance to start.
534
535   """
536   running_instances = GetInstanceList()
537
538   if instance.name in running_instances:
539     return True
540
541   block_devices = _GatherBlockDevs(instance)
542   hyper = hypervisor.GetHypervisor()
543
544   try:
545     hyper.StartInstance(instance, block_devices, extra_args)
546   except errors.HypervisorError, err:
547     logger.Error("Failed to start instance: %s" % err)
548     return False
549
550   return True
551
552
553 def ShutdownInstance(instance):
554   """Shut an instance down.
555
556   Args:
557     instance - name of instance to shutdown.
558
559   """
560   running_instances = GetInstanceList()
561
562   if instance.name not in running_instances:
563     return True
564
565   hyper = hypervisor.GetHypervisor()
566   try:
567     hyper.StopInstance(instance)
568   except errors.HypervisorError, err:
569     logger.Error("Failed to stop instance: %s" % err)
570     return False
571
572   # test every 10secs for 2min
573   shutdown_ok = False
574
575   time.sleep(1)
576   for dummy in range(11):
577     if instance.name not in GetInstanceList():
578       break
579     time.sleep(10)
580   else:
581     # the shutdown did not succeed
582     logger.Error("shutdown of '%s' unsuccessful, using destroy" % instance)
583
584     try:
585       hyper.StopInstance(instance, force=True)
586     except errors.HypervisorError, err:
587       logger.Error("Failed to stop instance: %s" % err)
588       return False
589
590     time.sleep(1)
591     if instance.name in GetInstanceList():
592       logger.Error("could not shutdown instance '%s' even by destroy")
593       return False
594
595   return True
596
597
598 def RebootInstance(instance, reboot_type, extra_args):
599   """Reboot an instance.
600
601   Args:
602     instance    - name of instance to reboot
603     reboot_type - how to reboot [soft,hard,full]
604
605   """
606   running_instances = GetInstanceList()
607
608   if instance.name not in running_instances:
609     logger.Error("Cannot reboot instance that is not running")
610     return False
611
612   hyper = hypervisor.GetHypervisor()
613   if reboot_type == constants.INSTANCE_REBOOT_SOFT:
614     try:
615       hyper.RebootInstance(instance)
616     except errors.HypervisorError, err:
617       logger.Error("Failed to soft reboot instance: %s" % err)
618       return False
619   elif reboot_type == constants.INSTANCE_REBOOT_HARD:
620     try:
621       ShutdownInstance(instance)
622       StartInstance(instance, extra_args)
623     except errors.HypervisorError, err:
624       logger.Error("Failed to hard reboot instance: %s" % err)
625       return False
626   else:
627     raise errors.ParameterError("reboot_type invalid")
628
629
630   return True
631
632
633 def CreateBlockDevice(disk, size, owner, on_primary, info):
634   """Creates a block device for an instance.
635
636   Args:
637    bdev: a ganeti.objects.Disk object
638    size: the size of the physical underlying devices
639    do_open: if the device should be `Assemble()`-d and
640             `Open()`-ed after creation
641
642   Returns:
643     the new unique_id of the device (this can sometime be
644     computed only after creation), or None. On secondary nodes,
645     it's not required to return anything.
646
647   """
648   clist = []
649   if disk.children:
650     for child in disk.children:
651       crdev = _RecursiveAssembleBD(child, owner, on_primary)
652       if on_primary or disk.AssembleOnSecondary():
653         # we need the children open in case the device itself has to
654         # be assembled
655         crdev.Open()
656       else:
657         crdev.Close()
658       clist.append(crdev)
659   try:
660     device = bdev.FindDevice(disk.dev_type, disk.physical_id, clist)
661     if device is not None:
662       logger.Info("removing existing device %s" % disk)
663       device.Remove()
664   except errors.BlockDeviceError, err:
665     pass
666
667   device = bdev.Create(disk.dev_type, disk.physical_id,
668                        clist, size)
669   if device is None:
670     raise ValueError("Can't create child device for %s, %s" %
671                      (disk, size))
672   if on_primary or disk.AssembleOnSecondary():
673     if not device.Assemble():
674       errorstring = "Can't assemble device after creation"
675       logger.Error(errorstring)
676       raise errors.BlockDeviceError("%s, very unusual event - check the node"
677                                     " daemon logs" % errorstring)
678     device.SetSyncSpeed(constants.SYNC_SPEED)
679     if on_primary or disk.OpenOnSecondary():
680       device.Open(force=True)
681     DevCacheManager.UpdateCache(device.dev_path, owner,
682                                 on_primary, disk.iv_name)
683
684   device.SetInfo(info)
685
686   physical_id = device.unique_id
687   return physical_id
688
689
690 def RemoveBlockDevice(disk):
691   """Remove a block device.
692
693   This is intended to be called recursively.
694
695   """
696   try:
697     # since we are removing the device, allow a partial match
698     # this allows removal of broken mirrors
699     rdev = _RecursiveFindBD(disk, allow_partial=True)
700   except errors.BlockDeviceError, err:
701     # probably can't attach
702     logger.Info("Can't attach to device %s in remove" % disk)
703     rdev = None
704   if rdev is not None:
705     r_path = rdev.dev_path
706     result = rdev.Remove()
707     if result:
708       DevCacheManager.RemoveCache(r_path)
709   else:
710     result = True
711   if disk.children:
712     for child in disk.children:
713       result = result and RemoveBlockDevice(child)
714   return result
715
716
717 def _RecursiveAssembleBD(disk, owner, as_primary):
718   """Activate a block device for an instance.
719
720   This is run on the primary and secondary nodes for an instance.
721
722   This function is called recursively.
723
724   Args:
725     disk: a objects.Disk object
726     as_primary: if we should make the block device read/write
727
728   Returns:
729     the assembled device or None (in case no device was assembled)
730
731   If the assembly is not successful, an exception is raised.
732
733   """
734   children = []
735   if disk.children:
736     mcn = disk.ChildrenNeeded()
737     if mcn == -1:
738       mcn = 0 # max number of Nones allowed
739     else:
740       mcn = len(disk.children) - mcn # max number of Nones
741     for chld_disk in disk.children:
742       try:
743         cdev = _RecursiveAssembleBD(chld_disk, owner, as_primary)
744       except errors.BlockDeviceError, err:
745         if children.count(None) >= mcn:
746           raise
747         cdev = None
748         logger.Debug("Error in child activation: %s" % str(err))
749       children.append(cdev)
750
751   if as_primary or disk.AssembleOnSecondary():
752     r_dev = bdev.AttachOrAssemble(disk.dev_type, disk.physical_id, children)
753     r_dev.SetSyncSpeed(constants.SYNC_SPEED)
754     result = r_dev
755     if as_primary or disk.OpenOnSecondary():
756       r_dev.Open()
757     else:
758       r_dev.Close()
759     DevCacheManager.UpdateCache(r_dev.dev_path, owner,
760                                 as_primary, disk.iv_name)
761
762   else:
763     result = True
764   return result
765
766
767 def AssembleBlockDevice(disk, owner, as_primary):
768   """Activate a block device for an instance.
769
770   This is a wrapper over _RecursiveAssembleBD.
771
772   Returns:
773     a /dev path for primary nodes
774     True for secondary nodes
775
776   """
777   result = _RecursiveAssembleBD(disk, owner, as_primary)
778   if isinstance(result, bdev.BlockDev):
779     result = result.dev_path
780   return result
781
782
783 def ShutdownBlockDevice(disk):
784   """Shut down a block device.
785
786   First, if the device is assembled (can `Attach()`), then the device
787   is shutdown. Then the children of the device are shutdown.
788
789   This function is called recursively. Note that we don't cache the
790   children or such, as oppossed to assemble, shutdown of different
791   devices doesn't require that the upper device was active.
792
793   """
794   r_dev = _RecursiveFindBD(disk)
795   if r_dev is not None:
796     r_path = r_dev.dev_path
797     result = r_dev.Shutdown()
798     if result:
799       DevCacheManager.RemoveCache(r_path)
800   else:
801     result = True
802   if disk.children:
803     for child in disk.children:
804       result = result and ShutdownBlockDevice(child)
805   return result
806
807
808 def MirrorAddChildren(parent_cdev, new_cdevs):
809   """Extend a mirrored block device.
810
811   """
812   parent_bdev = _RecursiveFindBD(parent_cdev, allow_partial=True)
813   if parent_bdev is None:
814     logger.Error("Can't find parent device")
815     return False
816   new_bdevs = [_RecursiveFindBD(disk) for disk in new_cdevs]
817   if new_bdevs.count(None) > 0:
818     logger.Error("Can't find new device(s) to add: %s:%s" %
819                  (new_bdevs, new_cdevs))
820     return False
821   parent_bdev.AddChildren(new_bdevs)
822   return True
823
824
825 def MirrorRemoveChildren(parent_cdev, new_cdevs):
826   """Shrink a mirrored block device.
827
828   """
829   parent_bdev = _RecursiveFindBD(parent_cdev)
830   if parent_bdev is None:
831     logger.Error("Can't find parent in remove children: %s" % parent_cdev)
832     return False
833   devs = []
834   for disk in new_cdevs:
835     rpath = disk.StaticDevPath()
836     if rpath is None:
837       bd = _RecursiveFindBD(disk)
838       if bd is None:
839         logger.Error("Can't find dynamic device %s while removing children" %
840                      disk)
841         return False
842       else:
843         devs.append(bd.dev_path)
844     else:
845       devs.append(rpath)
846   parent_bdev.RemoveChildren(devs)
847   return True
848
849
850 def GetMirrorStatus(disks):
851   """Get the mirroring status of a list of devices.
852
853   Args:
854     disks: list of `objects.Disk`
855
856   Returns:
857     list of (mirror_done, estimated_time) tuples, which
858     are the result of bdev.BlockDevice.CombinedSyncStatus()
859
860   """
861   stats = []
862   for dsk in disks:
863     rbd = _RecursiveFindBD(dsk)
864     if rbd is None:
865       raise errors.BlockDeviceError("Can't find device %s" % str(dsk))
866     stats.append(rbd.CombinedSyncStatus())
867   return stats
868
869
870 def _RecursiveFindBD(disk, allow_partial=False):
871   """Check if a device is activated.
872
873   If so, return informations about the real device.
874
875   Args:
876     disk: the objects.Disk instance
877     allow_partial: don't abort the find if a child of the
878                    device can't be found; this is intended to be
879                    used when repairing mirrors
880
881   Returns:
882     None if the device can't be found
883     otherwise the device instance
884
885   """
886   children = []
887   if disk.children:
888     for chdisk in disk.children:
889       children.append(_RecursiveFindBD(chdisk))
890
891   return bdev.FindDevice(disk.dev_type, disk.physical_id, children)
892
893
894 def FindBlockDevice(disk):
895   """Check if a device is activated.
896
897   If so, return informations about the real device.
898
899   Args:
900     disk: the objects.Disk instance
901   Returns:
902     None if the device can't be found
903     (device_path, major, minor, sync_percent, estimated_time, is_degraded)
904
905   """
906   rbd = _RecursiveFindBD(disk)
907   if rbd is None:
908     return rbd
909   return (rbd.dev_path, rbd.major, rbd.minor) + rbd.GetSyncStatus()
910
911
912 def UploadFile(file_name, data, mode, uid, gid, atime, mtime):
913   """Write a file to the filesystem.
914
915   This allows the master to overwrite(!) a file. It will only perform
916   the operation if the file belongs to a list of configuration files.
917
918   """
919   if not os.path.isabs(file_name):
920     logger.Error("Filename passed to UploadFile is not absolute: '%s'" %
921                  file_name)
922     return False
923
924   allowed_files = [constants.CLUSTER_CONF_FILE, "/etc/hosts",
925                    constants.SSH_KNOWN_HOSTS_FILE]
926   allowed_files.extend(ssconf.SimpleStore().GetFileList())
927   if file_name not in allowed_files:
928     logger.Error("Filename passed to UploadFile not in allowed"
929                  " upload targets: '%s'" % file_name)
930     return False
931
932   dir_name, small_name = os.path.split(file_name)
933   fd, new_name = tempfile.mkstemp('.new', small_name, dir_name)
934   # here we need to make sure we remove the temp file, if any error
935   # leaves it in place
936   try:
937     os.chown(new_name, uid, gid)
938     os.chmod(new_name, mode)
939     os.write(fd, data)
940     os.fsync(fd)
941     os.utime(new_name, (atime, mtime))
942     os.rename(new_name, file_name)
943   finally:
944     os.close(fd)
945     utils.RemoveFile(new_name)
946   return True
947
948
949 def _ErrnoOrStr(err):
950   """Format an EnvironmentError exception.
951
952   If the `err` argument has an errno attribute, it will be looked up
953   and converted into a textual EXXXX description. Otherwise the string
954   representation of the error will be returned.
955
956   """
957   if hasattr(err, 'errno'):
958     detail = errno.errorcode[err.errno]
959   else:
960     detail = str(err)
961   return detail
962
963
964 def _OSSearch(name, search_path=None):
965   """Search for OSes with the given name in the search_path.
966
967   Args:
968     name: The name of the OS to look for
969     search_path: List of dirs to search (defaults to constants.OS_SEARCH_PATH)
970
971   Returns:
972     The base_dir the OS resides in
973
974   """
975   if search_path is None:
976     search_path = constants.OS_SEARCH_PATH
977
978   for dir_name in search_path:
979     t_os_dir = os.path.sep.join([dir_name, name])
980     if os.path.isdir(t_os_dir):
981       return dir_name
982
983   return None
984
985
986 def _OSOndiskVersion(name, os_dir):
987   """Compute and return the API version of a given OS.
988
989   This function will try to read the API version of the os given by
990   the 'name' parameter and residing in the 'os_dir' directory.
991
992   Return value will be either an integer denoting the version or None in the
993   case when this is not a valid OS name.
994
995   """
996   api_file = os.path.sep.join([os_dir, "ganeti_api_version"])
997
998   try:
999     st = os.stat(api_file)
1000   except EnvironmentError, err:
1001     raise errors.InvalidOS(name, os_dir, "'ganeti_api_version' file not"
1002                            " found (%s)" % _ErrnoOrStr(err))
1003
1004   if not stat.S_ISREG(stat.S_IFMT(st.st_mode)):
1005     raise errors.InvalidOS(name, os_dir, "'ganeti_api_version' file is not"
1006                            " a regular file")
1007
1008   try:
1009     f = open(api_file)
1010     try:
1011       api_version = f.read(256)
1012     finally:
1013       f.close()
1014   except EnvironmentError, err:
1015     raise errors.InvalidOS(name, os_dir, "error while reading the"
1016                            " API version (%s)" % _ErrnoOrStr(err))
1017
1018   api_version = api_version.strip()
1019   try:
1020     api_version = int(api_version)
1021   except (TypeError, ValueError), err:
1022     raise errors.InvalidOS(name, os_dir,
1023                            "API version is not integer (%s)" % str(err))
1024
1025   return api_version
1026
1027
1028 def DiagnoseOS(top_dirs=None):
1029   """Compute the validity for all OSes.
1030
1031   Returns an OS object for each name in all the given top directories
1032   (if not given defaults to constants.OS_SEARCH_PATH)
1033
1034   Returns:
1035     list of OS objects
1036
1037   """
1038   if top_dirs is None:
1039     top_dirs = constants.OS_SEARCH_PATH
1040
1041   result = []
1042   for dir_name in top_dirs:
1043     if os.path.isdir(dir_name):
1044       try:
1045         f_names = utils.ListVisibleFiles(dir_name)
1046       except EnvironmentError, err:
1047         logger.Error("Can't list the OS directory %s: %s" %
1048                      (dir_name, str(err)))
1049         break
1050       for name in f_names:
1051         try:
1052           os_inst = OSFromDisk(name, base_dir=dir_name)
1053           result.append(os_inst)
1054         except errors.InvalidOS, err:
1055           result.append(objects.OS.FromInvalidOS(err))
1056
1057   return result
1058
1059
1060 def OSFromDisk(name, base_dir=None):
1061   """Create an OS instance from disk.
1062
1063   This function will return an OS instance if the given name is a
1064   valid OS name. Otherwise, it will raise an appropriate
1065   `errors.InvalidOS` exception, detailing why this is not a valid
1066   OS.
1067
1068   Args:
1069     os_dir: Directory containing the OS scripts. Defaults to a search
1070             in all the OS_SEARCH_PATH directories.
1071
1072   """
1073
1074   if base_dir is None:
1075     base_dir = _OSSearch(name)
1076
1077   if base_dir is None:
1078     raise errors.InvalidOS(name, None, "OS dir not found in search path")
1079
1080   os_dir = os.path.sep.join([base_dir, name])
1081   api_version = _OSOndiskVersion(name, os_dir)
1082
1083   if api_version != constants.OS_API_VERSION:
1084     raise errors.InvalidOS(name, os_dir, "API version mismatch"
1085                            " (found %s want %s)"
1086                            % (api_version, constants.OS_API_VERSION))
1087
1088   # OS Scripts dictionary, we will populate it with the actual script names
1089   os_scripts = {'create': '', 'export': '', 'import': '', 'rename': ''}
1090
1091   for script in os_scripts:
1092     os_scripts[script] = os.path.sep.join([os_dir, script])
1093
1094     try:
1095       st = os.stat(os_scripts[script])
1096     except EnvironmentError, err:
1097       raise errors.InvalidOS(name, os_dir, "'%s' script missing (%s)" %
1098                              (script, _ErrnoOrStr(err)))
1099
1100     if stat.S_IMODE(st.st_mode) & stat.S_IXUSR != stat.S_IXUSR:
1101       raise errors.InvalidOS(name, os_dir, "'%s' script not executable" %
1102                              script)
1103
1104     if not stat.S_ISREG(stat.S_IFMT(st.st_mode)):
1105       raise errors.InvalidOS(name, os_dir, "'%s' is not a regular file" %
1106                              script)
1107
1108
1109   return objects.OS(name=name, path=os_dir, status=constants.OS_VALID_STATUS,
1110                     create_script=os_scripts['create'],
1111                     export_script=os_scripts['export'],
1112                     import_script=os_scripts['import'],
1113                     rename_script=os_scripts['rename'],
1114                     api_version=api_version)
1115
1116
1117 def SnapshotBlockDevice(disk):
1118   """Create a snapshot copy of a block device.
1119
1120   This function is called recursively, and the snapshot is actually created
1121   just for the leaf lvm backend device.
1122
1123   Args:
1124     disk: the disk to be snapshotted
1125
1126   Returns:
1127     a config entry for the actual lvm device snapshotted.
1128
1129   """
1130   if disk.children:
1131     if len(disk.children) == 1:
1132       # only one child, let's recurse on it
1133       return SnapshotBlockDevice(disk.children[0])
1134     else:
1135       # more than one child, choose one that matches
1136       for child in disk.children:
1137         if child.size == disk.size:
1138           # return implies breaking the loop
1139           return SnapshotBlockDevice(child)
1140   elif disk.dev_type == constants.LD_LV:
1141     r_dev = _RecursiveFindBD(disk)
1142     if r_dev is not None:
1143       # let's stay on the safe side and ask for the full size, for now
1144       return r_dev.Snapshot(disk.size)
1145     else:
1146       return None
1147   else:
1148     raise errors.ProgrammerError("Cannot snapshot non-lvm block device"
1149                                  " '%s' of type '%s'" %
1150                                  (disk.unique_id, disk.dev_type))
1151
1152
1153 def ExportSnapshot(disk, dest_node, instance):
1154   """Export a block device snapshot to a remote node.
1155
1156   Args:
1157     disk: the snapshot block device
1158     dest_node: the node to send the image to
1159     instance: instance being exported
1160
1161   Returns:
1162     True if successful, False otherwise.
1163
1164   """
1165   inst_os = OSFromDisk(instance.os)
1166   export_script = inst_os.export_script
1167
1168   logfile = "%s/exp-%s-%s-%s.log" % (constants.LOG_OS_DIR, inst_os.name,
1169                                      instance.name, int(time.time()))
1170   if not os.path.exists(constants.LOG_OS_DIR):
1171     os.mkdir(constants.LOG_OS_DIR, 0750)
1172
1173   real_os_dev = _RecursiveFindBD(disk)
1174   if real_os_dev is None:
1175     raise errors.BlockDeviceError("Block device '%s' is not set up" %
1176                                   str(disk))
1177   real_os_dev.Open()
1178
1179   destdir = os.path.join(constants.EXPORT_DIR, instance.name + ".new")
1180   destfile = disk.physical_id[1]
1181
1182   # the target command is built out of three individual commands,
1183   # which are joined by pipes; we check each individual command for
1184   # valid parameters
1185
1186   expcmd = utils.BuildShellCmd("cd %s; %s -i %s -b %s 2>%s", inst_os.path,
1187                                export_script, instance.name,
1188                                real_os_dev.dev_path, logfile)
1189
1190   comprcmd = "gzip"
1191
1192   destcmd = utils.BuildShellCmd("mkdir -p %s && cat > %s/%s",
1193                                 destdir, destdir, destfile)
1194   remotecmd = ssh.BuildSSHCmd(dest_node, constants.GANETI_RUNAS, destcmd)
1195
1196
1197
1198   # all commands have been checked, so we're safe to combine them
1199   command = '|'.join([expcmd, comprcmd, utils.ShellQuoteArgs(remotecmd)])
1200
1201   result = utils.RunCmd(command)
1202
1203   if result.failed:
1204     logger.Error("os snapshot export command '%s' returned error: %s"
1205                  " output: %s" %
1206                  (command, result.fail_reason, result.output))
1207     return False
1208
1209   return True
1210
1211
1212 def FinalizeExport(instance, snap_disks):
1213   """Write out the export configuration information.
1214
1215   Args:
1216     instance: instance configuration
1217     snap_disks: snapshot block devices
1218
1219   Returns:
1220     False in case of error, True otherwise.
1221
1222   """
1223   destdir = os.path.join(constants.EXPORT_DIR, instance.name + ".new")
1224   finaldestdir = os.path.join(constants.EXPORT_DIR, instance.name)
1225
1226   config = objects.SerializableConfigParser()
1227
1228   config.add_section(constants.INISECT_EXP)
1229   config.set(constants.INISECT_EXP, 'version', '0')
1230   config.set(constants.INISECT_EXP, 'timestamp', '%d' % int(time.time()))
1231   config.set(constants.INISECT_EXP, 'source', instance.primary_node)
1232   config.set(constants.INISECT_EXP, 'os', instance.os)
1233   config.set(constants.INISECT_EXP, 'compression', 'gzip')
1234
1235   config.add_section(constants.INISECT_INS)
1236   config.set(constants.INISECT_INS, 'name', instance.name)
1237   config.set(constants.INISECT_INS, 'memory', '%d' % instance.memory)
1238   config.set(constants.INISECT_INS, 'vcpus', '%d' % instance.vcpus)
1239   config.set(constants.INISECT_INS, 'disk_template', instance.disk_template)
1240   for nic_count, nic in enumerate(instance.nics):
1241     config.set(constants.INISECT_INS, 'nic%d_mac' %
1242                nic_count, '%s' % nic.mac)
1243     config.set(constants.INISECT_INS, 'nic%d_ip' % nic_count, '%s' % nic.ip)
1244   # TODO: redundant: on load can read nics until it doesn't exist
1245   config.set(constants.INISECT_INS, 'nic_count' , '%d' % nic_count)
1246
1247   for disk_count, disk in enumerate(snap_disks):
1248     config.set(constants.INISECT_INS, 'disk%d_ivname' % disk_count,
1249                ('%s' % disk.iv_name))
1250     config.set(constants.INISECT_INS, 'disk%d_dump' % disk_count,
1251                ('%s' % disk.physical_id[1]))
1252     config.set(constants.INISECT_INS, 'disk%d_size' % disk_count,
1253                ('%d' % disk.size))
1254   config.set(constants.INISECT_INS, 'disk_count' , '%d' % disk_count)
1255
1256   cff = os.path.join(destdir, constants.EXPORT_CONF_FILE)
1257   cfo = open(cff, 'w')
1258   try:
1259     config.write(cfo)
1260   finally:
1261     cfo.close()
1262
1263   shutil.rmtree(finaldestdir, True)
1264   shutil.move(destdir, finaldestdir)
1265
1266   return True
1267
1268
1269 def ExportInfo(dest):
1270   """Get export configuration information.
1271
1272   Args:
1273     dest: directory containing the export
1274
1275   Returns:
1276     A serializable config file containing the export info.
1277
1278   """
1279   cff = os.path.join(dest, constants.EXPORT_CONF_FILE)
1280
1281   config = objects.SerializableConfigParser()
1282   config.read(cff)
1283
1284   if (not config.has_section(constants.INISECT_EXP) or
1285       not config.has_section(constants.INISECT_INS)):
1286     return None
1287
1288   return config
1289
1290
1291 def ImportOSIntoInstance(instance, os_disk, swap_disk, src_node, src_image):
1292   """Import an os image into an instance.
1293
1294   Args:
1295     instance: the instance object
1296     os_disk: the instance-visible name of the os device
1297     swap_disk: the instance-visible name of the swap device
1298     src_node: node holding the source image
1299     src_image: path to the source image on src_node
1300
1301   Returns:
1302     False in case of error, True otherwise.
1303
1304   """
1305   inst_os = OSFromDisk(instance.os)
1306   import_script = inst_os.import_script
1307
1308   os_device = instance.FindDisk(os_disk)
1309   if os_device is None:
1310     logger.Error("Can't find this device-visible name '%s'" % os_disk)
1311     return False
1312
1313   swap_device = instance.FindDisk(swap_disk)
1314   if swap_device is None:
1315     logger.Error("Can't find this device-visible name '%s'" % swap_disk)
1316     return False
1317
1318   real_os_dev = _RecursiveFindBD(os_device)
1319   if real_os_dev is None:
1320     raise errors.BlockDeviceError("Block device '%s' is not set up" %
1321                                   str(os_device))
1322   real_os_dev.Open()
1323
1324   real_swap_dev = _RecursiveFindBD(swap_device)
1325   if real_swap_dev is None:
1326     raise errors.BlockDeviceError("Block device '%s' is not set up" %
1327                                   str(swap_device))
1328   real_swap_dev.Open()
1329
1330   logfile = "%s/import-%s-%s-%s.log" % (constants.LOG_OS_DIR, instance.os,
1331                                         instance.name, int(time.time()))
1332   if not os.path.exists(constants.LOG_OS_DIR):
1333     os.mkdir(constants.LOG_OS_DIR, 0750)
1334
1335   destcmd = utils.BuildShellCmd('cat %s', src_image)
1336   remotecmd = ssh.BuildSSHCmd(src_node, constants.GANETI_RUNAS, destcmd)
1337
1338   comprcmd = "gunzip"
1339   impcmd = utils.BuildShellCmd("(cd %s; %s -i %s -b %s -s %s &>%s)",
1340                                inst_os.path, import_script, instance.name,
1341                                real_os_dev.dev_path, real_swap_dev.dev_path,
1342                                logfile)
1343
1344   command = '|'.join([utils.ShellQuoteArgs(remotecmd), comprcmd, impcmd])
1345
1346   result = utils.RunCmd(command)
1347
1348   if result.failed:
1349     logger.Error("os import command '%s' returned error: %s"
1350                  " output: %s" %
1351                  (command, result.fail_reason, result.output))
1352     return False
1353
1354   return True
1355
1356
1357 def ListExports():
1358   """Return a list of exports currently available on this machine.
1359
1360   """
1361   if os.path.isdir(constants.EXPORT_DIR):
1362     return utils.ListVisibleFiles(constants.EXPORT_DIR)
1363   else:
1364     return []
1365
1366
1367 def RemoveExport(export):
1368   """Remove an existing export from the node.
1369
1370   Args:
1371     export: the name of the export to remove
1372
1373   Returns:
1374     False in case of error, True otherwise.
1375
1376   """
1377   target = os.path.join(constants.EXPORT_DIR, export)
1378
1379   shutil.rmtree(target)
1380   # TODO: catch some of the relevant exceptions and provide a pretty
1381   # error message if rmtree fails.
1382
1383   return True
1384
1385
1386 def RenameBlockDevices(devlist):
1387   """Rename a list of block devices.
1388
1389   The devlist argument is a list of tuples (disk, new_logical,
1390   new_physical). The return value will be a combined boolean result
1391   (True only if all renames succeeded).
1392
1393   """
1394   result = True
1395   for disk, unique_id in devlist:
1396     dev = _RecursiveFindBD(disk)
1397     if dev is None:
1398       result = False
1399       continue
1400     try:
1401       old_rpath = dev.dev_path
1402       dev.Rename(unique_id)
1403       new_rpath = dev.dev_path
1404       if old_rpath != new_rpath:
1405         DevCacheManager.RemoveCache(old_rpath)
1406         # FIXME: we should add the new cache information here, like:
1407         # DevCacheManager.UpdateCache(new_rpath, owner, ...)
1408         # but we don't have the owner here - maybe parse from existing
1409         # cache? for now, we only lose lvm data when we rename, which
1410         # is less critical than DRBD or MD
1411     except errors.BlockDeviceError, err:
1412       logger.Error("Can't rename device '%s' to '%s': %s" %
1413                    (dev, unique_id, err))
1414       result = False
1415   return result
1416
1417
1418 class HooksRunner(object):
1419   """Hook runner.
1420
1421   This class is instantiated on the node side (ganeti-noded) and not on
1422   the master side.
1423
1424   """
1425   RE_MASK = re.compile("^[a-zA-Z0-9_-]+$")
1426
1427   def __init__(self, hooks_base_dir=None):
1428     """Constructor for hooks runner.
1429
1430     Args:
1431       - hooks_base_dir: if not None, this overrides the
1432         constants.HOOKS_BASE_DIR (useful for unittests)
1433       - logs_base_dir: if not None, this overrides the
1434         constants.LOG_HOOKS_DIR (useful for unittests)
1435       - logging: enable or disable logging of script output
1436
1437     """
1438     if hooks_base_dir is None:
1439       hooks_base_dir = constants.HOOKS_BASE_DIR
1440     self._BASE_DIR = hooks_base_dir
1441
1442   @staticmethod
1443   def ExecHook(script, env):
1444     """Exec one hook script.
1445
1446     Args:
1447      - phase: the phase
1448      - script: the full path to the script
1449      - env: the environment with which to exec the script
1450
1451     """
1452     # exec the process using subprocess and log the output
1453     fdstdin = None
1454     try:
1455       fdstdin = open("/dev/null", "r")
1456       child = subprocess.Popen([script], stdin=fdstdin, stdout=subprocess.PIPE,
1457                                stderr=subprocess.STDOUT, close_fds=True,
1458                                shell=False, cwd="/",env=env)
1459       output = ""
1460       try:
1461         output = child.stdout.read(4096)
1462         child.stdout.close()
1463       except EnvironmentError, err:
1464         output += "Hook script error: %s" % str(err)
1465
1466       while True:
1467         try:
1468           result = child.wait()
1469           break
1470         except EnvironmentError, err:
1471           if err.errno == errno.EINTR:
1472             continue
1473           raise
1474     finally:
1475       # try not to leak fds
1476       for fd in (fdstdin, ):
1477         if fd is not None:
1478           try:
1479             fd.close()
1480           except EnvironmentError, err:
1481             # just log the error
1482             #logger.Error("While closing fd %s: %s" % (fd, err))
1483             pass
1484
1485     return result == 0, output
1486
1487   def RunHooks(self, hpath, phase, env):
1488     """Run the scripts in the hooks directory.
1489
1490     This method will not be usually overriden by child opcodes.
1491
1492     """
1493     if phase == constants.HOOKS_PHASE_PRE:
1494       suffix = "pre"
1495     elif phase == constants.HOOKS_PHASE_POST:
1496       suffix = "post"
1497     else:
1498       raise errors.ProgrammerError("Unknown hooks phase: '%s'" % phase)
1499     rr = []
1500
1501     subdir = "%s-%s.d" % (hpath, suffix)
1502     dir_name = "%s/%s" % (self._BASE_DIR, subdir)
1503     try:
1504       dir_contents = utils.ListVisibleFiles(dir_name)
1505     except OSError, err:
1506       # must log
1507       return rr
1508
1509     # we use the standard python sort order,
1510     # so 00name is the recommended naming scheme
1511     dir_contents.sort()
1512     for relname in dir_contents:
1513       fname = os.path.join(dir_name, relname)
1514       if not (os.path.isfile(fname) and os.access(fname, os.X_OK) and
1515           self.RE_MASK.match(relname) is not None):
1516         rrval = constants.HKR_SKIP
1517         output = ""
1518       else:
1519         result, output = self.ExecHook(fname, env)
1520         if not result:
1521           rrval = constants.HKR_FAIL
1522         else:
1523           rrval = constants.HKR_SUCCESS
1524       rr.append(("%s/%s" % (subdir, relname), rrval, output))
1525
1526     return rr
1527
1528
1529 class DevCacheManager(object):
1530   """Simple class for managing a chache of block device information.
1531
1532   """
1533   _DEV_PREFIX = "/dev/"
1534   _ROOT_DIR = constants.BDEV_CACHE_DIR
1535
1536   @classmethod
1537   def _ConvertPath(cls, dev_path):
1538     """Converts a /dev/name path to the cache file name.
1539
1540     This replaces slashes with underscores and strips the /dev
1541     prefix. It then returns the full path to the cache file
1542
1543     """
1544     if dev_path.startswith(cls._DEV_PREFIX):
1545       dev_path = dev_path[len(cls._DEV_PREFIX):]
1546     dev_path = dev_path.replace("/", "_")
1547     fpath = "%s/bdev_%s" % (cls._ROOT_DIR, dev_path)
1548     return fpath
1549
1550   @classmethod
1551   def UpdateCache(cls, dev_path, owner, on_primary, iv_name):
1552     """Updates the cache information for a given device.
1553
1554     """
1555     if dev_path is None:
1556       logger.Error("DevCacheManager.UpdateCache got a None dev_path")
1557       return
1558     fpath = cls._ConvertPath(dev_path)
1559     if on_primary:
1560       state = "primary"
1561     else:
1562       state = "secondary"
1563     if iv_name is None:
1564       iv_name = "not_visible"
1565     fdata = "%s %s %s\n" % (str(owner), state, iv_name)
1566     try:
1567       utils.WriteFile(fpath, data=fdata)
1568     except EnvironmentError, err:
1569       logger.Error("Can't update bdev cache for %s, error %s" %
1570                    (dev_path, str(err)))
1571
1572   @classmethod
1573   def RemoveCache(cls, dev_path):
1574     """Remove data for a dev_path.
1575
1576     """
1577     if dev_path is None:
1578       logger.Error("DevCacheManager.RemoveCache got a None dev_path")
1579       return
1580     fpath = cls._ConvertPath(dev_path)
1581     try:
1582       utils.RemoveFile(fpath)
1583     except EnvironmentError, err:
1584       logger.Error("Can't update bdev cache for %s, error %s" %
1585                    (dev_path, str(err)))