Skip to content

Commit 7cbda36

Browse files
committed
docstrings, reorganizations and http purges
1 parent 12590e5 commit 7cbda36

File tree

2 files changed

+190
-51
lines changed

2 files changed

+190
-51
lines changed

runtests.py

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,35 @@
11
from varnish import VarnishManager
22
import unittest
33

4-
ADDR = raw_input('Varnish Management Address (ip:port): ')
4+
ADDR = raw_input('Varnish Management Address (ip:port) [localhost:2000]: ')
5+
if not ADDR: ADDR = 'localhost:2000'
6+
WEB_ADDR = raw_input('Varnish Instance Address (ip:port) [localhost:8080]: ')
7+
if not WEB_ADDR: WEB_ADDR = 'localhost:8080'
58

69
class VarnishTests(unittest.TestCase):
710
def setUp(self):
811
self.manager = VarnishManager((ADDR,))
9-
12+
1013
def test_ping(self):
1114
result = self.manager.run('ping')[0][0]
1215
self.assertEqual(len(result), 2)
1316
self.assert_(map(lambda x: isinstance(x, float), (True,True)))
14-
15-
def test_threading(self):
16-
self.manager.run(('purge.url', '^/myrandomurl/$'), threaded=True)
17-
self.assert_(self.manager.run('purge.list')[0][0].endswith('^/myrandomurl/$\n'))
18-
19-
def test_stats(self):
20-
self.assert_(isinstance(self.manager.run('stats')[0][0], dict))
21-
17+
18+
def test_purge(self):
19+
self.assertEqual(self.manager.purge_url(
20+
'http://%s/myrandomurl/.*' % WEB_ADDR).status, 200)
21+
22+
def test_ban(self):
23+
regex = '^/banned/*'
24+
self.manager.run('ban.url', regex)
25+
self.assert_(regex, str(self.manager.run('ban.list')))
26+
2227
def test_multiple(self):
2328
result = self.manager.run(( ('ping',None),('ping',None) ))
2429
self.assertEqual(result[0][0], result[0][1])
25-
30+
2631
def tearDown(self):
2732
self.manager.close()
28-
33+
2934
if __name__ == '__main__':
3035
unittest.main()
31-

varnish.py

Lines changed: 173 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,43 @@
11
"""
22
Simple Python interface for the Varnish management port.
3+
4+
Tested against
5+
Varnish v3.0.2
6+
Varnish Cache CLI 1.0
7+
8+
Supports the following commands
9+
10+
help [command]
11+
ping [timestamp]
12+
auth response
13+
quit
14+
status
15+
start
16+
stop
17+
vcl.load <configname> <filename>
18+
vcl.inline <configname> <quoted_VCLstring>
19+
vcl.use <configname>
20+
vcl.discard <configname>
21+
vcl.list
22+
vcl.show <configname>
23+
param.show [-l] [<param>]
24+
param.set <param> <value>
25+
ban.url <regexp>
26+
ban <field> <operator> <arg> [&& <field> <oper> <arg>]...
27+
ban.list
28+
29+
Also VarnishManager.purge will do HTTP purges. See below for configuration details
30+
31+
https://www.varnish-cache.org/docs/3.0/tutorial/purging.html
32+
333
"""
434
from telnetlib import Telnet
535
from threading import Thread
36+
from httplib import HTTPConnection
37+
from urlparse import urlparse
638
import logging
739

