~ubuntu-branches/ubuntu/trusty/juju-core/trusty-proposed

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package syslog

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"path/filepath"
	"strings"
	"text/template"
)

// tagOffset represents the substring start value for the tag to return
// the logfileName value from the syslogtag.  Substrings in syslog are
// indexed from 1, hence the + 1.
const tagOffset = len("juju-") + 1

// The rsyslog conf for state server nodes.
// Messages are gathered from other nodes and accumulated in an all-machines.log file.
//
// The apparmor profile is quite strict about where rsyslog can write files.
// Instead of poking with the profile, the local provider now logs to
// {{logDir}}-{{user}}-{{env name}}/all-machines.log, and a symlink is made
// in the local provider log dir to point to that file. By
// default rsyslog creates files with 0644, but in the ubuntu package, the
// setting is changed to 0640. Using a new action directive (new as in
// not-legacy), we can specify the file create mode so it doesn't use
// the default.
//
// I would dearly love to write the filtering action as follows to avoid setting
// and resetting the global $FileCreateMode, but alas, precise doesn't support it
//
// if $syslogtag startswith "juju{{namespace}}-" then
//   action(type="omfile"
//          File="{{logDir}}{{namespace}}/all-machines.log"
//          Template="JujuLogFormat{{namespace}}"
//          FileCreateMode="0644")
// & stop
//
// Instead we need to mess with the global FileCreateMode.  We set it back
// to the ubuntu default after defining our rule.
const stateServerRsyslogTemplate = `
$ModLoad imuxsock
$ModLoad imfile

# Messages received from remote rsyslog machines have messages prefixed with a space,
# so add one in for local messages too if needed.
$template JujuLogFormat{{namespace}},"%syslogtag:{{tagStart}}:$%%msg:::sp-if-no-1st-sp%%msg:::drop-last-lf%\n"

$template LongTagForwardFormat,"<%PRI%>%TIMESTAMP:::date-rfc3339% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg%"

{{range $i, $stateServerIP := stateServerHosts}}
# start: Forwarding rule for {{$stateServerIP}}
$ActionQueueType LinkedList
$ActionQueueFileName {{logfileName}}{{namespace}}_{{$i}}
$ActionResumeRetryCount -1
$ActionQueueSaveOnShutdown on
$ActionQueueMaxDiskSpace 512M
$DefaultNetstreamDriver gtls
$DefaultNetstreamDriverCAFile {{tlsCACertPath}}
$ActionSendStreamDriverAuthMode anon
$ActionSendStreamDriverMode 1 # run driver in TLS-only mode

:syslogtag, startswith, "juju{{namespace}}-" @@{{$stateServerIP}}:{{portNumber}};LongTagForwardFormat
# end: Forwarding rule for {{$stateServerIP}}
{{end}}
:syslogtag, startswith, "juju{{namespace}}-" stop

$FileCreateMode 0600

# Maximum size for the log on this outchannel is 512MB
# The command to execute when an outchannel as reached its size limit cannot accept any arguments
# that is why we have created the helper script for executing logrotate.
$outchannel logRotation,{{logDir}}/all-machines.log,536870912,{{logrotateHelperPath}}

$RuleSet remote
$FileCreateMode 0600
:syslogtag, startswith, "juju{{namespace}}-" :omfile:$logRotation;JujuLogFormat{{namespace}}
:syslogtag, startswith, "juju{{namespace}}-" stop
$FileCreateMode 0600

$InputFilePersistStateInterval 50
$InputFilePollInterval 5
$InputFileName {{logfilePath}}
$InputFileTag juju{{namespace}}-{{logfileName}}:
$InputFileStateFile {{logfileName}}{{namespace}}
$InputRunFileMonitor

$ModLoad imtcp
$DefaultNetstreamDriver gtls
$DefaultNetstreamDriverCAFile {{tlsCACertPath}}
$DefaultNetstreamDriverCertFile {{tlsCertPath}}
$DefaultNetstreamDriverKeyFile {{tlsKeyPath}}
$InputTCPServerStreamDriverAuthMode anon
$InputTCPServerStreamDriverMode 1 # run driver in TLS-only mode
$InputTCPMaxSessions 10000 # default is 200, all agents connect to all rsyslog daemons

$InputTCPServerBindRuleset remote
$InputTCPServerRun {{portNumber}}

# switch back to default ruleset for further rules
$RuleSet RSYSLOG_DefaultRuleset
`

