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 ## 1.6.3 / 2017-05-18
* [BUGFIX] Fix disappearing Alertmanger targets in Alertmanager discovery. * [BUGFIX] Fix disappearing Alertmanger targets in Alertmanager discovery.

View file

@ -23,6 +23,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
@ -1134,7 +1136,16 @@ func (c *EC2SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
return err return err
} }
if c.Region == "" { if c.Region == "" {
return fmt.Errorf("EC2 SD configuration requires a 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 return nil
} }

View file

@ -18,6 +18,7 @@ import (
"regexp" "regexp"
"sort" "sort"
"strconv" "strconv"
"strings"
"time" "time"
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
@ -27,11 +28,11 @@ import (
// Function represents a function of the expression language and is // Function represents a function of the expression language and is
// used by function nodes. // used by function nodes.
type Function struct { type Function struct {
Name string Name string
ArgTypes []ValueType ArgTypes []ValueType
OptionalArgs int Variadic int
ReturnType ValueType ReturnType ValueType
Call func(ev *evaluator, args Expressions) Value Call func(ev *evaluator, args Expressions) Value
} }
// === time() float64 === // === time() float64 ===
@ -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. // Common code for date related functions.
func dateWrapper(ev *evaluator, args Expressions, f func(time.Time) float64) Value { func dateWrapper(ev *evaluator, args Expressions, f func(time.Time) float64) Value {
var v Vector var v Vector
@ -1004,25 +1055,25 @@ var functions = map[string]*Function{
Call: funcCountScalar, Call: funcCountScalar,
}, },
"days_in_month": { "days_in_month": {
Name: "days_in_month", Name: "days_in_month",
ArgTypes: []ValueType{ValueTypeVector}, ArgTypes: []ValueType{ValueTypeVector},
OptionalArgs: 1, Variadic: 1,
ReturnType: ValueTypeVector, ReturnType: ValueTypeVector,
Call: funcDaysInMonth, Call: funcDaysInMonth,
}, },
"day_of_month": { "day_of_month": {
Name: "day_of_month", Name: "day_of_month",
ArgTypes: []ValueType{ValueTypeVector}, ArgTypes: []ValueType{ValueTypeVector},
OptionalArgs: 1, Variadic: 1,
ReturnType: ValueTypeVector, ReturnType: ValueTypeVector,
Call: funcDayOfMonth, Call: funcDayOfMonth,
}, },
"day_of_week": { "day_of_week": {
Name: "day_of_week", Name: "day_of_week",
ArgTypes: []ValueType{ValueTypeVector}, ArgTypes: []ValueType{ValueTypeVector},
OptionalArgs: 1, Variadic: 1,
ReturnType: ValueTypeVector, ReturnType: ValueTypeVector,
Call: funcDayOfWeek, Call: funcDayOfWeek,
}, },
"delta": { "delta": {
Name: "delta", Name: "delta",
@ -1067,11 +1118,11 @@ var functions = map[string]*Function{
Call: funcHoltWinters, Call: funcHoltWinters,
}, },
"hour": { "hour": {
Name: "hour", Name: "hour",
ArgTypes: []ValueType{ValueTypeVector}, ArgTypes: []ValueType{ValueTypeVector},
OptionalArgs: 1, Variadic: 1,
ReturnType: ValueTypeVector, ReturnType: ValueTypeVector,
Call: funcHour, Call: funcHour,
}, },
"idelta": { "idelta": {
Name: "idelta", Name: "idelta",
@ -1097,6 +1148,13 @@ var functions = map[string]*Function{
ReturnType: ValueTypeVector, ReturnType: ValueTypeVector,
Call: funcLabelReplace, Call: funcLabelReplace,
}, },
"label_join": {
Name: "label_join",
ArgTypes: []ValueType{ValueTypeVector, ValueTypeString, ValueTypeString, ValueTypeString},
Variadic: -1,
ReturnType: ValueTypeVector,
Call: funcLabelJoin,
},
"ln": { "ln": {
Name: "ln", Name: "ln",
ArgTypes: []ValueType{ValueTypeVector}, ArgTypes: []ValueType{ValueTypeVector},
@ -1128,18 +1186,18 @@ var functions = map[string]*Function{
Call: funcMinOverTime, Call: funcMinOverTime,
}, },
"minute": { "minute": {
Name: "minute", Name: "minute",
ArgTypes: []ValueType{ValueTypeVector}, ArgTypes: []ValueType{ValueTypeVector},
OptionalArgs: 1, Variadic: 1,
ReturnType: ValueTypeVector, ReturnType: ValueTypeVector,
Call: funcMinute, Call: funcMinute,
}, },
"month": { "month": {
Name: "month", Name: "month",
ArgTypes: []ValueType{ValueTypeVector}, ArgTypes: []ValueType{ValueTypeVector},
OptionalArgs: 1, Variadic: 1,
ReturnType: ValueTypeVector, ReturnType: ValueTypeVector,
Call: funcMonth, Call: funcMonth,
}, },
"predict_linear": { "predict_linear": {
Name: "predict_linear", Name: "predict_linear",
@ -1166,11 +1224,11 @@ var functions = map[string]*Function{
Call: funcResets, Call: funcResets,
}, },
"round": { "round": {
Name: "round", Name: "round",
ArgTypes: []ValueType{ValueTypeVector, ValueTypeScalar}, ArgTypes: []ValueType{ValueTypeVector, ValueTypeScalar},
OptionalArgs: 1, Variadic: 1,
ReturnType: ValueTypeVector, ReturnType: ValueTypeVector,
Call: funcRound, Call: funcRound,
}, },
"scalar": { "scalar": {
Name: "scalar", Name: "scalar",
@ -1233,11 +1291,11 @@ var functions = map[string]*Function{
Call: funcVector, Call: funcVector,
}, },
"year": { "year": {
Name: "year", Name: "year",
ArgTypes: []ValueType{ValueTypeVector}, ArgTypes: []ValueType{ValueTypeVector},
OptionalArgs: 1, Variadic: 1,
ReturnType: ValueTypeVector, ReturnType: ValueTypeVector,
Call: funcYear, Call: funcYear,
}, },
} }

View file

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

View file

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

View file

@ -1094,13 +1094,23 @@ func (p *parser) checkType(node Node) (typ ValueType) {
case *Call: case *Call:
nargs := len(n.Func.ArgTypes) nargs := len(n.Func.ArgTypes)
if na := nargs - n.Func.OptionalArgs; na > len(n.Args) { if n.Func.Variadic == 0 {
p.errorf("expected at least %d argument(s) in call to %q, got %d", na, n.Func.Name, len(n.Args)) if nargs != len(n.Args) {
} p.errorf("expected %d argument(s) in call to %q, got %d", nargs, 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)) } 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))
}
} }
for i, arg := range 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)) 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 // TODO(fabxc): willingly lexing wrong tokens allows for more precrise error
// messages from the parser - consider if this is an option. // messages from the parser - consider if this is an option.
errMsg: "unexpected character inside braces: '>'", 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}`, input: `foo{gibberish}`,
fail: true, fail: true,
@ -1356,11 +1360,11 @@ var testExpr = []struct {
}, { }, {
input: "floor()", input: "floor()",
fail: true, 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)", input: "floor(some_metric, other_metric)",
fail: true, 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)", input: "floor(1)",
fail: true, fail: true,
@ -1373,6 +1377,10 @@ var testExpr = []struct {
input: "rate(some_metric)", input: "rate(some_metric)",
fail: true, fail: true,
errMsg: "expected type range vector in call to function \"rate\", got instant vector", 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. // Fuzzing regression tests.
{ {

View file

@ -236,6 +236,44 @@ eval instant at 5s timestamp(metric)
eval instant at 10s timestamp(metric) eval instant at 10s timestamp(metric)
{} 10 {} 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) eval instant at 0m vector(1)
{} 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. // 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. // 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) { 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. // Copy labels into the labelset for the target if they are not set already.
scrapeLabels := []labels.Label{ scrapeLabels := []labels.Label{
{Name: model.JobLabel, Value: cfg.JobName}, {Name: model.JobLabel, Value: cfg.JobName},
@ -316,6 +313,9 @@ func populateLabels(lset labels.Labels, cfg *config.ScrapeConfig) (res, orig lab
if lset == nil { if lset == nil {
return nil, nil, nil return nil, nil, nil
} }
if v := lset.Get(model.AddressLabel); v == "" {
return nil, nil, fmt.Errorf("no address")
}
lb = labels.NewBuilder(lset) 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 == "" { if v := lset.Get(model.InstanceLabel); v == "" {
lb.Set(model.InstanceLabel, addr) 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. // targetsFromGroup builds targets based on the given TargetGroup and config.

View file

@ -14,6 +14,7 @@
package retrieval package retrieval
import ( import (
"fmt"
"reflect" "reflect"
"testing" "testing"
@ -36,6 +37,7 @@ func TestPopulateLabels(t *testing.T) {
cfg *config.ScrapeConfig cfg *config.ScrapeConfig
res labels.Labels res labels.Labels
resOrig labels.Labels resOrig labels.Labels
err error
}{ }{
// Regular population of scrape config options. // Regular population of scrape config options.
{ {
@ -118,34 +120,104 @@ func TestPopulateLabels(t *testing.T) {
model.JobLabel: "job", model.JobLabel: "job",
}), }),
}, },
// Apply relabeling. // Address label missing.
{ {
in: labels.FromMap(map[string]string{ in: labels.FromStrings("custom", "value"),
model.AddressLabel: "1.2.3.4:1000", cfg: &config.ScrapeConfig{
"custom": "value", 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{ cfg: &config.ScrapeConfig{
Scheme: "https", Scheme: "https",
MetricsPath: "/metrics", MetricsPath: "/metrics",
JobName: "job", JobName: "job",
RelabelConfigs: []*config.RelabelConfig{ RelabelConfigs: []*config.RelabelConfig{
{ {
Action: config.RelabelDrop, Action: config.RelabelReplace,
Regex: mustNewRegexp(".*"), Regex: mustNewRegexp("(.*)"),
SourceLabels: model.LabelNames{"job"}, 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, res: nil,
resOrig: nil, resOrig: nil,
err: fmt.Errorf("invalid label value for \"custom\": \"\\xbd\""),
}, },
} }
for i, c := range cases { for i, c := range cases {
in := c.in.Copy() in := c.in.Copy()
res, orig, err := populateLabels(c.in, c.cfg) res, orig, err := populateLabels(c.in, c.cfg)
if err != nil { if !reflect.DeepEqual(err, c.err) {
t.Fatalf("case %d: %s", i, err) t.Fatalf("case %d: wanted %v error, got %v", i, c.err, err)
} }
if !reflect.DeepEqual(c.in, in) { 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) 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 ### 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`. variable into which it will be unmarshalled `s`.
- Functions in `requests.go`, with the exception of functions that return a - 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. // 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) { func NewIdentityV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
endpoint := client.IdentityBase + "v2.0/" endpoint := client.IdentityBase + "v2.0/"
clientType := "identity"
var err error var err error
if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) { if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) {
eo.ApplyDefaults("identity") eo.ApplyDefaults(clientType)
endpoint, err = client.EndpointLocator(eo) endpoint, err = client.EndpointLocator(eo)
if err != nil { if err != nil {
return nil, err return nil, err
@ -194,15 +195,17 @@ func NewIdentityV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOp
return &gophercloud.ServiceClient{ return &gophercloud.ServiceClient{
ProviderClient: client, ProviderClient: client,
Endpoint: endpoint, Endpoint: endpoint,
Type: clientType,
}, nil }, nil
} }
// NewIdentityV3 creates a ServiceClient that may be used to access the v3 identity service. // 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) { func NewIdentityV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
endpoint := client.IdentityBase + "v3/" endpoint := client.IdentityBase + "v3/"
clientType := "identity"
var err error var err error
if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) { if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) {
eo.ApplyDefaults("identity") eo.ApplyDefaults(clientType)
endpoint, err = client.EndpointLocator(eo) endpoint, err = client.EndpointLocator(eo)
if err != nil { if err != nil {
return nil, err return nil, err
@ -212,125 +215,81 @@ func NewIdentityV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOp
return &gophercloud.ServiceClient{ return &gophercloud.ServiceClient{
ProviderClient: client, ProviderClient: client,
Endpoint: endpoint, Endpoint: endpoint,
Type: clientType,
}, nil }, 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. // 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) { func NewObjectStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("object-store") return initClientOpts(client, eo, "object-store")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
} }
// NewComputeV2 creates a ServiceClient that may be used with the v2 compute package. // NewComputeV2 creates a ServiceClient that may be used with the v2 compute package.
func NewComputeV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { func NewComputeV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("compute") return initClientOpts(client, eo, "compute")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
} }
// NewNetworkV2 creates a ServiceClient that may be used with the v2 network package. // NewNetworkV2 creates a ServiceClient that may be used with the v2 network package.
func NewNetworkV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { func NewNetworkV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("network") sc, err := initClientOpts(client, eo, "network")
url, err := client.EndpointLocator(eo) sc.ResourceBase = sc.Endpoint + "v2.0/"
if err != nil { return sc, err
return nil, err
}
return &gophercloud.ServiceClient{
ProviderClient: client,
Endpoint: url,
ResourceBase: url + "v2.0/",
}, nil
} }
// NewBlockStorageV1 creates a ServiceClient that may be used to access the v1 block storage service. // 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) { func NewBlockStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("volume") return initClientOpts(client, eo, "volume")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
} }
// NewBlockStorageV2 creates a ServiceClient that may be used to access the v2 block storage service. // 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) { func NewBlockStorageV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("volumev2") return initClientOpts(client, eo, "volumev2")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
} }
// NewSharedFileSystemV2 creates a ServiceClient that may be used to access the v2 shared file system service. // 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) { func NewSharedFileSystemV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("sharev2") return initClientOpts(client, eo, "sharev2")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
} }
// NewCDNV1 creates a ServiceClient that may be used to access the OpenStack v1 // NewCDNV1 creates a ServiceClient that may be used to access the OpenStack v1
// CDN service. // CDN service.
func NewCDNV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { func NewCDNV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("cdn") return initClientOpts(client, eo, "cdn")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
} }
// NewOrchestrationV1 creates a ServiceClient that may be used to access the v1 orchestration service. // 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) { func NewOrchestrationV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("orchestration") return initClientOpts(client, eo, "orchestration")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
} }
// NewDBV1 creates a ServiceClient that may be used to access the v1 DB service. // 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) { func NewDBV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("database") return initClientOpts(client, eo, "database")
url, err := client.EndpointLocator(eo)
if err != nil {
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
} }
// NewDNSV2 creates a ServiceClient that may be used to access the v2 DNS service. // 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) { func NewDNSV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("dns") sc, err := initClientOpts(client, eo, "dns")
url, err := client.EndpointLocator(eo) sc.ResourceBase = sc.Endpoint + "v2/"
if err != nil { return sc, err
return nil, err
}
return &gophercloud.ServiceClient{
ProviderClient: client,
Endpoint: url,
ResourceBase: url + "v2/"}, nil
} }
// NewImageServiceV2 creates a ServiceClient that may be used to access the v2 image service. // 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) { func NewImageServiceV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
eo.ApplyDefaults("image") sc, err := initClientOpts(client, eo, "image")
url, err := client.EndpointLocator(eo) sc.ResourceBase = sc.Endpoint + "v2/"
if err != nil { return sc, err
return nil, err
}
return &gophercloud.ServiceClient{ProviderClient: client,
Endpoint: url,
ResourceBase: url + "v2/"}, nil
} }

