mirror of
https://github.com/weaveworks/scope.git
synced 2026-08-23 22:36:24 +00:00
Vendor in github.com/weaveworks/weave/common
This commit is contained in:
+55
@@ -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
@@ -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
@@ -0,0 +1,365 @@
|
||||
# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/> [](https://travis-ci.org/Sirupsen/logrus) [][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):
|
||||
|
||||

|
||||
|
||||
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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,9 @@
|
||||
// +build darwin freebsd openbsd netbsd dragonfly
|
||||
|
||||
package logrus
|
||||
|
||||
import "syscall"
|
||||
|
||||
const ioctlReadTermios = syscall.TIOCGETA
|
||||
|
||||
type Termios syscall.Termios
|
||||
+12
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+516
@@ -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
@@ -0,0 +1,2 @@
|
||||
Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
||||
Aleksa Sarai <cyphar@cyphar.com> (@cyphar)
|
||||
+108
@@ -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
@@ -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)
|
||||
}
|
||||
+21
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+101
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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")
|
||||
}
|
||||
Vendored
+41
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user