40+
841
logging.basicConfig(
942
level = logging.DEBUG,
1043
format = '%(asctime)s %(levelname)s %(message)s',
@@ -27,14 +60,6 @@ def _read(self):
2760
content += self.read_some()
2861
return (status, length), content[:-1]
2962

30-
def quit(self): self.close()
31-
32-
def auth(self, secret, content):
33-
challenge = content[:32]
34-
response = sha256('%s\n%s\n%s\n' % (challenge, secret, challenge))
35-
response_str = 'auth %s' % response.hexdigest()
36-
self.fetch(response_str)
37-
3863
def fetch(self, command):
3964
"""
4065
Run a command on the Varnish backend and return the result
@@ -43,9 +68,9 @@ def fetch(self, command):
4368
logging.debug('SENT: %s: %s' % (self.host, command))
4469
self.write('%s\n' % command)
4570
while 1:
46-
buffer = self.read_until('\n').strip()
47-
if len(buffer):
48-
break
71+
buffer = self.read_until('\n').strip()
72+
if len(buffer):
73+
break
4974
status, length = map(int, buffer.split())
5075
content = ''
5176
assert status == 200, 'Bad response code: {status} {text} ({command})'.format(status=status, text=self.read_until('\n').strip(), command=command)
@@ -56,75 +81,166 @@ def fetch(self, command):
5681
return (status, length), content
5782

5883
# Service control methods
59-
def start(self): return self.fetch('start')
60-
def stop(self): return self.fetch('stop')
84+
def start(self):
85+
"""start Start the Varnish cache process if it is not already running."""
86+
return self.fetch('start')
87+
88+
def stop(self):
89+
"""stop Stop the Varnish cache process."""
90+
return self.fetch('stop')
91+
92+
def quit(self):
93+
"""quit Close the connection to the varnish admin port."""
94+
return self.close()
95+
96+
def auth(self, secret, content):
97+
challenge = content[:32]
98+
response = sha256('%s\n%s\n%s\n' % (challenge, secret, challenge))
99+
response_str = 'auth %s' % response.hexdigest()
100+
self.fetch(response_str)
61101

62102
# Information methods
63103
def ping(self, timestamp=None):
104+
"""
105+
ping [timestamp]
106+
Ping the Varnish cache process, keeping the connection alive.
107+
"""
64108
cmd = 'ping'
65109
if timestamp: cmd += ' %s' % timestamp
66110
return tuple(map(float, self.fetch(cmd)[1].split()[1:]))
67111

68-
def stats(self):
69-
stat = {}
70-
for line in self.fetch('stats')[1].splitlines():
71-
a = line.split()
72-
stat['_'.join(a[1:]).lower()] = int(a[0])
73-
return stat
112+
def status(self):
113+
"""status Check the status of the Varnish cache process."""
114+
return self.fetch('status')[1]
74115

75116
def help(self, command=None):
117+
"""
118+
help [command]
119+
Display a list of available commands.
120+
If the command is specified, display help for this command.
121+
"""
76122
cmd = 'help'
77123
if command: cmd += ' %s' % command
78124
return self.fetch(cmd)[1]
79125

80126
# VCL methods
81127
def vcl_load(self, configname, filename):
128+
"""
129+
vcl.load configname filename
130+
Create a new configuration named configname with the contents of the specified file.
131+
"""
82132
return self.fetch('vcl.load %s %s' % (configname, filename))
83133

84134
def vcl_inline(self, configname, vclcontent):
135+
"""
136+
vcl.inline configname vcl
137+
Create a new configuration named configname with the VCL code specified by vcl, which must be a
138+
quoted string.
139+
"""
85140
return self.fetch('vcl.inline %s %s' % (configname, vclcontent))
86141

87142
def vcl_show(self, configname):
143+
"""
144+
vcl.show configname
145+
Display the source code for the specified configuration.
146+
"""
88147
return self.fetch('vcl.show' % configname)
89148

90149
def vcl_use(self, configname):
150+
"""
151+
vcl.use configname
152+
Start using the configuration specified by configname for all new requests. Existing requests
153+
will coninue using whichever configuration was in use when they arrived.
154+
"""
91155
return self.fetch('vcl.use %s' % configname)
92156

93157
def vcl_discard(self, configname):
158+
"""
159+
vcl.discard configname
160+
Discard the configuration specified by configname. This will have no effect if the specified
161+
configuration has a non-zero reference count.
162+
"""
94163
return self.fetch('vcl.discard %s' % configname)
95164

96165
def vcl_list(self):
166+
"""
167+
vcl.list
168+
List available configurations and their respective reference counts. The active configuration
169+
is indicated with an asterisk ("*").
170+
"""
97171
vcls = {}
98172
for line in self.fetch('vcl.list')[1].splitlines():
99173
a = line.split()
100174
vcls[a[2]] = tuple(a[:-1])
101175
return vcls
102176

103177
# Param methods
104-
def param_show(self, param, long=False):
178+
def param_show(self, param, l=False):
179+
"""
180+
param.show [-l] [param]
181+
Display a list if run-time parameters and their values.
182+
If the -l option is specified, the list includes a brief explanation of each parameter.
183+
If a param is specified, display only the value and explanation for this parameter.
184+
"""
105185
cmd = 'param.show '
106-
if long: cmd += '-l '
186+
if l: cmd += '-l '
107187
return self.fetch(cmd + param)
108188

109189
def param_set(self, param, value):
190+
"""
191+
param.set param value
192+
Set the parameter specified by param to the specified value. See Run-Time Parameters for a list
193+
of paramea ters.
194+
"""
110195
self.fetch('param.set %s %s' % (param, value))
111196

112-
# Purge methods
197+
# Ban methods
198+
def ban(self, expression):
199+
"""
200+
ban field operator argument [&& field operator argument [...]]
201+
Immediately invalidate all documents matching the ban expression. See Ban Expressions for more
202+
documentation and examples.
203+
"""
204+
return self.fetch('ban %s' % expression)[1]
205+
113206
def ban_url(self, regex):
207+
"""
208+
ban.url regexp
209+
Immediately invalidate all documents whose URL matches the specified regular expression. Please
210+
note that the Host part of the URL is ignored, so if you have several virtual hosts all of them
211+
will be banned. Use ban to specify a complete ban if you need to narrow it down.
212+
"""
114213
return self.fetch('ban.url %s' % regex)[1]
115-
116-
def purge_url(self, regex):
117-
return self.fetch('purge.url %s' % regex)[1]
118214

119-
def purge_hash(self, regex):
120-
return self.fetch('purge.hash %s' % regex)[1]
215+
def ban_list(self):
216+
"""
217+
ban.list
218+
All requests for objects from the cache are matched against items on the ban list. If an object
219+
in the cache is older than a matching ban list item, it is considered "banned", and will be
220+
fetched from the backend instead.
221+
222+
When a ban expression is older than all the objects in the cache, it is removed from the list.
223+
224+
ban.list displays the ban list. The output looks something like this (broken into two lines):
225+
226+
0x7fea4fcb0580 1303835108.618863 131G req.http.host ~ www.myhost.com && req.url ~ /some/url
227+
228+
The first field is the address of the ban.
229+
230+
The second is the time of entry into the list, given as a high precision timestamp.
231+
232+
The third field describes many objects point to this ban. When an object is compared to a ban
233+
the object is marked with a reference to the newest ban it was tested against. This isn't really
234+
useful unless you're debugging.
235+
236+
A "G" marks that the ban is "Gone". Meaning it has been marked as a duplicate or it is no longer
237+
valid. It stays in the list for effiency reasons.
238+
239+
Then follows the actual ban it self.
240+
"""
241+
return self.fetch('ban.list')[1]
121242

122-
def purge_list(self):
123-
return self.fetch('purge.list')[1]
124243

125-
def purge(self, *args):
126-
for field, operator, arg in args:
127-
self.fetch('purge %s %s %s\n' % (field, operator, arg))[1]
128244

129245
class ThreadedRunner(Thread):
130246
"""
@@ -163,17 +279,36 @@ def run(addr, *commands, **kwargs):
163279
class VarnishManager(object):
164280
def __init__(self, servers):
165281
if not len(servers):
166-
print 'WARNING: No servers found, please declare some'
282+
logging.warn('No servers found, please declare some')
167283
self.servers = servers
168284

169285
def run(self, *commands, **kwargs):
170-
if kwargs.pop('threaded', False):
171-
[ThreadedRunner(server, *commands, **kwargs).start() for server in self.servers]
172-
else:
173-
return [run(server, *commands, **kwargs) for server in self.servers]
286+
threaded = kwargs.pop('threaded', False)
287+
for server in self.servers:
288+
if threaded:
289+
[ThreadedRunner(server, *commands, **kwargs).start()
290+
for server in self.servers]
291+
else:
292+
return [run(server, *commands, **kwargs)
293+
for server in self.servers]
174294

175-
def help(self, *args): return run(self.servers[0], *('help',)+args)[0]
295+
def help(self, *args):
296+
return run(self.servers[0], *('help',)+args)[0]
176297

177298
def close(self):
178299
self.run('close', threaded=True)
179300
self.servers = ()
301+
302+
def purge_url(self, url):
303+
"""
304+
Do an HTTP PURGE of the given asset.
305+
The URL is run through urlparse and must point to the varnish instance not the varnishadm
306+
"""
307+
url = urlparse(url)
308+
connection = HTTPConnection(url.hostname, url.port or 80)
309+
connection.request('PURGE', '%s?%s' % (url.path or '/', url.query), '',
310+
{'Host': url.hostname})
311+
response = connection.getresponse()
312+
if response.status != 200:
313+
logging.error('Purge failed with status: %s' % response.status)
314+
return response

0 commit comments

Comments
 (0)