View file

@ -11,6 +11,24 @@ type ListOptsBuilder interface {
ToFlavorListQuery() (string, error) 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. // 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. // 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. // 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 instructs List to refrain from sending excessively large lists of flavors.
Limit int `q:"limit"` 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. // ToFlavorListQuery formats a ListOpts into a query string.

View file

@ -46,6 +46,8 @@ type Flavor struct {
Swap int `json:"swap"` Swap int `json:"swap"`
// VCPUs indicates how many (virtual) CPUs are available for this flavor. // VCPUs indicates how many (virtual) CPUs are available for this flavor.
VCPUs int `json:"vcpus"` VCPUs int `json:"vcpus"`
// IsPublic indicates whether the flavor is public.
IsPublic bool `json:"is_public"`
} }
func (r *Flavor) UnmarshalJSON(b []byte) error { 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) { func Validate(c *gophercloud.ServiceClient, token string) (bool, error) {
resp, err := c.Request("HEAD", tokenURL(c), &gophercloud.RequestOpts{ resp, err := c.Request("HEAD", tokenURL(c), &gophercloud.RequestOpts{
MoreHeaders: subjectTokenHeaders(c, token), MoreHeaders: subjectTokenHeaders(c, token),
OkCodes: []int{204, 404}, OkCodes: []int{200, 204, 400, 401, 403, 404},
}) })
if err != nil { if err != nil {
return false, err return false, err
} }
return resp.StatusCode == 204, nil return resp.StatusCode == 200 || resp.StatusCode == 204, nil
} }
// Revoke immediately makes specified token invalid. // Revoke immediately makes specified token invalid.

