Additional code cleanup

This commit is contained in:
Gunnar Aasen 2018-05-07 09:52:54 -07:00
parent 78e5f52966
commit 4b8d0ad35d
5 changed files with 43 additions and 153 deletions

View File

@ -49,7 +49,7 @@ The resourceId used for Azure Monitor metrics.
## specified, the plugin will attempt to retrieve the resource ID
## of the VM via the instance metadata service (optional if running
## on an Azure VM with MSI)
#resourceId = "/subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.Compute/virtualMachines/<vm-name>"
#resource_id = "/subscriptions/<subscription_id>/resourceGroups/<resource_group>/providers/Microsoft.Compute/virtualMachines/<vm_name>"
## Azure region to publish metrics against. Defaults to eastus.
## Leave blank to automatically query the region via MSI.
#region = "useast"

View File

@ -80,24 +80,14 @@ func (m *msiToken) parseTimes() {
}
}
// ExpiresAt is the time at which the token expires
func (m *msiToken) ExpiresAt() time.Time {
return m.expiresAt
}
// ExpiresInDuration returns the duration until the token expires
func (m *msiToken) ExpiresInDuration() time.Duration {
func (m *msiToken) expiresInDuration() time.Duration {
expiresDuration := m.expiresAt.Sub(time.Now().UTC())
return expiresDuration
}
// NotBeforeTime returns the time at which the token becomes valid
func (m *msiToken) NotBeforeTime() time.Time {
return m.notBefore
}
// GetMsiToken retrieves a managed service identity token from the specified port on the local VM
func (a *AzureMonitor) getMsiToken(clientID string, resourceID string) (*msiToken, error) {
func (a *AzureMonitor) getMsiToken(clientID string) (*msiToken, error) {
// Acquire an MSI token. Documented at:
// https://docs.microsoft.com/en-us/azure/active-directory/managed-service-identity/how-to-use-vm-token
//
@ -110,13 +100,9 @@ func (a *AzureMonitor) getMsiToken(clientID string, resourceID string) (*msiToke
return nil, err
}
// Resource ID defaults to https://management.azure.com
if resourceID == "" {
resourceID = "https://management.azure.com"
}
msiParameters := url.Values{}
msiParameters.Add("resource", resourceID)
// Resource ID defaults to https://management.azure.com
msiParameters.Add("resource", defaultMSIResource)
msiParameters.Add("api-version", "2018-02-01")
// Client id is optional
@ -143,7 +129,7 @@ func (a *AzureMonitor) getMsiToken(clientID string, resourceID string) (*msiToke
}
if resp.StatusCode >= 300 || resp.StatusCode < 200 {
return nil, fmt.Errorf("Post Error. HTTP response code:%d message:%s, content: %s",
return nil, fmt.Errorf("E! Get Error. %d HTTP response: %s response body: %s",
resp.StatusCode, resp.Status, reply)
}
@ -156,41 +142,45 @@ func (a *AzureMonitor) getMsiToken(clientID string, resourceID string) (*msiToke
return &token, nil
}
const (
vmInstanceMetadataURL = "http://169.254.169.254/metadata/instance?api-version=2017-12-01"
msiInstanceMetadataURL = "http://169.254.169.254/metadata/identity/oauth2/token"
)
// GetInstanceMetadata retrieves metadata about the current Azure VM
func (a *AzureMonitor) GetInstanceMetadata() (*VirtualMachineMetadata, error) {
func (a *AzureMonitor) GetInstanceMetadata() error {
req, err := http.NewRequest("GET", vmInstanceMetadataURL, nil)
if err != nil {
return nil, fmt.Errorf("Error creating HTTP request")
return fmt.Errorf("Error creating HTTP request")
}
req.Header.Set("Metadata", "true")
resp, err := a.client.Do(req)
if err != nil {
return nil, err
return err
}
defer resp.Body.Close()
reply, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
return err
}
if resp.StatusCode >= 300 || resp.StatusCode < 200 {
return nil, fmt.Errorf("Post Error. HTTP response code:%d message:%s reply:\n%s",
return fmt.Errorf("Post Error. HTTP response code:%d message:%s reply:\n%s",
resp.StatusCode, resp.Status, reply)
}
var metadata VirtualMachineMetadata
if err := json.Unmarshal(reply, &metadata); err != nil {
return nil, err
return err
}
metadata.AzureResourceID = fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/virtualMachines/%s",
metadata.Compute.SubscriptionID, metadata.Compute.ResourceGroupName, metadata.Compute.Name)
return &metadata, nil
if a.ResourceID == "" {
a.ResourceID = fmt.Sprintf(resourceIDTemplate,
metadata.Compute.SubscriptionID, metadata.Compute.ResourceGroupName, metadata.Compute.Name)
}
if a.Region == "" {
a.Region = metadata.Compute.Location
}
a.url = fmt.Sprintf(urlTemplate, a.Region, a.ResourceID)
return nil
}

View File

@ -1,44 +0,0 @@
package azuremonitor
import (
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestGetMetadata(t *testing.T) {
azureMetadata := &AzureMonitor{}
metadata, err := azureMetadata.GetInstanceMetadata()
require.NoError(t, err)
require.NotNil(t, metadata)
require.NotEmpty(t, metadata.AzureResourceID)
require.NotEmpty(t, metadata.Compute.Location)
// if err != nil {
// t.Logf("could not get metadata: %v\n", err)
// } else {
// t.Logf("resource id \n%s", metadata.AzureResourceID)
// t.Logf("metadata is \n%v", metadata)
// }
//fmt.Printf("metadata is \n%v", metadata)
}
func TestGetTOKEN(t *testing.T) {
azureMetadata := &AzureMonitor{}
resourceID := "https://ingestion.monitor.azure.com/"
token, err := azureMetadata.getMsiToken("", resourceID)
require.NoError(t, err)
require.NotEmpty(t, token.AccessToken)
require.EqualValues(t, token.Resource, resourceID)
t.Logf("token is %+v\n", token)
t.Logf("expiry time is %s\n", token.ExpiresAt().Format(time.RFC3339))
t.Logf("expiry duration is %s\n", token.ExpiresInDuration().String())
t.Logf("resource is %s\n", token.Resource)
}

View File

@ -35,7 +35,7 @@ type AzureMonitor struct {
AzureClientSecret string `toml:"azure_client_secret"`
StringAsDimension bool `toml:"string_as_dimension"`
url string
url string
msiToken *msiToken
oauthConfig *adal.OAuthConfig
adalToken adal.OAuthTokenProvider
@ -51,11 +51,12 @@ type aggregate struct {
}
const (
defaultRegion string = "eastus"
defaultMSIResource string = "https://monitoring.azure.com/"
urlTemplate string = "https://%s.monitoring.azure.com%s/metrics"
defaultRegion string = "eastus"
defaultMSIResource string = "https://monitoring.azure.com/"
urlTemplate string = "https://%s.monitoring.azure.com%s/metrics"
resourceIDTemplate string = "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/virtualMachines/%s"
vmInstanceMetadataURL string = "http://169.254.169.254/metadata/instance?api-version=2017-12-01"
msiInstanceMetadataURL string = "http://169.254.169.254/metadata/identity/oauth2/token"
)
var sampleConfig = `
@ -63,7 +64,7 @@ var sampleConfig = `
## specified, the plugin will attempt to retrieve the resource ID
## of the VM via the instance metadata service (optional if running
## on an Azure VM with MSI)
#resource_id = "/subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.Compute/virtualMachines/<vm-name>"
#resource_id = "/subscriptions/<subscription_id>/resourceGroups/<resource_group>/providers/Microsoft.Compute/virtualMachines/<vm_name>"
## Azure region to publish metrics against. Defaults to eastus.
## Leave blank to automatically query the region via MSI.
#region = "useast"
@ -110,35 +111,27 @@ func (a *AzureMonitor) Connect() error {
if a.AzureSubscriptionID == "" && a.AzureTenantID == "" && a.AzureClientID == "" && a.AzureClientSecret == "" {
a.useMsi = true
} else if a.AzureSubscriptionID == "" || a.AzureTenantID == "" || a.AzureClientID == "" || a.AzureClientSecret == "" {
return fmt.Errorf("Must provide values for azureSubscription, azureTenant, azureClient and azureClientSecret, or leave all blank to default to MSI")
return fmt.Errorf("E! Must provide values for azure_subscription, azure_tenant, azure_client and azure_client_secret, or leave all blank to default to MSI")
}
if !a.useMsi {
// If using direct AD authentication create the AD access client
oauthConfig, err := adal.NewOAuthConfig(azure.PublicCloud.ActiveDirectoryEndpoint, a.AzureTenantID)
if err != nil {
return fmt.Errorf("Could not initialize AD client: %s", err)
return fmt.Errorf("E! Could not initialize AD client: %s", err)
}
a.oauthConfig = oauthConfig
}
// Validate the resource identifier
metadata, err := a.GetInstanceMetadata()
if err != nil {
return fmt.Errorf("No resource id specified, and Azure Instance metadata service not available. If not running on an Azure VM, provide a value for resourceId")
}
a.ResourceID = metadata.AzureResourceID
if a.Region == "" {
a.Region = metadata.Compute.Location
// Pull region and resource identifier
err := a.GetInstanceMetadata()
if err != nil && a.ResourceID == "" && a.Region == "" {
return fmt.Errorf("E! No resource id specified, and Azure Instance metadata service not available. If not running on an Azure VM, provide a value for resource_id")
}
a.url := fmt.Sprintf(urlTemplate, a.Region, a.ResourceID)
// Validate credentials
err = a.validateCredentials()
if err != nil {
return err
return fmt.Errorf("E! Unable to fetch authentication credentials: %v", err)
}
a.Reset()
@ -149,8 +142,8 @@ func (a *AzureMonitor) Connect() error {
func (a *AzureMonitor) validateCredentials() error {
if a.useMsi {
// Check expiry on the token
if a.msiToken == nil || a.msiToken.ExpiresInDuration() < time.Minute {
msiToken, err := a.getMsiToken(a.AzureClientID, defaultMSIResource)
if a.msiToken == nil || a.msiToken.expiresInDuration() < time.Minute {
msiToken, err := a.getMsiToken(a.AzureClientID)
if err != nil {
return err
}
@ -227,7 +220,7 @@ func (a *AzureMonitor) Write(metrics []telegraf.Metric) error {
}
if err := a.validateCredentials(); err != nil {
return fmt.Errorf("Error authenticating: %v", err)
return fmt.Errorf("E! Unable to fetch authentication credentials: %v", err)
}
req, err := http.NewRequest("POST", a.url, bytes.NewBuffer(body))
@ -249,7 +242,7 @@ func (a *AzureMonitor) Write(metrics []telegraf.Metric) error {
if err != nil {
reply = nil
}
return fmt.Errorf("Post Error. HTTP response code:%d message:%s reply:\n%s",
return fmt.Errorf("E! Get Error. %d HTTP response: %s response body: %s",
resp.StatusCode, resp.Status, reply)
}

View File

@ -7,23 +7,6 @@ import (
"github.com/influxdata/telegraf/metric"
)
// func TestDefaultConnectAndWrite(t *testing.T) {
// if testing.Short() {
// t.Skip("Skipping integration test in short mode")
// }
// // Test with all defaults (MSI+IMS)
// azmon := &AzureMonitor{}
// // Verify that we can connect to Log Analytics
// err := azmon.Connect()
// require.NoError(t, err)
// // Verify that we can write a metric to Log Analytics
// err = azmon.Write(testutil.MockMetrics())
// require.NoError(t, err)
// }
// MockMetrics returns a mock []telegraf.Metric object for using in unit tests
// of telegraf output sinks.
func getMockMetrics() []telegraf.Metric {
@ -55,35 +38,3 @@ func getTestMetric(value interface{}, name ...string) telegraf.Metric {
)
return pt
}
// func TestPostData(t *testing.T) {
// azmon := &AzureMonitor{
// Region: "eastus",
// }
// err := azmon.Connect()
// metrics := getMockMetrics()
// t.Logf("mock metrics are %+v\n", metrics)
// // metricsList, err := azmon.add(&metrics[0])
// for _, m := range metrics {
// azmon.Add(m)
// }
// jsonBytes, err := json.Marshal(azmon.cache)
// t.Logf("json content is:\n----------\n%s\n----------\n", string(jsonBytes))
// req, err := azmon.postData(&jsonBytes)
// if err != nil {
// // t.Logf("Error publishing metrics %s", err)
// t.Logf("url is %+v\n", req.URL)
// // t.Logf("failed request is %+v\n", req)
// // raw, err := httputil.DumpRequestOut(req, true)
// // if err != nil {
// // t.Logf("Request detail is \n%s\n", string(raw))
// // } else {
// // t.Logf("could not dump request: %s\n", err)
// // }
// }
// require.NoError(t, err)
// }