//-----------------------------------------------------------------------------
//
// Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License").
// You may not use this file except in compliance with the License.
// A copy of the License is located at
//
// http://aws.amazon.com/apache2.0
//
// or in the "license" file accompanying this file. This file 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.
//
//-----------------------------------------------------------------------------
using System.Collections.Generic;
using System.Threading.Tasks;
using System;
using Amazon.XRay.Recorder.Core.Internal.Utils;
using Amazon.Runtime.Internal.Util;
using Amazon.XRay.Recorder.Core.Sampling.Model;
using ThirdParty.LitJson;
using System.Text;
using System.Net.Http;
namespace Amazon.XRay.Recorder.Core.Sampling
{
///
/// Connector class that translates Sampling poller functions to
/// actual X-Ray back-end APIs and communicates with X-Ray daemon as the
/// signing proxy.
///
class ServiceConnector : IConnector
{
private static readonly Logger _logger = Logger.GetLogger(typeof(ServiceConnector));
private XRayConfig _xrayConfig;
private readonly object _xrayClientLock = new object();
private const int Version = 1;
private readonly DaemonConfig _daemonConfig;
private readonly DateTime EpochStart = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
private static readonly HttpClient _httpClient = new HttpClient();
///
/// Client id for the instance. Its 24 digit hex number.
///
public string ClientID;
public ServiceConnector(DaemonConfig daemonConfig, XRayConfig xrayConfig)
{
ClientID = ThreadSafeRandom.GenerateHexNumber(24);
if (daemonConfig == null)
{
daemonConfig = DaemonConfig.GetEndPoint();
}
_daemonConfig = daemonConfig;
if (xrayConfig == null)
{
xrayConfig = CreateXRayConfig();
}
_xrayConfig = xrayConfig;
}
private XRayConfig CreateXRayConfig()
{
var config = new XRayConfig();
config.ServiceURL = $"http://{_daemonConfig.TCPEndpoint.Address}:{_daemonConfig.TCPEndpoint.Port}";
return config;
}
private void RefreshEndPoint()
{
var serviceUrlCandidate = $"http://{_daemonConfig.TCPEndpoint.Address}:{_daemonConfig.TCPEndpoint.Port}";
if (serviceUrlCandidate.Equals(_xrayConfig.ServiceURL)) return; // endpoint do not need refreshing
_xrayConfig.ServiceURL = serviceUrlCandidate;
_logger.DebugFormat($"ServiceConnector Endpoint refreshed to: {_xrayConfig.ServiceURL}");
}
///
/// Get the sampling rules from X-Ray service.The call is proxied and signed by X-Ray Daemon.
///
///
public async Task GetSamplingRules()
{
Task responseTask;
lock (_xrayClientLock)
{
RefreshEndPoint();
responseTask = ServiceConnector.GetSamplingInfoAsync(_xrayConfig.ServiceURL + "/GetSamplingRules", string.Empty);
}
var responseContent = await responseTask;
List samplingRules = ServiceConnector.UnmarshallSamplingRuleResponse(responseContent);
GetSamplingRulesResponse result = new GetSamplingRulesResponse(samplingRules);
return result;
}
private static async Task GetSamplingInfoAsync(string url, string content)
{
using (var stringContent = new StringContent(content, Encoding.UTF8, "application/json"))
{
// Need to set header "ExpectContinue" as false for Daemon to sign properly.
// https://github.com/aws/aws-sdk-net/blob/master/sdk/src/Core/Amazon.Runtime/Internal/AmazonWebServiceRequest.cs#L41
_httpClient.DefaultRequestHeaders.ExpectContinue = false;
using (var response = await _httpClient.PostAsync(url, stringContent))
{
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
}
private static List UnmarshallSamplingRuleResponse(string responseContent)
{
List samplingRules = new List();
var samplingRuleResponse = JsonMapper.ToObject(responseContent);
foreach (var samplingRuleRecord in samplingRuleResponse.SamplingRuleRecords)
{
var samplingRuleModel = samplingRuleRecord.SamplingRule;
if (samplingRuleModel.Version.GetValueOrDefault() == Version && SamplingRule.IsValid(samplingRuleModel))
{
var samplingRule = new SamplingRule
(
samplingRuleModel.RuleName,
samplingRuleModel.Priority.GetValueOrDefault(),
samplingRuleModel.FixedRate.GetValueOrDefault(),
samplingRuleModel.ReservoirSize.GetValueOrDefault(),
samplingRuleModel.Host,
samplingRuleModel.ServiceName,
samplingRuleModel.HTTPMethod,
samplingRuleModel.URLPath,
samplingRuleModel.ServiceType,
samplingRuleModel.ResourceARN,
samplingRuleModel.Attributes
);
samplingRules.Add(samplingRule);
}
}
return samplingRules;
}
///
/// Report the current statistics of sampling rules and
/// get back the new assigned quota/TTL/Interval from the X-Ray service.
/// The call is proxied and signed via X-Ray Daemon.
///
/// List of .
/// Instance of .
public async Task GetSamplingTargets(List rules)
{
DateTime currentTime = TimeStamp.CurrentDateTime();
List samplingStatisticsDocumentModels = GetSamplingStatisticsDocuments(rules, currentTime);
var samplingStatisticsModel = new SamplingStatisticsModel();
samplingStatisticsModel.SamplingStatisticsDocuments = samplingStatisticsDocumentModels;
string requestContent = JsonMapper.ToJson(samplingStatisticsModel); // Marshall SamplingStatisticsDocument to json
Task responseTask;
lock (_xrayClientLock)
{
RefreshEndPoint();
responseTask = ServiceConnector.GetSamplingInfoAsync(_xrayConfig.ServiceURL + "/SamplingTargets", requestContent);
}
var responseContent = await responseTask;
var samplingTargetResponse = ServiceConnector.UnmarshallSamplingTargetResponse(responseContent);
var targetList = ConvertTargetList(samplingTargetResponse.SamplingTargetDocuments);
GetSamplingTargetsResponse result = new GetSamplingTargetsResponse(targetList);
result.RuleFreshness = new TimeStamp(ConvertDoubleToDateTime(samplingTargetResponse.LastRuleModification));
return result;
}
private List ConvertTargetList(List targetModels)
{
List result = new List();
foreach (var targetModel in targetModels)
{
Target t = new Target
(
targetModel.RuleName,
targetModel.FixedRate.GetValueOrDefault(),
targetModel.ReservoirQuota.GetValueOrDefault(),
ConvertDoubleToDateTime(targetModel.ReservoirQuotaTTL),
targetModel.Interval.GetValueOrDefault()
);
result.Add(t);
}
return result;
}
private static SamplingTargetResponseModel UnmarshallSamplingTargetResponse(string responseContent)
{
var samplingTargetResponse = JsonMapper.ToObject(responseContent);
return samplingTargetResponse;
}
private List GetSamplingStatisticsDocuments(List rules, DateTime currentTime)
{
List samplingStatisticsDocumentModels = new List();
foreach (var rule in rules)
{
Statistics statistics = rule.SnapShotStatistics();
SamplingStatisticsDocumentModel item = new SamplingStatisticsDocumentModel();
item.ClientID = ClientID;
item.RuleName = rule.RuleName;
item.RequestCount = statistics.RequestCount;
item.SampledCount = statistics.SampledCount;
item.BorrowCount = statistics.BorrowCount;
item.Timestamp = ConvertDateTimeToDouble(currentTime);
samplingStatisticsDocumentModels.Add(item);
}
return samplingStatisticsDocumentModels;
}
private double ConvertDateTimeToDouble(DateTime currentTime)
{
var current = new TimeSpan(currentTime.ToUniversalTime().Ticks - EpochStart.Ticks);
return Math.Round(current.TotalMilliseconds, 0) / 1000.0;
}
private DateTime ConvertDoubleToDateTime(double? seconds)
{
return seconds == null ? default(DateTime) : EpochStart.AddSeconds(seconds.GetValueOrDefault());
}
}
}