github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/worker/rsyslog/rsyslog_test.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  // +build !windows
     4  
     5  package rsyslog_test
     6  
     7  import (
     8  	"crypto/x509"
     9  	"encoding/pem"
    10  	"io/ioutil"
    11  	"os"
    12  	"path/filepath"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/juju/names"
    17  	jc "github.com/juju/testing/checkers"
    18  	gc "gopkg.in/check.v1"
    19  
    20  	"github.com/juju/juju/api"
    21  	"github.com/juju/juju/cert"
    22  	"github.com/juju/juju/network"
    23  	"github.com/juju/juju/state"
    24  	coretesting "github.com/juju/juju/testing"
    25  	"github.com/juju/juju/utils/syslog"
    26  	"github.com/juju/juju/worker/rsyslog"
    27  )
    28  
    29  func waitForRestart(c *gc.C, restarted chan struct{}) {
    30  	timeout := time.After(coretesting.LongWait)
    31  	for {
    32  		select {
    33  		case <-timeout:
    34  			c.Fatalf("timed out waiting for rsyslog to be restarted")
    35  		case <-restarted:
    36  			return
    37  		}
    38  	}
    39  }
    40  
    41  func assertPathExists(c *gc.C, path string) {
    42  	_, err := os.Stat(path)
    43  	c.Assert(err, jc.ErrorIsNil)
    44  }
    45  
    46  func (s *RsyslogSuite) TestStartStop(c *gc.C) {
    47  	st, m := s.OpenAPIAsNewMachine(c, state.JobHostUnits)
    48  	worker, err := rsyslog.NewRsyslogConfigWorker(st.Rsyslog(), rsyslog.RsyslogModeForwarding, m.Tag(), "", []string{"0.1.2.3"}, s.ConfDir())
    49  	c.Assert(err, jc.ErrorIsNil)
    50  	worker.Kill()
    51  	c.Assert(worker.Wait(), gc.IsNil)
    52  }
    53  
    54  func (s *RsyslogSuite) TestTearDown(c *gc.C) {
    55  	st, m := s.st, s.machine
    56  	worker, err := rsyslog.NewRsyslogConfigWorker(st.Rsyslog(), rsyslog.RsyslogModeAccumulate, m.Tag(), "", []string{"0.1.2.3"}, s.ConfDir())
    57  	c.Assert(err, jc.ErrorIsNil)
    58  	confFile := filepath.Join(*rsyslog.RsyslogConfDir, "25-juju.conf")
    59  	// On worker teardown, the rsyslog config file should be removed.
    60  	defer func() {
    61  		_, err := os.Stat(confFile)
    62  		c.Assert(err, jc.Satisfies, os.IsNotExist)
    63  	}()
    64  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
    65  	defer worker.Kill()
    66  	waitForFile(c, confFile)
    67  }
    68  
    69  func (s *RsyslogSuite) TestRsyslogCert(c *gc.C) {
    70  	st, m := s.st, s.machine
    71  	err := s.machine.SetProviderAddresses(network.NewAddress("example.com"))
    72  	c.Assert(err, jc.ErrorIsNil)
    73  
    74  	worker, err := rsyslog.NewRsyslogConfigWorker(st.Rsyslog(), rsyslog.RsyslogModeAccumulate, m.Tag(), "", []string{"0.1.2.3"}, s.ConfDir())
    75  	c.Assert(err, jc.ErrorIsNil)
    76  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
    77  	defer worker.Kill()
    78  	filename := filepath.Join(s.ConfDir(), "rsyslog", "rsyslog-cert.pem")
    79  	waitForFile(c, filename)
    80  
    81  	rsyslogCertPEM, err := ioutil.ReadFile(filename)
    82  	c.Assert(err, jc.ErrorIsNil)
    83  
    84  	cert, err := cert.ParseCert(string(rsyslogCertPEM))
    85  	c.Assert(err, jc.ErrorIsNil)
    86  
    87  	c.Assert(cert.DNSNames, gc.DeepEquals, []string{"example.com", "*"})
    88  
    89  	subject := cert.Subject
    90  	c.Assert(subject.CommonName, gc.Equals, "*")
    91  	c.Assert(subject.Organization, gc.DeepEquals, []string{"juju"})
    92  
    93  	issuer := cert.Issuer
    94  	c.Assert(issuer.CommonName, gc.Equals, "juju-generated CA for environment \"rsyslog\"")
    95  	c.Assert(issuer.Organization, gc.DeepEquals, []string{"juju"})
    96  }
    97  
    98  func (s *RsyslogSuite) TestModeAccumulate(c *gc.C) {
    99  	st, m := s.st, s.machine
   100  	worker, err := rsyslog.NewRsyslogConfigWorker(st.Rsyslog(), rsyslog.RsyslogModeAccumulate, m.Tag(), "", nil, s.ConfDir())
   101  	c.Assert(err, jc.ErrorIsNil)
   102  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
   103  	defer worker.Kill()
   104  	dirname := filepath.Join(s.ConfDir(), "rsyslog")
   105  	waitForFile(c, filepath.Join(dirname, "ca-cert.pem"))
   106  
   107  	// We should have ca-cert.pem, rsyslog-cert.pem, and rsyslog-key.pem.
   108  	caCertPEM, err := ioutil.ReadFile(filepath.Join(dirname, "ca-cert.pem"))
   109  	c.Assert(err, jc.ErrorIsNil)
   110  	rsyslogCertPEM, err := ioutil.ReadFile(filepath.Join(dirname, "rsyslog-cert.pem"))
   111  	c.Assert(err, jc.ErrorIsNil)
   112  	rsyslogKeyPEM, err := ioutil.ReadFile(filepath.Join(dirname, "rsyslog-key.pem"))
   113  	c.Assert(err, jc.ErrorIsNil)
   114  
   115  	_, _, err = cert.ParseCertAndKey(string(rsyslogCertPEM), string(rsyslogKeyPEM))
   116  	c.Assert(err, jc.ErrorIsNil)
   117  	err = cert.Verify(string(rsyslogCertPEM), string(caCertPEM), time.Now().UTC())
   118  	c.Assert(err, jc.ErrorIsNil)
   119  
   120  	// Verify rsyslog configuration.
   121  	waitForFile(c, filepath.Join(*rsyslog.RsyslogConfDir, "25-juju.conf"))
   122  	rsyslogConf, err := ioutil.ReadFile(filepath.Join(*rsyslog.RsyslogConfDir, "25-juju.conf"))
   123  	c.Assert(err, jc.ErrorIsNil)
   124  
   125  	syslogPort := s.Environ.Config().SyslogPort()
   126  
   127  	syslogConfig := &syslog.SyslogConfig{
   128  		LogFileName:          m.Tag().String(),
   129  		LogDir:               *rsyslog.LogDir,
   130  		Port:                 syslogPort,
   131  		Namespace:            "",
   132  		StateServerAddresses: []string{},
   133  	}
   134  
   135  	syslog.NewAccumulateConfig(syslogConfig)
   136  	syslogConfig.ConfigDir = *rsyslog.RsyslogConfDir
   137  	syslogConfig.JujuConfigDir = filepath.Join(s.ConfDir(), "rsyslog")
   138  	rendered, err := syslogConfig.Render()
   139  	c.Assert(err, jc.ErrorIsNil)
   140  
   141  	c.Assert(string(rsyslogConf), gc.DeepEquals, string(rendered))
   142  
   143  	// Verify logrotate files
   144  	assertPathExists(c, filepath.Join(dirname, "logrotate.conf"))
   145  	assertPathExists(c, filepath.Join(dirname, "logrotate.run"))
   146  
   147  }
   148  
   149  func (s *RsyslogSuite) TestAccumulateHA(c *gc.C) {
   150  	m := s.machine
   151  
   152  	syslogConfig := &syslog.SyslogConfig{
   153  		LogFileName:          m.Tag().String(),
   154  		LogDir:               *rsyslog.LogDir,
   155  		Port:                 6541,
   156  		Namespace:            "",
   157  		StateServerAddresses: []string{"192.168.1", "127.0.0.1"},
   158  	}
   159  
   160  	syslog.NewAccumulateConfig(syslogConfig)
   161  	syslogConfig.JujuConfigDir = filepath.Join(s.ConfDir(), "rsyslog")
   162  	rendered, err := syslogConfig.Render()
   163  	c.Assert(err, jc.ErrorIsNil)
   164  
   165  	stateServer1Config := ":syslogtag, startswith, \"juju-\" @@192.168.1:6541;LongTagForwardFormat"
   166  	stateServer2Config := ":syslogtag, startswith, \"juju-\" @@127.0.0.1:6541;LongTagForwardFormat"
   167  
   168  	c.Assert(strings.Contains(string(rendered), stateServer1Config), jc.IsTrue)
   169  	c.Assert(strings.Contains(string(rendered), stateServer2Config), jc.IsTrue)
   170  }
   171  
   172  // TestModeAccumulateCertsExist is a regression test for
   173  // https://bugs.launchpad.net/juju-core/+bug/1464335,
   174  // where the CA certs existing (in local provider) at
   175  // bootstrap caused the worker to not publish to state.
   176  func (s *RsyslogSuite) TestModeAccumulateCertsExistOnDisk(c *gc.C) {
   177  	dirname := filepath.Join(s.ConfDir(), "rsyslog")
   178  	err := os.MkdirAll(dirname, 0755)
   179  	c.Assert(err, jc.ErrorIsNil)
   180  	err = ioutil.WriteFile(filepath.Join(dirname, "ca-cert.pem"), nil, 0644)
   181  	c.Assert(err, jc.ErrorIsNil)
   182  
   183  	st, m := s.st, s.machine
   184  	worker, err := rsyslog.NewRsyslogConfigWorker(st.Rsyslog(), rsyslog.RsyslogModeAccumulate, m.Tag(), "", nil, s.ConfDir())
   185  	c.Assert(err, jc.ErrorIsNil)
   186  	// The worker should create certs and publish to state during setup,
   187  	// so we can kill and wait and be confident that the task is done.
   188  	worker.Kill()
   189  	c.Assert(worker.Wait(), jc.ErrorIsNil)
   190  
   191  	// The CA cert and key should have been published to state.
   192  	cfg, err := s.State.EnvironConfig()
   193  	c.Assert(err, jc.ErrorIsNil)
   194  	c.Assert(cfg.AllAttrs()["rsyslog-ca-cert"], gc.NotNil)
   195  	c.Assert(cfg.AllAttrs()["rsyslog-ca-key"], gc.NotNil)
   196  
   197  	// ca-cert.pem isn't updated on disk until the worker reacts to the
   198  	// state change. Let's just ensure that rsyslog-ca-cert is a valid
   199  	// certificate, and no the zero-length string we wrote to ca-cert.pem.
   200  	caCertPEM := cfg.AllAttrs()["rsyslog-ca-cert"].(string)
   201  	c.Assert(err, jc.ErrorIsNil)
   202  	block, _ := pem.Decode([]byte(caCertPEM))
   203  	c.Assert(block, gc.NotNil)
   204  	_, err = x509.ParseCertificate(block.Bytes)
   205  	c.Assert(err, jc.ErrorIsNil)
   206  }
   207  
   208  func (s *RsyslogSuite) TestNamespace(c *gc.C) {
   209  	st := s.st
   210  	// set the rsyslog cert
   211  	err := s.APIState.Client().EnvironmentSet(map[string]interface{}{"rsyslog-ca-cert": coretesting.CACert})
   212  	c.Assert(err, jc.ErrorIsNil)
   213  
   214  	// namespace only takes effect in filenames
   215  	// for machine-0; all others assume isolation.
   216  	s.testNamespace(c, st, names.NewMachineTag("0"), "", "25-juju.conf", *rsyslog.LogDir)
   217  	s.testNamespace(c, st, names.NewMachineTag("0"), "mynamespace", "25-juju-mynamespace.conf", *rsyslog.LogDir+"-mynamespace")
   218  	s.testNamespace(c, st, names.NewMachineTag("1"), "", "25-juju.conf", *rsyslog.LogDir)
   219  	s.testNamespace(c, st, names.NewMachineTag("1"), "mynamespace", "25-juju.conf", *rsyslog.LogDir)
   220  	s.testNamespace(c, st, names.NewUnitTag("myservice/0"), "", "26-juju-unit-myservice-0.conf", *rsyslog.LogDir)
   221  	s.testNamespace(c, st, names.NewUnitTag("myservice/0"), "mynamespace", "26-juju-unit-myservice-0.conf", *rsyslog.LogDir)
   222  }
   223  
   224  // testNamespace starts a worker and ensures that
   225  // the rsyslog config file has the expected filename,
   226  // and the appropriate log dir is used.
   227  func (s *RsyslogSuite) testNamespace(c *gc.C, st api.Connection, tag names.Tag, namespace, expectedFilename, expectedLogDir string) {
   228  	restarted := make(chan struct{}, 2) // once for create, once for teardown
   229  	s.PatchValue(rsyslog.RestartRsyslog, func() error {
   230  		restarted <- struct{}{}
   231  		return nil
   232  	})
   233  
   234  	err := os.MkdirAll(expectedLogDir, 0755)
   235  	c.Assert(err, jc.ErrorIsNil)
   236  	worker, err := rsyslog.NewRsyslogConfigWorker(st.Rsyslog(),
   237  		rsyslog.RsyslogModeAccumulate, tag, namespace, []string{"0.1.2.3"}, s.ConfDir())
   238  	c.Assert(err, jc.ErrorIsNil)
   239  	defer func() { c.Assert(worker.Wait(), gc.IsNil) }()
   240  	defer worker.Kill()
   241  
   242  	// change the API HostPorts to trigger an rsyslog restart
   243  	newHostPorts := network.NewHostPorts(6541, "127.0.0.1")
   244  	err = s.State.SetAPIHostPorts([][]network.HostPort{newHostPorts})
   245  	c.Assert(err, jc.ErrorIsNil)
   246  
   247  	// Wait for rsyslog to be restarted, so we can check to see
   248  	// what the name of the config file is.
   249  	waitForRestart(c, restarted)
   250  
   251  	// Ensure that ca-cert.pem gets written to the expected log dir.
   252  	dirname := filepath.Join(s.ConfDir(), "rsyslog")
   253  	waitForFile(c, filepath.Join(dirname, "ca-cert.pem"))
   254  
   255  	dir, err := os.Open(*rsyslog.RsyslogConfDir)
   256  	c.Assert(err, jc.ErrorIsNil)
   257  	names, err := dir.Readdirnames(-1)
   258  	dir.Close()
   259  	c.Assert(err, jc.ErrorIsNil)
   260  	c.Assert(names, gc.HasLen, 1)
   261  	c.Assert(names[0], gc.Equals, expectedFilename)
   262  }