我正在尝试使用 PowerShell 和 Azure 管理 API 创建Azure 托管缓存,这两种方法是必需的,因为官方 Azure PowerShell Cmdlet对 Azure 托管缓存的创建和更新的支持非常有限。然而,从 PowerShell 调用 Azure 管理 API有一个既定模式。
Azure Managed Cache API 上的有限文档阻碍了我查找要调用的正确 API 的尝试。然而,在使用源代码和-Debug
PowerShell 中的选项完成 cmdlet 之后,我已经能够找到似乎正确的 API 端点,因此我开发了一些代码来访问这些端点。
但是,在 Azure API 接受 PUT 请求后,我就卡住了,因为对管理 API /操作端点的后续调用显示此操作的结果是Internal Server Error
.
我一直在使用Joseph Alabarhari 的LinqPad 来探索 API,因为它允许我使用尽可能少的代码快速迭代解决方案,因此要执行以下代码片段,您将需要LinqPad和 My Extensions 脚本中的以下扩展:
public static X509Certificate2 GetCertificate(this StoreLocation storeLocation, string thumbprint) {
var certificateStore = new X509Store(StoreName.My, storeLocation);
certificateStore.Open(OpenFlags.ReadOnly);
var certificates = certificateStore.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false);
return certificates[0];
}
包括包含在内的完整源代码如下所示:
- 我的扩展- 您可以通过右键单击左下方窗格中的我的扩展并选择“在 Windows 资源管理器中打开脚本位置”来替换“我的扩展”,然后用这个替换突出显示的文件。或者,您可能希望将我的扩展合并到您自己的中。
- Azure 托管缓存脚本- 您应该能够下载并双击它,一旦打开并且上述扩展和证书到位,您将能够执行该脚本。
在整个脚本中使用了以下设置,任何使用自己的 Azure 订阅 ID 和管理证书的人都需要以下变量:
var cacheName = "amc551aee";
var subscriptionId = "{{YOUR_SUBSCRIPTION_ID}}";
var certThumbprint = "{{YOUR_MANAGEMENT_CERTIFICATE_THUMBPRINT}}";
var endpoint = "management.core.windows.net";
var putPayloadXml = @"{{PATH_TO_PUT_PAYLOAD}}\cloudService.xml"
首先,我在HttpClient上做了一些设置:
var handler = new WebRequestHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.ClientCertificates.Add(StoreLocation.CurrentUser.GetCertificate(certThumbprint));
var client = new HttpClient(handler);
client.DefaultRequestHeaders.Add("x-ms-version", "2012-08-01");
这会将 HttpClient 配置为同时使用客户端证书和x-ms-version
标头,第一次调用 API 会获取包含 Azure 托管缓存的现有 CloudService。请注意,这使用的是空的 Azure 订阅。
var getResult = client.GetAsync("https://" + endpoint + "/" + subscriptionId + "/CloudServices");
getResult.Result.Dump("GET " + getResult.Result.RequestMessage.RequestUri);
该请求返回成功StatusCode: 200, ReasonPhrase: 'OK'
,然后我从请求中解析出一些关键信息:CloudService Name、Cache Name 和 Cache ETag:
var cacheDataReader = new XmlTextReader(getResult.Result.Content.ReadAsStreamAsync().Result);
var cacheData = XDocument.Load(cacheDataReader);
var ns = cacheData.Root.GetDefaultNamespace();
var nsManager = new XmlNamespaceManager(cacheDataReader.NameTable);
nsManager.AddNamespace("wa", "http://schemas.microsoft.com/windowsazure");
var cloudServices = cacheData.Root.Elements(ns + "CloudService");
var serviceName = String.Empty;
var ETag = String.Empty;
foreach (var cloudService in cloudServices) {
if (cloudService.XPathSelectElements("//wa:CloudService/wa:Resources/wa:Resource/wa:Name", nsManager).Select(x => x.Value).Contains(cacheName)) {
serviceName = cloudService.XPathSelectElement("//wa:CloudService/wa:Name", nsManager).Value;
ETag = cloudService.XPathSelectElement("//wa:CloudService/wa:Resources/wa:Resource/wa:ETag", nsManager).Value;
}
}
我预先创建了一个 XML 文件,其中包含以下 PUT 请求的有效负载:
<Resource xmlns="http://schemas.microsoft.com/windowsazure">
<IntrinsicSettings>
<CacheServiceInput xmlns="">
<SkuType>Standard</SkuType>
<Location>North Europe</Location>
<SkuCount>1</SkuCount>
<ServiceVersion>1.3.0</ServiceVersion>
<ObjectSizeInBytes>1024</ObjectSizeInBytes>
<NamedCaches>
<NamedCache>
<CacheName>default</CacheName>
<NotificationsEnabled>false</NotificationsEnabled>
<HighAvailabilityEnabled>false</HighAvailabilityEnabled>
<EvictionPolicy>LeastRecentlyUsed</EvictionPolicy>
</NamedCache>
<NamedCache>
<CacheName>richard</CacheName>
<NotificationsEnabled>true</NotificationsEnabled>
<HighAvailabilityEnabled>true</HighAvailabilityEnabled>
<EvictionPolicy>LeastRecentlyUsed</EvictionPolicy>
</NamedCache>
</NamedCaches>
</CacheServiceInput>
</IntrinsicSettings>
</Resource>
我用上面的 Payload 和一个由 CloudService 和缓存名称组成的 URL构造了一个HttpRequestMessage :
var resourceUrl = "https://" + endpoint + "/" + subscriptionId + "/cloudservices/" + serviceName + "/resources/cacheservice/Caching/" + cacheName;
var data = File.ReadAllText(putPayloadXml);
XDocument.Parse(data).Dump("Payload");
var message = new HttpRequestMessage(HttpMethod.Put, resourceUrl);
message.Headers.TryAddWithoutValidation("If-Match", ETag);
message.Content = new StringContent(data, Encoding.UTF8, "application/xml");
var putResult = client.SendAsync(message);
putResult.Result.Dump("PUT " + putResult.Result.RequestMessage.RequestUri);
putResult.Result.Content.ReadAsStringAsync().Result.Dump("Content " + putResult.Result.RequestMessage.RequestUri);
Azure 服务管理 API 在返回响应时名义上接受此请求StatusCode: 202, ReasonPhrase: 'Accepted'
;这实质上意味着负载已被接受并将离线处理,操作 ID 可以从 HTTP 标头中解析出来以获取更多信息:
var requestId = putResult.Result.Headers.GetValues("x-ms-request-id").FirstOrDefault();
这requestId
可用于请求更新操作状态:
var operation = client.GetAsync("https://" + endpoint + "/" + subscriptionId + "/operations/" + requestId);
operation.Result.Dump(requestId);
XDocument.Load(operation.Result.Content.ReadAsStreamAsync().Result).Dump("Operation " + requestId);
对 /operations 端点的请求会产生以下负载:
<Operation xmlns="http://schemas.microsoft.com/windowsazure" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<ID>5364614d-4d82-0f14-be41-175b3b85b480</ID>
<Status>Failed</Status>
<HttpStatusCode>500</HttpStatusCode>
<Error>
<Code>InternalError</Code>
<Message>The server encountered an internal error. Please retry the request.</Message>
</Error>
</Operation>
这就是我被卡住的地方,我很可能会以某种方式巧妙地破坏请求,以至于底层请求会引发 500 Internal Server Error,但是如果没有更详细的错误消息或 API 文档,我认为没有我可以去任何地方。