summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsersut <serdar@opscode.com>2013-03-01 09:40:55 -0800
committersersut <serdar@opscode.com>2013-03-01 09:48:09 -0800
commit4b880bfe6d916b8ca844c72f7ab9f30a8b2b9f0d (patch)
tree6fab28acaa74eb3d633eef99001ff33baf95b9be
parent21882ab2304187b5bfba50f08674c91cbdef24df (diff)
downloadchef-4b880bfe6d916b8ca844c72f7ab9f30a8b2b9f0d.tar.gz
Tests, documentation and better error handling for windows service manager.
-rw-r--r--lib/chef/application/windows_service_manager.rb24
-rw-r--r--spec/functional/win32/service_manager_spec.rb264
-rw-r--r--spec/support/lib/spec_service.rb59
3 files changed, 344 insertions, 3 deletions
diff --git a/lib/chef/application/windows_service_manager.rb b/lib/chef/application/windows_service_manager.rb
index 83e89b9cfb..27e21a1acb 100644
--- a/lib/chef/application/windows_service_manager.rb
+++ b/lib/chef/application/windows_service_manager.rb
@@ -23,6 +23,12 @@ require 'mixlib/cli'
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,
@@ -57,14 +63,24 @@ class Chef
# 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'
@@ -137,7 +153,8 @@ class Chef
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
@@ -153,6 +170,7 @@ class Chef
sleep 1
end
end
+
end
end
end
diff --git a/spec/functional/win32/service_manager_spec.rb b/spec/functional/win32/service_manager_spec.rb
new file mode 100644
index 0000000000..439c87478b
--- /dev/null
+++ b/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
diff --git a/spec/support/lib/spec_service.rb b/spec/support/lib/spec_service.rb
new file mode 100644
index 0000000000..3e1f6c3638
--- /dev/null
+++ b/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