Skip to content

Commit

Permalink
Tests, documentation and better error handling for windows service ma…
Browse files Browse the repository at this point in the history
…nager.
  • Loading branch information
sersut committed Mar 1, 2013
1 parent 21882ab commit 4b880bf
Show file tree
Hide file tree
Showing 3 changed files with 344 additions and 3 deletions.
24 changes: 21 additions & 3 deletions lib/chef/application/windows_service_manager.rb
Expand Up @@ -23,6 +23,12 @@
class Chef
class Application
class WindowsServiceManager
#
# This class is used to create and manage a windows service.
# Service should be created using Daemon class from
# win32/service gem.
# For an example see: Chef::Application::WindowsService
#
include Mixlib::CLI

option :action,
Expand Down Expand Up @@ -57,14 +63,24 @@ def initialize(service_options)
# anti-pattern :(
super()

raise ArgumentError, "Service definition is not provided" if service_options.nil?

required_options = [:service_name, :service_display_name, :service_name, :service_description, :service_file_path]

required_options.each do |req_option|
if !service_options.has_key?(req_option)
raise ArgumentError, "Service definition doesn't contain required option #{req_option}"
end
end

@service_name = service_options[:service_name]
@service_display_name = service_options[:service_display_name]
@service_description = service_options[:service_description]
@service_file_path = service_options[:service_file_path]
end

def run
parse_options
def run(params = ARGV)
parse_options(params)

case config[:action]
when 'install'
Expand Down Expand Up @@ -137,7 +153,8 @@ def take_action(action=nil, desired_state=nil)
puts "Service '#{@service_name}' is already '#{desired_state}'."
end
else
puts "Cannot '#{action}' service '#{@service_name}', service does not exist."
puts "Cannot '#{action}' service '#{@service_name}'"
puts "Service #{@service_name} doesn't exist on the system."
end
end

Expand All @@ -153,6 +170,7 @@ def wait_for_state(desired_state)
sleep 1
end
end

end
end
end
264 changes: 264 additions & 0 deletions spec/functional/win32/service_manager_spec.rb
@@ -0,0 +1,264 @@
#
# Author:: Serdar Sutay (<serdar@opscode.com>)
# Copyright:: Copyright (c) 2013 Opscode, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

require 'spec_helper'
require 'chef/application/windows_service_manager'

#
# ATTENTION:
# This test creates a windows service for testing purposes and runs it
# as Local System on windows boxes.
# This test will fail if you run the tests inside a Windows VM by
# sharing the code from your host since Local System account by
# default can't see the mounted partitions.
# Run this test by copying the code to a local VM directory or setup
# Local System account to see the maunted partitions for the shared
# directories.
#

describe "Chef::Application::WindowsServiceManager", :windows_only do

# Definition for the test-service

let(:test_service) {
{
:service_name => "spec-service",
:service_display_name => "Spec Test Service",
:service_description => "Service for testing Chef::Application::WindowsServiceManager.",
:service_file_path => File.expand_path(File.join(File.dirname(__FILE__), '../../support/lib/spec_service.rb'))
}
}

# Test service creates a file for us to verify that it is running.
# Since our test service is running as Local System we should look
# for the file it creates under SYSTEM temp directory

let(:test_service_file) {
"#{ENV['SystemDrive']}\\windows\\temp\\spec_service_file"
}

context "with invalid service definition" do
it "throws an error when initialized with no service definition" do
lambda { Chef::Application::WindowsServiceManager.new(nil) }.should raise_error(ArgumentError)
end

it "throws an error with required missing options" do
test_service.each do |key,value|
service_def = test_service.dup
service_def.delete(key)

lambda { Chef::Application::WindowsServiceManager.new(service_def) }.should raise_error(ArgumentError)
end
end
end

context "with valid definition" do
before(:each) do
@service_manager_output = [ ]
# Uncomment below lines to debug this test
# original_puts = $stdout.method(:puts)
$stdout.stub(:puts) do |message|
@service_manager_output << message
# original_puts.call(message)
end
end

after(:each) do
cleanup
end

context "when service doesn't exist" do
it "default => should say service don't exist" do
service_manager.run

