[+list]

On Mon, Jun 28, 2010 at 12:44:12PM +0100, Balazs Lecz wrote:
> On Tue, Apr 20, 2010 at 05:51 PM, Iustin Pop wrote:
> > [...]
> > +  _DENIED_CAPABILITIES = [
> > +    "mac_override",    # Allow MAC configuration or state changes
> > +    # TODO: remove sys_admin too, for safety
> > +    #"sys_admin",       # Perform  a range of system administration 
> > operations
> 
> Was it verified that adding "sys_admin" to _DENIED_CAPABILITIES works as
> expected? If it was, I would remove the TODO entry and uncomment the line.
> Would it make sense to make these (or a subset of these) capabilities
> user-configurable via "gnt-instance modify"?

See the TODO above in the docstring for the class - making this
configurable is the end intention, and depends on some container
parameters framework.

> > +    "sys_boot",        # Use reboot(2) and kexec_load(2)
> > +    "sys_module",      # Load  and  unload kernel modules
> > +    "sys_time",        # Set  system  clock, set real-time (hardware) clock
> > +    ]
> > +
> > +  PARAMETERS = {
> > +    }
> > +
> > +  def __init__(self):
> > +    hv_base.BaseHypervisor.__init__(self)
> > +    if not os.path.exists(self._ROOT_DIR):
> > +      os.mkdir(self._ROOT_DIR)
> > +    if not os.path.isdir(self._ROOT_DIR):
> > +      raise HypervisorError("Needed path %s is not a directory" %
> > +                            self._ROOT_DIR)
> 
> How about using utils.EnsureDirs() instead?

Done.

> > +
> > +  @staticmethod
> > +  def _GetMountSubdirs(path):
> > +    """Return the list of mountpoints under a given path.
> > +
> > +    This function is Linux-specific.
> > +
> > +    """
> > +    #TODO(iustin): investigate and document non-linux options
> > +    #(e.g. via mount output)
> > +    data = []
> > +    fh = open("/proc/mounts", "r")
> > +    try:
> > +      for line in fh:
> > +        _, mountpoint, _ = line.split(" ", 2)
> > +        if (mountpoint.startswith(path) and
> > +            mountpoint != path):
> > +          data.append(mountpoint)
> > +    finally:
> > +      fh.close()
> > +    data.sort(key=lambda x: x.count("/"), reverse=True)
> > +    return data
> > +
> > +  @classmethod
> > +  def _InstanceDir(cls, instance_name):
> > +    """Return the root directory for an instance.
> > +
> > +    """
> > +    return utils.PathJoin(cls._ROOT_DIR, instance_name)
> > +
> > +  @classmethod
> > +  def _InstanceConfFile(cls, instance_name):
> > +    """Return the root directory for an instance.
> 
> Please correct the docstring.

Done.

> > +
> > +    """
> > +    return utils.PathJoin(cls._ROOT_DIR, instance_name + ".conf")
> > +
> > +  def ListInstances(self):
> > +    """Get the list of running instances.
> > +
> > +    """
> > +    result = utils.RunCmd(["lxc-ls"])
> > +    if result.failed:
> > +      raise errors.HypervisorError("Can't run lxc-ls: %s" % result.output)
> > +    return result.stdout.splitlines()
> > +
> > +  def GetInstanceInfo(self, instance_name):
> > +    """Get instance properties.
> > +
> > +    @type instance_name: string
> > +    @param instance_name: the instance name
> > +
> > +    @return: (name, id, memory, vcpus, stat, times)
> > +
> > +    """
> > +    # TODO: read container info from the cgroup mountpoint
> > +    return (instance_name, 0, 0, 0, 0, 0)
> > +
> > +  def GetAllInstancesInfo(self):
> > +    """Get properties of all instances.
> > +
> > +    @return: [(name, id, memory, vcpus, stat, times),...]
> > +
> > +    """
> > +    data = []
> > +    for name in self.ListInstances():
> 
> Please add a similar TODO entry here.

Done.

