Merge branch 'master' into dev-2.0

This commit is contained in:
Fabian Reinartz 2017-06-23 13:15:44 +02:00
commit ca2b68889b
20 changed files with 483 additions and 278 deletions

View file

@ -1,3 +1,22 @@
## 1.7.0 / 2017-06-06
* [CHANGE] Compress remote storage requests and responses with unframed/raw snappy.
* [CHANGE] Properly ellide secrets in config.
* [FEATURE] Add OpenStack service discovery.
* [FEATURE] Add ability to limit Kubernetes service discovery to certain namespaces.
* [FEATURE] Add metric for discovered number of Alertmanagers.
* [ENHANCEMENT] Print system information (uname) on start up.
* [ENHANCEMENT] Show gaps in graphs on expression browser.
* [ENHANCEMENT] Promtool linter checks counter naming and more reserved labels.
* [BUGFIX] Fix broken Mesos discovery.
* [BUGFIX] Fix redirect when external URL is set.
* [BUGFIX] Fix mutation of active alert elements by notifier.
* [BUGFIX] Fix HTTP error handling for remote write.
* [BUGFIX] Fix builds for Solaris/Illumos.
* [BUGFIX] Fix overflow checking in global config.
* [BUGFIX] Fix log level reporting issue.
* [BUGFIX] Fix ZooKeeper serverset discovery can become out-of-sync.
## 1.6.3 / 2017-05-18
* [BUGFIX] Fix disappearing Alertmanger targets in Alertmanager discovery.

View file

@ -23,6 +23,8 @@ import (
"strings"
"time"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/prometheus/common/model"
"gopkg.in/yaml.v2"
)
@ -1134,8 +1136,17 @@ func (c *EC2SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
return err
}
if c.Region == "" {
sess, err := session.NewSession()
if err != nil {
return err
}
metadata := ec2metadata.New(sess)
region, err := metadata.Region()
if err != nil {
return fmt.Errorf("EC2 SD configuration requires a region")
}
c.Region = region
}
return nil
}

View file