// The rsyslog conf for non-state server nodes.
// Messages are forwarded to the state server node.
//
// Each forwarding rule must be repeated in full for every state server and
// each rule must use a unique ActionQueueFileName
// See: http://www.rsyslog.com/doc/rsyslog_reliable_forwarding.html
const nodeRsyslogTemplate = `
$ModLoad imuxsock
$ModLoad imfile

$InputFilePersistStateInterval 50
$InputFilePollInterval 5
$InputFileName {{logfilePath}}
$InputFileTag juju{{namespace}}-{{logfileName}}:
$InputFileStateFile {{logfileName}}{{namespace}}
$InputRunFileMonitor
{{range $i, $stateServerIP := stateServerHosts}}
# start: Forwarding rule for {{$stateServerIP}}
$ActionQueueType LinkedList
$ActionQueueFileName {{logfileName}}{{namespace}}_{{$i}}
$ActionResumeRetryCount -1
$ActionQueueSaveOnShutdown on
$ActionQueueMaxDiskSpace 512M
$DefaultNetstreamDriver gtls
$DefaultNetstreamDriverCAFile {{tlsCACertPath}}
$ActionSendStreamDriverAuthMode anon
$ActionSendStreamDriverMode 1 # run driver in TLS-only mode

$template LongTagForwardFormat,"<%PRI%>%TIMESTAMP:::date-rfc3339% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg%"
:syslogtag, startswith, "juju{{namespace}}-" @@{{$stateServerIP}}:{{portNumber}};LongTagForwardFormat
# end: Forwarding rule for {{$stateServerIP}}
{{end}}
& ~
`

// The logrotate conf for state serve nodes.
// default size is 512MB, ensuring that the log + one rotation
// will never take up more than 1GB of space.
//
// The size of the logrotate configuration is low for
// a very specific reason, see config comment.
const logrotateConf = `
{{.LogDir}}/all-machines.log {
    # rsyslogd informs logrotate when to rotate.
    # The size specified here must be less than or equal to the log size
    # when rsyslogd informs logrotate, or logrotate will take no action.
    # The size value is otherwise unimportant.
    size 1K
    # maximum of one old file
    rotate 1
    # counting old files starts at 1 rather than 0
    start 1
	# ensure new file is created with the correct permissions
    create 600
	# reload rsyslog after rotation so it will use the new file
    postrotate
      service rsyslog reload
    endscript
}
`

var logrotateConfTemplate = template.Must(template.New("logrotate.conf").Parse(logrotateConf))

// The logrotate helper script for state server nodes.
// We specify a state file to ensure we have the proper permissions.
const logrotateHelper = `
/usr/sbin/logrotate -s {{.LogDir}}/logrotate.state {{.LogrotateConfPath}}
`

var logrotateHelperTemplate = template.Must(template.New("logrotate.run").Parse(logrotateHelper))

// nodeRsyslogTemplateTLSHeader is prepended to
// nodeRsyslogTemplate if TLS is to be used.
const nodeRsyslogTemplateTLSHeader = `
`

const (
	defaultConfigDir               = "/etc/rsyslog.d"
	defaultCACertFileName          = "ca-cert.pem"
	defaultServerCertFileName      = "rsyslog-cert.pem"
	defaultServerKeyFileName       = "rsyslog-key.pem"
	defaultLogrotateConfFileName   = "logrotate.conf"
	defaultLogrotateHelperFileName = "logrotate.run"
)

// SyslogConfigRenderer instances are used to generate a rsyslog conf file.
type SyslogConfigRenderer interface {
	Render() ([]byte, error)
}

// SyslogConfig provides a means to configure and generate rsyslog conf files for
// the state server nodes and unit nodes.
// rsyslog is configured to tail the specified log file.
type SyslogConfig struct {
	// the template representing the config file contents.
	configTemplate string
	// the directory where the config file is written.
	ConfigDir string
	// the config file name.
	ConfigFileName string
	// the name of the log file to tail.
	LogFileName string
	// the name of the logrotate configuration file.
	LogrotateConfFileName string
	// the name of the script that executes the logrotate command.
	LogrotateHelperFileName string
	// the addresses of the state server to which messages should be forwarded.
	StateServerAddresses []string
	// CA certificate file name.
	CACertFileName string
	// Server certificate file name.
	ServerCertFileName string
	// Server private key file name.
	ServerKeyFileName string
	// the port number for the listener
	Port int
	// the directory for the logfiles
	LogDir string
	// namespace is used when there are multiple environments on one machine
	Namespace string
}

// NewForwardConfig creates a SyslogConfig instance used on unit nodes to forward log entries
// to the state server nodes.
func NewForwardConfig(logFile, logDir string, port int, namespace string, stateServerAddresses []string) *SyslogConfig {
	conf := &SyslogConfig{
		configTemplate:       nodeRsyslogTemplate,
		StateServerAddresses: stateServerAddresses,
		LogFileName:          logFile,
		Port:                 port,
		LogDir:               logDir,
	}
	if namespace != "" {
		conf.Namespace = "-" + namespace
	}
	return conf
}