> > +      data.append((name, 0, 0, 0, 0, 0))
> > +    return data
> > +
> > +  def _CreateConfigFile(self, instance, root_dir):
> > +    """Create an lxc.conf file for an instance"""
> > +    out = []
> > +    # hostname
> > +    out.append("lxc.utsname = %s" % instance.name)
> > +
> > +    # separate pseudo-TTY instances
> > +    out.append("lxc.pts = 255")
> > +    # standard TTYs/console
> > +    out.append("lxc.tty = 6")
> > +
> > +    # root FS
> > +    out.append("lxc.rootfs = %s" % root_dir)
> > +
> > +    # TODO: additional mounts, if we disable CAP_SYS_ADMIN
> > +
> > +    # Device control
> > +    # deny direct device access
> > +    out.append("lxc.cgroup.devices.deny = a")
> > +    for devinfo in self._DEVS:
> > +      out.append("lxc.cgroup.devices.allow = %s rw" % devinfo)
> > +
> > +    # Networking
> > +    for idx, nic in enumerate(instance.nics):
> > +      out.append("# NIC %d" % idx)
> > +      mode = nic.nicparams[constants.NIC_MODE]
> > +      link = nic.nicparams[constants.NIC_LINK]
> > +      if mode == constants.NIC_MODE_BRIDGED:
> > +        out.append("lxc.network.type = veth")
> > +        out.append("lxc.network.link = %s" % link)
> > +      else:
> > +        raise errors.HypervisorError("LXC hypervisor supports only"
> > +                                     " bridged mode (NIC %d has mode %s)" %
> 
> s/supports only/only supports/

Done.

> > +                                     (idx, mode))
> > +      out.append("lxc.network.hwaddr = %s" % nic.mac)
> > +      out.append("lxc.network.flags = up")
> > +
> > +    # Capabilities
> > +    for cap in self._DENIED_CAPABILITIES:
> > +      out.append("lxc.cap.drop = %s" % cap)
> > +
> > +    return "\n".join(out) + "\n"
> > +
> > +  def StartInstance(self, instance, block_devices):
> > +    """Start an instance.
> > +
> > +    For LCX, we try to mount the block device and execute 'lxc-start
> > +    start' (we use volatile containers).
> > +
> > +    """
> > +    root_dir = self._InstanceDir(instance.name)
> > +    if not os.path.exists(root_dir):
> > +      try:
> > +        os.mkdir(root_dir)
> > +      except IOError, err:
> > +        raise HypervisorError("Failed to start instance %s: %s" %
> > +                              (instance.name, err))
> > +      if not os.path.isdir(root_dir):
> > +        raise HypervisorError("Needed path %s is not a directory" % 
> > root_dir)
> 
> How about using utils.EnsureDirs() here?

Done. Still needs the try except, since we do want to raise
HypervisorError and not GenericError.

> > +
> > +    conf_file = self._InstanceConfFile(instance.name)
> > +    utils.WriteFile(conf_file, data=self._CreateConfigFile(instance, 
> > root_dir))
> > +
> > +    if not os.path.ismount(root_dir):
> > +      if not block_devices:
> > +        raise HypervisorError("LXC needs at least one disk")
> > +
> > +      sda_dev_path = block_devices[0][1]
> > +      result = utils.RunCmd(["mount", sda_dev_path, root_dir])
> > +      if result.failed:
> > +        raise HypervisorError("Can't mount the chroot dir: %s" % 
> > result.output)
> > +    result = utils.RunCmd(["lxc-start", "-n", instance.name,
> > +                           "-o", "/tmp/lxc.log", "-l", "DEBUG",
> > +                           "-f", conf_file, "-d"])
> 
> Can you replace "/tmp/lxc.log" with something unique? ..or replace it
> with a constant if it doesn't need to be unique.

I've added a temporary constants in hv_lxc for the log output, and added
a TODO to replace this with a per-instance log file.

> > +    if result.failed:
> > +      raise HypervisorError("Can't run the lxc start script: %s" %
> > +                            result.output)
> 
> Nitpicking: s/Can't run the lxc start script/Running the lxc-start script 
> failed/
> (rationale: "Can't" suggests that lxc-start is not found or a similar
> problem that prevents executing lxc-start)

