Skip to content
June 21, 2012 / ahriman hpc mode

Пояснения по нововведениям в Windows Azure: сервисы хранилища. Ч.3.

Пояснения по нововведениям в Windows Azure: сервисы хранилища. Ч.2.

Пояснения по нововведениям в Windows Azure: сервисы хранилища.

Продолжаем наше относительно глубокое путешествие по нововведениям в Windows Azure. На этот раз посмотрим, что такое Shared Access Signatures и как их можно использовать для таблиц. В следующей части – использование SAS для очередей и блобов (+ код).

Shared Access Signatures ранее были доступны только для блобов, что позволяло хозяинам аккаунтов хранилища выдавать определенным образом подписанные URL для обеспечения доступа к блобам. Теперь же SAS доступны и для таблиц и очередей в добавление к блобам и контейнерам. До момента внедрения этой фичи для того, чтобы совершить что-то из CRUD с таблицей или очередью, необходимо было быть хозяином аккаунта. Теперь же можно предоставить другому человеку ссылку, подписанную SAS, и предоставить какие надо права. Функциональность SAS в этом и заключается – детальный контроль доступа к ресурсам, определив, какие операции может совершить пользователь над ресурсом, имея SAS. К списку доступных для  определения SAS операций относятся:

  • Чтение и запись контента – в случае блобов тут ещё и их свойства и метаданные, а также блок-списки.
  • Удаление, лизинг, создание снапшотов блобов.
  • Получение списков элементов контента.
  • Добавление, удаление, обновление сообщений в очередях.
  • Получение метаданных очередей, включая количество сообщений в очереди.
  • Чтение, добавление, обновление и вставка сущностей в таблицу.

Параметры SAS включают в себя всю информацию, необходимую для выдачи доступа к ресурсам хранилища – параметры запроса в URL определяют временной промежуток, через который SAS "протухнет", разрешения, предоставляемые данной SAS, ресурсы, к которым предоставляется доступ и, собственно, сигнатуру, с помощью которой происходит аутентификация. Кроме этого в SAS URL можно включить ссылку на хранимую политику доступа, с помощью которой можно предоставить ещё один слой контроля.

Естественно, что SAS должны распространяться с использованием HTTPS и разрешать доступ на максимально короткий временной промежуток, необходимый для выполнения операций.

Строение Shared Access Signature

В этой секции – вольный и дополненный перевод соответствующей статьи на MSDN – http://msdn.microsoft.com/en-us/library/hh508996.

В составе SAS, а именно SAS URL, находятся компоненты, приведенные на изображении.

Итак, сначала у нас есть некоторый REST URL с какими-то GET-параметрами. Далее идёт SAS-специфичный набор параметров.

Параметр signedversion

Содержит версию сервиса, обеспечивающего Shared Access Signature. При этом, если SAS указывает на сервис хранилища версии, более ранней, нежели 2012-02-12, этот SAS может оперировать только блобом или контейнером, и, в общем-то, если URL формируется вручную, параметр signedversion должен быть опущен. В версиях сервиса от 2012-02-12 (включительно) и выше параметр указывать надо.

Давайте по пути посмотрим, что значит версия сервиса и версия операции.

Сервисы хранилища Windows Azure могут принимать запросы, которые указывают конкретную версию каждой операции (сервиса), при этом разработчик может указать версию, используя заголовок x-ms-version. Естественно, что функциональность различных версий и механизмы работы (не концептуальные) могут различаться.

Request Headers:
x-ms-version: 2011-08-18

Если SAS URL использует этот параметр со значением версии, которая является более новой, нежели клиентское приложение, использующее этот URL, могут возникнуть функциональные недоразумения, поэтому для того же legacy-ПО крайне желательно явно указывать версию, которую это ПО понимает. Более того – в связи с нововведениями, если вы хотите воспользоваться SAS для таблицы, но в запросе будет старая версия, то совершенно закономерно будет возвращена ошибка. Пример: в версиях, более ранних нежели 2011-08-18, операция Get Blob не возвратит заголовок Accept-Ranges, и так далее.

Хочу заметить, что SAS для таблиц и очередей всегда будет использовать параметр signedversion.

Если же нас интересуют только старые версии сервиса, то надо учитывать правила системы, по которой сервис блобов определяет, какую версию использовать для интерпретации параметров SAS:

