1

我正在尝试使用 PowerShell 和 Azure 管理 API 创建Azure 托管缓存,这两种方法是必需的,因为官方 Azure PowerShell Cmdlet对 Azure 托管缓存的创建和更新的支持非常有限。然而,从 PowerShell 调用 Azure 管理 API有一个既定模式。

Azure Managed Cache API 上的有限文档阻碍了我查找要调用的正确 API 的尝试。然而,在使用源代码和-DebugPowerShell 中的选项完成 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 文档,我认为没有我可以去任何地方。

4

1 回答 1

1

我们与 Richard 离线合作,下面的 XML 有效负载让他畅通无阻。注意 - 在现有缓存​​中添加/删除命名缓存时,对象大小是固定的。

注意 2- Azure 托管缓存 ​​API 对元素和元素之间的空格很敏感。

另请注意,我们正在努力将命名缓存功能添加到我们的 PowerShell 本身,因此人们不必使用 API 来执行此操作。

<Resource xmlns="http://schemas.microsoft.com/windowsazure" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
  <IntrinsicSettings><CacheServiceInput xmlns="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <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>
        <ExpirationSettings>
          <TimeToLiveInMinutes>10</TimeToLiveInMinutes>
          <Type>Absolute</Type>
        </ExpirationSettings>
      </NamedCache>
      <NamedCache>
        <CacheName>richard</CacheName>
        <NotificationsEnabled>false</NotificationsEnabled>
        <HighAvailabilityEnabled>false</HighAvailabilityEnabled>
        <EvictionPolicy>LeastRecentlyUsed</EvictionPolicy>
        <ExpirationSettings>
          <TimeToLiveInMinutes>10</TimeToLiveInMinutes>
          <Type>Absolute</Type>
        </ExpirationSettings>
      </NamedCache>
    </NamedCaches>    
  </CacheServiceInput>
  </IntrinsicSettings>
</Resource>
于 2014-09-02T18:38:19.677 回答