Skip to content

Commit

Permalink
Merge pull request #38 from opsdis/issue_1
Browse files Browse the repository at this point in the history
Issue 1
  • Loading branch information
thenodon authored Sep 28, 2023
2 parents eb8aedf + 065d88d commit f671a6b
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 19 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,8 @@ The aci-exporter will attach the following labels to all metrics
All attributes in the configuration has default values, except for the fabric and the different query sections.
A fabric profile include the information specific to an ACI fabrics, like authentication and apic(s) url.

> The name of the fabric profile MUST BE in lower case.

> The user need to have admin read-only rights in the domain `All` to allow all kinds of queries.

If there is multiple apic urls configured the exporter will use the first apic it can login to in the list.
Expand Down
18 changes: 14 additions & 4 deletions aci-api.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,17 @@ import (
"strings"
"time"

"github.com/umisama/go-regexpcache"

"github.com/Knetic/govaluate"
log "github.com/sirupsen/logrus"
"github.com/spf13/viper"
"github.com/tidwall/gjson"
"github.com/umisama/go-regexpcache"
)

var arrayExtension = regexpcache.MustCompile("^(?P<stage_1>.*)\\.\\[(?P<child_name>.*)\\](?P<stage_2>.*)")

func newAciAPI(ctx context.Context, fabricConfig Fabric, configQueries AllQueries, queryFilter string) *aciAPI {
func newAciAPI(ctx context.Context, fabricConfig *Fabric, configQueries AllQueries, queryFilter string) *aciAPI {

executeQueries := configQueries
queryArray := strings.Split(queryFilter, ",")
Expand Down Expand Up @@ -307,12 +308,21 @@ func (p aciAPI) faults(ch chan []MetricDefinition) {
}

func (p aciAPI) getAciName() (string, error) {
data, err := p.connection.getByQuery("aci_name")
if p.connection.fabricConfig.AciName != "" {
return p.connection.fabricConfig.AciName, nil
}

data, err := p.connection.getByClassQuery("infraCont", "?query-target=self")

if err != nil {
return "", err
}
p.connection.fabricConfig.AciName = gjson.Get(data, "imdata.#.infraCont.attributes.fbDmNm").Array()[0].Str

return gjson.Get(data, "imdata.0.infraCont.attributes.fbDmNm").Str, nil
if p.connection.fabricConfig.AciName != "" {
return p.connection.fabricConfig.AciName, nil
}
return "", fmt.Errorf("could not determine ACI name")
}

func (p aciAPI) configuredCompoundsMetrics(chall chan []MetricDefinition) {
Expand Down
4 changes: 2 additions & 2 deletions aci-connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,15 @@ var responseTime = promauto.NewHistogramVec(prometheus.HistogramOpts{
// AciConnection is the connection object
type AciConnection struct {
ctx context.Context
fabricConfig Fabric
fabricConfig *Fabric
activeController *int
URLMap map[string]string
Headers map[string]string
Client http.Client
responseTime *prometheus.HistogramVec
}

func newAciConnction(ctx context.Context, fabricConfig Fabric) *AciConnection {
func newAciConnction(ctx context.Context, fabricConfig *Fabric) *AciConnection {
// Empty cookie jar
jar, _ := cookiejar.New(nil)

Expand Down
41 changes: 30 additions & 11 deletions aci-exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"fmt"
"os"
"strconv"
"strings"
"time"

"net/http"
Expand Down Expand Up @@ -156,13 +157,22 @@ func main() {
log.Error("Unable to decode compound_queries into struct - ", err)
os.Exit(1)
}

allQueries := AllQueries{
ClassQueries: classQueries,
CompoundClassQueries: compoundClassQueries,
GroupClassQueries: groupClassQueries,
}

handler := &HandlerInit{allQueries}
allFabrics := make(map[string]*Fabric)

err = viper.UnmarshalKey("fabrics", &allFabrics)
if err != nil {
log.Error("Unable to decode class_queries into struct - ", err)
os.Exit(1)
}

handler := &HandlerInit{allQueries, allFabrics}

// Create a Prometheus histogram for response time of the exporter
responseTime := promauto.NewHistogramVec(prometheus.HistogramOpts{
Expand Down Expand Up @@ -205,10 +215,11 @@ func cliQuery(fabric *string, class *string, query *string) string {
username := viper.GetString(fmt.Sprintf("fabrics.%s.username", *fabric))
password := viper.GetString(fmt.Sprintf("fabrics.%s.password", *fabric))
apicControllers := viper.GetStringSlice(fmt.Sprintf("fabrics.%s.apic", *fabric))
aciName := viper.GetString(fmt.Sprintf("fabrics.%s.aci_name", *fabric))

fabricConfig := Fabric{Username: username, Password: password, Apic: apicControllers}
fabricConfig := Fabric{Username: username, Password: password, Apic: apicControllers, AciName: aciName}
ctx := context.TODO()
con := *newAciConnction(ctx, fabricConfig)
con := *newAciConnction(ctx, &fabricConfig)
err = con.login()
if err != nil {
fmt.Printf("Login error %s", err)
Expand All @@ -231,6 +242,7 @@ func cliQuery(fabric *string, class *string, query *string) string {

type HandlerInit struct {
AllQueries AllQueries
AllFabrics map[string]*Fabric
}

func (h HandlerInit) getMonitorMetrics(w http.ResponseWriter, r *http.Request) {
Expand All @@ -244,25 +256,32 @@ func (h HandlerInit) getMonitorMetrics(w http.ResponseWriter, r *http.Request) {
fabric := r.URL.Query().Get("target")
queries := r.URL.Query().Get("queries")

if fabric != strings.ToLower(fabric) {
w.Header().Set("Content-Type", "text/plain; version=0.0.4; charset=utf-8")
w.Header().Set("Content-Length", "0")
log.WithFields(log.Fields{
"fabric": fabric,
}).Warning("fabric target must be in lower case")
lrw := loggingResponseWriter{ResponseWriter: w}
lrw.WriteHeader(400)
return
}

// Check if a valid target
if !viper.IsSet(fmt.Sprintf("fabrics.%s", fabric)) {
w.Header().Set("Content-Type", "text/plain; version=0.0.4; charset=utf-8")
w.Header().Set("Content-Length", "0")

log.WithFields(log.Fields{
"fabric": fabric,
}).Warning("fabric target do not exists")
lrw := loggingResponseWriter{ResponseWriter: w}
lrw.WriteHeader(404)
return
}

username := viper.GetString(fmt.Sprintf("fabrics.%s.username", fabric))
password := viper.GetString(fmt.Sprintf("fabrics.%s.password", fabric))
apicControllers := viper.GetStringSlice(fmt.Sprintf("fabrics.%s.apic", fabric))

fabricConfig := Fabric{Username: username, Password: password, Apic: apicControllers}

ctx := r.Context()
ctx = context.WithValue(ctx, "fabric", fabric)
api := *newAciAPI(ctx, fabricConfig, h.AllQueries, queries)
api := *newAciAPI(ctx, h.AllFabrics[fabric], h.AllQueries, queries)

aciName, metrics, err := api.CollectMetrics()

Expand Down
3 changes: 3 additions & 0 deletions example-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ config: config
prefix: aci_

# Profiles for different fabrics
# The profile name MUST be in lower case
fabrics:
# This is the Cisco provided sandbox that is open for testing
cisco_sandbox:
Expand All @@ -24,6 +25,8 @@ fabrics:
apic:
- https://apic1
- https://apic2
# Optional - The name of the aci cluster. If not set, aci-exporter will try to determine the name
aci_name: foobar

# Http client settings used to access apic
# Below is the default values, where 0 is no timeout
Expand Down
5 changes: 3 additions & 2 deletions fabricconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
package main

type Fabric struct {
Username string
Password string
Username string `mapstructure:"username"`
Password string `mapstructure:"password"`
Apic []string
AciName string `mapstructure:"aci_name"`
}

0 comments on commit f671a6b

Please sign in to comment.