1. Если в запросе есть заголовок x-ms-version, но нет signedversion, используется версия 2009-07-17 для интерпретации параметров SAS, для операции же используется версия, указанная в x-ms-version.

2. Если в запросе нет заголовка x-ms-version и нет signedversion, но владелец аккаунта установил версию сервиса, используя операцию Set Blob Service Properties, будет использована версия 2009-07-07 для интерпретации параметров SAS, для операции же используется версия, указанная владельцем аккаунта для сервиса.

3. Если в запросе нет заголовка x-ms-version, нет signedversion и владелец аккаунта не устанавливал версию сервиса, будет использована версия 2009-07-17 для интерпретации параметров SAS. Если контейнер имеет права public и ограничения доступа были установлены с использованием операции Set Container ACL, использующей версию 2009-09-19 (или новее) .будет использована версия 2009-09-19 для операции.

Подробнее про версии и поддерживаемую ими функциональность: http://msdn.microsoft.com/en-us/library/windowsazure/dd894041

Параметр Signed Resource (только для блобов)

Значение параметра определяет ресурсы, к которым обеспечивается доступ данной SAS. С этим параметром все просто – он имеет значение либо b (блоб), что обеспечивает доступ к контенту и метаданным блоба, либо c (контейнер), что обеспечивает доступ к контенту и метаданным любого блоба в контейнере и получение списка блобов в этом контейнере.

Параметр Table Name (только для таблиц)

Определяет имя таблицы.

Параметр Access Policy

Определяет самые главные значения – временного промежутка, в течение которого будет работать данная SAS, и набор разрешений. Состоит из следующих частей:

signedstart – начальный момент времени в ISO 8061 (например, так – YYYY-MM-DDThh:mm:ssTZD), когда сигнатура будет активирована. Если опущено, сигнатура будет активирована в момент получения запроса.

signedexpiry – конечный момент времени в ISO 8061, когда сигнатура станет неактивной. Опускается, если соответствующее значение указано в хранимой политике доступа.

signedpermissions – набор разрешений, ассоциированных с сигнатурой (r, w, d для блобов r и w для контейнеров блобов, r a (add) u p (process, забор и удаление и r a(add) u d (delete) для сущностей таблиц) для сообщений в очередях). Опускается, если соответствующее значение указано в хранимой политике доступа. Пример: sp=rwdl, sp=rw, sp=r. Нельзя выдавать разрешения на создание и удаление контейнеров, очередей и таблиц, получения списка сущностей в них, получения и редактирования метаданных и свойств контейнеров блобов и очистки метаданных и очистки очередей от сообщений.

Startpk, startrk – только для таблиц. Минимальное значение partition key и row key, которое доступно пользователю.

Endpk, endrk – только для таблиц. Максимальное значение partition key и row key, которое доступно пользователю.

Примечание: можно дать доступ ко всем данным таблицы либо диапазону данных, что полезно, если у вас многопользовательское приложение и данные каждого пользователя хранятся в отдельной таблице (можно дать пользователю сгенерированный SAS-токен, выданный на диапазон таблицы) и если у вас опять же многопользовательское приложение, но данные хранятся в одной таблице – тогда SAS замечательно подходит для выдачи прав каждому пользователю на соответствующий диапазон данных.

Давайте посмотрим, что значит диапазон данных в контексте таблицы и зачем это может понадобиться. Итак, нам доступны параметры startpk, startrk, endpk и endrk, которые определяют диапазон сущностей в таблице, которые будут ассоциированы с данной SAS. Запросы к таблице с использованием этой SAS будут возвращать только те результаты, которые находятся внутри указанного диапазона, аналогично с другими операциями – добавлением, удалением и т.д. Попытка обращения к сущностям вне указанного диапазона приведет к ошибке.

Примечание: если startpk == endpk, будет авторизован доступ к сущностям в пределах одной партиции, если же startpk==endpk, startrk==endrk, будет авторизован доступ только к одной сущности

Параметр Signed Identifier

Определяет соответствующую SAS хранимую политику доступа. Хранимые политики доступа предоставляют определенную степень контроля над одной или более SAS, включая отзыв SAS при необходимости. Каждый контейнер блобов, очередь или таблица может иметь до 5 ассоциированных с ней хранимых политик доступа.

В дополнение к уже описанным выше фичам, связанным с SAS, команда разработки внесла изменения и в уже имеющийся механизм SAS для блобов. Теперь токен SAS, который создан без политики доступа, не имеет ограничения на время валидности (раньше оно было равным одному часу).

