Vendor in github.com/weaveworks/weave/common

This commit is contained in:
Tom Wilkie
2015-12-04 09:57:44 +00:00
parent d921b528d8
commit a9b868d310
61 changed files with 9890 additions and 0 deletions
+55
View File
@@ -0,0 +1,55 @@
# 0.9.0 (Unreleased)
* logrus/text_formatter: don't emit empty msg
* logrus/hooks/airbrake: move out of main repository
* logrus/hooks/sentry: move out of main repository
* logrus/hooks/papertrail: move out of main repository
* logrus/hooks/bugsnag: move out of main repository
# 0.8.7
* logrus/core: fix possible race (#216)
* logrus/doc: small typo fixes and doc improvements
# 0.8.6
* hooks/raven: allow passing an initialized client
# 0.8.5
* logrus/core: revert #208
# 0.8.4
* formatter/text: fix data race (#218)
# 0.8.3
* logrus/core: fix entry log level (#208)
* logrus/core: improve performance of text formatter by 40%
* logrus/core: expose `LevelHooks` type
* logrus/core: add support for DragonflyBSD and NetBSD
* formatter/text: print structs more verbosely
# 0.8.2
* logrus: fix more Fatal family functions
# 0.8.1
* logrus: fix not exiting on `Fatalf` and `Fatalln`
# 0.8.0
* logrus: defaults to stderr instead of stdout
* hooks/sentry: add special field for `*http.Request`
* formatter/text: ignore Windows for colors
# 0.7.3
* formatter/\*: allow configuration of timestamp layout
# 0.7.2
* formatter/text: Add configuration option for time format (#158)
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Simon Eskildsen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+365
View File
@@ -0,0 +1,365 @@
# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/>&nbsp;[![Build Status](https://travis-ci.org/Sirupsen/logrus.svg?branch=master)](https://travis-ci.org/Sirupsen/logrus)&nbsp;[![godoc reference](https://godoc.org/github.com/Sirupsen/logrus?status.png)][godoc]
Logrus is a structured logger for Go (golang), completely API compatible with
the standard library logger. [Godoc][godoc]. **Please note the Logrus API is not
yet stable (pre 1.0). Logrus itself is completely stable and has been used in
many large deployments. The core API is unlikely to change much but please
version control your Logrus to make sure you aren't fetching latest `master` on
every build.**
Nicely color-coded in development (when a TTY is attached, otherwise just
plain text):
![Colored](http://i.imgur.com/PY7qMwd.png)
With `log.Formatter = new(logrus.JSONFormatter)`, for easy parsing by logstash
or Splunk:
```json
{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the
ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"}
{"level":"warning","msg":"The group's number increased tremendously!",
"number":122,"omg":true,"time":"2014-03-10 19:57:38.562471297 -0400 EDT"}
{"animal":"walrus","level":"info","msg":"A giant walrus appears!",
"size":10,"time":"2014-03-10 19:57:38.562500591 -0400 EDT"}
{"animal":"walrus","level":"info","msg":"Tremendously sized cow enters the ocean.",
"size":9,"time":"2014-03-10 19:57:38.562527896 -0400 EDT"}
{"level":"fatal","msg":"The ice breaks!","number":100,"omg":true,
"time":"2014-03-10 19:57:38.562543128 -0400 EDT"}
```
With the default `log.Formatter = new(&log.TextFormatter{})` when a TTY is not
attached, the output is compatible with the
[logfmt](http://godoc.org/github.com/kr/logfmt) format:
```text
time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8
time="2015-03-26T01:27:38-04:00" level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
time="2015-03-26T01:27:38-04:00" level=warning msg="The group's number increased tremendously!" number=122 omg=true
time="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4
time="2015-03-26T01:27:38-04:00" level=panic msg="It's over 9000!" animal=orca size=9009
time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x2082280c0 map[animal:orca size:9009] 2015-03-26 01:27:38.441574009 -0400 EDT panic It's over 9000!} number=100 omg=true
exit status 1
```
#### Example
The simplest way to use Logrus is simply the package-level exported logger:
```go
package main
import (
log "github.com/Sirupsen/logrus"
)
func main() {
log.WithFields(log.Fields{
"animal": "walrus",
}).Info("A walrus appears")
}
```
Note that it's completely api-compatible with the stdlib logger, so you can
replace your `log` imports everywhere with `log "github.com/Sirupsen/logrus"`
and you'll now have the flexibility of Logrus. You can customize it all you
want:
```go
package main
import (
"os"
log "github.com/Sirupsen/logrus"
)
func init() {
// Log as JSON instead of the default ASCII formatter.
log.SetFormatter(&log.JSONFormatter{})
// Output to stderr instead of stdout, could also be a file.
log.SetOutput(os.Stderr)
// Only log the warning severity or above.
log.SetLevel(log.WarnLevel)
}
func main() {
log.WithFields(log.Fields{
"animal": "walrus",
"size": 10,
}).Info("A group of walrus emerges from the ocean")
log.WithFields(log.Fields{
"omg": true,
"number": 122,
}).Warn("The group's number increased tremendously!")
log.WithFields(log.Fields{
"omg": true,
"number": 100,
}).Fatal("The ice breaks!")
// A common pattern is to re-use fields between logging statements by re-using
// the logrus.Entry returned from WithFields()
contextLogger := log.WithFields(log.Fields{
"common": "this is a common field",
"other": "I also should be logged always",
})
contextLogger.Info("I'll be logged with common and other field")
contextLogger.Info("Me too")
}
```
For more advanced usage such as logging to multiple locations from the same
application, you can also create an instance of the `logrus` Logger:
```go
package main
import (
"github.com/Sirupsen/logrus"
)
// Create a new instance of the logger. You can have any number of instances.
var log = logrus.New()
func main() {
// The API for setting attributes is a little different than the package level
// exported logger. See Godoc.
log.Out = os.Stderr
log.WithFields(logrus.Fields{
"animal": "walrus",
"size": 10,
}).Info("A group of walrus emerges from the ocean")
}
```
#### Fields
Logrus encourages careful, structured logging though logging fields instead of
long, unparseable error messages. For example, instead of: `log.Fatalf("Failed
to send event %s to topic %s with key %d")`, you should log the much more
discoverable:
```go
log.WithFields(log.Fields{
"event": event,
"topic": topic,
"key": key,
}).Fatal("Failed to send event")
```
We've found this API forces you to think about logging in a way that produces
much more useful logging messages. We've been in countless situations where just
a single added field to a log statement that was already there would've saved us
hours. The `WithFields` call is optional.
In general, with Logrus using any of the `printf`-family functions should be
seen as a hint you should add a field, however, you can still use the
`printf`-family functions with Logrus.
#### Hooks
You can add hooks for logging levels. For example to send errors to an exception
tracking service on `Error`, `Fatal` and `Panic`, info to StatsD or log to
multiple places simultaneously, e.g. syslog.
Logrus comes with [built-in hooks](hooks/). Add those, or your custom hook, in
`init`:
```go
import (
log "github.com/Sirupsen/logrus"
"gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "aibrake"
logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog"
"log/syslog"
)
func init() {
// Use the Airbrake hook to report errors that have Error severity or above to
// an exception tracker. You can create custom hooks, see the Hooks section.
log.AddHook(airbrake.NewHook(123, "xyz", "production"))
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
if err != nil {
log.Error("Unable to connect to local syslog daemon")
} else {
log.AddHook(hook)
}
}
```
Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). For the detail, please check the [syslog hook README](hooks/syslog/README.md).
| Hook | Description |
| ----- | ----------- |
| [Airbrake](https://github.com/gemnasium/logrus-airbrake-hook) | Send errors to the Airbrake API V3. Uses the official [`gobrake`](https://github.com/airbrake/gobrake) behind the scenes. |
| [Airbrake "legacy"](https://github.com/gemnasium/logrus-airbrake-legacy-hook) | Send errors to an exception tracking service compatible with the Airbrake API V2. Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes. |
| [Papertrail](https://github.com/polds/logrus-papertrail-hook) | Send errors to the [Papertrail](https://papertrailapp.com) hosted logging service via UDP. |
| [Syslog](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go) | Send errors to remote syslog server. Uses standard library `log/syslog` behind the scenes. |
| [Bugsnag](https://github.com/Shopify/logrus-bugsnag/blob/master/bugsnag.go) | Send errors to the Bugsnag exception tracking service. |
| [Sentry](https://github.com/evalphobia/logrus_sentry) | Send errors to the Sentry error logging and aggregation service. |
| [Hiprus](https://github.com/nubo/hiprus) | Send errors to a channel in hipchat. |
| [Logrusly](https://github.com/sebest/logrusly) | Send logs to [Loggly](https://www.loggly.com/) |
| [Slackrus](https://github.com/johntdyer/slackrus) | Hook for Slack chat. |
| [Journalhook](https://github.com/wercker/journalhook) | Hook for logging to `systemd-journald` |
| [Graylog](https://github.com/gemnasium/logrus-graylog-hook) | Hook for logging to [Graylog](http://graylog2.org/) |
| [Raygun](https://github.com/squirkle/logrus-raygun-hook) | Hook for logging to [Raygun.io](http://raygun.io/) |
| [LFShook](https://github.com/rifflock/lfshook) | Hook for logging to the local filesystem |
| [Honeybadger](https://github.com/agonzalezro/logrus_honeybadger) | Hook for sending exceptions to Honeybadger |
| [Mail](https://github.com/zbindenren/logrus_mail) | Hook for sending exceptions via mail |
| [Rollrus](https://github.com/heroku/rollrus) | Hook for sending errors to rollbar |
| [Fluentd](https://github.com/evalphobia/logrus_fluent) | Hook for logging to fluentd |
| [Mongodb](https://github.com/weekface/mgorus) | Hook for logging to mongodb |
| [InfluxDB](https://github.com/Abramovic/logrus_influxdb) | Hook for logging to influxdb |
| [Octokit](https://github.com/dorajistyle/logrus-octokit-hook) | Hook for logging to github via octokit |
| [DeferPanic](https://github.com/deferpanic/dp-logrus) | Hook for logging to DeferPanic |
#### Level logging
Logrus has six logging levels: Debug, Info, Warning, Error, Fatal and Panic.
```go
log.Debug("Useful debugging information.")
log.Info("Something noteworthy happened!")
log.Warn("You should probably take a look at this.")
log.Error("Something failed but I'm not quitting.")
// Calls os.Exit(1) after logging
log.Fatal("Bye.")
// Calls panic() after logging
log.Panic("I'm bailing.")
```
You can set the logging level on a `Logger`, then it will only log entries with
that severity or anything above it:
```go
// Will log anything that is info or above (warn, error, fatal, panic). Default.
log.SetLevel(log.InfoLevel)
```
It may be useful to set `log.Level = logrus.DebugLevel` in a debug or verbose
environment if your application has that.
#### Entries
Besides the fields added with `WithField` or `WithFields` some fields are
automatically added to all logging events:
1. `time`. The timestamp when the entry was created.
2. `msg`. The logging message passed to `{Info,Warn,Error,Fatal,Panic}` after
the `AddFields` call. E.g. `Failed to send event.`
3. `level`. The logging level. E.g. `info`.
#### Environments
Logrus has no notion of environment.
If you wish for hooks and formatters to only be used in specific environments,
you should handle that yourself. For example, if your application has a global
variable `Environment`, which is a string representation of the environment you
could do:
```go
import (
log "github.com/Sirupsen/logrus"
)
init() {
// do something here to set environment depending on an environment variable
// or command-line flag
if Environment == "production" {
log.SetFormatter(&log.JSONFormatter{})
} else {
// The TextFormatter is default, you don't actually have to do this.
log.SetFormatter(&log.TextFormatter{})
}
}
```
This configuration is how `logrus` was intended to be used, but JSON in
production is mostly only useful if you do log aggregation with tools like
Splunk or Logstash.
#### Formatters
The built-in logging formatters are:
* `logrus.TextFormatter`. Logs the event in colors if stdout is a tty, otherwise
without colors.
* *Note:* to force colored output when there is no TTY, set the `ForceColors`
field to `true`. To force no colored output even if there is a TTY set the
`DisableColors` field to `true`
* `logrus.JSONFormatter`. Logs fields as JSON.
* `logrus/formatters/logstash.LogstashFormatter`. Logs fields as [Logstash](http://logstash.net) Events.
```go
logrus.SetFormatter(&logstash.LogstashFormatter{Type: “application_name"})
```
Third party logging formatters:
* [`prefixed`](https://github.com/x-cray/logrus-prefixed-formatter). Displays log entry source along with alternative layout.
* [`zalgo`](https://github.com/aybabtme/logzalgo). Invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦.
You can define your formatter by implementing the `Formatter` interface,
requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a
`Fields` type (`map[string]interface{}`) with all your fields as well as the
default ones (see Entries section above):
```go
type MyJSONFormatter struct {
}
log.SetFormatter(new(MyJSONFormatter))
func (f *MyJSONFormatter) Format(entry *Entry) ([]byte, error) {
// Note this doesn't include Time, Level and Message which are available on
// the Entry. Consult `godoc` on information about those fields or read the
// source of the official loggers.
serialized, err := json.Marshal(entry.Data)
if err != nil {
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
}
return append(serialized, '\n'), nil
}
```
#### Logger as an `io.Writer`
Logrus can be transformed into an `io.Writer`. That writer is the end of an `io.Pipe` and it is your responsibility to close it.
```go
w := logger.Writer()
defer w.Close()
srv := http.Server{
// create a stdlib log.Logger that writes to
// logrus.Logger.
ErrorLog: log.New(w, "", 0),
}
```
Each line written to that writer will be printed the usual way, using formatters
and hooks. The level for those entries is `info`.
#### Rotation
Log rotation is not provided with Logrus. Log rotation should be done by an
external program (like `logrotate(8)`) that can compress and delete old log
entries. It should not be a feature of the application-level logger.
#### Tools
| Tool | Description |
| ---- | ----------- |
|[Logrus Mate](https://github.com/gogap/logrus_mate)|Logrus mate is a tool for Logrus to manage loggers, you can initial logger's level, hook and formatter by config file, the logger will generated with different config at different environment.|
[godoc]: https://godoc.org/github.com/Sirupsen/logrus
+26
View File
@@ -0,0 +1,26 @@
/*
Package logrus is a structured logger for Go, completely API compatible with the standard library logger.
The simplest way to use Logrus is simply the package-level exported logger:
package main
import (
log "github.com/Sirupsen/logrus"
)
func main() {
log.WithFields(log.Fields{
"animal": "walrus",
"number": 1,
"size": 10,
}).Info("A walrus appears")
}
Output:
time="2015-09-07T08:48:33Z" level=info msg="A walrus appears" animal=walrus number=1 size=10
For a full guide visit https://github.com/Sirupsen/logrus
*/
package logrus
+264
View File
@@ -0,0 +1,264 @@
package logrus
import (
"bytes"
"fmt"
"io"
"os"
"time"
)
// Defines the key when adding errors using WithError.
var ErrorKey = "error"
// An entry is the final or intermediate Logrus logging entry. It contains all
// the fields passed with WithField{,s}. It's finally logged when Debug, Info,
// Warn, Error, Fatal or Panic is called on it. These objects can be reused and
// passed around as much as you wish to avoid field duplication.
type Entry struct {
Logger *Logger
// Contains all the fields set by the user.
Data Fields
// Time at which the log entry was created
Time time.Time
// Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic
Level Level
// Message passed to Debug, Info, Warn, Error, Fatal or Panic
Message string
}
func NewEntry(logger *Logger) *Entry {
return &Entry{
Logger: logger,
// Default is three fields, give a little extra room
Data: make(Fields, 5),
}
}
// Returns a reader for the entry, which is a proxy to the formatter.
func (entry *Entry) Reader() (*bytes.Buffer, error) {
serialized, err := entry.Logger.Formatter.Format(entry)
return bytes.NewBuffer(serialized), err
}
// Returns the string representation from the reader and ultimately the
// formatter.
func (entry *Entry) String() (string, error) {
reader, err := entry.Reader()
if err != nil {
return "", err
}
return reader.String(), err
}
// Add an error as single field (using the key defined in ErrorKey) to the Entry.
func (entry *Entry) WithError(err error) *Entry {
return entry.WithField(ErrorKey, err)
}
// Add a single field to the Entry.
func (entry *Entry) WithField(key string, value interface{}) *Entry {
return entry.WithFields(Fields{key: value})
}
// Add a map of fields to the Entry.
func (entry *Entry) WithFields(fields Fields) *Entry {
data := Fields{}
for k, v := range entry.Data {
data[k] = v
}
for k, v := range fields {
data[k] = v
}
return &Entry{Logger: entry.Logger, Data: data}
}
// This function is not declared with a pointer value because otherwise
// race conditions will occur when using multiple goroutines
func (entry Entry) log(level Level, msg string) {
entry.Time = time.Now()
entry.Level = level
entry.Message = msg
if err := entry.Logger.Hooks.Fire(level, &entry); err != nil {
entry.Logger.mu.Lock()
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
entry.Logger.mu.Unlock()
}
reader, err := entry.Reader()
if err != nil {
entry.Logger.mu.Lock()
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
entry.Logger.mu.Unlock()
}
entry.Logger.mu.Lock()
defer entry.Logger.mu.Unlock()
_, err = io.Copy(entry.Logger.Out, reader)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
}
// To avoid Entry#log() returning a value that only would make sense for
// panic() to use in Entry#Panic(), we avoid the allocation by checking
// directly here.
if level <= PanicLevel {
panic(&entry)
}
}
func (entry *Entry) Debug(args ...interface{}) {
if entry.Logger.Level >= DebugLevel {
entry.log(DebugLevel, fmt.Sprint(args...))
}
}
func (entry *Entry) Print(args ...interface{}) {
entry.Info(args...)
}
func (entry *Entry) Info(args ...interface{}) {
if entry.Logger.Level >= InfoLevel {
entry.log(InfoLevel, fmt.Sprint(args...))
}
}
func (entry *Entry) Warn(args ...interface{}) {
if entry.Logger.Level >= WarnLevel {
entry.log(WarnLevel, fmt.Sprint(args...))
}
}
func (entry *Entry) Warning(args ...interface{}) {
entry.Warn(args...)
}
func (entry *Entry) Error(args ...interface{}) {
if entry.Logger.Level >= ErrorLevel {
entry.log(ErrorLevel, fmt.Sprint(args...))
}
}
func (entry *Entry) Fatal(args ...interface{}) {
if entry.Logger.Level >= FatalLevel {
entry.log(FatalLevel, fmt.Sprint(args...))
}
os.Exit(1)
}
func (entry *Entry) Panic(args ...interface{}) {
if entry.Logger.Level >= PanicLevel {
entry.log(PanicLevel, fmt.Sprint(args...))
}
panic(fmt.Sprint(args...))
}
// Entry Printf family functions
func (entry *Entry) Debugf(format string, args ...interface{}) {
if entry.Logger.Level >= DebugLevel {
entry.Debug(fmt.Sprintf(format, args...))
}
}
func (entry *Entry) Infof(format string, args ...interface{}) {
if entry.Logger.Level >= InfoLevel {
entry.Info(fmt.Sprintf(format, args...))
}
}
func (entry *Entry) Printf(format string, args ...interface{}) {
entry.Infof(format, args...)
}
func (entry *Entry) Warnf(format string, args ...interface{}) {
if entry.Logger.Level >= WarnLevel {
entry.Warn(fmt.Sprintf(format, args...))
}
}
func (entry *Entry) Warningf(format string, args ...interface{}) {
entry.Warnf(format, args...)
}
func (entry *Entry) Errorf(format string, args ...interface{}) {
if entry.Logger.Level >= ErrorLevel {
entry.Error(fmt.Sprintf(format, args...))
}
}
func (entry *Entry) Fatalf(format string, args ...interface{}) {
if entry.Logger.Level >= FatalLevel {
entry.Fatal(fmt.Sprintf(format, args...))
}
os.Exit(1)
}
func (entry *Entry) Panicf(format string, args ...interface{}) {
if entry.Logger.Level >= PanicLevel {
entry.Panic(fmt.Sprintf(format, args...))
}
}
// Entry Println family functions
func (entry *Entry) Debugln(args ...interface{}) {
if entry.Logger.Level >= DebugLevel {
entry.Debug(entry.sprintlnn(args...))
}
}
func (entry *Entry) Infoln(args ...interface{}) {
if entry.Logger.Level >= InfoLevel {
entry.Info(entry.sprintlnn(args...))
}
}
func (entry *Entry) Println(args ...interface{}) {
entry.Infoln(args...)
}
func (entry *Entry) Warnln(args ...interface{}) {
if entry.Logger.Level >= WarnLevel {
entry.Warn(entry.sprintlnn(args...))
}
}
func (entry *Entry) Warningln(args ...interface{}) {
entry.Warnln(args...)
}
func (entry *Entry) Errorln(args ...interface{}) {
if entry.Logger.Level >= ErrorLevel {
entry.Error(entry.sprintlnn(args...))
}
}
func (entry *Entry) Fatalln(args ...interface{}) {
if entry.Logger.Level >= FatalLevel {
entry.Fatal(entry.sprintlnn(args...))
}
os.Exit(1)
}
func (entry *Entry) Panicln(args ...interface{}) {
if entry.Logger.Level >= PanicLevel {
entry.Panic(entry.sprintlnn(args...))
}
}
// Sprintlnn => Sprint no newline. This is to get the behavior of how
// fmt.Sprintln where spaces are always added between operands, regardless of
// their type. Instead of vendoring the Sprintln implementation to spare a
// string allocation, we do the simplest thing.
func (entry *Entry) sprintlnn(args ...interface{}) string {
msg := fmt.Sprintln(args...)
return msg[:len(msg)-1]
}
+77
View File
@@ -0,0 +1,77 @@
package logrus
import (
"bytes"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestEntryWithError(t *testing.T) {
assert := assert.New(t)
defer func() {
ErrorKey = "error"
}()
err := fmt.Errorf("kaboom at layer %d", 4711)
assert.Equal(err, WithError(err).Data["error"])
logger := New()
logger.Out = &bytes.Buffer{}
entry := NewEntry(logger)
assert.Equal(err, entry.WithError(err).Data["error"])
ErrorKey = "err"
assert.Equal(err, entry.WithError(err).Data["err"])
}
func TestEntryPanicln(t *testing.T) {
errBoom := fmt.Errorf("boom time")
defer func() {
p := recover()
assert.NotNil(t, p)
switch pVal := p.(type) {
case *Entry:
assert.Equal(t, "kaboom", pVal.Message)
assert.Equal(t, errBoom, pVal.Data["err"])
default:
t.Fatalf("want type *Entry, got %T: %#v", pVal, pVal)
}
}()
logger := New()
logger.Out = &bytes.Buffer{}
entry := NewEntry(logger)
entry.WithField("err", errBoom).Panicln("kaboom")
}
func TestEntryPanicf(t *testing.T) {
errBoom := fmt.Errorf("boom again")
defer func() {
p := recover()
assert.NotNil(t, p)
switch pVal := p.(type) {
case *Entry:
assert.Equal(t, "kaboom true", pVal.Message)
assert.Equal(t, errBoom, pVal.Data["err"])
default:
t.Fatalf("want type *Entry, got %T: %#v", pVal, pVal)
}
}()
logger := New()
logger.Out = &bytes.Buffer{}
entry := NewEntry(logger)
entry.WithField("err", errBoom).Panicf("kaboom %v", true)
}
+50
View File
@@ -0,0 +1,50 @@
package main
import (
"github.com/Sirupsen/logrus"
)
var log = logrus.New()
func init() {
log.Formatter = new(logrus.JSONFormatter)
log.Formatter = new(logrus.TextFormatter) // default
log.Level = logrus.DebugLevel
}
func main() {
defer func() {
err := recover()
if err != nil {
log.WithFields(logrus.Fields{
"omg": true,
"err": err,
"number": 100,
}).Fatal("The ice breaks!")
}
}()
log.WithFields(logrus.Fields{
"animal": "walrus",
"number": 8,
}).Debug("Started observing beach")
log.WithFields(logrus.Fields{
"animal": "walrus",
"size": 10,
}).Info("A group of walrus emerges from the ocean")
log.WithFields(logrus.Fields{
"omg": true,
"number": 122,
}).Warn("The group's number increased tremendously!")
log.WithFields(logrus.Fields{
"temperature": -4,
}).Debug("Temperature changes")
log.WithFields(logrus.Fields{
"animal": "orca",
"size": 9009,
}).Panic("It's over 9000!")
}
+30
View File
@@ -0,0 +1,30 @@
package main
import (
"github.com/Sirupsen/logrus"
"gopkg.in/gemnasium/logrus-airbrake-hook.v2"
)
var log = logrus.New()
func init() {
log.Formatter = new(logrus.TextFormatter) // default
log.Hooks.Add(airbrake.NewHook(123, "xyz", "development"))
}
func main() {
log.WithFields(logrus.Fields{
"animal": "walrus",
"size": 10,
}).Info("A group of walrus emerges from the ocean")
log.WithFields(logrus.Fields{
"omg": true,
"number": 122,
}).Warn("The group's number increased tremendously!")
log.WithFields(logrus.Fields{
"omg": true,
"number": 100,
}).Fatal("The ice breaks!")
}
+193
View File
@@ -0,0 +1,193 @@
package logrus
import (
"io"
)
var (
// std is the name of the standard logger in stdlib `log`
std = New()
)
func StandardLogger() *Logger {
return std
}
// SetOutput sets the standard logger output.
func SetOutput(out io.Writer) {
std.mu.Lock()
defer std.mu.Unlock()
std.Out = out
}
// SetFormatter sets the standard logger formatter.
func SetFormatter(formatter Formatter) {
std.mu.Lock()
defer std.mu.Unlock()
std.Formatter = formatter
}
// SetLevel sets the standard logger level.
func SetLevel(level Level) {
std.mu.Lock()
defer std.mu.Unlock()
std.Level = level
}
// GetLevel returns the standard logger level.
func GetLevel() Level {
std.mu.Lock()
defer std.mu.Unlock()
return std.Level
}
// AddHook adds a hook to the standard logger hooks.
func AddHook(hook Hook) {
std.mu.Lock()
defer std.mu.Unlock()
std.Hooks.Add(hook)
}
// WithError creates an entry from the standard logger and adds an error to it, using the value defined in ErrorKey as key.
func WithError(err error) *Entry {
return std.WithField(ErrorKey, err)
}
// WithField creates an entry from the standard logger and adds a field to
// it. If you want multiple fields, use `WithFields`.
//
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
// or Panic on the Entry it returns.
func WithField(key string, value interface{}) *Entry {
return std.WithField(key, value)
}
// WithFields creates an entry from the standard logger and adds multiple
// fields to it. This is simply a helper for `WithField`, invoking it
// once for each field.
//
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
// or Panic on the Entry it returns.
func WithFields(fields Fields) *Entry {
return std.WithFields(fields)
}
// Debug logs a message at level Debug on the standard logger.
func Debug(args ...interface{}) {
std.Debug(args...)
}
// Print logs a message at level Info on the standard logger.
func Print(args ...interface{}) {
std.Print(args...)
}
// Info logs a message at level Info on the standard logger.
func Info(args ...interface{}) {
std.Info(args...)
}
// Warn logs a message at level Warn on the standard logger.
func Warn(args ...interface{}) {
std.Warn(args...)
}
// Warning logs a message at level Warn on the standard logger.
func Warning(args ...interface{}) {
std.Warning(args...)
}
// Error logs a message at level Error on the standard logger.
func Error(args ...interface{}) {
std.Error(args...)
}
// Panic logs a message at level Panic on the standard logger.
func Panic(args ...interface{}) {
std.Panic(args...)
}
// Fatal logs a message at level Fatal on the standard logger.
func Fatal(args ...interface{}) {
std.Fatal(args...)
}
// Debugf logs a message at level Debug on the standard logger.
func Debugf(format string, args ...interface{}) {
std.Debugf(format, args...)
}
// Printf logs a message at level Info on the standard logger.
func Printf(format string, args ...interface{}) {
std.Printf(format, args...)
}
// Infof logs a message at level Info on the standard logger.
func Infof(format string, args ...interface{}) {
std.Infof(format, args...)
}
// Warnf logs a message at level Warn on the standard logger.
func Warnf(format string, args ...interface{}) {
std.Warnf(format, args...)
}
// Warningf logs a message at level Warn on the standard logger.
func Warningf(format string, args ...interface{}) {
std.Warningf(format, args...)
}
// Errorf logs a message at level Error on the standard logger.
func Errorf(format string, args ...interface{}) {
std.Errorf(format, args...)
}
// Panicf logs a message at level Panic on the standard logger.
func Panicf(format string, args ...interface{}) {
std.Panicf(format, args...)
}
// Fatalf logs a message at level Fatal on the standard logger.
func Fatalf(format string, args ...interface{}) {
std.Fatalf(format, args...)
}
// Debugln logs a message at level Debug on the standard logger.
func Debugln(args ...interface{}) {
std.Debugln(args...)
}
// Println logs a message at level Info on the standard logger.
func Println(args ...interface{}) {
std.Println(args...)
}
// Infoln logs a message at level Info on the standard logger.
func Infoln(args ...interface{}) {
std.Infoln(args...)
}
// Warnln logs a message at level Warn on the standard logger.
func Warnln(args ...interface{}) {
std.Warnln(args...)
}
// Warningln logs a message at level Warn on the standard logger.
func Warningln(args ...interface{}) {
std.Warningln(args...)
}
// Errorln logs a message at level Error on the standard logger.
func Errorln(args ...interface{}) {
std.Errorln(args...)
}
// Panicln logs a message at level Panic on the standard logger.
func Panicln(args ...interface{}) {
std.Panicln(args...)
}
// Fatalln logs a message at level Fatal on the standard logger.
func Fatalln(args ...interface{}) {
std.Fatalln(args...)
}
+48
View File
@@ -0,0 +1,48 @@
package logrus
import "time"
const DefaultTimestampFormat = time.RFC3339
// The Formatter interface is used to implement a custom Formatter. It takes an
// `Entry`. It exposes all the fields, including the default ones:
//
// * `entry.Data["msg"]`. The message passed from Info, Warn, Error ..
// * `entry.Data["time"]`. The timestamp.
// * `entry.Data["level"]. The level the entry was logged at.
//
// Any additional fields added with `WithField` or `WithFields` are also in
// `entry.Data`. Format is expected to return an array of bytes which are then
// logged to `logger.Out`.
type Formatter interface {
Format(*Entry) ([]byte, error)
}
// This is to not silently overwrite `time`, `msg` and `level` fields when
// dumping it. If this code wasn't there doing:
//
// logrus.WithField("level", 1).Info("hello")
//
// Would just silently drop the user provided level. Instead with this code
// it'll logged as:
//
// {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."}
//
// It's not exported because it's still using Data in an opinionated way. It's to
// avoid code duplication between the two default formatters.
func prefixFieldClashes(data Fields) {
_, ok := data["time"]
if ok {
data["fields.time"] = data["time"]
}
_, ok = data["msg"]
if ok {
data["fields.msg"] = data["msg"]
}
_, ok = data["level"]
if ok {
data["fields.level"] = data["level"]
}
}
+98
View File
@@ -0,0 +1,98 @@
package logrus
import (
"fmt"
"testing"
"time"
)
// smallFields is a small size data set for benchmarking
var smallFields = Fields{
"foo": "bar",
"baz": "qux",
"one": "two",
"three": "four",
}
// largeFields is a large size data set for benchmarking
var largeFields = Fields{
"foo": "bar",
"baz": "qux",
"one": "two",
"three": "four",
"five": "six",
"seven": "eight",
"nine": "ten",
"eleven": "twelve",
"thirteen": "fourteen",
"fifteen": "sixteen",
"seventeen": "eighteen",
"nineteen": "twenty",
"a": "b",
"c": "d",
"e": "f",
"g": "h",
"i": "j",
"k": "l",
"m": "n",
"o": "p",
"q": "r",
"s": "t",
"u": "v",
"w": "x",
"y": "z",
"this": "will",
"make": "thirty",
"entries": "yeah",
}
var errorFields = Fields{
"foo": fmt.Errorf("bar"),
"baz": fmt.Errorf("qux"),
}
func BenchmarkErrorTextFormatter(b *testing.B) {
doBenchmark(b, &TextFormatter{DisableColors: true}, errorFields)
}
func BenchmarkSmallTextFormatter(b *testing.B) {
doBenchmark(b, &TextFormatter{DisableColors: true}, smallFields)
}
func BenchmarkLargeTextFormatter(b *testing.B) {
doBenchmark(b, &TextFormatter{DisableColors: true}, largeFields)
}
func BenchmarkSmallColoredTextFormatter(b *testing.B) {
doBenchmark(b, &TextFormatter{ForceColors: true}, smallFields)
}
func BenchmarkLargeColoredTextFormatter(b *testing.B) {
doBenchmark(b, &TextFormatter{ForceColors: true}, largeFields)
}
func BenchmarkSmallJSONFormatter(b *testing.B) {
doBenchmark(b, &JSONFormatter{}, smallFields)
}
func BenchmarkLargeJSONFormatter(b *testing.B) {
doBenchmark(b, &JSONFormatter{}, largeFields)
}
func doBenchmark(b *testing.B, formatter Formatter, fields Fields) {
entry := &Entry{
Time: time.Time{},
Level: InfoLevel,
Message: "message",
Data: fields,
}
var d []byte
var err error
for i := 0; i < b.N; i++ {
d, err = formatter.Format(entry)
if err != nil {
b.Fatal(err)
}
b.SetBytes(int64(len(d)))
}
}
+56
View File
@@ -0,0 +1,56 @@
package logstash
import (
"encoding/json"
"fmt"
"github.com/Sirupsen/logrus"
)
// Formatter generates json in logstash format.
// Logstash site: http://logstash.net/
type LogstashFormatter struct {
Type string // if not empty use for logstash type field.
// TimestampFormat sets the format used for timestamps.
TimestampFormat string
}
func (f *LogstashFormatter) Format(entry *logrus.Entry) ([]byte, error) {
entry.Data["@version"] = 1
if f.TimestampFormat == "" {
f.TimestampFormat = logrus.DefaultTimestampFormat
}
entry.Data["@timestamp"] = entry.Time.Format(f.TimestampFormat)
// set message field
v, ok := entry.Data["message"]
if ok {
entry.Data["fields.message"] = v
}
entry.Data["message"] = entry.Message
// set level field
v, ok = entry.Data["level"]
if ok {
entry.Data["fields.level"] = v
}
entry.Data["level"] = entry.Level.String()
// set type field
if f.Type != "" {
v, ok = entry.Data["type"]
if ok {
entry.Data["fields.type"] = v
}
entry.Data["type"] = f.Type
}
serialized, err := json.Marshal(entry.Data)
if err != nil {
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
}
return append(serialized, '\n'), nil
}
+52
View File
@@ -0,0 +1,52 @@
package logstash
import (
"bytes"
"encoding/json"
"github.com/Sirupsen/logrus"
"github.com/stretchr/testify/assert"
"testing"
)
func TestLogstashFormatter(t *testing.T) {
assert := assert.New(t)
lf := LogstashFormatter{Type: "abc"}
fields := logrus.Fields{
"message": "def",
"level": "ijk",
"type": "lmn",
"one": 1,
"pi": 3.14,
"bool": true,
}
entry := logrus.WithFields(fields)
entry.Message = "msg"
entry.Level = logrus.InfoLevel
b, _ := lf.Format(entry)
var data map[string]interface{}
dec := json.NewDecoder(bytes.NewReader(b))
dec.UseNumber()
dec.Decode(&data)
// base fields
assert.Equal(json.Number("1"), data["@version"])
assert.NotEmpty(data["@timestamp"])
assert.Equal("abc", data["type"])
assert.Equal("msg", data["message"])
assert.Equal("info", data["level"])
// substituted fields
assert.Equal("def", data["fields.message"])
assert.Equal("ijk", data["fields.level"])
assert.Equal("lmn", data["fields.type"])
// formats
assert.Equal(json.Number("1"), data["one"])
assert.Equal(json.Number("3.14"), data["pi"])
assert.Equal(true, data["bool"])
}
+122
View File
@@ -0,0 +1,122 @@
package logrus
import (
"testing"
"github.com/stretchr/testify/assert"
)
type TestHook struct {
Fired bool
}
func (hook *TestHook) Fire(entry *Entry) error {
hook.Fired = true
return nil
}
func (hook *TestHook) Levels() []Level {
return []Level{
DebugLevel,
InfoLevel,
WarnLevel,
ErrorLevel,
FatalLevel,
PanicLevel,
}
}
func TestHookFires(t *testing.T) {
hook := new(TestHook)
LogAndAssertJSON(t, func(log *Logger) {
log.Hooks.Add(hook)
assert.Equal(t, hook.Fired, false)
log.Print("test")
}, func(fields Fields) {
assert.Equal(t, hook.Fired, true)
})
}
type ModifyHook struct {
}
func (hook *ModifyHook) Fire(entry *Entry) error {
entry.Data["wow"] = "whale"
return nil
}
func (hook *ModifyHook) Levels() []Level {
return []Level{
DebugLevel,
InfoLevel,
WarnLevel,
ErrorLevel,
FatalLevel,
PanicLevel,
}
}
func TestHookCanModifyEntry(t *testing.T) {
hook := new(ModifyHook)
LogAndAssertJSON(t, func(log *Logger) {
log.Hooks.Add(hook)
log.WithField("wow", "elephant").Print("test")
}, func(fields Fields) {
assert.Equal(t, fields["wow"], "whale")
})
}
func TestCanFireMultipleHooks(t *testing.T) {
hook1 := new(ModifyHook)
hook2 := new(TestHook)
LogAndAssertJSON(t, func(log *Logger) {
log.Hooks.Add(hook1)
log.Hooks.Add(hook2)
log.WithField("wow", "elephant").Print("test")
}, func(fields Fields) {
assert.Equal(t, fields["wow"], "whale")
assert.Equal(t, hook2.Fired, true)
})
}
type ErrorHook struct {
Fired bool
}
func (hook *ErrorHook) Fire(entry *Entry) error {
hook.Fired = true
return nil
}
func (hook *ErrorHook) Levels() []Level {
return []Level{
ErrorLevel,
}
}
func TestErrorHookShouldntFireOnInfo(t *testing.T) {
hook := new(ErrorHook)
LogAndAssertJSON(t, func(log *Logger) {
log.Hooks.Add(hook)
log.Info("test")
}, func(fields Fields) {
assert.Equal(t, hook.Fired, false)
})
}
func TestErrorHookShouldFireOnError(t *testing.T) {
hook := new(ErrorHook)
LogAndAssertJSON(t, func(log *Logger) {
log.Hooks.Add(hook)
log.Error("test")
}, func(fields Fields) {
assert.Equal(t, hook.Fired, true)
})
}
+34
View File
@@ -0,0 +1,34 @@
package logrus
// A hook to be fired when logging on the logging levels returned from
// `Levels()` on your implementation of the interface. Note that this is not
// fired in a goroutine or a channel with workers, you should handle such
// functionality yourself if your call is non-blocking and you don't wish for
// the logging calls for levels returned from `Levels()` to block.
type Hook interface {
Levels() []Level
Fire(*Entry) error
}
// Internal type for storing the hooks on a logger instance.
type LevelHooks map[Level][]Hook
// Add a hook to an instance of logger. This is called with
// `log.Hooks.Add(new(MyHook))` where `MyHook` implements the `Hook` interface.
func (hooks LevelHooks) Add(hook Hook) {
for _, level := range hook.Levels() {
hooks[level] = append(hooks[level], hook)
}
}
// Fire all the hooks for the passed level. Used by `entry.log` to fire
// appropriate hooks for a log entry.
func (hooks LevelHooks) Fire(level Level, entry *Entry) error {
for _, hook := range hooks[level] {
if err := hook.Fire(entry); err != nil {
return err
}
}
return nil
}
+39
View File
@@ -0,0 +1,39 @@
# Syslog Hooks for Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/>
## Usage
```go
import (
"log/syslog"
"github.com/Sirupsen/logrus"
logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog"
)
func main() {
log := logrus.New()
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
if err == nil {
log.Hooks.Add(hook)
}
}
```
If you want to connect to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). Just assign empty string to the first two parameters of `NewSyslogHook`. It should look like the following.
```go
import (
"log/syslog"
"github.com/Sirupsen/logrus"
logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog"
)
func main() {
log := logrus.New()
hook, err := logrus_syslog.NewSyslogHook("", "", syslog.LOG_INFO, "")
if err == nil {
log.Hooks.Add(hook)
}
}
```
+61
View File
@@ -0,0 +1,61 @@
// +build !windows,!nacl,!plan9
package logrus_syslog
import (
"fmt"
"github.com/Sirupsen/logrus"
"log/syslog"
"os"
)
// SyslogHook to send logs via syslog.
type SyslogHook struct {
Writer *syslog.Writer
SyslogNetwork string
SyslogRaddr string
}
// Creates a hook to be added to an instance of logger. This is called with
// `hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_DEBUG, "")`
// `if err == nil { log.Hooks.Add(hook) }`
func NewSyslogHook(network, raddr string, priority syslog.Priority, tag string) (*SyslogHook, error) {
w, err := syslog.Dial(network, raddr, priority, tag)
return &SyslogHook{w, network, raddr}, err
}
func (hook *SyslogHook) Fire(entry *logrus.Entry) error {
line, err := entry.String()
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to read entry, %v", err)
return err
}
switch entry.Level {
case logrus.PanicLevel:
return hook.Writer.Crit(line)
case logrus.FatalLevel:
return hook.Writer.Crit(line)
case logrus.ErrorLevel:
return hook.Writer.Err(line)
case logrus.WarnLevel:
return hook.Writer.Warning(line)
case logrus.InfoLevel:
return hook.Writer.Info(line)
case logrus.DebugLevel:
return hook.Writer.Debug(line)
default:
return nil
}
}
func (hook *SyslogHook) Levels() []logrus.Level {
return []logrus.Level{
logrus.PanicLevel,
logrus.FatalLevel,
logrus.ErrorLevel,
logrus.WarnLevel,
logrus.InfoLevel,
logrus.DebugLevel,
}
}
+26
View File
@@ -0,0 +1,26 @@
package logrus_syslog
import (
"github.com/Sirupsen/logrus"
"log/syslog"
"testing"
)
func TestLocalhostAddAndPrint(t *testing.T) {
log := logrus.New()
hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
if err != nil {
t.Errorf("Unable to connect to local syslog.")
}
log.Hooks.Add(hook)
for _, level := range hook.Levels() {
if len(log.Hooks[level]) != 1 {
t.Errorf("SyslogHook was not added. The length of log.Hooks[%v]: %v", level, len(log.Hooks[level]))
}
}
log.Info("Congratulations!")
}
+41
View File
@@ -0,0 +1,41 @@
package logrus
import (
"encoding/json"
"fmt"
)
type JSONFormatter struct {
// TimestampFormat sets the format used for marshaling timestamps.
TimestampFormat string
}
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
data := make(Fields, len(entry.Data)+3)
for k, v := range entry.Data {
switch v := v.(type) {
case error:
// Otherwise errors are ignored by `encoding/json`
// https://github.com/Sirupsen/logrus/issues/137
data[k] = v.Error()
default:
data[k] = v
}
}
prefixFieldClashes(data)
timestampFormat := f.TimestampFormat
if timestampFormat == "" {
timestampFormat = DefaultTimestampFormat
}
data["time"] = entry.Time.Format(timestampFormat)
data["msg"] = entry.Message
data["level"] = entry.Level.String()
serialized, err := json.Marshal(data)
if err != nil {
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
}
return append(serialized, '\n'), nil
}
+120
View File
@@ -0,0 +1,120 @@
package logrus
import (
"encoding/json"
"errors"
"testing"
)
func TestErrorNotLost(t *testing.T) {
formatter := &JSONFormatter{}
b, err := formatter.Format(WithField("error", errors.New("wild walrus")))
if err != nil {
t.Fatal("Unable to format entry: ", err)
}
entry := make(map[string]interface{})
err = json.Unmarshal(b, &entry)
if err != nil {
t.Fatal("Unable to unmarshal formatted entry: ", err)
}
if entry["error"] != "wild walrus" {
t.Fatal("Error field not set")
}
}
func TestErrorNotLostOnFieldNotNamedError(t *testing.T) {
formatter := &JSONFormatter{}
b, err := formatter.Format(WithField("omg", errors.New("wild walrus")))
if err != nil {
t.Fatal("Unable to format entry: ", err)
}
entry := make(map[string]interface{})
err = json.Unmarshal(b, &entry)
if err != nil {
t.Fatal("Unable to unmarshal formatted entry: ", err)
}
if entry["omg"] != "wild walrus" {
t.Fatal("Error field not set")
}
}
func TestFieldClashWithTime(t *testing.T) {
formatter := &JSONFormatter{}
b, err := formatter.Format(WithField("time", "right now!"))
if err != nil {
t.Fatal("Unable to format entry: ", err)
}
entry := make(map[string]interface{})
err = json.Unmarshal(b, &entry)
if err != nil {
t.Fatal("Unable to unmarshal formatted entry: ", err)
}
if entry["fields.time"] != "right now!" {
t.Fatal("fields.time not set to original time field")
}
if entry["time"] != "0001-01-01T00:00:00Z" {
t.Fatal("time field not set to current time, was: ", entry["time"])
}
}
func TestFieldClashWithMsg(t *testing.T) {
formatter := &JSONFormatter{}
b, err := formatter.Format(WithField("msg", "something"))
if err != nil {
t.Fatal("Unable to format entry: ", err)
}
entry := make(map[string]interface{})
err = json.Unmarshal(b, &entry)
if err != nil {
t.Fatal("Unable to unmarshal formatted entry: ", err)
}
if entry["fields.msg"] != "something" {
t.Fatal("fields.msg not set to original msg field")
}
}
func TestFieldClashWithLevel(t *testing.T) {
formatter := &JSONFormatter{}
b, err := formatter.Format(WithField("level", "something"))
if err != nil {
t.Fatal("Unable to format entry: ", err)
}
entry := make(map[string]interface{})
err = json.Unmarshal(b, &entry)
if err != nil {
t.Fatal("Unable to unmarshal formatted entry: ", err)
}
if entry["fields.level"] != "something" {
t.Fatal("fields.level not set to original level field")
}
}
func TestJSONEntryEndsWithNewline(t *testing.T) {
formatter := &JSONFormatter{}
b, err := formatter.Format(WithField("level", "something"))
if err != nil {
t.Fatal("Unable to format entry: ", err)
}
if b[len(b)-1] != '\n' {
t.Fatal("Expected JSON log entry to end with a newline")
}
}
+212
View File
@@ -0,0 +1,212 @@
package logrus
import (
"io"
"os"
"sync"
)
type Logger struct {
// The logs are `io.Copy`'d to this in a mutex. It's common to set this to a
// file, or leave it default which is `os.Stderr`. You can also set this to
// something more adventorous, such as logging to Kafka.
Out io.Writer
// Hooks for the logger instance. These allow firing events based on logging
// levels and log entries. For example, to send errors to an error tracking
// service, log to StatsD or dump the core on fatal errors.
Hooks LevelHooks
// All log entries pass through the formatter before logged to Out. The
// included formatters are `TextFormatter` and `JSONFormatter` for which
// TextFormatter is the default. In development (when a TTY is attached) it
// logs with colors, but to a file it wouldn't. You can easily implement your
// own that implements the `Formatter` interface, see the `README` or included
// formatters for examples.
Formatter Formatter
// The logging level the logger should log at. This is typically (and defaults
// to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
// logged. `logrus.Debug` is useful in
Level Level
// Used to sync writing to the log.
mu sync.Mutex
}
// Creates a new logger. Configuration should be set by changing `Formatter`,
// `Out` and `Hooks` directly on the default logger instance. You can also just
// instantiate your own:
//
// var log = &Logger{
// Out: os.Stderr,
// Formatter: new(JSONFormatter),
// Hooks: make(LevelHooks),
// Level: logrus.DebugLevel,
// }
//
// It's recommended to make this a global instance called `log`.
func New() *Logger {
return &Logger{
Out: os.Stderr,
Formatter: new(TextFormatter),
Hooks: make(LevelHooks),
Level: InfoLevel,
}
}
// Adds a field to the log entry, note that you it doesn't log until you call
// Debug, Print, Info, Warn, Fatal or Panic. It only creates a log entry.
// If you want multiple fields, use `WithFields`.
func (logger *Logger) WithField(key string, value interface{}) *Entry {
return NewEntry(logger).WithField(key, value)
}
// Adds a struct of fields to the log entry. All it does is call `WithField` for
// each `Field`.
func (logger *Logger) WithFields(fields Fields) *Entry {
return NewEntry(logger).WithFields(fields)
}
// Add an error as single field to the log entry. All it does is call
// `WithError` for the given `error`.
func (logger *Logger) WithError(err error) *Entry {
return NewEntry(logger).WithError(err)
}
func (logger *Logger) Debugf(format string, args ...interface{}) {
if logger.Level >= DebugLevel {
NewEntry(logger).Debugf(format, args...)
}
}
func (logger *Logger) Infof(format string, args ...interface{}) {
if logger.Level >= InfoLevel {
NewEntry(logger).Infof(format, args...)
}
}
func (logger *Logger) Printf(format string, args ...interface{}) {
NewEntry(logger).Printf(format, args...)
}
func (logger *Logger) Warnf(format string, args ...interface{}) {
if logger.Level >= WarnLevel {
NewEntry(logger).Warnf(format, args...)
}
}
func (logger *Logger) Warningf(format string, args ...interface{}) {
if logger.Level >= WarnLevel {
NewEntry(logger).Warnf(format, args...)
}
}
func (logger *Logger) Errorf(format string, args ...interface{}) {
if logger.Level >= ErrorLevel {
NewEntry(logger).Errorf(format, args...)
}
}
func (logger *Logger) Fatalf(format string, args ...interface{}) {
if logger.Level >= FatalLevel {
NewEntry(logger).Fatalf(format, args...)
}
os.Exit(1)
}
func (logger *Logger) Panicf(format string, args ...interface{}) {
if logger.Level >= PanicLevel {
NewEntry(logger).Panicf(format, args...)
}
}
func (logger *Logger) Debug(args ...interface{}) {
if logger.Level >= DebugLevel {
NewEntry(logger).Debug(args...)
}
}
func (logger *Logger) Info(args ...interface{}) {
if logger.Level >= InfoLevel {
NewEntry(logger).Info(args...)
}
}
func (logger *Logger) Print(args ...interface{}) {
NewEntry(logger).Info(args...)
}
func (logger *Logger) Warn(args ...interface{}) {
if logger.Level >= WarnLevel {
NewEntry(logger).Warn(args...)
}
}
func (logger *Logger) Warning(args ...interface{}) {
if logger.Level >= WarnLevel {
NewEntry(logger).Warn(args...)
}
}
func (logger *Logger) Error(args ...interface{}) {
if logger.Level >= ErrorLevel {
NewEntry(logger).Error(args...)
}
}
func (logger *Logger) Fatal(args ...interface{}) {
if logger.Level >= FatalLevel {
NewEntry(logger).Fatal(args...)
}
os.Exit(1)
}
func (logger *Logger) Panic(args ...interface{}) {
if logger.Level >= PanicLevel {
NewEntry(logger).Panic(args...)
}
}
func (logger *Logger) Debugln(args ...interface{}) {
if logger.Level >= DebugLevel {
NewEntry(logger).Debugln(args...)
}
}
func (logger *Logger) Infoln(args ...interface{}) {
if logger.Level >= InfoLevel {
NewEntry(logger).Infoln(args...)
}
}
func (logger *Logger) Println(args ...interface{}) {
NewEntry(logger).Println(args...)
}
func (logger *Logger) Warnln(args ...interface{}) {
if logger.Level >= WarnLevel {
NewEntry(logger).Warnln(args...)
}
}
func (logger *Logger) Warningln(args ...interface{}) {
if logger.Level >= WarnLevel {
NewEntry(logger).Warnln(args...)
}
}
func (logger *Logger) Errorln(args ...interface{}) {
if logger.Level >= ErrorLevel {
NewEntry(logger).Errorln(args...)
}
}
func (logger *Logger) Fatalln(args ...interface{}) {
if logger.Level >= FatalLevel {
NewEntry(logger).Fatalln(args...)
}
os.Exit(1)
}
func (logger *Logger) Panicln(args ...interface{}) {
if logger.Level >= PanicLevel {
NewEntry(logger).Panicln(args...)
}
}
+98
View File
@@ -0,0 +1,98 @@
package logrus
import (
"fmt"
"log"
)
// Fields type, used to pass to `WithFields`.
type Fields map[string]interface{}
// Level type
type Level uint8
// Convert the Level to a string. E.g. PanicLevel becomes "panic".
func (level Level) String() string {
switch level {
case DebugLevel:
return "debug"
case InfoLevel:
return "info"
case WarnLevel:
return "warning"
case ErrorLevel:
return "error"
case FatalLevel:
return "fatal"
case PanicLevel:
return "panic"
}
return "unknown"
}
// ParseLevel takes a string level and returns the Logrus log level constant.
func ParseLevel(lvl string) (Level, error) {
switch lvl {
case "panic":
return PanicLevel, nil
case "fatal":
return FatalLevel, nil
case "error":
return ErrorLevel, nil
case "warn", "warning":
return WarnLevel, nil
case "info":
return InfoLevel, nil
case "debug":
return DebugLevel, nil
}
var l Level
return l, fmt.Errorf("not a valid logrus Level: %q", lvl)
}
// These are the different logging levels. You can set the logging level to log
// on your instance of logger, obtained with `logrus.New()`.
const (
// PanicLevel level, highest level of severity. Logs and then calls panic with the
// message passed to Debug, Info, ...
PanicLevel Level = iota
// FatalLevel level. Logs and then calls `os.Exit(1)`. It will exit even if the
// logging level is set to Panic.
FatalLevel
// ErrorLevel level. Logs. Used for errors that should definitely be noted.
// Commonly used for hooks to send errors to an error tracking service.
ErrorLevel
// WarnLevel level. Non-critical entries that deserve eyes.
WarnLevel
// InfoLevel level. General operational entries about what's going on inside the
// application.
InfoLevel
// DebugLevel level. Usually only enabled when debugging. Very verbose logging.
DebugLevel
)
// Won't compile if StdLogger can't be realized by a log.Logger
var (
_ StdLogger = &log.Logger{}
_ StdLogger = &Entry{}
_ StdLogger = &Logger{}
)
// StdLogger is what your logrus-enabled library should take, that way
// it'll accept a stdlib logger and a logrus logger. There's no standard
// interface, this is the closest we get, unfortunately.
type StdLogger interface {
Print(...interface{})
Printf(string, ...interface{})
Println(...interface{})
Fatal(...interface{})
Fatalf(string, ...interface{})
Fatalln(...interface{})
Panic(...interface{})
Panicf(string, ...interface{})
Panicln(...interface{})
}
+301
View File
@@ -0,0 +1,301 @@
package logrus
import (
"bytes"
"encoding/json"
"strconv"
"strings"
"sync"
"testing"
"github.com/stretchr/testify/assert"
)
func LogAndAssertJSON(t *testing.T, log func(*Logger), assertions func(fields Fields)) {
var buffer bytes.Buffer
var fields Fields
logger := New()
logger.Out = &buffer
logger.Formatter = new(JSONFormatter)
log(logger)
err := json.Unmarshal(buffer.Bytes(), &fields)
assert.Nil(t, err)
assertions(fields)
}
func LogAndAssertText(t *testing.T, log func(*Logger), assertions func(fields map[string]string)) {
var buffer bytes.Buffer
logger := New()
logger.Out = &buffer
logger.Formatter = &TextFormatter{
DisableColors: true,
}
log(logger)
fields := make(map[string]string)
for _, kv := range strings.Split(buffer.String(), " ") {
if !strings.Contains(kv, "=") {
continue
}
kvArr := strings.Split(kv, "=")
key := strings.TrimSpace(kvArr[0])
val := kvArr[1]
if kvArr[1][0] == '"' {
var err error
val, err = strconv.Unquote(val)
assert.NoError(t, err)
}
fields[key] = val
}
assertions(fields)
}
func TestPrint(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.Print("test")
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "test")
assert.Equal(t, fields["level"], "info")
})
}
func TestInfo(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.Info("test")
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "test")
assert.Equal(t, fields["level"], "info")
})
}
func TestWarn(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.Warn("test")
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "test")
assert.Equal(t, fields["level"], "warning")
})
}
func TestInfolnShouldAddSpacesBetweenStrings(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.Infoln("test", "test")
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "test test")
})
}
func TestInfolnShouldAddSpacesBetweenStringAndNonstring(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.Infoln("test", 10)
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "test 10")
})
}
func TestInfolnShouldAddSpacesBetweenTwoNonStrings(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.Infoln(10, 10)
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "10 10")
})
}
func TestInfoShouldAddSpacesBetweenTwoNonStrings(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.Infoln(10, 10)
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "10 10")
})
}
func TestInfoShouldNotAddSpacesBetweenStringAndNonstring(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.Info("test", 10)
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "test10")
})
}
func TestInfoShouldNotAddSpacesBetweenStrings(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.Info("test", "test")
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "testtest")
})
}
func TestWithFieldsShouldAllowAssignments(t *testing.T) {
var buffer bytes.Buffer
var fields Fields
logger := New()
logger.Out = &buffer
logger.Formatter = new(JSONFormatter)
localLog := logger.WithFields(Fields{
"key1": "value1",
})
localLog.WithField("key2", "value2").Info("test")
err := json.Unmarshal(buffer.Bytes(), &fields)
assert.Nil(t, err)
assert.Equal(t, "value2", fields["key2"])
assert.Equal(t, "value1", fields["key1"])
buffer = bytes.Buffer{}
fields = Fields{}
localLog.Info("test")
err = json.Unmarshal(buffer.Bytes(), &fields)
assert.Nil(t, err)
_, ok := fields["key2"]
assert.Equal(t, false, ok)
assert.Equal(t, "value1", fields["key1"])
}
func TestUserSuppliedFieldDoesNotOverwriteDefaults(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.WithField("msg", "hello").Info("test")
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "test")
})
}
func TestUserSuppliedMsgFieldHasPrefix(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.WithField("msg", "hello").Info("test")
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "test")
assert.Equal(t, fields["fields.msg"], "hello")
})
}
func TestUserSuppliedTimeFieldHasPrefix(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.WithField("time", "hello").Info("test")
}, func(fields Fields) {
assert.Equal(t, fields["fields.time"], "hello")
})
}
func TestUserSuppliedLevelFieldHasPrefix(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.WithField("level", 1).Info("test")
}, func(fields Fields) {
assert.Equal(t, fields["level"], "info")
assert.Equal(t, fields["fields.level"], 1.0) // JSON has floats only
})
}
func TestDefaultFieldsAreNotPrefixed(t *testing.T) {
LogAndAssertText(t, func(log *Logger) {
ll := log.WithField("herp", "derp")
ll.Info("hello")
ll.Info("bye")
}, func(fields map[string]string) {
for _, fieldName := range []string{"fields.level", "fields.time", "fields.msg"} {
if _, ok := fields[fieldName]; ok {
t.Fatalf("should not have prefixed %q: %v", fieldName, fields)
}
}
})
}
func TestDoubleLoggingDoesntPrefixPreviousFields(t *testing.T) {
var buffer bytes.Buffer
var fields Fields
logger := New()
logger.Out = &buffer
logger.Formatter = new(JSONFormatter)
llog := logger.WithField("context", "eating raw fish")
llog.Info("looks delicious")
err := json.Unmarshal(buffer.Bytes(), &fields)
assert.NoError(t, err, "should have decoded first message")
assert.Equal(t, len(fields), 4, "should only have msg/time/level/context fields")
assert.Equal(t, fields["msg"], "looks delicious")
assert.Equal(t, fields["context"], "eating raw fish")
buffer.Reset()
llog.Warn("omg it is!")
err = json.Unmarshal(buffer.Bytes(), &fields)
assert.NoError(t, err, "should have decoded second message")
assert.Equal(t, len(fields), 4, "should only have msg/time/level/context fields")
assert.Equal(t, fields["msg"], "omg it is!")
assert.Equal(t, fields["context"], "eating raw fish")
assert.Nil(t, fields["fields.msg"], "should not have prefixed previous `msg` entry")
}
func TestConvertLevelToString(t *testing.T) {
assert.Equal(t, "debug", DebugLevel.String())
assert.Equal(t, "info", InfoLevel.String())
assert.Equal(t, "warning", WarnLevel.String())
assert.Equal(t, "error", ErrorLevel.String())
assert.Equal(t, "fatal", FatalLevel.String())
assert.Equal(t, "panic", PanicLevel.String())
}
func TestParseLevel(t *testing.T) {
l, err := ParseLevel("panic")
assert.Nil(t, err)
assert.Equal(t, PanicLevel, l)
l, err = ParseLevel("fatal")
assert.Nil(t, err)
assert.Equal(t, FatalLevel, l)
l, err = ParseLevel("error")
assert.Nil(t, err)
assert.Equal(t, ErrorLevel, l)
l, err = ParseLevel("warn")
assert.Nil(t, err)
assert.Equal(t, WarnLevel, l)
l, err = ParseLevel("warning")
assert.Nil(t, err)
assert.Equal(t, WarnLevel, l)
l, err = ParseLevel("info")
assert.Nil(t, err)
assert.Equal(t, InfoLevel, l)
l, err = ParseLevel("debug")
assert.Nil(t, err)
assert.Equal(t, DebugLevel, l)
l, err = ParseLevel("invalid")
assert.Equal(t, "not a valid logrus Level: \"invalid\"", err.Error())
}
func TestGetSetLevelRace(t *testing.T) {
wg := sync.WaitGroup{}
for i := 0; i < 100; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
if i%2 == 0 {
SetLevel(InfoLevel)
} else {
GetLevel()
}
}(i)
}
wg.Wait()
}
+9
View File
@@ -0,0 +1,9 @@
// +build darwin freebsd openbsd netbsd dragonfly
package logrus
import "syscall"
const ioctlReadTermios = syscall.TIOCGETA
type Termios syscall.Termios
+12
View File
@@ -0,0 +1,12 @@
// Based on ssh/terminal:
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package logrus
import "syscall"
const ioctlReadTermios = syscall.TCGETS
type Termios syscall.Termios
+21
View File
@@ -0,0 +1,21 @@
// Based on ssh/terminal:
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build linux darwin freebsd openbsd netbsd dragonfly
package logrus
import (
"syscall"
"unsafe"
)
// IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal() bool {
fd := syscall.Stdout
var termios Termios
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
return err == 0
}
+15
View File
@@ -0,0 +1,15 @@
// +build solaris
package logrus
import (
"os"
"golang.org/x/sys/unix"
)
// IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal() bool {
_, err := unix.IoctlGetTermios(int(os.Stdout.Fd()), unix.TCGETA)
return err == nil
}
+27
View File
@@ -0,0 +1,27 @@
// Based on ssh/terminal:
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
package logrus
import (
"syscall"
"unsafe"
)
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
var (
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
)
// IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal() bool {
fd := syscall.Stdout
var st uint32
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
return r != 0 && e == 0
}
+161
View File
@@ -0,0 +1,161 @@
package logrus
import (
"bytes"
"fmt"
"runtime"
"sort"
"strings"
"time"
)
const (
nocolor = 0
red = 31
green = 32
yellow = 33
blue = 34
gray = 37
)
var (
baseTimestamp time.Time
isTerminal bool
)
func init() {
baseTimestamp = time.Now()
isTerminal = IsTerminal()
}
func miniTS() int {
return int(time.Since(baseTimestamp) / time.Second)
}
type TextFormatter struct {
// Set to true to bypass checking for a TTY before outputting colors.
ForceColors bool
// Force disabling colors.
DisableColors bool
// Disable timestamp logging. useful when output is redirected to logging
// system that already adds timestamps.
DisableTimestamp bool
// Enable logging the full timestamp when a TTY is attached instead of just
// the time passed since beginning of execution.
FullTimestamp bool
// TimestampFormat to use for display when a full timestamp is printed
TimestampFormat string
// The fields are sorted by default for a consistent output. For applications
// that log extremely frequently and don't use the JSON formatter this may not
// be desired.
DisableSorting bool
}
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
var keys []string = make([]string, 0, len(entry.Data))
for k := range entry.Data {
keys = append(keys, k)
}
if !f.DisableSorting {
sort.Strings(keys)
}
b := &bytes.Buffer{}
prefixFieldClashes(entry.Data)
isColorTerminal := isTerminal && (runtime.GOOS != "windows")
isColored := (f.ForceColors || isColorTerminal) && !f.DisableColors
timestampFormat := f.TimestampFormat
if timestampFormat == "" {
timestampFormat = DefaultTimestampFormat
}
if isColored {
f.printColored(b, entry, keys, timestampFormat)
} else {
if !f.DisableTimestamp {
f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat))
}
f.appendKeyValue(b, "level", entry.Level.String())
if entry.Message != "" {
f.appendKeyValue(b, "msg", entry.Message)
}
for _, key := range keys {
f.appendKeyValue(b, key, entry.Data[key])
}
}
b.WriteByte('\n')
return b.Bytes(), nil
}
func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, timestampFormat string) {
var levelColor int
switch entry.Level {
case DebugLevel:
levelColor = gray
case WarnLevel:
levelColor = yellow
case ErrorLevel, FatalLevel, PanicLevel:
levelColor = red
default:
levelColor = blue
}
levelText := strings.ToUpper(entry.Level.String())[0:4]
if !f.FullTimestamp {
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message)
} else {
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), entry.Message)
}
for _, k := range keys {
v := entry.Data[k]
fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=%+v", levelColor, k, v)
}
}
func needsQuoting(text string) bool {
for _, ch := range text {
if !((ch >= 'a' && ch <= 'z') ||
(ch >= 'A' && ch <= 'Z') ||
(ch >= '0' && ch <= '9') ||
ch == '-' || ch == '.') {
return false
}
}
return true
}
func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
b.WriteString(key)
b.WriteByte('=')
switch value := value.(type) {
case string:
if needsQuoting(value) {
b.WriteString(value)
} else {
fmt.Fprintf(b, "%q", value)
}
case error:
errmsg := value.Error()
if needsQuoting(errmsg) {
b.WriteString(errmsg)
} else {
fmt.Fprintf(b, "%q", value)
}
default:
fmt.Fprint(b, value)
}
b.WriteByte(' ')
}
+61
View File
@@ -0,0 +1,61 @@
package logrus
import (
"bytes"
"errors"
"testing"
"time"
)
func TestQuoting(t *testing.T) {
tf := &TextFormatter{DisableColors: true}
checkQuoting := func(q bool, value interface{}) {
b, _ := tf.Format(WithField("test", value))
idx := bytes.Index(b, ([]byte)("test="))
cont := bytes.Contains(b[idx+5:], []byte{'"'})
if cont != q {
if q {
t.Errorf("quoting expected for: %#v", value)
} else {
t.Errorf("quoting not expected for: %#v", value)
}
}
}
checkQuoting(false, "abcd")
checkQuoting(false, "v1.0")
checkQuoting(false, "1234567890")
checkQuoting(true, "/foobar")
checkQuoting(true, "x y")
checkQuoting(true, "x,y")
checkQuoting(false, errors.New("invalid"))
checkQuoting(true, errors.New("invalid argument"))
}
func TestTimestampFormat(t *testing.T) {
checkTimeStr := func(format string) {
customFormatter := &TextFormatter{DisableColors: true, TimestampFormat: format}
customStr, _ := customFormatter.Format(WithField("test", "test"))
timeStart := bytes.Index(customStr, ([]byte)("time="))
timeEnd := bytes.Index(customStr, ([]byte)("level="))
timeStr := customStr[timeStart+5 : timeEnd-1]
if timeStr[0] == '"' && timeStr[len(timeStr)-1] == '"' {
timeStr = timeStr[1 : len(timeStr)-1]
}
if format == "" {
format = time.RFC3339
}
_, e := time.Parse(format, (string)(timeStr))
if e != nil {
t.Errorf("time string \"%s\" did not match provided time format \"%s\": %s", timeStr, format, e)
}
}
checkTimeStr("2006-01-02T15:04:05.000000000Z07:00")
checkTimeStr("Mon Jan _2 15:04:05 2006")
checkTimeStr("")
}
// TODO add tests for sorting etc., this requires a parser for the text
// formatter output.
+31
View File
@@ -0,0 +1,31 @@
package logrus
import (
"bufio"
"io"
"runtime"
)
func (logger *Logger) Writer() *io.PipeWriter {
reader, writer := io.Pipe()
go logger.writerScanner(reader)
runtime.SetFinalizer(writer, writerFinalizer)
return writer
}
func (logger *Logger) writerScanner(reader *io.PipeReader) {
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
logger.Print(scanner.Text())
}
if err := scanner.Err(); err != nil {
logger.Errorf("Error while reading from Writer: %s", err)
}
reader.Close()
}
func writerFinalizer(writer *io.PipeWriter) {
writer.Close()
}
+39
View File
@@ -0,0 +1,39 @@
package homedir
import (
"os"
"runtime"
"github.com/opencontainers/runc/libcontainer/user"
)
// Key returns the env var name for the user's home dir based on
// the platform being run on
func Key() string {
if runtime.GOOS == "windows" {
return "USERPROFILE"
}
return "HOME"
}
// Get returns the home directory of the current user with the help of
// environment variables depending on the target operating system.
// Returned path should be used with "path/filepath" to form new paths.
func Get() string {
home := os.Getenv(Key())
if home == "" && runtime.GOOS != "windows" {
if u, err := user.CurrentUser(); err == nil {
return u.Home
}
}
return home
}
// GetShortcutString returns the string that is shortcut to user's home directory
// in the native shell of the platform running on.
func GetShortcutString() string {
if runtime.GOOS == "windows" {
return "%USERPROFILE%" // be careful while using in format functions
}
return "~"
}
+24
View File
@@ -0,0 +1,24 @@
package homedir
import (
"path/filepath"
"testing"
)
func TestGet(t *testing.T) {
home := Get()
if home == "" {
t.Fatal("returned home directory is empty")
}
if !filepath.IsAbs(home) {
t.Fatalf("returned path is not absolute: %s", home)
}
}
func TestGetShortcutString(t *testing.T) {
shortcut := GetShortcutString()
if shortcut == "" {
t.Fatal("returned shortcut string is empty")
}
}
+27
View File
@@ -0,0 +1,27 @@
Copyright (c) 2014-2015 The Docker & Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+40
View File
@@ -0,0 +1,40 @@
Package mflag (aka multiple-flag) implements command-line flag parsing.
It's an **hacky** fork of the [official golang package](http://golang.org/pkg/flag/)
It adds:
* both short and long flag version
`./example -s red` `./example --string blue`
* multiple names for the same option
```
$>./example -h
Usage of example:
-s, --string="": a simple string
```
___
It is very flexible on purpose, so you can do things like:
```
$>./example -h
Usage of example:
-s, -string, --string="": a simple string
```
Or:
```
$>./example -h
Usage of example:
-oldflag, --newflag="": a simple string
```
You can also hide some flags from the usage, so if we want only `--newflag`:
```
$>./example -h
Usage of example:
--newflag="": a simple string
$>./example -oldflag str
str
```
See [example.go](example/example.go) for more details.
+36
View File
@@ -0,0 +1,36 @@
package main
import (
"fmt"
flag "github.com/docker/docker/pkg/mflag"
)
var (
i int
str string
b, b2, h bool
)
func init() {
flag.Bool([]string{"#hp", "#-halp"}, false, "display the halp")
flag.BoolVar(&b, []string{"b", "#bal", "#bol", "-bal"}, false, "a simple bool")
flag.BoolVar(&b, []string{"g", "#gil"}, false, "a simple bool")
flag.BoolVar(&b2, []string{"#-bool"}, false, "a simple bool")
flag.IntVar(&i, []string{"-integer", "-number"}, -1, "a simple integer")
flag.StringVar(&str, []string{"s", "#hidden", "-string"}, "", "a simple string") //-s -hidden and --string will work, but -hidden won't be in the usage
flag.BoolVar(&h, []string{"h", "#help", "-help"}, false, "display the help")
flag.StringVar(&str, []string{"mode"}, "mode1", "set the mode\nmode1: use the mode1\nmode2: use the mode2\nmode3: use the mode3")
flag.Parse()
}
func main() {
if h {
flag.PrintDefaults()
} else {
fmt.Printf("s/#hidden/-string: %s\n", str)
fmt.Printf("b: %t\n", b)
fmt.Printf("-bool: %t\n", b2)
fmt.Printf("s/#hidden/-string(via lookup): %s\n", flag.Lookup("s").Value.String())
fmt.Printf("ARGS: %v\n", flag.Args())
}
}
+1264
View File
File diff suppressed because it is too large Load Diff
+516
View File
@@ -0,0 +1,516 @@
// Copyright 2014-2015 The Docker & Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package mflag
import (
"bytes"
"fmt"
"os"
"sort"
"strings"
"testing"
"time"
)
// ResetForTesting clears all flag state and sets the usage function as directed.
// After calling ResetForTesting, parse errors in flag handling will not
// exit the program.
func ResetForTesting(usage func()) {
CommandLine = NewFlagSet(os.Args[0], ContinueOnError)
Usage = usage
}
func boolString(s string) string {
if s == "0" {
return "false"
}
return "true"
}
func TestEverything(t *testing.T) {
ResetForTesting(nil)
Bool([]string{"test_bool"}, false, "bool value")
Int([]string{"test_int"}, 0, "int value")
Int64([]string{"test_int64"}, 0, "int64 value")
Uint([]string{"test_uint"}, 0, "uint value")
Uint64([]string{"test_uint64"}, 0, "uint64 value")
String([]string{"test_string"}, "0", "string value")
Float64([]string{"test_float64"}, 0, "float64 value")
Duration([]string{"test_duration"}, 0, "time.Duration value")
m := make(map[string]*Flag)
desired := "0"
visitor := func(f *Flag) {
for _, name := range f.Names {
if len(name) > 5 && name[0:5] == "test_" {
m[name] = f
ok := false
switch {
case f.Value.String() == desired:
ok = true
case name == "test_bool" && f.Value.String() == boolString(desired):
ok = true
case name == "test_duration" && f.Value.String() == desired+"s":
ok = true
}
if !ok {
t.Error("Visit: bad value", f.Value.String(), "for", name)
}
}
}
}
VisitAll(visitor)
if len(m) != 8 {
t.Error("VisitAll misses some flags")
for k, v := range m {
t.Log(k, *v)
}
}
m = make(map[string]*Flag)
Visit(visitor)
if len(m) != 0 {
t.Errorf("Visit sees unset flags")
for k, v := range m {
t.Log(k, *v)
}
}
// Now set all flags
Set("test_bool", "true")
Set("test_int", "1")
Set("test_int64", "1")
Set("test_uint", "1")
Set("test_uint64", "1")
Set("test_string", "1")
Set("test_float64", "1")
Set("test_duration", "1s")
desired = "1"
Visit(visitor)
if len(m) != 8 {
t.Error("Visit fails after set")
for k, v := range m {
t.Log(k, *v)
}
}
// Now test they're visited in sort order.
var flagNames []string
Visit(func(f *Flag) {
for _, name := range f.Names {
flagNames = append(flagNames, name)
}
})
if !sort.StringsAreSorted(flagNames) {
t.Errorf("flag names not sorted: %v", flagNames)
}
}
func TestGet(t *testing.T) {
ResetForTesting(nil)
Bool([]string{"test_bool"}, true, "bool value")
Int([]string{"test_int"}, 1, "int value")
Int64([]string{"test_int64"}, 2, "int64 value")
Uint([]string{"test_uint"}, 3, "uint value")
Uint64([]string{"test_uint64"}, 4, "uint64 value")
String([]string{"test_string"}, "5", "string value")
Float64([]string{"test_float64"}, 6, "float64 value")
Duration([]string{"test_duration"}, 7, "time.Duration value")
visitor := func(f *Flag) {
for _, name := range f.Names {
if len(name) > 5 && name[0:5] == "test_" {
g, ok := f.Value.(Getter)
if !ok {
t.Errorf("Visit: value does not satisfy Getter: %T", f.Value)
return
}
switch name {
case "test_bool":
ok = g.Get() == true
case "test_int":
ok = g.Get() == int(1)
case "test_int64":
ok = g.Get() == int64(2)
case "test_uint":
ok = g.Get() == uint(3)
case "test_uint64":
ok = g.Get() == uint64(4)
case "test_string":
ok = g.Get() == "5"
case "test_float64":
ok = g.Get() == float64(6)
case "test_duration":
ok = g.Get() == time.Duration(7)
}
if !ok {
t.Errorf("Visit: bad value %T(%v) for %s", g.Get(), g.Get(), name)
}
}
}
}
VisitAll(visitor)
}
func testParse(f *FlagSet, t *testing.T) {
if f.Parsed() {
t.Error("f.Parse() = true before Parse")
}
boolFlag := f.Bool([]string{"bool"}, false, "bool value")
bool2Flag := f.Bool([]string{"bool2"}, false, "bool2 value")
f.Bool([]string{"bool3"}, false, "bool3 value")
bool4Flag := f.Bool([]string{"bool4"}, false, "bool4 value")
intFlag := f.Int([]string{"-int"}, 0, "int value")
int64Flag := f.Int64([]string{"-int64"}, 0, "int64 value")
uintFlag := f.Uint([]string{"uint"}, 0, "uint value")
uint64Flag := f.Uint64([]string{"-uint64"}, 0, "uint64 value")
stringFlag := f.String([]string{"string"}, "0", "string value")
f.String([]string{"string2"}, "0", "string2 value")
singleQuoteFlag := f.String([]string{"squote"}, "", "single quoted value")
doubleQuoteFlag := f.String([]string{"dquote"}, "", "double quoted value")
mixedQuoteFlag := f.String([]string{"mquote"}, "", "mixed quoted value")
mixed2QuoteFlag := f.String([]string{"mquote2"}, "", "mixed2 quoted value")
nestedQuoteFlag := f.String([]string{"nquote"}, "", "nested quoted value")
nested2QuoteFlag := f.String([]string{"nquote2"}, "", "nested2 quoted value")
float64Flag := f.Float64([]string{"float64"}, 0, "float64 value")
durationFlag := f.Duration([]string{"duration"}, 5*time.Second, "time.Duration value")
extra := "one-extra-argument"
args := []string{
"-bool",
"-bool2=true",
"-bool4=false",
"--int", "22",
"--int64", "0x23",
"-uint", "24",
"--uint64", "25",
"-string", "hello",
"-squote='single'",
`-dquote="double"`,
`-mquote='mixed"`,
`-mquote2="mixed2'`,
`-nquote="'single nested'"`,
`-nquote2='"double nested"'`,
"-float64", "2718e28",
"-duration", "2m",
extra,
}
if err := f.Parse(args); err != nil {
t.Fatal(err)
}
if !f.Parsed() {
t.Error("f.Parse() = false after Parse")
}
if *boolFlag != true {
t.Error("bool flag should be true, is ", *boolFlag)
}
if *bool2Flag != true {
t.Error("bool2 flag should be true, is ", *bool2Flag)
}
if !f.IsSet("bool2") {
t.Error("bool2 should be marked as set")
}
if f.IsSet("bool3") {
t.Error("bool3 should not be marked as set")
}
if !f.IsSet("bool4") {
t.Error("bool4 should be marked as set")
}
if *bool4Flag != false {
t.Error("bool4 flag should be false, is ", *bool4Flag)
}
if *intFlag != 22 {
t.Error("int flag should be 22, is ", *intFlag)
}
if *int64Flag != 0x23 {
t.Error("int64 flag should be 0x23, is ", *int64Flag)
}
if *uintFlag != 24 {
t.Error("uint flag should be 24, is ", *uintFlag)
}
if *uint64Flag != 25 {
t.Error("uint64 flag should be 25, is ", *uint64Flag)
}
if *stringFlag != "hello" {
t.Error("string flag should be `hello`, is ", *stringFlag)
}
if !f.IsSet("string") {
t.Error("string flag should be marked as set")
}
if f.IsSet("string2") {
t.Error("string2 flag should not be marked as set")
}
if *singleQuoteFlag != "single" {
t.Error("single quote string flag should be `single`, is ", *singleQuoteFlag)
}
if *doubleQuoteFlag != "double" {
t.Error("double quote string flag should be `double`, is ", *doubleQuoteFlag)
}
if *mixedQuoteFlag != `'mixed"` {
t.Error("mixed quote string flag should be `'mixed\"`, is ", *mixedQuoteFlag)
}
if *mixed2QuoteFlag != `"mixed2'` {
t.Error("mixed2 quote string flag should be `\"mixed2'`, is ", *mixed2QuoteFlag)
}
if *nestedQuoteFlag != "'single nested'" {
t.Error("nested quote string flag should be `'single nested'`, is ", *nestedQuoteFlag)
}
if *nested2QuoteFlag != `"double nested"` {
t.Error("double quote string flag should be `\"double nested\"`, is ", *nested2QuoteFlag)
}
if *float64Flag != 2718e28 {
t.Error("float64 flag should be 2718e28, is ", *float64Flag)
}
if *durationFlag != 2*time.Minute {
t.Error("duration flag should be 2m, is ", *durationFlag)
}
if len(f.Args()) != 1 {
t.Error("expected one argument, got", len(f.Args()))
} else if f.Args()[0] != extra {
t.Errorf("expected argument %q got %q", extra, f.Args()[0])
}
}
func testPanic(f *FlagSet, t *testing.T) {
f.Int([]string{"-int"}, 0, "int value")
if f.Parsed() {
t.Error("f.Parse() = true before Parse")
}
args := []string{
"-int", "21",
}
f.Parse(args)
}
func TestParsePanic(t *testing.T) {
ResetForTesting(func() {})
testPanic(CommandLine, t)
}
func TestParse(t *testing.T) {
ResetForTesting(func() { t.Error("bad parse") })
testParse(CommandLine, t)
}
func TestFlagSetParse(t *testing.T) {
testParse(NewFlagSet("test", ContinueOnError), t)
}
// Declare a user-defined flag type.
type flagVar []string
func (f *flagVar) String() string {
return fmt.Sprint([]string(*f))
}
func (f *flagVar) Set(value string) error {
*f = append(*f, value)
return nil
}
func TestUserDefined(t *testing.T) {
var flags FlagSet
flags.Init("test", ContinueOnError)
var v flagVar
flags.Var(&v, []string{"v"}, "usage")
if err := flags.Parse([]string{"-v", "1", "-v", "2", "-v=3"}); err != nil {
t.Error(err)
}
if len(v) != 3 {
t.Fatal("expected 3 args; got ", len(v))
}
expect := "[1 2 3]"
if v.String() != expect {
t.Errorf("expected value %q got %q", expect, v.String())
}
}
// Declare a user-defined boolean flag type.
type boolFlagVar struct {
count int
}
func (b *boolFlagVar) String() string {
return fmt.Sprintf("%d", b.count)
}
func (b *boolFlagVar) Set(value string) error {
if value == "true" {
b.count++
}
return nil
}
func (b *boolFlagVar) IsBoolFlag() bool {
return b.count < 4
}
func TestUserDefinedBool(t *testing.T) {
var flags FlagSet
flags.Init("test", ContinueOnError)
var b boolFlagVar
var err error
flags.Var(&b, []string{"b"}, "usage")
if err = flags.Parse([]string{"-b", "-b", "-b", "-b=true", "-b=false", "-b", "barg", "-b"}); err != nil {
if b.count < 4 {
t.Error(err)
}
}
if b.count != 4 {
t.Errorf("want: %d; got: %d", 4, b.count)
}
if err == nil {
t.Error("expected error; got none")
}
}
func TestSetOutput(t *testing.T) {
var flags FlagSet
var buf bytes.Buffer
flags.SetOutput(&buf)
flags.Init("test", ContinueOnError)
flags.Parse([]string{"-unknown"})
if out := buf.String(); !strings.Contains(out, "-unknown") {
t.Logf("expected output mentioning unknown; got %q", out)
}
}
// This tests that one can reset the flags. This still works but not well, and is
// superseded by FlagSet.
func TestChangingArgs(t *testing.T) {
ResetForTesting(func() { t.Fatal("bad parse") })
oldArgs := os.Args
defer func() { os.Args = oldArgs }()
os.Args = []string{"cmd", "-before", "subcmd", "-after", "args"}
before := Bool([]string{"before"}, false, "")
if err := CommandLine.Parse(os.Args[1:]); err != nil {
t.Fatal(err)
}
cmd := Arg(0)
os.Args = Args()
after := Bool([]string{"after"}, false, "")
Parse()
args := Args()
if !*before || cmd != "subcmd" || !*after || len(args) != 1 || args[0] != "args" {
t.Fatalf("expected true subcmd true [args] got %v %v %v %v", *before, cmd, *after, args)
}
}
// Test that -help invokes the usage message and returns ErrHelp.
func TestHelp(t *testing.T) {
var helpCalled = false
fs := NewFlagSet("help test", ContinueOnError)
fs.Usage = func() { helpCalled = true }
var flag bool
fs.BoolVar(&flag, []string{"flag"}, false, "regular flag")
// Regular flag invocation should work
err := fs.Parse([]string{"-flag=true"})
if err != nil {
t.Fatal("expected no error; got ", err)
}
if !flag {
t.Error("flag was not set by -flag")
}
if helpCalled {
t.Error("help called for regular flag")
helpCalled = false // reset for next test
}
// Help flag should work as expected.
err = fs.Parse([]string{"-help"})
if err == nil {
t.Fatal("error expected")
}
if err != ErrHelp {
t.Fatal("expected ErrHelp; got ", err)
}
if !helpCalled {
t.Fatal("help was not called")
}
// If we define a help flag, that should override.
var help bool
fs.BoolVar(&help, []string{"help"}, false, "help flag")
helpCalled = false
err = fs.Parse([]string{"-help"})
if err != nil {
t.Fatal("expected no error for defined -help; got ", err)
}
if helpCalled {
t.Fatal("help was called; should not have been for defined help flag")
}
}
// Test the flag count functions.
func TestFlagCounts(t *testing.T) {
fs := NewFlagSet("help test", ContinueOnError)
var flag bool
fs.BoolVar(&flag, []string{"flag1"}, false, "regular flag")
fs.BoolVar(&flag, []string{"#deprecated1"}, false, "regular flag")
fs.BoolVar(&flag, []string{"f", "flag2"}, false, "regular flag")
fs.BoolVar(&flag, []string{"#d", "#deprecated2"}, false, "regular flag")
fs.BoolVar(&flag, []string{"flag3"}, false, "regular flag")
fs.BoolVar(&flag, []string{"g", "#flag4", "-flag4"}, false, "regular flag")
if fs.FlagCount() != 6 {
t.Fatal("FlagCount wrong. ", fs.FlagCount())
}
if fs.FlagCountUndeprecated() != 4 {
t.Fatal("FlagCountUndeprecated wrong. ", fs.FlagCountUndeprecated())
}
if fs.NFlag() != 0 {
t.Fatal("NFlag wrong. ", fs.NFlag())
}
err := fs.Parse([]string{"-fd", "-g", "-flag4"})
if err != nil {
t.Fatal("expected no error for defined -help; got ", err)
}
if fs.NFlag() != 4 {
t.Fatal("NFlag wrong. ", fs.NFlag())
}
}
// Show up bug in sortFlags
func TestSortFlags(t *testing.T) {
fs := NewFlagSet("help TestSortFlags", ContinueOnError)
var err error
var b bool
fs.BoolVar(&b, []string{"b", "-banana"}, false, "usage")
err = fs.Parse([]string{"--banana=true"})
if err != nil {
t.Fatal("expected no error; got ", err)
}
count := 0
fs.VisitAll(func(flag *Flag) {
count++
if flag == nil {
t.Fatal("VisitAll should not return a nil flag")
}
})
flagcount := fs.FlagCount()
if flagcount != count {
t.Fatalf("FlagCount (%d) != number (%d) of elements visited", flagcount, count)
}
// Make sure its idempotent
if flagcount != fs.FlagCount() {
t.Fatalf("FlagCount (%d) != fs.FlagCount() (%d) of elements visited", flagcount, fs.FlagCount())
}
count = 0
fs.Visit(func(flag *Flag) {
count++
if flag == nil {
t.Fatal("Visit should not return a nil flag")
}
})
nflag := fs.NFlag()
if nflag != count {
t.Fatalf("NFlag (%d) != number (%d) of elements visited", nflag, count)
}
if nflag != fs.NFlag() {
t.Fatalf("NFlag (%d) != fs.NFlag() (%d) of elements visited", nflag, fs.NFlag())
}
}
+2
View File
@@ -0,0 +1,2 @@
Tianon Gravi <admwiggin@gmail.com> (@tianon)
Aleksa Sarai <cyphar@cyphar.com> (@cyphar)
+108
View File
@@ -0,0 +1,108 @@
package user
import (
"errors"
"fmt"
"syscall"
)
var (
// The current operating system does not provide the required data for user lookups.
ErrUnsupported = errors.New("user lookup: operating system does not provide passwd-formatted data")
)
func lookupUser(filter func(u User) bool) (User, error) {
// Get operating system-specific passwd reader-closer.
passwd, err := GetPasswd()
if err != nil {
return User{}, err
}
defer passwd.Close()
// Get the users.
users, err := ParsePasswdFilter(passwd, filter)
if err != nil {
return User{}, err
}
// No user entries found.
if len(users) == 0 {
return User{}, fmt.Errorf("no matching entries in passwd file")
}
// Assume the first entry is the "correct" one.
return users[0], nil
}
// CurrentUser looks up the current user by their user id in /etc/passwd. If the
// user cannot be found (or there is no /etc/passwd file on the filesystem),
// then CurrentUser returns an error.
func CurrentUser() (User, error) {
return LookupUid(syscall.Getuid())
}
// LookupUser looks up a user by their username in /etc/passwd. If the user
// cannot be found (or there is no /etc/passwd file on the filesystem), then
// LookupUser returns an error.
func LookupUser(username string) (User, error) {
return lookupUser(func(u User) bool {
return u.Name == username
})
}
// LookupUid looks up a user by their user id in /etc/passwd. If the user cannot
// be found (or there is no /etc/passwd file on the filesystem), then LookupId
// returns an error.
func LookupUid(uid int) (User, error) {
return lookupUser(func(u User) bool {
return u.Uid == uid
})
}
func lookupGroup(filter func(g Group) bool) (Group, error) {
// Get operating system-specific group reader-closer.
group, err := GetGroup()
if err != nil {
return Group{}, err
}
defer group.Close()
// Get the users.
groups, err := ParseGroupFilter(group, filter)
if err != nil {
return Group{}, err
}
// No user entries found.
if len(groups) == 0 {
return Group{}, fmt.Errorf("no matching entries in group file")
}
// Assume the first entry is the "correct" one.
return groups[0], nil
}
// CurrentGroup looks up the current user's group by their primary group id's
// entry in /etc/passwd. If the group cannot be found (or there is no
// /etc/group file on the filesystem), then CurrentGroup returns an error.
func CurrentGroup() (Group, error) {
return LookupGid(syscall.Getgid())
}
// LookupGroup looks up a group by its name in /etc/group. If the group cannot
// be found (or there is no /etc/group file on the filesystem), then LookupGroup
// returns an error.
func LookupGroup(groupname string) (Group, error) {
return lookupGroup(func(g Group) bool {
return g.Name == groupname
})
}
// LookupGid looks up a group by its group id in /etc/group. If the group cannot
// be found (or there is no /etc/group file on the filesystem), then LookupGid
// returns an error.
func LookupGid(gid int) (Group, error) {
return lookupGroup(func(g Group) bool {
return g.Gid == gid
})
}
+30
View File
@@ -0,0 +1,30 @@
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
package user
import (
"io"
"os"
)
// Unix-specific path to the passwd and group formatted files.
const (
unixPasswdPath = "/etc/passwd"
unixGroupPath = "/etc/group"
)
func GetPasswdPath() (string, error) {
return unixPasswdPath, nil
}
func GetPasswd() (io.ReadCloser, error) {
return os.Open(unixPasswdPath)
}
func GetGroupPath() (string, error) {
return unixGroupPath, nil
}
func GetGroup() (io.ReadCloser, error) {
return os.Open(unixGroupPath)
}
@@ -0,0 +1,21 @@
// +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris
package user
import "io"
func GetPasswdPath() (string, error) {
return "", ErrUnsupported
}
func GetPasswd() (io.ReadCloser, error) {
return nil, ErrUnsupported
}
func GetGroupPath() (string, error) {
return "", ErrUnsupported
}
func GetGroup() (io.ReadCloser, error) {
return nil, ErrUnsupported
}
+418
View File
@@ -0,0 +1,418 @@
package user
import (
"bufio"
"fmt"
"io"
"os"
"strconv"
"strings"
)
const (
minId = 0
maxId = 1<<31 - 1 //for 32-bit systems compatibility
)
var (
ErrRange = fmt.Errorf("Uids and gids must be in range %d-%d", minId, maxId)
)
type User struct {
Name string
Pass string
Uid int
Gid int
Gecos string
Home string
Shell string
}
type Group struct {
Name string
Pass string
Gid int
List []string
}
func parseLine(line string, v ...interface{}) {
if line == "" {
return
}
parts := strings.Split(line, ":")
for i, p := range parts {
if len(v) <= i {
// if we have more "parts" than we have places to put them, bail for great "tolerance" of naughty configuration files
break
}
switch e := v[i].(type) {
case *string:
// "root", "adm", "/bin/bash"
*e = p
case *int:
// "0", "4", "1000"
// ignore string to int conversion errors, for great "tolerance" of naughty configuration files
*e, _ = strconv.Atoi(p)
case *[]string:
// "", "root", "root,adm,daemon"
if p != "" {
*e = strings.Split(p, ",")
} else {
*e = []string{}
}
default:
// panic, because this is a programming/logic error, not a runtime one
panic("parseLine expects only pointers! argument " + strconv.Itoa(i) + " is not a pointer!")
}
}
}
func ParsePasswdFile(path string) ([]User, error) {
passwd, err := os.Open(path)
if err != nil {
return nil, err
}
defer passwd.Close()
return ParsePasswd(passwd)
}
func ParsePasswd(passwd io.Reader) ([]User, error) {
return ParsePasswdFilter(passwd, nil)
}
func ParsePasswdFileFilter(path string, filter func(User) bool) ([]User, error) {
passwd, err := os.Open(path)
if err != nil {
return nil, err
}
defer passwd.Close()
return ParsePasswdFilter(passwd, filter)
}
func ParsePasswdFilter(r io.Reader, filter func(User) bool) ([]User, error) {
if r == nil {
return nil, fmt.Errorf("nil source for passwd-formatted data")
}
var (
s = bufio.NewScanner(r)
out = []User{}
)
for s.Scan() {
if err := s.Err(); err != nil {
return nil, err
}
text := strings.TrimSpace(s.Text())
if text == "" {
continue
}
// see: man 5 passwd
// name:password:UID:GID:GECOS:directory:shell
// Name:Pass:Uid:Gid:Gecos:Home:Shell
// root:x:0:0:root:/root:/bin/bash
// adm:x:3:4:adm:/var/adm:/bin/false
p := User{}
parseLine(
text,
&p.Name, &p.Pass, &p.Uid, &p.Gid, &p.Gecos, &p.Home, &p.Shell,
)
if filter == nil || filter(p) {
out = append(out, p)
}
}
return out, nil
}
func ParseGroupFile(path string) ([]Group, error) {
group, err := os.Open(path)
if err != nil {
return nil, err
}
defer group.Close()
return ParseGroup(group)
}
func ParseGroup(group io.Reader) ([]Group, error) {
return ParseGroupFilter(group, nil)
}
func ParseGroupFileFilter(path string, filter func(Group) bool) ([]Group, error) {
group, err := os.Open(path)
if err != nil {
return nil, err
}
defer group.Close()
return ParseGroupFilter(group, filter)
}
func ParseGroupFilter(r io.Reader, filter func(Group) bool) ([]Group, error) {
if r == nil {
return nil, fmt.Errorf("nil source for group-formatted data")
}
var (
s = bufio.NewScanner(r)
out = []Group{}
)
for s.Scan() {
if err := s.Err(); err != nil {
return nil, err
}
text := s.Text()
if text == "" {
continue
}
// see: man 5 group
// group_name:password:GID:user_list
// Name:Pass:Gid:List
// root:x:0:root
// adm:x:4:root,adm,daemon
p := Group{}
parseLine(
text,
&p.Name, &p.Pass, &p.Gid, &p.List,
)
if filter == nil || filter(p) {
out = append(out, p)
}
}
return out, nil
}
type ExecUser struct {
Uid, Gid int
Sgids []int
Home string
}
// GetExecUserPath is a wrapper for GetExecUser. It reads data from each of the
// given file paths and uses that data as the arguments to GetExecUser. If the
// files cannot be opened for any reason, the error is ignored and a nil
// io.Reader is passed instead.
func GetExecUserPath(userSpec string, defaults *ExecUser, passwdPath, groupPath string) (*ExecUser, error) {
passwd, err := os.Open(passwdPath)
if err != nil {
passwd = nil
} else {
defer passwd.Close()
}
group, err := os.Open(groupPath)
if err != nil {
group = nil
} else {
defer group.Close()
}
return GetExecUser(userSpec, defaults, passwd, group)
}
// GetExecUser parses a user specification string (using the passwd and group
// readers as sources for /etc/passwd and /etc/group data, respectively). In
// the case of blank fields or missing data from the sources, the values in
// defaults is used.
//
// GetExecUser will return an error if a user or group literal could not be
// found in any entry in passwd and group respectively.
//
// Examples of valid user specifications are:
// * ""
// * "user"
// * "uid"
// * "user:group"
// * "uid:gid
// * "user:gid"
// * "uid:group"
func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (*ExecUser, error) {
var (
userArg, groupArg string
name string
)
if defaults == nil {
defaults = new(ExecUser)
}
// Copy over defaults.
user := &ExecUser{
Uid: defaults.Uid,
Gid: defaults.Gid,
Sgids: defaults.Sgids,
Home: defaults.Home,
}
// Sgids slice *cannot* be nil.
if user.Sgids == nil {
user.Sgids = []int{}
}
// allow for userArg to have either "user" syntax, or optionally "user:group" syntax
parseLine(userSpec, &userArg, &groupArg)
users, err := ParsePasswdFilter(passwd, func(u User) bool {
if userArg == "" {
return u.Uid == user.Uid
}
return u.Name == userArg || strconv.Itoa(u.Uid) == userArg
})
if err != nil && passwd != nil {
if userArg == "" {
userArg = strconv.Itoa(user.Uid)
}
return nil, fmt.Errorf("Unable to find user %v: %v", userArg, err)
}
haveUser := users != nil && len(users) > 0
if haveUser {
// if we found any user entries that matched our filter, let's take the first one as "correct"
name = users[0].Name
user.Uid = users[0].Uid
user.Gid = users[0].Gid
user.Home = users[0].Home
} else if userArg != "" {
// we asked for a user but didn't find them... let's check to see if we wanted a numeric user
user.Uid, err = strconv.Atoi(userArg)
if err != nil {
// not numeric - we have to bail
return nil, fmt.Errorf("Unable to find user %v", userArg)
}
// Must be inside valid uid range.
if user.Uid < minId || user.Uid > maxId {
return nil, ErrRange
}
// if userArg couldn't be found in /etc/passwd but is numeric, just roll with it - this is legit
}
if groupArg != "" || name != "" {
groups, err := ParseGroupFilter(group, func(g Group) bool {
// Explicit group format takes precedence.
if groupArg != "" {
return g.Name == groupArg || strconv.Itoa(g.Gid) == groupArg
}
// Check if user is a member.
for _, u := range g.List {
if u == name {
return true
}
}
return false
})
if err != nil && group != nil {
return nil, fmt.Errorf("Unable to find groups for user %v: %v", users[0].Name, err)
}
haveGroup := groups != nil && len(groups) > 0
if groupArg != "" {
if haveGroup {
// if we found any group entries that matched our filter, let's take the first one as "correct"
user.Gid = groups[0].Gid
} else {
// we asked for a group but didn't find id... let's check to see if we wanted a numeric group
user.Gid, err = strconv.Atoi(groupArg)
if err != nil {
// not numeric - we have to bail
return nil, fmt.Errorf("Unable to find group %v", groupArg)
}
// Ensure gid is inside gid range.
if user.Gid < minId || user.Gid > maxId {
return nil, ErrRange
}
// if groupArg couldn't be found in /etc/group but is numeric, just roll with it - this is legit
}
} else if haveGroup {
// If implicit group format, fill supplementary gids.
user.Sgids = make([]int, len(groups))
for i, group := range groups {
user.Sgids[i] = group.Gid
}
}
}
return user, nil
}
// GetAdditionalGroups looks up a list of groups by name or group id
// against the given /etc/group formatted data. If a group name cannot
// be found, an error will be returned. If a group id cannot be found,
// or the given group data is nil, the id will be returned as-is
// provided it is in the legal range.
func GetAdditionalGroups(additionalGroups []string, group io.Reader) ([]int, error) {
var groups = []Group{}
if group != nil {
var err error
groups, err = ParseGroupFilter(group, func(g Group) bool {
for _, ag := range additionalGroups {
if g.Name == ag || strconv.Itoa(g.Gid) == ag {
return true
}
}
return false
})
if err != nil {
return nil, fmt.Errorf("Unable to find additional groups %v: %v", additionalGroups, err)
}
}
gidMap := make(map[int]struct{})
for _, ag := range additionalGroups {
var found bool
for _, g := range groups {
// if we found a matched group either by name or gid, take the
// first matched as correct
if g.Name == ag || strconv.Itoa(g.Gid) == ag {
if _, ok := gidMap[g.Gid]; !ok {
gidMap[g.Gid] = struct{}{}
found = true
break
}
}
}
// we asked for a group but didn't find it. let's check to see
// if we wanted a numeric group
if !found {
gid, err := strconv.Atoi(ag)
if err != nil {
return nil, fmt.Errorf("Unable to find group %s", ag)
}
// Ensure gid is inside gid range.
if gid < minId || gid > maxId {
return nil, ErrRange
}
gidMap[gid] = struct{}{}
}
}
gids := []int{}
for gid := range gidMap {
gids = append(gids, gid)
}
return gids, nil
}
// GetAdditionalGroupsPath is a wrapper around GetAdditionalGroups
// that opens the groupPath given and gives it as an argument to
// GetAdditionalGroups.
func GetAdditionalGroupsPath(additionalGroups []string, groupPath string) ([]int, error) {
group, err := os.Open(groupPath)
if err == nil {
defer group.Close()
}
return GetAdditionalGroups(additionalGroups, group)
}
+472
View File
@@ -0,0 +1,472 @@
package user
import (
"io"
"reflect"
"sort"
"strconv"
"strings"
"testing"
)
func TestUserParseLine(t *testing.T) {
var (
a, b string
c []string
d int
)
parseLine("", &a, &b)
if a != "" || b != "" {
t.Fatalf("a and b should be empty ('%v', '%v')", a, b)
}
parseLine("a", &a, &b)
if a != "a" || b != "" {
t.Fatalf("a should be 'a' and b should be empty ('%v', '%v')", a, b)
}
parseLine("bad boys:corny cows", &a, &b)
if a != "bad boys" || b != "corny cows" {
t.Fatalf("a should be 'bad boys' and b should be 'corny cows' ('%v', '%v')", a, b)
}
parseLine("", &c)
if len(c) != 0 {
t.Fatalf("c should be empty (%#v)", c)
}
parseLine("d,e,f:g:h:i,j,k", &c, &a, &b, &c)
if a != "g" || b != "h" || len(c) != 3 || c[0] != "i" || c[1] != "j" || c[2] != "k" {
t.Fatalf("a should be 'g', b should be 'h', and c should be ['i','j','k'] ('%v', '%v', '%#v')", a, b, c)
}
parseLine("::::::::::", &a, &b, &c)
if a != "" || b != "" || len(c) != 0 {
t.Fatalf("a, b, and c should all be empty ('%v', '%v', '%#v')", a, b, c)
}
parseLine("not a number", &d)
if d != 0 {
t.Fatalf("d should be 0 (%v)", d)
}
parseLine("b:12:c", &a, &d, &b)
if a != "b" || b != "c" || d != 12 {
t.Fatalf("a should be 'b' and b should be 'c', and d should be 12 ('%v', '%v', %v)", a, b, d)
}
}
func TestUserParsePasswd(t *testing.T) {
users, err := ParsePasswdFilter(strings.NewReader(`
root:x:0:0:root:/root:/bin/bash
adm:x:3:4:adm:/var/adm:/bin/false
this is just some garbage data
`), nil)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if len(users) != 3 {
t.Fatalf("Expected 3 users, got %v", len(users))
}
if users[0].Uid != 0 || users[0].Name != "root" {
t.Fatalf("Expected users[0] to be 0 - root, got %v - %v", users[0].Uid, users[0].Name)
}
if users[1].Uid != 3 || users[1].Name != "adm" {
t.Fatalf("Expected users[1] to be 3 - adm, got %v - %v", users[1].Uid, users[1].Name)
}
}
func TestUserParseGroup(t *testing.T) {
groups, err := ParseGroupFilter(strings.NewReader(`
root:x:0:root
adm:x:4:root,adm,daemon
this is just some garbage data
`), nil)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if len(groups) != 3 {
t.Fatalf("Expected 3 groups, got %v", len(groups))
}
if groups[0].Gid != 0 || groups[0].Name != "root" || len(groups[0].List) != 1 {
t.Fatalf("Expected groups[0] to be 0 - root - 1 member, got %v - %v - %v", groups[0].Gid, groups[0].Name, len(groups[0].List))
}
if groups[1].Gid != 4 || groups[1].Name != "adm" || len(groups[1].List) != 3 {
t.Fatalf("Expected groups[1] to be 4 - adm - 3 members, got %v - %v - %v", groups[1].Gid, groups[1].Name, len(groups[1].List))
}
}
func TestValidGetExecUser(t *testing.T) {
const passwdContent = `
root:x:0:0:root user:/root:/bin/bash
adm:x:42:43:adm:/var/adm:/bin/false
this is just some garbage data
`
const groupContent = `
root:x:0:root
adm:x:43:
grp:x:1234:root,adm
this is just some garbage data
`
defaultExecUser := ExecUser{
Uid: 8888,
Gid: 8888,
Sgids: []int{8888},
Home: "/8888",
}
tests := []struct {
ref string
expected ExecUser
}{
{
ref: "root",
expected: ExecUser{
Uid: 0,
Gid: 0,
Sgids: []int{0, 1234},
Home: "/root",
},
},
{
ref: "adm",
expected: ExecUser{
Uid: 42,
Gid: 43,
Sgids: []int{1234},
Home: "/var/adm",
},
},
{
ref: "root:adm",
expected: ExecUser{
Uid: 0,
Gid: 43,
Sgids: defaultExecUser.Sgids,
Home: "/root",
},
},
{
ref: "adm:1234",
expected: ExecUser{
Uid: 42,
Gid: 1234,
Sgids: defaultExecUser.Sgids,
Home: "/var/adm",
},
},
{
ref: "42:1234",
expected: ExecUser{
Uid: 42,
Gid: 1234,
Sgids: defaultExecUser.Sgids,
Home: "/var/adm",
},
},
{
ref: "1337:1234",
expected: ExecUser{
Uid: 1337,
Gid: 1234,
Sgids: defaultExecUser.Sgids,
Home: defaultExecUser.Home,
},
},
{
ref: "1337",
expected: ExecUser{
Uid: 1337,
Gid: defaultExecUser.Gid,
Sgids: defaultExecUser.Sgids,
Home: defaultExecUser.Home,
},
},
{
ref: "",
expected: ExecUser{
Uid: defaultExecUser.Uid,
Gid: defaultExecUser.Gid,
Sgids: defaultExecUser.Sgids,
Home: defaultExecUser.Home,
},
},
}
for _, test := range tests {
passwd := strings.NewReader(passwdContent)
group := strings.NewReader(groupContent)
execUser, err := GetExecUser(test.ref, &defaultExecUser, passwd, group)
if err != nil {
t.Logf("got unexpected error when parsing '%s': %s", test.ref, err.Error())
t.Fail()
continue
}
if !reflect.DeepEqual(test.expected, *execUser) {
t.Logf("got: %#v", execUser)
t.Logf("expected: %#v", test.expected)
t.Fail()
continue
}
}
}
func TestInvalidGetExecUser(t *testing.T) {
const passwdContent = `
root:x:0:0:root user:/root:/bin/bash
adm:x:42:43:adm:/var/adm:/bin/false
this is just some garbage data
`
const groupContent = `
root:x:0:root
adm:x:43:
grp:x:1234:root,adm
this is just some garbage data
`
tests := []string{
// No such user/group.
"notuser",
"notuser:notgroup",
"root:notgroup",
"notuser:adm",
"8888:notgroup",
"notuser:8888",
// Invalid user/group values.
"-1:0",
"0:-3",
"-5:-2",
}
for _, test := range tests {
passwd := strings.NewReader(passwdContent)
group := strings.NewReader(groupContent)
execUser, err := GetExecUser(test, nil, passwd, group)
if err == nil {
t.Logf("got unexpected success when parsing '%s': %#v", test, execUser)
t.Fail()
continue
}
}
}
func TestGetExecUserNilSources(t *testing.T) {
const passwdContent = `
root:x:0:0:root user:/root:/bin/bash
adm:x:42:43:adm:/var/adm:/bin/false
this is just some garbage data
`
const groupContent = `
root:x:0:root
adm:x:43:
grp:x:1234:root,adm
this is just some garbage data
`
defaultExecUser := ExecUser{
Uid: 8888,
Gid: 8888,
Sgids: []int{8888},
Home: "/8888",
}
tests := []struct {
ref string
passwd, group bool
expected ExecUser
}{
{
ref: "",
passwd: false,
group: false,
expected: ExecUser{
Uid: 8888,
Gid: 8888,
Sgids: []int{8888},
Home: "/8888",
},
},
{
ref: "root",
passwd: true,
group: false,
expected: ExecUser{
Uid: 0,
Gid: 0,
Sgids: []int{8888},
Home: "/root",
},
},
{
ref: "0",
passwd: false,
group: false,
expected: ExecUser{
Uid: 0,
Gid: 8888,
Sgids: []int{8888},
Home: "/8888",
},
},
{
ref: "0:0",
passwd: false,
group: false,
expected: ExecUser{
Uid: 0,
Gid: 0,
Sgids: []int{8888},
Home: "/8888",
},
},
}
for _, test := range tests {
var passwd, group io.Reader
if test.passwd {
passwd = strings.NewReader(passwdContent)
}
if test.group {
group = strings.NewReader(groupContent)
}
execUser, err := GetExecUser(test.ref, &defaultExecUser, passwd, group)
if err != nil {
t.Logf("got unexpected error when parsing '%s': %s", test.ref, err.Error())
t.Fail()
continue
}
if !reflect.DeepEqual(test.expected, *execUser) {
t.Logf("got: %#v", execUser)
t.Logf("expected: %#v", test.expected)
t.Fail()
continue
}
}
}
func TestGetAdditionalGroups(t *testing.T) {
const groupContent = `
root:x:0:root
adm:x:43:
grp:x:1234:root,adm
adm:x:4343:root,adm-duplicate
this is just some garbage data
`
tests := []struct {
groups []string
expected []int
hasError bool
}{
{
// empty group
groups: []string{},
expected: []int{},
},
{
// single group
groups: []string{"adm"},
expected: []int{43},
},
{
// multiple groups
groups: []string{"adm", "grp"},
expected: []int{43, 1234},
},
{
// invalid group
groups: []string{"adm", "grp", "not-exist"},
expected: nil,
hasError: true,
},
{
// group with numeric id
groups: []string{"43"},
expected: []int{43},
},
{
// group with unknown numeric id
groups: []string{"adm", "10001"},
expected: []int{43, 10001},
},
{
// groups specified twice with numeric and name
groups: []string{"adm", "43"},
expected: []int{43},
},
{
// groups with too small id
groups: []string{"-1"},
expected: nil,
hasError: true,
},
{
// groups with too large id
groups: []string{strconv.Itoa(1 << 31)},
expected: nil,
hasError: true,
},
}
for _, test := range tests {
group := strings.NewReader(groupContent)
gids, err := GetAdditionalGroups(test.groups, group)
if test.hasError && err == nil {
t.Errorf("Parse(%#v) expects error but has none", test)
continue
}
if !test.hasError && err != nil {
t.Errorf("Parse(%#v) has error %v", test, err)
continue
}
sort.Sort(sort.IntSlice(gids))
if !reflect.DeepEqual(gids, test.expected) {
t.Errorf("Gids(%v), expect %v from groups %v", gids, test.expected, test.groups)
}
}
}
func TestGetAdditionalGroupsNumeric(t *testing.T) {
tests := []struct {
groups []string
expected []int
hasError bool
}{
{
// numeric groups only
groups: []string{"1234", "5678"},
expected: []int{1234, 5678},
},
{
// numeric and alphabetic
groups: []string{"1234", "fake"},
expected: nil,
hasError: true,
},
}
for _, test := range tests {
gids, err := GetAdditionalGroups(test.groups, nil)
if test.hasError && err == nil {
t.Errorf("Parse(%#v) expects error but has none", test)
continue
}
if !test.hasError && err != nil {
t.Errorf("Parse(%#v) has error %v", test, err)
continue
}
sort.Sort(sort.IntSlice(gids))
if !reflect.DeepEqual(gids, test.expected) {
t.Errorf("Gids(%v), expect %v from groups %v", gids, test.expected, test.groups)
}
}
}
+172
View File
@@ -0,0 +1,172 @@
package odp
import (
"fmt"
"syscall"
)
// Datapaths are identified by the ifindex of their netdev.
type DatapathID int32
type datapathInfo struct {
ifindex DatapathID
name string
}
func (dpif *Dpif) parseDatapathInfo(msg *NlMsgParser) (res datapathInfo, err error) {
_, ovshdr, err := dpif.checkNlMsgHeaders(msg, DATAPATH, OVS_DP_CMD_NEW)
if err != nil {
return
}
res.ifindex = ovshdr.datapathID()
attrs, err := msg.TakeAttrs()
if err != nil {
return
}
res.name, err = attrs.GetString(OVS_DP_ATTR_NAME)
return
}
type DatapathHandle struct {
dpif *Dpif
ifindex DatapathID
}
func (dp DatapathHandle) ID() DatapathID {
return dp.ifindex
}
func (dp DatapathHandle) Reopen() (DatapathHandle, error) {
dpif, err := dp.dpif.Reopen()
return DatapathHandle{dpif: dpif, ifindex: dp.ifindex}, err
}
func (dpif *Dpif) CreateDatapath(name string) (DatapathHandle, error) {
var features uint32 = OVS_DP_F_UNALIGNED | OVS_DP_F_VPORT_PIDS
req := NewNlMsgBuilder(RequestFlags, dpif.families[DATAPATH].id)
req.PutGenlMsghdr(OVS_DP_CMD_NEW, OVS_DATAPATH_VERSION)
req.putOvsHeader(0)
req.PutStringAttr(OVS_DP_ATTR_NAME, name)
req.PutUint32Attr(OVS_DP_ATTR_UPCALL_PID, 0)
req.PutUint32Attr(OVS_DP_ATTR_USER_FEATURES, features)
resp, err := dpif.sock.Request(req)
if err != nil {
return DatapathHandle{}, err
}
dpi, err := dpif.parseDatapathInfo(resp)
if err != nil {
return DatapathHandle{}, err
}
return DatapathHandle{dpif: dpif, ifindex: dpi.ifindex}, nil
}
func IsDatapathNameAlreadyExistsError(err error) bool {
return err == NetlinkError(syscall.EEXIST)
}
func (dpif *Dpif) LookupDatapath(name string) (DatapathHandle, error) {
req := NewNlMsgBuilder(RequestFlags, dpif.families[DATAPATH].id)
req.PutGenlMsghdr(OVS_DP_CMD_GET, OVS_DATAPATH_VERSION)
req.putOvsHeader(0)
req.PutStringAttr(OVS_DP_ATTR_NAME, name)
resp, err := dpif.sock.Request(req)
if err != nil {
return DatapathHandle{}, err
}
dpi, err := dpif.parseDatapathInfo(resp)
if err != nil {
return DatapathHandle{}, err
}
return DatapathHandle{dpif: dpif, ifindex: dpi.ifindex}, nil
}
type Datapath struct {
Handle DatapathHandle
Name string
}
func (dpif *Dpif) LookupDatapathByID(ifindex DatapathID) (Datapath, error) {
req := NewNlMsgBuilder(RequestFlags, dpif.families[DATAPATH].id)
req.PutGenlMsghdr(OVS_DP_CMD_GET, OVS_DATAPATH_VERSION)
req.putOvsHeader(ifindex)
resp, err := dpif.sock.Request(req)
if err != nil {
return Datapath{}, err
}
dpi, err := dpif.parseDatapathInfo(resp)
if err != nil {
return Datapath{}, err
}
return Datapath{
Handle: DatapathHandle{dpif: dpif, ifindex: ifindex},
Name: dpi.name,
}, nil
}
func IsNoSuchDatapathError(err error) bool {
return err == NetlinkError(syscall.ENODEV)
}
func (dpif *Dpif) EnumerateDatapaths() (map[string]DatapathHandle, error) {
res := make(map[string]DatapathHandle)
req := NewNlMsgBuilder(DumpFlags, dpif.families[DATAPATH].id)
req.PutGenlMsghdr(OVS_DP_CMD_GET, OVS_DATAPATH_VERSION)
req.putOvsHeader(0)
consumer := func(resp *NlMsgParser) error {
dpi, err := dpif.parseDatapathInfo(resp)
if err != nil {
return err
}
res[dpi.name] = DatapathHandle{dpif: dpif, ifindex: dpi.ifindex}
return nil
}
err := dpif.sock.RequestMulti(req, consumer)
if err != nil {
return nil, err
}
return res, nil
}
func (dp DatapathHandle) Delete() error {
req := NewNlMsgBuilder(RequestFlags, dp.dpif.families[DATAPATH].id)
req.PutGenlMsghdr(OVS_DP_CMD_DEL, OVS_DATAPATH_VERSION)
req.putOvsHeader(dp.ifindex)
_, err := dp.dpif.sock.Request(req)
if err != nil {
return err
}
dp.dpif = nil
dp.ifindex = 0
return nil
}
func (dp DatapathHandle) checkNlMsgHeaders(msg *NlMsgParser, family int, cmd int) error {
_, ovshdr, err := dp.dpif.checkNlMsgHeaders(msg, family, cmd)
if err != nil {
return err
}
if ovshdr.datapathID() != dp.ifindex {
return fmt.Errorf("wrong datapath ifindex received (got %d, expected %d)", ovshdr.datapathID(), dp.ifindex)
}
return nil
}
+181
View File
@@ -0,0 +1,181 @@
package odp
import (
"fmt"
"syscall"
"unsafe"
)
const (
DATAPATH = iota
VPORT = iota
FLOW = iota
PACKET = iota
FAMILY_COUNT = iota
)
var familyNames = [FAMILY_COUNT]string{
"ovs_datapath",
"ovs_vport",
"ovs_flow",
"ovs_packet",
}
type Dpif struct {
sock *NetlinkSocket
families [FAMILY_COUNT]GenlFamily
}
type familyUnavailableError struct {
family string
}
func (fue familyUnavailableError) Error() string {
return fmt.Sprintf("Generic netlink family '%s' unavailable; the Open vSwitch kernel module is probably not loaded, try 'modprobe openvswitch'", fue.family)
}
func IsKernelLacksODPError(err error) bool {
_, ok := err.(familyUnavailableError)
return ok
}
func lookupFamily(sock *NetlinkSocket, name string) (GenlFamily, error) {
family, err := sock.LookupGenlFamily(name)
if err == nil {
return family, nil
}
if err == NetlinkError(syscall.ENOENT) {
loadOpenvswitchModule()
// The module might be loaded now, so try again
family, err = sock.LookupGenlFamily(name)
if err == nil {
return family, nil
}
if err == NetlinkError(syscall.ENOENT) {
err = familyUnavailableError{name}
}
}
return GenlFamily{}, err
}
var triedLoadOpenvswitchModule bool
// This tries to provoke the kernel into loading the openvswitch
// module. Yes, netdev ioctls can be used to load arbitrary modules,
// if you have CAP_SYS_MODULE.
func loadOpenvswitchModule() {
if triedLoadOpenvswitchModule {
return
}
// netdev ioctls don't seem to work on netlink sockets, so we
// need a new socket for this purpose.
s, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, 0)
if err != nil {
triedLoadOpenvswitchModule = true
return
}
defer syscall.Close(s)
var req ifreqIfindex
copy(req.name[:], []byte("openvswitch"))
syscall.Syscall(syscall.SYS_IOCTL, uintptr(s),
syscall.SIOCGIFINDEX, uintptr(unsafe.Pointer(&req)))
triedLoadOpenvswitchModule = true
}
func NewDpif() (*Dpif, error) {
sock, err := OpenNetlinkSocket(syscall.NETLINK_GENERIC)
if err != nil {
return nil, err
}
dpif := &Dpif{sock: sock}
for i := 0; i < FAMILY_COUNT; i++ {
dpif.families[i], err = lookupFamily(sock, familyNames[i])
if err != nil {
sock.Close()
return nil, err
}
}
return dpif, nil
}
// Open a dpif with a new socket, but reuing the family info
func (dpif *Dpif) Reopen() (*Dpif, error) {
sock, err := OpenNetlinkSocket(syscall.NETLINK_GENERIC)
if err != nil {
return nil, err
}
return &Dpif{sock: sock, families: dpif.families}, nil
}
func (dpif *Dpif) getMCGroup(family int, name string) (uint32, error) {
mcGroup, ok := dpif.families[family].mcGroups[name]
if !ok {
return 0, fmt.Errorf("No genl MC group %s in family %s", name, familyNames[family])
}
return mcGroup, nil
}
func (dpif *Dpif) Close() error {
return dpif.sock.Close()
}
func (nlmsg *NlMsgBuilder) putOvsHeader(ifindex DatapathID) {
pos := nlmsg.AlignGrow(syscall.NLMSG_ALIGNTO, SizeofOvsHeader)
h := ovsHeaderAt(nlmsg.buf, pos)
h.DpIfIndex = int32(ifindex)
}
func (nlmsg *NlMsgParser) takeOvsHeader() (*OvsHeader, error) {
pos, err := nlmsg.AlignAdvance(syscall.NLMSG_ALIGNTO, SizeofOvsHeader)
if err != nil {
return nil, err
}
return ovsHeaderAt(nlmsg.data, pos), nil
}
func (ovshdr OvsHeader) datapathID() DatapathID {
return DatapathID(ovshdr.DpIfIndex)
}
func (dpif *Dpif) checkNlMsgHeaders(msg *NlMsgParser, family int, cmd int) (*GenlMsghdr, *OvsHeader, error) {
if _, err := msg.ExpectNlMsghdr(dpif.families[family].id); err != nil {
return nil, nil, err
}
genlhdr, err := msg.CheckGenlMsghdr(cmd)
if err != nil {
return nil, nil, err
}
ovshdr, err := msg.takeOvsHeader()
if err != nil {
return nil, nil, err
}
return genlhdr, ovshdr, nil
}
type Cancelable interface {
Cancel() error
}
type cancelableDpif struct {
*Dpif
}
func (dpif cancelableDpif) Cancel() error {
return dpif.Close()
}
+437
View File
@@ -0,0 +1,437 @@
package odp
import (
"fmt"
"math/rand"
"syscall"
"testing"
"time"
)
func init() {
rand.Seed(time.Now().UTC().UnixNano())
}
func checkedCloseDpif(dpif *Dpif, t *testing.T) {
err := dpif.Close()
if err != nil {
t.Fatal(err)
}
}
func TestCreateDatapath(t *testing.T) {
dpif, err := NewDpif()
if err != nil {
t.Fatal(err)
}
defer checkedCloseDpif(dpif, t)
name := fmt.Sprintf("test%d", rand.Intn(100000))
dp, err := dpif.CreateDatapath(name)
if err != nil {
t.Fatal(err)
}
err = dp.Delete()
if err != nil {
t.Fatal(err)
}
}
func TestLookupDatapath(t *testing.T) {
dpif, err := NewDpif()
if err != nil {
t.Fatal(err)
}
defer checkedCloseDpif(dpif, t)
name := fmt.Sprintf("test%d", rand.Intn(100000))
dp, err := dpif.LookupDatapath(name)
if !IsNoSuchDatapathError(err) {
t.Fatal(err)
}
_, err = dpif.CreateDatapath(name)
if err != nil {
t.Fatal(err)
}
checkedCloseDpif(dpif, t)
dpif, err = NewDpif()
if err != nil {
t.Fatal(err)
}
defer checkedCloseDpif(dpif, t)
dp, err = dpif.LookupDatapath(name)
if err != nil {
t.Fatal(err)
}
err = dp.Delete()
if err != nil {
t.Fatal(err)
}
}
func TestEnumerateDatapaths(t *testing.T) {
dpif, err := NewDpif()
if err != nil {
t.Fatal(err)
}
defer checkedCloseDpif(dpif, t)
var names []string
var dps []DatapathHandle
cleanup := func() {
for _, dp := range dps {
dp.Delete()
}
}
defer cleanup()
for i := 0; i < 10; i++ {
name := fmt.Sprintf("test%d", rand.Intn(100000))
dp, err := dpif.CreateDatapath(name)
if err != nil {
t.Fatal(err)
}
names = append(names, name)
dps = append(dps, dp)
}
name2dp, err := dpif.EnumerateDatapaths()
if err != nil {
t.Fatal(err)
}
for _, name := range names {
_, ok := name2dp[name]
if !ok {
t.Fatal()
}
}
cleanup()
name2dp, err = dpif.EnumerateDatapaths()
if err != nil {
t.Fatal(err)
}
for _, name := range names {
_, ok := name2dp[name]
if ok {
t.Fatal()
}
}
}
func checkedDeleteDatapath(dp DatapathHandle, t *testing.T) {
err := dp.Delete()
if err != nil {
t.Fatal(err)
}
}
func TestCreateVport(t *testing.T) {
dpif, err := NewDpif()
if err != nil {
t.Fatal(err)
}
defer checkedCloseDpif(dpif, t)
dp, err := dpif.CreateDatapath(fmt.Sprintf("test%d", rand.Intn(100000)))
if err != nil {
t.Fatal(err)
}
defer checkedDeleteDatapath(dp, t)
name := fmt.Sprintf("test%d", rand.Intn(100000))
vport, err := dp.CreateVport(NewInternalVportSpec(name))
if err != nil {
t.Fatal(err)
}
err = dp.DeleteVport(vport)
if err != nil {
t.Fatal(err)
}
}
func TestLookupVport(t *testing.T) {
dpif, err := NewDpif()
if err != nil {
t.Fatal(err)
}
defer checkedCloseDpif(dpif, t)
dpname := fmt.Sprintf("test%d", rand.Intn(100000))
dp, err := dpif.CreateDatapath(dpname)
if err != nil {
t.Fatal(err)
}
name := fmt.Sprintf("test%d", rand.Intn(100000))
vport, err := dp.LookupVportByName(name)
if !IsNoSuchVportError(err) {
t.Fatal(err)
}
_, err = dp.CreateVport(NewInternalVportSpec(name))
if err != nil {
t.Fatal(err)
}
checkedCloseDpif(dpif, t)
dpif, err = NewDpif()
if err != nil {
t.Fatal(err)
}
defer checkedCloseDpif(dpif, t)
dp, err = dpif.LookupDatapath(dpname)
if err != nil {
t.Fatal(err)
}
defer dp.Delete()
vport, err = dp.LookupVportByName(name)
if err != nil {
t.Fatal(err)
}
err = dp.DeleteVport(vport.ID)
if err != nil {
t.Fatal(err)
}
}
func TestEnumerateVports(t *testing.T) {
dpif, err := NewDpif()
if err != nil {
t.Fatal(err)
}
defer checkedCloseDpif(dpif, t)
dp, err := dpif.CreateDatapath(fmt.Sprintf("test%d", rand.Intn(100000)))
if err != nil {
t.Fatal(err)
}
defer checkedDeleteDatapath(dp, t)
var names []string
var vports []VportID
for i := 0; i < 10; i++ {
name := fmt.Sprintf("test%d", rand.Intn(100000))
vport, err := dp.CreateVport(NewInternalVportSpec(name))
if err != nil {
t.Fatal(err)
}
names = append(names, name)
vports = append(vports, vport)
}
gotvports, err := dp.EnumerateVports()
if err != nil {
t.Fatal(err)
}
gotnames := make(map[string]bool)
for _, vport := range gotvports {
gotnames[vport.Spec.Name()] = true
}
for _, name := range names {
_, ok := gotnames[name]
if !ok {
t.Fatal()
}
}
for _, vport := range vports {
dp.DeleteVport(vport)
}
gotvports, err = dp.EnumerateVports()
if err != nil {
t.Fatal(err)
}
gotnames = make(map[string]bool)
for _, vport := range gotvports {
gotnames[vport.Spec.Name()] = true
}
for _, name := range names {
_, ok := gotnames[name]
if ok {
t.Fatal()
}
}
}
var exactOvsKeyEthernetMask OvsKeyEthernet = OvsKeyEthernet{
EthSrc: [...]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
EthDst: [...]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
}
func TestCreateFlow(t *testing.T) {
dpif, err := NewDpif()
if err != nil {
t.Fatal(err)
}
defer checkedCloseDpif(dpif, t)
dp, err := dpif.CreateDatapath(fmt.Sprintf("test%d", rand.Intn(100000)))
if err != nil {
t.Fatal(err)
}
defer checkedDeleteDatapath(dp, t)
vpname := fmt.Sprintf("test%d", rand.Intn(100000))
vport, err := dp.CreateVport(NewInternalVportSpec(vpname))
if err != nil {
t.Fatal(err)
}
f := NewFlowSpec()
fk := NewEthernetFlowKey()
fk.SetEthSrc([...]byte{1, 2, 3, 4, 5, 6})
fk.SetEthDst([...]byte{1, 2, 3, 4, 5, 6})
f.AddKey(fk)
f.AddAction(NewOutputAction(vport))
err = dp.CreateFlow(f)
if err != nil {
t.Fatal(err)
}
err = dp.DeleteFlow(f.FlowKeys)
if err != nil {
t.Fatal(err)
}
err = dp.DeleteFlow(f.FlowKeys)
if !IsNoSuchFlowError(err) {
t.Fatal()
}
}
func TestEnumerateFlows(t *testing.T) {
dpif, err := NewDpif()
if err != nil {
t.Fatal(err)
}
defer checkedCloseDpif(dpif, t)
dp, err := dpif.CreateDatapath(fmt.Sprintf("test%d", rand.Intn(100000)))
if err != nil {
t.Fatal(err)
}
defer checkedDeleteDatapath(dp, t)
vpname := fmt.Sprintf("test%d", rand.Intn(100000))
vport, err := dp.CreateVport(NewInternalVportSpec(vpname))
if err != nil {
t.Fatal(err)
}
const n = 10
var flows [n]FlowSpec
for i := range flows {
flow := NewFlowSpec()
fk := NewEthernetFlowKey()
fk.SetEthSrc([...]byte{1, 2, 3, 4, 5, byte(i)})
fk.SetEthDst([...]byte{6, 5, 4, 3, 2, 1})
flow.AddKey(fk)
flow.AddAction(NewOutputAction(vport))
err = dp.CreateFlow(flow)
if err != nil {
t.Fatal(err)
}
flows[i] = flow
}
eflows, err := dp.EnumerateFlows()
if err != nil {
t.Fatal(err)
}
if len(eflows) != n {
t.Fatal()
}
for _, eflow := range eflows {
found := false
for _, flow := range flows {
if eflow.Equals(flow) {
found = true
break
}
}
if !found {
t.Fatal(eflow)
}
}
for _, eflow := range eflows {
err = dp.DeleteFlow(eflow.FlowKeys)
if err != nil {
t.Fatal(err)
}
}
eflows, err = dp.EnumerateFlows()
if err != nil {
t.Fatal(err)
}
if len(eflows) != 0 {
t.Fatal()
}
}
func TestConsumeVportEvents(t *testing.T) {
dpif, err := NewDpif()
if err != nil {
t.Fatal(err)
}
time.Sleep(100 * time.Millisecond)
ch := make(chan error)
cancel, err := dpif.ConsumeVportEvents(vportTestConsumer{ch})
if err != nil {
t.Fatal(err)
}
if err := cancel.Cancel(); err != nil {
t.Fatal(err)
}
if <-ch != syscall.EBADF {
t.Fatal()
}
}
type vportTestConsumer struct {
ch chan error
}
func (vportTestConsumer) VportCreated(ifindex int32, vport Vport) error {
return nil
}
func (vportTestConsumer) VportDeleted(ifindex int32, vport Vport) error {
return nil
}
func (consumer vportTestConsumer) Error(err error, stopped bool) {
consumer.ch <- err
}
+1349
View File
File diff suppressed because it is too large Load Diff
+101
View File
@@ -0,0 +1,101 @@
package odp
import (
"fmt"
"syscall"
)
type GenlFamily struct {
id uint16
mcGroups map[string]uint32
}
func (nlmsg *NlMsgBuilder) PutGenlMsghdr(cmd uint8, version uint8) *GenlMsghdr {
pos := nlmsg.AlignGrow(syscall.NLMSG_ALIGNTO, SizeofGenlMsghdr)
res := genlMsghdrAt(nlmsg.buf, pos)
res.Cmd = cmd
res.Version = version
return res
}
func (nlmsg *NlMsgParser) CheckGenlMsghdr(cmd int) (*GenlMsghdr, error) {
pos, err := nlmsg.AlignAdvance(syscall.NLMSG_ALIGNTO, SizeofGenlMsghdr)
if err != nil {
return nil, err
}
gh := genlMsghdrAt(nlmsg.data, pos)
if cmd >= 0 && gh.Cmd != uint8(cmd) {
return nil, fmt.Errorf("generic netlink response has wrong cmd (got %d, expected %d)", gh.Cmd, cmd)
}
// Deliberately ignore the version field in the genl header.
// It's unclear exactly what its meaning is, and how we should
// handle it. E.g., if the version is higher than we expect,
// should we still try to handle the message? It's unclear,
// but the fact that ODP bumped the kernel
// OVS_DATAPATH_VERSION from 1 to 2 while expecting existing
// userspace to keep working suggests that we should be
// libreral in what we accept.
return gh, nil
}
func (s *NetlinkSocket) LookupGenlFamily(name string) (family GenlFamily, err error) {
req := NewNlMsgBuilder(RequestFlags, GENL_ID_CTRL)
req.PutGenlMsghdr(CTRL_CMD_GETFAMILY, 0)
req.PutStringAttr(CTRL_ATTR_FAMILY_NAME, name)
resp, err := s.Request(req)
if err != nil {
return
}
_, err = resp.ExpectNlMsghdr(GENL_ID_CTRL)
if err != nil {
return
}
_, err = resp.CheckGenlMsghdr(CTRL_CMD_NEWFAMILY)
if err != nil {
return
}
attrs, err := resp.TakeAttrs()
if err != nil {
return
}
family.id, err = attrs.GetUint16(CTRL_ATTR_FAMILY_ID)
if err != nil {
return
}
mcGroupAttrs, err := attrs.GetNestedAttrs(CTRL_ATTR_MCAST_GROUPS, true)
if err != nil || mcGroupAttrs == nil {
return
}
family.mcGroups = make(map[string]uint32)
for _, data := range mcGroupAttrs {
groupAttrs, err := ParseNestedAttrs(data)
if err != nil {
return family, err
}
id, err := groupAttrs.GetUint32(CTRL_ATTR_MCAST_GRP_ID)
if err != nil {
return family, err
}
name, err := groupAttrs.GetString(CTRL_ATTR_MCAST_GRP_NAME)
if err != nil {
return family, err
}
family.mcGroups[name] = id
}
return
}
+698
View File
@@ -0,0 +1,698 @@
package odp
import (
"fmt"
"reflect"
"sync/atomic"
"syscall"
)
func align(n int, a int) int {
return (n + a - 1) & -a
}
type NetlinkSocket struct {
fd int
addr *syscall.SockaddrNetlink
buf []byte
}
func OpenNetlinkSocket(protocol int) (*NetlinkSocket, error) {
fd, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW, protocol)
if err != nil {
return nil, err
}
success := false
defer func() {
if !success {
syscall.Close(fd)
}
}()
// It's fairly easy to provoke ENOBUFS from a netlink socket
// receiving miss upcalls when every packet misses. The
// default socket buffer size is relatively small at 200KB,
// and the default of /proc/sys/net/core/rmem_max means we
// can't easily increase it.
if err := syscall.SetsockoptInt(fd, SOL_NETLINK, syscall.NETLINK_NO_ENOBUFS, 1); err != nil {
return nil, err
}
addr := syscall.SockaddrNetlink{Family: syscall.AF_NETLINK}
if err := syscall.Bind(fd, &addr); err != nil {
return nil, err
}
localaddr, err := syscall.Getsockname(fd)
if err != nil {
return nil, err
}
nladdr, ok := localaddr.(*syscall.SockaddrNetlink)
if !ok {
return nil, fmt.Errorf("Expected netlink sockaddr, got %s", reflect.TypeOf(localaddr))
}
success = true
return &NetlinkSocket{
fd: fd,
addr: nladdr,
// netlink messages can be bigger than this, but it
// seems unlikely in practice, and this is similar to
// the limit that the OVS userspace imposes.
buf: make([]byte, 65536),
}, nil
}
func (s *NetlinkSocket) PortId() uint32 {
return s.addr.Pid
}
func (s *NetlinkSocket) Close() error {
if s.fd < 0 {
return nil
}
err := syscall.Close(s.fd)
s.fd = -1
return err
}
type NlMsgBuilder struct {
buf []byte
}
func NewNlMsgBuilder(flags uint16, typ uint16) *NlMsgBuilder {
buf := MakeAlignedByteSlice(syscall.NLMSG_HDRLEN)
nlmsg := &NlMsgBuilder{buf: buf}
h := nlMsghdrAt(buf, 0)
h.Flags = flags
h.Type = typ
return nlmsg
}
// Expand the array underlying a slice to have capacity of at least l
func expand(buf []byte, l int) []byte {
c := (cap(buf) + 1) * 3 / 2
for l > c {
c = (c + 1) * 3 / 2
}
new := MakeAlignedByteSliceCap(len(buf), c)
copy(new, buf)
return new
}
func (nlmsg *NlMsgBuilder) Align(a int) {
l := align(len(nlmsg.buf), a)
if l > cap(nlmsg.buf) {
nlmsg.buf = expand(nlmsg.buf, l)
}
nlmsg.buf = nlmsg.buf[:l]
}
func (nlmsg *NlMsgBuilder) Grow(size uintptr) int {
pos := len(nlmsg.buf)
l := pos + int(size)
if l > cap(nlmsg.buf) {
nlmsg.buf = expand(nlmsg.buf, l)
}
nlmsg.buf = nlmsg.buf[:l]
return pos
}
func (nlmsg *NlMsgBuilder) AlignGrow(a int, size uintptr) int {
apos := align(len(nlmsg.buf), a)
l := apos + int(size)
if l > cap(nlmsg.buf) {
nlmsg.buf = expand(nlmsg.buf, l)
}
nlmsg.buf = nlmsg.buf[:l]
return apos
}
var nextSeqNo uint32
func (nlmsg *NlMsgBuilder) Finish() (res []byte, seq uint32) {
h := nlMsghdrAt(nlmsg.buf, 0)
h.Len = uint32(len(nlmsg.buf))
seq = atomic.AddUint32(&nextSeqNo, 1)
h.Seq = seq
res = nlmsg.buf
nlmsg.buf = nil
return
}
func (nlmsg *NlMsgBuilder) PutAttr(typ uint16, gen func()) {
pos := nlmsg.AlignGrow(syscall.NLA_ALIGNTO, syscall.SizeofNlAttr)
gen()
nla := nlAttrAt(nlmsg.buf, pos)
nla.Type = typ
nla.Len = uint16(len(nlmsg.buf) - pos)
}
func (nlmsg *NlMsgBuilder) PutNestedAttrs(typ uint16, gen func()) {
nlmsg.PutAttr(typ, func() {
gen()
// The kernel nlattr parser expects the alignment
// padding at the end of a nested attributes value to
// be included in the length of the enclosing
// attribute
nlmsg.Align(syscall.NLA_ALIGNTO)
})
}
func (nlmsg *NlMsgBuilder) PutEmptyAttr(typ uint16) {
nlmsg.PutAttr(typ, func() {})
}
func (nlmsg *NlMsgBuilder) PutUint8Attr(typ uint16, val uint8) {
nlmsg.PutAttr(typ, func() {
pos := nlmsg.Grow(1)
nlmsg.buf[pos] = val
})
}
func (nlmsg *NlMsgBuilder) PutUint16Attr(typ uint16, val uint16) {
nlmsg.PutAttr(typ, func() {
pos := nlmsg.Grow(2)
*uint16At(nlmsg.buf, pos) = val
})
}
func (nlmsg *NlMsgBuilder) PutUint32Attr(typ uint16, val uint32) {
nlmsg.PutAttr(typ, func() {
pos := nlmsg.Grow(4)
*uint32At(nlmsg.buf, pos) = val
})
}
func (nlmsg *NlMsgBuilder) putStringZ(str string) {
l := len(str)
pos := nlmsg.Grow(uintptr(l) + 1)
copy(nlmsg.buf[pos:], str)
nlmsg.buf[pos+l] = 0
}
func (nlmsg *NlMsgBuilder) PutStringAttr(typ uint16, str string) {
nlmsg.PutAttr(typ, func() { nlmsg.putStringZ(str) })
}
func (nlmsg *NlMsgBuilder) PutSliceAttr(typ uint16, data []byte) {
nlmsg.PutAttr(typ, func() {
pos := nlmsg.Grow(uintptr(len(data)))
copy(nlmsg.buf[pos:], data)
})
}
type NetlinkError syscall.Errno
func (err NetlinkError) Error() string {
return fmt.Sprintf("netlink error response: %s", syscall.Errno(err))
}
type NlMsgParser struct {
data []byte
pos int
}
func (nlmsg *NlMsgParser) Advance(size uintptr) error {
if err := nlmsg.CheckAvailable(size); err != nil {
return err
}
nlmsg.pos += int(size)
return nil
}
func (nlmsg *NlMsgParser) AlignAdvance(a int, size uintptr) (int, error) {
pos := align(nlmsg.pos, a)
nlmsg.pos = pos
if err := nlmsg.Advance(size); err != nil {
return 0, err
}
return pos, nil
}
func (nlmsg *NlMsgParser) NlMsghdr() *syscall.NlMsghdr {
return nlMsghdrAt(nlmsg.data, nlmsg.pos)
}
func (msg *NlMsgParser) nextNlMsg() (*NlMsgParser, error) {
pos := msg.pos
avail := len(msg.data) - pos
if avail <= 0 {
return nil, nil
}
if avail < syscall.SizeofNlMsghdr {
return nil, fmt.Errorf("netlink message header truncated")
}
h := msg.NlMsghdr()
if avail < int(h.Len) {
return nil, fmt.Errorf("netlink message truncated (%d bytes available, %d expected)", avail, h.Len)
}
end := pos + int(h.Len)
msg.pos = align(end, syscall.NLMSG_ALIGNTO)
return &NlMsgParser{data: msg.data[:end], pos: pos}, nil
}
func (nlmsg *NlMsgParser) CheckAvailable(size uintptr) error {
if nlmsg.pos+int(size) > len(nlmsg.data) {
return fmt.Errorf("netlink message truncated")
}
return nil
}
func (nlmsg *NlMsgParser) checkHeader() error {
// nextNlMsg ensures that there is an nlmsghdr-worth of data
// present
h := nlmsg.NlMsghdr()
if h.Type == syscall.NLMSG_ERROR {
nlerr := nlMsgerrAt(nlmsg.data, nlmsg.pos+syscall.NLMSG_HDRLEN)
if nlerr.Error != 0 {
return NetlinkError(-nlerr.Error)
}
// an error code of 0 means the error is an ack, so
// return normally.
}
return nil
}
func (nlmsg *NlMsgParser) checkResponseHeader(expectedPortId uint32, expectedSeq uint32) (relevant bool, err error) {
// nextNlMsg ensures that there is an nlmsghdr-worth of data
// present
h := nlmsg.NlMsghdr()
if h.Pid != expectedPortId {
return true, fmt.Errorf("netlink reply port id mismatch (got %d, expected %d)", h.Pid, expectedPortId)
}
if h.Seq != expectedSeq {
// This doesn't necessarily indicate an error. For
// example, if an early requestMulti was interrupted
// due to an error, we might still be getting its
// response messages back that, and we should discard
// them. On the other hand, sequence number
// mismatches might indicate bugs, so it is sometimes
// nice to see them in development.
fmt.Printf("netlink reply sequence number mismatch (got %d, expected %d)\n", h.Seq, expectedSeq)
return false, nil
}
return true, nlmsg.checkHeader()
}
func (nlmsg *NlMsgParser) ExpectNlMsghdr(typ uint16) (*syscall.NlMsghdr, error) {
h := nlmsg.NlMsghdr()
if err := nlmsg.Advance(syscall.SizeofNlMsghdr); err != nil {
return nil, err
}
if h.Type != typ {
return nil, fmt.Errorf("netlink response has wrong type (got %d, expected %d)", h.Type, typ)
}
return h, nil
}
type Attrs map[uint16][]byte
func (attrs Attrs) Get(typ uint16, optional bool) ([]byte, error) {
val, ok := attrs[typ]
if !ok && !optional {
return nil, fmt.Errorf("missing netlink attribute %d", typ)
}
return val, nil
}
func (attrs Attrs) GetFixedBytes(typ uint16, expect int, optional bool) ([]byte, error) {
val, err := attrs.Get(typ, optional)
if err != nil || val == nil {
return nil, err
}
if len(val) != expect {
return nil, fmt.Errorf("attribute %d has wrong length (got %d bytes, expected %d bytes)", typ, len(val), expect)
}
return val, nil
}
func (attrs Attrs) GetOptionalBytes(typ uint16, dest []byte) (bool, error) {
val, err := attrs.GetFixedBytes(typ, len(dest), true)
if err != nil || val == nil {
return false, err
}
copy(dest, val)
return true, nil
}
func (attrs Attrs) GetEmpty(typ uint16) (bool, error) {
val, err := attrs.Get(typ, true)
if err != nil || val == nil {
return false, err
}
if len(val) != 0 {
return false, fmt.Errorf("empty attribute %d has wrong length (%d bytes)", typ, len(val))
}
return true, nil
}
func (attrs Attrs) GetOptionalUint8(typ uint16) (uint8, bool, error) {
val, err := attrs.Get(typ, true)
if err != nil || val == nil {
return 0, false, err
}
if len(val) != 1 {
return 0, false, fmt.Errorf("uint8 attribute %d has wrong length (%d bytes)", typ, len(val))
}
return val[0], true, nil
}
func (attrs Attrs) getUint16(typ uint16, optional bool) (uint16, bool, error) {
val, err := attrs.Get(typ, optional)
if err != nil || val == nil {
return 0, false, err
}
if len(val) != 2 {
return 0, false, fmt.Errorf("uint16 attribute %d has wrong length (%d bytes)", typ, len(val))
}
return *uint16At(val, 0), true, nil
}
func (attrs Attrs) GetUint16(typ uint16) (uint16, error) {
res, _, err := attrs.getUint16(typ, false)
return res, err
}
func (attrs Attrs) GetOptionalUint16(typ uint16) (uint16, bool, error) {
return attrs.getUint16(typ, true)
}
func (attrs Attrs) GetUint32(typ uint16) (uint32, error) {
val, err := attrs.Get(typ, false)
if err != nil {
return 0, err
}
if len(val) != 4 {
return 0, fmt.Errorf("uint32 attribute %d has wrong length (%d bytes)", typ, len(val))
}
return *uint32At(val, 0), nil
}
func (attrs Attrs) getUint64(typ uint16, optional bool) (uint64, bool, error) {
val, err := attrs.Get(typ, optional)
if err != nil || val == nil {
return 0, false, err
}
if len(val) != 8 {
return 0, false, fmt.Errorf("uint64 attribute %d has wrong length (%d bytes)", typ, len(val))
}
return *uint64At(val, 0), true, nil
}
func (attrs Attrs) GetUint64(typ uint16) (uint64, error) {
res, _, err := attrs.getUint64(typ, false)
return res, err
}
func (attrs Attrs) GetOptionalUint64(typ uint16) (uint64, bool, error) {
return attrs.getUint64(typ, true)
}
func (attrs Attrs) GetString(typ uint16) (string, error) {
val, err := attrs.Get(typ, false)
if err != nil {
return "", err
}
if len(val) == 0 {
return "", fmt.Errorf("string attribute %d has zero length", typ)
}
if val[len(val)-1] != 0 {
return "", fmt.Errorf("string attribute %d does not end with nul byte", typ)
}
return string(val[0 : len(val)-1]), nil
}
func (nlmsg *NlMsgParser) checkData(l uintptr, obj string) error {
if nlmsg.pos+int(l) <= len(nlmsg.data) {
return nil
} else {
return fmt.Errorf("truncated %s (have %d bytes, expected %d)", obj, len(nlmsg.data)-nlmsg.pos, l)
}
}
func (nlmsg *NlMsgParser) parseAttrs(consumer func(uint16, []byte)) error {
for {
apos := align(nlmsg.pos, syscall.NLA_ALIGNTO)
if len(nlmsg.data) <= apos {
break
}
nlmsg.pos = apos
if err := nlmsg.checkData(syscall.SizeofNlAttr, "netlink attribute"); err != nil {
return err
}
nla := nlAttrAt(nlmsg.data, nlmsg.pos)
if err := nlmsg.checkData(uintptr(nla.Len), "netlink attribute"); err != nil {
return err
}
valpos := align(nlmsg.pos+syscall.SizeofNlAttr, syscall.NLA_ALIGNTO)
consumer(nla.Type, nlmsg.data[valpos:nlmsg.pos+int(nla.Len)])
nlmsg.pos += int(nla.Len)
}
return nil
}
func (nlmsg *NlMsgParser) TakeAttrs() (Attrs, error) {
res := make(Attrs)
err := nlmsg.parseAttrs(func(typ uint16, val []byte) {
res[typ] = val
})
return res, err
}
func ParseNestedAttrs(data []byte) (Attrs, error) {
parser := NlMsgParser{data: data, pos: 0}
return parser.TakeAttrs()
}
func (attrs Attrs) GetNestedAttrs(typ uint16, optional bool) (Attrs, error) {
val, err := attrs.Get(typ, optional)
if val == nil {
return nil, err
}
return ParseNestedAttrs(val)
}
// Usually we parse attributes into a map, but there are cases where
// attribute order matters.
type Attr struct {
typ uint16
val []byte
}
func (attrs Attrs) GetOrderedAttrs(typ uint16) ([]Attr, error) {
val, err := attrs.Get(typ, false)
if val == nil {
return nil, err
}
parser := NlMsgParser{data: val, pos: 0}
res := make([]Attr, 0)
err = parser.parseAttrs(func(typ uint16, val []byte) {
res = append(res, Attr{typ, val})
})
return res, err
}
func (s *NetlinkSocket) send(msg *NlMsgBuilder) (uint32, error) {
sa := syscall.SockaddrNetlink{
Family: syscall.AF_NETLINK,
Pid: 0,
Groups: 0,
}
data, seq := msg.Finish()
return seq, syscall.Sendto(s.fd, data, 0, &sa)
}
func (s *NetlinkSocket) recv(peer uint32) (*NlMsgParser, error) {
nr, from, err := syscall.Recvfrom(s.fd, s.buf, 0)
if err != nil {
return nil, err
}
buf := MakeAlignedByteSlice(nr)
copy(buf, s.buf)
switch nlfrom := from.(type) {
case *syscall.SockaddrNetlink:
if nlfrom.Pid != peer {
return nil, fmt.Errorf("wrong netlink peer pid (expected %d, got %d)", peer, nlfrom.Pid)
}
return &NlMsgParser{data: buf, pos: 0}, nil
default:
return nil, fmt.Errorf("Expected netlink sockaddr, got %s", reflect.TypeOf(from))
}
}
func (s *NetlinkSocket) Receive(consumer func(*NlMsgParser) (bool, error)) error {
for {
resp, err := s.recv(0)
if err != nil {
return err
}
msg, err := resp.nextNlMsg()
if err != nil {
return err
}
if msg == nil {
return fmt.Errorf("netlink response message missing")
}
for {
done, err := consumer(msg)
if done || err != nil {
return err
}
msg, err = resp.nextNlMsg()
if err != nil {
return err
}
if msg == nil {
break
}
}
}
}
// Some generic netlink operations always return a reply message (e.g
// *_GET), others don't by default (e.g. *_NEW). In the latter case,
// NLM_F_ECHO forces a reply. This is undocumented AFAICT.
const RequestFlags = syscall.NLM_F_REQUEST | syscall.NLM_F_ECHO
// Do a netlink request that yields a single response message.
func (s *NetlinkSocket) Request(req *NlMsgBuilder) (resp *NlMsgParser, err error) {
seq, err := s.send(req)
if err != nil {
return nil, err
}
err = s.Receive(func(msg *NlMsgParser) (bool, error) {
relevant, err := msg.checkResponseHeader(s.PortId(), seq)
if relevant && err == nil {
resp = msg
}
return true, err
})
return
}
const DumpFlags = syscall.NLM_F_DUMP | syscall.NLM_F_REQUEST
// Do a netlink request that yield multiple response messages.
func (s *NetlinkSocket) RequestMulti(req *NlMsgBuilder, consumer func(*NlMsgParser) error) error {
seq, err := s.send(req)
if err != nil {
return err
}
return s.Receive(func(msg *NlMsgParser) (bool, error) {
relevant, err := msg.checkResponseHeader(s.PortId(), seq)
if !relevant || err != nil {
return false, err
}
if msg.NlMsghdr().Type == syscall.NLMSG_DONE {
return true, processNlMsgDone(msg)
}
err = consumer(msg)
if err != nil {
return true, err
}
return false, nil
})
}
func processNlMsgDone(msg *NlMsgParser) error {
err := msg.Advance(syscall.SizeofNlMsghdr)
if err != nil {
return err
}
err = msg.checkData(4, "NLMSG_DONE error code")
if err != nil {
return err
}
errno := *int32At(msg.data, msg.pos)
if errno == 0 {
return nil
} else {
return NetlinkError(-errno)
}
}
type Consumer interface {
Error(err error, stopped bool)
}
func (s *NetlinkSocket) consume(consumer Consumer, handler func(*NlMsgParser) error) {
for {
err := s.Receive(func(msg *NlMsgParser) (bool, error) {
err := msg.checkHeader()
if err == nil {
err = handler(msg)
if err == nil {
return false, nil
}
}
consumer.Error(err, false)
return false, nil
})
if err != nil {
consumer.Error(err, true)
break
}
}
}
+173
View File
@@ -0,0 +1,173 @@
package odp
import (
"sync"
)
type MissConsumer interface {
Miss(packet []byte, flowKeys FlowKeys) error
Error(err error, stopped bool)
}
func (origDP DatapathHandle) ConsumeMisses(consumer MissConsumer) (Cancelable, error) {
// We end up needing 3 netlink sockets: one to consume
// misses, one to consume vport events, and one for general
// use.
dp, err := origDP.Reopen()
if err != nil {
return nil, err
}
success := false
defer func() {
if !success {
dp.dpif.Close()
}
}()
missDP, err := origDP.Reopen()
if err != nil {
return nil, err
}
defer func() {
if !success {
missDP.dpif.Close()
}
}()
// We need to set the upcall port ID on all vports. That
// includes vports that get added while we are listening, so
// we need to listen for them too.
vportConsumer := &missVportConsumer{
dp: dp,
upcallPortId: missDP.dpif.sock.PortId(),
missConsumer: consumer,
vportsDone: make(map[VportID]struct{}),
}
vportCancel, err := origDP.ConsumeVportEvents(vportConsumer)
if err != nil {
return nil, err
}
defer func() {
if !success {
vportCancel.Cancel()
}
}()
vports, err := origDP.EnumerateVports()
if err != nil {
return nil, err
}
for _, vport := range vports {
err = vportConsumer.setVportUpcallPortId(vport.ID)
if err != nil {
return nil, err
}
}
success = true
vportConsumer.cancel = vportCancel
go missDP.consumeMisses(consumer, vportConsumer)
return cancelableDpif{missDP.dpif}, nil
}
type missVportConsumer struct {
dp DatapathHandle
upcallPortId uint32
missConsumer MissConsumer
cancel Cancelable
lock sync.Mutex
vportsDone map[VportID]struct{}
}
// Set a vport's upcall port ID. This generates a OVS_VPORT_CMD_NEW
// (not a OVS_VPORT_CMD_SET), leading to a call of the New method
// below. So we need to record which vports we already processed in
// order to avoid a vicious circle.
func (c *missVportConsumer) setVportUpcallPortId(vport VportID) error {
c.lock.Lock()
defer c.lock.Unlock()
if _, doneAlready := c.vportsDone[vport]; doneAlready {
return nil
}
if err := c.dp.setVportUpcallPortId(vport, c.upcallPortId); err != nil {
return err
}
c.vportsDone[vport] = struct{}{}
return nil
}
func (c *missVportConsumer) VportCreated(dpid DatapathID, vport Vport) error {
return c.setVportUpcallPortId(vport.ID)
}
func (c *missVportConsumer) VportDeleted(dpid DatapathID, vport Vport) error {
c.lock.Lock()
delete(c.vportsDone, vport.ID)
c.lock.Unlock()
return nil
}
func (c *missVportConsumer) Error(err error, stopped bool) {
c.missConsumer.Error(err, stopped)
}
func (dp DatapathHandle) consumeMisses(consumer MissConsumer, vportConsumer *missVportConsumer) {
dp.dpif.sock.consume(consumer, func(msg *NlMsgParser) error {
if err := dp.checkNlMsgHeaders(msg, PACKET, OVS_PACKET_CMD_MISS); err != nil {
return err
}
attrs, err := msg.TakeAttrs()
if err != nil {
return err
}
fkattrs, err := attrs.GetNestedAttrs(OVS_PACKET_ATTR_KEY, false)
if err != nil {
return err
}
fks, err := ParseFlowKeys(fkattrs, nil)
if err != nil {
return err
}
return consumer.Miss(attrs[OVS_PACKET_ATTR_PACKET], fks)
})
vportConsumer.cancel.Cancel()
vportConsumer.dp.dpif.Close()
}
func (dp DatapathHandle) Execute(packet []byte, keys FlowKeys, actions []Action) error {
dpif := dp.dpif
req := NewNlMsgBuilder(RequestFlags, dpif.families[PACKET].id)
req.PutGenlMsghdr(OVS_PACKET_CMD_EXECUTE, OVS_PACKET_VERSION)
req.putOvsHeader(dp.ifindex)
req.PutSliceAttr(OVS_PACKET_ATTR_PACKET, packet)
req.PutNestedAttrs(OVS_PACKET_ATTR_KEY, func() {
for _, k := range keys {
k.putKeyNlAttr(req)
}
})
req.PutNestedAttrs(OVS_PACKET_ATTR_ACTIONS, func() {
for _, a := range actions {
a.toNlAttr(req)
}
})
_, err := dpif.sock.send(req)
return err
}
+220
View File
@@ -0,0 +1,220 @@
package odp
import "syscall"
// from linux/include/linux/socket.h
const SOL_NETLINK = 270
type GenlMsghdr struct {
Cmd uint8
Version uint8
Reserved uint16
}
const SizeofGenlMsghdr = 4
// reserved static generic netlink identifiers:
const (
GENL_ID_GENERATE = 0
GENL_ID_CTRL = syscall.NLMSG_MIN_TYPE
GENL_ID_VFS_DQUOT = syscall.NLMSG_MIN_TYPE + 1
GENL_ID_PMCRAID = syscall.NLMSG_MIN_TYPE + 2
)
const (
CTRL_CMD_UNSPEC = 0
CTRL_CMD_NEWFAMILY = 1
CTRL_CMD_DELFAMILY = 2
CTRL_CMD_GETFAMILY = 3
CTRL_CMD_NEWOPS = 4
CTRL_CMD_DELOPS = 5
CTRL_CMD_GETOPS = 6
CTRL_CMD_NEWMCAST_GRP = 7
CTRL_CMD_DELMCAST_GRP = 8
)
const (
CTRL_ATTR_UNSPEC = 0
CTRL_ATTR_FAMILY_ID = 1
CTRL_ATTR_FAMILY_NAME = 2
CTRL_ATTR_VERSION = 3
CTRL_ATTR_HDRSIZE = 4
CTRL_ATTR_MAXATTR = 5
CTRL_ATTR_OPS = 6
CTRL_ATTR_MCAST_GROUPS = 7
)
const (
CTRL_ATTR_MCAST_GRP_UNSPEC = 0
CTRL_ATTR_MCAST_GRP_NAME = 1
CTRL_ATTR_MCAST_GRP_ID = 2
)
type OvsHeader struct {
DpIfIndex int32
}
const SizeofOvsHeader = 4
const (
OVS_DATAPATH_VERSION = 2
OVS_VPORT_VERSION = 1
OVS_FLOW_VERSION = 1
OVS_PACKET_VERSION = 1
)
const ( // ovs_datapath_cmd
OVS_DP_CMD_UNSPEC = 0
OVS_DP_CMD_NEW = 1
OVS_DP_CMD_DEL = 2
OVS_DP_CMD_GET = 3
OVS_DP_CMD_SET = 4
)
const ( // ovs_datapath_attr
OVS_DP_ATTR_UNSPEC = 0
OVS_DP_ATTR_NAME = 1
OVS_DP_ATTR_UPCALL_PID = 2
OVS_DP_ATTR_STATS = 3
OVS_DP_ATTR_MEGAFLOW_STATS = 4
OVS_DP_ATTR_USER_FEATURES = 5
)
const (
OVS_DP_F_UNALIGNED = 1
OVS_DP_F_VPORT_PIDS = 2
)
const ( // ovs_vport_cmd
OVS_VPORT_CMD_UNSPEC = 0
OVS_VPORT_CMD_NEW = 1
OVS_VPORT_CMD_DEL = 2
OVS_VPORT_CMD_GET = 3
OVS_VPORT_CMD_SET = 4
)
const ( // ovs_vport_attr
OVS_VPORT_ATTR_UNSPEC = 0
OVS_VPORT_ATTR_PORT_NO = 1
OVS_VPORT_ATTR_TYPE = 2
OVS_VPORT_ATTR_NAME = 3
OVS_VPORT_ATTR_OPTIONS = 4
OVS_VPORT_ATTR_UPCALL_PID = 5
OVS_VPORT_ATTR_STATS = 6
)
const ( // ovs_vport_type
OVS_VPORT_TYPE_UNSPEC = 0
OVS_VPORT_TYPE_NETDEV = 1
OVS_VPORT_TYPE_INTERNAL = 2
OVS_VPORT_TYPE_GRE = 3
OVS_VPORT_TYPE_VXLAN = 4
)
const ( // OVS_VPORT_ATTR_OPTIONS attributes for tunnels
OVS_TUNNEL_ATTR_UNSPEC = 0
OVS_TUNNEL_ATTR_DST_PORT = 1
)
const ( // ovs_flow_cmd
OVS_FLOW_CMD_UNSPEC = 0
OVS_FLOW_CMD_NEW = 1
OVS_FLOW_CMD_DEL = 2
OVS_FLOW_CMD_GET = 3
OVS_FLOW_CMD_SET = 4
)
const ( // ovs_flow_attr
OVS_FLOW_ATTR_UNSPEC = 0
OVS_FLOW_ATTR_KEY = 1
OVS_FLOW_ATTR_ACTIONS = 2
OVS_FLOW_ATTR_STATS = 3
OVS_FLOW_ATTR_TCP_FLAGS = 4
OVS_FLOW_ATTR_USED = 5
OVS_FLOW_ATTR_CLEAR = 6
OVS_FLOW_ATTR_MASK = 7
)
type OvsFlowStats struct {
NPackets uint64
NBytes uint64
}
const SizeofOvsFlowStats = 16
const ( // ovs_key_attr
OVS_KEY_ATTR_UNSPEC = 0
OVS_KEY_ATTR_ENCAP = 1
OVS_KEY_ATTR_PRIORITY = 2
OVS_KEY_ATTR_IN_PORT = 3
OVS_KEY_ATTR_ETHERNET = 4
OVS_KEY_ATTR_VLAN = 5
OVS_KEY_ATTR_ETHERTYPE = 6
OVS_KEY_ATTR_IPV4 = 7
OVS_KEY_ATTR_IPV6 = 8
OVS_KEY_ATTR_TCP = 9
OVS_KEY_ATTR_UDP = 10
OVS_KEY_ATTR_ICMP = 11
OVS_KEY_ATTR_ICMPV6 = 12
OVS_KEY_ATTR_ARP = 13
OVS_KEY_ATTR_ND = 14
OVS_KEY_ATTR_SKB_MARK = 15
OVS_KEY_ATTR_TUNNEL = 16
OVS_KEY_ATTR_SCTP = 17
OVS_KEY_ATTR_TCP_FLAGS = 18
OVS_KEY_ATTR_DP_HASH = 19
OVS_KEY_ATTR_RECIRC_ID = 20
)
const ( // ovs_tunnel_key_attr
OVS_TUNNEL_KEY_ATTR_ID = 0
OVS_TUNNEL_KEY_ATTR_IPV4_SRC = 1
OVS_TUNNEL_KEY_ATTR_IPV4_DST = 2
OVS_TUNNEL_KEY_ATTR_TOS = 3
OVS_TUNNEL_KEY_ATTR_TTL = 4
OVS_TUNNEL_KEY_ATTR_DONT_FRAGMENT = 5
OVS_TUNNEL_KEY_ATTR_CSUM = 6
OVS_TUNNEL_KEY_ATTR_OAM = 7
OVS_TUNNEL_KEY_ATTR_GENEVE_OPTS = 8
OVS_TUNNEL_KEY_ATTR_TP_SRC = 9
OVS_TUNNEL_KEY_ATTR_TP_DST = 10
)
const ETH_ALEN = 6
type OvsKeyEthernet struct {
EthSrc [ETH_ALEN]byte
EthDst [ETH_ALEN]byte
}
const SizeofOvsKeyEthernet = 12
const ( // ovs_action_attr
OVS_ACTION_ATTR_UNSPEC = 0
OVS_ACTION_ATTR_OUTPUT = 1
OVS_ACTION_ATTR_USERSPACE = 2
OVS_ACTION_ATTR_SET = 3
OVS_ACTION_ATTR_PUSH_VLAN = 4
OVS_ACTION_ATTR_POP_VLAN = 5
OVS_ACTION_ATTR_SAMPLE = 6
)
const ( // ovs_packet_cmd
OVS_PACKET_CMD_UNSPEC = 0
OVS_PACKET_CMD_MISS = 1
OVS_PACKET_CMD_ACTION = 2
OVS_PACKET_CMD_EXECUTE = 3
)
const ( // ovs_packet_attr
OVS_PACKET_ATTR_UNSPEC = 0
OVS_PACKET_ATTR_PACKET = 1
OVS_PACKET_ATTR_KEY = 2
OVS_PACKET_ATTR_ACTIONS = 3
OVS_PACKET_ATTR_USERDATA = 4
)
type ifreqIfindex struct {
name [syscall.IFNAMSIZ]byte
ifindex int32
}
+82
View File
@@ -0,0 +1,82 @@
package odp
import (
"syscall"
"unsafe"
)
const ALIGN_BUFFERS = 8
// Normal slice or array allocations in golang do not appear to be
// guaranteed to be aligned (though in practice they are). Unaligned
// access are slow on some architectures and blow up on others. So
// this allocates a slice aligned to ALIGN_BUFFERS.
func MakeAlignedByteSliceCap(len int, cap int) []byte {
b := make([]byte, cap+ALIGN_BUFFERS-1)
off := int(uintptr(unsafe.Pointer(&b[0])) & (ALIGN_BUFFERS - 1))
if off == 0 {
// Already aligned
return b[:len]
} else {
// Need to offset the slice to make it aligned
off = ALIGN_BUFFERS - off
return b[off : len+off]
}
}
func MakeAlignedByteSlice(len int) []byte {
return MakeAlignedByteSliceCap(len, len)
}
func uint16At(data []byte, pos int) *uint16 {
return (*uint16)(unsafe.Pointer(&data[pos]))
}
func uint32At(data []byte, pos int) *uint32 {
return (*uint32)(unsafe.Pointer(&data[pos]))
}
func int32At(data []byte, pos int) *int32 {
return (*int32)(unsafe.Pointer(&data[pos]))
}
func uint64At(data []byte, pos int) *uint64 {
return (*uint64)(unsafe.Pointer(&data[pos]))
}
func nlMsghdrAt(data []byte, pos int) *syscall.NlMsghdr {
return (*syscall.NlMsghdr)(unsafe.Pointer(&data[pos]))
}
func nlAttrAt(data []byte, pos int) *syscall.NlAttr {
return (*syscall.NlAttr)(unsafe.Pointer(&data[pos]))
}
func nlMsgerrAt(data []byte, pos int) *syscall.NlMsgerr {
return (*syscall.NlMsgerr)(unsafe.Pointer(&data[pos]))
}
func genlMsghdrAt(data []byte, pos int) *GenlMsghdr {
return (*GenlMsghdr)(unsafe.Pointer(&data[pos]))
}
func ovsHeaderAt(data []byte, pos int) *OvsHeader {
return (*OvsHeader)(unsafe.Pointer(&data[pos]))
}
func ovsKeyEthernetAt(data []byte, pos int) *OvsKeyEthernet {
return (*OvsKeyEthernet)(unsafe.Pointer(&data[pos]))
}
func ovsFlowStatsAt(data []byte, pos int) *OvsFlowStats {
return (*OvsFlowStats)(unsafe.Pointer(&data[pos]))
}
func uint16FromBE(n uint16) uint16 {
a := (*[2]byte)(unsafe.Pointer(&n))
return uint16(a[0])<<8 + uint16(a[1])
}
func uint16ToBE(n uint16) uint16 {
return uint16FromBE(n)
}
+362
View File
@@ -0,0 +1,362 @@
package odp
import (
"fmt"
"syscall"
)
type VportSpec interface {
TypeName() string
Name() string
typeId() uint32
optionNlAttrs(req *NlMsgBuilder)
}
type VportSpecBase struct {
name string
}
func (v VportSpecBase) Name() string {
return v.name
}
type SimpleVportSpec struct {
VportSpecBase
typ uint32
typeName string
}
func (s SimpleVportSpec) TypeName() string {
return s.typeName
}
func (s SimpleVportSpec) typeId() uint32 {
return s.typ
}
func (SimpleVportSpec) optionNlAttrs(req *NlMsgBuilder) {
}
func NewNetdevVportSpec(name string) VportSpec {
return SimpleVportSpec{
VportSpecBase{name},
OVS_VPORT_TYPE_NETDEV,
"netdev",
}
}
func NewInternalVportSpec(name string) VportSpec {
return SimpleVportSpec{
VportSpecBase{name},
OVS_VPORT_TYPE_INTERNAL,
"internal",
}
}
type VxlanVportSpec struct {
VportSpecBase
Port uint16
}
func (VxlanVportSpec) TypeName() string {
return "vxlan"
}
func (VxlanVportSpec) typeId() uint32 {
return OVS_VPORT_TYPE_VXLAN
}
func (v VxlanVportSpec) optionNlAttrs(req *NlMsgBuilder) {
req.PutUint16Attr(OVS_TUNNEL_ATTR_DST_PORT, v.Port)
}
func NewVxlanVportSpec(name string, port uint16) VportSpec {
return VxlanVportSpec{VportSpecBase{name}, port}
}
func parseVxlanVportSpec(name string, opts Attrs) (VportSpec, error) {
port, err := opts.GetUint16(OVS_TUNNEL_ATTR_DST_PORT)
if err != nil {
return nil, err
}
return VxlanVportSpec{VportSpecBase{name}, port}, nil
}
// Vport numbers are scoped to a particular datapath
type VportID uint32
func parseVport(msg *NlMsgParser) (id VportID, s VportSpec, err error) {
attrs, err := msg.TakeAttrs()
if err != nil {
return
}
rawid, err := attrs.GetUint32(OVS_VPORT_ATTR_PORT_NO)
if err != nil {
return
}
id = VportID(rawid)
typ, err := attrs.GetUint32(OVS_VPORT_ATTR_TYPE)
if err != nil {
return
}
name, err := attrs.GetString(OVS_VPORT_ATTR_NAME)
if err != nil {
return
}
opts, err := attrs.GetNestedAttrs(OVS_VPORT_ATTR_OPTIONS, true)
if err != nil {
return
}
if opts == nil {
opts = make(Attrs)
}
switch typ {
case OVS_VPORT_TYPE_NETDEV:
s = NewNetdevVportSpec(name)
break
case OVS_VPORT_TYPE_INTERNAL:
s = NewInternalVportSpec(name)
break
case OVS_VPORT_TYPE_VXLAN:
s, err = parseVxlanVportSpec(name, opts)
break
default:
err = fmt.Errorf("unsupported vport type %d", typ)
}
return
}
func (dp DatapathHandle) CreateVport(spec VportSpec) (VportID, error) {
dpif := dp.dpif
req := NewNlMsgBuilder(RequestFlags, dpif.families[VPORT].id)
req.PutGenlMsghdr(OVS_VPORT_CMD_NEW, OVS_VPORT_VERSION)
req.putOvsHeader(dp.ifindex)
req.PutStringAttr(OVS_VPORT_ATTR_NAME, spec.Name())
req.PutUint32Attr(OVS_VPORT_ATTR_TYPE, spec.typeId())
req.PutNestedAttrs(OVS_VPORT_ATTR_OPTIONS, func() {
spec.optionNlAttrs(req)
})
req.PutUint32Attr(OVS_VPORT_ATTR_UPCALL_PID, 0)
resp, err := dpif.sock.Request(req)
if err != nil {
return 0, err
}
_, _, err = dpif.checkNlMsgHeaders(resp, VPORT, OVS_VPORT_CMD_NEW)
if err != nil {
return 0, err
}
id, _, err := parseVport(resp)
if err != nil {
return 0, err
}
return id, nil
}
func IsNoSuchVportError(err error) bool {
return err == NetlinkError(syscall.ENODEV)
}
type Vport struct {
ID VportID
Spec VportSpec
}
func lookupVport(dpif *Dpif, dpifindex DatapathID, name string) (DatapathID, Vport, error) {
req := NewNlMsgBuilder(RequestFlags, dpif.families[VPORT].id)
req.PutGenlMsghdr(OVS_VPORT_CMD_GET, OVS_VPORT_VERSION)
req.putOvsHeader(dpifindex)
req.PutStringAttr(OVS_VPORT_ATTR_NAME, name)
resp, err := dpif.sock.Request(req)
if err != nil {
return 0, Vport{}, err
}
_, ovshdr, err := dpif.checkNlMsgHeaders(resp, VPORT, OVS_VPORT_CMD_NEW)
if err != nil {
return 0, Vport{}, err
}
id, s, err := parseVport(resp)
if err != nil {
return 0, Vport{}, err
}
return ovshdr.datapathID(), Vport{id, s}, nil
}
func (dpif *Dpif) LookupVportByName(name string) (DatapathHandle, Vport, error) {
dpifindex, vport, err := lookupVport(dpif, 0, name)
return DatapathHandle{dpif: dpif, ifindex: dpifindex}, vport, err
}
func (dp DatapathHandle) LookupVportByName(name string) (Vport, error) {
_, vport, err := lookupVport(dp.dpif, dp.ifindex, name)
return vport, err
}
func (dp DatapathHandle) LookupVport(id VportID) (Vport, error) {
req := NewNlMsgBuilder(RequestFlags, dp.dpif.families[VPORT].id)
req.PutGenlMsghdr(OVS_VPORT_CMD_GET, OVS_VPORT_VERSION)
req.putOvsHeader(dp.ifindex)
req.PutUint32Attr(OVS_VPORT_ATTR_PORT_NO, uint32(id))
resp, err := dp.dpif.sock.Request(req)
if err != nil {
return Vport{}, err
}
err = dp.checkNlMsgHeaders(resp, VPORT, OVS_VPORT_CMD_NEW)
if err != nil {
return Vport{}, err
}
id, s, err := parseVport(resp)
if err != nil {
return Vport{}, err
}
return Vport{id, s}, nil
}
func (dp DatapathHandle) LookupVportName(id VportID) (string, error) {
vport, err := dp.LookupVport(id)
if err != nil {
if !IsNoSuchVportError(err) {
return "", err
}
// No vport with the given port number, so just
// show the number
return fmt.Sprintf("%d:%d", dp.ifindex, id), nil
}
return vport.Spec.Name(), nil
}
func (dp DatapathHandle) EnumerateVports() ([]Vport, error) {
req := NewNlMsgBuilder(DumpFlags, dp.dpif.families[VPORT].id)
req.PutGenlMsghdr(OVS_VPORT_CMD_GET, OVS_VPORT_VERSION)
req.putOvsHeader(dp.ifindex)
var res []Vport
consumer := func(resp *NlMsgParser) error {
err := dp.checkNlMsgHeaders(resp, VPORT, OVS_VPORT_CMD_NEW)
if err != nil {
return err
}
id, spec, err := parseVport(resp)
if err != nil {
return err
}
res = append(res, Vport{id, spec})
return nil
}
err := dp.dpif.sock.RequestMulti(req, consumer)
if err != nil {
return nil, err
}
return res, nil
}
func (dp DatapathHandle) DeleteVport(id VportID) error {
req := NewNlMsgBuilder(RequestFlags, dp.dpif.families[VPORT].id)
req.PutGenlMsghdr(OVS_VPORT_CMD_DEL, OVS_VPORT_VERSION)
req.putOvsHeader(dp.ifindex)
req.PutUint32Attr(OVS_VPORT_ATTR_PORT_NO, uint32(id))
_, err := dp.dpif.sock.Request(req)
return err
}
func (dp DatapathHandle) setVportUpcallPortId(id VportID, pid uint32) error {
req := NewNlMsgBuilder(RequestFlags, dp.dpif.families[VPORT].id)
req.PutGenlMsghdr(OVS_VPORT_CMD_SET, OVS_VPORT_VERSION)
req.putOvsHeader(dp.ifindex)
req.PutUint32Attr(OVS_VPORT_ATTR_PORT_NO, uint32(id))
req.PutUint32Attr(OVS_VPORT_ATTR_UPCALL_PID, pid)
_, err := dp.dpif.sock.Request(req)
return err
}
type VportEventsConsumer interface {
VportCreated(dpid DatapathID, vport Vport) error
VportDeleted(dpid DatapathID, vport Vport) error
Error(err error, stopped bool)
}
func (dpif *Dpif) ConsumeVportEvents(consumer VportEventsConsumer) (Cancelable, error) {
return DatapathHandle{dpif, -1}.ConsumeVportEvents(consumer)
}
func (dp DatapathHandle) ConsumeVportEvents(consumer VportEventsConsumer) (Cancelable, error) {
mcGroup, err := dp.dpif.getMCGroup(VPORT, "ovs_vport")
if err != nil {
return nil, err
}
consumeDpif, err := dp.dpif.Reopen()
if err != nil {
return nil, err
}
err = syscall.SetsockoptInt(consumeDpif.sock.fd, SOL_NETLINK, syscall.NETLINK_ADD_MEMBERSHIP, int(mcGroup))
if err != nil {
consumeDpif.Close()
return nil, err
}
go consumeDpif.consumeVportEvents(consumer, dp.ifindex)
return cancelableDpif{consumeDpif}, nil
}
func (dpif *Dpif) consumeVportEvents(consumer VportEventsConsumer, ifindex DatapathID) {
dpif.sock.consume(consumer, func(msg *NlMsgParser) error {
genlhdr, ovshdr, err := dpif.checkNlMsgHeaders(msg, VPORT, -1)
if err != nil {
return err
}
// filter by ifindex, if consuming on a specific datapath
if ifindex >= 0 && ovshdr.datapathID() != ifindex {
return nil
}
id, spec, err := parseVport(msg)
if err != nil {
return err
}
switch genlhdr.Cmd {
case OVS_VPORT_CMD_NEW:
return consumer.VportCreated(ovshdr.datapathID(), Vport{id, spec})
case OVS_VPORT_CMD_DEL:
return consumer.VportDeleted(ovshdr.datapathID(), Vport{id, spec})
default:
return nil
}
})
}
+109
View File
@@ -0,0 +1,109 @@
package docker
import (
"errors"
"github.com/fsouza/go-dockerclient"
. "github.com/weaveworks/weave/common"
)
// An observer for container events
type ContainerObserver interface {
ContainerStarted(ident string)
ContainerDied(ident string)
}
type Client struct {
*docker.Client
}
// NewClient creates a new Docker client and checks we can talk to Docker
func NewClient(apiPath string) (*Client, error) {
dc, err := docker.NewClient(apiPath)
if err != nil {
return nil, err
}
client := &Client{dc}
return client, client.checkWorking(apiPath)
}
func NewVersionedClient(apiPath string, apiVersionString string) (*Client, error) {
dc, err := docker.NewVersionedClient(apiPath, apiVersionString)
if err != nil {
return nil, err
}
client := &Client{dc}
return client, client.checkWorking(apiPath)
}
func (c *Client) checkWorking(apiPath string) error {
env, err := c.Version()
if err != nil {
return err
}
Log.Infof("[docker] Using Docker API on %s: %v", apiPath, env)
return nil
}
// AddObserver adds an observer for docker events
func (c *Client) AddObserver(ob ContainerObserver) error {
events := make(chan *docker.APIEvents)
if err := c.AddEventListener(events); err != nil {
Log.Errorf("[docker] Unable to add listener to Docker API: %s", err)
return err
}
go func() {
for event := range events {
switch event.Status {
case "start":
id := event.ID
ob.ContainerStarted(id)
case "die":
id := event.ID
ob.ContainerDied(id)
}
}
}()
return nil
}
// IsContainerNotRunning returns true if we have checked with Docker that the ID is not running
func (c *Client) IsContainerNotRunning(idStr string) bool {
container, err := c.InspectContainer(idStr)
if err == nil {
return !container.State.Running
}
if _, notThere := err.(*docker.NoSuchContainer); notThere {
return true
}
Log.Errorf("[docker] Could not check container status: %s", err)
return false
}
// This is intended to find an IP address that we can reach the container on;
// if it is on the Docker bridge network then that address; if on the host network
// then localhost
func (c *Client) GetContainerIP(nameOrID string) (string, error) {
Log.Debugf("Getting IP for container %s", nameOrID)
info, err := c.InspectContainer(nameOrID)
if err != nil {
return "", err
}
if info.NetworkSettings.Networks != nil {
Log.Debugln("Networks: ", info.NetworkSettings.Networks)
if bridgeNetwork, ok := info.NetworkSettings.Networks["bridge"]; ok {
return bridgeNetwork.IPAddress, nil
} else if _, ok := info.NetworkSettings.Networks["host"]; ok {
return "127.0.0.1", nil
}
} else if info.HostConfig.NetworkMode == "host" {
return "127.0.0.1", nil
}
if info.NetworkSettings.IPAddress == "" {
return "", errors.New("No IP address found for container " + nameOrID)
}
return info.NetworkSettings.IPAddress, nil
}
+66
View File
@@ -0,0 +1,66 @@
package common
import (
"bytes"
"fmt"
"strings"
"github.com/Sirupsen/logrus"
)
type textFormatter struct {
}
// Based off logrus.TextFormatter, which behaves completely
// differently when you don't want colored output
func (f *textFormatter) Format(entry *logrus.Entry) ([]byte, error) {
b := &bytes.Buffer{}
levelText := strings.ToUpper(entry.Level.String())[0:4]
timeStamp := entry.Time.Format("2006/01/02 15:04:05.000000")
if len(entry.Data) > 0 {
fmt.Fprintf(b, "%s: %s %-44s ", levelText, timeStamp, entry.Message)
for k, v := range entry.Data {
fmt.Fprintf(b, " %s=%v", k, v)
}
} else {
// No padding when there's no fields
fmt.Fprintf(b, "%s: %s %s", levelText, timeStamp, entry.Message)
}
b.WriteByte('\n')
return b.Bytes(), nil
}
var (
standardTextFormatter = &textFormatter{}
)
var (
Log *logrus.Logger
)
func init() {
Log = logrus.New()
Log.Formatter = standardTextFormatter
}
func SetLogLevel(levelname string) {
level, err := logrus.ParseLevel(levelname)
if err != nil {
Log.Fatal(err)
}
Log.Level = level
}
func CheckFatal(e error) {
if e != nil {
Log.Fatal(e)
}
}
func CheckWarn(e error) {
if e != nil {
Log.Warnln(e)
}
}
+31
View File
@@ -0,0 +1,31 @@
package mflagext
import (
"fmt"
"github.com/docker/docker/pkg/mflag"
)
type listOpts struct {
value *[]string
hasBeenSet bool
}
func ListVar(p *[]string, names []string, value []string, usage string) {
*p = value
mflag.Var(&listOpts{p, false}, names, usage)
}
func (opts *listOpts) Set(value string) error {
if opts.hasBeenSet {
(*opts.value) = append((*opts.value), value)
} else {
(*opts.value) = []string{value}
opts.hasBeenSet = true
}
return nil
}
func (opts *listOpts) String() string {
return fmt.Sprintf("%v", []string(*opts.value))
}
+93
View File
@@ -0,0 +1,93 @@
package odp
import (
"fmt"
"net"
"syscall"
"github.com/weaveworks/go-odp/odp"
)
// ODP admin functionality
func CreateDatapath(dpname string) (err error, supported bool) {
dpif, err := odp.NewDpif()
if err != nil {
if odp.IsKernelLacksODPError(err) {
return nil, false
}
return err, true
}
defer dpif.Close()
dp, err := dpif.CreateDatapath(dpname)
if err != nil && !odp.IsDatapathNameAlreadyExistsError(err) {
return err, true
}
// Pick an ephemeral port number to use in probing for vxlan
// support.
udpconn, err := net.ListenUDP("udp4", nil)
if err != nil {
return err, true
}
// we leave the UDP socket open, so creating a vxlan vport on
// the same port number should fail. But that's fine: It's
// still sufficient to probe for support.
portno := uint16(udpconn.LocalAddr().(*net.UDPAddr).Port)
vpid, err := dp.CreateVport(odp.NewVxlanVportSpec(
fmt.Sprintf("vxlan-%d", portno), portno))
if nlerr, ok := err.(odp.NetlinkError); ok {
if syscall.Errno(nlerr) == syscall.EAFNOSUPPORT {
dp.Delete()
return fmt.Errorf("kernel does not have Open vSwitch VXLAN support"), false
}
}
if err == nil {
dp.DeleteVport(vpid)
}
udpconn.Close()
return nil, true
}
func DeleteDatapath(dpname string) error {
dpif, err := odp.NewDpif()
if err != nil {
return err
}
defer dpif.Close()
dp, err := dpif.LookupDatapath(dpname)
if err != nil {
if odp.IsNoSuchDatapathError(err) {
return nil
}
return err
}
return dp.Delete()
}
func AddDatapathInterface(dpname string, ifname string) error {
dpif, err := odp.NewDpif()
if err != nil {
return err
}
defer dpif.Close()
dp, err := dpif.LookupDatapath(dpname)
if err != nil {
return err
}
_, err = dp.CreateVport(odp.NewNetdevVportSpec(ifname))
return err
}
+32
View File
@@ -0,0 +1,32 @@
package common
import (
"os"
"os/signal"
"runtime"
"syscall"
)
// A subsystem/server/... that can be stopped or queried about the status with a signal
type SignalReceiver interface {
Stop() error
}
func SignalHandlerLoop(ss ...SignalReceiver) {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM)
buf := make([]byte, 1<<20)
for {
switch <-sigs {
case syscall.SIGINT, syscall.SIGTERM:
Log.Infof("=== received SIGINT/SIGTERM ===\n*** exiting")
for _, subsystem := range ss {
subsystem.Stop()
}
return
case syscall.SIGQUIT:
stacklen := runtime.Stack(buf, true)
Log.Infof("=== received SIGQUIT ===\n*** goroutine dump...\n%s\n*** end", buf[:stacklen])
}
}
}
+20
View File
@@ -0,0 +1,20 @@
package common
import (
"strings"
)
// Assert test is true, panic otherwise
func Assert(test bool) {
if !test {
panic("Assertion failure")
}
}
func ErrorMessages(errors []error) string {
var result []string
for _, err := range errors {
result = append(result, err.Error())
}
return strings.Join(result, "\n")
}
+41
View File
@@ -27,6 +27,12 @@
"branch": "master",
"path": "/handlers"
},
{
"importpath": "github.com/Sirupsen/logrus",
"repository": "https://github.com/Sirupsen/logrus",
"revision": "cdaedc68f2894175ac2b3221869685602c759e71",
"branch": "master"
},
{
"importpath": "github.com/armon/go-metrics",
"repository": "https://github.com/armon/go-metrics",
@@ -430,6 +436,20 @@
"branch": "master",
"path": "/spew"
},
{
"importpath": "github.com/docker/docker/pkg/homedir",
"repository": "https://github.com/docker/docker",
"revision": "7c1c96551d41e369a588e365a9bb99acb5bc8fdb",
"branch": "master",
"path": "/pkg/homedir"
},
{
"importpath": "github.com/docker/docker/pkg/mflag",
"repository": "https://github.com/docker/docker",
"revision": "7c1c96551d41e369a588e365a9bb99acb5bc8fdb",
"branch": "master",
"path": "/pkg/mflag"
},
{
"importpath": "github.com/docker/docker/pkg/mount",
"repository": "https://github.com/docker/docker",
@@ -604,6 +624,13 @@
"revision": "179d4d0c4d8d407a32af483c2354df1d2c91e6c3",
"branch": "master"
},
{
"importpath": "github.com/opencontainers/runc/libcontainer/user",
"repository": "https://github.com/opencontainers/runc",
"revision": "3317785f562b363eb386a2fa4909a55f267088c8",
"branch": "master",
"path": "/libcontainer/user"
},
{
"importpath": "github.com/pborman/uuid",
"repository": "https://github.com/pborman/uuid",
@@ -682,12 +709,26 @@
"branch": "master",
"path": "/codec"
},
{
"importpath": "github.com/weaveworks/go-odp/odp",
"repository": "https://github.com/weaveworks/go-odp",
"revision": "f8c8c40c18898d7c4f6be33978d68f5d2810f373",
"branch": "master",
"path": "/odp"
},
{
"importpath": "github.com/weaveworks/procspy",
"repository": "https://github.com/weaveworks/procspy",
"revision": "cb970aa190c374d1e47711dbffb3c2c6e9ef0dd1",
"branch": "master"
},
{
"importpath": "github.com/weaveworks/weave/common",
"repository": "https://github.com/weaveworks/weave",
"revision": "29f3d711c65121f436a9d191af4633ba4600d0fd",
"branch": "master",
"path": "/common"
},
{
"importpath": "golang.org/x/crypto/curve25519",
"repository": "https://go.googlesource.com/crypto",