Skip to content

Commit

Permalink
Merge pull request #216 from Tumblr/service-api-breakout
Browse files Browse the repository at this point in the history
Support Systemd
  • Loading branch information
Graham Christensen authored and GitHub Enterprise committed Mar 28, 2017
2 parents 3ab12b8 + a7e8490 commit d64bf16
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 22 deletions.
2 changes: 1 addition & 1 deletion lib/jetpants.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
module Jetpants; end

$LOAD_PATH.unshift File.join(File.dirname(__FILE__), 'jetpants'), File.join(File.dirname(__FILE__), '..', 'plugins')
%w(output callback table host db pool topology shard shardpool monkeypatch commandsuite).each {|g| require g}
%w(output callback table host hostservice db pool topology shard shardpool monkeypatch commandsuite).each {|g| require g}

# Since Jetpants is extremely multi-threaded, we need to force uncaught exceptions to
# kill all threads in order to have any kind of sane error handling.
Expand Down
7 changes: 4 additions & 3 deletions lib/jetpants/db/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ module Jetpants

class DB

# options to pass to MySQL on start
attr_reader :start_options

# options to pass to DB#restart_mysql for quick restarts
attr_accessor :enable_flush_innodb_cache

def start_options
@start_options || []
end

# add a server start option for the instance
# will be combined with options passed into start_mysql
def add_start_option(option)
Expand Down
30 changes: 12 additions & 18 deletions lib/jetpants/host.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def initialize(ip)
@lock = Mutex.new
@available = nil
@clone_multi_threaded = false # default use fast_copy_chain
@service_manager = nil
end

# Returns a Host object for the machine Jetpants is running on.
Expand Down Expand Up @@ -770,40 +771,33 @@ def mount_stats(mount)
end
end

###### Misc methods ########################################################
###### Service management methods ##########################################
def service_api
@service_manager ||= Jetpants::HostService.pick_by_preflight(self)
end

def service_start(name, options=[])
output service(:start, name, options.join(' '))
output service_api.start(name, options)
end

def service_restart(name, options=[])
output service(:restart, name, options.join(' '))
output service_api.restart(name, options)
end

def service_stop(name)
output service(:stop, name)
output service_api.stop(name)
end

def service_running?(name)
status = service(:status, name).downcase
# mysql is running if the output of "service mysql status" doesn't include any of these strings
not_running_strings = ['not running', 'stop/waiting']

not_running_strings.none? {|str| status.include? str}
service_api.running?(name)
end

# Performs the given operation (:start, :stop, :restart, :status) for the
# specified service (ie "mysql"). Requires that the "service" bin is in
# root's PATH.
# Please be aware that the output format and exit codes for the service
# binary vary between Linux distros! You may find that you need to override
# methods that call Host#service with :status operation (such as
# DB#probe_running) in a custom plugin, to parse the output properly on
# your chosen Linux distro.
def service(operation, name, options='')
ssh_cmd "service #{name} #{operation.to_s} #{options}".rstrip
output "Warning: Calling Host.service directly is deprecated!".red
service_manager.service_direct(operation, name, options).rstrip
end

###### Misc methods ########################################################
# `stat` call to get all the information about the given file
def get_file_stats(filename)
mode_re = /^Access:\s+\((?<mode>\d+)\/(?<permissions>[drwx-]+)\)\s+Uid:\s+\(\s+\d+\/\s+(?<user>\w+)\)\s+Gid:\s+\(\s+\d+\/\s+(?<group>\w+)\)$/x
Expand Down
38 changes: 38 additions & 0 deletions lib/jetpants/hostservice.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
require 'hostservice/upstart'
require 'hostservice/systemd'

module Jetpants
module HostService
def self.pick_by_preflight(host)
# In /proc/1/stat we get to see the name of the init process (ie: pid1) so
# we can best match it to our providers.
#
# We expect to see something like:
# 1 (process_name) S 0 1 1 0 -1 4202752 2925 7358649115 10 1047 604 1079 [...snipped...]
#
# on upstart, we'd see:
# 1 (init) S 0 1 1 0 -1 4202752 2925 7358649115 10 1047 604 1079 [...snipped...]
#
# on systemd we'd see:
# 1 (systemd) S 0 1 1 0 -1 4219136 2502297 91460739 50 3010 3771 3113 [...snipped...]
#
# For more information, see proc(5) (`man proc`) and look for `/proc/[pid]/stat`.
pid1_match = host.ssh_cmd('cat /proc/1/stat').match(/^1 \((.*?)\) /)
throw "/proc/1/stat isn't matching, something is wrong!" if pid1_match.nil?
pid1_name = pid1_match[1]

provider = all_providers.find { |candidate| candidate.preflight(host, pid1_name) }
raise "Cannot detect a valid service provider for #{host}" if provider.nil?

return provider.new(host)
end

def self.all_providers
# Service managers that we can support, in order of most to least likely
[
Jetpants::HostService::Upstart,
Jetpants::HostService::Systemd,
]
end
end
end
41 changes: 41 additions & 0 deletions lib/jetpants/hostservice/systemd.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
module Jetpants
module HostService
class Systemd
def self.preflight(host, pid1_name)
pid1_name == "systemd"
end

def initialize(host)
@host = host
end

def start(name, options=[])
service(:start, name, options)
end

def restart(name, options=[])
service(:restart, name, options)
end

def stop(name)
service(:stop, name)
end

def running?(name)
status = service(:status, name).downcase

return !status.include?("active: inactive")
end

def service_direct(operation, name, options='')
service(operation, name, [options])
end

def service(operation, name, options=[])
raise "Systemd doesn't support options! :( (Passed: #{options.join(' ')})" unless options.empty?

@host.ssh_cmd "systemctl #{operation.to_s} #{name}".rstrip
end
end
end
end
47 changes: 47 additions & 0 deletions lib/jetpants/hostservice/upstart.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
module Jetpants
module HostService
class Upstart
def self.preflight(host, pid1_name)
pid1_name == "init" && host.has_installed('service')
end

def initialize(host)
@host = host
end

def start(name, options=[])
service(:start, name, options.join(' '))
end

def restart(name, options=[])
service(:restart, name, options.join(' '))
end

def stop(name)
service(:stop, name)
end

def running?(name)
status = service(:status, name).downcase
# the service is running if the output of "service #{name} status" doesn't include any of
# these strings
not_running_strings = ['not running', 'stop/waiting']

not_running_strings.none? {|str| status.include? str}
end

def service_direct(operation, name, options='')
service(operation, name, options)
end

# Performs the given operation (:start, :stop, :restart, :status) for the
# specified service (ie "mysql"). Requires that the "service" bin is in
# root's PATH.
# Please be aware that the output format and exit codes for the service
# binary vary between Linux distros!
def service(operation, name, options='')
@host.ssh_cmd "service #{name} #{operation.to_s} #{options}".rstrip
end
end
end
end

0 comments on commit d64bf16

Please sign in to comment.