Done.

> > +
> > +  def StopInstance(self, instance, force=False, retry=False, name=None):
> > +    """Stop an instance.
> > +
> > +    This method has complicated cleanup tests, as we must:
> > +      - try to kill all leftover processes
> > +      - try to unmount any additional sub-mountpoints
> > +      - finally unmount the instance dir
> > +
> > +    """
> > +    if name is None:
> > +      name = instance.name
> > +
> > +    root_dir = self._InstanceDir(name)
> > +    if not os.path.exists(root_dir):
> > +      return
> > +
> > +    if name in self.ListInstances():
> > +      # Signal init to shutdown; this is a hack
> > +      if not retry and not force:
> > +        result = utils.RunCmd(["chroot", root_dir, "poweroff"])
> > +        if result.failed:
> > +          raise HypervisorError("Can't run the lxc stop script: %s" %
> > +                                result.output)
> 
> I would reword the error message to "Stopping lxc instance failed:"
> as this step has nothing to do with the "lxc-stop" script.

Done.

> > +      time.sleep(2)
> > +      utils.RunCmd(["lxc-stop", "-n", name])
> 
> The result code is not checked here. Please add checks or a comment why we
> don't care about the result. Applies to the other RunCmd calls below.

Done.

> > +
> > +    for mpath in self._GetMountSubdirs(root_dir):
> > +      utils.RunCmd(["umount", mpath])
> > +
> > +    result = utils.RunCmd(["umount", root_dir])
> > +    if result.failed and force:
> > +      msg = ("Processes still alive in the chroot: %s" %
> > +             utils.RunCmd("fuser -vm %s" % root_dir).output)
> > +      logging.error(msg)
> > +      raise HypervisorError("Can't umount the chroot dir: %s (%s)" %
> > +                            (result.output, msg))
> > +
> > +  def RebootInstance(self, instance):
> > +    """Reboot an instance.
> > +
> > +    This is not (yet) implemented for lxc.
> 
> Does it depend on a missing feature in lxc? If it does, please say so in
> the comment.
> If it's just not yet implemented in Ganeti, please put in a TODO.

Done.

Note I don't have an LXC setup now, so I don't know if the new patch
works correctly (the original one did).

Interdiff:

diff --git a/lib/hypervisor/hv_lxc.py b/lib/hypervisor/hv_lxc.py
index fd0cfa8..9dfcbad 100644
--- a/lib/hypervisor/hv_lxc.py
+++ b/lib/hypervisor/hv_lxc.py
@@ -65,6 +65,7 @@ class LXCHypervisor(hv_base.BaseHypervisor):
 
   """
   _ROOT_DIR = constants.RUN_GANETI_DIR + "/lxc"
+  _LOG_FILE = constants.LOG_DIR + "hv_lxc.log"
   _DEVS = [
     "c 1:3",   # /dev/null
     "c 1:5",   # /dev/zero
@@ -85,17 +86,14 @@ class LXCHypervisor(hv_base.BaseHypervisor):
     "sys_module",      # Load  and  unload kernel modules
     "sys_time",        # Set  system  clock, set real-time (hardware) clock
     ]
+  _DIR_MODE = 0755
 
   PARAMETERS = {
     }
 
   def __init__(self):
     hv_base.BaseHypervisor.__init__(self)
-    if not os.path.exists(self._ROOT_DIR):
-      os.mkdir(self._ROOT_DIR)
-    if not os.path.isdir(self._ROOT_DIR):
-      raise HypervisorError("Needed path %s is not a directory" %
-                            self._ROOT_DIR)
+    utils.EnsureDirs([(self._ROOT_DIR, self._DIR_MODE)])
 
   @staticmethod
   def _GetMountSubdirs(path):
@@ -128,7 +126,7 @@ class LXCHypervisor(hv_base.BaseHypervisor):
 
   @classmethod
   def _InstanceConfFile(cls, instance_name):