@service_manager_output.grep(/doesn't exist on the system/).length.should > 0
end

it "install => should install the service" do
service_manager.run(["-a", "install"])

test_service_exists?.should be_true
end

it "other actions => should say service doesn't exist" do
["delete", "start", "stop", "pause", "resume", "uninstall"].each do |action|
service_manager.run(["-a", action])
@service_manager_output.grep(/doesn't exist on the system/).length.should > 0
@service_manager_output = [ ]
end
end
end

context "when service exists" do
before(:each) do
service_manager.run(["-a", "install"])
end

it "install => should say service already exists" do
service_manager.run(["-a", "install"])
@service_manager_output.grep(/already exists/).length.should > 0
end

context "and service is stopped" do
["delete", "uninstall"].each do |action|
it "#{action} => should remove the service" do
service_manager.run(["-a", action])
test_service_exists?.should be_false
end
end

it "default, status => should say service is stopped" do
service_manager.run([ ])
@service_manager_output.grep(/stopped/).length.should > 0
@service_manager_output = [ ]

service_manager.run(["-a", "status"])
@service_manager_output.grep(/stopped/).length.should > 0
end

it "start should start the service" do
service_manager.run(["-a", "start"])
test_service_state.should == "running"
File.exists?(test_service_file).should be_true
end

it "stop should not affect the service" do
service_manager.run(["-a", "stop"])
test_service_state.should == "stopped"
end


["pause", "resume"].each do |action|
it "#{action} => should raise error" do
lambda {service_manager.run(["-a", action])}.should raise_error(::Win32::Service::Error)
end
end

context "and service is started" do
before(:each) do
service_manager.run(["-a", "start"])
end

["delete", "uninstall"].each do |action|
it "#{action} => should remove the service" do
service_manager.run(["-a", action])
test_service_exists?.should be_false
end
end

it "default, status => should say service is running" do
service_manager.run([ ])
@service_manager_output.grep(/running/).length.should > 0
@service_manager_output = [ ]

service_manager.run(["-a", "status"])
@service_manager_output.grep(/running/).length.should > 0
end

it "stop should stop the service" do
service_manager.run(["-a", "stop"])
test_service_state.should == "stopped"
end

it "pause should pause the service" do
service_manager.run(["-a", "pause"])
test_service_state.should == "paused"
end

it "resume should have no affect" do
service_manager.run(["-a", "resume"])
test_service_state.should == "running"
end
end

context "and service is paused" do
before(:each) do
service_manager.run(["-a", "start"])
service_manager.run(["-a", "pause"])
end

actions = ["delete", "uninstall"]
actions.each do |action|
it "#{action} => should remove the service" do
service_manager.run(["-a", action])
test_service_exists?.should be_false
end
end

it "default, status => should say service is paused" do
service_manager.run([ ])
@service_manager_output.grep(/paused/).length.should > 0
@service_manager_output = [ ]

service_manager.run(["-a", "status"])
@service_manager_output.grep(/paused/).length.should > 0
end

it "stop should stop the service" do
service_manager.run(["-a", "stop"])
test_service_state.should == "stopped"
end

it "pause should not affect the service" do
service_manager.run(["-a", "pause"])
test_service_state.should == "paused"
end

it "start should raise an error" do
lambda {service_manager.run(["-a", "start"])}.should raise_error(::Win32::Service::Error)
end

end
end
end
end

def test_service_exists?
::Win32::Service.exists?("spec-service")
end

def test_service_state
::Win32::Service.status("spec-service").current_state
end

def service_manager
Chef::Application::WindowsServiceManager.new(test_service)
end

def cleanup
# Uninstall if the test service is installed.
if test_service_exists?

# We can only uninstall when the service is stopped.
if test_service_state != "stopped"
::Win32::Service.send("stop", "spec-service")
while test_service_state != "stopped"
sleep 1
end
end

::Win32::Service.delete("spec-service")
end

# Delete the test_service_file if it exists
if File.exists?(test_service_file)
File.delete(test_service_file)
end

end
end
59 changes: 59 additions & 0 deletions spec/support/lib/spec_service.rb
@@ -0,0 +1,59 @@
#
# Author:: Serdar Sutay (<serdar@lambda.local>)
# Copyright:: Copyright (c) 2013 Opscode, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

require 'win32/daemon'

class SpecService < ::Win32::Daemon
def service_init
@test_service_file = "#{ENV['TMP']}/spec_service_file"
end

def service_main(*startup_parameters)
while running? do
if !File.exists?(@test_service_file)
File.open(@test_service_file, 'wb') do |f|
f.write("This file is created by SpecService")
end
end

sleep 1
end
end

################################################################################
# Control Signal Callback Methods
################################################################################

def service_stop
end

def service_pause
end

def service_resume
end

def service_shutdown
end
end

# To run this file as a service, it must be called as a script from within
# the Windows Service framework. In that case, kick off the main loop!
if __FILE__ == $0
SpecService.mainloop
end

0 comments on commit 4b880bf

Please sign in to comment.