diff --git a/pkg/cloudmon/providerdriver/base.go b/pkg/cloudmon/providerdriver/base.go index e86949b94a..5c36c9cf9a 100644 --- a/pkg/cloudmon/providerdriver/base.go +++ b/pkg/cloudmon/providerdriver/base.go @@ -221,6 +221,7 @@ func (self *SCollectByResourceIdDriver) CollectServerMetrics(ctx context.Context RegionExtId: vm.RegionExtId, StartTime: start, EndTime: end, + OsType: vm.OsType, } opts.ResourceId = vm.ExternalId diff --git a/pkg/cloudprovider/metrics.go b/pkg/cloudprovider/metrics.go index cb81bc1a70..872c75fc58 100644 --- a/pkg/cloudprovider/metrics.go +++ b/pkg/cloudprovider/metrics.go @@ -358,6 +358,7 @@ type MetricListOptions struct { StartTime time.Time EndTime time.Time + OsType string Interval int // rds Engine string diff --git a/pkg/multicloud/azure/azure.go b/pkg/multicloud/azure/azure.go index de36578810..135924830a 100644 --- a/pkg/multicloud/azure/azure.go +++ b/pkg/multicloud/azure/azure.go @@ -64,8 +64,9 @@ const ( type TAzureResource string var ( - GraphResource = TAzureResource("graph") - DefaultResource = TAzureResource("default") + GraphResource = TAzureResource("graph") + DefaultResource = TAzureResource("default") + LoganalyticsResource = TAzureResource("loganalytics") ) type azureAuthClient struct { @@ -87,6 +88,8 @@ type SAzureClient struct { subscriptions []SSubscription debug bool + + workspaces []SLoganalyticsWorkspace } type AzureClientConfig struct { @@ -182,6 +185,13 @@ func (self *SAzureClient) getClient(resource TAzureResource) (*azureAuthClient, case GraphResource: ret.domain = env.GraphEndpoint conf.Resource = env.GraphEndpoint + case LoganalyticsResource: + ret.domain = env.ResourceIdentifiers.OperationalInsights + conf.Resource = env.ResourceIdentifiers.OperationalInsights + if conf.Resource == "N/A" && self.envName == "AzureChinaCloud" { + ret.domain = "https://api.loganalytics.azure.cn" + conf.Resource = ret.domain + } default: ret.domain = env.ResourceManagerEndpoint conf.Resource = env.ResourceManagerEndpoint @@ -213,6 +223,10 @@ func (self *SAzureClient) getGraphClient() (*azureAuthClient, error) { return self.getClient(GraphResource) } +func (self *SAzureClient) getLoganalyticsClient() (*azureAuthClient, error) { + return self.getClient(LoganalyticsResource) +} + func (self *SAzureClient) jsonRequest(method, path string, body jsonutils.JSONObject, params url.Values, showErrorMsg bool) (jsonutils.JSONObject, error) { cli, err := self.getDefaultClient() if err != nil { @@ -260,6 +274,18 @@ func (self *SAzureClient) jsonRequest(method, path string, body jsonutils.JSONOb return resp, err } +func (self *SAzureClient) ljsonRequest(method, path string, body jsonutils.JSONObject, params url.Values) (jsonutils.JSONObject, error) { + cli, err := self.getLoganalyticsClient() + if err != nil { + return nil, errors.Wrapf(err, "getLoganalyticsClient") + } + if params == nil { + params = url.Values{} + } + params.Set("api-version", "2021-12-01-preview") + return jsonRequest(cli.client, method, cli.domain, path, body, params, self.debug) +} + func (self *SAzureClient) gjsonRequest(method, path string, body jsonutils.JSONObject, params url.Values) (jsonutils.JSONObject, error) { cli, err := self.getGraphClient() if err != nil { @@ -512,6 +538,8 @@ func (self *SAzureClient) _apiVersion(resource string, params url.Values) string return "2020-06-01" } else if utils.IsInStringArray("microsoft.containerservice", info) { return "2021-05-01" + } else if utils.IsInStringArray("microsoft.operationalinsights", info) { + return "2021-12-01-preview" } return AZURE_API_VERSION } diff --git a/pkg/multicloud/azure/loganalytics.go b/pkg/multicloud/azure/loganalytics.go new file mode 100644 index 0000000000..83e79a5793 --- /dev/null +++ b/pkg/multicloud/azure/loganalytics.go @@ -0,0 +1,92 @@ +// Copyright 2019 Yunion +// +// 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 azure + +import ( + "fmt" + "net/url" + "time" + + "yunion.io/x/onecloud/pkg/util/httputils" +) + +type SLoganalyticsWorkspace struct { + Id string `json:"id"` + Location string `json:"location"` + Name string `json:"name"` + SLoganalyticsWorkspaceProperties SLoganalyticsWorkspaceProperties `json:"properties"` + Type string `json:"type"` +} + +type SLoganalyticsWorkspaceProperties struct { + CreatedDate string `json:"createdDate"` + CustomerId string `json:"customerId"` + SLoganalyticsWorkspacePropertiesFeatures SLoganalyticsWorkspacePropertiesFeatures `json:"features"` + ModifiedDate string `json:"modifiedDate"` + ProvisioningState string `json:"provisioningState"` + PublicNetworkAccessForIngestion string `json:"publicNetworkAccessForIngestion"` + PublicNetworkAccessForQuery string `json:"publicNetworkAccessForQuery"` + RetentionInDays int `json:"retentionInDays"` + SLoganalyticsWorkspacePropertiesSku SLoganalyticsWorkspacePropertiesSku `json:"sku"` + Source string `json:"source"` + SLoganalyticsWorkspacePropertiesWorkspaceCapping SLoganalyticsWorkspacePropertiesWorkspaceCapping `json:"workspaceCapping"` +} + +type SLoganalyticsWorkspacePropertiesWorkspaceCapping struct { + DailyQuotaGb int `json:"dailyQuotaGb"` + DataIngestionStatus string `json:"dataIngestionStatus"` + QuotaNextResetTime string `json:"quotaNextResetTime"` +} + +type SLoganalyticsWorkspacePropertiesSku struct { + LastSkuUpdate string `json:"lastSkuUpdate"` + Name string `json:"name"` +} + +type SLoganalyticsWorkspacePropertiesFeatures struct { + EnableLogAccessUsingOnlyResourcePermissions bool `json:"enableLogAccessUsingOnlyResourcePermissions"` + Legacy int `json:"legacy"` + SearchVersion int `json:"searchVersion"` +} + +func (self *SAzureClient) GetLoganalyticsWorkspaces() ([]SLoganalyticsWorkspace, error) { + if len(self.workspaces) > 0 { + return self.workspaces, nil + } + self.workspaces = []SLoganalyticsWorkspace{} + return self.workspaces, self.list("Microsoft.OperationalInsights/workspaces", url.Values{}, &self.workspaces) +} + +type WorkspaceData struct { + Name string + Columns []struct { + Name string + Type string + } + Rows [][]string +} + +func (self *SAzureClient) GetInstanceDiskUsage(workspace string, instanceId string, start, end time.Time) ([]WorkspaceData, error) { + params := url.Values{} + params.Set("timespan", "P1D") + query := fmt.Sprintf(`Perf | where ObjectName == "LogicalDisk" or ObjectName == "Logical Disk" | where _ResourceId == "%s" | where InstanceName != "_Total" | where CounterName == "%% Free Space" | where TimeGenerated between(datetime("%s") .. datetime("%s")) | project TimeGenerated, InstanceName, CounterValue, Computer, _ResourceId`, instanceId, start.Format(time.RFC3339), end.Format(time.RFC3339)) + params.Set("query", query) + resp, err := self.ljsonRequest(string(httputils.GET), fmt.Sprintf("v1/workspaces/%s/query", workspace), nil, params) + if err != nil { + return nil, err + } + ret := []WorkspaceData{} + return ret, resp.Unmarshal(&ret, "tables") +} diff --git a/pkg/multicloud/azure/monitor.go b/pkg/multicloud/azure/monitor.go index e8ceaeda3d..2771e7b2b7 100644 --- a/pkg/multicloud/azure/monitor.go +++ b/pkg/multicloud/azure/monitor.go @@ -19,11 +19,13 @@ import ( "fmt" "net/http" "net/url" + "strconv" "strings" "time" "yunion.io/x/log" "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/timeutils" "yunion.io/x/pkg/utils" api "yunion.io/x/onecloud/pkg/apis/compute" @@ -234,6 +236,48 @@ func (self *SAzureClient) GetEcsMetrics(opts *cloudprovider.MetricListOptions) ( } } + if len(opts.OsType) == 0 || strings.Contains(strings.ToLower(opts.OsType), "win") { + workspaces, err := self.GetLoganalyticsWorkspaces() + if err != nil { + return ret, nil + } + winmetric := cloudprovider.MetricValues{} + winmetric.MetricType = cloudprovider.VM_METRIC_TYPE_DISK_USAGE + winmetric.Values = []cloudprovider.MetricValue{} + for i := range workspaces { + data, err := self.GetInstanceDiskUsage(workspaces[i].SLoganalyticsWorkspaceProperties.CustomerId, opts.ResourceId, opts.StartTime, opts.EndTime) + if err != nil { + continue + } + for i := range data { + for j := range data[i].Rows { + if len(data[i].Rows[j]) == 5 { + date, device, free := data[i].Rows[j][0], data[i].Rows[j][1], data[i].Rows[j][2] + dataTime, err := timeutils.ParseTimeStr(date) + if err != nil { + continue + } + if dataTime.Second() > 15 { + continue + } + freeSize, err := strconv.ParseFloat(free, 64) + if err != nil { + continue + } + winmetric.Values = append(winmetric.Values, cloudprovider.MetricValue{ + Timestamp: dataTime, + Value: 100 - freeSize, + Tags: map[string]string{cloudprovider.METRIC_TAG_DEVICE: device}, + }) + } + } + } + } + if len(winmetric.Values) > 0 { + ret = append(ret, winmetric) + } + } + return ret, nil } diff --git a/pkg/multicloud/azure/shell/loganalytics.go b/pkg/multicloud/azure/shell/loganalytics.go new file mode 100644 index 0000000000..486e9139a8 --- /dev/null +++ b/pkg/multicloud/azure/shell/loganalytics.go @@ -0,0 +1,52 @@ +// Copyright 2019 Yunion +// +// 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 shell + +import ( + "time" + + "yunion.io/x/onecloud/pkg/multicloud/azure" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type LoganalyticsWorkspaceListOptions struct { + } + shellutils.R(&LoganalyticsWorkspaceListOptions{}, "loganalytics-workspace-list", "List loganalytics workspaces", func(cli *azure.SRegion, args *LoganalyticsWorkspaceListOptions) error { + workspaces, err := cli.GetClient().GetLoganalyticsWorkspaces() + if err != nil { + return err + } + printList(workspaces, len(workspaces), 0, 0, []string{}) + return nil + }) + + type DiskUsageListOptions struct { + WORKSPACE_ID string + NAME string + START time.Time + END time.Time + } + + shellutils.R(&DiskUsageListOptions{}, "disk-usage-list", "List disk usage", func(cli *azure.SRegion, args *DiskUsageListOptions) error { + usage, err := cli.GetClient().GetInstanceDiskUsage(args.WORKSPACE_ID, args.NAME, args.START, args.END) + if err != nil { + return err + } + printList(usage, len(usage), 0, 0, []string{}) + return nil + }) + +}