Add first version of node-problem-detector

This commit is contained in:
Lantao Liu
2016-05-17 15:55:33 -07:00
parent 802acee7e3
commit f0312655bd
31 changed files with 2370 additions and 0 deletions
+166
View File
@@ -0,0 +1,166 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package kernelmonitor
import (
"bufio"
"os"
"k8s.io/node-problem-detector/pkg/kernelmonitor/translator"
"k8s.io/node-problem-detector/pkg/kernelmonitor/types"
"k8s.io/node-problem-detector/pkg/kernelmonitor/util"
"github.com/golang/glog"
"github.com/hpcloud/tail"
)
const (
defaultKernelLogPath = "/var/log/kern.log"
)
// WatcherConfig is the configuration of kernel log watcher.
type WatcherConfig struct {
KernelLogPath string `json:"logPath, omitempty"`
}
// KernelLogWatcher watches and translates the kernel log. Once there is new log line,
// it will translate and report the log.
type KernelLogWatcher interface {
// Watch starts the kernel log watcher and returns a watch channel.
Watch() (<-chan *types.KernelLog, error)
// Stop stops the kernel log watcher.
Stop()
}
type kernelLogWatcher struct {
// trans is the translator translates the log into internal format.
trans translator.Translator
cfg WatcherConfig
tl *tail.Tail
logCh chan *types.KernelLog
tomb *util.Tomb
}
// NewKernelLogWatcher creates a new kernel log watcher.
func NewKernelLogWatcher(cfg WatcherConfig) KernelLogWatcher {
return &kernelLogWatcher{
trans: translator.NewDefaultTranslator(),
cfg: cfg,
tomb: util.NewTomb(),
// A capacity 1000 buffer should be enough
logCh: make(chan *types.KernelLog, 1000),
}
}
func (k *kernelLogWatcher) Watch() (<-chan *types.KernelLog, error) {
path := defaultKernelLogPath
if k.cfg.KernelLogPath != "" {
path = k.cfg.KernelLogPath
}
start, err := k.getStartPoint(path)
if err != nil {
return nil, err
}
// TODO(random-liu): If the file gets recreated during this interval, the logic
// will go wrong here.
// TODO(random-liu): Rate limit tail file.
// TODO(random-liu): Figure out what happens if log lines are removed.
k.tl, err = tail.TailFile(path, tail.Config{
Location: &tail.SeekInfo{
Offset: start,
Whence: os.SEEK_SET,
},
Poll: true,
ReOpen: true,
Follow: true,
})
if err != nil {
return nil, err
}
glog.Info("Start watching kernel log")
go k.watchLoop()
return k.logCh, nil
}
func (k *kernelLogWatcher) Stop() {
k.tomb.Stop()
}
// watchLoop is the main watch loop of kernel log watcher.
func (k *kernelLogWatcher) watchLoop() {
defer func() {
close(k.logCh)
k.tomb.Done()
}()
for {
select {
case line := <-k.tl.Lines:
// Notice that tail has trimmed '\n'
if line.Err != nil {
glog.Errorf("Tail error: %v", line.Err)
continue
}
log, err := k.trans.Translate(line.Text)
if err != nil {
glog.Infof("Unable to parse line: %q, %v", line, err)
continue
}
k.logCh <- log
case <-k.tomb.Stopping():
k.tl.Stop()
glog.Infof("Stop watching kernel log")
return
}
}
}
// getStartPoint parses the newest kernel log file and try to find the latest reboot point.
// Currently we rely on the kernel log timestamp to find the reboot point. The basic idea
// is straight forward: In the whole lifecycle of a node, the kernel log timestamp should
// always increase, only when it is reboot, the timestamp will decrease. We just parse the
// log and find the latest timestamp decreasing, then it should be the latest reboot point.
// TODO(random-liu): A drawback is that if the node is started long time ago, we'll only get
// logs in the newest kernel log file. We may want to improve this in the future.
func (k *kernelLogWatcher) getStartPoint(path string) (int64, error) {
f, err := os.Open(path)
if err != nil {
return -1, err
}
defer f.Close()
start := int64(0)
total := 0
lastTimestamp := int64(0)
reader := bufio.NewReader(f)
done := false
for !done {
line, err := reader.ReadString('\n')
if err != nil {
done = true
}
total += len(line)
log, err := k.trans.Translate(line)
if err != nil {
glog.Infof("unable to parse line: %q, %v", line, err)
continue
}
if log.Timestamp < lastTimestamp {
start = int64(total - len(line))
}
lastTimestamp = log.Timestamp
}
return start, nil
}
@@ -0,0 +1,108 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package kernelmonitor
import (
"io/ioutil"
"os"
"reflect"
"testing"
"time"
"k8s.io/node-problem-detector/pkg/kernelmonitor/types"
)
func TestGetStartPoint(t *testing.T) {
testCases := []struct {
log string
logs []types.KernelLog
err bool
}{
{
// The start point is at the head of the log file.
log: `kernel: [1.000000] 1
kernel: [2.000000] 2
kernel: [3.000000] 3
`,
logs: []types.KernelLog{
{
Timestamp: 1000000,
Message: "1",
},
{
Timestamp: 2000000,
Message: "2",
},
{
Timestamp: 3000000,
Message: "3",
},
},
},
{
// The start point is in the middle of the log file.
log: `kernel: [3.000000] 3
kernel: [1.000000] 1
kernel: [2.000000] 2
`,
logs: []types.KernelLog{
{
Timestamp: 1000000,
Message: "1",
},
{
Timestamp: 2000000,
Message: "2",
},
},
},
}
for c, test := range testCases {
f, err := ioutil.TempFile("", "kernel_log_watcher_test")
if err != nil {
t.Fatal(err)
}
defer func() {
f.Close()
os.Remove(f.Name())
}()
_, err = f.Write([]byte(test.log))
if err != nil {
t.Fatal(err)
}
w := NewKernelLogWatcher(WatcherConfig{KernelLogPath: f.Name()})
logCh, err := w.Watch()
if err != nil {
t.Fatal(err)
}
defer w.Stop()
for _, expected := range test.logs {
got := <-logCh
if !reflect.DeepEqual(&expected, got) {
t.Errorf("case %d: expect %+v, got %+v", c+1, expected, *got)
}
}
// The log channel should have already been drained
// There could stil be future messages sent into the channel, but the chance is really slim.
timeout := time.After(100 * time.Millisecond)
select {
case log := <-logCh:
t.Errorf("unexpected extra log: %+v", *log)
case <-timeout:
}
}
}
+210
View File
@@ -0,0 +1,210 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package kernelmonitor
import (
"encoding/json"
"io/ioutil"
"regexp"
"syscall"
"time"
kerntypes "k8s.io/node-problem-detector/pkg/kernelmonitor/types"
"k8s.io/node-problem-detector/pkg/kernelmonitor/util"
"k8s.io/node-problem-detector/pkg/types"
"github.com/golang/glog"
)
// May want to add more conditions if we need finer grained node conditions.
// TODO(random-liu): Make the kernel condition to be a predefined list, and make it configurable
// in rule.
const (
KernelDeadlockCondition = "KernelDeadlock"
KernelMonitorSource = "kernel-monitor"
)
// MonitorConfig is the configuration of kernel monitor.
type MonitorConfig struct {
// WatcherConfig is the configuration of kernel log watcher.
WatcherConfig
// BufferSize is the size (in lines) of the log buffer.
BufferSize int `json:"bufferSize"`
// Rules are the rules kernel monitor will follow to parse the log file.
Rules []kerntypes.Rule `json:"rules"`
}
// KernelMonitor monitors the kernel log and reports node problem condition and event according to
// the rules.
type KernelMonitor interface {
// Start starts the kernel monitor.
Start() (<-chan *types.Status, error)
// Stop stops the kernel monitor.
Stop()
}
type kernelMonitor struct {
watcher KernelLogWatcher
buffer LogBuffer
config MonitorConfig
condition types.Condition
uptime time.Time
logCh <-chan *kerntypes.KernelLog
output chan *types.Status
tomb *util.Tomb
}
// NewKernelMonitorOrDie create a new KernelMonitor, panic if error occurs.
func NewKernelMonitorOrDie(configPath string) KernelMonitor {
k := &kernelMonitor{
condition: defaultCondition(),
tomb: util.NewTomb(),
}
f, err := ioutil.ReadFile(configPath)
if err != nil {
panic(err)
}
err = json.Unmarshal(f, &k.config)
if err != nil {
panic(err)
}
err = validateRules(k.config.Rules)
if err != nil {
panic(err)
}
glog.Infof("Finish parsing log file: %+v", k.config)
var info syscall.Sysinfo_t
err = syscall.Sysinfo(&info)
if err != nil {
panic(err)
}
k.uptime = time.Now().Add(time.Duration(-info.Uptime * int64(time.Second)))
glog.Infof("Got system boot time: %v", k.uptime)
k.watcher = NewKernelLogWatcher(k.config.WatcherConfig)
k.buffer = NewLogBuffer(k.config.BufferSize)
// A 1000 size channel should be big enough.
k.output = make(chan *types.Status, 1000)
return k
}
func (k *kernelMonitor) Start() (<-chan *types.Status, error) {
glog.Info("Start kernel monitor")
var err error
k.logCh, err = k.watcher.Watch()
if err != nil {
return nil, err
}
go k.monitorLoop()
return k.output, nil
}
func (k *kernelMonitor) Stop() {
glog.Info("Stop kernel monitor")
k.tomb.Stop()
}
// monitorLoop is the main loop of kernel monitor.
func (k *kernelMonitor) monitorLoop() {
defer k.tomb.Done()
k.output <- defaultStatus() // Update the default status
for {
select {
case log := <-k.logCh:
// Once there is new log, kernel monitor will push it into the log buffer and try
// to match each rule. If any rule is matched, kernel monitor will report a status.
k.buffer.Push(log)
for _, rule := range k.config.Rules {
matched := k.buffer.Match(rule.Pattern)
if len(matched) == 0 {
continue
}
status := k.generateStatus(matched, rule)
glog.Infof("New status generated: %+v", status)
k.output <- status
}
case <-k.tomb.Stopping():
k.watcher.Stop()
glog.Infof("Kernel monitor stopped")
return
}
}
}
// generateStatus generates status from the logs.
func (k *kernelMonitor) generateStatus(logs []*kerntypes.KernelLog, rule kerntypes.Rule) *types.Status {
// We use the timestamp of the first log line as the timestamp of the status.
timestamp := k.generateTimestamp(logs[0].Timestamp)
messages := []string{}
for _, log := range logs {
messages = append(messages, log.Message)
}
message := concatLogs(messages)
var event *types.Event
if rule.Type == kerntypes.Temp {
// For temporary error only generate event
event = &types.Event{
Severity: types.Warn,
Timestamp: timestamp,
Reason: rule.Reason,
Message: message,
}
} else {
// For permanent error changes the condition
k.condition.Type = KernelDeadlockCondition
k.condition.Status = true
k.condition.Transition = timestamp
k.condition.Reason = rule.Reason
k.condition.Message = message
}
return &types.Status{
Source: KernelMonitorSource,
Event: event,
Condition: k.condition,
}
}
// generateTimestamp converts the kernel log time to real time.
func (k *kernelMonitor) generateTimestamp(timestamp int64) time.Time {
return k.uptime.Add(time.Duration(timestamp * int64(time.Microsecond)))
}
// defaultStatus returns the default status with default condition.
func defaultStatus() *types.Status {
return &types.Status{
Source: KernelMonitorSource,
Condition: defaultCondition(),
}
}
func defaultCondition() types.Condition {
return types.Condition{
Type: KernelDeadlockCondition,
Status: false,
Transition: time.Now(),
}
}
// validateRules verifies whether the regular expressions in the rules are valid.
func validateRules(rules []kerntypes.Rule) error {
for _, rule := range rules {
_, err := regexp.Compile(rule.Pattern)
if err != nil {
return err
}
}
return nil
}
+88
View File
@@ -0,0 +1,88 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package kernelmonitor
import (
"reflect"
"testing"
"time"
kerntypes "k8s.io/node-problem-detector/pkg/kernelmonitor/types"
"k8s.io/node-problem-detector/pkg/types"
)
func TestGenerateStatus(t *testing.T) {
uptime := time.Unix(1000, 0)
initCondition := defaultCondition()
logs := []*kerntypes.KernelLog{
{
Timestamp: 100000,
Message: "test message 1",
},
{
Timestamp: 200000,
Message: "test message 2",
},
}
for c, test := range []struct {
rule kerntypes.Rule
expected types.Status
}{
// Do not need Pattern because we don't do pattern match in this test
{
rule: kerntypes.Rule{
Type: kerntypes.Perm,
Reason: "test reason",
},
expected: types.Status{
Source: KernelMonitorSource,
Condition: types.Condition{
Type: KernelDeadlockCondition,
Status: true,
Transition: time.Unix(1000, 100000*1000),
Reason: "test reason",
Message: "test message 1\ntest message 2",
},
},
},
{
rule: kerntypes.Rule{
Type: kerntypes.Temp,
Reason: "test reason",
},
expected: types.Status{
Source: KernelMonitorSource,
Event: &types.Event{
Severity: types.Warn,
Timestamp: time.Unix(1000, 100000*1000),
Reason: "test reason",
Message: "test message 1\ntest message 2",
},
Condition: initCondition,
},
},
} {
k := &kernelMonitor{
condition: initCondition,
uptime: uptime,
}
got := k.generateStatus(logs, test.rule)
if !reflect.DeepEqual(&test.expected, got) {
t.Errorf("case %d: expected status %+v, got %+v", c+1, test.expected, got)
}
}
}
+102
View File
@@ -0,0 +1,102 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package kernelmonitor
import (
"regexp"
"strings"
"k8s.io/node-problem-detector/pkg/kernelmonitor/types"
)
// LogBuffer buffers the logs and supports match in the log buffer with regular expression.
type LogBuffer interface {
// Push pushes log into the log buffer.
Push(*types.KernelLog)
// Match with regular expression in the log buffer.
Match(string) []*types.KernelLog
// String returns a concatenated string of the buffered logs.
String() string
}
type logBuffer struct {
// buffer is a simple ring buffer.
buffer []*types.KernelLog
msg []string
max int
current int
}
// NewLogBuffer creates log buffer with max line number limit. Because we only match logs
// in the log buffer, the max buffer line number is also the max pattern line number we
// support. Smaller buffer line number means less memory and cpu usage, but also means less
// lines of patterns we support.
func NewLogBuffer(maxLines int) *logBuffer {
return &logBuffer{
buffer: make([]*types.KernelLog, maxLines, maxLines),
msg: make([]string, maxLines, maxLines),
max: maxLines,
}
}
func (b *logBuffer) Push(log *types.KernelLog) {
b.buffer[b.current%b.max] = log
b.msg[b.current%b.max] = log.Message
b.current++
}
// TODO(random-liu): Cache regexp if garbage collection becomes a problem someday.
func (b *logBuffer) Match(expr string) []*types.KernelLog {
// The expression should be checked outside, and it must match to the end.
reg := regexp.MustCompile(expr + `\z`)
log := b.String()
loc := reg.FindStringIndex(log)
if loc == nil {
// No match
return nil
}
// reverse index
s := len(log) - loc[0] - 1
total := 0
matched := []*types.KernelLog{}
for i := b.tail(); i >= b.current && b.buffer[i%b.max] != nil; i-- {
matched = append(matched, b.buffer[i%b.max])
total += len(b.msg[i%b.max]) + 1 // Add '\n'
if total > s {
break
}
}
for i := 0; i < len(matched)/2; i++ {
matched[i], matched[len(matched)-i-1] = matched[len(matched)-i-1], matched[i]
}
return matched
}
func (b *logBuffer) String() string {
logs := append(b.msg[b.current%b.max:], b.msg[:b.current%b.max]...)
return concatLogs(logs)
}
// tail returns current tail index.
func (b *logBuffer) tail() int {
return b.current + b.max - 1
}
// concatLogs concatenates multiple lines of logs into one string.
func concatLogs(logs []string) string {
return strings.Join(logs, "\n")
}
+110
View File
@@ -0,0 +1,110 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package kernelmonitor
import (
"reflect"
"testing"
"k8s.io/node-problem-detector/pkg/kernelmonitor/types"
)
func TestPush(t *testing.T) {
for c, test := range []struct {
max int
logs []string
expected string
}{
{
max: 1,
logs: []string{"a", "b"},
expected: "b",
},
{
max: 2,
logs: []string{"a", "b"},
expected: "a\nb",
},
{
max: 2,
logs: []string{"a", "b", "c"},
expected: "b\nc",
},
{
max: 2,
logs: []string{"a", "b", "c", "d"},
expected: "c\nd",
},
} {
b := NewLogBuffer(test.max)
for _, log := range test.logs {
b.Push(&types.KernelLog{Message: log})
}
got := b.String()
if test.expected != got {
t.Errorf("case %d: expected %q, got %q", c+1, test.expected, got)
}
}
}
func TestMatch(t *testing.T) {
max := 4
for c, test := range []struct {
logs []string
exprs []string
expected [][]string
}{
{
// Buffer not full
logs: []string{"a1", "b2"},
exprs: []string{
"a1", // Not including the last line, should not match
"b1", // Not match
"b2", // match
`\w{2}`, // Regexp should work
"a1\nb2", // Including the last line, should match
`a1b2`, // No new line, should not match
},
expected: [][]string{{}, {}, {"b2"}, {"b2"}, {"a1", "b2"}, {}},
},
{
// Buffer full
logs: []string{"a1", "b2", "c3", "d4", "e5"},
exprs: []string{
"(?s)a1.+", // Rotate out, should not match
`[a-z]\d\n[a-z]\d`, // New line should work, and only the one contains the last line should match
`[a-z]\d`, // Multiple match, only the one contains the last line should match
},
expected: [][]string{{}, {"d4", "e5"}, {"e5"}},
},
} {
b := NewLogBuffer(max)
for _, log := range test.logs {
b.Push(&types.KernelLog{Message: log})
}
for i, expr := range test.exprs {
kLogs := b.Match(expr)
got := []string{}
for _, kLog := range kLogs {
got = append(got, kLog.Message)
}
if !reflect.DeepEqual(test.expected[i], got) {
t.Errorf("case %d.%d: expected %v, got %v", c+1, i+1, test.expected[i], got)
}
}
}
}
@@ -0,0 +1,86 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package translator
import (
"fmt"
"strconv"
"strings"
"k8s.io/node-problem-detector/pkg/kernelmonitor/types"
)
// Translator translates a log line into types.KernelLog, so that kernel monitor
// could parse it whatever the original format is.
type Translator interface {
// Translate translates one log line into types.KernelLog.
Translate(string) (*types.KernelLog, error)
}
// defaultTranslator works well for ubuntu and debian, but may not work well with
// other os distros. However it is easy to add a new translator for new os distro.
type defaultTranslator struct{}
// NewDefaultTranslator creates a default translator.
func NewDefaultTranslator() Translator {
return &defaultTranslator{}
}
func (t *defaultTranslator) Translate(line string) (*types.KernelLog, error) {
timestr, message, err := parseLine(line)
if err != nil {
return nil, err
}
timestamp, err := parseTimestamp(timestr)
if err != nil {
return nil, err
}
return &types.KernelLog{
Timestamp: timestamp,
Message: message,
}, nil
}
func parseLine(line string) (string, string, error) {
// Example line: Jan 1 00:00:00 hostname kernel: [0.000000] component: log message
timestampPrefix := "kernel: ["
timestampSuffix := "]"
idx := strings.Index(line, timestampPrefix)
if idx == -1 {
return "", "", fmt.Errorf("can't find timestamp prefix %q in line %q", timestampPrefix, line)
}
line = line[idx+len(timestampPrefix):]
idx = strings.Index(line, timestampSuffix)
if idx == -1 {
return "", "", fmt.Errorf("can't find timestamp suffix %q in line %q", timestampSuffix, line)
}
timestamp := strings.Trim(line[:idx], " ")
message := strings.Trim(line[idx+1:], " ")
return timestamp, message, nil
}
func parseTimestamp(timestamp string) (int64, error) {
f, err := strconv.ParseFloat(timestamp, 64)
if err != nil {
return 0, err
}
// seconds to microseconds
return int64(f * 1000000), nil
}
@@ -0,0 +1,64 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package translator
import (
"testing"
)
func TestDefaultTranslator(t *testing.T) {
tr := NewDefaultTranslator()
testCases := []struct {
input string
err bool
timestamp int64
message string
}{
{
input: "Jan 1 00:00:00 hostname kernel: [9.999999] component: log message",
timestamp: 9999999,
message: "component: log message",
},
{
input: "Jan 1 00:00:00 hostname kernel: [9.999999]",
timestamp: 9999999,
message: "",
},
{
input: "Jan 1 00:00:00 hostname kernel: [9.999999 component: log message",
err: true,
},
{
input: "Jan 1 00:00:00 hostname user: [9.999999] component: log message",
err: true,
},
}
for c, test := range testCases {
log, err := tr.Translate(test.input)
if test.err {
if err == nil {
t.Errorf("case %d: expect error should occur, got %+v, %v", c+1, log, err)
}
continue
}
if test.timestamp != log.Timestamp || test.message != log.Message {
t.Errorf("case %d: expect timestamp: %d, message: %q; got %+v", c+1, test.timestamp, test.message, log)
}
}
}
+45
View File
@@ -0,0 +1,45 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package types
// KernelLog is the log item returned by translator. It's very easy to extend this
// to support other log monitoring, such as docker log monitoring.
type KernelLog struct {
Timestamp int64 // microseconds since kernel boot
Message string
}
// Type is the type of the kernel problem.
type Type string
const (
// Temp means the kernel problem is temporary, only need to report an event.
Temp Type = "temporary"
// Perm means the kernel problem is permanent, need to change the node condition.
Perm Type = "permanent"
)
// Rule describes how kernel monitor should analyze the kernel log.
type Rule struct {
// Type is the type of matched kernel problem.
Type Type `json:"type"`
// Reason is the short reason of the kernel problem.
Reason string `json:"reason"`
// Pattern is the regular expression to match the kernel problem in kernel log.
// Notice that the pattern must match to the end of the line.
Pattern string `json:"pattern"`
}
+47
View File
@@ -0,0 +1,47 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
// Tomb is used to control the lifecycle of a goroutine.
type Tomb struct {
stop chan struct{}
done chan struct{}
}
// NewTomb creates a new tomb.
func NewTomb() *Tomb {
return &Tomb{
stop: make(chan struct{}),
done: make(chan struct{}),
}
}
// Stop is used to stop the goroutine outside.
func (t *Tomb) Stop() {
close(t.stop)
<-t.done
}
// Stopping is used by the goroutine to tell whether it should stop.
func (t *Tomb) Stopping() <-chan struct{} {
return t.stop
}
// Done is used by the goroutine to inform that it has stopped.
func (t *Tomb) Done() {
close(t.done)
}
+39
View File
@@ -0,0 +1,39 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
"reflect"
"testing"
)
func TestTomb(t *testing.T) {
tomb := NewTomb()
workflow := []string{}
expected := []string{"stop", "stopping", "stopped"}
go func() {
defer tomb.Done()
<-tomb.Stopping()
workflow = append(workflow, "stopping")
}()
workflow = append(workflow, "stop")
tomb.Stop()
workflow = append(workflow, "stopped")
if !reflect.DeepEqual(workflow, expected) {
t.Errorf("expected workflow %v, got %v", expected, workflow)
}
}