Skip to content

Commit ad3c8fd

Browse files
authored
Merge pull request juju#5576 from ericsnowcurrently/logging-record
Add a Record type to use for log forwarding. The type aligns well with the structured data we'll be sending for our syslog wire format. The new type will be used in a follow-up patch. (Review request: http://reviews.vapour.ws/r/5020/)
2 parents 8e7e260 + c7c473f commit ad3c8fd

File tree

5 files changed

+729
-0
lines changed

5 files changed

+729
-0
lines changed

logfwd/origin.go

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
// Copyright 2016 Canonical Ltd.
2+
// Licensed under the AGPLv3, see LICENCE file for details.
3+
4+
package logfwd
5+
6+
import (
7+
"fmt"
8+
9+
"github.com/juju/errors"
10+
"github.com/juju/version"
11+
"gopkg.in/juju/names.v2"
12+
)
13+
14+
// canonicalPEN is the IANA-registered Private Enterprise Number
15+
// assigned to Canonical. Among other things, this is used in RFC 5424
16+
// structured data.
17+
//
18+
// See https://www.iana.org/assignments/enterprise-numbers/enterprise-numbers.
19+
const canonicalPEN = 28978
20+
21+
// These are the recognized origin types.
22+
const (
23+
OriginTypeUnknown OriginType = 0
24+
OriginTypeUser = iota
25+
)
26+
27+
var originTypes = map[OriginType]string{
28+
OriginTypeUnknown: "unknown",
29+
OriginTypeUser: names.UserTagKind,
30+
}
31+
32+
// OriginType is the "enum" type for the different kinds of log record
33+
// origin.
34+
type OriginType int
35+
36+
// ParseOriginType converts a string to an OriginType or fails if
37+
// not able. It round-trips with String().
38+
func ParseOriginType(value string) (OriginType, error) {
39+
for ot, str := range originTypes {
40+
if value == str {
41+
return ot, nil
42+
}
43+
}
44+
const originTypeInvalid OriginType = -1
45+
return originTypeInvalid, errors.Errorf("unrecognized origin type %q", value)
46+
}
47+
48+
// String returns a string representation of the origin type.
49+
func (ot OriginType) String() string {
50+
return originTypes[ot]
51+
}
52+
53+
// Validate ensures that the origin type is correct.
54+
func (ot OriginType) Validate() error {
55+
// As noted above, typedef'ing int means that the use of int
56+
// literals or explicit type conversion could result in unsupported
57+
// "enum" values. Otherwise OriginType would not need this method.
58+
if _, ok := originTypes[ot]; !ok {
59+
return errors.NewNotValid(nil, "unsupported origin type")
60+
}
61+
return nil
62+
}
63+
64+
// ValidateName ensures that the given origin name is valid within the
65+
// context of the origin type.
66+
func (ot OriginType) ValidateName(name string) error {
67+
switch ot {
68+
case OriginTypeUnknown:
69+
if name != "" {
70+
return errors.NewNotValid(nil, "origin name must not be set if type is unknown")
71+
}
72+
case OriginTypeUser:
73+
if !names.IsValidUser(name) {
74+
return errors.NewNotValid(nil, "bad user name")
75+
}
76+
}
77+
return nil
78+
}
79+
80+
// Validate ensures that the origin is correct.
81+
type Origin struct {
82+
ControllerUUID string
83+
ModelUUID string
84+
Type OriginType
85+
Name string
86+
JujuVersion version.Number
87+
}
88+
89+
// PrivateEnterpriseNumber returns the IANA-registered "SMI Network
90+
// Management Private Enterprise Code" to use for the log record.
91+
//
92+
// See https://tools.ietf.org/html/rfc5424#section-7.2.2.
93+
func (o Origin) PrivateEnterpriseNumber() int {
94+
return canonicalPEN
95+
}
96+
97+
// SofwareName identifies the software that generated the log message.
98+
// It is unique within the context of the enterprise ID.
99+
func (o Origin) SoftwareName() string {
100+
return "jujud"
101+
}
102+
103+
// Validate ensures that the origin is correct.
104+
func (o Origin) Validate() error {
105+
if o.ControllerUUID == "" {
106+
return errors.NewNotValid(nil, "empty ControllerUUID")
107+
}
108+
if !names.IsValidModel(o.ControllerUUID) {
109+
return errors.NewNotValid(nil, fmt.Sprintf("ControllerUUID %q not a valid UUID", o.ControllerUUID))
110+
}
111+
112+
if o.ModelUUID == "" {
113+
return errors.NewNotValid(nil, "empty ModelUUID")
114+
}
115+
if !names.IsValidModel(o.ModelUUID) {
116+
return errors.NewNotValid(nil, fmt.Sprintf("ModelUUID %q not a valid UUID", o.ModelUUID))
117+
}
118+
119+
if err := o.Type.Validate(); err != nil {
120+
return errors.Annotate(err, "invalid Type")
121+
}
122+
123+
if o.Name == "" && o.Type != OriginTypeUnknown {
124+
return errors.NewNotValid(nil, "empty Name")
125+
}
126+
if err := o.Type.ValidateName(o.Name); err != nil {
127+
return errors.Annotatef(err, "invalid Name %q", o.Name)
128+
}
129+
130+
if o.JujuVersion == version.Zero {
131+
return errors.NewNotValid(nil, "empty JujuVersion")
132+
}
133+
134+
return nil
135+
}