К сценариям использования Shared Access Signature можно отнести два основных сценария:

  1. Сервис должен давать доступ клиентам к каким-то частям аккаунта хранилища с какими-то определенными разрешениями, например, приложение для Windows Phone для сервиса, который запущен в Windows Azure. Токен SAS должен распространяться клиентам (приложениям Windows Phone) для обеспечения доступа к хранилищу Windows Azure.
  2. Сервис должен выдавать доступ к ресурсам по необходимости, также быстро закрывая его после окончания срока действия токенов.

Генерация токенов может происходить согласно следующим моделям:

  • Токен генерируется специальным сервисом на ограниченный период времени. Успешнее всего эта модель подходит к первому сценарию использования, описанному выше. Непосредственно перед истечением периода действия токена приложение запрашивает новый токен, сервис же решает по каким-либо правилам, выдавать этот токен или нет.

  • Канал коммуникаций между клиентом и сервисом, генерирующим токены, может использовать механизм аутентификации (например, OAuth) и клиент должен сначала аутентифицироваться, после чего запросить токен SAS.
  • Для второго описанного выше сценария можно использовать сгенерированные токены SAS, привязанные с хранимой политикой доступа.

Код

В этом разделе используются коды, предоставленные командой разработки.

Допустим, мы должны разработать сервис адресной книги, и надо бы, чтобы он мог масштабироваться для большого количества пользователей. Сервис предоставляет пользователю возможность хранить собственную адресную книгу в облаке и получать к ней доступ с любого устройства или приложения. Итак, пользователь подписывается на услуги сервиса и получает адресную книгу. Можно реализовать этот сценарий с помощью ролевой модели Windows Azure, и тогда сервис будет работать как прослойка между клиентским приложением и сервисами хранилища облачной платформы. После аутентификации клиентского приложения оно будет получать доступ к адресной книге через веб-интерфейс сервиса, который будет посылать запросы, инициированные клиентом, в сервис хранилища таблиц облачной платформы.

В этом сценарии отлично смотрится нововведение SAS для сервиса таблиц, и реализуется он довольно просто. SAS для сервиса таблиц можно использовать для предоставления прямого доступа к адресной книге приложению. Подобный подход позволяет увеличить степень масштабируемости системы и уменьшить стоимость решения, удалив прослойку, обрабатывающую запросы, в виде сервиса. Роль сервиса в данном случае будет сведена к обработке подписки клиентов и генерации токенов SAS для клиентского приложения.

Архитектура системы, использующей SAS для сервиса таблиц, будет выглядеть так: таблица в Windows Azure “AddressBook”, в которой хранятся записи адресных книг всех клиентов и имеющая PartitionKey, установленный в логин пользователя (customerID) и RowKey, отображающая ключ записи, определенный как значение LastName,FirstName. Это значит, что все записи для определенного клиента будут иметь один PartitionKey (customerID). Код, описывающий запись в адресной книге.

[DataServiceKey("PartitionKey", "RowKey")]

public class AddressBookEntry

{

public AddressBookEntry(string partitionKey, string rowKey)

{

this.PartitionKey = partitionKey;

this.RowKey = rowKey;

}

public AddressBookEntry() { }

public string PartitionKey { get; set; }

public string RowKey { get; set; }

public DateTime Timestamp { get; set; }

public string Address { get; set; }

public string Email { get; set; }

public string PhoneNumber { get; set; }

}

Сервис адресной книги будет состоять из двух компонентов:

  1. Издатель токенов SAS, который будет работать в Windows Azure и принимать запросы на токены SAS от клиентских приложений. Сначала сервис будет аутентифицировать клиентское приложение согласно собственным правилам, после чего будет генерировать токен, дающий право на доступ к PartitionKey таблицы “AddressBook”, равный customerID. Доступ будет выдаваться на чтение ®, обновление (u), добавление записей (a) и удаление записей (d). Доступ, в случае решения сервиса о прекращении подписки пользователя, будет ограничен 30 минутами без возможности дальнейшего обновления. Период в 30 минут значительно уменьшит загрузку на издателя токенов по сравнению с ситуацией, когда сервис будет проксировать все входящие запросы.
  2. Клиентское приложение совершает операции чтения, обновления, добавления и удаления записей для клиента. Приложение сначала получает от издателя токенов токен SAS и локально кэширует его, пока он ещё не «протух», после чего использует его для REST-запросов к сервису таблиц. При этом будет использоваться стандартный подход обновления токенов раз в N минут, где N – половина времени от периода жизни токена, поэтому в нашей ситуации N будет равно 15 минутам.

