klarlack 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Max Schöfmann
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,43 @@
1
+ = klarlack
2
+
3
+ Klarlack is a ruby client library for the varnish administration interface.
4
+
5
+ See also: http://www.varnish-cache.org
6
+
7
+ Please note: You need at least version 2.0.3 of varnish for purging to work.
8
+
9
+ === Installation
10
+
11
+ sudo gem install schoefmax-klarlack --source=http://gems.github.com
12
+
13
+ === Example
14
+
15
+ Lets purge all blog posts from the cache...
16
+
17
+ require 'rubygems'
18
+ require 'klarlack'
19
+
20
+ varnish = Varnish::Client.new '127.0.0.1:6082'
21
+ # the regexp is not a ruby regexp, just a plain string varnishd understands
22
+ varnish.purge :url, "^/posts/.*"
23
+
24
+ In a Rails app, you might want to use use this in a cache sweeper.
25
+
26
+ === Specs
27
+
28
+ Start up a local varnishd with <tt>-T 127.0.0.1:6082</tt>. Then run
29
+
30
+ spec spec
31
+
32
+ === TODO
33
+
34
+ * Support authentication when varnishd is started with <tt>-S</tt>
35
+ * Make parameter manipulation/display more friendly
36
+
37
+ === WTF?
38
+
39
+ http://dict.leo.org/?search=klarlack
40
+
41
+ === Copyright
42
+
43
+ Copyright (c) 2009 Max Schöfmann. Distributed under the MIT-License
@@ -0,0 +1,48 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "klarlack"
8
+ gem.summary = %Q{ruby client for varnishd's admin interface}
9
+ gem.email = "max@pragmatic-it.de"
10
+ gem.homepage = "http://github.com/schoefmax/klarlack"
11
+ gem.authors = ["Max Schöfmann"]
12
+
13
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
14
+ end
15
+ rescue LoadError
16
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
17
+ end
18
+
19
+ require 'spec/rake/spectask'
20
+ Spec::Rake::SpecTask.new(:spec) do |spec|
21
+ spec.libs << 'lib' << 'spec'
22
+ spec.spec_files = FileList['spec/**/*_spec.rb']
23
+ end
24
+
25
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
26
+ spec.libs << 'lib' << 'spec'
27
+ spec.pattern = 'spec/**/*_spec.rb'
28
+ spec.rcov = true
29
+ end
30
+
31
+
32
+ task :default => :spec
33
+
34
+ require 'rake/rdoctask'
35
+ Rake::RDocTask.new do |rdoc|
36
+ if File.exist?('VERSION.yml')
37
+ config = YAML.load(File.read('VERSION.yml'))
38
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
39
+ else
40
+ version = ""
41
+ end
42
+
43
+ rdoc.rdoc_dir = 'rdoc'
44
+ rdoc.title = "klarlack #{version}"
45
+ rdoc.rdoc_files.include('README*')
46
+ rdoc.rdoc_files.include('lib/**/*.rb')
47
+ end
48
+
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 0
4
+ :patch: 2
@@ -0,0 +1,8 @@
1
+ require 'socket'
2
+ require 'varnish/socket_factory'
3
+ require 'varnish/client'
4
+
5
+ module Varnish
6
+ class Error < StandardError; end
7
+ VERSION = '0.0.1'
8
+ end
@@ -0,0 +1,258 @@
1
+ module Varnish
2
+ class Client
3
+ # Default management port of varnishd
4
+ DEFAULT_PORT = 6082
5
+
6
+ # We assume varnishd on localhost
7
+ DEFAULT_HOST = 'localhost'
8
+
9
+ DEFAULT_OPTS = {
10
+ :keep_alive => false,
11
+ :timeout => 1
12
+ }
13
+
14
+ # timeout in seconds when connecting to varnishd. Default is 1
15
+ attr_accessor :timeout
16
+
17
+ # set to true, to keep the connection alive. Default is false
18
+ attr_accessor :keep_alive
19
+
20
+ # hostname or IP-address of varnishd. Default is "localhost"
21
+ attr_accessor :host
22
+
23
+ # port number of varnishd. Default is 6082
24
+ attr_accessor :port
25
+
26
+ # Examples:
27
+ #
28
+ # Varnish::Client.new "127.0.0.1"
29
+ # Varnish::Client.new "mydomain.com:6082"
30
+ # Varnish::Client.new :timeout => 5
31
+ # Varnish::Client.new "10.0.0.3:6060", :timeout => nil, :keep_alive => true
32
+ #
33
+ # === Configuration options
34
+ #
35
+ # +timeout+:: if specified (seconds), calls to varnish
36
+ # will be wrapped in a timeout, default is 1 second.
37
+ # Disable with <tt>:timeout => nil</tt>
38
+ # +keep_alive+:: if true, the connection is kept alive by sending
39
+ # ping commands to varnishd every few seconds
40
+ def initialize(*args)
41
+ opts = {}
42
+
43
+ case args.length
44
+ when 0
45
+ self.server = DEFAULT_HOST
46
+ when 1
47
+ arg = args[0]
48
+ case arg
49
+ when String
50
+ self.server = arg
51
+ when Hash
52
+ self.server = DEFAULT_HOST
53
+ opts = arg
54
+ end
55
+ when 2
56
+ self.server = args[0]
57
+ opts = args[1]
58
+ else
59
+ raise ArgumentError, "wrong number of arguments (#{args.length} for 2)"
60
+ end
61
+
62
+ opts = DEFAULT_OPTS.merge(opts)
63
+ @timeout = opts[:timeout]
64
+ @keep_alive = opts[:keep_alive]
65
+
66
+ @mutex = Mutex.new
67
+ end
68
+
69
+ # Set the varnishd management host and port.
70
+ # Expects a string as "hostname" or "hostname:port"
71
+ def server=(server)
72
+ @host, @port = server.split(':')
73
+ @port = (@port || DEFAULT_PORT).to_i
74
+ server
75
+ end
76
+
77
+ # Returns the varnishd management host and port as "hostname:port"
78
+ def server
79
+ "#{@host}:#{@port}"
80
+ end
81
+
82
+ # Manipulate the VCL configuration
83
+ #
84
+ # .vcl :load, <configname>, <filename>
85
+ # .vcl :inline, <configname>, <quoted_VCLstring>
86
+ # .vcl :use, <configname>
87
+ # .vcl :discard, <configname>
88
+ # .vcl :list
89
+ # .vcl :show, <configname>
90
+ #
91
+ # Returns an array of VCL configurations for :list, and the servers
92
+ # response as string otherwise
93
+ #
94
+ # Ex.:
95
+ # v = Varnish::Client.new
96
+ # v.vcl :list
97
+ # #=> [["active", 0, "boot"]]
98
+ #
99
+ # v.vcl :load, "newconf", "/etc/varnish/myconf.vcl"
100
+ #
101
+ #
102
+ def vcl(op, *params)
103
+ response = cmd("vcl.#{op}", *params)
104
+ case op
105
+ when :list
106
+ response.split("\n").map do |line|
107
+ a = line.split(/\s+/, 3)
108
+ [a[0], a[1].to_i, a[2]]
109
+ end
110
+ else
111
+ response
112
+ end
113
+ end
114
+
115
+ # Purge objects from the cache or show the purge queue.
116
+ #
117
+ # .purge :url, <regexp>
118
+ # .purge :hash, <regexp>
119
+ # .purge :list
120
+ # .purge <costum-field> <args>
121
+ #
122
+ # +op+:: :url, :hash, :list or a custom field
123
+ # +regexp+:: a string containing a varnish compatible regexp
124
+ #
125
+ # Returns true for purging, returns an array containing the purge queue
126
+ # for :list
127
+ #
128
+ # Ex.:
129
+ # v = Varnish::Client.new
130
+ # v.purge :url, '.*'
131
+ #
132
+ # v.purge :list
133
+ # #=> [[1, "req.url ~ .*"]]
134
+ #
135
+ def purge(op, *regexp_or_args)
136
+ c = [:url, :hash, :list].include?(op) ? "purge.#{op}" : "purge #{op}"
137
+ response = cmd(c, *regexp_or_args)
138
+ case op
139
+ when :list
140
+ response.split("\n").map do |line|
141
+ a = line.split("\t")
142
+ [a[0].to_i, a[1]]
143
+ end
144
+ else
145
+ bool response
146
+ end
147
+ end
148
+
149
+ # Ping the server to keep the connection alive
150
+ def ping(timestamp = nil)
151
+ cmd("ping", timestamp)
152
+ end
153
+
154
+ # Returns a hash of status information
155
+ #
156
+ # Ex.:
157
+ # v = Varnish::Client.new
158
+ # v.stats
159
+ # => {"Total header bytes"=>0, "Cache misses"=>0 ...}
160
+ def stats
161
+ result = cmd("stats")
162
+ Hash[*result.split("\n").map { |line|
163
+ stat = line.strip!.split(/\s+/, 2)
164
+ [stat[1], stat[0].to_i]
165
+ }.flatten
166
+ ]
167
+ end
168
+
169
+ # Set and show parameters
170
+ #
171
+ # .param :show, [-l], [<param>]
172
+ # .param :set, <param>, <value>
173
+ def param(op, *args)
174
+ cmd("param.#{op}", *args)
175
+ end
176
+
177
+ # Returns the status string from varnish.
178
+ # See also #running? and #stopped?
179
+ def status
180
+ cmd("status")
181
+ end
182
+
183
+ def start
184
+ bool cmd("start")
185
+ end
186
+
187
+ def stop
188
+ bool cmd("stop")
189
+ end
190
+
191
+ def running?
192
+ bool status =~ /running/
193
+ end
194
+
195
+ def stopped?
196
+ bool status =~ /stopped/
197
+ end
198
+
199
+ # close the connection to varnishd.
200
+ # Note that the connection will automatically be re-established
201
+ # when another command is issued.
202
+ def disconnect
203
+ if connected?
204
+ @conn.write "quit\n"
205
+ @conn.gets
206
+ @conn.close unless @conn.closed?
207
+ end
208
+ end
209
+
210
+ def connected?
211
+ bool @conn && !@conn.closed?
212
+ end
213
+
214
+ private
215
+
216
+ # Sends a command to varnishd.
217
+ # Raises an Varnish::Error when a non-200 status is returned
218
+ # Returns the response text
219
+ def cmd(name, *params)
220
+ @mutex.synchronize do
221
+ connect unless connected?
222
+ @conn.write "#{name} #{params.join(' ')}\n"
223
+ status, length = @conn.gets.split # <status> <content_length>\n
224
+ content = @conn.read(length.to_i + 1) # +1 = \n
225
+ content.chomp!
226
+ raise Error, "Command #{name} returned with status #{status}: #{content}" if status.to_i != 200
227
+ content
228
+ end
229
+ end
230
+
231
+ def connect
232
+ @conn = SocketFactory.tcp_socket(@host, @port, @timeout)
233
+
234
+ # If keep alive, we ping the server every few seconds.
235
+ if @keep_alive
236
+ varnish = self
237
+ Thread.new do
238
+ while(true) do
239
+ if varnish.connected?
240
+ varnish.ping
241
+ sleep 5
242
+ else
243
+ break
244
+ end
245
+ end
246
+ end
247
+ end
248
+
249
+ @conn
250
+ end
251
+
252
+ # converts +value+ into a boolean
253
+ def bool(value)
254
+ !!value
255
+ end
256
+
257
+ end
258
+ end
@@ -0,0 +1,57 @@
1
+ module Varnish
2
+ # Wrapper around Ruby's Socket.
3
+ #
4
+ # Uses Mike Perhams superior (in both reliability and
5
+ # performance) connection technique with proper timeouts:
6
+ # See: http://github.com/mperham/memcache-client
7
+ class SocketFactory
8
+
9
+ begin
10
+ # Try to use the SystemTimer gem instead of Ruby's timeout library
11
+ # when running on something that looks like Ruby 1.8.x. See:
12
+ # http://ph7spot.com/articles/system_timer
13
+ # We don't want to bother trying to load SystemTimer on jruby and
14
+ # ruby 1.9+.
15
+ if !defined?(RUBY_ENGINE)
16
+ require 'system_timer'
17
+ Timer = SystemTimer
18
+ else
19
+ require 'timeout'
20
+ Timer = Timeout
21
+ end
22
+ rescue LoadError => e
23
+ $stderr.puts "[klarlack] Could not load SystemTimer gem, falling back to Ruby's slower/unsafe timeout library: #{e.message}"
24
+ require 'timeout'
25
+ Timer = Timeout
26
+ end
27
+
28
+ def self.tcp_socket(host, port, timeout = nil)
29
+ addr = Socket.getaddrinfo(host, nil)
30
+ sock = Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)
31
+
32
+ if timeout
33
+ secs = Integer(timeout)
34
+ usecs = Integer((timeout - secs) * 1_000_000)
35
+ optval = [secs, usecs].pack("l_2")
36
+ sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
37
+ sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
38
+
39
+ # Socket timeouts don't work for more complex IO operations
40
+ # like gets which lay on top of read. We need to fall back to
41
+ # the standard Timeout mechanism.
42
+ sock.instance_eval <<-EOR
43
+ alias :blocking_gets :gets
44
+ def gets
45
+ Timer.timeout(#{timeout}) do
46
+ self.blocking_gets
47
+ end
48
+ end
49
+ EOR
50
+ end
51
+ sock.connect(Socket.pack_sockaddr_in(port, addr[0][3]))
52
+ sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
53
+ sock
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,154 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe Varnish::Client do
4
+
5
+ before(:each) do
6
+ @varnish = Varnish::Client.new "127.0.0.1:6082"
7
+ end
8
+
9
+ describe '(connection handling)' do
10
+
11
+ it 'should not be connected on object instantiation' do
12
+ @varnish.connected?.should be_false
13
+ end
14
+
15
+ it 'should not raise an error when trying to disconnect a non-connected client' do
16
+ lambda { @varnish.disconnect }.should_not raise_error
17
+ end
18
+
19
+ it 'should automatically connect when a command is issued' do
20
+ @varnish.ping
21
+ @varnish.connected?.should be_true
22
+ end
23
+
24
+ it 'should use timeouts when sending commands' do
25
+ Varnish::SocketFactory::Timer.should_receive(:timeout).and_return("200 0")
26
+ @varnish.timeout = 10
27
+ @varnish.ping
28
+ end
29
+
30
+ it 'should be possible to disable timeouts' do
31
+ Varnish::SocketFactory::Timer.should_not_receive(:timeout)
32
+ @varnish.timeout = nil
33
+ @varnish.ping
34
+ end
35
+
36
+ it '#disconnect should close the connection' do
37
+ @varnish.ping
38
+ @varnish.connected?.should be_true
39
+ @varnish.disconnect
40
+ @varnish.connected?.should be_false
41
+ end
42
+
43
+ it 'given keep_alive is set, the connection should be kept alive with pings' do
44
+ @varnish.keep_alive = true
45
+ @varnish.should_receive :ping
46
+ @varnish.send :connect
47
+ end
48
+
49
+ it 'given keep_alive is not set, no pings should be sent to varnishd' do
50
+ @varnish.keep_alive = false
51
+ @varnish.should_not_receive :ping
52
+ @varnish.send :connect
53
+ end
54
+
55
+ it '#server should return the host and port for the connection' do
56
+ @varnish.host = "foohost"
57
+ @varnish.port = 1234
58
+ @varnish.server.should == "foohost:1234"
59
+ end
60
+
61
+ it '#server= should set the host and port for the connection' do
62
+ @varnish.server = "blahost:9876"
63
+ @varnish.host.should == "blahost"
64
+ @varnish.port.should == 9876
65
+ end
66
+
67
+ end
68
+
69
+ describe '(commands)' do
70
+
71
+ before(:each) do
72
+ ensure_started
73
+ end
74
+
75
+ # ... the specs for #param, #purge and #vcl could be better ...
76
+
77
+ it '#param should send the param command to varnishd' do
78
+ @varnish.param(:show).should_not be_empty
79
+ end
80
+
81
+ it '#purge should allow purging by url, hash and custom fields' do
82
+ @varnish.purge(:url, '^/articles/.*').should be_true
83
+ @varnish.purge(:hash, 12345).should be_true
84
+ @varnish.purge("req.http.host", "~", "www.example.com").should be_true
85
+ end
86
+
87
+ it '#purge with :list should return an array with queued purges' do
88
+ @varnish.purge(:url, '^/posts/.*')
89
+ list = @varnish.purge(:list)
90
+ list.last[0].should be_kind_of(Integer)
91
+ list.last[1].should == "req.url ~ ^/posts/.*"
92
+ end
93
+
94
+ it '#vcl with :list should return an array of VCL configurations' do
95
+ list = @varnish.vcl(:list)
96
+ list.should_not be_empty
97
+ list.should be_kind_of(Array)
98
+ list.first[0].should be_kind_of(String)
99
+ list.first[1].should be_kind_of(Integer)
100
+ list.first[2].should be_kind_of(String)
101
+ end
102
+
103
+ it '#ping should send a ping to the server and return a string containing the response' do
104
+ @varnish.ping.should =~ /^PONG \d+/
105
+ end
106
+
107
+
108
+ it '#status should return a string explaining the daemons status' do
109
+ @varnish.status.should =~ /running|stopped|stopping|starting/
110
+ end
111
+
112
+ it "#stats should return a hash containing status information" do
113
+ stats = @varnish.stats
114
+ stats.should_not be_empty
115
+ stats.values.each {|v| v.should be_kind_of(Integer) }
116
+ stats.keys.each {|k| k.should_not be_empty }
117
+ end
118
+
119
+ end
120
+
121
+ describe '(daemon lifecycle)' do
122
+
123
+ it '#start, #stop, #running?, #stopped? should bahave as advertised' do
124
+ ensure_stopped # issues #stop
125
+ @varnish.stopped?.should be_true
126
+ @varnish.running?.should be_false
127
+ ensure_started # issues #start
128
+ @varnish.stopped?.should be_false
129
+ @varnish.running?.should be_true
130
+ end
131
+
132
+ it 'starting an already started daemon should raise an error' do
133
+ ensure_started
134
+ lambda { @varnish.start }.should raise_error(Varnish::Error)
135
+ end
136
+
137
+ it 'stopping an already stopped daemon should raise an error' do
138
+ ensure_stopped
139
+ lambda { @varnish.stop }.should raise_error(Varnish::Error)
140
+ end
141
+
142
+ end
143
+
144
+ def ensure_started
145
+ @varnish.start if @varnish.stopped?
146
+ while(!@varnish.running?) do sleep 0.1 end
147
+ end
148
+
149
+ def ensure_stopped
150
+ @varnish.stop if @varnish.running?
151
+ while(!@varnish.stopped?) do sleep 0.1 end
152
+ end
153
+
154
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec'
2
+
3
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
4
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
5
+ require 'klarlack'
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: klarlack
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - "Max Sch\xC3\xB6fmann"
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-04-24 00:00:00 +02:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: max@pragmatic-it.de
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - LICENSE
24
+ - README.rdoc
25
+ files:
26
+ - LICENSE
27
+ - README.rdoc
28
+ - Rakefile
29
+ - VERSION.yml
30
+ - lib/klarlack.rb
31
+ - lib/varnish/client.rb
32
+ - lib/varnish/socket_factory.rb
33
+ - spec/klarlack_spec.rb
34
+ - spec/spec_helper.rb
35
+ has_rdoc: true
36
+ homepage: http://github.com/schoefmax/klarlack
37
+ licenses: []
38
+
39
+ post_install_message:
40
+ rdoc_options:
41
+ - --charset=UTF-8
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: "0"
49
+ version:
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ version:
56
+ requirements: []
57
+
58
+ rubyforge_project:
59
+ rubygems_version: 1.3.5
60
+ signing_key:
61
+ specification_version: 2
62
+ summary: ruby client for varnishd's admin interface
63
+ test_files:
64
+ - spec/klarlack_spec.rb
65
+ - spec/spec_helper.rb