logfwd/origin_test.go

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
// Copyright 2016 Canonical Ltd.
2+
// Licensed under the AGPLv3, see LICENCE file for details.
3+
4+
package logfwd_test
5+
6+
import (
7+
"github.com/juju/errors"
8+
"github.com/juju/testing"
9+
jc "github.com/juju/testing/checkers"
10+
"github.com/juju/version"
11+
gc "gopkg.in/check.v1"
12+
13+
"github.com/juju/juju/logfwd"
14+
)
15+
16+
type OriginTypeSuite struct {
17+
testing.IsolationSuite
18+
}
19+
20+
var _ = gc.Suite(&OriginTypeSuite{})
21+
22+
func (s *OriginTypeSuite) TestZeroValue(c *gc.C) {
23+
var ot logfwd.OriginType
24+
25+
c.Check(ot, gc.Equals, logfwd.OriginTypeUnknown)
26+
}
27+
28+
func (s *OriginTypeSuite) TestParseOriginTypeValid(c *gc.C) {
29+
tests := map[string]logfwd.OriginType{
30+
"unknown": logfwd.OriginTypeUnknown,
31+
"user": logfwd.OriginTypeUser,
32+
}
33+
for str, expected := range tests {
34+
c.Logf("trying %q", str)
35+
36+
ot, err := logfwd.ParseOriginType(str)
37+
c.Assert(err, jc.ErrorIsNil)
38+
39+
c.Check(ot, gc.Equals, expected)
40+
}
41+
}
42+
43+
func (s *OriginTypeSuite) TestParseOriginTypeEmpty(c *gc.C) {
44+
_, err := logfwd.ParseOriginType("")
45+
46+
c.Check(err, gc.ErrorMatches, `unrecognized origin type ""`)
47+
}
48+
49+
func (s *OriginTypeSuite) TestParseOriginTypeInvalid(c *gc.C) {
50+
_, err := logfwd.ParseOriginType("spam")
51+
52+
c.Check(err, gc.ErrorMatches, `unrecognized origin type "spam"`)
53+
}
54+
55+
func (s *OriginTypeSuite) TestString(c *gc.C) {
56+
tests := map[logfwd.OriginType]string{
57+
logfwd.OriginTypeUnknown: "unknown",
58+
logfwd.OriginTypeUser: "user",
59+
}
60+
for ot, expected := range tests {
61+
c.Logf("trying %q", ot)
62+
63+
str := ot.String()
64+
65+
c.Check(str, gc.Equals, expected)
66+
}
67+
}
68+
69+
func (s *OriginTypeSuite) TestValidateValid(c *gc.C) {
70+
tests := []logfwd.OriginType{
71+
logfwd.OriginTypeUnknown,
72+
logfwd.OriginTypeUser,
73+
}
74+
for _, ot := range tests {
75+
c.Logf("trying %q", ot)
76+
77+
err := ot.Validate()
78+
79+
c.Check(err, jc.ErrorIsNil)
80+
}
81+
}
82+
83+
func (s *OriginTypeSuite) TestValidateZero(c *gc.C) {
84+
var ot logfwd.OriginType
85+
86+
err := ot.Validate()
87+
88+
c.Check(err, jc.ErrorIsNil)
89+
}
90+
91+
func (s *OriginTypeSuite) TestValidateInvalid(c *gc.C) {
92+
ot := logfwd.OriginType(999)
93+
94+
err := ot.Validate()
95+
96+
c.Check(err, jc.Satisfies, errors.IsNotValid)
97+
c.Check(err, gc.ErrorMatches, `unsupported origin type`)
98+
}
99+
100+
func (s *OriginTypeSuite) TestValidateNameValid(c *gc.C) {
101+
tests := map[logfwd.OriginType]string{
102+
logfwd.OriginTypeUnknown: "",
103+
logfwd.OriginTypeUser: "a-user",
104+
}
105+
for ot, name := range tests {
106+
c.Logf("trying %q + %q", ot, name)
107+
108+
err := ot.ValidateName(name)
109+
110+
c.Check(err, jc.ErrorIsNil)
111+
}
112+
}
113+
114+
func (s *OriginTypeSuite) TestValidateNameInvalid(c *gc.C) {
115+
tests := []struct {
116+
ot logfwd.OriginType
117+
name string
118+
err string
119+
}{{
120+
ot: logfwd.OriginTypeUnknown,
121+
name: "...",
122+
err: `origin name must not be set if type is unknown`,
123+
}, {
124+
ot: logfwd.OriginTypeUser,
125+
name: "...",
126+
err: `bad user name`,
127+
}}
128+
for _, test := range tests {
129+
c.Logf("trying %q + %q", test.ot, test.name)
130+
131+
err := test.ot.ValidateName(test.name)
132+
133+
c.Check(err, jc.Satisfies, errors.IsNotValid)
134+
c.Check(err, gc.ErrorMatches, test.err)
135+
}
136+
}
137+
138+
type OriginSuite struct {
139+
testing.IsolationSuite
140+
}
141+
142+
var _ = gc.Suite(&OriginSuite{})
143+
144+
func (s *OriginSuite) TestPrivateEnterpriseNumber(c *gc.C) {
145+
var origin logfwd.Origin
146+
147+
id := origin.PrivateEnterpriseNumber()
148+
149+
c.Check(id, gc.Equals, 28978)
150+
}
151+
152+
func (s *OriginSuite) TestSoftwareName(c *gc.C) {
153+
var origin logfwd.Origin
154+
155+
swName := origin.SoftwareName()
156+
157+
c.Check(swName, gc.Equals, "jujud")
158+
}
159+
160+
func (s *OriginSuite) TestValidateValid(c *gc.C) {
161+
origin := validOrigin
162+
163+
err := origin.Validate()
164+
165+
c.Check(err, jc.ErrorIsNil)
166+
}
167+
168+
func (s *OriginSuite) TestValidateEmpty(c *gc.C) {
169+
var origin logfwd.Origin
170+
171+
err := origin.Validate()
172+
173+
c.Check(err, jc.Satisfies, errors.IsNotValid)
174+
}
175+
176+
func (s *OriginSuite) TestValidateEmptyControllerUUID(c *gc.C) {
177+
origin := validOrigin
178+
origin.ControllerUUID = ""
179+
180+
err := origin.Validate()
181+
182+
c.Check(err, jc.Satisfies, errors.IsNotValid)
183+
c.Check(err, gc.ErrorMatches, `empty ControllerUUID`)
184+
}
185+
186+
func (s *OriginSuite) TestValidateBadControllerUUID(c *gc.C) {
187+
origin := validOrigin
188+
origin.ControllerUUID = "..."
189+
190+
err := origin.Validate()
191+
192+
c.Check(err, jc.Satisfies, errors.IsNotValid)
193+
c.Check(err, gc.ErrorMatches, `ControllerUUID "..." not a valid UUID`)
194+
}
195+
196+
func (s *OriginSuite) TestValidateEmptyModelUUID(c *gc.C) {
197+
origin := validOrigin
198+
origin.ModelUUID = ""
199+
200+
err := origin.Validate()
201+
202+
c.Check(err, jc.Satisfies, errors.IsNotValid)
203+
c.Check(err, gc.ErrorMatches, `empty ModelUUID`)
204+
}
205+
206+
func (s *OriginSuite) TestValidateBadModelUUID(c *gc.C) {
207+
origin := validOrigin
208+
origin.ModelUUID = "..."
209+
210+
err := origin.Validate()
211+
212+
c.Check(err, jc.Satisfies, errors.IsNotValid)
213+
c.Check(err, gc.ErrorMatches, `ModelUUID "..." not a valid UUID`)
214+
}
215+
216+
func (s *OriginSuite) TestValidateBadOriginType(c *gc.C) {
217+
origin := validOrigin
218+
origin.Type = logfwd.OriginType(999)
219+
220+
err := origin.Validate()
221+
222+
c.Check(err, jc.Satisfies, errors.IsNotValid)
223+
c.Check(err, gc.ErrorMatches, `invalid Type: unsupported origin type`)
224+
}
225+
226+
func (s *OriginSuite) TestValidateEmptyName(c *gc.C) {
227+
origin := validOrigin
228+
origin.Name = ""
229+
230+
err := origin.Validate()
231+
232+
c.Check(err, jc.Satisfies, errors.IsNotValid)
233+
c.Check(err, gc.ErrorMatches, `empty Name`)
234+
}
235+
236+
func (s *OriginSuite) TestValidateBadName(c *gc.C) {
237+
origin := validOrigin
238+
origin.Name = "..."
239+
240+
err := origin.Validate()
241+
242+
c.Check(err, jc.Satisfies, errors.IsNotValid)
243+
c.Check(err, gc.ErrorMatches, `invalid Name "...": bad user name`)
244+
}
245+
246+
func (s *OriginSuite) TestValidateEmptyVersion(c *gc.C) {
247+
origin := validOrigin
248+
origin.JujuVersion = version.Zero
249+
250+
err := origin.Validate()
251+
252+
c.Check(err, jc.Satisfies, errors.IsNotValid)
253+
c.Check(err, gc.ErrorMatches, `empty JujuVersion`)
254+
}
255+
256+
var validOrigin = logfwd.Origin{
257+
ControllerUUID: "9f484882-2f18-4fd2-967d-db9663db7bea",
258+
ModelUUID: "deadbeef-2f18-4fd2-967d-db9663db7bea",
259+
Type: logfwd.OriginTypeUser,
260+
Name: "a-user",
261+
JujuVersion: version.MustParse("2.0.1"),
262+
}

0 commit comments

Comments
 (0)