Код для издателя токенов приведен ниже – класс SasProducer, реализующей RequestSasToken для выдачи токена клиентскому приложению. В этом примере коммуникации между клиентским приложением и издателем осуществляются вызовом метода при необходимости выдачи токена.

/// <summary>

/// Класс издателя токенов, контролирующий доступ к адресной книге токенами ///SAS

/// </summary>

public class SasProducer

{

/* ... */

/// <summary>

/// Выдает токен SAS, определяющий доступ к записям адресной книги, принадлежащим определенному клиенту.

/// </summary>

public string RequestSasToken(string customerId)

{

// логика аутентификации опущена

// создание политика доступа, истекающей через 30 минут, без даты начала активации, то есть токен начинает действовать сразу после его выдачи. Токен определяет полные разрешения на ресурс. 

SharedAccessTablePolicy policy = new SharedAccessTablePolicy()

{

SharedAccessExpiryTime = DateTime.UtcNow.AddMinutes(

SasProducer.AccessPolicyDurationInMinutes),

Permissions = SharedAccessTablePermissions.Add

| SharedAccessTablePermissions.Query

| SharedAccessTablePermissions.Update

| SharedAccessTablePermissions.Delete

};

// Генерация токена без идентификатора политики доступа, что делает токен неотзываемым. Предоставляет доступ к записям таблицы, имеющим идентификатор клиента, пришедший в запросе. 

string sasToken = this.addressBookTable.GetSharedAccessSignature(

policy /* политика доступа */,

null /* идентификатор политики */,

customerId /* начальное значение PK */,

null /* начальное значение RK */,

customerId /* конечное значение PK */,

null /* конечное значение RK */);

return sasToken;

}

}

Код клиентского приложения приведен ниже и состоит из класса Client, предоставляющего публичные методы для управления адресной книгой клиента.

public class Client

{

/// <summary>

/// Период обновления токена в минутах. 

/// </summary>

private const int SasRefreshThresholdInMinutes = 15;

/// <summary>

/// the cached copy of the sas credentials of the customer's addressbook

/// </summary>

private StorageCredentialsSharedAccessSignature addressBookSasCredentials;

/// <summary>

/// Время истечения срока жизни токена, используемое для определения момента, в который необходимо произвести его обновление.

/// </summary>

private DateTime addressBookSasExpiryTime;

/* ... */

public StorageCredentials GetAddressBookSasCredentials()

{

if (this.addressBookSasCredentials == null ||

DateTime.UtcNow.AddMinutes(SasRefreshThresholdInMinutes) >= this.addressBookSasExpiryTime)

{

this.RefreshAccessCredentials();

}

return this.addressBookSasCredentials;

}

/// <summary>

/// Запрос нового токена SAS и обновление кэшированных значений. 

/// </summary>

public void RefreshAccessCredentials()

{

// Запрос токена.

string sasToken = this.addressBookService.RequestSasToken(this.customerId);

// Создание учетных данных с использованием нового токена. 

this.addressBookSasCredentials = new StorageCredentialsSharedAccessSignature(sasToken);

this.addressBookSasExpiryTime = DateTime.UtcNow.AddMinutes(

SasProducer.AccessPolicyDurationInMinutes);

}

/// <summary>

/// Получение записи из адресной книги. 

/// </summary>

public AddressBookEntry LookupByName(string contactname)

{

StorageCredentials credentials = GetAddressBookSasCredentials();

CloudTableClient tableClient = new CloudTableClient(this.tableEndpoint, credentials);

TableServiceContext context = tableClient.GetDataServiceContext();

CloudTableQuery<AddressBookEntry> query = 

(from entry in context.CreateQuery<AddressBookEntry>(Client.AddressBookTableName)

where entry.PartitionKey == this.customerId && entry.RowKey == contactname

select entry).AsTableServiceQuery();

return query.Execute().SingleOrDefault();

}

/// <summary>

/// Вставка новой записи в адресную книгу или обновление существующей записи. 

/// </summary>

public void UpsertEntry(AddressBookEntry entry)

{

StorageCredentials credentials = GetAddressBookSasCredentials();

CloudTableClient tableClient = new CloudTableClient(this.tableEndpoint, credentials);

TableServiceContext context = tableClient.GetDataServiceContext();

// Определение правильного customerID. 

entry.PartitionKey = this.customerId;

// “Insert or Merge” записи.

context.AttachTo(Client.AddressBookTableName, entry);

context.UpdateObject(entry);

context.SaveChangesWithRetries();

}

}

