1
0
golang-github-prometheus-pr.../0003-Add-Uyuni-service-discovery.patch
2020-09-11 09:32:03 +00:00

2195 lines
60 KiB
Diff
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

From: Joao Cavalheiro <jcavalheiro@suse.de>
Date: Mon Jul 27 17:42:33 2020 +0200
Subject: A fork of Prometheus with Uyuni Service discovery
References: https://github.com/uyuni-project/prometheus-uyuni-sd
---
discovery/config/config.go | 3
discovery/manager.go | 6
discovery/uyuni/uyuni.go | 384 +++++++++++++++++
discovery/uyuni/uyuni_test.go | 33 +
go.mod | 1
go.sum | 2
vendor/github.com/kolo/xmlrpc/LICENSE | 19
vendor/github.com/kolo/xmlrpc/README.md | 89 ++++
vendor/github.com/kolo/xmlrpc/client.go | 170 +++++++
vendor/github.com/kolo/xmlrpc/client_test.go | 141 ++++++
vendor/github.com/kolo/xmlrpc/decoder.go | 473 ++++++++++++++++++++++
vendor/github.com/kolo/xmlrpc/decoder_test.go | 234 ++++++++++
vendor/github.com/kolo/xmlrpc/encoder.go | 171 +++++++
vendor/github.com/kolo/xmlrpc/encoder_test.go | 58 ++
vendor/github.com/kolo/xmlrpc/fixtures/cp1251.xml | 6
vendor/github.com/kolo/xmlrpc/request.go | 57 ++
vendor/github.com/kolo/xmlrpc/response.go | 52 ++
vendor/github.com/kolo/xmlrpc/response_test.go | 84 +++
vendor/github.com/kolo/xmlrpc/test_server.rb | 25 +
vendor/github.com/kolo/xmlrpc/xmlrpc.go | 19
20 files changed, 2027 insertions(+)
Index: prometheus-2.18.0/discovery/config/config.go
===================================================================
--- prometheus-2.18.0.orig/discovery/config/config.go
+++ prometheus-2.18.0/discovery/config/config.go
@@ -27,6 +27,7 @@ import (
"github.com/prometheus/prometheus/discovery/openstack"
"github.com/prometheus/prometheus/discovery/targetgroup"
"github.com/prometheus/prometheus/discovery/triton"
+ "github.com/prometheus/prometheus/discovery/uyuni"
"github.com/prometheus/prometheus/discovery/zookeeper"
)
@@ -58,6 +59,8 @@ type ServiceDiscoveryConfig struct {
AzureSDConfigs []*azure.SDConfig `yaml:"azure_sd_configs,omitempty"`
// List of Triton service discovery configurations.
TritonSDConfigs []*triton.SDConfig `yaml:"triton_sd_configs,omitempty"`
+ // List of Uyuni service discovery configurations.
+ UyuniSDConfigs []*uyuni.SDConfig `yaml:"uyuni_sd_configs,omitempty"`
}
// Validate validates the ServiceDiscoveryConfig.
Index: prometheus-2.18.0/discovery/manager.go
===================================================================
--- prometheus-2.18.0.orig/discovery/manager.go
+++ prometheus-2.18.0/discovery/manager.go
@@ -37,6 +37,7 @@ import (
"github.com/prometheus/prometheus/discovery/marathon"
"github.com/prometheus/prometheus/discovery/openstack"
"github.com/prometheus/prometheus/discovery/triton"
+ "github.com/prometheus/prometheus/discovery/uyuni"
"github.com/prometheus/prometheus/discovery/zookeeper"
)
@@ -414,6 +415,11 @@ func (m *Manager) registerProviders(cfg
return triton.New(log.With(m.logger, "discovery", "triton"), c)
})
}
+ for _, c := range cfg.UyuniSDConfigs {
+ add(c, func() (Discoverer, error) {
+ return uyuni.NewDiscovery(c, log.With(m.logger, "discovery", "uyuni")), nil
+ })
+ }
if len(cfg.StaticConfigs) > 0 {
add(setName, func() (Discoverer, error) {
return &StaticProvider{TargetGroups: cfg.StaticConfigs}, nil
Index: prometheus-2.18.0/discovery/uyuni/uyuni.go
===================================================================
--- /dev/null
+++ prometheus-2.18.0/discovery/uyuni/uyuni.go
@@ -0,0 +1,384 @@
+// Copyright 2019 The Prometheus Authors
+// 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 uyuni
+
+import (
+ "context"
+ "fmt"
+ "net"
+ "net/url"
+ "regexp"
+ "strings"
+ "time"
+
+ "github.com/go-kit/kit/log"
+ "github.com/go-kit/kit/log/level"
+ "github.com/kolo/xmlrpc"
+ "github.com/pkg/errors"
+ "github.com/prometheus/common/model"
+
+ "github.com/prometheus/prometheus/discovery/refresh"
+ "github.com/prometheus/prometheus/discovery/targetgroup"
+)
+
+const (
+ monitoringEntitlementLabel = "monitoring_entitled"
+ prometheusExporterFormulaName = "prometheus-exporters"
+ uyuniXMLRPCAPIPath = "/rpc/api"
+)
+
+// DefaultSDConfig is the default Uyuni SD configuration.
+var DefaultSDConfig = SDConfig{
+ RefreshInterval: model.Duration(1 * time.Minute),
+}
+
+// Regular expression to extract port from formula data
+var monFormulaRegex = regexp.MustCompile(`--(?:telemetry\.address|web\.listen-address)=\":([0-9]*)\"`)
+
+// SDConfig is the configuration for Uyuni based service discovery.
+type SDConfig struct {
+ Host string `yaml:"host"`
+ User string `yaml:"username"`
+ Pass string `yaml:"password"`
+ RefreshInterval model.Duration `yaml:"refresh_interval,omitempty"`
+}
+
+// Uyuni API Response structures
+type systemGroupID struct {
+ GroupID int `xmlrpc:"id"`
+ GroupName string `xmlrpc:"name"`
+}
+
+type networkInfo struct {
+ SystemID int `xmlrpc:"system_id"`
+ Hostname string `xmlrpc:"hostname"`
+ IP string `xmlrpc:"ip"`
+}
+
+type exporterConfig struct {
+ Address string `xmlrpc:"address"`
+ Args string `xmlrpc:"args"`
+ Enabled bool `xmlrpc:"enabled"`
+}
+
+type proxiedExporterConfig struct {
+ ProxyIsEnabled bool `xmlrpc:"proxy_enabled"`
+ ProxyPort float32 `xmlrpc:"proxy_port"`
+ NodeExporter exporterConfig `xmlrpc:"node_exporter"`
+ ApacheExporter exporterConfig `xmlrpc:"apache_exporter"`
+ PostgresExporter exporterConfig `xmlrpc:"postgres_exporter"`
+}
+
+// Discovery periodically performs Uyuni API requests. It implements the Discoverer interface.
+type Discovery struct {
+ *refresh.Discovery
+ interval time.Duration
+ sdConfig *SDConfig
+ logger log.Logger
+}
+
+// UnmarshalYAML implements the yaml.Unmarshaler interface.
+func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
+ *c = DefaultSDConfig
+ type plain SDConfig
+ err := unmarshal((*plain)(c))
+
+ if err != nil {
+ return err
+ }
+ if c.Host == "" {
+ return errors.New("Uyuni SD configuration requires a Host")
+ }
+ if c.User == "" {
+ return errors.New("Uyuni SD configuration requires a Username")
+ }
+ if c.Pass == "" {
+ return errors.New("Uyuni SD configuration requires a Password")
+ }
+ if c.RefreshInterval <= 0 {
+ return errors.New("Uyuni SD configuration requires RefreshInterval to be a positive integer")
+ }
+ return nil
+}
+
+// Attempt to login in Uyuni Server and get an auth token
+func login(rpcclient *xmlrpc.Client, user string, pass string) (string, error) {
+ var result string
+ err := rpcclient.Call("auth.login", []interface{}{user, pass}, &result)
+ return result, err
+}
+
+// Logout from Uyuni API
+func logout(rpcclient *xmlrpc.Client, token string) error {
+ err := rpcclient.Call("auth.logout", token, nil)
+ return err
+}
+
+// Get the system groups information of monitored clients
+func getSystemGroupsInfoOfMonitoredClients(rpcclient *xmlrpc.Client, token string) (map[int][]systemGroupID, error) {
+ var systemGroupsInfos []struct {
+ SystemID int `xmlrpc:"id"`
+ SystemGroups []systemGroupID `xmlrpc:"system_groups"`
+ }
+ err := rpcclient.Call("system.listSystemGroupsForSystemsWithEntitlement", []interface{}{token, monitoringEntitlementLabel}, &systemGroupsInfos)
+ if err != nil {
+ return nil, err
+ }
+ result := make(map[int][]systemGroupID)
+ for _, systemGroupsInfo := range systemGroupsInfos {
+ result[systemGroupsInfo.SystemID] = systemGroupsInfo.SystemGroups
+ }
+ return result, nil
+}
+
+// GetSystemNetworkInfo lists client FQDNs
+func getNetworkInformationForSystems(rpcclient *xmlrpc.Client, token string, systemIDs []int) (map[int]networkInfo, error) {
+ var networkInfos []networkInfo
+ err := rpcclient.Call("system.getNetworkForSystems", []interface{}{token, systemIDs}, &networkInfos)
+ if err != nil {
+ return nil, err
+ }
+ result := make(map[int]networkInfo)
+ for _, networkInfo := range networkInfos {
+ result[networkInfo.SystemID] = networkInfo
+ }
+ return result, nil
+}
+
+// Get formula data for a given system
+func getExporterDataForSystems(
+ rpcclient *xmlrpc.Client,
+ token string,
+ systemIDs []int,
+) (map[int]proxiedExporterConfig, error) {
+ var combinedFormulaData []struct {
+ SystemID int `xmlrpc:"system_id"`
+ ExporterConfigs proxiedExporterConfig `xmlrpc:"formula_values"`
+ }
+ err := rpcclient.Call(
+ "formula.getCombinedFormulaDataByServerIds",
+ []interface{}{token, prometheusExporterFormulaName, systemIDs},
+ &combinedFormulaData)
+ if err != nil {
+ return nil, err
+ }
+ result := make(map[int]proxiedExporterConfig)
+ for _, combinedFormulaData := range combinedFormulaData {
+ result[combinedFormulaData.SystemID] = combinedFormulaData.ExporterConfigs
+ }
+ return result, nil
+}
+
+// extractPortFromFormulaData gets exporter port configuration from the formula.
+// args takes precedence over address.
+func extractPortFromFormulaData(args string, address string) (string, error) {
+ // first try args
+ var port string
+ tokens := monFormulaRegex.FindStringSubmatch(args)
+ if len(tokens) < 1 {
+ err := "Unable to find port in args: " + args
+ // now try address
+ _, addrPort, addrErr := net.SplitHostPort(address)
+ if addrErr != nil || len(addrPort) == 0 {
+ if addrErr != nil {
+ err = strings.Join([]string{addrErr.Error(), err}, " ")
+ }
+ return "", errors.New(err)
+ }
+ port = addrPort
+ } else {
+ port = tokens[1]
+ }
+
+ return port, nil
+}
+
+// NewDiscovery returns a new file discovery for the given paths.
+func NewDiscovery(conf *SDConfig, logger log.Logger) *Discovery {
+ d := &Discovery{
+ interval: time.Duration(conf.RefreshInterval),
+ sdConfig: conf,
+ logger: logger,
+ }
+ d.Discovery = refresh.NewDiscovery(
+ logger,
+ "uyuni",
+ time.Duration(conf.RefreshInterval),
+ d.refresh,
+ )
+ return d
+}
+
+func initializeExporterTargets(
+ targets *[]model.LabelSet,
+ module string, config exporterConfig,
+ proxyPort string,
+ errors *[]error,
+) {
+ if !(config.Enabled) {
+ return
+ }
+ var port string
+ if len(proxyPort) == 0 {
+ exporterPort, err := extractPortFromFormulaData(config.Args, config.Address)
+ if err != nil {
+ *errors = append(*errors, err)
+ return
+ }
+ port = exporterPort
+ } else {
+ port = proxyPort
+ }
+
+ labels := model.LabelSet{}
+ labels["exporter"] = model.LabelValue(module + "_exporter")
+ // for now set only port number here
+ labels[model.AddressLabel] = model.LabelValue(port)
+ if len(proxyPort) > 0 {
+ labels[model.ParamLabelPrefix+"module"] = model.LabelValue(module)
+ }
+ *targets = append(*targets, labels)
+}
+
+func (d *Discovery) getTargetsForSystem(
+ systemID int,
+ systemGroupsIDs []systemGroupID,
+ networkInfo networkInfo,
+ combinedFormulaData proxiedExporterConfig,
+) []model.LabelSet {
+
+ var labelSets []model.LabelSet
+ var errors []error
+ var proxyPortNumber string
+ if combinedFormulaData.ProxyIsEnabled {
+ proxyPortNumber = fmt.Sprintf("%d", int(combinedFormulaData.ProxyPort))
+ }
+ initializeExporterTargets(&labelSets, "node", combinedFormulaData.NodeExporter, proxyPortNumber, &errors)
+ initializeExporterTargets(&labelSets, "apache", combinedFormulaData.ApacheExporter, proxyPortNumber, &errors)
+ initializeExporterTargets(&labelSets, "postgres", combinedFormulaData.PostgresExporter, proxyPortNumber, &errors)
+ managedGroupNames := getSystemGroupNames(systemGroupsIDs)
+ for _, labels := range labelSets {
+ // add hostname to the address label
+ addr := fmt.Sprintf("%s:%s", networkInfo.IP, labels[model.AddressLabel])
+ labels[model.AddressLabel] = model.LabelValue(addr)
+ labels["hostname"] = model.LabelValue(networkInfo.Hostname)
+ labels["groups"] = model.LabelValue(strings.Join(managedGroupNames, ","))
+ if combinedFormulaData.ProxyIsEnabled {
+ labels[model.MetricsPathLabel] = "/proxy"
+ }
+ _ = level.Debug(d.logger).Log("msg", "Configured target", "Labels", fmt.Sprintf("%+v", labels))
+ }
+ for _, err := range errors {
+ level.Error(d.logger).Log("msg", "Invalid exporter port", "clientId", systemID, "err", err)
+ }
+
+ return labelSets
+}
+
+func getSystemGroupNames(systemGroupsIDs []systemGroupID) []string {
+ managedGroupNames := make([]string, 0, len(systemGroupsIDs))
+ for _, systemGroupInfo := range systemGroupsIDs {
+ managedGroupNames = append(managedGroupNames, systemGroupInfo.GroupName)
+ }
+
+ if len(managedGroupNames) == 0 {
+ managedGroupNames = []string{"No group"}
+ }
+ return managedGroupNames
+}
+
+func (d *Discovery) getTargetsForSystems(
+ rpcClient *xmlrpc.Client,
+ token string,
+ systemGroupIDsBySystemID map[int][]systemGroupID,
+) ([]model.LabelSet, error) {
+
+ result := make([]model.LabelSet, 0)
+
+ systemIDs := make([]int, 0, len(systemGroupIDsBySystemID))
+ for systemID := range systemGroupIDsBySystemID {
+ systemIDs = append(systemIDs, systemID)
+ }
+
+ combinedFormulaDataBySystemID, err := getExporterDataForSystems(rpcClient, token, systemIDs)
+ if err != nil {
+ return nil, errors.Wrap(err, "Unable to get systems combined formula data")
+ }
+ networkInfoBySystemID, err := getNetworkInformationForSystems(rpcClient, token, systemIDs)
+ if err != nil {
+ return nil, errors.Wrap(err, "Unable to get the systems network information")
+ }
+
+ for _, systemID := range systemIDs {
+ targets := d.getTargetsForSystem(
+ systemID,
+ systemGroupIDsBySystemID[systemID],
+ networkInfoBySystemID[systemID],
+ combinedFormulaDataBySystemID[systemID])
+ result = append(result, targets...)
+
+ // Log debug information
+ if networkInfoBySystemID[systemID].IP != "" {
+ level.Debug(d.logger).Log("msg", "Found monitored system",
+ "Host", networkInfoBySystemID[systemID].Hostname,
+ "Network", fmt.Sprintf("%+v", networkInfoBySystemID[systemID]),
+ "Groups", fmt.Sprintf("%+v", systemGroupIDsBySystemID[systemID]),
+ "Formulas", fmt.Sprintf("%+v", combinedFormulaDataBySystemID[systemID]))
+ }
+ }
+ return result, nil
+}
+
+func (d *Discovery) refresh(ctx context.Context) ([]*targetgroup.Group, error) {
+ config := d.sdConfig
+ apiURL := config.Host + uyuniXMLRPCAPIPath
+
+ startTime := time.Now()
+
+ // Check if the URL is valid and create rpc client
+ _, err := url.ParseRequestURI(apiURL)
+ if err != nil {
+ return nil, errors.Wrap(err, "Uyuni Server URL is not valid")
+ }
+
+ rpcClient, _ := xmlrpc.NewClient(apiURL, nil)
+
+ token, err := login(rpcClient, config.User, config.Pass)
+ if err != nil {
+ return nil, errors.Wrap(err, "Unable to login to Uyuni API")
+ }
+ systemGroupIDsBySystemID, err := getSystemGroupsInfoOfMonitoredClients(rpcClient, token)
+ if err != nil {
+ return nil, errors.Wrap(err, "Unable to get the managed system groups information of monitored clients")
+ }
+
+ targets := make([]model.LabelSet, 0)
+ if len(systemGroupIDsBySystemID) > 0 {
+ targetsForSystems, err := d.getTargetsForSystems(rpcClient, token, systemGroupIDsBySystemID)
+ if err != nil {
+ return nil, err
+ }
+ targets = append(targets, targetsForSystems...)
+ level.Info(d.logger).Log("msg", "Total discovery time", "time", time.Since(startTime))
+ } else {
+ fmt.Printf("\tFound 0 systems.\n")
+ }
+
+ err = logout(rpcClient, token)
+ if err != nil {
+ level.Warn(d.logger).Log("msg", "Failed to log out from Uyuni API", "err", err)
+ }
+ rpcClient.Close()
+ return []*targetgroup.Group{&targetgroup.Group{Targets: targets, Source: config.Host}}, nil
+}
Index: prometheus-2.18.0/discovery/uyuni/uyuni_test.go
===================================================================
--- /dev/null
+++ prometheus-2.18.0/discovery/uyuni/uyuni_test.go
@@ -0,0 +1,33 @@
+package uyuni
+
+import "testing"
+
+func TestExtractPortFromFormulaData(t *testing.T) {
+ type args struct {
+ args string
+ address string
+ }
+ tests := []struct {
+ name string
+ args args
+ want string
+ wantErr bool
+ }{
+ {name: `TestArgs`, args: args{args: `--web.listen-address=":9100"`}, want: `9100`},
+ {name: `TestAddress`, args: args{address: `:9100`}, want: `9100`},
+ {name: `TestArgsAndAddress`, args: args{args: `--web.listen-address=":9100"`, address: `9999`}, want: `9100`},
+ {name: `TestMissingPort`, args: args{args: `localhost`}, wantErr: true},
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := extractPortFromFormulaData(tt.args.args, tt.args.address)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("extractPortFromFormulaData() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if got != tt.want {
+ t.Errorf("extractPortFromFormulaData() got = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
Index: prometheus-2.18.0/go.mod
===================================================================
--- prometheus-2.18.0.orig/go.mod
+++ prometheus-2.18.0/go.mod
@@ -41,6 +41,7 @@ require (
github.com/jpillora/backoff v1.0.0 // indirect
github.com/json-iterator/go v1.1.9
github.com/julienschmidt/httprouter v1.3.0 // indirect
+ github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b
github.com/mattn/go-colorable v0.1.6 // indirect
github.com/miekg/dns v1.1.29
github.com/mitchellh/mapstructure v1.2.2 // indirect
Index: prometheus-2.18.0/go.sum
===================================================================
--- prometheus-2.18.0.orig/go.sum
+++ prometheus-2.18.0/go.sum
@@ -505,6 +505,8 @@ github.com/klauspost/compress v1.9.5/go.
github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg=
github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
+github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b h1:DzHy0GlWeF0KAglaTMY7Q+khIFoG8toHP+wLFBVBQJc=
+github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b/go.mod h1:o03bZfuBwAXHetKXuInt4S7omeXUu62/A845kiycsSQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY=
Index: prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/LICENSE
===================================================================
--- /dev/null
+++ prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/LICENSE
@@ -0,0 +1,19 @@
+Copyright (C) 2012 Dmitry Maksimov
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
Index: prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/README.md
===================================================================
--- /dev/null
+++ prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/README.md
@@ -0,0 +1,89 @@
+[![GoDoc](https://godoc.org/github.com/kolo/xmlrpc?status.svg)](https://godoc.org/github.com/kolo/xmlrpc)
+
+## Overview
+
+xmlrpc is an implementation of client side part of XMLRPC protocol in Go language.
+
+## Status
+
+This project is in minimal maintenance mode with no further development. Bug fixes
+are accepted, but it might take some time until they will be merged.
+
+## Installation
+
+To install xmlrpc package run `go get github.com/kolo/xmlrpc`. To use
+it in application add `"github.com/kolo/xmlrpc"` string to `import`
+statement.
+
+## Usage
+
+ client, _ := xmlrpc.NewClient("https://bugzilla.mozilla.org/xmlrpc.cgi", nil)
+ result := struct{
+ Version string `xmlrpc:"version"`
+ }{}
+ client.Call("Bugzilla.version", nil, &result)
+ fmt.Printf("Version: %s\n", result.Version) // Version: 4.2.7+
+
+Second argument of NewClient function is an object that implements
+[http.RoundTripper](http://golang.org/pkg/net/http/#RoundTripper)
+interface, it can be used to get more control over connection options.
+By default it initialized by http.DefaultTransport object.
+
+### Arguments encoding
+
+xmlrpc package supports encoding of native Go data types to method
+arguments.
+
+Data types encoding rules:
+
+* int, int8, int16, int32, int64 encoded to int;
+* float32, float64 encoded to double;
+* bool encoded to boolean;
+* string encoded to string;
+* time.Time encoded to datetime.iso8601;
+* xmlrpc.Base64 encoded to base64;
+* slice encoded to array;
+
+Structs decoded to struct by following rules:
+
+* all public field become struct members;
+* field name become member name;
+* if field has xmlrpc tag, its value become member name.
+
+Server method can accept few arguments, to handle this case there is
+special approach to handle slice of empty interfaces (`[]interface{}`).
+Each value of such slice encoded as separate argument.
+
+### Result decoding
+
+Result of remote function is decoded to native Go data type.
+
+Data types decoding rules:
+
+* int, i4 decoded to int, int8, int16, int32, int64;
+* double decoded to float32, float64;
+* boolean decoded to bool;
+* string decoded to string;
+* array decoded to slice;
+* structs decoded following the rules described in previous section;
+* datetime.iso8601 decoded as time.Time data type;
+* base64 decoded to string.
+
+## Implementation details
+
+xmlrpc package contains clientCodec type, that implements [rpc.ClientCodec](http://golang.org/pkg/net/rpc/#ClientCodec)
+interface of [net/rpc](http://golang.org/pkg/net/rpc) package.
+
+xmlrpc package works over HTTP protocol, but some internal functions
+and data type were made public to make it easier to create another
+implementation of xmlrpc that works over another protocol. To encode
+request body there is EncodeMethodCall function. To decode server
+response Response data type can be used.
+
+## Contribution
+
+See [project status](#status).
+
+## Authors
+
+Dmitry Maksimov (dmtmax@gmail.com)
Index: prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/client.go
===================================================================
--- /dev/null
+++ prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/client.go
@@ -0,0 +1,170 @@
+package xmlrpc
+
+import (
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "net/http/cookiejar"
+ "net/rpc"
+ "net/url"
+ "sync"
+)
+
+type Client struct {
+ *rpc.Client
+}
+
+// clientCodec is rpc.ClientCodec interface implementation.
+type clientCodec struct {
+ // url presents url of xmlrpc service
+ url *url.URL
+
+ // httpClient works with HTTP protocol
+ httpClient *http.Client
+
+ // cookies stores cookies received on last request
+ cookies http.CookieJar
+
+ // responses presents map of active requests. It is required to return request id, that
+ // rpc.Client can mark them as done.
+ responses map[uint64]*http.Response
+ mutex sync.Mutex
+
+ response *Response
+
+ // ready presents channel, that is used to link request and it`s response.
+ ready chan uint64
+
+ // close notifies codec is closed.
+ close chan uint64
+}
+
+func (codec *clientCodec) WriteRequest(request *rpc.Request, args interface{}) (err error) {
+ httpRequest, err := NewRequest(codec.url.String(), request.ServiceMethod, args)
+
+ if codec.cookies != nil {
+ for _, cookie := range codec.cookies.Cookies(codec.url) {
+ httpRequest.AddCookie(cookie)
+ }
+ }
+
+ if err != nil {
+ return err
+ }
+
+ var httpResponse *http.Response
+ httpResponse, err = codec.httpClient.Do(httpRequest)
+
+ if err != nil {
+ return err
+ }
+
+ if codec.cookies != nil {
+ codec.cookies.SetCookies(codec.url, httpResponse.Cookies())
+ }
+
+ codec.mutex.Lock()
+ codec.responses[request.Seq] = httpResponse
+ codec.mutex.Unlock()
+
+ codec.ready <- request.Seq
+
+ return nil
+}
+
+func (codec *clientCodec) ReadResponseHeader(response *rpc.Response) (err error) {
+ var seq uint64
+
+ select {
+ case seq = <-codec.ready:
+ case <-codec.close:
+ return errors.New("codec is closed")
+ }
+
+ codec.mutex.Lock()
+ httpResponse := codec.responses[seq]
+ codec.mutex.Unlock()
+
+ if httpResponse.StatusCode < 200 || httpResponse.StatusCode >= 300 {
+ return fmt.Errorf("request error: bad status code - %d", httpResponse.StatusCode)
+ }
+
+ respData, err := ioutil.ReadAll(httpResponse.Body)
+
+ if err != nil {
+ return err
+ }
+
+ httpResponse.Body.Close()
+
+ resp := NewResponse(respData)
+
+ if resp.Failed() {
+ response.Error = fmt.Sprintf("%v", resp.Err())
+ }
+
+ codec.response = resp
+
+ response.Seq = seq
+
+ codec.mutex.Lock()
+ delete(codec.responses, seq)
+ codec.mutex.Unlock()
+
+ return nil
+}
+
+func (codec *clientCodec) ReadResponseBody(v interface{}) (err error) {
+ if v == nil {
+ return nil
+ }
+
+ if err = codec.response.Unmarshal(v); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (codec *clientCodec) Close() error {
+ if transport, ok := codec.httpClient.Transport.(*http.Transport); ok {
+ transport.CloseIdleConnections()
+ }
+
+ close(codec.close)
+
+ return nil
+}
+
+// NewClient returns instance of rpc.Client object, that is used to send request to xmlrpc service.
+func NewClient(requrl string, transport http.RoundTripper) (*Client, error) {
+ if transport == nil {
+ transport = http.DefaultTransport
+ }
+
+ httpClient := &http.Client{Transport: transport}
+
+ jar, err := cookiejar.New(nil)
+
+ if err != nil {
+ return nil, err
+ }
+
+ u, err := url.Parse(requrl)
+
+ if err != nil {
+ return nil, err
+ }
+
+ codec := clientCodec{
+ url: u,
+ httpClient: httpClient,
+ close: make(chan uint64),
+ ready: make(chan uint64),
+ responses: make(map[uint64]*http.Response),
+ cookies: jar,
+ }
+
+ return &Client{rpc.NewClientWithCodec(&codec)}, nil
+}
Index: prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/client_test.go
===================================================================
--- /dev/null
+++ prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/client_test.go
@@ -0,0 +1,141 @@
+// +build integration
+
+package xmlrpc
+
+import (
+ "context"
+ "runtime"
+ "sync"
+ "testing"
+ "time"
+)
+
+func Test_CallWithoutArgs(t *testing.T) {
+ client := newClient(t)
+ defer client.Close()
+
+ var result time.Time
+ if err := client.Call("service.time", nil, &result); err != nil {
+ t.Fatalf("service.time call error: %v", err)
+ }
+}
+
+func Test_CallWithOneArg(t *testing.T) {
+ client := newClient(t)
+ defer client.Close()
+
+ var result string
+ if err := client.Call("service.upcase", "xmlrpc", &result); err != nil {
+ t.Fatalf("service.upcase call error: %v", err)
+ }
+
+ if result != "XMLRPC" {
+ t.Fatalf("Unexpected result of service.upcase: %s != %s", "XMLRPC", result)
+ }
+}
+
+func Test_CallWithTwoArgs(t *testing.T) {
+ client := newClient(t)
+ defer client.Close()
+
+ var sum int
+ if err := client.Call("service.sum", []interface{}{2, 3}, &sum); err != nil {
+ t.Fatalf("service.sum call error: %v", err)
+ }
+
+ if sum != 5 {
+ t.Fatalf("Unexpected result of service.sum: %d != %d", 5, sum)
+ }
+}
+
+func Test_TwoCalls(t *testing.T) {
+ client := newClient(t)
+ defer client.Close()
+
+ var upcase string
+ if err := client.Call("service.upcase", "xmlrpc", &upcase); err != nil {
+ t.Fatalf("service.upcase call error: %v", err)
+ }
+
+ var sum int
+ if err := client.Call("service.sum", []interface{}{2, 3}, &sum); err != nil {
+ t.Fatalf("service.sum call error: %v", err)
+ }
+
+}
+
+func Test_FailedCall(t *testing.T) {
+ client := newClient(t)
+ defer client.Close()
+
+ var result int
+ if err := client.Call("service.error", nil, &result); err == nil {
+ t.Fatal("expected service.error returns error, but it didn't")
+ }
+}
+
+func Test_ConcurrentCalls(t *testing.T) {
+ client := newClient(t)
+
+ call := func() {
+ var result time.Time
+ client.Call("service.time", nil, &result)
+ }
+
+ var wg sync.WaitGroup
+ for i := 0; i < 100; i++ {
+ wg.Add(1)
+ go func() {
+ call()
+ wg.Done()
+ }()
+ }
+
+ wg.Wait()
+ client.Close()
+}
+
+func Test_CloseMemoryLeak(t *testing.T) {
+ expected := runtime.NumGoroutine()
+
+ for i := 0; i < 3; i++ {
+ client := newClient(t)
+ client.Call("service.time", nil, nil)
+ client.Close()
+ }
+
+ var actual int
+
+ // It takes some time to stop running goroutinges. This function checks number of
+ // running goroutines. It finishes execution if number is same as expected or timeout
+ // has been reached.
+ func() {
+ ctx, cancel := context.WithTimeout(context.Background(), time.Second)
+ defer cancel()
+
+ for {
+ select {
+ case <-ctx.Done():
+ return
+ default:
+ actual = runtime.NumGoroutine()
+ if actual == expected {
+ return
+ }
+ }
+ }
+ }()
+
+ if actual != expected {
+ t.Errorf("expected number of running goroutines to be %d, but got %d", expected, actual)
+ }
+}
+
+func newClient(t *testing.T) *Client {
+ client, err := NewClient("http://localhost:5001", nil)
+ if err != nil {
+ t.Fatalf("Can't create client: %v", err)
+ }
+
+ return client
+}
Index: prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/decoder.go
===================================================================
--- /dev/null
+++ prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/decoder.go
@@ -0,0 +1,473 @@
+package xmlrpc
+
+import (
+ "bytes"
+ "encoding/xml"
+ "errors"
+ "fmt"
+ "io"
+ "reflect"
+ "strconv"
+ "strings"
+ "time"
+)
+
+const (
+ iso8601 = "20060102T15:04:05"
+ iso8601Z = "20060102T15:04:05Z07:00"
+ iso8601Hyphen = "2006-01-02T15:04:05"
+ iso8601HyphenZ = "2006-01-02T15:04:05Z07:00"
+)
+
+var (
+ // CharsetReader is a function to generate reader which converts a non UTF-8
+ // charset into UTF-8.
+ CharsetReader func(string, io.Reader) (io.Reader, error)
+
+ timeLayouts = []string{iso8601, iso8601Z, iso8601Hyphen, iso8601HyphenZ}
+ invalidXmlError = errors.New("invalid xml")
+)
+
+type TypeMismatchError string
+
+func (e TypeMismatchError) Error() string { return string(e) }
+
+type decoder struct {
+ *xml.Decoder
+}
+
+func unmarshal(data []byte, v interface{}) (err error) {
+ dec := &decoder{xml.NewDecoder(bytes.NewBuffer(data))}
+
+ if CharsetReader != nil {
+ dec.CharsetReader = CharsetReader
+ }
+
+ var tok xml.Token
+ for {
+ if tok, err = dec.Token(); err != nil {
+ return err
+ }
+
+ if t, ok := tok.(xml.StartElement); ok {
+ if t.Name.Local == "value" {
+ val := reflect.ValueOf(v)
+ if val.Kind() != reflect.Ptr {
+ return errors.New("non-pointer value passed to unmarshal")
+ }
+ if err = dec.decodeValue(val.Elem()); err != nil {
+ return err
+ }
+
+ break
+ }
+ }
+ }
+
+ // read until end of document
+ err = dec.Skip()
+ if err != nil && err != io.EOF {
+ return err
+ }
+
+ return nil
+}
+
+func (dec *decoder) decodeValue(val reflect.Value) error {
+ var tok xml.Token
+ var err error
+
+ if val.Kind() == reflect.Ptr {
+ if val.IsNil() {
+ val.Set(reflect.New(val.Type().Elem()))
+ }
+ val = val.Elem()
+ }
+
+ var typeName string
+ for {
+ if tok, err = dec.Token(); err != nil {
+ return err
+ }
+
+ if t, ok := tok.(xml.EndElement); ok {
+ if t.Name.Local == "value" {
+ return nil
+ } else {
+ return invalidXmlError
+ }
+ }
+
+ if t, ok := tok.(xml.StartElement); ok {
+ typeName = t.Name.Local
+ break
+ }
+
+ // Treat value data without type identifier as string
+ if t, ok := tok.(xml.CharData); ok {
+ if value := strings.TrimSpace(string(t)); value != "" {
+ if err = checkType(val, reflect.String); err != nil {
+ return err
+ }
+
+ val.SetString(value)
+ return nil
+ }
+ }
+ }
+
+ switch typeName {
+ case "struct":
+ ismap := false
+ pmap := val
+ valType := val.Type()
+
+ if err = checkType(val, reflect.Struct); err != nil {
+ if checkType(val, reflect.Map) == nil {
+ if valType.Key().Kind() != reflect.String {
+ return fmt.Errorf("only maps with string key type can be unmarshalled")
+ }
+ ismap = true
+ } else if checkType(val, reflect.Interface) == nil && val.IsNil() {
+ var dummy map[string]interface{}
+ valType = reflect.TypeOf(dummy)
+ pmap = reflect.New(valType).Elem()
+ val.Set(pmap)
+ ismap = true
+ } else {
+ return err
+ }
+ }
+
+ var fields map[string]reflect.Value
+
+ if !ismap {
+ fields = make(map[string]reflect.Value)
+
+ for i := 0; i < valType.NumField(); i++ {
+ field := valType.Field(i)
+ fieldVal := val.FieldByName(field.Name)
+
+ if fieldVal.CanSet() {
+ if fn := field.Tag.Get("xmlrpc"); fn != "" {
+ fields[fn] = fieldVal
+ } else {
+ fields[field.Name] = fieldVal
+ }
+ }
+ }
+ } else {
+ // Create initial empty map
+ pmap.Set(reflect.MakeMap(valType))
+ }
+
+ // Process struct members.
+ StructLoop:
+ for {
+ if tok, err = dec.Token(); err != nil {
+ return err
+ }
+ switch t := tok.(type) {
+ case xml.StartElement:
+ if t.Name.Local != "member" {
+ return invalidXmlError
+ }
+
+ tagName, fieldName, err := dec.readTag()
+ if err != nil {
+ return err
+ }
+ if tagName != "name" {
+ return invalidXmlError
+ }
+
+ var fv reflect.Value
+ ok := true
+
+ if !ismap {
+ fv, ok = fields[string(fieldName)]
+ } else {
+ fv = reflect.New(valType.Elem())
+ }
+
+ if ok {
+ for {
+ if tok, err = dec.Token(); err != nil {
+ return err
+ }
+ if t, ok := tok.(xml.StartElement); ok && t.Name.Local == "value" {
+ if err = dec.decodeValue(fv); err != nil {
+ return err
+ }
+
+ // </value>
+ if err = dec.Skip(); err != nil {
+ return err
+ }
+
+ break
+ }
+ }
+ }
+
+ // </member>
+ if err = dec.Skip(); err != nil {
+ return err
+ }
+
+ if ismap {
+ pmap.SetMapIndex(reflect.ValueOf(string(fieldName)), reflect.Indirect(fv))
+ val.Set(pmap)
+ }
+ case xml.EndElement:
+ break StructLoop
+ }
+ }
+ case "array":
+ slice := val
+ if checkType(val, reflect.Interface) == nil && val.IsNil() {
+ slice = reflect.ValueOf([]interface{}{})
+ } else if err = checkType(val, reflect.Slice); err != nil {
+ return err
+ }
+
+ ArrayLoop:
+ for {
+ if tok, err = dec.Token(); err != nil {
+ return err
+ }
+
+ switch t := tok.(type) {
+ case xml.StartElement:
+ var index int
+ if t.Name.Local != "data" {
+ return invalidXmlError
+ }
+ DataLoop:
+ for {
+ if tok, err = dec.Token(); err != nil {
+ return err
+ }
+
+ switch tt := tok.(type) {
+ case xml.StartElement:
+ if tt.Name.Local != "value" {
+ return invalidXmlError
+ }
+
+ if index < slice.Len() {
+ v := slice.Index(index)
+ if v.Kind() == reflect.Interface {
+ v = v.Elem()
+ }
+ if v.Kind() != reflect.Ptr {
+ return errors.New("error: cannot write to non-pointer array element")
+ }
+ if err = dec.decodeValue(v); err != nil {
+ return err
+ }
+ } else {
+ v := reflect.New(slice.Type().Elem())
+ if err = dec.decodeValue(v); err != nil {
+ return err
+ }
+ slice = reflect.Append(slice, v.Elem())
+ }
+
+ // </value>
+ if err = dec.Skip(); err != nil {
+ return err
+ }
+ index++
+ case xml.EndElement:
+ val.Set(slice)
+ break DataLoop
+ }
+ }
+ case xml.EndElement:
+ break ArrayLoop
+ }
+ }
+ default:
+ if tok, err = dec.Token(); err != nil {
+ return err
+ }
+
+ var data []byte
+
+ switch t := tok.(type) {
+ case xml.EndElement:
+ return nil
+ case xml.CharData:
+ data = []byte(t.Copy())
+ default:
+ return invalidXmlError
+ }
+
+ switch typeName {
+ case "int", "i4", "i8":
+ if checkType(val, reflect.Interface) == nil && val.IsNil() {
+ i, err := strconv.ParseInt(string(data), 10, 64)
+ if err != nil {
+ return err
+ }
+
+ pi := reflect.New(reflect.TypeOf(i)).Elem()
+ pi.SetInt(i)
+ val.Set(pi)
+ } else if err = checkType(val, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64); err != nil {
+ return err
+ } else {
+ i, err := strconv.ParseInt(string(data), 10, val.Type().Bits())
+ if err != nil {
+ return err
+ }
+
+ val.SetInt(i)
+ }
+ case "string", "base64":
+ str := string(data)
+ if checkType(val, reflect.Interface) == nil && val.IsNil() {
+ pstr := reflect.New(reflect.TypeOf(str)).Elem()
+ pstr.SetString(str)
+ val.Set(pstr)
+ } else if err = checkType(val, reflect.String); err != nil {
+ return err
+ } else {
+ val.SetString(str)
+ }
+ case "dateTime.iso8601":
+ var t time.Time
+ var err error
+
+ for _, layout := range timeLayouts {
+ t, err = time.Parse(layout, string(data))
+ if err == nil {
+ break
+ }
+ }
+ if err != nil {
+ return err
+ }
+
+ if checkType(val, reflect.Interface) == nil && val.IsNil() {
+ ptime := reflect.New(reflect.TypeOf(t)).Elem()
+ ptime.Set(reflect.ValueOf(t))
+ val.Set(ptime)
+ } else if _, ok := val.Interface().(time.Time); !ok {
+ return TypeMismatchError(fmt.Sprintf("error: type mismatch error - can't decode %v to time", val.Kind()))
+ } else {
+ val.Set(reflect.ValueOf(t))
+ }
+ case "boolean":
+ v, err := strconv.ParseBool(string(data))
+ if err != nil {
+ return err
+ }
+
+ if checkType(val, reflect.Interface) == nil && val.IsNil() {
+ pv := reflect.New(reflect.TypeOf(v)).Elem()
+ pv.SetBool(v)
+ val.Set(pv)
+ } else if err = checkType(val, reflect.Bool); err != nil {
+ return err
+ } else {
+ val.SetBool(v)
+ }
+ case "double":
+ if checkType(val, reflect.Interface) == nil && val.IsNil() {
+ i, err := strconv.ParseFloat(string(data), 64)
+ if err != nil {
+ return err
+ }
+
+ pdouble := reflect.New(reflect.TypeOf(i)).Elem()
+ pdouble.SetFloat(i)
+ val.Set(pdouble)
+ } else if err = checkType(val, reflect.Float32, reflect.Float64); err != nil {
+ return err
+ } else {
+ i, err := strconv.ParseFloat(string(data), val.Type().Bits())
+ if err != nil {
+ return err
+ }
+
+ val.SetFloat(i)
+ }
+ default:
+ return errors.New("unsupported type")
+ }
+
+ // </type>
+ if err = dec.Skip(); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (dec *decoder) readTag() (string, []byte, error) {
+ var tok xml.Token
+ var err error
+
+ var name string
+ for {
+ if tok, err = dec.Token(); err != nil {
+ return "", nil, err
+ }
+
+ if t, ok := tok.(xml.StartElement); ok {
+ name = t.Name.Local
+ break
+ }
+ }
+
+ value, err := dec.readCharData()
+ if err != nil {
+ return "", nil, err
+ }
+
+ return name, value, dec.Skip()
+}
+
+func (dec *decoder) readCharData() ([]byte, error) {
+ var tok xml.Token
+ var err error
+
+ if tok, err = dec.Token(); err != nil {
+ return nil, err
+ }
+
+ if t, ok := tok.(xml.CharData); ok {
+ return []byte(t.Copy()), nil
+ } else {
+ return nil, invalidXmlError
+ }
+}
+
+func checkType(val reflect.Value, kinds ...reflect.Kind) error {
+ if len(kinds) == 0 {
+ return nil
+ }
+
+ if val.Kind() == reflect.Ptr {
+ val = val.Elem()
+ }
+
+ match := false
+
+ for _, kind := range kinds {
+ if val.Kind() == kind {
+ match = true
+ break
+ }
+ }
+
+ if !match {
+ return TypeMismatchError(fmt.Sprintf("error: type mismatch - can't unmarshal %v to %v",
+ val.Kind(), kinds[0]))
+ }
+
+ return nil
+}
Index: prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/decoder_test.go
===================================================================
--- /dev/null
+++ prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/decoder_test.go
@@ -0,0 +1,234 @@
+package xmlrpc
+
+import (
+ "fmt"
+ "io"
+ "io/ioutil"
+ "reflect"
+ "testing"
+ "time"
+
+ "golang.org/x/text/encoding/charmap"
+ "golang.org/x/text/transform"
+)
+
+type book struct {
+ Title string
+ Amount int
+}
+
+type bookUnexported struct {
+ title string
+ amount int
+}
+
+var unmarshalTests = []struct {
+ value interface{}
+ ptr interface{}
+ xml string
+}{
+ // int, i4, i8
+ {0, new(*int), "<value><int></int></value>"},
+ {100, new(*int), "<value><int>100</int></value>"},
+ {389451, new(*int), "<value><i4>389451</i4></value>"},
+ {int64(45659074), new(*int64), "<value><i8>45659074</i8></value>"},
+
+ // string
+ {"Once upon a time", new(*string), "<value><string>Once upon a time</string></value>"},
+ {"Mike & Mick <London, UK>", new(*string), "<value><string>Mike &amp; Mick &lt;London, UK&gt;</string></value>"},
+ {"Once upon a time", new(*string), "<value>Once upon a time</value>"},
+
+ // base64
+ {"T25jZSB1cG9uIGEgdGltZQ==", new(*string), "<value><base64>T25jZSB1cG9uIGEgdGltZQ==</base64></value>"},
+
+ // boolean
+ {true, new(*bool), "<value><boolean>1</boolean></value>"},
+ {false, new(*bool), "<value><boolean>0</boolean></value>"},
+
+ // double
+ {12.134, new(*float32), "<value><double>12.134</double></value>"},
+ {-12.134, new(*float32), "<value><double>-12.134</double></value>"},
+
+ // datetime.iso8601
+ {_time("2013-12-09T21:00:12Z"), new(*time.Time), "<value><dateTime.iso8601>20131209T21:00:12</dateTime.iso8601></value>"},
+ {_time("2013-12-09T21:00:12Z"), new(*time.Time), "<value><dateTime.iso8601>20131209T21:00:12Z</dateTime.iso8601></value>"},
+ {_time("2013-12-09T21:00:12-01:00"), new(*time.Time), "<value><dateTime.iso8601>20131209T21:00:12-01:00</dateTime.iso8601></value>"},
+ {_time("2013-12-09T21:00:12+01:00"), new(*time.Time), "<value><dateTime.iso8601>20131209T21:00:12+01:00</dateTime.iso8601></value>"},
+ {_time("2013-12-09T21:00:12Z"), new(*time.Time), "<value><dateTime.iso8601>2013-12-09T21:00:12</dateTime.iso8601></value>"},
+ {_time("2013-12-09T21:00:12Z"), new(*time.Time), "<value><dateTime.iso8601>2013-12-09T21:00:12Z</dateTime.iso8601></value>"},
+ {_time("2013-12-09T21:00:12-01:00"), new(*time.Time), "<value><dateTime.iso8601>2013-12-09T21:00:12-01:00</dateTime.iso8601></value>"},
+ {_time("2013-12-09T21:00:12+01:00"), new(*time.Time), "<value><dateTime.iso8601>2013-12-09T21:00:12+01:00</dateTime.iso8601></value>"},
+
+ // array
+ {[]int{1, 5, 7}, new(*[]int), "<value><array><data><value><int>1</int></value><value><int>5</int></value><value><int>7</int></value></data></array></value>"},
+ {[]interface{}{"A", "5"}, new(interface{}), "<value><array><data><value><string>A</string></value><value><string>5</string></value></data></array></value>"},
+ {[]interface{}{"A", int64(5)}, new(interface{}), "<value><array><data><value><string>A</string></value><value><int>5</int></value></data></array></value>"},
+
+ // struct
+ {book{"War and Piece", 20}, new(*book), "<value><struct><member><name>Title</name><value><string>War and Piece</string></value></member><member><name>Amount</name><value><int>20</int></value></member></struct></value>"},
+ {bookUnexported{}, new(*bookUnexported), "<value><struct><member><name>title</name><value><string>War and Piece</string></value></member><member><name>amount</name><value><int>20</int></value></member></struct></value>"},
+ {map[string]interface{}{"Name": "John Smith"}, new(interface{}), "<value><struct><member><name>Name</name><value><string>John Smith</string></value></member></struct></value>"},
+ {map[string]interface{}{}, new(interface{}), "<value><struct></struct></value>"},
+}
+
+func _time(s string) time.Time {
+ t, err := time.Parse(time.RFC3339, s)
+ if err != nil {
+ panic(fmt.Sprintf("time parsing error: %v", err))
+ }
+ return t
+}
+
+func Test_unmarshal(t *testing.T) {
+ for _, tt := range unmarshalTests {
+ v := reflect.New(reflect.TypeOf(tt.value))
+ if err := unmarshal([]byte(tt.xml), v.Interface()); err != nil {
+ t.Fatalf("unmarshal error: %v", err)
+ }
+
+ v = v.Elem()
+
+ if v.Kind() == reflect.Slice {
+ vv := reflect.ValueOf(tt.value)
+ if vv.Len() != v.Len() {
+ t.Fatalf("unmarshal error:\nexpected: %v\n got: %v", tt.value, v.Interface())
+ }
+ for i := 0; i < v.Len(); i++ {
+ if v.Index(i).Interface() != vv.Index(i).Interface() {
+ t.Fatalf("unmarshal error:\nexpected: %v\n got: %v", tt.value, v.Interface())
+ }
+ }
+ } else {
+ a1 := v.Interface()
+ a2 := interface{}(tt.value)
+
+ if !reflect.DeepEqual(a1, a2) {
+ t.Fatalf("unmarshal error:\nexpected: %v\n got: %v", tt.value, v.Interface())
+ }
+ }
+ }
+}
+
+func Test_unmarshalToNil(t *testing.T) {
+ for _, tt := range unmarshalTests {
+ if err := unmarshal([]byte(tt.xml), tt.ptr); err != nil {
+ t.Fatalf("unmarshal error: %v", err)
+ }
+ }
+}
+
+func Test_typeMismatchError(t *testing.T) {
+ var s string
+
+ encoded := "<value><int>100</int></value>"
+ var err error
+
+ if err = unmarshal([]byte(encoded), &s); err == nil {
+ t.Fatal("unmarshal error: expected error, but didn't get it")
+ }
+
+ if _, ok := err.(TypeMismatchError); !ok {
+ t.Fatal("unmarshal error: expected type mistmatch error, but didn't get it")
+ }
+}
+
+func Test_unmarshalEmptyValueTag(t *testing.T) {
+ var v int
+
+ if err := unmarshal([]byte("<value/>"), &v); err != nil {
+ t.Fatalf("unmarshal error: %v", err)
+ }
+}
+
+const structEmptyXML = `
+<value>
+ <struct>
+ </struct>
+</value>
+`
+
+func Test_unmarshalEmptyStruct(t *testing.T) {
+ var v interface{}
+ if err := unmarshal([]byte(structEmptyXML), &v); err != nil {
+ t.Fatal(err)
+ }
+ if v == nil {
+ t.Fatalf("got nil map")
+ }
+}
+
+const arrayValueXML = `
+<value>
+ <array>
+ <data>
+ <value><int>234</int></value>
+ <value><boolean>1</boolean></value>
+ <value><string>Hello World</string></value>
+ <value><string>Extra Value</string></value>
+ </data>
+ </array>
+</value>
+`
+
+func Test_unmarshalExistingArray(t *testing.T) {
+
+ var (
+ v1 int
+ v2 bool
+ v3 string
+
+ v = []interface{}{&v1, &v2, &v3}
+ )
+ if err := unmarshal([]byte(arrayValueXML), &v); err != nil {
+ t.Fatal(err)
+ }
+
+ // check pre-existing values
+ if want := 234; v1 != want {
+ t.Fatalf("want %d, got %d", want, v1)
+ }
+ if want := true; v2 != want {
+ t.Fatalf("want %t, got %t", want, v2)
+ }
+ if want := "Hello World"; v3 != want {
+ t.Fatalf("want %s, got %s", want, v3)
+ }
+ // check the appended result
+ if n := len(v); n != 4 {
+ t.Fatalf("missing appended result")
+ }
+ if got, ok := v[3].(string); !ok || got != "Extra Value" {
+ t.Fatalf("got %s, want %s", got, "Extra Value")
+ }
+}
+
+func Test_decodeNonUTF8Response(t *testing.T) {
+ data, err := ioutil.ReadFile("fixtures/cp1251.xml")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ CharsetReader = decode
+
+ var s string
+ if err = unmarshal(data, &s); err != nil {
+ fmt.Println(err)
+ t.Fatal("unmarshal error: cannot decode non utf-8 response")
+ }
+
+ expected := "Л.Н. Толстой - Война и Мир"
+
+ if s != expected {
+ t.Fatalf("unmarshal error:\nexpected: %v\n got: %v", expected, s)
+ }
+
+ CharsetReader = nil
+}
+
+func decode(charset string, input io.Reader) (io.Reader, error) {
+ if charset != "cp1251" {
+ return nil, fmt.Errorf("unsupported charset")
+ }
+
+ return transform.NewReader(input, charmap.Windows1251.NewDecoder()), nil
+}
Index: prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/encoder.go
===================================================================
--- /dev/null
+++ prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/encoder.go
@@ -0,0 +1,171 @@
+package xmlrpc
+
+import (
+ "bytes"
+ "encoding/xml"
+ "fmt"
+ "reflect"
+ "sort"
+ "strconv"
+ "time"
+)
+
+type encodeFunc func(reflect.Value) ([]byte, error)
+
+func marshal(v interface{}) ([]byte, error) {
+ if v == nil {
+ return []byte{}, nil
+ }
+
+ val := reflect.ValueOf(v)
+ return encodeValue(val)
+}
+
+func encodeValue(val reflect.Value) ([]byte, error) {
+ var b []byte
+ var err error
+
+ if val.Kind() == reflect.Ptr || val.Kind() == reflect.Interface {
+ if val.IsNil() {
+ return []byte("<value/>"), nil
+ }
+
+ val = val.Elem()
+ }
+
+ switch val.Kind() {
+ case reflect.Struct:
+ switch val.Interface().(type) {
+ case time.Time:
+ t := val.Interface().(time.Time)
+ b = []byte(fmt.Sprintf("<dateTime.iso8601>%s</dateTime.iso8601>", t.Format(iso8601)))
+ default:
+ b, err = encodeStruct(val)
+ }
+ case reflect.Map:
+ b, err = encodeMap(val)
+ case reflect.Slice:
+ b, err = encodeSlice(val)
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ b = []byte(fmt.Sprintf("<int>%s</int>", strconv.FormatInt(val.Int(), 10)))
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+ b = []byte(fmt.Sprintf("<i4>%s</i4>", strconv.FormatUint(val.Uint(), 10)))
+ case reflect.Float32, reflect.Float64:
+ b = []byte(fmt.Sprintf("<double>%s</double>",
+ strconv.FormatFloat(val.Float(), 'f', -1, val.Type().Bits())))
+ case reflect.Bool:
+ if val.Bool() {
+ b = []byte("<boolean>1</boolean>")
+ } else {
+ b = []byte("<boolean>0</boolean>")
+ }
+ case reflect.String:
+ var buf bytes.Buffer
+
+ xml.Escape(&buf, []byte(val.String()))
+
+ if _, ok := val.Interface().(Base64); ok {
+ b = []byte(fmt.Sprintf("<base64>%s</base64>", buf.String()))
+ } else {
+ b = []byte(fmt.Sprintf("<string>%s</string>", buf.String()))
+ }
+ default:
+ return nil, fmt.Errorf("xmlrpc encode error: unsupported type")
+ }
+
+ if err != nil {
+ return nil, err
+ }
+
+ return []byte(fmt.Sprintf("<value>%s</value>", string(b))), nil
+}
+
+func encodeStruct(val reflect.Value) ([]byte, error) {
+ var b bytes.Buffer
+
+ b.WriteString("<struct>")
+
+ t := val.Type()
+ for i := 0; i < t.NumField(); i++ {
+ b.WriteString("<member>")
+ f := t.Field(i)
+
+ name := f.Tag.Get("xmlrpc")
+ if name == "" {
+ name = f.Name
+ }
+ b.WriteString(fmt.Sprintf("<name>%s</name>", name))
+
+ p, err := encodeValue(val.FieldByName(f.Name))
+ if err != nil {
+ return nil, err
+ }
+ b.Write(p)
+
+ b.WriteString("</member>")
+ }
+
+ b.WriteString("</struct>")
+
+ return b.Bytes(), nil
+}
+
+var sortMapKeys bool
+
+func encodeMap(val reflect.Value) ([]byte, error) {
+ var t = val.Type()
+
+ if t.Key().Kind() != reflect.String {
+ return nil, fmt.Errorf("xmlrpc encode error: only maps with string keys are supported")
+ }
+
+ var b bytes.Buffer
+
+ b.WriteString("<struct>")
+
+ keys := val.MapKeys()
+
+ if sortMapKeys {
+ sort.Slice(keys, func(i, j int) bool { return keys[i].String() < keys[j].String() })
+ }
+
+ for i := 0; i < val.Len(); i++ {
+ key := keys[i]
+ kval := val.MapIndex(key)
+
+ b.WriteString("<member>")
+ b.WriteString(fmt.Sprintf("<name>%s</name>", key.String()))
+
+ p, err := encodeValue(kval)
+
+ if err != nil {
+ return nil, err
+ }
+
+ b.Write(p)
+ b.WriteString("</member>")
+ }
+
+ b.WriteString("</struct>")
+
+ return b.Bytes(), nil
+}
+
+func encodeSlice(val reflect.Value) ([]byte, error) {
+ var b bytes.Buffer
+
+ b.WriteString("<array><data>")
+
+ for i := 0; i < val.Len(); i++ {
+ p, err := encodeValue(val.Index(i))
+ if err != nil {
+ return nil, err
+ }
+
+ b.Write(p)
+ }
+
+ b.WriteString("</data></array>")
+
+ return b.Bytes(), nil
+}
Index: prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/encoder_test.go
===================================================================
--- /dev/null
+++ prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/encoder_test.go
@@ -0,0 +1,58 @@
+package xmlrpc
+
+import (
+ "testing"
+ "time"
+)
+
+var marshalTests = []struct {
+ value interface{}
+ xml string
+}{
+ {100, "<value><int>100</int></value>"},
+ {"Once upon a time", "<value><string>Once upon a time</string></value>"},
+ {"Mike & Mick <London, UK>", "<value><string>Mike &amp; Mick &lt;London, UK&gt;</string></value>"},
+ {Base64("T25jZSB1cG9uIGEgdGltZQ=="), "<value><base64>T25jZSB1cG9uIGEgdGltZQ==</base64></value>"},
+ {true, "<value><boolean>1</boolean></value>"},
+ {false, "<value><boolean>0</boolean></value>"},
+ {12.134, "<value><double>12.134</double></value>"},
+ {-12.134, "<value><double>-12.134</double></value>"},
+ {738777323.0, "<value><double>738777323</double></value>"},
+ {time.Unix(1386622812, 0).UTC(), "<value><dateTime.iso8601>20131209T21:00:12</dateTime.iso8601></value>"},
+ {[]interface{}{1, "one"}, "<value><array><data><value><int>1</int></value><value><string>one</string></value></data></array></value>"},
+ {&struct {
+ Title string
+ Amount int
+ }{"War and Piece", 20}, "<value><struct><member><name>Title</name><value><string>War and Piece</string></value></member><member><name>Amount</name><value><int>20</int></value></member></struct></value>"},
+ {&struct {
+ Value interface{} `xmlrpc:"value"`
+ }{}, "<value><struct><member><name>value</name><value/></member></struct></value>"},
+ {
+ map[string]interface{}{"title": "War and Piece", "amount": 20},
+ "<value><struct><member><name>amount</name><value><int>20</int></value></member><member><name>title</name><value><string>War and Piece</string></value></member></struct></value>",
+ },
+ {
+ map[string]interface{}{
+ "Name": "John Smith",
+ "Age": 6,
+ "Wight": []float32{66.67, 100.5},
+ "Dates": map[string]interface{}{"Birth": time.Date(1829, time.November, 10, 23, 0, 0, 0, time.UTC), "Death": time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)}},
+ "<value><struct><member><name>Age</name><value><int>6</int></value></member><member><name>Dates</name><value><struct><member><name>Birth</name><value><dateTime.iso8601>18291110T23:00:00</dateTime.iso8601></value></member><member><name>Death</name><value><dateTime.iso8601>20091110T23:00:00</dateTime.iso8601></value></member></struct></value></member><member><name>Name</name><value><string>John Smith</string></value></member><member><name>Wight</name><value><array><data><value><double>66.67</double></value><value><double>100.5</double></value></data></array></value></member></struct></value>",
+ },
+}
+
+func Test_marshal(t *testing.T) {
+ sortMapKeys = true
+
+ for _, tt := range marshalTests {
+ b, err := marshal(tt.value)
+ if err != nil {
+ t.Fatalf("unexpected marshal error: %v", err)
+ }
+
+ if string(b) != tt.xml {
+ t.Fatalf("marshal error:\nexpected: %s\n got: %s", tt.xml, string(b))
+ }
+
+ }
+}
Index: prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/fixtures/cp1251.xml
===================================================================
--- /dev/null
+++ prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/fixtures/cp1251.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="cp1251" ?>
+<methodResponse>
+ <params>
+ <param><value><string><3E>.<2E>. <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> - <20><><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD></string></value></param>
+ </params>
+</methodResponse>
\ No newline at end of file
Index: prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/request.go
===================================================================
--- /dev/null
+++ prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/request.go
@@ -0,0 +1,57 @@
+package xmlrpc
+
+import (
+ "bytes"
+ "fmt"
+ "net/http"
+)
+
+func NewRequest(url string, method string, args interface{}) (*http.Request, error) {
+ var t []interface{}
+ var ok bool
+ if t, ok = args.([]interface{}); !ok {
+ if args != nil {
+ t = []interface{}{args}
+ }
+ }
+
+ body, err := EncodeMethodCall(method, t...)
+ if err != nil {
+ return nil, err
+ }
+
+ request, err := http.NewRequest("POST", url, bytes.NewReader(body))
+ if err != nil {
+ return nil, err
+ }
+
+ request.Header.Set("Content-Type", "text/xml")
+ request.Header.Set("Content-Length", fmt.Sprintf("%d", len(body)))
+
+ return request, nil
+}
+
+func EncodeMethodCall(method string, args ...interface{}) ([]byte, error) {
+ var b bytes.Buffer
+ b.WriteString(`<?xml version="1.0" encoding="UTF-8"?>`)
+ b.WriteString(fmt.Sprintf("<methodCall><methodName>%s</methodName>", method))
+
+ if args != nil {
+ b.WriteString("<params>")
+
+ for _, arg := range args {
+ p, err := marshal(arg)
+ if err != nil {
+ return nil, err
+ }
+
+ b.WriteString(fmt.Sprintf("<param>%s</param>", string(p)))
+ }
+
+ b.WriteString("</params>")
+ }
+
+ b.WriteString("</methodCall>")
+
+ return b.Bytes(), nil
+}
Index: prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/response.go
===================================================================
--- /dev/null
+++ prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/response.go
@@ -0,0 +1,52 @@
+package xmlrpc
+
+import (
+ "regexp"
+)
+
+var (
+ faultRx = regexp.MustCompile(`<fault>(\s|\S)+</fault>`)
+)
+
+type failedResponse struct {
+ Code int `xmlrpc:"faultCode"`
+ Error string `xmlrpc:"faultString"`
+}
+
+func (r *failedResponse) err() error {
+ return &xmlrpcError{
+ code: r.Code,
+ err: r.Error,
+ }
+}
+
+type Response struct {
+ data []byte
+}
+
+func NewResponse(data []byte) *Response {
+ return &Response{
+ data: data,
+ }
+}
+
+func (r *Response) Failed() bool {
+ return faultRx.Match(r.data)
+}
+
+func (r *Response) Err() error {
+ failedResp := new(failedResponse)
+ if err := unmarshal(r.data, failedResp); err != nil {
+ return err
+ }
+
+ return failedResp.err()
+}
+
+func (r *Response) Unmarshal(v interface{}) error {
+ if err := unmarshal(r.data, v); err != nil {
+ return err
+ }
+
+ return nil
+}
Index: prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/response_test.go
===================================================================
--- /dev/null
+++ prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/response_test.go
@@ -0,0 +1,84 @@
+package xmlrpc
+
+import (
+ "testing"
+)
+
+const faultRespXml = `
+<?xml version="1.0" encoding="UTF-8"?>
+<methodResponse>
+ <fault>
+ <value>
+ <struct>
+ <member>
+ <name>faultString</name>
+ <value>
+ <string>You must log in before using this part of Bugzilla.</string>
+ </value>
+ </member>
+ <member>
+ <name>faultCode</name>
+ <value>
+ <int>410</int>
+ </value>
+ </member>
+ </struct>
+ </value>
+ </fault>
+</methodResponse>`
+
+func Test_failedResponse(t *testing.T) {
+ resp := NewResponse([]byte(faultRespXml))
+
+ if !resp.Failed() {
+ t.Fatal("Failed() error: expected true, got false")
+ }
+
+ if resp.Err() == nil {
+ t.Fatal("Err() error: expected error, got nil")
+ }
+
+ err := resp.Err().(*xmlrpcError)
+ if err.code != 410 && err.err != "You must log in before using this part of Bugzilla." {
+ t.Fatal("Err() error: got wrong error")
+ }
+}
+
+const emptyValResp = `
+<?xml version="1.0" encoding="UTF-8"?>
+<methodResponse>
+ <params>
+ <param>
+ <value>
+ <struct>
+ <member>
+ <name>user</name>
+ <value><string>Joe Smith</string></value>
+ </member>
+ <member>
+ <name>token</name>
+ <value/>
+ </member>
+ </struct>
+ </value>
+ </param>
+ </params>
+</methodResponse>`
+
+
+func Test_responseWithEmptyValue(t *testing.T) {
+ resp := NewResponse([]byte(emptyValResp))
+
+ result := struct{
+ User string `xmlrpc:"user"`
+ Token string `xmlrpc:"token"`
+ }{}
+
+ if err := resp.Unmarshal(&result); err != nil {
+ t.Fatalf("unmarshal error: %v", err)
+ }
+
+ if result.User != "Joe Smith" || result.Token != "" {
+ t.Fatalf("unexpected result: %v", result)
+ }
+}
Index: prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/test_server.rb
===================================================================
--- /dev/null
+++ prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/test_server.rb
@@ -0,0 +1,25 @@
+# encoding: utf-8
+
+require "xmlrpc/server"
+
+class Service
+ def time
+ Time.now
+ end
+
+ def upcase(s)
+ s.upcase
+ end
+
+ def sum(x, y)
+ x + y
+ end
+
+ def error
+ raise XMLRPC::FaultException.new(500, "Server error")
+ end
+end
+
+server = XMLRPC::Server.new 5001, 'localhost'
+server.add_handler "service", Service.new
+server.serve
Index: prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/xmlrpc.go
===================================================================
--- /dev/null
+++ prometheus-2.18.0/vendor/github.com/kolo/xmlrpc/xmlrpc.go
@@ -0,0 +1,19 @@
+package xmlrpc
+
+import (
+ "fmt"
+)
+
+// xmlrpcError represents errors returned on xmlrpc request.
+type xmlrpcError struct {
+ code int
+ err string
+}
+
+// Error() method implements Error interface
+func (e *xmlrpcError) Error() string {
+ return fmt.Sprintf("error: \"%s\" code: %d", e.err, e.code)
+}
+
+// Base64 represents value in base64 encoding
+type Base64 string