Merge lp:~stub/charms/precise/postgresql/syslog into lp:charms/postgresql

Proposed by Stuart Bishop
Status: Merged
Merged at revision: 88
Proposed branch: lp:~stub/charms/precise/postgresql/syslog
Merge into: lp:charms/postgresql
Prerequisite: lp:~stub/charms/precise/postgresql/bug-1278731-hot-standby-allowed-units
Diff against target: 271 lines (+147/-1)
7 files modified
README.md (+10/-0)
config.yaml (+1/-1)
hooks/hooks.py (+73/-0)
metadata.yaml (+2/-0)
templates/postgresql.conf.tmpl (+2/-0)
templates/rsyslog_forward.conf (+19/-0)
test.py (+40/-0)
To merge this branch: bzr merge lp:~stub/charms/precise/postgresql/syslog
Reviewer Review Type Date Requested Status
Marco Ceppi (community) Approve
Charles Butler (community) deployment + audit Approve
Review via email: mp+206176@code.launchpad.net

Description of the change

Ship PostgreSQL log files via syslog.

I'm not too familiar with other charms using syslog, in particular if I should be sending on port 10514 or 514, so I'd like a review from someone who has done work in this area.

The goal of this work is to connect PostgreSQL to reporting programs, and I'll be creating a pgBadger charm shortly that will consume the logs. For this use case, I also need to advertise the log_line_prefix that PostgreSQL uses to consumers, so they can decode the logs. I also added the programname required to tell logs from different units apart (IP address isn't reliable).

To post a comment you must log in.
175. By Stuart Bishop

Basic docs for syslog

Revision history for this message
Charles Butler (lazypower) wrote :

Stuart,

These modifications look fine to me. I've pulled them and deployed, setup a rather basic deployment test using rsyslog charm and the branch+prereq on HP-Cloud, AWS, and Local providers. All of which were properly forwarding the syslog. I haven't deployed the PGBadger service, however I see it as being ready in the queue, and I'll get to that next with additional commentary.

From what I've experienced in the past, forwarding over port 514 to your logging-nexus is perfectly acceptable. If you want to get into TLS wrapping your rsyslog forwarding, that's typically when you go to port 1999, so I'm a bit confused on the port 10514 dialogue above. Can you shed some light on why that was of a concern?

I'll be pushing this review to another charmer for an additional +1 review.

Thank you for the submission, keep up the excellent work!

review: Approve (deployment + audit)
Revision history for this message
Stuart Bishop (stub) wrote :

On 25 February 2014 02:27, Charles Butler <email address hidden> wrote:

> These modifications look fine to me. I've pulled them and deployed, setup a rather basic deployment test using rsyslog charm and the branch+prereq on HP-Cloud, AWS, and Local providers. All of which were properly forwarding the syslog. I haven't deployed the PGBadger service, however I see it as being ready in the queue, and I'll get to that next with additional commentary.
>
> >From what I've experienced in the past, forwarding over port 514 to your logging-nexus is perfectly acceptable. If you want to get into TLS wrapping your rsyslog forwarding, that's typically when you go to port 1999, so I'm a bit confused on the port 10514 dialogue above. Can you shed some light on why that was of a concern?

Its a concern as I'm cargo culting code from elsewhere, rather than
follow a specification. If there actually is a specification for the
syslog interface somewhere please let me know ;)

From what I can tell with the rsyslog and rsyslog-forarder charms, the
'open' port is 514 (TCP+UDP) but the 'closed' one used for syslog
forwarding is 10514 (TCP only). I also see nothing about TLS in those
charms, and nothing on port 1999.

--
Stuart Bishop <email address hidden>

Revision history for this message
Charles Butler (lazypower) wrote :

Ah, i reached out to my prior co-workers about the TLS security wrappers
and port 1999, that was a convention we coined. its not part of the spec.

Apparently getting transport security on our syslog forwarding constituted
partying like its 1999, hence the port chosen.

On Tue, Feb 25, 2014 at 12:42 AM, Stuart Bishop <<email address hidden>
> wrote:

