19
32
default_squid3_config_dir = "/etc/squid3"
20
33
default_squid3_config = "%s/squid.conf" % default_squid3_config_dir
21
34
default_squid3_config_cache_dir = "/var/run/squid3"
22
hook_name = os.path.basename(sys.argv[0])
35
default_nagios_plugin_dir = "/usr/lib/nagios/plugins"
36
Server = collections.namedtuple("Server", "name address port options")
37
service_affecting_packages = ['squid3']
24
39
###############################################################################
25
40
# Supporting functions
26
41
###############################################################################
29
#------------------------------------------------------------------------------
30
# config_get: Returns a dictionary containing all of the config information
31
# Optional parameter: scope
32
# scope: limits the scope of the returned configuration to the
33
# desired config item.
34
#------------------------------------------------------------------------------
35
def config_get(scope=None):
37
config_cmd_line = ['config-get']
39
config_cmd_line.append(scope)
40
config_cmd_line.append('--format=json')
41
config_data = json.loads(subprocess.check_output(config_cmd_line))
43
subprocess.call(['juju-log', str(e)])
49
#------------------------------------------------------------------------------
50
# relation_json: Returns json-formatted relation data
51
# Optional parameters: scope, relation_id
52
# scope: limits the scope of the returned data to the
54
# unit_name: limits the data ( and optionally the scope )
55
# to the specified unit
56
# relation_id: specify relation id for out of context usage.
57
#------------------------------------------------------------------------------
58
def relation_json(scope=None, unit_name=None, relation_id=None):
60
relation_cmd_line = ['relation-get', '--format=json']
61
if relation_id is not None:
62
relation_cmd_line.extend(('-r', relation_id))
64
relation_cmd_line.append(scope)
66
relation_cmd_line.append('-')
67
relation_cmd_line.append(unit_name)
68
relation_data = subprocess.check_output(relation_cmd_line)
70
subprocess.call(['juju-log', str(e)])
76
#------------------------------------------------------------------------------
77
# relation_get: Returns a dictionary containing the relation information
78
# Optional parameters: scope, relation_id
79
# scope: limits the scope of the returned data to the
81
# unit_name: limits the data ( and optionally the scope )
82
# to the specified unit
83
# relation_id: specify relation id for out of context usage.
84
#------------------------------------------------------------------------------
85
def relation_get(scope=None, unit_name=None, relation_id=None):
87
relation_data = json.loads(relation_json())
89
subprocess.call(['juju-log', str(e)])
95
#------------------------------------------------------------------------------
96
# relation_ids: Returns a list of relation ids
97
# optional parameters: relation_type
98
# relation_type: return relations only of this type
99
#------------------------------------------------------------------------------
100
def relation_ids(relation_types=['website']):
101
# accept strings or iterators
102
if isinstance(relation_types, basestring):
103
reltypes = [relation_types]
105
reltypes = relation_types
107
for reltype in reltypes:
108
relid_cmd_line = ['relation-ids', '--format=json', reltype]
109
relids.extend(json.loads(subprocess.check_output(relid_cmd_line)))
113
#------------------------------------------------------------------------------
114
# relation_get_all: Returns a dictionary containing the relation information
115
# optional parameters: relation_type
116
# relation_type: limits the scope of the returned data to the
118
#------------------------------------------------------------------------------
119
def relation_get_all():
122
relids = relation_ids()
124
units_cmd_line = ['relation-list', '--format=json', '-r', relid]
125
units = json.loads(subprocess.check_output(units_cmd_line))
128
json.loads(relation_json(
129
relation_id=relid, unit_name=unit))
130
if 'sitenames' in reldata[unit]:
131
reldata[unit]['sitenames'] = \
132
reldata[unit]['sitenames'].split()
133
reldata[unit]['relation-id'] = relid
134
reldata[unit]['name'] = unit.replace("/", "_")
136
subprocess.call(['juju-log', str(e)])
142
#------------------------------------------------------------------------------
143
# apt_get_install( packages ): Installs a package
144
#------------------------------------------------------------------------------
145
def apt_get_install(packages=None):
148
cmd_line = ['apt-get', '-y', 'install', '-qq']
150
cmd_line.extend(packages.split())
151
except AttributeError:
152
cmd_line.extend(packages)
153
return(subprocess.call(cmd_line))
156
#------------------------------------------------------------------------------
157
# enable_squid3: Enable squid3 at boot time
158
#------------------------------------------------------------------------------
160
# squid is enabled at boot time
44
def get_id(sitename, relation_id, unit_name):
45
unit_name = unit_name.replace('/', '_').replace('.', '_')
46
relation_id = relation_id.replace(':', '_').replace('.', '_')
48
return relation_id + '__' + unit_name
49
return sitename.replace('.', '_') + '__' + relation_id + '__' + unit_name
52
def get_forward_sites():
54
for relid in get_relation_ids('cached-website'):
55
units = related_units(relid)
57
data = relation_get(rid=relid, unit=unit)
58
if 'sitenames' in data:
59
data['sitenames'] = data['sitenames'].split()
60
data['relation-id'] = relid
61
data['name'] = unit.replace("/", "_")
66
def get_reverse_sites():
69
config_data = config_get()
70
config_services = yaml.safe_load(config_data.get("services", "")) or ()
71
server_options_by_site = {}
73
for service_item in config_services:
74
site = service_item["service_name"]
75
servers = all_sites.setdefault(site, [])
76
options = " ".join(service_item.get("server_options", []))
77
server_options_by_site[site] = options
78
for host, port in service_item["servers"]:
80
Server(name=get_id(None, site, host),
81
address=host, port=port,
84
relations = relations_of_type("website")
86
for relation_data in relations:
87
unit = relation_data["__unit__"]
88
relation_id = relation_data["__relid__"]
89
if not ("port" in relation_data or "all_services" in relation_data):
90
log("No port in relation data for '%s', skipping." % unit)
93
if not "private-address" in relation_data:
94
log("No private-address in relation data "
95
"for '%s', skipping." % unit)
98
for site in relation_data.get("sitenames", "").split():
99
servers = all_sites.setdefault(site, [])
101
Server(name=get_id(site, relation_id, unit),
102
address=relation_data["private-address"],
103
port=relation_data["port"],
104
options=server_options_by_site.get(site, '')))
106
services = yaml.safe_load(relation_data.get("all_services", "")) or ()
107
for service_item in services:
108
site = service_item["service_name"]
109
servers = all_sites.setdefault(site, [])
111
Server(name=get_id(site, relation_id, unit),
112
address=relation_data["private-address"],
113
port=service_item["service_port"],
114
options=server_options_by_site.get(site, '')))
116
if not ("sitenames" in relation_data or
117
"all_services" in relation_data):
118
servers = all_sites.setdefault(None, [])
120
Server(name=get_id(None, relation_id, unit),
121
address=relation_data["private-address"],
122
port=relation_data["port"],
123
options=server_options_by_site.get(None, '')))
125
if len(all_sites) == 0:
128
for site, servers in all_sites.iteritems():
134
def ensure_package_status(packages, status):
135
if status in ['install', 'hold']:
136
selections = ''.join(['{} {}\n'.format(package, status)
137
for package in packages])
138
dpkg = subprocess.Popen(['dpkg', '--set-selections'],
139
stdin=subprocess.PIPE)
140
dpkg.communicate(input=selections)
164
143
#------------------------------------------------------------------------------
257
213
def construct_squid3_config():
258
214
from jinja2 import Environment, FileSystemLoader
259
215
config_data = config_get()
260
relations = relation_get_all()
216
reverse_sites = get_reverse_sites()
218
if reverse_sites is not None:
219
for site, servers in reverse_sites.iteritems():
221
only_direct.add(site)
261
223
if config_data['refresh_patterns']:
262
refresh_patterns = json.loads(config_data['refresh_patterns'])
224
refresh_patterns = yaml.safe_load(config_data['refresh_patterns'])
264
226
refresh_patterns = {}
265
template_env = Environment(loader=FileSystemLoader(
266
os.path.join(os.environ['CHARM_DIR'], 'templates')))
227
# Use default refresh pattern if specified.
228
if '.' in refresh_patterns:
229
default_refresh_pattern = refresh_patterns.pop('.')
231
default_refresh_pattern = {
238
templates_dir = os.path.join(os.environ['CHARM_DIR'], 'templates')
239
template_env = Environment(loader=FileSystemLoader(templates_dir))
267
241
config_data['cache_l1'] = int(math.ceil(math.sqrt(
268
int(config_data['cache_size_mb']) * 1024 / (16 *
269
int(config_data['target_objs_per_dir']) * int(
270
config_data['avg_obj_size_kb'])))))
242
int(config_data['cache_size_mb']) * 1024 / (
243
16 * int(config_data['target_objs_per_dir']) * int(
244
config_data['avg_obj_size_kb'])))))
271
245
config_data['cache_l2'] = config_data['cache_l1'] * 16
273
247
'config': config_data,
274
'relations': relations,
248
'sites': reverse_sites,
249
'forward_relations': get_forward_sites(),
250
'only_direct': only_direct,
275
251
'refresh_patterns': refresh_patterns,
252
'default_refresh_pattern': default_refresh_pattern,
277
254
template = template_env.get_template('main_config.template').\
278
255
render(templ_vars)
256
write_squid3_config('\n'.join(
257
(l.strip() for l in str(template).splitlines())))
260
def write_squid3_config(contents):
279
261
with open(default_squid3_config, 'w') as squid3_config:
280
squid3_config.write(str(template))
262
squid3_config.write(contents)
283
265
#------------------------------------------------------------------------------
286
268
#------------------------------------------------------------------------------
287
269
def service_squid3(action=None, squid3_config=default_squid3_config):
288
270
if action is None or squid3_config is None:
290
272
elif action == "check":
291
273
check_cmd = ['/usr/sbin/squid3', '-f', squid3_config, '-k', 'parse']
292
274
retVal = subprocess.call(check_cmd)
295
277
elif retVal == 0:
299
281
elif action == 'status':
300
282
status = subprocess.check_output(['status', 'squid3'])
301
283
if re.search('running', status) is not None:
305
287
elif action in ('start', 'stop', 'reload', 'restart'):
306
288
retVal = subprocess.call([action, 'squid3'])
295
def install_nrpe_scripts():
296
if not os.path.exists(default_nagios_plugin_dir):
297
os.makedirs(default_nagios_plugin_dir)
298
shutil.copy2('%s/files/check_squidpeers' % (
299
os.environ['CHARM_DIR']),
300
'{}/check_squidpeers'.format(default_nagios_plugin_dir))
313
303
def update_nrpe_checks():
314
config_data = config_get()
316
nagios_uid = pwd.getpwnam('nagios').pw_uid
317
nagios_gid = grp.getgrnam('nagios').gr_gid
319
subprocess.call(['juju-log', "Nagios user not setup, exiting"])
322
unit_name = os.environ['JUJU_UNIT_NAME'].replace('/', '-')
323
nrpe_check_file = '/etc/nagios/nrpe.d/check_squidrp.cfg'
324
nagios_hostname = "%s-%s" % (config_data['nagios_context'], unit_name)
325
nagios_logdir = '/var/log/nagios'
326
nagios_exportdir = '/var/lib/nagios/export'
327
nrpe_service_file = \
328
'/var/lib/nagios/export/service__%s_check_squidrp.cfg' % \
330
if not os.path.exists(nagios_logdir):
331
os.mkdir(nagios_logdir)
332
os.chown(nagios_logdir, nagios_uid, nagios_gid)
333
if not os.path.exists(nagios_exportdir):
334
subprocess.call(['juju-log', 'Exiting as %s is not accessible' %
337
for f in os.listdir(nagios_exportdir):
338
if re.search('.*check_squidrp.cfg', f):
339
os.remove(os.path.join(nagios_exportdir, f))
340
from jinja2 import Environment, FileSystemLoader
341
template_env = Environment(
342
loader=FileSystemLoader(os.path.join(os.environ['CHARM_DIR'], 'templates')))
344
'nagios_hostname': nagios_hostname,
345
'nagios_servicegroup': config_data['nagios_context'],
347
template = template_env.get_template('nrpe_service.template').\
349
with open(nrpe_service_file, 'w') as nrpe_service_config:
350
nrpe_service_config.write(str(template))
351
with open(nrpe_check_file, 'w') as nrpe_check_config:
352
nrpe_check_config.write("# check squid\n")
353
nrpe_check_config.write(
354
"command[check_squidrp]=/usr/lib/nagios/plugins/check_http %s\n" %
355
(config_data['nagios_check_http_params']))
356
if os.path.isfile('/etc/init.d/nagios-nrpe-server'):
357
subprocess.call(['service', 'nagios-nrpe-server', 'reload'])
304
install_nrpe_scripts()
306
conf = nrpe_compat.config
307
nrpe_compat.add_check(
308
shortname='squidpeers',
309
description='Check Squid Peers',
310
check_cmd='check_squidpeers'
312
check_http_params = conf.get('nagios_check_http_params')
313
if check_http_params:
314
nrpe_compat.add_check(
316
description='Check Squid',
317
check_cmd='check_http %s' % check_http_params
319
config_services_str = conf.get('services', '')
320
config_services = yaml.safe_load(config_services_str) or ()
321
for service in config_services:
322
path = service.get('nrpe_check_path')
324
command = 'check_http -I 127.0.0.1 -p 3128 --method=HEAD '
325
service_name = service['service_name']
326
if conf.get('x_balancer_name_allowed'):
327
command += ("-u http://localhost%s "
328
"-k \\'X-Balancer-Name: %s\\'" % (
331
command += "-u http://%s%s" % (service_name, path)
332
nrpe_compat.add_check(
333
shortname='squid-%s' % service_name.replace(".", "_"),
334
description='Check Squid for site %s' % service_name,
340
def install_packages():
341
apt_install("squid3 squidclient python-jinja2".split(), fatal=True)
342
ensure_package_status(service_affecting_packages,
343
config_get('package_status'))
360
346
###############################################################################
362
348
###############################################################################
363
349
def install_hook():
364
for f in glob.glob('exec.d/*/charm-pre-install'):
365
if os.path.isfile(f) and os.access(f, os.X_OK):
366
subprocess.check_call(['sh', '-c', f])
367
350
if not os.path.exists(default_squid3_config_dir):
368
351
os.mkdir(default_squid3_config_dir, 0600)
369
352
if not os.path.exists(default_squid3_config_cache_dir):
370
353
os.mkdir(default_squid3_config_cache_dir, 0600)
371
shutil.copy2('%s/files/default.squid3' % (os.environ['CHARM_DIR']), '/etc/default/squid3')
372
return (apt_get_install(
373
"squid3 python-jinja2") == enable_squid3() is not True)
354
shutil.copy2('%s/files/default.squid3' % (
355
os.environ['CHARM_DIR']), '/etc/default/squid3')
376
360
def config_changed():
377
current_service_ports = get_service_ports()
361
old_service_ports = get_service_ports()
362
old_sitenames = get_sitenames()
378
363
construct_squid3_config()
379
364
update_nrpe_checks()
365
ensure_package_status(service_affecting_packages,
366
config_get('package_status'))
381
368
if service_squid3("check"):
382
369
updated_service_ports = get_service_ports()
383
update_service_ports(current_service_ports, updated_service_ports)
370
update_service_ports(old_service_ports, updated_service_ports)
384
371
service_squid3("reload")
372
if not (old_sitenames == get_sitenames()):
373
notify_cached_website()
375
# XXX Ideally the config should be restored to a working state if the
376
# check fails, otherwise an inadvertent reload will cause the service
378
log("squid configuration check failed, exiting")
389
382
def start_hook():
390
383
if service_squid3("status"):
391
return(service_squid3("restart"))
384
return service_squid3("restart")
393
return(service_squid3("start"))
386
return service_squid3("start")
397
390
if service_squid3("status"):
398
return(service_squid3("stop"))
391
return service_squid3("stop")
401
394
def website_interface(hook_name=None):
402
395
if hook_name is None:
404
if hook_name in ["joined", "changed", "broken", "departed"]:
397
if hook_name in ("joined", "changed", "broken", "departed"):
408
401
def cached_website_interface(hook_name=None):
409
402
if hook_name is None:
411
if hook_name in ["joined", "changed"]:
412
sitenames = ' '.join(get_sitenames())
413
# passing only one port - the first one defined
414
subprocess.call(['relation-set', 'port=%s' % get_service_ports()[0],
415
'sitenames=%s' % sitenames])
404
if hook_name in ("joined", "changed"):
405
notify_cached_website(relation_ids=(None,))
406
config_data = config_get()
407
if config_data['enable_forward_proxy']:
411
def get_hostname(host=None):
412
my_host = socket.gethostname()
413
if host is None or host == "0.0.0.0":
414
# If the listen ip has been set to 0.0.0.0 then pass back the hostname
415
return socket.getfqdn(my_host)
416
elif host == "localhost":
417
# If the fqdn lookup has returned localhost (lxc setups) then return
423
def notify_cached_website(relation_ids=None):
424
hostname = get_hostname()
425
# passing only one port - the first one defined
426
port = get_service_ports()[0]
427
sitenames = ' '.join(get_sitenames())
429
for rid in relation_ids or get_relation_ids("cached-website"):
430
relation_set(relation_id=rid, port=port,
436
# Ensure that all current dependencies are installed.
418
440
###############################################################################
420
442
###############################################################################
422
444
if hook_name == "install":
424
446
elif hook_name == "config-changed":
426
449
elif hook_name == "start":
428
451
elif hook_name == "stop":
453
elif hook_name == "upgrade-charm":
431
458
elif hook_name == "website-relation-joined":
432
459
website_interface("joined")