Hi,

On Fri, 06 Feb 2015 15:49:17 +0200 Faidon Liambotis <parav...@debian.org> wrote:
> This seems like a nice approach for status/start/stop/restart but 
> unfortunately doesn't address enabled?/enable/disable at all. For 
> starters, puppet seems to call update-rc.d with "defaults", not 
> "enable". Even "enable", though, does not seem to be sufficient for 
> systemd-only service files :(
> 
> enabled? is similarly broken: it calls invoke-rc.d --query, which 
> returns 105 for test.service and puppet handles 105 by proceeding to 
> check for symlinks under /etc/rc*.d/...
> 
> Finally, self.instances is also broken, as it just lists /etc/init.d 
> init files. The systemd provider calls "systemctl list-unit-files --type 
> service --full --all" instead.
> 
> Honestly, I'd just switch the default provider for Debian 8+ to systemd 
> and let users who use a non-default init system handle it in their 
> manifest by supplying provider => debian or provider => upstart.

Unfortunately, switching the default provider to systemd wouldn't work 
either (see below). Besides that, we still have architectures where sysv-rc is
the default.

To put things in perspective, we currently have three types of services:

 A those shipping an initscript only
 B those shipping and initscript and a systemd service file
 C those shipping only a systemd service file and no initscript. This 
   type includes all systemd-specific services, and - inevitably - many 
   services written/managed by sysadmins.

Gaudenz's patch makes sure that start/stop works for all three types of 
services, however it does not change the enable/disable logic nor the 
service listing, which are still problematic:

 - `update-rc.d enable|disable' does not work for type C services, as 
   demonstrated by Faidon.
 - `update-rc.d -f remove && update-rc.d defaults' as currently used in 
   the `enable' does *not* enable type C *and* type B services at all.  
   `update-rc.d enable' would enable type B services but not type C.
 - querying service enable status using invoke-rc.d also doesn't work 
   for type C services.

If OTOH we were to change the default provider to systemd, we wouldn't 
be much better off:

 - `systemctl is-enabled' doesn't work for type A services, which are 
   still the majority:

   $ systemctl is-enabled ferm
   Failed to get unit file state for ferm.service: No such file or directory

 - `systemctl list-unit-files' as used by systemd's self.instances, does 
   not list type A services as well:

   $ systemctl list-unit-files --all --type service --no-pager | grep ferm
   $

So, we actually need a hybrid provider that will do the following:

 - Detect if we're running systemd as PID 1. This is trivial to do by 
   checking for the existence of /run/systemd/system.

 - Keep compatibility with sysv-rc (and I'd suggest to drop pre-2.88 
   support and spare an ugly call to dpkg).

 - If running under systemd:
   • Use `systemctl enable|disable' for *all* services. This works 
     correctly with our current systemd version for all three types of 
     services and invokes update-rc.d if appropriate.

   • Use `systemctl is-enabled` to query for enabled services only for 
     types B and C. Type A services can be detected because their 
     (auto-generated) units have the SourcePath property set to the 
     initscript path:

     $ systemctl show -pSourcePath ferm.service
     SourcePath=/etc/init.d/ferm

     For type A we need to keep the current invoke-rc.d implementation.

   • For self.instances, augment the type B and C services returned by
     `systemctl list-unit-files' with type A services from the init provider's
     self.instances.

The attached patch on top of 3.7.2-2 (hopefully) addresses all of these 
issues (and drops support for pre-2.88 sysv-rc if you don't mind). I 
have not tested it on a sysvinit Jessie system though, so if anyone could do
this it would be appreciated!

Cheers,
Apollon

P.S.: I'm a member of the Puppet maintainers team, but I haven't touched 
      the package (yet :). I could do an upload if anyone else can't.
From ef06e8211bb82ae4f984aaaf3a80947c633d00fd Mon Sep 17 00:00:00 2001
From: Apollon Oikonomopoulos <apoi...@debian.org>
Date: Fri, 27 Feb 2015 10:55:34 +0200
Subject: [PATCH] Fix service listing and enable/disable in Debian
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Add two support methods to detect when we're running systemd as PID 1
and if a service has only an initscript.

Use these to implement the following functionality:

 • Under systemd, use systemctl enable/disable for all services. This
   works correctly for all types of services.

 • Under systemd, use systemctl is-enabled only for services that have a
   systemd unit file and fall back to invoke-rc.d for sysv services.

Also, fix self.instances to augment the list of systemd-enabled services
with the sysv services.

Finally drop pre-2.88 sysv-rc support and use `update-rc.d enable' for
all services when running under sysv-rc, preserving order changes.
---
 lib/puppet/provider/service/debian.rb | 94 ++++++++++++++++++++++++++---------
 1 file changed, 71 insertions(+), 23 deletions(-)

diff --git a/lib/puppet/provider/service/debian.rb b/lib/puppet/provider/service/debian.rb
index 9f7a2f5..681c7c8 100644
--- a/lib/puppet/provider/service/debian.rb
+++ b/lib/puppet/provider/service/debian.rb
@@ -15,6 +15,7 @@ Puppet::Type.type(:service).provide :debian, :parent => :init do
   # http://projects.reductivelabs.com/issues/2538
   # is resolved.
   commands :invoke_rc => "/usr/sbin/invoke-rc.d"
+  commands :systemctl => "/bin/systemctl"
 
   # This isn't being used directly, it's just here to ensure
   # that the /usr/sbin/service binary is available.
@@ -23,38 +24,82 @@ Puppet::Type.type(:service).provide :debian, :parent => :init do
 
   defaultfor :operatingsystem => :debian
 
+  def self.runs_on_systemd?
+    Dir.exists? "/run/systemd/system"
+  end
+
+  def is_sysv_unit?
+    # The sysv generator sets the SourcePath attribute to the name of the
+    # initscript. Use this to detect whether a unit is backed by an initscript
+    # or not.
+    source = systemctl(:show, "-pSourcePath", @resource[:name])
+    source.start_with? "SourcePath=/etc/init.d/"
+  end
+
+  def self.instances
+    # We need to merge services with systemd unit files with those only having
+    # an initscript. Note that we could use `systemctl --all` to get sysv
+    # services as well, however it would only output *enabled* services.
+    i = {}
+    if self.runs_on_systemd?
+      begin
+        output = systemctl('list-unit-files', '--type', 'service', '--full', '--all',  '--no-pager')
+        output.scan(/^(\S+)\.service\s+(disabled|enabled)\s*$/i).each do |m|
+          i[m[0]] = new(:name => m[0])
+        end
+      rescue Puppet::ExecutionFailure
+      end
+    end
+    get_services(defpath).each do |sysv|
+      unless i.has_key?(sysv.name)
+        i[sysv.name] = sysv
+      end
+    end
+    return i.values
+  end
+
   # Remove the symlinks
   def disable
-    if `dpkg --compare-versions $(dpkg-query -W --showformat '${Version}' sysv-rc) ge 2.88 ; echo $?`.to_i == 0
-      update_rc @resource[:name], "disable"
+    if self.class.runs_on_systemd?
+      systemctl(:disable, @resource[:name])
     else
-      update_rc "-f", @resource[:name], "remove"
-      update_rc @resource[:name], "stop", "00", "1", "2", "3", "4", "5", "6", "."
+      update_rc @resource[:name], "disable"
     end
   end
 
   def enabled?
-    # TODO: Replace system call when Puppet::Util::Execution.execute gives us a way
-    # to determine exit status.  http://projects.reductivelabs.com/issues/2538
-    system("/usr/sbin/invoke-rc.d", "--quiet", "--query", @resource[:name], "start")
-
-    # 104 is the exit status when you query start an enabled service.
-    # 106 is the exit status when the policy layer supplies a fallback action
-    # See x-man-page://invoke-rc.d
-    if [104, 106].include?($CHILD_STATUS.exitstatus)
-      return :true
-    elsif [105].include?($CHILD_STATUS.exitstatus)
-      # 105 is unknown, which generally means the iniscript does not support query
-      # The debian policy states that the initscript should support methods of query
-      # For those that do not, peform the checks manually
-      # http://www.debian.org/doc/debian-policy/ch-opersys.html
-      if get_start_link_count >= 4
+    # Initscript-backed services have no enabled status in systemd, so we
+    # need to query them using invoke-rc.d.
+    if self.class.runs_on_systemd? and not is_sysv_unit?
+      begin
+        systemctl("is-enabled", @resource[:name])
         return :true
-      else
+      rescue Puppet::ExecutionFailure
         return :false
       end
     else
-      return :false
+      # TODO: Replace system call when Puppet::Util::Execution.execute gives us a way
+      # to determine exit status.  http://projects.reductivelabs.com/issues/2538
+      system("/usr/sbin/invoke-rc.d", "--quiet", "--query", @resource[:name], "start")
+
+      # 104 is the exit status when you query start an enabled service.
+      # 106 is the exit status when the policy layer supplies a fallback action
+      # See x-man-page://invoke-rc.d
+      if [104, 106].include?($CHILD_STATUS.exitstatus)
+        return :true
+      elsif [105].include?($CHILD_STATUS.exitstatus)
+        # 105 is unknown, which generally means the iniscript does not support query
+        # The debian policy states that the initscript should support methods of query
+        # For those that do not, peform the checks manually
+        # http://www.debian.org/doc/debian-policy/ch-opersys.html
+        if get_start_link_count >= 4
+          return :true
+        else
+          return :false
+        end
+      else
+        return :false
+      end
     end
   end
 
@@ -63,8 +108,11 @@ Puppet::Type.type(:service).provide :debian, :parent => :init do
   end
 
   def enable
-    update_rc "-f", @resource[:name], "remove"
-    update_rc @resource[:name], "defaults"
+    if self.class.runs_on_systemd?
+      systemctl(:enable, @resource[:name])
+    else
+      update_rc @resource[:name], "enable"
+    end
   end
 
   # The start, stop, restart and status command use service
-- 
2.1.4

Attachment: signature.asc
Description: Digital signature

Reply via email to