Merge pull request #1633 from simonpasquier/fix-v2-with-prefix
Fix route prefix for the API v2
This commit is contained in:
commit
2962039407
|
@ -129,6 +129,10 @@ func newMarkerMetrics(marker types.Marker) {
|
|||
const defaultClusterAddr = "0.0.0.0:9094"
|
||||
|
||||
func main() {
|
||||
os.Exit(run())
|
||||
}
|
||||
|
||||
func run() int {
|
||||
if os.Getenv("DEBUG") != "" {
|
||||
runtime.SetBlockProfileRate(20)
|
||||
runtime.SetMutexProfileFraction(20)
|
||||
|
@ -177,7 +181,7 @@ func main() {
|
|||
err := os.MkdirAll(*dataDir, 0777)
|
||||
if err != nil {
|
||||
level.Error(logger).Log("msg", "Unable to create data directory", "err", err)
|
||||
os.Exit(1)
|
||||
return 1
|
||||
}
|
||||
|
||||
var peer *cluster.Peer
|
||||
|
@ -197,7 +201,7 @@ func main() {
|
|||
)
|
||||
if err != nil {
|
||||
level.Error(logger).Log("msg", "unable to initialize gossip mesh", "err", err)
|
||||
os.Exit(1)
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -216,7 +220,7 @@ func main() {
|
|||
notificationLog, err := nflog.New(notificationLogOpts...)
|
||||
if err != nil {
|
||||
level.Error(logger).Log("err", err)
|
||||
os.Exit(1)
|
||||
return 1
|
||||
}
|
||||
if peer != nil {
|
||||
c := peer.AddState("nfl", notificationLog, prometheus.DefaultRegisterer)
|
||||
|
@ -236,7 +240,7 @@ func main() {
|
|||
silences, err := silence.New(silenceOpts)
|
||||
if err != nil {
|
||||
level.Error(logger).Log("err", err)
|
||||
os.Exit(1)
|
||||
return 1
|
||||
}
|
||||
if peer != nil {
|
||||
c := peer.AddState("sil", silences, prometheus.DefaultRegisterer)
|
||||
|
@ -277,7 +281,7 @@ func main() {
|
|||
alerts, err := mem.NewAlerts(context.Background(), marker, *alertGCInterval, logger)
|
||||
if err != nil {
|
||||
level.Error(logger).Log("err", err)
|
||||
os.Exit(1)
|
||||
return 1
|
||||
}
|
||||
defer alerts.Close()
|
||||
|
||||
|
@ -306,13 +310,13 @@ func main() {
|
|||
)
|
||||
if err != nil {
|
||||
level.Error(logger).Log("err", fmt.Errorf("failed to create API v2: %v", err.Error()))
|
||||
os.Exit(1)
|
||||
return 1
|
||||
}
|
||||
|
||||
amURL, err := extURL(*listenAddress, *externalURL)
|
||||
if err != nil {
|
||||
level.Error(logger).Log("err", err)
|
||||
os.Exit(1)
|
||||
return 1
|
||||
}
|
||||
|
||||
waitFunc := func() time.Duration { return 0 }
|
||||
|
@ -387,7 +391,7 @@ func main() {
|
|||
}
|
||||
|
||||
if err := reload(); err != nil {
|
||||
os.Exit(1)
|
||||
return 1
|
||||
}
|
||||
|
||||
// Make routePrefix default to externalURL path if empty string.
|
||||
|
@ -409,9 +413,30 @@ func main() {
|
|||
|
||||
apiV1.Register(router.WithPrefix("/api/v1"))
|
||||
|
||||
// TODO: How about having a http.handler for each (web, apiv1, apiv2) and
|
||||
// combine them all together in `listen()`
|
||||
go listen(*listenAddress, router, apiV2.Handler, logger)
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/", router)
|
||||
|
||||
apiPrefix := ""
|
||||
if *routePrefix != "/" {
|
||||
apiPrefix = *routePrefix
|
||||
}
|
||||
mux.Handle(apiPrefix+"/api/v2/", http.StripPrefix(apiPrefix+"/api/v2", apiV2.Handler))
|
||||
|
||||
srv := http.Server{Addr: *listenAddress, Handler: mux}
|
||||
srvc := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
level.Info(logger).Log("msg", "Listening", "address", *listenAddress)
|
||||
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
|
||||
level.Error(logger).Log("msg", "Listen error", "err", err)
|
||||
close(srvc)
|
||||
}
|
||||
defer func() {
|
||||
if err := srv.Close(); err != nil {
|
||||
level.Error(logger).Log("msg", "Error on closing the server", "err", err)
|
||||
}
|
||||
}()
|
||||
}()
|
||||
|
||||
var (
|
||||
hup = make(chan os.Signal, 1)
|
||||
|
@ -437,9 +462,15 @@ func main() {
|
|||
// Wait for reload or termination signals.
|
||||
close(hupReady) // Unblock SIGHUP handler.
|
||||
|
||||
<-term
|
||||
|
||||
level.Info(logger).Log("msg", "Received SIGTERM, exiting gracefully...")
|
||||
for {
|
||||
select {
|
||||
case <-term:
|
||||
level.Info(logger).Log("msg", "Received SIGTERM, exiting gracefully...")
|
||||
return 0
|
||||
case <-srvc:
|
||||
return 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// clusterWait returns a function that inspects the current peer state and returns
|
||||
|
@ -478,17 +509,6 @@ func extURL(listen, external string) (*url.URL, error) {
|
|||
return u, nil
|
||||
}
|
||||
|
||||
func listen(listen string, apiV1Handler *route.Router, apiV2Handler http.Handler, logger log.Logger) {
|
||||
level.Info(logger).Log("msg", "Listening", "address", listen)
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/", apiV1Handler)
|
||||
mux.Handle("/api/v2/", http.StripPrefix("/api/v2", apiV2Handler))
|
||||
if err := http.ListenAndServe(listen, mux); err != nil {
|
||||
level.Error(logger).Log("msg", "Listen error", "err", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func md5HashAsMetricValue(data []byte) float64 {
|
||||
sum := md5.Sum(data)
|
||||
// We only want 48 bits as a float64 only has a 53 bit mantissa.
|
||||
|
|
|
@ -50,8 +50,9 @@ type AcceptanceTest struct {
|
|||
|
||||
// AcceptanceOpts defines configuration paramters for an acceptance test.
|
||||
type AcceptanceOpts struct {
|
||||
Tolerance time.Duration
|
||||
baseTime time.Time
|
||||
RoutePrefix string
|
||||
Tolerance time.Duration
|
||||
baseTime time.Time
|
||||
}
|
||||
|
||||
func (opts *AcceptanceOpts) alertString(a *model.Alert) string {
|
||||
|
@ -135,7 +136,7 @@ func (t *AcceptanceTest) Alertmanager(conf string) *Alertmanager {
|
|||
t.Logf("AM on %s", am.apiAddr)
|
||||
|
||||
c, err := api.NewClient(api.Config{
|
||||
Address: fmt.Sprintf("http://%s", am.apiAddr),
|
||||
Address: am.getURL(""),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -257,14 +258,18 @@ type Alertmanager struct {
|
|||
|
||||
// Start the alertmanager and wait until it is ready to receive.
|
||||
func (am *Alertmanager) Start() {
|
||||
cmd := exec.Command("../../../alertmanager",
|
||||
args := []string{
|
||||
"--config.file", am.confFile.Name(),
|
||||
"--log.level", "debug",
|
||||
"--web.listen-address", am.apiAddr,
|
||||
"--storage.path", am.dir,
|
||||
"--cluster.listen-address", am.clusterAddr,
|
||||
"--cluster.settle-timeout", "0s",
|
||||
)
|
||||
}
|
||||
if am.opts.RoutePrefix != "" {
|
||||
args = append(args, "--web.route-prefix", am.opts.RoutePrefix)
|
||||
}
|
||||
cmd := exec.Command("../../../alertmanager", args...)
|
||||
|
||||
if am.cmd == nil {
|
||||
var outb, errb buffer
|
||||
|
@ -288,16 +293,21 @@ func (am *Alertmanager) Start() {
|
|||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
for i := 0; i < 10; i++ {
|
||||
resp, err := http.Get(fmt.Sprintf("http://%s/status", am.apiAddr))
|
||||
if err == nil {
|
||||
_, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
am.t.Fatalf("Starting alertmanager failed: %s", err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
return
|
||||
resp, err := http.Get(am.getURL("/"))
|
||||
if err != nil {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
am.t.Fatalf("Starting alertmanager failed: expected HTTP status '200', got '%d'", resp.StatusCode)
|
||||
}
|
||||
_, err = ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
am.t.Fatalf("Starting alertmanager failed: %s", err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
return
|
||||
}
|
||||
am.t.Fatalf("Starting alertmanager failed: timeout")
|
||||
}
|
||||
|
@ -363,7 +373,7 @@ func (am *Alertmanager) SetSilence(at float64, sil *TestSilence) {
|
|||
return
|
||||
}
|
||||
|
||||
resp, err := http.Post(fmt.Sprintf("http://%s/api/v1/silences", am.apiAddr), "application/json", &buf)
|
||||
resp, err := http.Post(am.getURL("/api/v1/silences"), "application/json", &buf)
|
||||
if err != nil {
|
||||
am.t.Errorf("Error setting silence %v: %s", sil, err)
|
||||
return
|
||||
|
@ -392,7 +402,7 @@ func (am *Alertmanager) SetSilence(at float64, sil *TestSilence) {
|
|||
// DelSilence deletes the silence with the sid at the given time.
|
||||
func (am *Alertmanager) DelSilence(at float64, sil *TestSilence) {
|
||||
am.t.Do(at, func() {
|
||||
req, err := http.NewRequest("DELETE", fmt.Sprintf("http://%s/api/v1/silence/%s", am.apiAddr, sil.ID()), nil)
|
||||
req, err := http.NewRequest("DELETE", am.getURL(fmt.Sprintf("/api/v1/silence/%s", sil.ID())), nil)
|
||||
if err != nil {
|
||||
am.t.Errorf("Error deleting silence %v: %s", sil, err)
|
||||
return
|
||||
|
@ -418,3 +428,7 @@ func (am *Alertmanager) UpdateConfig(conf string) {
|
|||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (am *Alertmanager) getURL(path string) string {
|
||||
return fmt.Sprintf("http://%s%s%s", am.apiAddr, am.opts.RoutePrefix, path)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright 2018 Prometheus Team
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
a "github.com/prometheus/alertmanager/test/with_api_v1"
|
||||
)
|
||||
|
||||
func TestWebWithPrefix(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
conf := `
|
||||
route:
|
||||
receiver: "default"
|
||||
group_by: []
|
||||
group_wait: 1s
|
||||
group_interval: 1s
|
||||
repeat_interval: 1h
|
||||
|
||||
receivers:
|
||||
- name: "default"
|
||||
`
|
||||
|
||||
// The test framework polls the API with the given prefix during
|
||||
// Alertmanager startup and thereby ensures proper configuration.
|
||||
at := a.NewAcceptanceTest(t, &a.AcceptanceOpts{RoutePrefix: "/foo"})
|
||||
at.Alertmanager(conf)
|
||||
at.Run()
|
||||
}
|
|
@ -53,8 +53,9 @@ type AcceptanceTest struct {
|
|||
|
||||
// AcceptanceOpts defines configuration paramters for an acceptance test.
|
||||
type AcceptanceOpts struct {
|
||||
Tolerance time.Duration
|
||||
baseTime time.Time
|
||||
RoutePrefix string
|
||||
Tolerance time.Duration
|
||||
baseTime time.Time
|
||||
}
|
||||
|
||||
func (opts *AcceptanceOpts) alertString(a *models.Alert) string {
|
||||
|
@ -141,7 +142,7 @@ func (t *AcceptanceTest) AlertmanagerCluster(conf string, size int) *Alertmanage
|
|||
am.apiAddr = freeAddress()
|
||||
am.clusterAddr = freeAddress()
|
||||
|
||||
transport := httptransport.New(am.apiAddr, "/api/v2/", nil)
|
||||
transport := httptransport.New(am.apiAddr, t.opts.RoutePrefix+"/api/v2/", nil)
|
||||
am.clientV2 = apiclient.New(transport, strfmt.Default)
|
||||
|
||||
amc.ams = append(amc.ams, am)
|
||||
|
@ -290,6 +291,7 @@ func (amc *AlertmanagerCluster) Start() error {
|
|||
|
||||
// Start the alertmanager and wait until it is ready to receive.
|
||||
func (am *Alertmanager) Start(additionalArg []string) error {
|
||||
am.t.Helper()
|
||||
args := []string{
|
||||
"--config.file", am.confFile.Name(),
|
||||
"--log.level", "debug",
|
||||
|
@ -298,6 +300,9 @@ func (am *Alertmanager) Start(additionalArg []string) error {
|
|||
"--cluster.listen-address", am.clusterAddr,
|
||||
"--cluster.settle-timeout", "0s",
|
||||
}
|
||||
if am.opts.RoutePrefix != "" {
|
||||
args = append(args, "--web.route-prefix", am.opts.RoutePrefix)
|
||||
}
|
||||
args = append(args, additionalArg...)
|
||||
|
||||
cmd := exec.Command("../../../alertmanager", args...)
|
||||
|
@ -324,16 +329,20 @@ func (am *Alertmanager) Start(additionalArg []string) error {
|
|||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
for i := 0; i < 10; i++ {
|
||||
resp, err := http.Get(fmt.Sprintf("http://%s/status", am.apiAddr))
|
||||
if err == nil {
|
||||
_, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("starting alertmanager failed: %s", err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
resp, err := http.Get(am.getURL("/"))
|
||||
if err != nil {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("starting alertmanager failed: expected HTTP status '200', got '%d'", resp.StatusCode)
|
||||
}
|
||||
_, err = ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("starting alertmanager failed: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("starting alertmanager failed: timeout")
|
||||
}
|
||||
|
@ -377,6 +386,7 @@ func (amc *AlertmanagerCluster) Terminate() {
|
|||
// Terminate kills the underlying Alertmanager process and remove intermediate
|
||||
// data.
|
||||
func (am *Alertmanager) Terminate() {
|
||||
am.t.Helper()
|
||||
if err := syscall.Kill(am.cmd.Process.Pid, syscall.SIGTERM); err != nil {
|
||||
am.t.Fatalf("Error sending SIGTERM to Alertmanager process: %v", err)
|
||||
}
|
||||
|
@ -391,18 +401,14 @@ func (amc *AlertmanagerCluster) Reload() {
|
|||
|
||||
// Reload sends the reloading signal to the Alertmanager process.
|
||||
func (am *Alertmanager) Reload() {
|
||||
am.t.Helper()
|
||||
if err := syscall.Kill(am.cmd.Process.Pid, syscall.SIGHUP); err != nil {
|
||||
am.t.Fatalf("Error sending SIGHUP to Alertmanager process: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (amc *AlertmanagerCluster) cleanup() {
|
||||
for _, am := range amc.ams {
|
||||
am.cleanup()
|
||||
}
|
||||
}
|
||||
|
||||
func (am *Alertmanager) cleanup() {
|
||||
am.t.Helper()
|
||||
if err := os.RemoveAll(am.confFile.Name()); err != nil {
|
||||
am.t.Errorf("Error removing test config file %q: %v", am.confFile.Name(), err)
|
||||
}
|
||||
|
@ -458,7 +464,7 @@ func (am *Alertmanager) SetSilence(at float64, sil *TestSilence) {
|
|||
return
|
||||
}
|
||||
|
||||
resp, err := http.Post(fmt.Sprintf("http://%s/api/v1/silences", am.apiAddr), "application/json", &buf)
|
||||
resp, err := http.Post(am.getURL("/api/v1/silences"), "application/json", &buf)
|
||||
if err != nil {
|
||||
am.t.Errorf("Error setting silence %v: %s", sil, err)
|
||||
return
|
||||
|
@ -494,7 +500,7 @@ func (amc *AlertmanagerCluster) DelSilence(at float64, sil *TestSilence) {
|
|||
// DelSilence deletes the silence with the sid at the given time.
|
||||
func (am *Alertmanager) DelSilence(at float64, sil *TestSilence) {
|
||||
am.t.Do(at, func() {
|
||||
req, err := http.NewRequest("DELETE", fmt.Sprintf("http://%s/api/v1/silence/%s", am.apiAddr, sil.ID()), nil)
|
||||
req, err := http.NewRequest("DELETE", am.getURL(fmt.Sprintf("/api/v1/silence/%s", sil.ID())), nil)
|
||||
if err != nil {
|
||||
am.t.Errorf("Error deleting silence %v: %s", sil, err)
|
||||
return
|
||||
|
@ -541,3 +547,7 @@ func (amc *AlertmanagerCluster) GenericAPIV2Call(at float64, f func()) {
|
|||
func (am *Alertmanager) GenericAPIV2Call(at float64, f func()) {
|
||||
am.t.Do(at, f)
|
||||
}
|
||||
|
||||
func (am *Alertmanager) getURL(path string) string {
|
||||
return fmt.Sprintf("http://%s%s%s", am.apiAddr, am.opts.RoutePrefix, path)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright 2018 Prometheus Team
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
a "github.com/prometheus/alertmanager/test/with_api_v2"
|
||||
)
|
||||
|
||||
func TestWebWithPrefix(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
conf := `
|
||||
route:
|
||||
receiver: "default"
|
||||
group_by: []
|
||||
group_wait: 1s
|
||||
group_interval: 1s
|
||||
repeat_interval: 1h
|
||||
|
||||
receivers:
|
||||
- name: "default"
|
||||
`
|
||||
|
||||
// The test framework polls the API with the given prefix during
|
||||
// Alertmanager startup and thereby ensures proper configuration.
|
||||
at := a.NewAcceptanceTest(t, &a.AcceptanceOpts{RoutePrefix: "/foo"})
|
||||
at.AlertmanagerCluster(conf, 1)
|
||||
at.Run()
|
||||
}
|
Loading…
Reference in New Issue