@ -18,6 +18,7 @@ import (
"regexp"
"sort"
"strconv"
"strings"
"time"
"github.com/prometheus/common/model"
@ -29,7 +30,7 @@ import (
type Function struct {
Name string
ArgTypes []ValueType
OptionalArgs int
Variadic int
ReturnType ValueType
Call func(ev *evaluator, args Expressions) Value
}
@ -876,6 +877,56 @@ func funcVector(ev *evaluator, args Expressions) Value {
}
}
// === label_join(vector model.ValVector, dest_labelname, separator, src_labelname...) Vector ===
func funcLabelJoin(ev *evaluator, args Expressions) Value {
var (
vector = ev.evalVector(args[0])
dst = ev.evalString(args[1]).V
sep = ev.evalString(args[2]).V
srcLabels = make([]string, len(args)-3)
)
for i := 3; i < len(args); i++ {
src := ev.evalString(args[i]).V
if !model.LabelName(src).IsValid() {
ev.errorf("invalid source label name in label_join(): %s", src)
}
srcLabels[i-3] = src
}
if !model.LabelName(dst).IsValid() {
ev.errorf("invalid destination label name in label_join(): %s", dst)
}
outSet := make(map[uint64]struct{}, len(vector))
for i := range vector {
el := &vector[i]
srcVals := make([]string, len(srcLabels))
for i, src := range srcLabels {
srcVals[i] = el.Metric.Get(src)
}
lb := labels.NewBuilder(el.Metric)
strval := strings.Join(srcVals, sep)
if strval == "" {
lb.Del(dst)
} else {
lb.Set(dst, strval)
}
el.Metric = lb.Labels()
h := el.Metric.Hash()
if _, exists := outSet[h]; exists {
ev.errorf("duplicated label set in output of label_join(): %s", el.Metric)
} else {
outSet[h] = struct{}{}
}
}
return vector
}
// Common code for date related functions.
func dateWrapper(ev *evaluator, args Expressions, f func(time.Time) float64) Value {
var v Vector
@ -1006,21 +1057,21 @@ var functions = map[string]*Function{
"days_in_month": {
Name: "days_in_month",
ArgTypes: []ValueType{ValueTypeVector},
OptionalArgs: 1,
Variadic: 1,
ReturnType: ValueTypeVector,
Call: funcDaysInMonth,
},
"day_of_month": {
Name: "day_of_month",
ArgTypes: []ValueType{ValueTypeVector},
OptionalArgs: 1,
Variadic: 1,
ReturnType: ValueTypeVector,
Call: funcDayOfMonth,
},
"day_of_week": {
Name: "day_of_week",
ArgTypes: []ValueType{ValueTypeVector},
OptionalArgs: 1,
Variadic: 1,
ReturnType: ValueTypeVector,
Call: funcDayOfWeek,
},
@ -1069,7 +1120,7 @@ var functions = map[string]*Function{
"hour": {
Name: "hour",
ArgTypes: []ValueType{ValueTypeVector},
OptionalArgs: 1,
Variadic: 1,
ReturnType: ValueTypeVector,
Call: funcHour,
},
@ -1097,6 +1148,13 @@ var functions = map[string]*Function{
ReturnType: ValueTypeVector,
Call: funcLabelReplace,
},
"label_join": {
Name: "label_join",
ArgTypes: []ValueType{ValueTypeVector, ValueTypeString, ValueTypeString, ValueTypeString},
Variadic: -1,
ReturnType: ValueTypeVector,
Call: funcLabelJoin,
},
"ln": {
Name: "ln",
ArgTypes: []ValueType{ValueTypeVector},
@ -1130,14 +1188,14 @@ var functions = map[string]*Function{
"minute": {
Name: "minute",
ArgTypes: []ValueType{ValueTypeVector},
OptionalArgs: 1,
Variadic: 1,
ReturnType: ValueTypeVector,
Call: funcMinute,
},
"month": {
Name: "month",
ArgTypes: []ValueType{ValueTypeVector},
OptionalArgs: 1,
Variadic: 1,
ReturnType: ValueTypeVector,
Call: funcMonth,
},
@ -1168,7 +1226,7 @@ var functions = map[string]*Function{
"round": {
Name: "round",
ArgTypes: []ValueType{ValueTypeVector, ValueTypeScalar},
OptionalArgs: 1,
Variadic: 1,
ReturnType: ValueTypeVector,
Call: funcRound,
},
@ -1235,7 +1293,7 @@ var functions = map[string]*Function{
"year": {
Name: "year",
ArgTypes: []ValueType{ValueTypeVector},
OptionalArgs: 1,
Variadic: 1,
ReturnType: ValueTypeVector,
Call: funcYear,
},

View file

@ -713,6 +713,8 @@ Loop:
switch l.next() {
case '\\':
lexEscape(l)
case utf8.RuneError:
return l.errorf("invalid UTF-8 rune")
case eof, '\n':
return l.errorf("unterminated quoted string")
case l.stringOpen:
@ -728,6 +730,8 @@ func lexRawString(l *lexer) stateFn {
Loop:
for {
switch l.next() {
case utf8.RuneError:
return l.errorf("invalid UTF-8 rune")
case eof:
return l.errorf("unterminated raw string")
case l.stringOpen:

View file

@ -396,6 +396,13 @@ var tests = []struct {
}, {
input: `]`, fail: true,
},
// Test encoding issues.
{
input: "\"\xff\"", fail: true,
},
{
input: "`\xff`", fail: true,
},
// Test series description.
{
input: `{} _ 1 x .3`,

View file

@ -1094,13 +1094,23 @@ func (p *parser) checkType(node Node) (typ ValueType) {
case *Call:
nargs := len(n.Func.ArgTypes)
if na := nargs - n.Func.OptionalArgs; na > len(n.Args) {
if n.Func.Variadic == 0 {
if nargs != len(n.Args) {
p.errorf("expected %d argument(s) in call to %q, got %d", nargs, n.Func.Name, len(n.Args))
}
} else {
na := nargs - 1
if na > len(n.Args) {
p.errorf("expected at least %d argument(s) in call to %q, got %d", na, n.Func.Name, len(n.Args))
} else if nargsmax := na + n.Func.Variadic; n.Func.Variadic > 0 && nargsmax < len(n.Args) {
p.errorf("expected at most %d argument(s) in call to %q, got %d", nargsmax, n.Func.Name, len(n.Args))
}
if nargs < len(n.Args) {
p.errorf("expected at most %d argument(s) in call to %q, got %d", nargs, n.Func.Name, len(n.Args))
}
for i, arg := range n.Args {
if i >= len(n.Func.ArgTypes) {
i = len(n.Func.ArgTypes) - 1
}
p.expectType(arg, n.Func.ArgTypes[i], fmt.Sprintf("call to function %q", n.Func.Name))
}

View file

@ -904,6 +904,10 @@ var testExpr = []struct {
// TODO(fabxc): willingly lexing wrong tokens allows for more precrise error
// messages from the parser - consider if this is an option.
errMsg: "unexpected character inside braces: '>'",
}, {
input: "some_metric{a=\"\xff\"}",
fail: true,
errMsg: "parse error at char 15: invalid UTF-8 rune",
}, {
input: `foo{gibberish}`,
fail: true,
@ -1356,11 +1360,11 @@ var testExpr = []struct {
}, {
input: "floor()",
fail: true,
errMsg: "expected at least 1 argument(s) in call to \"floor\", got 0",
errMsg: "expected 1 argument(s) in call to \"floor\", got 0",
}, {
input: "floor(some_metric, other_metric)",
fail: true,
errMsg: "expected at most 1 argument(s) in call to \"floor\", got 2",
errMsg: "expected 1 argument(s) in call to \"floor\", got 2",
}, {
input: "floor(1)",
fail: true,
@ -1373,6 +1377,10 @@ var testExpr = []struct {
input: "rate(some_metric)",
fail: true,
errMsg: "expected type range vector in call to function \"rate\", got instant vector",
}, {
input: "label_replace(a, `b`, `c\xff`, `d`, `.*`)",
fail: true,
errMsg: "parse error at char 23: invalid UTF-8 rune",
},
// Fuzzing regression tests.
{

View file

@ -236,6 +236,44 @@ eval instant at 5s timestamp(metric)
eval instant at 10s timestamp(metric)
{} 10
# Tests for label_join.
load 5m
testmetric{src="a",src1="b",src2="c",dst="original-destination-value"} 0
testmetric{src="d",src1="e",src2="f",dst="original-destination-value"} 1
# label_join joins all src values in order.
eval instant at 0m label_join(testmetric, "dst", "-", "src", "src1", "src2")
testmetric{src="a",src1="b",src2="c",dst="a-b-c"} 0
testmetric{src="d",src1="e",src2="f",dst="d-e-f"} 1
# label_join treats non existent src labels as empty strings.
eval instant at 0m label_join(testmetric, "dst", "-", "src", "src3", "src1")
testmetric{src="a",src1="b",src2="c",dst="a--b"} 0
testmetric{src="d",src1="e",src2="f",dst="d--e"} 1
# label_join overwrites the destination label even if the resulting dst label is empty string
eval instant at 0m label_join(testmetric, "dst", "", "emptysrc", "emptysrc1", "emptysrc2")
testmetric{src="a",src1="b",src2="c"} 0
testmetric{src="d",src1="e",src2="f"} 1
# test without src label for label_join
eval instant at 0m label_join(testmetric, "dst", ", ")
testmetric{src="a",src1="b",src2="c"} 0
testmetric{src="d",src1="e",src2="f"} 1
# test without dst label for label_join
load 5m
testmetric1{src="foo",src1="bar",src2="foobar"} 0
testmetric1{src="fizz",src1="buzz",src2="fizzbuzz"} 1
# label_join creates dst label if not present.
eval instant at 0m label_join(testmetric1, "dst", ", ", "src", "src1", "src2")
testmetric1{src="foo",src1="bar",src2="foobar",dst="foo, bar, foobar"} 0
testmetric1{src="fizz",src1="buzz",src2="fizzbuzz",dst="fizz, buzz, fizzbuzz"} 1
clear
# Tests for vector.
eval instant at 0m vector(1)
{} 1

View file

@ -286,9 +286,6 @@ func (app relabelAppender) Add(lset labels.Labels, t int64, v float64) (string,
// It returns a label set before relabeling was applied as the second return value.
// Returns a nil label set if the target is dropped during relabeling.
func populateLabels(lset labels.Labels, cfg *config.ScrapeConfig) (res, orig labels.Labels, err error) {
if v := lset.Get(model.AddressLabel); v == "" {
return nil, nil, fmt.Errorf("no address")
}
// Copy labels into the labelset for the target if they are not set already.
scrapeLabels := []labels.Label{
{Name: model.JobLabel, Value: cfg.JobName},
@ -316,6 +313,9 @@ func populateLabels(lset labels.Labels, cfg *config.ScrapeConfig) (res, orig lab
if lset == nil {
return nil, nil, nil
}
if v := lset.Get(model.AddressLabel); v == "" {
return nil, nil, fmt.Errorf("no address")
}
lb = labels.NewBuilder(lset)
@ -362,7 +362,15 @@ func populateLabels(lset labels.Labels, cfg *config.ScrapeConfig) (res, orig lab
if v := lset.Get(model.InstanceLabel); v == "" {
lb.Set(model.InstanceLabel, addr)
}
return lb.Labels(), preRelabelLabels, nil
res = lb.Labels()
for _, l := range res {
// Check label values are valid, drop the target if not.
if !model.LabelValue(l.Value).IsValid() {
return nil, nil, fmt.Errorf("invalid label value for %q: %q", l.Name, l.Value)
}
}
return res, preRelabelLabels, nil
}
// targetsFromGroup builds targets based on the given TargetGroup and config.

View file

@ -14,6 +14,7 @@
package retrieval
import (
"fmt"
"reflect"
"testing"
@ -36,6 +37,7 @@ func TestPopulateLabels(t *testing.T) {
cfg *config.ScrapeConfig
res labels.Labels
resOrig labels.Labels
err error
}{
// Regular population of scrape config options.
{
@ -118,34 +120,104 @@ func TestPopulateLabels(t *testing.T) {
model.JobLabel: "job",
}),
},
// Apply relabeling.
// Address label missing.
{
in: labels.FromMap(map[string]string{
model.AddressLabel: "1.2.3.4:1000",
"custom": "value",
}),
in: labels.FromStrings("custom", "value"),
cfg: &config.ScrapeConfig{
Scheme: "https",
MetricsPath: "/metrics",
JobName: "job",
},
res: nil,
resOrig: nil,
err: fmt.Errorf("no address"),
},
// Address label missing, but added in relabelling.
{
in: labels.FromStrings("custom", "host:1234"),
cfg: &config.ScrapeConfig{
Scheme: "https",
MetricsPath: "/metrics",
JobName: "job",
RelabelConfigs: []*config.RelabelConfig{
{
Action: config.RelabelDrop,
Regex: mustNewRegexp(".*"),
SourceLabels: model.LabelNames{"job"},
Action: config.RelabelReplace,
Regex: mustNewRegexp("(.*)"),
SourceLabels: model.LabelNames{"custom"},
Replacement: "${1}",
TargetLabel: string(model.AddressLabel),
},
},
},
res: labels.FromMap(map[string]string{
model.AddressLabel: "host:1234",
model.InstanceLabel: "host:1234",
model.SchemeLabel: "https",
model.MetricsPathLabel: "/metrics",
model.JobLabel: "job",
"custom": "host:1234",
}),
resOrig: labels.FromMap(map[string]string{
model.SchemeLabel: "https",
model.MetricsPathLabel: "/metrics",
model.JobLabel: "job",
"custom": "host:1234",
}),
},
// Address label missing, but added in relabelling.
{
in: labels.FromStrings("custom", "host:1234"),
cfg: &config.ScrapeConfig{
Scheme: "https",
MetricsPath: "/metrics",
JobName: "job",
RelabelConfigs: []*config.RelabelConfig{
{
Action: config.RelabelReplace,
Regex: mustNewRegexp("(.*)"),
SourceLabels: model.LabelNames{"custom"},
Replacement: "${1}",
TargetLabel: string(model.AddressLabel),
},
},
},
res: labels.FromMap(map[string]string{
model.AddressLabel: "host:1234",
model.InstanceLabel: "host:1234",
model.SchemeLabel: "https",
model.MetricsPathLabel: "/metrics",
model.JobLabel: "job",
"custom": "host:1234",
}),
resOrig: labels.FromMap(map[string]string{
model.SchemeLabel: "https",
model.MetricsPathLabel: "/metrics",
model.JobLabel: "job",
"custom": "host:1234",
}),
},
// Invalid UTF-8 in label.
{
in: labels.FromMap(map[string]string{
model.AddressLabel: "1.2.3.4:1000",
"custom": "\xbd",
}),
cfg: &config.ScrapeConfig{
Scheme: "https",
MetricsPath: "/metrics",
JobName: "job",
},
res: nil,
resOrig: nil,
err: fmt.Errorf("invalid label value for \"custom\": \"\\xbd\""),
},
}
for i, c := range cases {
in := c.in.Copy()
res, orig, err := populateLabels(c.in, c.cfg)
if err != nil {
t.Fatalf("case %d: %s", i, err)
if !reflect.DeepEqual(err, c.err) {
t.Fatalf("case %d: wanted %v error, got %v", i, c.err, err)
}
if !reflect.DeepEqual(c.in, in) {
t.Errorf("case %d: input lset was changed was\n\t%+v\n now\n\t%+v", i, in, c.in)

View file

@ -57,7 +57,7 @@
### Naming
- For methods on a type in `response.go`, the receiver should be named `r` and the
- For methods on a type in `results.go`, the receiver should be named `r` and the
variable into which it will be unmarshalled `s`.
- Functions in `requests.go`, with the exception of functions that return a

View file

@ -182,9 +182,10 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.Au
// NewIdentityV2 creates a ServiceClient that may be used to interact with the v2 identity service.
func NewIdentityV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
endpoint := client.IdentityBase + "v2.0/"
clientType := "identity"
var err error
if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) {
eo.ApplyDefaults("identity")
eo.ApplyDefaults(clientType)
endpoint, err = client.EndpointLocator(eo)
if err != nil {
return nil, err
@ -194,15 +195,17 @@ func NewIdentityV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOp
return &gophercloud.ServiceClient{
ProviderClient: client,
Endpoint: endpoint,
Type: clientType,
}, nil
}
// NewIdentityV3 creates a ServiceClient that may be used to access the v3 identity service.
func NewIdentityV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
endpoint := client.IdentityBase + "v3/"
clientType := "identity"
var err error
if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) {
eo.ApplyDefaults("identity")
eo.ApplyDefaults(clientType)
endpoint, err = client.EndpointLocator(eo)
if err != nil {
return nil, err
@ -212,125 +215,81 @@ func NewIdentityV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOp
return &gophercloud.ServiceClient{
ProviderClient: client,
Endpoint: endpoint,
Type: clientType,
}, nil
}
func initClientOpts(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts, clientType string) (*gophercloud.ServiceClient, error) {
sc := new(gophercloud.ServiceClient)
eo.ApplyDefaults(clientType)
url, err := client.EndpointLocator(eo)
if err != nil {
return sc, err
}
sc.ProviderClient = client
sc.Endpoint = url
sc.Type = clientType
return sc, nil
}
// NewObjectStorageV1 creates a ServiceClient that may be used with the v1 object storage package.
func NewObjectStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("object-store")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
return initClientOpts(client, eo, "object-store")
}
// NewComputeV2 creates a ServiceClient that may be used with the v2 compute package.
func NewComputeV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("compute")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
return initClientOpts(client, eo, "compute")
}
// NewNetworkV2 creates a ServiceClient that may be used with the v2 network package.
func NewNetworkV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("network")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{
ProviderClient: client,
Endpoint: url,
ResourceBase: url + "v2.0/",
}, nil
sc, err := initClientOpts(client, eo, "network")
sc.ResourceBase = sc.Endpoint + "v2.0/"
return sc, err
}
// NewBlockStorageV1 creates a ServiceClient that may be used to access the v1 block storage service.
func NewBlockStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("volume")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
return initClientOpts(client, eo, "volume")
}
// NewBlockStorageV2 creates a ServiceClient that may be used to access the v2 block storage service.
func NewBlockStorageV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("volumev2")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
return initClientOpts(client, eo, "volumev2")
}
// NewSharedFileSystemV2 creates a ServiceClient that may be used to access the v2 shared file system service.
func NewSharedFileSystemV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("sharev2")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
return initClientOpts(client, eo, "sharev2")
}
// NewCDNV1 creates a ServiceClient that may be used to access the OpenStack v1
// CDN service.
func NewCDNV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("cdn")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
return initClientOpts(client, eo, "cdn")
}
// NewOrchestrationV1 creates a ServiceClient that may be used to access the v1 orchestration service.
func NewOrchestrationV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("orchestration")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
return initClientOpts(client, eo, "orchestration")
}
// NewDBV1 creates a ServiceClient that may be used to access the v1 DB service.
func NewDBV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("database")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
return initClientOpts(client, eo, "database")
}
// NewDNSV2 creates a ServiceClient that may be used to access the v2 DNS service.
func NewDNSV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("dns")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{
ProviderClient: client,
Endpoint: url,
ResourceBase: url + "v2/"}, nil
sc, err := initClientOpts(client, eo, "dns")
sc.ResourceBase = sc.Endpoint + "v2/"
return sc, err
}
// NewImageServiceV2 creates a ServiceClient that may be used to access the v2 image service.
func NewImageServiceV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("image")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client,
Endpoint: url,
ResourceBase: url + "v2/"}, nil
sc, err := initClientOpts(client, eo, "image")
sc.ResourceBase = sc.Endpoint + "v2/"
return sc, err
}

View file

@ -11,6 +11,24 @@ type ListOptsBuilder interface {
ToFlavorListQuery() (string, error)
}
// AccessType maps to OpenStack's Flavor.is_public field. Although the is_public field is boolean, the
// request options are ternary, which is why AccessType is a string. The following values are
// allowed:
//
// PublicAccess (the default): Returns public flavors and private flavors associated with that project.
// PrivateAccess (admin only): Returns private flavors, across all projects.
// AllAccess (admin only): Returns public and private flavors across all projects.
//
// The AccessType arguement is optional, and if it is not supplied, OpenStack returns the PublicAccess
// flavors.
type AccessType string
const (
PublicAccess AccessType = "true"
PrivateAccess AccessType = "false"
AllAccess AccessType = "None"
)
// ListOpts helps control the results returned by the List() function.
// For example, a flavor with a minDisk field of 10 will not be returned if you specify MinDisk set to 20.
// Typically, software will use the last ID of the previous call to List to set the Marker for the current call.
@ -29,6 +47,10 @@ type ListOpts struct {
// Limit instructs List to refrain from sending excessively large lists of flavors.
Limit int `q:"limit"`
// AccessType, if provided, instructs List which set of flavors to return. If IsPublic not provided,
// flavors for the current project are returned.
AccessType AccessType `q:"is_public"`
}
// ToFlavorListQuery formats a ListOpts into a query string.

View file

@ -46,6 +46,8 @@ type Flavor struct {
Swap int `json:"swap"`
// VCPUs indicates how many (virtual) CPUs are available for this flavor.
VCPUs int `json:"vcpus"`
// IsPublic indicates whether the flavor is public.
IsPublic bool `json:"is_public"`
}
func (r *Flavor) UnmarshalJSON(b []byte) error {

View file

@ -182,13 +182,13 @@ func Get(c *gophercloud.ServiceClient, token string) (r GetResult) {
func Validate(c *gophercloud.ServiceClient, token string) (bool, error) {
resp, err := c.Request("HEAD", tokenURL(c), &gophercloud.RequestOpts{
MoreHeaders: subjectTokenHeaders(c, token),
OkCodes: []int{204, 404},
OkCodes: []int{200, 204, 400, 401, 403, 404},
})
if err != nil {
return false, err
}
return resp.StatusCode == 204, nil
return resp.StatusCode == 200 || resp.StatusCode == 204, nil
}
// Revoke immediately makes specified token invalid.

View file

@ -21,6 +21,12 @@ type ServiceClient struct {
// as-is, instead.
ResourceBase string
// This is the service client type (e.g. compute, sharev2).
// NOTE: FOR INTERNAL USE ONLY. DO NOT SET. GOPHERCLOUD WILL SET THIS.
// It is only exported because it gets set in a different package.
Type string
// The microversion of the service to use. Set this to use a particular microversion.
Microversion string
}
@ -37,11 +43,13 @@ func (client *ServiceClient) ServiceURL(parts ...string) string {
return client.ResourceBaseURL() + strings.Join(parts, "/")
}
// Get calls `Request` with the "GET" HTTP verb.
func (client *ServiceClient) Get(url string, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) {
if opts == nil {
opts = &RequestOpts{}
func (client *ServiceClient) initReqOpts(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) {
if v, ok := (JSONBody).(io.Reader); ok {
opts.RawBody = v
} else if JSONBody != nil {
opts.JSONBody = JSONBody
}
if JSONResponse != nil {
opts.JSONResponse = JSONResponse
}
@ -49,93 +57,66 @@ func (client *ServiceClient) Get(url string, JSONResponse interface{}, opts *Req
if opts.MoreHeaders == nil {
opts.MoreHeaders = make(map[string]string)
}
opts.MoreHeaders["X-OpenStack-Nova-API-Version"] = client.Microversion
if client.Microversion != "" {
client.setMicroversionHeader(opts)
}
}
// Get calls `Request` with the "GET" HTTP verb.
func (client *ServiceClient) Get(url string, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) {
if opts == nil {
opts = new(RequestOpts)
}
client.initReqOpts(url, nil, JSONResponse, opts)
return client.Request("GET", url, opts)
}
// Post calls `Request` with the "POST" HTTP verb.
func (client *ServiceClient) Post(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) {
if opts == nil {
opts = &RequestOpts{}
opts = new(RequestOpts)
}
if v, ok := (JSONBody).(io.Reader); ok {
opts.RawBody = v
} else if JSONBody != nil {
opts.JSONBody = JSONBody
}
if JSONResponse != nil {
opts.JSONResponse = JSONResponse
}
if opts.MoreHeaders == nil {
opts.MoreHeaders = make(map[string]string)
}
opts.MoreHeaders["X-OpenStack-Nova-API-Version"] = client.Microversion
client.initReqOpts(url, JSONBody, JSONResponse, opts)
return client.Request("POST", url, opts)
}
// Put calls `Request` with the "PUT" HTTP verb.
func (client *ServiceClient) Put(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) {
if opts == nil {
opts = &RequestOpts{}
opts = new(RequestOpts)
}
if v, ok := (JSONBody).(io.Reader); ok {
opts.RawBody = v
} else if JSONBody != nil {
opts.JSONBody = JSONBody
}
if JSONResponse != nil {
opts.JSONResponse = JSONResponse
}
if opts.MoreHeaders == nil {
opts.MoreHeaders = make(map[string]string)
}
opts.MoreHeaders["X-OpenStack-Nova-API-Version"] = client.Microversion
client.initReqOpts(url, JSONBody, JSONResponse, opts)
return client.Request("PUT", url, opts)
}
// Patch calls `Request` with the "PATCH" HTTP verb.
func (client *ServiceClient) Patch(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) {
if opts == nil {
opts = &RequestOpts{}
opts = new(RequestOpts)
}
if v, ok := (JSONBody).(io.Reader); ok {
opts.RawBody = v
} else if JSONBody != nil {
opts.JSONBody = JSONBody
}
if JSONResponse != nil {
opts.JSONResponse = JSONResponse
}
if opts.MoreHeaders == nil {
opts.MoreHeaders = make(map[string]string)
}
opts.MoreHeaders["X-OpenStack-Nova-API-Version"] = client.Microversion
client.initReqOpts(url, JSONBody, JSONResponse, opts)
return client.Request("PATCH", url, opts)
}
// Delete calls `Request` with the "DELETE" HTTP verb.
func (client *ServiceClient) Delete(url string, opts *RequestOpts) (*http.Response, error) {
if opts == nil {
opts = &RequestOpts{}
opts = new(RequestOpts)
}
if opts.MoreHeaders == nil {
opts.MoreHeaders = make(map[string]string)
}
opts.MoreHeaders["X-OpenStack-Nova-API-Version"] = client.Microversion
client.initReqOpts(url, nil, nil, opts)
return client.Request("DELETE", url, opts)
}
func (client *ServiceClient) setMicroversionHeader(opts *RequestOpts) {
switch client.Type {
case "compute":
opts.MoreHeaders["X-OpenStack-Nova-API-Version"] = client.Microversion
case "sharev2":
opts.MoreHeaders["X-OpenStack-Manila-API-Version"] = client.Microversion
}
if client.Type != "" {
opts.MoreHeaders["OpenStack-API-Version"] = client.Type + " " + client.Microversion
}
}

58
vendor/vendor.json vendored
View file

@ -478,40 +478,46 @@
"revisionTime": "2016-09-30T00:14:02Z"
},
{
"checksumSHA1": "cuTQkSEiIoMhDhB7xCbYDwDhrgw=",
"checksumSHA1": "ndG2xmm2bas4/t+kIEUp2xIni8c=",
"path": "github.com/gophercloud/gophercloud",
"revision": "0bf921da554eacc1552a70204be7a1201937c1e1",
"revisionTime": "2017-05-04T01:40:32Z"
"revision": "caf34a65f60295108141f62929245943bd00f237",
"revisionTime": "2017-06-07T03:48:29Z"
},
{
"checksumSHA1": "0KdIjTH5IO8hlIl8kdfI6313GiY=",
"checksumSHA1": "24DO5BEQdFKNl1rfWgI2b4+ry5U=",
"path": "github.com/gophercloud/gophercloud/openstack",
"revision": "0bf921da554eacc1552a70204be7a1201937c1e1",
"revisionTime": "2017-05-04T01:40:32Z"
"revision": "caf34a65f60295108141f62929245943bd00f237",
"revisionTime": "2017-06-07T03:48:29Z"
},
{
"checksumSHA1": "4XWDCGMYqipwJymi9xJo9UffD7g=",
"path": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions",
"revision": "caf34a65f60295108141f62929245943bd00f237",
"revisionTime": "2017-06-07T03:48:29Z"
},
{
"checksumSHA1": "e7AW3YDVYJPKUjpqsB4AL9RRlTw=",
"path": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips",
"revision": "caa61b3ca9f504196fd3b338f43cd99d830f7e2e",
"revisionTime": "2017-05-11T18:09:16Z"
"revision": "caf34a65f60295108141f62929245943bd00f237",
"revisionTime": "2017-06-07T03:48:29Z"
},
{
"checksumSHA1": "S1BV3o8Pa0aM5RaUuRYXY7LnPIc=",
"checksumSHA1": "vTyXSR+Znw7/o/70UBOWG0F09r8=",
"path": "github.com/gophercloud/gophercloud/openstack/compute/v2/flavors",
"revision": "0bf921da554eacc1552a70204be7a1201937c1e1",
"revisionTime": "2017-05-04T01:40:32Z"
"revision": "caf34a65f60295108141f62929245943bd00f237",
"revisionTime": "2017-06-07T03:48:29Z"
},
{
"checksumSHA1": "Rnzx2YgOD41k8KoPA08tR992PxQ=",
"path": "github.com/gophercloud/gophercloud/openstack/compute/v2/images",
"revision": "0bf921da554eacc1552a70204be7a1201937c1e1",
"revisionTime": "2017-05-04T01:40:32Z"
"revision": "caf34a65f60295108141f62929245943bd00f237",
"revisionTime": "2017-06-07T03:48:29Z"
},
{
"checksumSHA1": "IjCvcaNnRW++hclt21WUkMYinaA=",
"path": "github.com/gophercloud/gophercloud/openstack/compute/v2/servers",
"revision": "0bf921da554eacc1552a70204be7a1201937c1e1",
"revisionTime": "2017-05-04T01:40:32Z"
"revision": "caf34a65f60295108141f62929245943bd00f237",
"revisionTime": "2017-06-07T03:48:29Z"
},
{
"checksumSHA1": "9z5GwbsfpB49gzkHu10pDH5roKA=",
@ -522,14 +528,14 @@
{
"checksumSHA1": "1sVqsZBZBNhDXLY9XzjMkcOkcbg=",
"path": "github.com/gophercloud/gophercloud/openstack/identity/v2/tenants",
"revision": "0bf921da554eacc1552a70204be7a1201937c1e1",
"revisionTime": "2017-05-04T01:40:32Z"
"revision": "caf34a65f60295108141f62929245943bd00f237",
"revisionTime": "2017-06-07T03:48:29Z"
},
{
"checksumSHA1": "AvUU5En9YpG25iLlcAPDgcQODjI=",
"path": "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens",
"revision": "0bf921da554eacc1552a70204be7a1201937c1e1",
"revisionTime": "2017-05-04T01:40:32Z"
"revision": "caf34a65f60295108141f62929245943bd00f237",
"revisionTime": "2017-06-07T03:48:29Z"
},
{
"checksumSHA1": "vsgZmEVQLtCmxuxf/q4u8XJGWpE=",
@ -538,22 +544,22 @@
"revisionTime": "2017-05-04T01:40:32Z"
},
{
"checksumSHA1": "ZKyEbJuIlvuZ9aUushINCXJHF4w=",
"checksumSHA1": "fHOkQNWyeGoMPjkYvqiOrOifTUw=",
"path": "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens",
"revision": "0bf921da554eacc1552a70204be7a1201937c1e1",
"revisionTime": "2017-05-04T01:40:32Z"
"revision": "caf34a65f60295108141f62929245943bd00f237",
"revisionTime": "2017-06-07T03:48:29Z"
},
{
"checksumSHA1": "TDOZnaS0TO0NirpxV1QwPerAQTY=",
"path": "github.com/gophercloud/gophercloud/openstack/utils",
"revision": "0bf921da554eacc1552a70204be7a1201937c1e1",
"revisionTime": "2017-05-04T01:40:32Z"
"revision": "caf34a65f60295108141f62929245943bd00f237",
"revisionTime": "2017-06-07T03:48:29Z"
},
{
"checksumSHA1": "FNy075ydQZXvnL2bNNIOCmy/ghs=",
"path": "github.com/gophercloud/gophercloud/pagination",
"revision": "0bf921da554eacc1552a70204be7a1201937c1e1",
"revisionTime": "2017-05-04T01:40:32Z"
"revision": "caf34a65f60295108141f62929245943bd00f237",
"revisionTime": "2017-06-07T03:48:29Z"
},
{
"checksumSHA1": "LclVLJYrBi03PBjsVPpgoMbUDQ8=",

File diff suppressed because one or more lines are too long

View file

@ -364,7 +364,7 @@ Prometheus.Graph.prototype.submitQuery = function() {
var startTime = new Date().getTime();
var rangeSeconds = self.parseDuration(self.rangeInput.val());
var resolution = self.queryForm.find("input[name=step_input]").val() || Math.max(Math.floor(rangeSeconds / 250), 1);
var resolution = parseInt(self.queryForm.find("input[name=step_input]").val()) || Math.max(Math.floor(rangeSeconds / 250), 1);
var endDate = self.getEndDate() / 1000;
if (self.queryXhr) {

View file

@ -171,7 +171,7 @@ func New(o *Options) *Handler {
instrf := prometheus.InstrumentHandlerFunc
router.Get("/", func(w http.ResponseWriter, r *http.Request) {
router.Redirect(w, r, path.Join(o.ExternalURL.Path, "/graph"), http.StatusFound)
http.Redirect(w, r, path.Join(o.ExternalURL.Path, "/graph"), http.StatusFound)
})
router.Get("/alerts", instrf("alerts", h.alerts))