Возможно, нам необходимо реализовать сборщик мусора, который будет удалять адресные книги пользователей, которые уже не являются клиентами нашего сервиса. В этом случае во избежание утечки учетных данных к аккаунту хранилища worker-роль, реализующая сборщик мусора, будет использовать токен SAS для сервиса таблиц с максимальным временем доступа и хранимой политикой доступа. Токен будет предоставлять этому сервису неограниченный по времени или диапазону значений доступ к таблице “AddressBook”, но только на операцию удаления. В случае утечки токена хозяин сервиса сможет просто отозвать этот токен, удалив идентификатор, ассоциированный с таблицей “AddressBook”. Кроме этого worker-роль сервиса сборщика мусора будет знать customerID тех клиентов, кто больше не пользуется сервисом. Как только подписка клиента будет истекать, идентификатор этого клиента будет передаваться в очередь “gcqueue”, которая будет постоянно опрашиваться сервисом сборщика мусора.

public const string GCQueueName = "gcqueue";

/// <summary>

/// Очередь сборщика мусора.

/// </summary>

private CloudQueue gcQueue;

/// <summary>

/// Генерация токенов для таблицы адресной книги и очереди для сборщика мусора, которые будут использоваться сервисом сборки мусора.

/// </summary>

public void GetGCSasTokens(out string tableSasToken, out string queueSasToken)

{

string gcPolicySignedIdentifier = "GCAccessPolicy" + DateTime.UtcNow.ToString();

TablePermissions addressBookPermissions = new TablePermissions();

SharedAccessTablePolicy gcTablePolicy = new SharedAccessTablePolicy()

{

SharedAccessExpiryTime = DateTime.MaxValue,

Permissions = SharedAccessTablePermissions.Query | SharedAccessTablePermissions.Delete

};

addressBookPermissions.SharedAccessPolicies.Add(

gcPolicySignedIdentifier,

gcTablePolicy);

this.addressBookTable.SetPermissions(addressBookPermissions);

tableSasToken = this.addressBookTable.GetSharedAccessSignature(

new SharedAccessTablePolicy(),

gcPolicySignedIdentifier,

null /* начальное значение PK */,

null /* начальное значение RK */,

null /* конечное значение PK */,

null /* конечное значение RK */);

// Инициализация очереди и создание токена. 

CloudQueueClient queueClient = 

this.serviceStorageAccount.CreateCloudQueueClient();

this.gcQueue = queueClient.GetQueueReference(GCQueueName);

this.gcQueue.CreateIfNotExist();

QueuePermissions gcQueuePermissions = new QueuePermissions();

SharedAccessQueuePolicy gcQueuePolicy = new SharedAccessQueuePolicy()

{

SharedAccessExpiryTime = DateTime.MaxValue,

Permissions = SharedAccessQueuePermissions.ProcessMessages

};

gcQueuePermissions.SharedAccessPolicies.Add(

gcPolicySignedIdentifier,

gcQueuePolicy);

this.gcQueue.SetPermissions(gcQueuePermissions);

queueSasToken = this.gcQueue.GetSharedAccessSignature(

new SharedAccessQueuePolicy(),

gcPolicySignedIdentifier);

}

Код метода удаления данных клиента.

/// <summary>

/// Помещение идентификатора клиента в очередь для удаления его данных.

/// </summary>

public void DeleteCustomer(string customerId)

{

CloudQueueMessage message = new CloudQueueMessage(customerId);

this.gcQueue.AddMessage(message);

}

Код отзыва токена в случае его утечки. 

/// <summary>

/// Отзыв токена SAS таблицы. 

/// </summary>

public void RevokeAccessToTable(CloudTable table, string signedIdentifier)

{

TablePermissions tablePermissions = table.GetPermissions();

bool success = tablePermissions.SharedAccessPolicies.Remove(signedIdentifier);

if (success)

{

this.addressBookTable.SetPermissions(tablePermissions);

}

}

Код сборки мусора, использующий сгенерированный токен. 