> On 25 February 2014 02:27, Charles Butler <email address hidden>
> wrote:
>
> > These modifications look fine to me. I've pulled them and deployed,
> setup a rather basic deployment test using rsyslog charm and the
> branch+prereq on HP-Cloud, AWS, and Local providers. All of which were
> properly forwarding the syslog. I haven't deployed the PGBadger service,
> however I see it as being ready in the queue, and I'll get to that next
> with additional commentary.
> >
> > >From what I've experienced in the past, forwarding over port 514 to
> your logging-nexus is perfectly acceptable. If you want to get into TLS
> wrapping your rsyslog forwarding, that's typically when you go to port
> 1999, so I'm a bit confused on the port 10514 dialogue above. Can you shed
> some light on why that was of a concern?
>
> Its a concern as I'm cargo culting code from elsewhere, rather than
> follow a specification. If there actually is a specification for the
> syslog interface somewhere please let me know ;)
>
> >From what I can tell with the rsyslog and rsyslog-forarder charms, the
> 'open' port is 514 (TCP+UDP) but the 'closed' one used for syslog
> forwarding is 10514 (TCP only). I also see nothing about TLS in those
> charms, and nothing on port 1999.
>
>
> --
> Stuart Bishop <email address hidden>
>
>
> https://code.launchpad.net/~stub/charms/precise/postgresql/syslog/+merge/206176
> You are reviewing the proposed merge of
> lp:~stub/charms/precise/postgresql/syslog into lp:charms/postgresql.
>

Revision history for this message
Marco Ceppi (marcoceppi) wrote :

LGTM, +1

review: Approve
176. By Stuart Bishop

Merged bug-1278731-hot-standby-allowed-units into syslog.

177. By Stuart Bishop

Merged bug-1278731-hot-standby-allowed-units into syslog.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'README.md'
2--- README.md 2014-02-05 12:29:09 +0000
3+++ README.md 2014-02-13 13:49:35 +0000
4@@ -130,6 +130,16 @@
5 - `allowed-units`: space separated list of allowed clients (unit name). You
6 should check this to determine if you can connect to the database yet.
7
8+## During syslog-relation-changed
9+
10+### the postgresql service provides:
11+
12+- `programname`: the syslog 'programname' identifying this unit's
13+ PostgreSQL logs.
14+- `log_line_prefix`: the 'log_line_prefix' setting for the PostgreSQL
15+ service.
16+
17+
18 ## For replicated database support
19
20 A PostgreSQL service may contain multiple units (a single master, and
21
22=== modified file 'config.yaml'
23--- config.yaml 2014-01-23 11:41:03 +0000
24+++ config.yaml 2014-02-13 13:49:35 +0000
25@@ -90,7 +90,7 @@
26 type: boolean
27 description: Log disconnections
28 log_line_prefix:
29- default: "%t "
30+ default: "%t [%p]: [%l-1] db=%d,user=%u "
31 type: string
32 description: |
33 special values:
34
35=== modified file 'hooks/hooks.py'
36--- hooks/hooks.py 2014-02-13 13:49:35 +0000
37+++ hooks/hooks.py 2014-02-13 13:49:35 +0000
38@@ -14,6 +14,7 @@
39 import subprocess
40 import sys
41 from tempfile import NamedTemporaryFile
42+from textwrap import dedent
43 import time
44 import yaml
45 from yaml.constructor import ConstructorError
46@@ -482,6 +483,9 @@
47 config_file, pg_config,
48 owner="postgres", group="postgres", perms=0600)
49
50+ # Create or update files included from postgresql.conf.
51+ configure_log_destination(os.path.dirname(config_file))
52+
53 local_state['saved_config'] = config_data
54 local_state.save()
55
56@@ -999,6 +1003,11 @@
57 postgresql_restart()
58 postgresql_reload_or_restart()
59
60+ # In case the log_line_prefix has changed, inform syslog consumers.
61+ for relid in hookenv.relation_ids('syslog'):
62+ hookenv.relation_set(
63+ relid, log_line_prefix=hookenv.config('log_line_prefix'))
64+
65
66 @hooks.hook()
67 def install(run_pre=True):
68@@ -2194,6 +2203,69 @@
69 host.service_reload('nagios-nrpe-server')
70
71
72+@hooks.hook()
73+def syslog_relation_changed():
74+ configure_log_destination(_get_postgresql_config_dir())
75+ postgresql_reload()
76+
77+ # We extend the syslog interface by exposing the log_line_prefix.
78+ # This is required so consumers of the PostgreSQL logs can decode
79+ # them. Consumers not smart enough to cope with arbitrary prefixes
80+ # can at a minimum abort if they detect it is set to something they
81+ # cannot support. Similarly, inform the consumer of the programname
82+ # we are using so they can tell one units log messages from another.
83+ hookenv.relation_set(
84+ log_line_prefix=hookenv.config('log_line_prefix'),
85+ programname=sanitize(hookenv.local_unit()))
86+
87+ template_path = "{0}/templates/rsyslog_forward.conf".format(
88+ hookenv.charm_dir())
89+ rsyslog_conf = Template(open(template_path).read()).render(
90+ local_unit=sanitize(hookenv.local_unit()),
91+ raw_local_unit=hookenv.local_unit(),
92+ raw_remote_unit=hookenv.remote_unit(),
93+ remote_addr=hookenv.relation_get('private-address'))
94+ host.write_file(rsyslog_conf_path(hookenv.remote_unit()), rsyslog_conf)
95+ run(['service', 'rsyslog', 'restart'])
96+
97+
98+@hooks.hook()
99+def syslog_relation_departed():
100+ configure_log_destination(_get_postgresql_config_dir())
101+ postgresql_reload()
102+ os.unlink(rsyslog_conf_path(hookenv.remote_unit()))
103+ run(['service', 'rsyslog', 'restart'])
104+
105+
106+def configure_log_destination(config_dir):
107+ """Set the log_destination PostgreSQL config flag appropriately"""
108+ # We currently support either 'standard' logs (the files in
109+ # /var/log/postgresql), or syslog + 'standard' logs. This should
110+ # grow more complex in the future, as the local logs will be
111+ # redundant if you are using syslog for log aggregation, and we
112+ # probably want to add csvlog in the future. Note that csvlog
113+ # requires switching from 'Debian' log redirection and rotation to
114+ # the PostgreSQL builtin facilities.
115+ logdest_conf_path = os.path.join(config_dir, 'juju_logdest.conf')
116+ logdest_conf = open(logdest_conf_path, 'w')
117+ if hookenv.relation_ids('syslog'):
118+ # For syslog, we change the ident from the default of 'postgres'
119+ # to the unit name to allow remote services to easily identify
120+ # and filter which unit messages are from. We don't use IP
121+ # address for this as it is not necessarily unique.
122+ logdest_conf.write(dedent("""\
123+ log_destination='stderr,syslog'
124+ syslog_ident={0}
125+ """).format(sanitize(hookenv.local_unit())))
126+ else:
127+ open(logdest_conf_path, 'w').write("log_destination='stderr'")
128+
129+
130+def rsyslog_conf_path(remote_unit):
131+ return '/etc/rsyslog.d/juju-{0}-{1}.conf'.format(
132+ sanitize(hookenv.local_unit()), sanitize(remote_unit))
133+
134+
135 def _get_postgresql_config_dir(config_data=None):
136 """ Return the directory path of the postgresql configuration files. """
137 if config_data is None:
138@@ -2202,6 +2274,7 @@
139 cluster_name = config_data['cluster_name']
140 return os.path.join("/etc/postgresql", version, cluster_name)
141
142+
143 ###############################################################################
144 # Global variables
145 ###############################################################################
146
147=== added symlink 'hooks/syslog-relation-changed'
148=== target is u'hooks.py'
149=== added symlink 'hooks/syslog-relation-departed'
150=== target is u'hooks.py'
151=== modified file 'metadata.yaml'
152--- metadata.yaml 2013-10-08 22:44:28 +0000
153+++ metadata.yaml 2014-02-13 13:49:35 +0000
154@@ -26,6 +26,8 @@
155 requires:
156 persistent-storage:
157 interface: directory-path
158+ syslog:
159+ interface: syslog
160 peers:
161 replication:
162 interface: pgreplication
163
164=== modified file 'templates/postgresql.conf.tmpl'
165--- templates/postgresql.conf.tmpl 2014-01-30 16:44:33 +0000
166+++ templates/postgresql.conf.tmpl 2014-02-13 13:49:35 +0000
167@@ -88,6 +88,8 @@
168 # ERROR REPORTING AND LOGGING
169 #------------------------------------------------------------------------------
170
171+include 'juju_logdest.conf'
172+
173 {% if log_min_duration_statement != "" -%}
174 log_min_duration_statement = {{log_min_duration_statement}}
175 {% endif -%}
176
177=== added file 'templates/rsyslog_forward.conf'
178--- templates/rsyslog_forward.conf 1970-01-01 00:00:00 +0000
179+++ templates/rsyslog_forward.conf 2014-02-13 13:49:35 +0000
180@@ -0,0 +1,19 @@
181+##
182+## Managed by juju
183+##
184+
185+# This file is installed into /etc/rsyslog.d to forward the unit's
186+# PostgreSQL logs via syslog to another service.
187+
188+# This configuration is pretty dumb, and was cargo culted from the
189+# rsyslog-forwarder charm. The client really needs to inform the
190+# server what sort of service it requires (eg. reliability), but that
191+# involves extending and documenting the juju common syslog interface.
192+
193+$ActionQueueType LinkedList # use asynchronous processing
194+$ActionQueueFileName @@service@@ # set file name, also enables disk mode
195+$ActionResumeRetryCount -1 # infinite retries on insert failure
196+$ActionQueueSaveOnShutdown on # save in-memory data if rsyslog shuts down
197+
198+# Forward all PostgreSQL unit {{raw_local_unit}} logs to {{raw_remote_unit}}
199+if $programname == '{{local_unit}}' then @@{{remote_addr}}:10514
200
201=== modified file 'test.py'
202--- test.py 2014-02-13 13:49:35 +0000
203+++ test.py 2014-02-13 13:49:35 +0000
204@@ -14,6 +14,7 @@
205 import subprocess
206 import time
207 import unittest
208+import uuid
209
210 import fixtures
211 import psycopg2
212@@ -137,6 +138,10 @@
213 result = self.sql('SELECT TRUE')
214 self.assertEqual(result, [['t']])
215
216+ # Confirm that the relation tears down without errors.
217+ self.juju.do(['destroy-relation', 'postgresql:db', 'psql:db'])
218+ self.juju.wait_until_ready()
219+
220 def test_streaming_replication(self):
221 self.juju.deploy(
222 TEST_CHARM, 'postgresql', num_units=2, config=self.pg_config)
223@@ -163,6 +168,11 @@
224 result = self.sql('SELECT TRUE', dbname='postgres')
225 self.assertEqual(result, [['t']])
226
227+ # Confirm that the relation tears down without errors.
228+ self.juju.do([
229+ 'destroy-relation', 'postgresql:db-admin', 'psql:db-admin'])
230+ self.juju.wait_until_ready()
231+
232 def is_master(self, postgres_unit, dbname=None):
233 is_master = self.sql(
234 'SELECT NOT pg_is_in_recovery()',
235@@ -425,6 +435,36 @@
236 ''')
237 self.assertEqual(result, [['f', 'f', 'f']])
238
239+ def test_syslog(self):
240+ # Deploy 2 PostgreSQL units and 2 rsyslog units to ensure that
241+ # log messages from every source reach every sink.
242+ self.pg_config['log_min_duration_statement'] = 0 # Log all statements
243+ self.juju.deploy(
244+ TEST_CHARM, 'postgresql', num_units=2, config=self.pg_config)
245+ self.juju.deploy(PSQL_CHARM, 'psql')
246+ self.juju.do(['add-relation', 'postgresql:db', 'psql:db'])
247+ self.juju.deploy('cs:rsyslog', 'rsyslog', num_units=2)
248+ self.juju.do([
249+ 'add-relation', 'postgresql:syslog', 'rsyslog:aggregator'])
250+ self.juju.wait_until_ready()
251+
252+ token = str(uuid.uuid1())
253+
254+ self.sql("SELECT 'master {}'".format(token), 'master')
255+ self.sql("SELECT 'hot standby {}'".format(token), 'hot standby')
256+ time.sleep(2)
257+
258+ for runit in ['rsyslog/0', 'rsyslog/1']:
259+ cmd = ['juju', 'ssh', runit, 'tail -100 /var/log/syslog']
260+ out = run(self, cmd)
261+ self.failUnless('master {}'.format(token) in out)
262+ self.failUnless('hot standby {}'.format(token) in out)
263+
264+ # Confirm that the relation tears down correctly.
265+ self.juju.do([
266+ 'destroy-relation', 'postgresql:syslog', 'rsyslog:aggregator'])
267+ self.juju.wait_until_ready()
268+
269
270 class PG91Tests(
271 PostgreSQLCharmBaseTestCase,

Subscribers

People subscribed via source and target branches