// NewAccumulateConfig creates a SyslogConfig instance used to accumulate log entries from the
// various unit nodes.
func NewAccumulateConfig(logFile, logDir string, port int, namespace string, stateServerAddresses []string) *SyslogConfig {
	conf := &SyslogConfig{
		configTemplate:       stateServerRsyslogTemplate,
		LogFileName:          logFile,
		Port:                 port,
		LogDir:               logDir,
		StateServerAddresses: stateServerAddresses,
	}
	if namespace != "" {
		conf.Namespace = "-" + namespace
	}
	return conf
}

func either(a, b string) string {
	if a != "" {
		return a
	}
	return b
}

func (slConfig *SyslogConfig) ConfigFilePath() string {
	dir := either(slConfig.ConfigDir, defaultConfigDir)
	return filepath.Join(dir, slConfig.ConfigFileName)
}

func (slConfig *SyslogConfig) CACertPath() string {
	filename := either(slConfig.CACertFileName, defaultCACertFileName)
	return filepath.Join(slConfig.LogDir, filename)
}

func (slConfig *SyslogConfig) ServerCertPath() string {
	filename := either(slConfig.ServerCertFileName, defaultServerCertFileName)
	return filepath.Join(slConfig.LogDir, filename)
}

func (slConfig *SyslogConfig) ServerKeyPath() string {
	filename := either(slConfig.ServerCertFileName, defaultServerKeyFileName)
	return filepath.Join(slConfig.LogDir, filename)
}

// LogrotateConfPath returns the the the entire logrotate.conf path including filename.
func (slConfig *SyslogConfig) LogrotateConfPath() string {
	filename := either(slConfig.LogrotateConfFileName, defaultLogrotateConfFileName)
	return filepath.Join(slConfig.LogDir, filename)
}

// LogrotateHelperPath returns the entire logrotate.helper path including filename.
func (slConfig *SyslogConfig) LogrotateHelperPath() string {
	filename := either(slConfig.LogrotateHelperFileName, defaultLogrotateHelperFileName)
	return filepath.Join(slConfig.LogDir, filename)
}

// LogrotateConfFile returns a ready to write to disk byte array of the logrotate.conf file.
func (slConfig *SyslogConfig) LogrotateConfFile() ([]byte, error) {
	return slConfig.logrotateRender(logrotateConfTemplate)
}

// LogrotateHelperFile returns a ready to write to disk byte array of the logrotate.helper file.
func (slConfig *SyslogConfig) LogrotateHelperFile() ([]byte, error) {
	return slConfig.logrotateRender(logrotateHelperTemplate)
}

func (slConfig *SyslogConfig) logrotateRender(t *template.Template) ([]byte, error) {
	var buffer bytes.Buffer
	if err := t.Execute(&buffer, slConfig); err != nil {
		return nil, err
	}
	return buffer.Bytes(), nil
}

// Render generates the rsyslog config.
func (slConfig *SyslogConfig) Render() ([]byte, error) {
	var stateServerHosts = func() []string {
		var hosts []string
		for _, addr := range slConfig.StateServerAddresses {
			parts := strings.Split(addr, ":")
			hosts = append(hosts, parts[0])
		}
		return hosts
	}

	var logFilePath = func() string {
		return fmt.Sprintf("%s/%s.log", slConfig.LogDir, slConfig.LogFileName)
	}

	t := template.New("syslogConfig")
	t.Funcs(template.FuncMap{
		"logfileName":         func() string { return slConfig.LogFileName },
		"stateServerHosts":    stateServerHosts,
		"logfilePath":         logFilePath,
		"portNumber":          func() int { return slConfig.Port },
		"logDir":              func() string { return slConfig.LogDir },
		"namespace":           func() string { return slConfig.Namespace },
		"tagStart":            func() int { return tagOffset + len(slConfig.Namespace) },
		"tlsCACertPath":       slConfig.CACertPath,
		"tlsCertPath":         slConfig.ServerCertPath,
		"tlsKeyPath":          slConfig.ServerKeyPath,
		"logrotateHelperPath": slConfig.LogrotateHelperPath,
	})

	// Process the rsyslog config template and echo to the conf file.
	p, err := t.Parse(slConfig.configTemplate)
	if err != nil {
		return nil, err
	}
	var confBuf bytes.Buffer
	if err := p.Execute(&confBuf, nil); err != nil {
		return nil, err
	}
	return confBuf.Bytes(), nil
}

// Write generates and writes the rsyslog config.
func (slConfig *SyslogConfig) Write() error {
	data, err := slConfig.Render()
	if err != nil {
		return err
	}
	err = ioutil.WriteFile(slConfig.ConfigFilePath(), data, 0644)
	return err
}