/// <summary>

/// Класс, реализующий сборку мусора. 

/// </summary>

public class GCWorker

{

private CloudTable addressBook;

private CloudQueue gcQueue;

public GCWorker(

string tableEndpoint,

string sasTokenForAddressBook,

string queueEndpoint,

string sasTokenForQueue)

{

StorageCredentials credentialsForAddressBook = 

new StorageCredentialsSharedAccessSignature(sasTokenForAddressBook);

CloudTableClient tableClient = 

new CloudTableClient(tableEndpoint, credentialsForAddressBook);

this.addressBook = 

tableClient.GetTableReference(SasProducer.AddressBookTableName);

StorageCredentials credentialsForQueue = 

new StorageCredentialsSharedAccessSignature(sasTokenForQueue);

CloudQueueClient queueClient = 

new CloudQueueClient(queueEndpoint, credentialsForQueue);

this.gcQueue = 

queueClient.GetQueueReference(SasProducer.GCQueueName);

}

public void Start()

{

while (true)

{

// Получение сообщение из очереди с указанием периода невидимости этого сообщения в 2 минуты (в это время сообщение будет невидимо для остальных обработчиков). 

CloudQueueMessage message = this.gcQueue.GetMessage(TimeSpan.FromMinutes(2));

if (message == null)

{

Thread.Sleep(TimeSpan.FromMinutes(1));

continue;

}

string customerIDToGC = message.AsString;

TableServiceContext context = this.addressBook.ServiceClient.GetDataServiceContext();

CloudTableQuery<AddressBookEntry> query = 

(from entry in context.CreateQuery<AddressBookEntry>(this.addressBook.Name)

where entry.PartitionKey == customerIDToGC

select entry).AsTableServiceQuery();

int numberOfEntriesInBatch = 0;

foreach (AddressBookEntry r in query.Execute())

{

context.DeleteObject(r);

numberOfEntriesInBatch++;

if (numberOfEntriesInBatch == 100)

{

context.SaveChangesWithRetries(SaveChangesOptions.Batch);

numberOfEntriesInBatch = 0;

}

}

if (numberOfEntriesInBatch > 0)

{

context.SaveChangesWithRetries(SaveChangesOptions.Batch);

}

this.gcQueue.DeleteMessage(message);

}

}

}

Код метода Main, использующего весь вышеприведенный код.

public static void Main()

{

string accountName = "someaccountname";

string accountKey = "someaccountkey";

string tableEndpoint = string.Format(

"http://{0}.table.core.windows.net", accountName);

string queueEndpoint = string.Format(

"http://{0}.queue.core.windows.net", accountName);

CloudStorageAccount storageAccount = CloudStorageAccount.Parse(

string.Format(

"DefaultEndpointsProtocol=http;AccountName={0};AccountKey={1}",

accountName, accountKey));

SasProducer sasProducer = new SasProducer(storageAccount);

string sasTokenForAddressBook, sasTokenForQueue;

sasProducer.GetGCSasTokens(out sasTokenForAddressBook, out sasTokenForQueue);

GCWorker gcWorker = new GCWorker(

tableEndpoint,

sasTokenForAddressBook,

queueEndpoint,

sasTokenForQueue);

ThreadPool.QueueUserWorkItem((state) => gcWorker.Start());

string customerId = "davidhamilton";

Client client = new Client(sasProducer, tableEndpoint, customerId);

AddressBookEntry contactEntry = new AddressBookEntry

{

RowKey = "Harp,Walter",

Address = "1345 Fictitious St, St Buffalo, NY 98052",

PhoneNumber = "425-555-0101"

};

client.UpsertEntry(contactEntry);

contactEntry = new AddressBookEntry

{

RowKey = "Foster,Jonathan",

Email = "Jonathan@fourthcoffee.com"

};

client.UpsertEntry(contactEntry);

contactEntry = new AddressBookEntry

{

RowKey = "Miller,Lisa",

PhoneNumber = "425-555-2141"

};

client.UpsertEntry(contactEntry);

contactEntry = new AddressBookEntry

{

RowKey = "Harp,Walter",

Email = "Walter@contoso.com"

};

client.UpsertEntry(contactEntry);

contactEntry = client.LookupByName("Foster,Jonathan");

sasProducer.DeleteCustomer(customerId);

Thread.Sleep(TimeSpan.FromSeconds(120));

}

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: