klarlack 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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