SQL Azure. Программное создание сервера.

 

Содержание предыдущей серии

 

Обогащенный этим знанием, я создам еще один сервер в датацентре Western Europe. Для разнообразия сделаем это не ручками, как в прошлый раз, а программно, как описано в BOL. Программный интерфейс управления SQL Azure основывается на REST API. Вначале нужно создать сертификат X.509, чтобы аутентифицировать наш HttpWebRequest. Создание сертификатов мы разбирали в апреле-мае прошлого года в серии постов "Шифрование трафика между SQL Server и клиентом". Очень хорошо, значит, на этом можно не останавливаться.

 

"C:\Program Files (x86)\Microsoft SDKs\Windows\v6.0A\bin\makecert.exe" -sky exchange -r -n "CN=SQLAzureCert" -pe -a sha1 -len 2048 -ss My "c:\Temp\SQLAzureCert.cer"

 

clip_image002[4]

Рис.1

 

 Добавляем в ММС оснастку сертификатов My User Account и в папке Personal находим созданный на Рис.1 сертификат. Экспортируем в файл формата Personal Information Exchange (pfx) закрытый ключ сертификата, как описано в документации на Windows Azure.

 

clip_image004[4]

Рис.2

 

clip_image006[4]

Рис.3

 

и подключаем сертификат к подписке. Для этого встаем слева на созданный сервер в предыдущем посте сервер SQL Azure, как на Рис.11, 12 предыдущего поста и кликаем Manage Certificates слева в строке меню:

На открывшейся странице жмем кнопку Add Certificate (тоже слева вверху):

 

clip_image007[4] 

Рис.3

 

и указываем cer-файл, созданный на Рис.1

 

clip_image009[4]

Рис.4

 

Сертификат успешно добавился - он второй. Тот, что виднеется над ним, существовал ранее и не имеет отношения к нашим нынешним потугам.

 

clip_image011[4]

Рис.5

 

Отлично. Теперь после добавления сертификата с открытым ключом в хранилище сертификатов подписки, мы можем программно авторизовываться на сайте управления SQL Azure https://management.database.windows.net:8443/\<SubscriptionId>/servers для этой подписки при помощи парного ему закрытого ключа в файле pfx. Посмотреть ID подписки можно в ее свойствах на панели справа - см., например, Рис.7, 10-12 предыдущего поста. Создание нового сервера делается при помощи HTTPS POST-запроса. Исходными данными являются логин и пароль администратора на будущем сервере, а также датацентр, в котором он будет создан. Датацентр указывается строкой в том виде, как их названия перечислены в комбобоксе на Рис.7 предыдущего поста. В качестве ответа возвращается имя созданного сервера.

 

cls

[string] $login = "alexejs"; [string] $loginPwd = 'Tiwanaku'; [string] $dc = "West Europe" #Будет создан сервер SQL Azure c администратором $login c паролем $loginPwd в датацентре $dc

[string] $certFilename = 'C:\Temp\SQLAzureCert.pfx'; [string] $certPwd = 'Tenochtitlan' #Файл, в который из -pe .cer экспортнули .pfx и зашифровали его паролем $certPwd

[string] $subscriptionId = "0d292ad4-6ef9-4803-9ca9-5b1ed5e6f843"

[System.Security.Cryptography.X509Certificates.X509Certificate2] $cert = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList ($certFilename, $certPwd)

[string] $url = [System.String]::Format("https://management.database.windows.net:8443/{0}/servers", $subscriptionId)

 

[System.Net.WebRequest] $webRequest = [System.Net.WebRequest]::Create($url)

$webRequest.ClientCertificates.Add($cert) | Out-Null

$webRequest.Headers.Add("x-ms-version", "1.0") | Out-Null

$webRequest.Method = "POST"

 

[string] $queryToCreateSrv = [String]::Format('<?xml version="1.0" encoding="utf-8"?>' + `

                                          '<Server xmlns="http://schemas.microsoft.com/sqlazure/2010/12/">' + `

                                              ' <AdministratorLogin>{0}</AdministratorLogin>' + `

                                              ' <AdministratorLoginPassword>{1}</AdministratorLoginPassword>' + `

                                              ' <Location>{2}</Location>' + `

                                              '</Server>', $login, $loginPwd, $dc)

[byte[]] $b = [System.Text.Encoding]::GetEncoding("utf-8").GetBytes($queryToCreateSrv)

$webRequest.ContentLength = $b.Length

$webRequest.ContentType = "application/xml;charset=utf-8"

$webRequest.GetRequestStream().Write($b, 0, $b.Length)

 

trap [System.Net.WebException]

{

 $_.Exception

 (New-Object -TypeName System.IO.StreamReader -ArgumentList $_.Exception.Response.GetResponseStream()).ReadToEnd()

 return

}

[System.Net.HttpWebResponse] $webResponse = $webRequest.GetResponse()

 

[System.IO.StreamReader] $sr = New-Object -TypeName System.IO.StreamReader -ArgumentList ($webResponse.GetResponseStream(), [System.Text.Encoding]::GetEncoding("utf-8"))

[string] $response = $sr.ReadToEnd(); $sr.Close(); $webResponse.Close()

$response

-------------------------------------------------------------------------------------------------------------------------------

<ServerName xmlns="http://schemas.microsoft.com/sqlazure/2010/12/">efhvxkujnb</ServerName>

 

clip_image013[4]

Рис.6

 

Полезная нагрузка сосредоточена в строке $queryToCreateSrv. Это XML-запрос на создание нового сервера, значениями элементов в котором являются заданные параметры: логин и пароль администратора и местоположение датацентра. Выполняем скрип, после чего можно зайти на портал, отрефрешиться и убедиться, что сервер с обведенным именем действительно создался:

 

clip_image015[4]

Рис.7

 

Как всегда, самое муторное - обработка ошибок. Она реализована в блоке trap [System.Net.WebException]. Просто выводить $_.Exception недостаточно, потому что, например, ошибка (400) Bad Request может означать разные причины: <AdministratorLogin> is not a valid name because it contains invalid characters; Password validation failed. The password does not meet policy requirements because it is too short и т.д. - см. Коды состояний и ошибок в BOL. Чтобы понять, что конкретно не понравилось SQL Azure Management Service, нужно читать Response исключения: (New-Object -TypeName System.IO.StreamReader -ArgumentList $_.Exception.Response.GetResponseStream()).ReadToEnd(). Закомментарим для иллюстрации строку $webRequest.ContentType = "application/xml;charset=utf-8" в скрипте Рис.6 и попробуем его снова выполнить. Выполнение завершится с ошибкой:

 

The remote server returned an error: (400) Bad Request.

<Error xmlns="http://schemas.microsoft.com/sqlazure/2010/12/">

  <Code>40649</Code>

  <Message>Invalid content-type is specified. Only application/xml is supported.</Message>

  <Severity>16</Severity>

  <State>1</State>

</Error>

 

clip_image017[4]

Рис.8

 

Обведенная вторая (детализирующая) часть ошибки содержится в ResponseStream'e ошибочного респонса.

 

Продолжаем рассматривать выполнение различных действий над серверами SQL Azure при помощи REST API. Как известно, REST (Representational State Transfer) - это CRUD-операции (Create, Retrieve, Update, Delete) поверх HTTP над какими-то ресурсами, до которых при помощи этого HTTP можно доступиться. Создание нового ресурса, как правило, происходит методом POST, возвращение - GET, обновление - PUT, удаление - DELETE. Пример перечисления серверов SQL Azure, имеющихся в данной подписке:

 

cls

[string] $subscriptionId = "0d292ad4-6ef9-4803-9ca9-5b1ed5e6f843"

[string] $certFilename = 'C:\Temp\SQLAzureCert.pfx'; [string] $certPwd = 'Tenochtitlan' #Файл, в который из -pe .cer экспортнули .pfx и зашифровали его паролем $certPwd

[System.Security.Cryptography.X509Certificates.X509Certificate2] $cert = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList ($certFilename, $certPwd)

[string] $url = [System.String]::Format("https://management.database.windows.net:8443/{0}/servers", $subscriptionId)

 

[System.Net.WebRequest] $webRequest = [System.Net.WebRequest]::Create($url)

$webRequest.ClientCertificates.Add($cert) | Out-Null

$webRequest.Headers.Add("x-ms-version", "1.0") | Out-Null

trap [System.Net.WebException]

{

 $_.Exception

 (New-Object -TypeName System.IO.StreamReader -ArgumentList $_.Exception.Response.GetResponseStream()).ReadToEnd()

 return

}

[System.Net.HttpWebResponse] $webResponse = $webRequest.GetResponse()

[System.IO.StreamReader] $sr = New-Object -TypeName System.IO.StreamReader -ArgumentList ($webResponse.GetResponseStream(), [System.Text.Encoding]::GetEncoding("utf-8"))

[string] $response = $sr.ReadToEnd(); $sr.Close(); $webResponse.Close()

$response

-------------------------------------------------------------------------------------------------------------------------------

<Servers xmlns="http://schemas.microsoft.com/sqlazure/2010/12/">

  <Server>

    <Name>efhvxkujnb</Name>

    <AdministratorLogin>alexejs</AdministratorLogin>

    <Location>West Europe</Location>

  </Server>

  <Server>

    <Name>fxv4koqar4</Name>

    <AdministratorLogin>alexejs</AdministratorLogin>

    <Location>West Europe</Location>

  </Server>

</Servers>

 

clip_image019[4]

Рис.9

 

Сравниваем с Рис.7 и убеждаемся, что все корректно. Единственно, я забыл в воспитательных целях явно прописать в коде $webRequest.Method = "GET", он подхватился по умолчанию.

  

Аналогично происходит удаление сервера. Из интерфейса портала это делается нажатием кнопки Drop в панели меню справа от Create (см., напр., Рис.7). При этом будет предложено ввести вручную название удаляемого сервера, чтобы избежать удаления по ошибке. Цена ошибки велика, т.к. сервер будет безвозвратно удален со всеми находящимися на нем базами.

 

clip_image021[4]

Рис.10

 

То же можно сделать программно, отправив по адресу https://management.database.windows.net:8443/\<идентификатор_подписки>/servers/<имя_сервера> HTTP-запрос с методом DELETE. Изменившиеся по сравнению с Рис.9 строчки кода показаны жирным цветом

 

cls

[string] $subscriptionId = "0d292ad4-6ef9-4803-9ca9-5b1ed5e6f843"

[string] $serverName = "efhvxkujnb"

[string] $certFilename = 'C:\Temp\SQLAzureCert.pfx'; [string] $certPwd = 'Tenochtitlan' #Файл, в который из -pe .cer экспортнули .pfx и зашифровали его паролем $certPwd

[System.Security.Cryptography.X509Certificates.X509Certificate2] $cert = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList ($certFilename, $certPwd)

[string] $url = [System.String]::Format("https://management.database.windows.net:8443/{0}/servers /{1} ", $subscriptionId , $serverName)

 

[System.Net.WebRequest] $webRequest = [System.Net.WebRequest]::Create($url)

$webRequest.ClientCertificates.Add($cert) | Out-Null

$webRequest.Headers.Add("x-ms-version", "1.0") | Out-Null

$webRequest.Method = "DELETE"

trap [System.Net.WebException]

{

 $_.Exception

 (New-Object -TypeName System.IO.StreamReader -ArgumentList $_.Exception.Response.GetResponseStream()).ReadToEnd()

 return

}

[System.Net.HttpWebResponse] $webResponse = $webRequest.GetResponse()

[System.IO.StreamReader] $sr = New-Object -TypeName System.IO.StreamReader -ArgumentList ($webResponse.GetResponseStream(), [System.Text.Encoding]::GetEncoding("utf-8"))

[string] $response = $sr.ReadToEnd(); $sr.Close(); $webResponse.Close()

$response

 

clip_image023[4]

Рис.11

 

В случае отсутствия ошибок в имени сервера, авторизации и пр. сервер молчаливо удаляется, т.е. в респонсе не возвращается никаких результатов. Понять, что сервера не стало, можно, нажав кнопку Refresh на Рис.7 или выполнив скрипт Рис.9.

 

Программное обновление сервера предоставляется читателям в качестве самостоятельного упражнения в соответствии с инструкцией. Тем более, что обновлять там сейчас особо нечего, только пароль администратора. Обновление производится методом POST, как и создание, хотя по классике REST должeн быть PUT. Ну то по классике. На практике все, как всегда, перемешано. Скажем, удаление гипотетически могло бы также производиться при помощи метода POST, если в шаблон XML-запроса $queryToCreateSrv, образующего RequestStream, запихнуть какой-нибудь элемент delete.

 

Продолжение следует.

 

 

Алексей Шуленин