View file

@ -21,6 +21,12 @@ type ServiceClient struct {
// as-is, instead. // as-is, instead.
ResourceBase string 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 Microversion string
} }
@ -37,11 +43,13 @@ func (client *ServiceClient) ServiceURL(parts ...string) string {
return client.ResourceBaseURL() + strings.Join(parts, "/") return client.ResourceBaseURL() + strings.Join(parts, "/")
} }
// Get calls `Request` with the "GET" HTTP verb. func (client *ServiceClient) initReqOpts(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) {
func (client *ServiceClient) Get(url string, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) { if v, ok := (JSONBody).(io.Reader); ok {
if opts == nil { opts.RawBody = v
opts = &RequestOpts{} } else if JSONBody != nil {
opts.JSONBody = JSONBody
} }
if JSONResponse != nil { if JSONResponse != nil {
opts.JSONResponse = JSONResponse opts.JSONResponse = JSONResponse
} }
@ -49,93 +57,66 @@ func (client *ServiceClient) Get(url string, JSONResponse interface{}, opts *Req
if opts.MoreHeaders == nil { if opts.MoreHeaders == nil {
opts.MoreHeaders = make(map[string]string) 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) return client.Request("GET", url, opts)
} }
// Post calls `Request` with the "POST" HTTP verb. // Post calls `Request` with the "POST" HTTP verb.
func (client *ServiceClient) Post(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) { func (client *ServiceClient) Post(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) {
if opts == nil { if opts == nil {
opts = &RequestOpts{} opts = new(RequestOpts)
} }
client.initReqOpts(url, JSONBody, JSONResponse, opts)
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
return client.Request("POST", url, opts) return client.Request("POST", url, opts)
} }
// Put calls `Request` with the "PUT" HTTP verb. // Put calls `Request` with the "PUT" HTTP verb.
func (client *ServiceClient) Put(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) { func (client *ServiceClient) Put(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) {
if opts == nil { if opts == nil {
opts = &RequestOpts{} opts = new(RequestOpts)
} }
client.initReqOpts(url, JSONBody, JSONResponse, opts)
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
return client.Request("PUT", url, opts) return client.Request("PUT", url, opts)
} }
// Patch calls `Request` with the "PATCH" HTTP verb. // Patch calls `Request` with the "PATCH" HTTP verb.
func (client *ServiceClient) Patch(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) { func (client *ServiceClient) Patch(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) {
if opts == nil { if opts == nil {
opts = &RequestOpts{} opts = new(RequestOpts)
} }
client.initReqOpts(url, JSONBody, JSONResponse, opts)
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
return client.Request("PATCH", url, opts) return client.Request("PATCH", url, opts)
} }
// Delete calls `Request` with the "DELETE" HTTP verb. // Delete calls `Request` with the "DELETE" HTTP verb.
func (client *ServiceClient) Delete(url string, opts *RequestOpts) (*http.Response, error) { func (client *ServiceClient) Delete(url string, opts *RequestOpts) (*http.Response, error) {
if opts == nil { if opts == nil {
opts = &RequestOpts{} opts = new(RequestOpts)
} }
client.initReqOpts(url, nil, nil, opts)
if opts.MoreHeaders == nil {
opts.MoreHeaders = make(map[string]string)
}
opts.MoreHeaders["X-OpenStack-Nova-API-Version"] = client.Microversion
return client.Request("DELETE", url, 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" "revisionTime": "2016-09-30T00:14:02Z"
}, },
{ {
"checksumSHA1": "cuTQkSEiIoMhDhB7xCbYDwDhrgw=", "checksumSHA1": "ndG2xmm2bas4/t+kIEUp2xIni8c=",
"path": "github.com/gophercloud/gophercloud", "path": "github.com/gophercloud/gophercloud",
"revision": "0bf921da554eacc1552a70204be7a1201937c1e1", "revision": "caf34a65f60295108141f62929245943bd00f237",
"revisionTime": "2017-05-04T01:40:32Z" "revisionTime": "2017-06-07T03:48:29Z"
}, },
{ {
"checksumSHA1": "0KdIjTH5IO8hlIl8kdfI6313GiY=", "checksumSHA1": "24DO5BEQdFKNl1rfWgI2b4+ry5U=",
"path": "github.com/gophercloud/gophercloud/openstack", "path": "github.com/gophercloud/gophercloud/openstack",
"revision": "0bf921da554eacc1552a70204be7a1201937c1e1", "revision": "caf34a65f60295108141f62929245943bd00f237",
"revisionTime": "2017-05-04T01:40:32Z" "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=", "checksumSHA1": "e7AW3YDVYJPKUjpqsB4AL9RRlTw=",
"path": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips", "path": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips",
"revision": "caa61b3ca9f504196fd3b338f43cd99d830f7e2e", "revision": "caf34a65f60295108141f62929245943bd00f237",
"revisionTime": "2017-05-11T18:09:16Z" "revisionTime": "2017-06-07T03:48:29Z"
}, },
{ {
"checksumSHA1": "S1BV3o8Pa0aM5RaUuRYXY7LnPIc=", "checksumSHA1": "vTyXSR+Znw7/o/70UBOWG0F09r8=",
"path": "github.com/gophercloud/gophercloud/openstack/compute/v2/flavors", "path": "github.com/gophercloud/gophercloud/openstack/compute/v2/flavors",
"revision": "0bf921da554eacc1552a70204be7a1201937c1e1", "revision": "caf34a65f60295108141f62929245943bd00f237",
"revisionTime": "2017-05-04T01:40:32Z" "revisionTime": "2017-06-07T03:48:29Z"
}, },
{ {
"checksumSHA1": "Rnzx2YgOD41k8KoPA08tR992PxQ=", "checksumSHA1": "Rnzx2YgOD41k8KoPA08tR992PxQ=",
"path": "github.com/gophercloud/gophercloud/openstack/compute/v2/images", "path": "github.com/gophercloud/gophercloud/openstack/compute/v2/images",
"revision": "0bf921da554eacc1552a70204be7a1201937c1e1", "revision": "caf34a65f60295108141f62929245943bd00f237",
"revisionTime": "2017-05-04T01:40:32Z" "revisionTime": "2017-06-07T03:48:29Z"
}, },
{ {
"checksumSHA1": "IjCvcaNnRW++hclt21WUkMYinaA=", "checksumSHA1": "IjCvcaNnRW++hclt21WUkMYinaA=",
"path": "github.com/gophercloud/gophercloud/openstack/compute/v2/servers", "path": "github.com/gophercloud/gophercloud/openstack/compute/v2/servers",
"revision": "0bf921da554eacc1552a70204be7a1201937c1e1", "revision": "caf34a65f60295108141f62929245943bd00f237",
"revisionTime": "2017-05-04T01:40:32Z" "revisionTime": "2017-06-07T03:48:29Z"
}, },
{ {
"checksumSHA1": "9z5GwbsfpB49gzkHu10pDH5roKA=", "checksumSHA1": "9z5GwbsfpB49gzkHu10pDH5roKA=",
@ -522,14 +528,14 @@
{ {
"checksumSHA1": "1sVqsZBZBNhDXLY9XzjMkcOkcbg=", "checksumSHA1": "1sVqsZBZBNhDXLY9XzjMkcOkcbg=",
"path": "github.com/gophercloud/gophercloud/openstack/identity/v2/tenants", "path": "github.com/gophercloud/gophercloud/openstack/identity/v2/tenants",
"revision": "0bf921da554eacc1552a70204be7a1201937c1e1", "revision": "caf34a65f60295108141f62929245943bd00f237",
"revisionTime": "2017-05-04T01:40:32Z" "revisionTime": "2017-06-07T03:48:29Z"
}, },
{ {
"checksumSHA1": "AvUU5En9YpG25iLlcAPDgcQODjI=", "checksumSHA1": "AvUU5En9YpG25iLlcAPDgcQODjI=",
"path": "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens", "path": "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens",
"revision": "0bf921da554eacc1552a70204be7a1201937c1e1", "revision": "caf34a65f60295108141f62929245943bd00f237",
"revisionTime": "2017-05-04T01:40:32Z" "revisionTime": "2017-06-07T03:48:29Z"
}, },
{ {
"checksumSHA1": "vsgZmEVQLtCmxuxf/q4u8XJGWpE=", "checksumSHA1": "vsgZmEVQLtCmxuxf/q4u8XJGWpE=",
@ -538,22 +544,22 @@
"revisionTime": "2017-05-04T01:40:32Z" "revisionTime": "2017-05-04T01:40:32Z"
}, },
{ {
"checksumSHA1": "ZKyEbJuIlvuZ9aUushINCXJHF4w=", "checksumSHA1": "fHOkQNWyeGoMPjkYvqiOrOifTUw=",
"path": "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens", "path": "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens",
"revision": "0bf921da554eacc1552a70204be7a1201937c1e1", "revision": "caf34a65f60295108141f62929245943bd00f237",
"revisionTime": "2017-05-04T01:40:32Z" "revisionTime": "2017-06-07T03:48:29Z"
}, },
{ {
"checksumSHA1": "TDOZnaS0TO0NirpxV1QwPerAQTY=", "checksumSHA1": "TDOZnaS0TO0NirpxV1QwPerAQTY=",
"path": "github.com/gophercloud/gophercloud/openstack/utils", "path": "github.com/gophercloud/gophercloud/openstack/utils",
"revision": "0bf921da554eacc1552a70204be7a1201937c1e1", "revision": "caf34a65f60295108141f62929245943bd00f237",
"revisionTime": "2017-05-04T01:40:32Z" "revisionTime": "2017-06-07T03:48:29Z"
}, },
{ {
"checksumSHA1": "FNy075ydQZXvnL2bNNIOCmy/ghs=", "checksumSHA1": "FNy075ydQZXvnL2bNNIOCmy/ghs=",
"path": "github.com/gophercloud/gophercloud/pagination", "path": "github.com/gophercloud/gophercloud/pagination",
"revision": "0bf921da554eacc1552a70204be7a1201937c1e1", "revision": "caf34a65f60295108141f62929245943bd00f237",
"revisionTime": "2017-05-04T01:40:32Z" "revisionTime": "2017-06-07T03:48:29Z"
}, },
{ {
"checksumSHA1": "LclVLJYrBi03PBjsVPpgoMbUDQ8=", "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 startTime = new Date().getTime();
var rangeSeconds = self.parseDuration(self.rangeInput.val()); 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; var endDate = self.getEndDate() / 1000;
if (self.queryXhr) { if (self.queryXhr) {

View file

@ -171,7 +171,7 @@ func New(o *Options) *Handler {
instrf := prometheus.InstrumentHandlerFunc instrf := prometheus.InstrumentHandlerFunc
router.Get("/", func(w http.ResponseWriter, r *http.Request) { 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)) router.Get("/alerts", instrf("alerts", h.alerts))