diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7d801fceb..2e18ae527 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@
- (Bugfix) Fix Image Error Propagation
- (Feature) JobScheduler Coverage
- (Feature) JobScheduler Volumes, Probes, Lifecycle and Ports integration
+- (Feature) Merge ArangoDB Usage Metrics
## [1.2.38](https://github.com/arangodb/kube-arangodb/tree/1.2.38) (2024-02-22)
- (Feature) Extract GRPC Server
diff --git a/cmd/exporter.go b/cmd/exporter.go
index cb0a39098..3b23f7e6c 100644
--- a/cmd/exporter.go
+++ b/cmd/exporter.go
@@ -1,7 +1,7 @@
//
// DISCLAIMER
//
-// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
+// Copyright 2016-2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -32,6 +32,7 @@ import (
"github.com/arangodb/kube-arangodb/pkg/exporter"
"github.com/arangodb/kube-arangodb/pkg/util"
+ "github.com/arangodb/kube-arangodb/pkg/util/errors"
)
var (
@@ -43,9 +44,9 @@ var (
exporterInput struct {
listenAddress string
- endpoint string
- jwtFile string
- timeout time.Duration
+ endpoints []string
+ jwtFile string
+ timeout time.Duration
keyfile string
}
@@ -57,7 +58,7 @@ func init() {
f.StringVar(&exporterInput.listenAddress, "server.address", ":9101", "Address the exporter will listen on (IP:port)")
f.StringVar(&exporterInput.keyfile, "ssl.keyfile", "", "File containing TLS certificate used for the metrics server. Format equal to ArangoDB keyfiles")
- f.StringVar(&exporterInput.endpoint, "arangodb.endpoint", "http://127.0.0.1:8529", "Endpoint used to reach the ArangoDB server")
+ f.StringSliceVar(&exporterInput.endpoints, "arangodb.endpoint", []string{"http://127.0.0.1:8529"}, "Endpoints used to reach the ArangoDB server")
f.StringVar(&exporterInput.jwtFile, "arangodb.jwt-file", "", "File containing the JWT for authentication with ArangoDB server")
f.DurationVar(&exporterInput.timeout, "arangodb.timeout", time.Second*15, "Timeout of statistics requests for ArangoDB")
@@ -83,7 +84,11 @@ func onSigterm(f func()) {
}
func cmdExporterCheckE() error {
- p, err := exporter.NewPassthru(exporterInput.endpoint, func() (string, error) {
+ if len(exporterInput.endpoints) < 1 {
+ return errors.Errorf("Requires at least one ArangoDB Endpoint to be present")
+ }
+
+ p, err := exporter.NewPassthru(func() (string, error) {
if exporterInput.jwtFile == "" {
return "", nil
}
@@ -94,12 +99,12 @@ func cmdExporterCheckE() error {
}
return string(data), nil
- }, false, 15*time.Second)
+ }, false, 15*time.Second, exporterInput.endpoints...)
if err != nil {
return err
}
- mon := exporter.NewMonitor(exporterInput.endpoint, func() (string, error) {
+ mon := exporter.NewMonitor(exporterInput.endpoints[0], func() (string, error) {
if exporterInput.jwtFile == "" {
return "", nil
}
diff --git a/docs/api/ArangoDeployment.V1.md b/docs/api/ArangoDeployment.V1.md
index 50757ef0a..97334f398 100644
--- a/docs/api/ArangoDeployment.V1.md
+++ b/docs/api/ArangoDeployment.V1.md
@@ -3405,6 +3405,22 @@ Default Value: `false`
***
+### .spec.metrics.extensions.usageMetrics
+
+Type: `boolean` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.38/pkg/apis/deployment/v1/deployment_metrics_spec_extensions.go#L29)
+
+> [!IMPORTANT]
+> **UsageMetrics needs to be also enabled via DBServer Arguments**
+
+UsageMetrics enables ArangoDB Usage metrics scrape. Affects only DBServers in the Cluster mode.
+
+Links:
+* [Documentation](https://docs.arangodb.com/devel/develop/http-api/monitoring/metrics/#get-usage-metrics)
+
+Default Value: `false`
+
+***
+
### .spec.metrics.image
Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.38/pkg/apis/deployment/v1/deployment_metrics_spec.go#L86)
diff --git a/pkg/apis/deployment/v1/deployment_metrics_spec.go b/pkg/apis/deployment/v1/deployment_metrics_spec.go
index f9394ade1..322a0edb9 100644
--- a/pkg/apis/deployment/v1/deployment_metrics_spec.go
+++ b/pkg/apis/deployment/v1/deployment_metrics_spec.go
@@ -105,6 +105,9 @@ type MetricsSpec struct {
ServiceMonitor *MetricsServiceMonitorSpec `json:"serviceMonitor,omitempty"`
Port *uint16 `json:"port,omitempty"`
+
+ // Extensions keeps the information about Metrics Extensions
+ Extensions *MetricsSpecExtensions `json:"extensions,omitempty"`
}
func (s *MetricsSpec) IsTLS() bool {
@@ -123,6 +126,14 @@ func (s *MetricsSpec) GetPort() uint16 {
return *s.Port
}
+func (s *MetricsSpec) GetExtensions() *MetricsSpecExtensions {
+ if s == nil || s.Extensions == nil {
+ return nil
+ }
+
+ return s.Extensions
+}
+
// IsEnabled returns whether metrics are enabled or not
func (s *MetricsSpec) IsEnabled() bool {
return util.TypeOrDefault[bool](s.Enabled, false)
diff --git a/pkg/apis/deployment/v1/deployment_metrics_spec_extensions.go b/pkg/apis/deployment/v1/deployment_metrics_spec_extensions.go
new file mode 100644
index 000000000..6b3f2c010
--- /dev/null
+++ b/pkg/apis/deployment/v1/deployment_metrics_spec_extensions.go
@@ -0,0 +1,38 @@
+//
+// DISCLAIMER
+//
+// Copyright 2024 ArangoDB GmbH, Cologne, Germany
+//
+// 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.
+//
+// Copyright holder is ArangoDB GmbH, Cologne, Germany
+//
+
+package v1
+
+// MetricsSpecExtensions defines enabled extensions for MetricsExporter
+type MetricsSpecExtensions struct {
+ // UsageMetrics enables ArangoDB Usage metrics scrape. Affects only DBServers in the Cluster mode.
+ // +doc/default: false
+ // +doc/link: Documentation|https://docs.arangodb.com/devel/develop/http-api/monitoring/metrics/#get-usage-metrics
+ // +doc/important: UsageMetrics needs to be also enabled via DBServer Arguments
+ UsageMetrics *bool `json:"usageMetrics,omitempty"`
+}
+
+func (m *MetricsSpecExtensions) GetUsageMetrics() bool {
+ if m == nil || m.UsageMetrics == nil {
+ return false
+ }
+
+ return *m.UsageMetrics
+}
diff --git a/pkg/apis/deployment/v1/zz_generated.deepcopy.go b/pkg/apis/deployment/v1/zz_generated.deepcopy.go
index 74c9a2900..2b9c00f0f 100644
--- a/pkg/apis/deployment/v1/zz_generated.deepcopy.go
+++ b/pkg/apis/deployment/v1/zz_generated.deepcopy.go
@@ -1958,6 +1958,11 @@ func (in *MetricsSpec) DeepCopyInto(out *MetricsSpec) {
*out = new(uint16)
**out = **in
}
+ if in.Extensions != nil {
+ in, out := &in.Extensions, &out.Extensions
+ *out = new(MetricsSpecExtensions)
+ (*in).DeepCopyInto(*out)
+ }
return
}
@@ -1971,6 +1976,27 @@ func (in *MetricsSpec) DeepCopy() *MetricsSpec {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *MetricsSpecExtensions) DeepCopyInto(out *MetricsSpecExtensions) {
+ *out = *in
+ if in.UsageMetrics != nil {
+ in, out := &in.UsageMetrics, &out.UsageMetrics
+ *out = new(bool)
+ **out = **in
+ }
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricsSpecExtensions.
+func (in *MetricsSpecExtensions) DeepCopy() *MetricsSpecExtensions {
+ if in == nil {
+ return nil
+ }
+ out := new(MetricsSpecExtensions)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MonitoringSpec) DeepCopyInto(out *MonitoringSpec) {
*out = *in
diff --git a/pkg/apis/deployment/v2alpha1/deployment_metrics_spec.go b/pkg/apis/deployment/v2alpha1/deployment_metrics_spec.go
index ef9a470dc..f9cca38fc 100644
--- a/pkg/apis/deployment/v2alpha1/deployment_metrics_spec.go
+++ b/pkg/apis/deployment/v2alpha1/deployment_metrics_spec.go
@@ -105,6 +105,9 @@ type MetricsSpec struct {
ServiceMonitor *MetricsServiceMonitorSpec `json:"serviceMonitor,omitempty"`
Port *uint16 `json:"port,omitempty"`
+
+ // Extensions keeps the information about Metrics Extensions
+ Extensions *MetricsSpecExtensions `json:"extensions,omitempty"`
}
func (s *MetricsSpec) IsTLS() bool {
@@ -123,6 +126,14 @@ func (s *MetricsSpec) GetPort() uint16 {
return *s.Port
}
+func (s *MetricsSpec) GetExtensions() *MetricsSpecExtensions {
+ if s == nil || s.Extensions == nil {
+ return nil
+ }
+
+ return s.Extensions
+}
+
// IsEnabled returns whether metrics are enabled or not
func (s *MetricsSpec) IsEnabled() bool {
return util.TypeOrDefault[bool](s.Enabled, false)
diff --git a/pkg/apis/deployment/v2alpha1/deployment_metrics_spec_extensions.go b/pkg/apis/deployment/v2alpha1/deployment_metrics_spec_extensions.go
new file mode 100644
index 000000000..453b78528
--- /dev/null
+++ b/pkg/apis/deployment/v2alpha1/deployment_metrics_spec_extensions.go
@@ -0,0 +1,38 @@
+//
+// DISCLAIMER
+//
+// Copyright 2024 ArangoDB GmbH, Cologne, Germany
+//
+// 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.
+//
+// Copyright holder is ArangoDB GmbH, Cologne, Germany
+//
+
+package v2alpha1
+
+// MetricsSpecExtensions defines enabled extensions for MetricsExporter
+type MetricsSpecExtensions struct {
+ // UsageMetrics enables ArangoDB Usage metrics scrape. Affects only DBServers in the Cluster mode.
+ // +doc/default: false
+ // +doc/link: Documentation|https://docs.arangodb.com/devel/develop/http-api/monitoring/metrics/#get-usage-metrics
+ // +doc/important: UsageMetrics needs to be also enabled via DBServer Arguments
+ UsageMetrics *bool `json:"usageMetrics,omitempty"`
+}
+
+func (m *MetricsSpecExtensions) GetUsageMetrics() bool {
+ if m == nil || m.UsageMetrics == nil {
+ return false
+ }
+
+ return *m.UsageMetrics
+}
diff --git a/pkg/apis/deployment/v2alpha1/zz_generated.deepcopy.go b/pkg/apis/deployment/v2alpha1/zz_generated.deepcopy.go
index 06abb3604..d68e718fe 100644
--- a/pkg/apis/deployment/v2alpha1/zz_generated.deepcopy.go
+++ b/pkg/apis/deployment/v2alpha1/zz_generated.deepcopy.go
@@ -1958,6 +1958,11 @@ func (in *MetricsSpec) DeepCopyInto(out *MetricsSpec) {
*out = new(uint16)
**out = **in
}
+ if in.Extensions != nil {
+ in, out := &in.Extensions, &out.Extensions
+ *out = new(MetricsSpecExtensions)
+ (*in).DeepCopyInto(*out)
+ }
return
}
@@ -1971,6 +1976,27 @@ func (in *MetricsSpec) DeepCopy() *MetricsSpec {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *MetricsSpecExtensions) DeepCopyInto(out *MetricsSpecExtensions) {
+ *out = *in
+ if in.UsageMetrics != nil {
+ in, out := &in.UsageMetrics, &out.UsageMetrics
+ *out = new(bool)
+ **out = **in
+ }
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricsSpecExtensions.
+func (in *MetricsSpecExtensions) DeepCopy() *MetricsSpecExtensions {
+ if in == nil {
+ return nil
+ }
+ out := new(MetricsSpecExtensions)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MonitoringSpec) DeepCopyInto(out *MonitoringSpec) {
*out = *in
diff --git a/pkg/apis/shared/constants.go b/pkg/apis/shared/constants.go
index 17c6e2eae..13a6cc049 100644
--- a/pkg/apis/shared/constants.go
+++ b/pkg/apis/shared/constants.go
@@ -31,6 +31,7 @@ const (
ArangoExporterClusterHealthEndpoint = "/_admin/cluster/health"
ArangoExporterInternalEndpoint = "/_admin/metrics"
ArangoExporterInternalEndpointV2 = "/_admin/metrics/v2"
+ ArangoExporterUsageEndpoint = "/_admin/usage-metrics"
ArangoExporterDefaultEndpoint = "/metrics"
ArangoSyncStatusEndpoint = "/_api/version"
diff --git a/pkg/crd/crds/database-deployment.schema.generated.yaml b/pkg/crd/crds/database-deployment.schema.generated.yaml
index 83abaf135..015bd85da 100644
--- a/pkg/crd/crds/database-deployment.schema.generated.yaml
+++ b/pkg/crd/crds/database-deployment.schema.generated.yaml
@@ -6917,6 +6917,13 @@ v1:
Enabled if this is set to `true`, the operator runs a sidecar container for
every Agent, DB-Server, Coordinator and Single server.
type: boolean
+ extensions:
+ description: Extensions keeps the information about Metrics Extensions
+ properties:
+ usageMetrics:
+ description: UsageMetrics enables ArangoDB Usage metrics scrape. Affects only DBServers in the Cluster mode.
+ type: boolean
+ type: object
image:
description: Image used for the Metrics Sidecar
type: string
@@ -20398,6 +20405,13 @@ v1alpha:
Enabled if this is set to `true`, the operator runs a sidecar container for
every Agent, DB-Server, Coordinator and Single server.
type: boolean
+ extensions:
+ description: Extensions keeps the information about Metrics Extensions
+ properties:
+ usageMetrics:
+ description: UsageMetrics enables ArangoDB Usage metrics scrape. Affects only DBServers in the Cluster mode.
+ type: boolean
+ type: object
image:
description: Image used for the Metrics Sidecar
type: string
@@ -33879,6 +33893,13 @@ v2alpha1:
Enabled if this is set to `true`, the operator runs a sidecar container for
every Agent, DB-Server, Coordinator and Single server.
type: boolean
+ extensions:
+ description: Extensions keeps the information about Metrics Extensions
+ properties:
+ usageMetrics:
+ description: UsageMetrics enables ArangoDB Usage metrics scrape. Affects only DBServers in the Cluster mode.
+ type: boolean
+ type: object
image:
description: Image used for the Metrics Sidecar
type: string
diff --git a/pkg/deployment/resources/exporter.go b/pkg/deployment/resources/exporter.go
index 040b27759..0ad45aaa9 100644
--- a/pkg/deployment/resources/exporter.go
+++ b/pkg/deployment/resources/exporter.go
@@ -1,5 +1,5 @@
//
-// Copyright 2016-2023 ArangoDB GmbH, Cologne, Germany
+// Copyright 2016-2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -30,7 +30,7 @@ import (
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil/probes"
)
-func createInternalExporterArgs(spec api.DeploymentSpec, groupSpec api.ServerGroupSpec, version driver.Version) []string {
+func createInternalExporterArgs(spec api.DeploymentSpec, group api.ServerGroup, groupSpec api.ServerGroupSpec, version driver.Version) []string {
tokenpath := filepath.Join(shared.ExporterJWTVolumeMountDir, constants.SecretKeyToken)
options := k8sutil.CreateOptionPairs(64)
@@ -46,8 +46,14 @@ func createInternalExporterArgs(spec api.DeploymentSpec, groupSpec api.ServerGro
scheme = "https"
}
options.Addf("--arangodb.endpoint", "%s://localhost:%d%s", scheme, groupSpec.GetPort(), path)
+ if spec.Metrics.GetExtensions().GetUsageMetrics() && group == api.ServerGroupDBServers {
+ options.Addf("--arangodb.endpoint", "%s://localhost:%d%s", scheme, groupSpec.GetPort(), shared.ArangoExporterUsageEndpoint)
+ }
} else {
options.Addf("--arangodb.endpoint", "http://localhost:%d%s", *port, path)
+ if spec.Metrics.GetExtensions().GetUsageMetrics() && group == api.ServerGroupDBServers {
+ options.Addf("--arangodb.endpoint", "http://localhost:%d%s", groupSpec.GetPort(), shared.ArangoExporterUsageEndpoint)
+ }
}
keyPath := filepath.Join(shared.TLSKeyfileVolumeMountDir, constants.SecretTLSKeyfile)
diff --git a/pkg/deployment/resources/internal_exporter.go b/pkg/deployment/resources/internal_exporter.go
index 08943c68a..47303f3ac 100644
--- a/pkg/deployment/resources/internal_exporter.go
+++ b/pkg/deployment/resources/internal_exporter.go
@@ -1,5 +1,5 @@
//
-// Copyright 2016-2023 ArangoDB GmbH, Cologne, Germany
+// Copyright 2016-2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -50,7 +50,7 @@ func ArangodbInternalExporterContainer(image string, args []string, livenessProb
Command: append([]string{exePath, "exporter"}, args...),
Ports: []core.ContainerPort{
{
- Name: "exporter",
+ Name: shared.ExporterContainerName,
ContainerPort: int32(port),
Protocol: core.ProtocolTCP,
},
diff --git a/pkg/deployment/resources/pod_creator_arangod.go b/pkg/deployment/resources/pod_creator_arangod.go
index 5ffa35c07..1de9c2297 100644
--- a/pkg/deployment/resources/pod_creator_arangod.go
+++ b/pkg/deployment/resources/pod_creator_arangod.go
@@ -561,7 +561,7 @@ func (m *MemberArangoDPod) GetRestartPolicy() core.RestartPolicy {
func (m *MemberArangoDPod) createMetricsExporterSidecarInternalExporter() (*core.Container, error) {
image := m.GetContainerCreator().GetImage()
- args := createInternalExporterArgs(m.spec, m.groupSpec, m.imageInfo.ArangoDBVersion)
+ args := createInternalExporterArgs(m.spec, m.group, m.groupSpec, m.imageInfo.ArangoDBVersion)
c, err := ArangodbInternalExporterContainer(image, args,
createExporterLivenessProbe(m.spec.IsSecure() && m.spec.Metrics.IsTLS()), m.spec.Metrics.Resources,
diff --git a/pkg/deployment/resources/secrets.go b/pkg/deployment/resources/secrets.go
index 5998b4b61..63983c46e 100644
--- a/pkg/deployment/resources/secrets.go
+++ b/pkg/deployment/resources/secrets.go
@@ -426,8 +426,11 @@ var (
token.ClaimISS: token.ClaimISSValue,
"server_id": "exporter",
"allowed_paths": []interface{}{"/_admin/statistics", "/_admin/statistics-description",
- shared.ArangoExporterInternalEndpoint, shared.ArangoExporterInternalEndpointV2,
- shared.ArangoExporterStatusEndpoint, shared.ArangoExporterClusterHealthEndpoint},
+ shared.ArangoExporterInternalEndpoint,
+ shared.ArangoExporterInternalEndpointV2,
+ shared.ArangoExporterUsageEndpoint,
+ shared.ArangoExporterStatusEndpoint,
+ shared.ArangoExporterClusterHealthEndpoint},
}
)
@@ -449,6 +452,15 @@ func (r *Resources) ensureExporterTokenSecret(ctx context.Context, cachedStatus
// Failed to create secret
return errors.WithStack(err)
}
+ } else {
+ err = k8sutil.UpdateJWTFromSecret(ctx, cachedStatus.Secret().V1().Read(), secrets, tokenSecretName, secretSecretName, exporterTokenClaims)
+ if kerrors.IsAlreadyExists(err) {
+ // Secret added while we tried it also
+ return nil
+ } else if err != nil {
+ // Failed to create secret
+ return errors.WithStack(err)
+ }
}
return errors.Reconcile()
diff --git a/pkg/exporter/monitor.go b/pkg/exporter/monitor.go
index b9932938e..511d5b9cb 100644
--- a/pkg/exporter/monitor.go
+++ b/pkg/exporter/monitor.go
@@ -42,7 +42,7 @@ import (
)
const (
- monitorMetricTemplate = "arangodb_member_health{role=\"%s\",id=\"%s\"} %d \n"
+ monitorMetricTemplate = "arangodb_member_health{role=\"%s\",id=\"%s\"} %d\n"
successRefreshInterval = time.Second * 120
failRefreshInterval = time.Second * 15
)
@@ -59,7 +59,8 @@ func NewMonitor(arangodbEndpoint string, auth Authentication, sslVerify bool, ti
}
return &monitor{
- factory: newHttpClientFactory(arangodbEndpoint, auth, sslVerify, timeout),
+ factory: newHttpClientFactory(auth, sslVerify, timeout),
+ endpoint: arangodbEndpoint,
healthURI: uri,
}
}
@@ -67,6 +68,7 @@ func NewMonitor(arangodbEndpoint string, auth Authentication, sslVerify bool, ti
type monitor struct {
factory httpClientFactory
healthURI *url.URL
+ endpoint string
}
// UpdateMonitorStatus load monitor metrics for current cluster into currentMembersStatus
@@ -102,7 +104,7 @@ func (m monitor) UpdateMonitorStatus(ctx context.Context) {
// GetClusterHealth returns current ArangoDeployment cluster health status
func (m monitor) GetClusterHealth() (*driver.ClusterHealth, error) {
- c, req, err := m.factory()
+ c, req, err := m.factory(m.endpoint)
if err != nil {
return nil, err
}
@@ -130,7 +132,7 @@ func (m monitor) GetClusterHealth() (*driver.ClusterHealth, error) {
func (m monitor) GetMemberStatus(id driver.ServerID, member driver.ServerHealth) (string, error) {
result := fmt.Sprintf(monitorMetricTemplate, member.Role, id, 0)
- c, req, err := m.factory()
+ c, req, err := m.factory(m.endpoint)
if err != nil {
return result, err
}
diff --git a/pkg/exporter/passthru.go b/pkg/exporter/passthru.go
index 54b064172..69b6f31ce 100644
--- a/pkg/exporter/passthru.go
+++ b/pkg/exporter/passthru.go
@@ -1,7 +1,7 @@
//
// DISCLAIMER
//
-// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
+// Copyright 2016-2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@ import (
"io"
"net/http"
"strings"
+ "sync"
"time"
"github.com/arangodb/kube-arangodb/pkg/util/errors"
@@ -32,19 +33,20 @@ import (
var _ http.Handler = &passthru{}
-func NewPassthru(arangodbEndpoint string, auth Authentication, sslVerify bool, timeout time.Duration) (http.Handler, error) {
+func NewPassthru(auth Authentication, sslVerify bool, timeout time.Duration, endpoints ...string) (http.Handler, error) {
return &passthru{
- factory: newHttpClientFactory(arangodbEndpoint, auth, sslVerify, timeout),
+ factory: newHttpClientFactory(auth, sslVerify, timeout),
+ endpoints: endpoints,
}, nil
}
-type httpClientFactory func() (*http.Client, *http.Request, error)
+type httpClientFactory func(endpoint string) (*http.Client, *http.Request, error)
-func newHttpClientFactory(arangodbEndpoint string, auth Authentication, sslVerify bool, timeout time.Duration) httpClientFactory {
- return func() (*http.Client, *http.Request, error) {
+func newHttpClientFactory(auth Authentication, sslVerify bool, timeout time.Duration) httpClientFactory {
+ return func(endpoint string) (*http.Client, *http.Request, error) {
transport := &http.Transport{}
- req, err := http.NewRequest("GET", arangodbEndpoint, nil)
+ req, err := http.NewRequest("GET", endpoint, nil)
if err != nil {
return nil, nil, errors.WithStack(err)
}
@@ -78,37 +80,79 @@ func newHttpClientFactory(arangodbEndpoint string, auth Authentication, sslVerif
}
type passthru struct {
- factory httpClientFactory
+ endpoints []string
+ factory httpClientFactory
}
-func (p passthru) get() (*http.Response, error) {
- c, req, err := p.factory()
+func (p passthru) get(endpoint string) (*http.Response, error) {
+ c, req, err := p.factory(endpoint)
if err != nil {
return nil, err
}
return c.Do(req)
}
-func (p passthru) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
- data, err := p.get()
+func (p passthru) read(endpoint string) (string, error) {
+ data, err := p.get(endpoint)
if err != nil {
- // Ignore error
- resp.WriteHeader(http.StatusInternalServerError)
- resp.Write([]byte(err.Error()))
- return
+ return "", err
}
if data.Body == nil {
- // Ignore error
- resp.WriteHeader(http.StatusInternalServerError)
- resp.Write([]byte("Body is empty"))
- return
+ return "", err
}
defer data.Body.Close()
response, err := io.ReadAll(data.Body)
+ if err != nil {
+ return "", err
+ }
+
+ responseStr := string(response)
+
+ // Fix Header response
+ return strings.ReplaceAll(responseStr, "guage", "gauge"), nil
+}
+
+func (p passthru) getAll() (string, error) {
+ errs := make([]error, len(p.endpoints))
+ responses := make([]string, len(p.endpoints))
+
+ var wg sync.WaitGroup
+
+ for id := range p.endpoints {
+ wg.Add(1)
+
+ go func(id int) {
+ defer wg.Done()
+ responses[id], errs[id] = p.read(p.endpoints[id])
+ }(id)
+ }
+
+ wg.Wait()
+
+ for _, err := range errs {
+ if err != nil {
+ return "", errors.WithStack(err)
+ }
+ }
+
+ response := strings.Join(responses, "\n")
+
+ // Attach monitor data
+ monitorData := currentMembersStatus.Load()
+ if monitorData != nil {
+ response = response + monitorData.(string)
+ }
+
+ return response, nil
+}
+
+func (p passthru) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
+ response, err := p.getAll()
+
if err != nil {
// Ignore error
resp.WriteHeader(http.StatusInternalServerError)
@@ -116,19 +160,8 @@ func (p passthru) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
return
}
- responseStr := string(response)
-
- // Fix Header response
- responseStr = strings.ReplaceAll(responseStr, "guage", "gauge")
-
- // Attach monitor data
- monitorData := currentMembersStatus.Load()
- if monitorData != nil {
- responseStr = responseStr + monitorData.(string)
- }
-
- resp.WriteHeader(data.StatusCode)
- _, err = resp.Write([]byte(responseStr))
+ resp.WriteHeader(http.StatusOK)
+ _, err = resp.Write([]byte(response))
if err != nil {
// Ignore error
resp.WriteHeader(http.StatusInternalServerError)
diff --git a/pkg/util/k8sutil/secrets.go b/pkg/util/k8sutil/secrets.go
index 865da02f5..6dfb68146 100644
--- a/pkg/util/k8sutil/secrets.go
+++ b/pkg/util/k8sutil/secrets.go
@@ -295,6 +295,19 @@ func CreateTokenSecret(ctx context.Context, secrets secretv1.ModInterface, secre
return nil
}
+// UpdateTokenSecret updates a secret with given name in given namespace
+// with a given token as value.
+func UpdateTokenSecret(ctx context.Context, secrets secretv1.ModInterface, secret *core.Secret, token string) error {
+ secret.Data = map[string][]byte{
+ constants.SecretKeyToken: []byte(token),
+ }
+ if _, err := secrets.Update(ctx, secret, meta.UpdateOptions{}); err != nil {
+ // Failed to update secret
+ return kerrors.NewResourceError(err, secret)
+ }
+ return nil
+}
+
// CreateJWTFromSecret creates a JWT using the secret stored in secretSecretName and stores the
// result in a new secret called tokenSecretName
func CreateJWTFromSecret(ctx context.Context, cachedSecrets secretv1.ReadInterface, secrets secretv1.ModInterface, tokenSecretName, secretSecretName string, claims map[string]interface{}, ownerRef *meta.OwnerReference) error {
@@ -313,6 +326,29 @@ func CreateJWTFromSecret(ctx context.Context, cachedSecrets secretv1.ReadInterfa
})
}
+// UpdateJWTFromSecret updates a JWT using the secret stored in secretSecretName and stores the
+// result in a new secret called tokenSecretName
+func UpdateJWTFromSecret(ctx context.Context, cachedSecrets secretv1.ReadInterface, secrets secretv1.ModInterface, tokenSecretName, secretSecretName string, claims map[string]interface{}) error {
+ current, err := cachedSecrets.Get(ctx, tokenSecretName, meta.GetOptions{})
+ if err != nil {
+ return errors.WithStack(err)
+ }
+
+ secret, err := GetTokenSecret(ctx, cachedSecrets, secretSecretName)
+ if err != nil {
+ return errors.WithStack(err)
+ }
+
+ signedToken, err := token.New([]byte(secret), claims)
+ if err != nil {
+ return errors.WithStack(err)
+ }
+
+ return globals.GetGlobalTimeouts().Kubernetes().RunWithTimeout(ctx, func(ctxChild context.Context) error {
+ return UpdateTokenSecret(ctxChild, secrets, current, signedToken)
+ })
+}
+
// CreateBasicAuthSecret creates a secret with given name in given namespace
// with a given username and password as value.
func CreateBasicAuthSecret(ctx context.Context, secrets secretv1.ModInterface, secretName, username, password string,