-    """Return the root directory for an instance.
+    """Return the configuration file for an instance.
 
     """
     return utils.PathJoin(cls._ROOT_DIR, instance_name + ".conf")
@@ -160,6 +158,7 @@ class LXCHypervisor(hv_base.BaseHypervisor):
     @return: [(name, id, memory, vcpus, stat, times),...]
 
     """
+    # TODO: read container info from the cgroup mountpoint
     data = []
     for name in self.ListInstances():
       data.append((name, 0, 0, 0, 0, 0))
@@ -196,7 +195,7 @@ class LXCHypervisor(hv_base.BaseHypervisor):
         out.append("lxc.network.type = veth")
         out.append("lxc.network.link = %s" % link)
       else:
-        raise errors.HypervisorError("LXC hypervisor supports only"
+        raise errors.HypervisorError("LXC hypervisor only supports"
                                      " bridged mode (NIC %d has mode %s)" %
                                      (idx, mode))
       out.append("lxc.network.hwaddr = %s" % nic.mac)
@@ -216,14 +215,10 @@ class LXCHypervisor(hv_base.BaseHypervisor):
 
     """
     root_dir = self._InstanceDir(instance.name)
-    if not os.path.exists(root_dir):
-      try:
-        os.mkdir(root_dir)
-      except IOError, err:
-        raise HypervisorError("Failed to start instance %s: %s" %
-                              (instance.name, err))
-      if not os.path.isdir(root_dir):
-        raise HypervisorError("Needed path %s is not a directory" % root_dir)
+    try:
+      utils.EnsureDirs([(root_dir, self._DIR_MODE)])
+    except errors.GenericError, err:
+      raise HypervisorError("Cannot create instance directory: %s", str(err))
 
     conf_file = self._InstanceConfFile(instance.name)
     utils.WriteFile(conf_file, data=self._CreateConfigFile(instance, root_dir))
@@ -236,11 +231,12 @@ class LXCHypervisor(hv_base.BaseHypervisor):
       result = utils.RunCmd(["mount", sda_dev_path, root_dir])
       if result.failed:
         raise HypervisorError("Can't mount the chroot dir: %s" % result.output)
+    # TODO: replace the global log file with a per-instance log file
     result = utils.RunCmd(["lxc-start", "-n", instance.name,
-                           "-o", "/tmp/lxc.log", "-l", "DEBUG",
+                           "-o", self._LOG_FILE, "-l", "DEBUG",
                            "-f", conf_file, "-d"])
     if result.failed:
-      raise HypervisorError("Can't run the lxc start script: %s" %
+      raise HypervisorError("Running the lxc-start script failed: %s" %
                             result.output)
 
   def StopInstance(self, instance, force=False, retry=False, name=None):
@@ -264,13 +260,19 @@ class LXCHypervisor(hv_base.BaseHypervisor):
       if not retry and not force:
         result = utils.RunCmd(["chroot", root_dir, "poweroff"])
         if result.failed:
-          raise HypervisorError("Can't run the lxc stop script: %s" %
+          raise HypervisorError("Can't run 'poweroff' for the instance: %s" %
                                 result.output)
       time.sleep(2)
-      utils.RunCmd(["lxc-stop", "-n", name])
+      result = utils.RunCmd(["lxc-stop", "-n", name])
+      if result.failed:
+        logging.warning("Error while doing lxc-stop for %s: %s", name,
+                        result.output)
 
     for mpath in self._GetMountSubdirs(root_dir):
-      utils.RunCmd(["umount", mpath])
+      result = utils.RunCmd(["umount", mpath])
+      if result.failed:
+        logging.warning("Error while umounting subpath %s for instance %s: %s",
+                        mpath, name, result.output)
 
     result = utils.RunCmd(["umount", root_dir])
     if result.failed and force:
@@ -283,9 +285,10 @@ class LXCHypervisor(hv_base.BaseHypervisor):
   def RebootInstance(self, instance):
     """Reboot an instance.
 
-    This is not (yet) implemented for lxc.
+    This is not (yet) implemented (in Ganeti) for the LXC hypervisor.
 
     """
+    # TODO: implement reboot
     raise HypervisorError("The LXC hypervisor doesn't implement the"
                           " reboot functionality")

-- 
iustin

Reply via email to