mirror of
https://github.com/ansible/awx.git
synced 2026-01-19 05:31:22 -03:30
Update azure package to 0.9.0
This commit is contained in:
parent
18e7541f3b
commit
d2c46d015b
@ -5,6 +5,7 @@ amqp==1.4.5 (amqp/*)
|
||||
ansi2html==1.0.6 (ansi2html/*)
|
||||
anyjson==0.3.3 (anyjson/*)
|
||||
argparse==1.2.1 (argparse.py, needed for Python 2.6 support)
|
||||
azure==0.9.0 (azure/*)
|
||||
Babel==1.3 (babel/*, excluded bin/pybabel)
|
||||
billiard==3.3.0.16 (billiard/*, funtests/*, excluded _billiard.so)
|
||||
boto==2.34.0 (boto/*, excluded bin/asadmin, bin/bundle_image, bin/cfadmin,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
81
awx/lib/site-packages/azure/azure.pyproj
Normal file
81
awx/lib/site-packages/azure/azure.pyproj
Normal file
@ -0,0 +1,81 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{25b2c65a-0553-4452-8907-8b5b17544e68}</ProjectGuid>
|
||||
<ProjectHome>
|
||||
</ProjectHome>
|
||||
<StartupFile>storage\blobservice.py</StartupFile>
|
||||
<SearchPath>..</SearchPath>
|
||||
<WorkingDirectory>.</WorkingDirectory>
|
||||
<OutputPath>.</OutputPath>
|
||||
<Name>azure</Name>
|
||||
<RootNamespace>azure</RootNamespace>
|
||||
<IsWindowsApplication>False</IsWindowsApplication>
|
||||
<LaunchProvider>Standard Python launcher</LaunchProvider>
|
||||
<CommandLineArguments />
|
||||
<InterpreterPath />
|
||||
<InterpreterArguments />
|
||||
<InterpreterId>{2af0f10d-7135-4994-9156-5d01c9c11b7e}</InterpreterId>
|
||||
<InterpreterVersion>2.7</InterpreterVersion>
|
||||
<SccProjectName>SAK</SccProjectName>
|
||||
<SccProvider>SAK</SccProvider>
|
||||
<SccAuxPath>SAK</SccAuxPath>
|
||||
<SccLocalPath>SAK</SccLocalPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="http\batchclient.py" />
|
||||
<Compile Include="http\httpclient.py" />
|
||||
<Compile Include="http\requestsclient.py" />
|
||||
<Compile Include="http\winhttp.py" />
|
||||
<Compile Include="http\__init__.py" />
|
||||
<Compile Include="servicemanagement\schedulermanagementservice.py" />
|
||||
<Compile Include="servicemanagement\servicebusmanagementservice.py" />
|
||||
<Compile Include="servicemanagement\servicemanagementclient.py" />
|
||||
<Compile Include="servicemanagement\servicemanagementservice.py" />
|
||||
<Compile Include="servicemanagement\sqldatabasemanagementservice.py" />
|
||||
<Compile Include="servicemanagement\websitemanagementservice.py" />
|
||||
<Compile Include="servicemanagement\__init__.py" />
|
||||
<Compile Include="servicebus\servicebusservice.py" />
|
||||
<Compile Include="storage\blobservice.py" />
|
||||
<Compile Include="storage\queueservice.py" />
|
||||
<Compile Include="storage\cloudstorageaccount.py" />
|
||||
<Compile Include="storage\tableservice.py" />
|
||||
<Compile Include="storage\sharedaccesssignature.py" />
|
||||
<Compile Include="__init__.py" />
|
||||
<Compile Include="servicebus\__init__.py" />
|
||||
<Compile Include="storage\storageclient.py" />
|
||||
<Compile Include="storage\__init__.py" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="http" />
|
||||
<Folder Include="servicemanagement" />
|
||||
<Folder Include="servicebus" />
|
||||
<Folder Include="storage" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<InterpreterReference Include="{2af0f10d-7135-4994-9156-5d01c9c11b7e}\2.6" />
|
||||
<InterpreterReference Include="{2af0f10d-7135-4994-9156-5d01c9c11b7e}\2.7" />
|
||||
<InterpreterReference Include="{2af0f10d-7135-4994-9156-5d01c9c11b7e}\3.3" />
|
||||
<InterpreterReference Include="{2af0f10d-7135-4994-9156-5d01c9c11b7e}\3.4" />
|
||||
<InterpreterReference Include="{9a7a9026-48c1-4688-9d5d-e5699d47d074}\2.7" />
|
||||
<InterpreterReference Include="{9a7a9026-48c1-4688-9d5d-e5699d47d074}\3.3" />
|
||||
<InterpreterReference Include="{9a7a9026-48c1-4688-9d5d-e5699d47d074}\3.4" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
<PtvsTargetsFile>$(VSToolsPath)\Python Tools\Microsoft.PythonTools.targets</PtvsTargetsFile>
|
||||
</PropertyGroup>
|
||||
<Import Condition="Exists($(PtvsTargetsFile))" Project="$(PtvsTargetsFile)" />
|
||||
<Import Condition="!Exists($(PtvsTargetsFile))" Project="$(MSBuildToolsPath)\Microsoft.Common.targets" />
|
||||
</Project>
|
||||
@ -1,73 +1,73 @@
|
||||
#-------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft. 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.
|
||||
# You may obtain a copy of the License at
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License 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.
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
HTTP_RESPONSE_NO_CONTENT = 204
|
||||
|
||||
|
||||
class HTTPError(Exception):
|
||||
|
||||
''' HTTP Exception when response status code >= 300 '''
|
||||
|
||||
def __init__(self, status, message, respheader, respbody):
|
||||
'''Creates a new HTTPError with the specified status, message,
|
||||
response headers and body'''
|
||||
self.status = status
|
||||
self.respheader = respheader
|
||||
self.respbody = respbody
|
||||
Exception.__init__(self, message)
|
||||
|
||||
|
||||
class HTTPResponse(object):
|
||||
|
||||
"""Represents a response from an HTTP request. An HTTPResponse has the
|
||||
following attributes:
|
||||
|
||||
status: the status code of the response
|
||||
message: the message
|
||||
headers: the returned headers, as a list of (name, value) pairs
|
||||
body: the body of the response
|
||||
"""
|
||||
|
||||
def __init__(self, status, message, headers, body):
|
||||
self.status = status
|
||||
self.message = message
|
||||
self.headers = headers
|
||||
self.body = body
|
||||
|
||||
|
||||
class HTTPRequest(object):
|
||||
|
||||
'''Represents an HTTP Request. An HTTP Request consists of the following
|
||||
attributes:
|
||||
|
||||
host: the host name to connect to
|
||||
method: the method to use to connect (string such as GET, POST, PUT, etc.)
|
||||
path: the uri fragment
|
||||
query: query parameters specified as a list of (name, value) pairs
|
||||
headers: header values specified as (name, value) pairs
|
||||
body: the body of the request.
|
||||
protocol_override:
|
||||
specify to use this protocol instead of the global one stored in
|
||||
_HTTPClient.
|
||||
'''
|
||||
|
||||
def __init__(self):
|
||||
self.host = ''
|
||||
self.method = ''
|
||||
self.path = ''
|
||||
self.query = [] # list of (name, value)
|
||||
self.headers = [] # list of (header name, header value)
|
||||
self.body = ''
|
||||
self.protocol_override = None
|
||||
#-------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft. 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.
|
||||
# You may obtain a copy of the License at
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License 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.
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
HTTP_RESPONSE_NO_CONTENT = 204
|
||||
|
||||
|
||||
class HTTPError(Exception):
|
||||
|
||||
''' HTTP Exception when response status code >= 300 '''
|
||||
|
||||
def __init__(self, status, message, respheader, respbody):
|
||||
'''Creates a new HTTPError with the specified status, message,
|
||||
response headers and body'''
|
||||
self.status = status
|
||||
self.respheader = respheader
|
||||
self.respbody = respbody
|
||||
Exception.__init__(self, message)
|
||||
|
||||
|
||||
class HTTPResponse(object):
|
||||
|
||||
"""Represents a response from an HTTP request. An HTTPResponse has the
|
||||
following attributes:
|
||||
|
||||
status: the status code of the response
|
||||
message: the message
|
||||
headers: the returned headers, as a list of (name, value) pairs
|
||||
body: the body of the response
|
||||
"""
|
||||
|
||||
def __init__(self, status, message, headers, body):
|
||||
self.status = status
|
||||
self.message = message
|
||||
self.headers = headers
|
||||
self.body = body
|
||||
|
||||
|
||||
class HTTPRequest(object):
|
||||
|
||||
'''Represents an HTTP Request. An HTTP Request consists of the following
|
||||
attributes:
|
||||
|
||||
host: the host name to connect to
|
||||
method: the method to use to connect (string such as GET, POST, PUT, etc.)
|
||||
path: the uri fragment
|
||||
query: query parameters specified as a list of (name, value) pairs
|
||||
headers: header values specified as (name, value) pairs
|
||||
body: the body of the request.
|
||||
protocol_override:
|
||||
specify to use this protocol instead of the global one stored in
|
||||
_HTTPClient.
|
||||
'''
|
||||
|
||||
def __init__(self):
|
||||
self.host = ''
|
||||
self.method = ''
|
||||
self.path = ''
|
||||
self.query = [] # list of (name, value)
|
||||
self.headers = [] # list of (header name, header value)
|
||||
self.body = ''
|
||||
self.protocol_override = None
|
||||
|
||||
@ -1,339 +1,339 @@
|
||||
#-------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft. 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.
|
||||
# You may obtain a copy of the License at
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License 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.
|
||||
#--------------------------------------------------------------------------
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
from azure import (
|
||||
_update_request_uri_query,
|
||||
WindowsAzureError,
|
||||
WindowsAzureBatchOperationError,
|
||||
_get_children_from_path,
|
||||
url_unquote,
|
||||
_ERROR_CANNOT_FIND_PARTITION_KEY,
|
||||
_ERROR_CANNOT_FIND_ROW_KEY,
|
||||
_ERROR_INCORRECT_TABLE_IN_BATCH,
|
||||
_ERROR_INCORRECT_PARTITION_KEY_IN_BATCH,
|
||||
_ERROR_DUPLICATE_ROW_KEY_IN_BATCH,
|
||||
_ERROR_BATCH_COMMIT_FAIL,
|
||||
)
|
||||
from azure.http import HTTPError, HTTPRequest, HTTPResponse
|
||||
from azure.http.httpclient import _HTTPClient
|
||||
from azure.storage import (
|
||||
_update_storage_table_header,
|
||||
METADATA_NS,
|
||||
_sign_storage_table_request,
|
||||
)
|
||||
from xml.dom import minidom
|
||||
|
||||
_DATASERVICES_NS = 'http://schemas.microsoft.com/ado/2007/08/dataservices'
|
||||
|
||||
if sys.version_info < (3,):
|
||||
def _new_boundary():
|
||||
return str(uuid.uuid1())
|
||||
else:
|
||||
def _new_boundary():
|
||||
return str(uuid.uuid1()).encode('utf-8')
|
||||
|
||||
|
||||
class _BatchClient(_HTTPClient):
|
||||
|
||||
'''
|
||||
This is the class that is used for batch operation for storage table
|
||||
service. It only supports one changeset.
|
||||
'''
|
||||
|
||||
def __init__(self, service_instance, account_key, account_name,
|
||||
protocol='http'):
|
||||
_HTTPClient.__init__(self, service_instance, account_name=account_name,
|
||||
account_key=account_key, protocol=protocol)
|
||||
self.is_batch = False
|
||||
self.batch_requests = []
|
||||
self.batch_table = ''
|
||||
self.batch_partition_key = ''
|
||||
self.batch_row_keys = []
|
||||
|
||||
def get_request_table(self, request):
|
||||
'''
|
||||
Extracts table name from request.uri. The request.uri has either
|
||||
"/mytable(...)" or "/mytable" format.
|
||||
|
||||
request: the request to insert, update or delete entity
|
||||
'''
|
||||
if '(' in request.path:
|
||||
pos = request.path.find('(')
|
||||
return request.path[1:pos]
|
||||
else:
|
||||
return request.path[1:]
|
||||
|
||||
def get_request_partition_key(self, request):
|
||||
'''
|
||||
Extracts PartitionKey from request.body if it is a POST request or from
|
||||
request.path if it is not a POST request. Only insert operation request
|
||||
is a POST request and the PartitionKey is in the request body.
|
||||
|
||||
request: the request to insert, update or delete entity
|
||||
'''
|
||||
if request.method == 'POST':
|
||||
doc = minidom.parseString(request.body)
|
||||
part_key = _get_children_from_path(
|
||||
doc, 'entry', 'content', (METADATA_NS, 'properties'),
|
||||
(_DATASERVICES_NS, 'PartitionKey'))
|
||||
if not part_key:
|
||||
raise WindowsAzureError(_ERROR_CANNOT_FIND_PARTITION_KEY)
|
||||
return part_key[0].firstChild.nodeValue
|
||||
else:
|
||||
uri = url_unquote(request.path)
|
||||
pos1 = uri.find('PartitionKey=\'')
|
||||
pos2 = uri.find('\',', pos1)
|
||||
if pos1 == -1 or pos2 == -1:
|
||||
raise WindowsAzureError(_ERROR_CANNOT_FIND_PARTITION_KEY)
|
||||
return uri[pos1 + len('PartitionKey=\''):pos2]
|
||||
|
||||
def get_request_row_key(self, request):
|
||||
'''
|
||||
Extracts RowKey from request.body if it is a POST request or from
|
||||
request.path if it is not a POST request. Only insert operation request
|
||||
is a POST request and the Rowkey is in the request body.
|
||||
|
||||
request: the request to insert, update or delete entity
|
||||
'''
|
||||
if request.method == 'POST':
|
||||
doc = minidom.parseString(request.body)
|
||||
row_key = _get_children_from_path(
|
||||
doc, 'entry', 'content', (METADATA_NS, 'properties'),
|
||||
(_DATASERVICES_NS, 'RowKey'))
|
||||
if not row_key:
|
||||
raise WindowsAzureError(_ERROR_CANNOT_FIND_ROW_KEY)
|
||||
return row_key[0].firstChild.nodeValue
|
||||
else:
|
||||
uri = url_unquote(request.path)
|
||||
pos1 = uri.find('RowKey=\'')
|
||||
pos2 = uri.find('\')', pos1)
|
||||
if pos1 == -1 or pos2 == -1:
|
||||
raise WindowsAzureError(_ERROR_CANNOT_FIND_ROW_KEY)
|
||||
row_key = uri[pos1 + len('RowKey=\''):pos2]
|
||||
return row_key
|
||||
|
||||
def validate_request_table(self, request):
|
||||
'''
|
||||
Validates that all requests have the same table name. Set the table
|
||||
name if it is the first request for the batch operation.
|
||||
|
||||
request: the request to insert, update or delete entity
|
||||
'''
|
||||
if self.batch_table:
|
||||
if self.get_request_table(request) != self.batch_table:
|
||||
raise WindowsAzureError(_ERROR_INCORRECT_TABLE_IN_BATCH)
|
||||
else:
|
||||
self.batch_table = self.get_request_table(request)
|
||||
|
||||
def validate_request_partition_key(self, request):
|
||||
'''
|
||||
Validates that all requests have the same PartitiionKey. Set the
|
||||
PartitionKey if it is the first request for the batch operation.
|
||||
|
||||
request: the request to insert, update or delete entity
|
||||
'''
|
||||
if self.batch_partition_key:
|
||||
if self.get_request_partition_key(request) != \
|
||||
self.batch_partition_key:
|
||||
raise WindowsAzureError(_ERROR_INCORRECT_PARTITION_KEY_IN_BATCH)
|
||||
else:
|
||||
self.batch_partition_key = self.get_request_partition_key(request)
|
||||
|
||||
def validate_request_row_key(self, request):
|
||||
'''
|
||||
Validates that all requests have the different RowKey and adds RowKey
|
||||
to existing RowKey list.
|
||||
|
||||
request: the request to insert, update or delete entity
|
||||
'''
|
||||
if self.batch_row_keys:
|
||||
if self.get_request_row_key(request) in self.batch_row_keys:
|
||||
raise WindowsAzureError(_ERROR_DUPLICATE_ROW_KEY_IN_BATCH)
|
||||
else:
|
||||
self.batch_row_keys.append(self.get_request_row_key(request))
|
||||
|
||||
def begin_batch(self):
|
||||
'''
|
||||
Starts the batch operation. Intializes the batch variables
|
||||
|
||||
is_batch: batch operation flag.
|
||||
batch_table: the table name of the batch operation
|
||||
batch_partition_key: the PartitionKey of the batch requests.
|
||||
batch_row_keys: the RowKey list of adding requests.
|
||||
batch_requests: the list of the requests.
|
||||
'''
|
||||
self.is_batch = True
|
||||
self.batch_table = ''
|
||||
self.batch_partition_key = ''
|
||||
self.batch_row_keys = []
|
||||
self.batch_requests = []
|
||||
|
||||
def insert_request_to_batch(self, request):
|
||||
'''
|
||||
Adds request to batch operation.
|
||||
|
||||
request: the request to insert, update or delete entity
|
||||
'''
|
||||
self.validate_request_table(request)
|
||||
self.validate_request_partition_key(request)
|
||||
self.validate_request_row_key(request)
|
||||
self.batch_requests.append(request)
|
||||
|
||||
def commit_batch(self):
|
||||
''' Resets batch flag and commits the batch requests. '''
|
||||
if self.is_batch:
|
||||
self.is_batch = False
|
||||
self.commit_batch_requests()
|
||||
|
||||
def commit_batch_requests(self):
|
||||
''' Commits the batch requests. '''
|
||||
|
||||
batch_boundary = b'batch_' + _new_boundary()
|
||||
changeset_boundary = b'changeset_' + _new_boundary()
|
||||
|
||||
# Commits batch only the requests list is not empty.
|
||||
if self.batch_requests:
|
||||
request = HTTPRequest()
|
||||
request.method = 'POST'
|
||||
request.host = self.batch_requests[0].host
|
||||
request.path = '/$batch'
|
||||
request.headers = [
|
||||
('Content-Type', 'multipart/mixed; boundary=' + \
|
||||
batch_boundary.decode('utf-8')),
|
||||
('Accept', 'application/atom+xml,application/xml'),
|
||||
('Accept-Charset', 'UTF-8')]
|
||||
|
||||
request.body = b'--' + batch_boundary + b'\n'
|
||||
request.body += b'Content-Type: multipart/mixed; boundary='
|
||||
request.body += changeset_boundary + b'\n\n'
|
||||
|
||||
content_id = 1
|
||||
|
||||
# Adds each request body to the POST data.
|
||||
for batch_request in self.batch_requests:
|
||||
request.body += b'--' + changeset_boundary + b'\n'
|
||||
request.body += b'Content-Type: application/http\n'
|
||||
request.body += b'Content-Transfer-Encoding: binary\n\n'
|
||||
request.body += batch_request.method.encode('utf-8')
|
||||
request.body += b' http://'
|
||||
request.body += batch_request.host.encode('utf-8')
|
||||
request.body += batch_request.path.encode('utf-8')
|
||||
request.body += b' HTTP/1.1\n'
|
||||
request.body += b'Content-ID: '
|
||||
request.body += str(content_id).encode('utf-8') + b'\n'
|
||||
content_id += 1
|
||||
|
||||
# Add different headers for different type requests.
|
||||
if not batch_request.method == 'DELETE':
|
||||
request.body += \
|
||||
b'Content-Type: application/atom+xml;type=entry\n'
|
||||
for name, value in batch_request.headers:
|
||||
if name == 'If-Match':
|
||||
request.body += name.encode('utf-8') + b': '
|
||||
request.body += value.encode('utf-8') + b'\n'
|
||||
break
|
||||
request.body += b'Content-Length: '
|
||||
request.body += str(len(batch_request.body)).encode('utf-8')
|
||||
request.body += b'\n\n'
|
||||
request.body += batch_request.body + b'\n'
|
||||
else:
|
||||
for name, value in batch_request.headers:
|
||||
# If-Match should be already included in
|
||||
# batch_request.headers, but in case it is missing,
|
||||
# just add it.
|
||||
if name == 'If-Match':
|
||||
request.body += name.encode('utf-8') + b': '
|
||||
request.body += value.encode('utf-8') + b'\n\n'
|
||||
break
|
||||
else:
|
||||
request.body += b'If-Match: *\n\n'
|
||||
|
||||
request.body += b'--' + changeset_boundary + b'--' + b'\n'
|
||||
request.body += b'--' + batch_boundary + b'--'
|
||||
|
||||
request.path, request.query = _update_request_uri_query(request)
|
||||
request.headers = _update_storage_table_header(request)
|
||||
auth = _sign_storage_table_request(request,
|
||||
self.account_name,
|
||||
self.account_key)
|
||||
request.headers.append(('Authorization', auth))
|
||||
|
||||
# Submit the whole request as batch request.
|
||||
response = self.perform_request(request)
|
||||
if response.status >= 300:
|
||||
raise HTTPError(response.status,
|
||||
_ERROR_BATCH_COMMIT_FAIL,
|
||||
self.respheader,
|
||||
response.body)
|
||||
|
||||
# http://www.odata.org/documentation/odata-version-2-0/batch-processing/
|
||||
# The body of a ChangeSet response is either a response for all the
|
||||
# successfully processed change request within the ChangeSet,
|
||||
# formatted exactly as it would have appeared outside of a batch,
|
||||
# or a single response indicating a failure of the entire ChangeSet.
|
||||
responses = self._parse_batch_response(response.body)
|
||||
if responses and responses[0].status >= 300:
|
||||
self._report_batch_error(responses[0])
|
||||
|
||||
def cancel_batch(self):
|
||||
''' Resets the batch flag. '''
|
||||
self.is_batch = False
|
||||
|
||||
def _parse_batch_response(self, body):
|
||||
parts = body.split(b'--changesetresponse_')
|
||||
|
||||
responses = []
|
||||
for part in parts:
|
||||
httpLocation = part.find(b'HTTP/')
|
||||
if httpLocation > 0:
|
||||
response = self._parse_batch_response_part(part[httpLocation:])
|
||||
responses.append(response)
|
||||
|
||||
return responses
|
||||
|
||||
def _parse_batch_response_part(self, part):
|
||||
lines = part.splitlines();
|
||||
|
||||
# First line is the HTTP status/reason
|
||||
status, _, reason = lines[0].partition(b' ')[2].partition(b' ')
|
||||
|
||||
# Followed by headers and body
|
||||
headers = []
|
||||
body = b''
|
||||
isBody = False
|
||||
for line in lines[1:]:
|
||||
if line == b'' and not isBody:
|
||||
isBody = True
|
||||
elif isBody:
|
||||
body += line
|
||||
else:
|
||||
headerName, _, headerVal = line.partition(b':')
|
||||
headers.append((headerName.lower(), headerVal))
|
||||
|
||||
return HTTPResponse(int(status), reason.strip(), headers, body)
|
||||
|
||||
def _report_batch_error(self, response):
|
||||
xml = response.body.decode('utf-8')
|
||||
doc = minidom.parseString(xml)
|
||||
|
||||
n = _get_children_from_path(doc, (METADATA_NS, 'error'), 'code')
|
||||
code = n[0].firstChild.nodeValue if n and n[0].firstChild else ''
|
||||
|
||||
n = _get_children_from_path(doc, (METADATA_NS, 'error'), 'message')
|
||||
message = n[0].firstChild.nodeValue if n and n[0].firstChild else xml
|
||||
|
||||
raise WindowsAzureBatchOperationError(message, code)
|
||||
#-------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft. 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.
|
||||
# You may obtain a copy of the License at
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License 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.
|
||||
#--------------------------------------------------------------------------
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
from azure import (
|
||||
_update_request_uri_query,
|
||||
WindowsAzureError,
|
||||
WindowsAzureBatchOperationError,
|
||||
_get_children_from_path,
|
||||
url_unquote,
|
||||
_ERROR_CANNOT_FIND_PARTITION_KEY,
|
||||
_ERROR_CANNOT_FIND_ROW_KEY,
|
||||
_ERROR_INCORRECT_TABLE_IN_BATCH,
|
||||
_ERROR_INCORRECT_PARTITION_KEY_IN_BATCH,
|
||||
_ERROR_DUPLICATE_ROW_KEY_IN_BATCH,
|
||||
_ERROR_BATCH_COMMIT_FAIL,
|
||||
)
|
||||
from azure.http import HTTPError, HTTPRequest, HTTPResponse
|
||||
from azure.http.httpclient import _HTTPClient
|
||||
from azure.storage import (
|
||||
_update_storage_table_header,
|
||||
METADATA_NS,
|
||||
_sign_storage_table_request,
|
||||
)
|
||||
from xml.dom import minidom
|
||||
|
||||
_DATASERVICES_NS = 'http://schemas.microsoft.com/ado/2007/08/dataservices'
|
||||
|
||||
if sys.version_info < (3,):
|
||||
def _new_boundary():
|
||||
return str(uuid.uuid1())
|
||||
else:
|
||||
def _new_boundary():
|
||||
return str(uuid.uuid1()).encode('utf-8')
|
||||
|
||||
|
||||
class _BatchClient(_HTTPClient):
|
||||
|
||||
'''
|
||||
This is the class that is used for batch operation for storage table
|
||||
service. It only supports one changeset.
|
||||
'''
|
||||
|
||||
def __init__(self, service_instance, account_key, account_name,
|
||||
protocol='http'):
|
||||
_HTTPClient.__init__(self, service_instance, account_name=account_name,
|
||||
account_key=account_key, protocol=protocol)
|
||||
self.is_batch = False
|
||||
self.batch_requests = []
|
||||
self.batch_table = ''
|
||||
self.batch_partition_key = ''
|
||||
self.batch_row_keys = []
|
||||
|
||||
def get_request_table(self, request):
|
||||
'''
|
||||
Extracts table name from request.uri. The request.uri has either
|
||||
"/mytable(...)" or "/mytable" format.
|
||||
|
||||
request: the request to insert, update or delete entity
|
||||
'''
|
||||
if '(' in request.path:
|
||||
pos = request.path.find('(')
|
||||
return request.path[1:pos]
|
||||
else:
|
||||
return request.path[1:]
|
||||
|
||||
def get_request_partition_key(self, request):
|
||||
'''
|
||||
Extracts PartitionKey from request.body if it is a POST request or from
|
||||
request.path if it is not a POST request. Only insert operation request
|
||||
is a POST request and the PartitionKey is in the request body.
|
||||
|
||||
request: the request to insert, update or delete entity
|
||||
'''
|
||||
if request.method == 'POST':
|
||||
doc = minidom.parseString(request.body)
|
||||
part_key = _get_children_from_path(
|
||||
doc, 'entry', 'content', (METADATA_NS, 'properties'),
|
||||
(_DATASERVICES_NS, 'PartitionKey'))
|
||||
if not part_key:
|
||||
raise WindowsAzureError(_ERROR_CANNOT_FIND_PARTITION_KEY)
|
||||
return part_key[0].firstChild.nodeValue
|
||||
else:
|
||||
uri = url_unquote(request.path)
|
||||
pos1 = uri.find('PartitionKey=\'')
|
||||
pos2 = uri.find('\',', pos1)
|
||||
if pos1 == -1 or pos2 == -1:
|
||||
raise WindowsAzureError(_ERROR_CANNOT_FIND_PARTITION_KEY)
|
||||
return uri[pos1 + len('PartitionKey=\''):pos2]
|
||||
|
||||
def get_request_row_key(self, request):
|
||||
'''
|
||||
Extracts RowKey from request.body if it is a POST request or from
|
||||
request.path if it is not a POST request. Only insert operation request
|
||||
is a POST request and the Rowkey is in the request body.
|
||||
|
||||
request: the request to insert, update or delete entity
|
||||
'''
|
||||
if request.method == 'POST':
|
||||
doc = minidom.parseString(request.body)
|
||||
row_key = _get_children_from_path(
|
||||
doc, 'entry', 'content', (METADATA_NS, 'properties'),
|
||||
(_DATASERVICES_NS, 'RowKey'))
|
||||
if not row_key:
|
||||
raise WindowsAzureError(_ERROR_CANNOT_FIND_ROW_KEY)
|
||||
return row_key[0].firstChild.nodeValue
|
||||
else:
|
||||
uri = url_unquote(request.path)
|
||||
pos1 = uri.find('RowKey=\'')
|
||||
pos2 = uri.find('\')', pos1)
|
||||
if pos1 == -1 or pos2 == -1:
|
||||
raise WindowsAzureError(_ERROR_CANNOT_FIND_ROW_KEY)
|
||||
row_key = uri[pos1 + len('RowKey=\''):pos2]
|
||||
return row_key
|
||||
|
||||
def validate_request_table(self, request):
|
||||
'''
|
||||
Validates that all requests have the same table name. Set the table
|
||||
name if it is the first request for the batch operation.
|
||||
|
||||
request: the request to insert, update or delete entity
|
||||
'''
|
||||
if self.batch_table:
|
||||
if self.get_request_table(request) != self.batch_table:
|
||||
raise WindowsAzureError(_ERROR_INCORRECT_TABLE_IN_BATCH)
|
||||
else:
|
||||
self.batch_table = self.get_request_table(request)
|
||||
|
||||
def validate_request_partition_key(self, request):
|
||||
'''
|
||||
Validates that all requests have the same PartitiionKey. Set the
|
||||
PartitionKey if it is the first request for the batch operation.
|
||||
|
||||
request: the request to insert, update or delete entity
|
||||
'''
|
||||
if self.batch_partition_key:
|
||||
if self.get_request_partition_key(request) != \
|
||||
self.batch_partition_key:
|
||||
raise WindowsAzureError(_ERROR_INCORRECT_PARTITION_KEY_IN_BATCH)
|
||||
else:
|
||||
self.batch_partition_key = self.get_request_partition_key(request)
|
||||
|
||||
def validate_request_row_key(self, request):
|
||||
'''
|
||||
Validates that all requests have the different RowKey and adds RowKey
|
||||
to existing RowKey list.
|
||||
|
||||
request: the request to insert, update or delete entity
|
||||
'''
|
||||
if self.batch_row_keys:
|
||||
if self.get_request_row_key(request) in self.batch_row_keys:
|
||||
raise WindowsAzureError(_ERROR_DUPLICATE_ROW_KEY_IN_BATCH)
|
||||
else:
|
||||
self.batch_row_keys.append(self.get_request_row_key(request))
|
||||
|
||||
def begin_batch(self):
|
||||
'''
|
||||
Starts the batch operation. Intializes the batch variables
|
||||
|
||||
is_batch: batch operation flag.
|
||||
batch_table: the table name of the batch operation
|
||||
batch_partition_key: the PartitionKey of the batch requests.
|
||||
batch_row_keys: the RowKey list of adding requests.
|
||||
batch_requests: the list of the requests.
|
||||
'''
|
||||
self.is_batch = True
|
||||
self.batch_table = ''
|
||||
self.batch_partition_key = ''
|
||||
self.batch_row_keys = []
|
||||
self.batch_requests = []
|
||||
|
||||
def insert_request_to_batch(self, request):
|
||||
'''
|
||||
Adds request to batch operation.
|
||||
|
||||
request: the request to insert, update or delete entity
|
||||
'''
|
||||
self.validate_request_table(request)
|
||||
self.validate_request_partition_key(request)
|
||||
self.validate_request_row_key(request)
|
||||
self.batch_requests.append(request)
|
||||
|
||||
def commit_batch(self):
|
||||
''' Resets batch flag and commits the batch requests. '''
|
||||
if self.is_batch:
|
||||
self.is_batch = False
|
||||
self.commit_batch_requests()
|
||||
|
||||
def commit_batch_requests(self):
|
||||
''' Commits the batch requests. '''
|
||||
|
||||
batch_boundary = b'batch_' + _new_boundary()
|
||||
changeset_boundary = b'changeset_' + _new_boundary()
|
||||
|
||||
# Commits batch only the requests list is not empty.
|
||||
if self.batch_requests:
|
||||
request = HTTPRequest()
|
||||
request.method = 'POST'
|
||||
request.host = self.batch_requests[0].host
|
||||
request.path = '/$batch'
|
||||
request.headers = [
|
||||
('Content-Type', 'multipart/mixed; boundary=' + \
|
||||
batch_boundary.decode('utf-8')),
|
||||
('Accept', 'application/atom+xml,application/xml'),
|
||||
('Accept-Charset', 'UTF-8')]
|
||||
|
||||
request.body = b'--' + batch_boundary + b'\n'
|
||||
request.body += b'Content-Type: multipart/mixed; boundary='
|
||||
request.body += changeset_boundary + b'\n\n'
|
||||
|
||||
content_id = 1
|
||||
|
||||
# Adds each request body to the POST data.
|
||||
for batch_request in self.batch_requests:
|
||||
request.body += b'--' + changeset_boundary + b'\n'
|
||||
request.body += b'Content-Type: application/http\n'
|
||||
request.body += b'Content-Transfer-Encoding: binary\n\n'
|
||||
request.body += batch_request.method.encode('utf-8')
|
||||
request.body += b' http://'
|
||||
request.body += batch_request.host.encode('utf-8')
|
||||
request.body += batch_request.path.encode('utf-8')
|
||||
request.body += b' HTTP/1.1\n'
|
||||
request.body += b'Content-ID: '
|
||||
request.body += str(content_id).encode('utf-8') + b'\n'
|
||||
content_id += 1
|
||||
|
||||
# Add different headers for different type requests.
|
||||
if not batch_request.method == 'DELETE':
|
||||
request.body += \
|
||||
b'Content-Type: application/atom+xml;type=entry\n'
|
||||
for name, value in batch_request.headers:
|
||||
if name == 'If-Match':
|
||||
request.body += name.encode('utf-8') + b': '
|
||||
request.body += value.encode('utf-8') + b'\n'
|
||||
break
|
||||
request.body += b'Content-Length: '
|
||||
request.body += str(len(batch_request.body)).encode('utf-8')
|
||||
request.body += b'\n\n'
|
||||
request.body += batch_request.body + b'\n'
|
||||
else:
|
||||
for name, value in batch_request.headers:
|
||||
# If-Match should be already included in
|
||||
# batch_request.headers, but in case it is missing,
|
||||
# just add it.
|
||||
if name == 'If-Match':
|
||||
request.body += name.encode('utf-8') + b': '
|
||||
request.body += value.encode('utf-8') + b'\n\n'
|
||||
break
|
||||
else:
|
||||
request.body += b'If-Match: *\n\n'
|
||||
|
||||
request.body += b'--' + changeset_boundary + b'--' + b'\n'
|
||||
request.body += b'--' + batch_boundary + b'--'
|
||||
|
||||
request.path, request.query = _update_request_uri_query(request)
|
||||
request.headers = _update_storage_table_header(request)
|
||||
auth = _sign_storage_table_request(request,
|
||||
self.account_name,
|
||||
self.account_key)
|
||||
request.headers.append(('Authorization', auth))
|
||||
|
||||
# Submit the whole request as batch request.
|
||||
response = self.perform_request(request)
|
||||
if response.status >= 300:
|
||||
raise HTTPError(response.status,
|
||||
_ERROR_BATCH_COMMIT_FAIL,
|
||||
self.respheader,
|
||||
response.body)
|
||||
|
||||
# http://www.odata.org/documentation/odata-version-2-0/batch-processing/
|
||||
# The body of a ChangeSet response is either a response for all the
|
||||
# successfully processed change request within the ChangeSet,
|
||||
# formatted exactly as it would have appeared outside of a batch,
|
||||
# or a single response indicating a failure of the entire ChangeSet.
|
||||
responses = self._parse_batch_response(response.body)
|
||||
if responses and responses[0].status >= 300:
|
||||
self._report_batch_error(responses[0])
|
||||
|
||||
def cancel_batch(self):
|
||||
''' Resets the batch flag. '''
|
||||
self.is_batch = False
|
||||
|
||||
def _parse_batch_response(self, body):
|
||||
parts = body.split(b'--changesetresponse_')
|
||||
|
||||
responses = []
|
||||
for part in parts:
|
||||
httpLocation = part.find(b'HTTP/')
|
||||
if httpLocation > 0:
|
||||
response = self._parse_batch_response_part(part[httpLocation:])
|
||||
responses.append(response)
|
||||
|
||||
return responses
|
||||
|
||||
def _parse_batch_response_part(self, part):
|
||||
lines = part.splitlines();
|
||||
|
||||
# First line is the HTTP status/reason
|
||||
status, _, reason = lines[0].partition(b' ')[2].partition(b' ')
|
||||
|
||||
# Followed by headers and body
|
||||
headers = []
|
||||
body = b''
|
||||
isBody = False
|
||||
for line in lines[1:]:
|
||||
if line == b'' and not isBody:
|
||||
isBody = True
|
||||
elif isBody:
|
||||
body += line
|
||||
else:
|
||||
headerName, _, headerVal = line.partition(b':')
|
||||
headers.append((headerName.lower(), headerVal))
|
||||
|
||||
return HTTPResponse(int(status), reason.strip(), headers, body)
|
||||
|
||||
def _report_batch_error(self, response):
|
||||
xml = response.body.decode('utf-8')
|
||||
doc = minidom.parseString(xml)
|
||||
|
||||
n = _get_children_from_path(doc, (METADATA_NS, 'error'), 'code')
|
||||
code = n[0].firstChild.nodeValue if n and n[0].firstChild else ''
|
||||
|
||||
n = _get_children_from_path(doc, (METADATA_NS, 'error'), 'message')
|
||||
message = n[0].firstChild.nodeValue if n and n[0].firstChild else xml
|
||||
|
||||
raise WindowsAzureBatchOperationError(message, code)
|
||||
|
||||
@ -1,223 +1,251 @@
|
||||
#-------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft. 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.
|
||||
# You may obtain a copy of the License at
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License 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.
|
||||
#--------------------------------------------------------------------------
|
||||
import base64
|
||||
import os
|
||||
import sys
|
||||
|
||||
if sys.version_info < (3,):
|
||||
from httplib import (
|
||||
HTTPSConnection,
|
||||
HTTPConnection,
|
||||
HTTP_PORT,
|
||||
HTTPS_PORT,
|
||||
)
|
||||
from urlparse import urlparse
|
||||
else:
|
||||
from http.client import (
|
||||
HTTPSConnection,
|
||||
HTTPConnection,
|
||||
HTTP_PORT,
|
||||
HTTPS_PORT,
|
||||
)
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from azure.http import HTTPError, HTTPResponse
|
||||
from azure import _USER_AGENT_STRING, _update_request_uri_query
|
||||
|
||||
|
||||
class _HTTPClient(object):
|
||||
|
||||
'''
|
||||
Takes the request and sends it to cloud service and returns the response.
|
||||
'''
|
||||
|
||||
def __init__(self, service_instance, cert_file=None, account_name=None,
|
||||
account_key=None, service_namespace=None, issuer=None,
|
||||
protocol='https'):
|
||||
'''
|
||||
service_instance: service client instance.
|
||||
cert_file:
|
||||
certificate file name/location. This is only used in hosted
|
||||
service management.
|
||||
account_name: the storage account.
|
||||
account_key:
|
||||
the storage account access key for storage services or servicebus
|
||||
access key for service bus service.
|
||||
service_namespace: the service namespace for service bus.
|
||||
issuer: the issuer for service bus service.
|
||||
'''
|
||||
self.service_instance = service_instance
|
||||
self.status = None
|
||||
self.respheader = None
|
||||
self.message = None
|
||||
self.cert_file = cert_file
|
||||
self.account_name = account_name
|
||||
self.account_key = account_key
|
||||
self.service_namespace = service_namespace
|
||||
self.issuer = issuer
|
||||
self.protocol = protocol
|
||||
self.proxy_host = None
|
||||
self.proxy_port = None
|
||||
self.proxy_user = None
|
||||
self.proxy_password = None
|
||||
self.use_httplib = self.should_use_httplib()
|
||||
|
||||
def should_use_httplib(self):
|
||||
if sys.platform.lower().startswith('win') and self.cert_file:
|
||||
# On Windows, auto-detect between Windows Store Certificate
|
||||
# (winhttp) and OpenSSL .pem certificate file (httplib).
|
||||
#
|
||||
# We used to only support certificates installed in the Windows
|
||||
# Certificate Store.
|
||||
# cert_file example: CURRENT_USER\my\CertificateName
|
||||
#
|
||||
# We now support using an OpenSSL .pem certificate file,
|
||||
# for a consistent experience across all platforms.
|
||||
# cert_file example: account\certificate.pem
|
||||
#
|
||||
# When using OpenSSL .pem certificate file on Windows, make sure
|
||||
# you are on CPython 2.7.4 or later.
|
||||
|
||||
# If it's not an existing file on disk, then treat it as a path in
|
||||
# the Windows Certificate Store, which means we can't use httplib.
|
||||
if not os.path.isfile(self.cert_file):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def set_proxy(self, host, port, user, password):
|
||||
'''
|
||||
Sets the proxy server host and port for the HTTP CONNECT Tunnelling.
|
||||
|
||||
host: Address of the proxy. Ex: '192.168.0.100'
|
||||
port: Port of the proxy. Ex: 6000
|
||||
user: User for proxy authorization.
|
||||
password: Password for proxy authorization.
|
||||
'''
|
||||
self.proxy_host = host
|
||||
self.proxy_port = port
|
||||
self.proxy_user = user
|
||||
self.proxy_password = password
|
||||
|
||||
def get_connection(self, request):
|
||||
''' Create connection for the request. '''
|
||||
protocol = request.protocol_override \
|
||||
if request.protocol_override else self.protocol
|
||||
target_host = request.host
|
||||
target_port = HTTP_PORT if protocol == 'http' else HTTPS_PORT
|
||||
|
||||
if not self.use_httplib:
|
||||
import azure.http.winhttp
|
||||
connection = azure.http.winhttp._HTTPConnection(
|
||||
target_host, cert_file=self.cert_file, protocol=protocol)
|
||||
proxy_host = self.proxy_host
|
||||
proxy_port = self.proxy_port
|
||||
else:
|
||||
if ':' in target_host:
|
||||
target_host, _, target_port = target_host.rpartition(':')
|
||||
if self.proxy_host:
|
||||
proxy_host = target_host
|
||||
proxy_port = target_port
|
||||
host = self.proxy_host
|
||||
port = self.proxy_port
|
||||
else:
|
||||
host = target_host
|
||||
port = target_port
|
||||
|
||||
if protocol == 'http':
|
||||
connection = HTTPConnection(host, int(port))
|
||||
else:
|
||||
connection = HTTPSConnection(
|
||||
host, int(port), cert_file=self.cert_file)
|
||||
|
||||
if self.proxy_host:
|
||||
headers = None
|
||||
if self.proxy_user and self.proxy_password:
|
||||
auth = base64.encodestring(
|
||||
"{0}:{1}".format(self.proxy_user, self.proxy_password))
|
||||
headers = {'Proxy-Authorization': 'Basic {0}'.format(auth)}
|
||||
connection.set_tunnel(proxy_host, int(proxy_port), headers)
|
||||
|
||||
return connection
|
||||
|
||||
def send_request_headers(self, connection, request_headers):
|
||||
if self.use_httplib:
|
||||
if self.proxy_host:
|
||||
for i in connection._buffer:
|
||||
if i.startswith("Host: "):
|
||||
connection._buffer.remove(i)
|
||||
connection.putheader(
|
||||
'Host', "{0}:{1}".format(connection._tunnel_host,
|
||||
connection._tunnel_port))
|
||||
|
||||
for name, value in request_headers:
|
||||
if value:
|
||||
connection.putheader(name, value)
|
||||
|
||||
connection.putheader('User-Agent', _USER_AGENT_STRING)
|
||||
connection.endheaders()
|
||||
|
||||
def send_request_body(self, connection, request_body):
|
||||
if request_body:
|
||||
assert isinstance(request_body, bytes)
|
||||
connection.send(request_body)
|
||||
elif (not isinstance(connection, HTTPSConnection) and
|
||||
not isinstance(connection, HTTPConnection)):
|
||||
connection.send(None)
|
||||
|
||||
def perform_request(self, request):
|
||||
''' Sends request to cloud service server and return the response. '''
|
||||
connection = self.get_connection(request)
|
||||
try:
|
||||
connection.putrequest(request.method, request.path)
|
||||
|
||||
if not self.use_httplib:
|
||||
if self.proxy_host and self.proxy_user:
|
||||
connection.set_proxy_credentials(
|
||||
self.proxy_user, self.proxy_password)
|
||||
|
||||
self.send_request_headers(connection, request.headers)
|
||||
self.send_request_body(connection, request.body)
|
||||
|
||||
resp = connection.getresponse()
|
||||
self.status = int(resp.status)
|
||||
self.message = resp.reason
|
||||
self.respheader = headers = resp.getheaders()
|
||||
|
||||
# for consistency across platforms, make header names lowercase
|
||||
for i, value in enumerate(headers):
|
||||
headers[i] = (value[0].lower(), value[1])
|
||||
|
||||
respbody = None
|
||||
if resp.length is None:
|
||||
respbody = resp.read()
|
||||
elif resp.length > 0:
|
||||
respbody = resp.read(resp.length)
|
||||
|
||||
response = HTTPResponse(
|
||||
int(resp.status), resp.reason, headers, respbody)
|
||||
if self.status == 307:
|
||||
new_url = urlparse(dict(headers)['location'])
|
||||
request.host = new_url.hostname
|
||||
request.path = new_url.path
|
||||
request.path, request.query = _update_request_uri_query(request)
|
||||
return self.perform_request(request)
|
||||
if self.status >= 300:
|
||||
raise HTTPError(self.status, self.message,
|
||||
self.respheader, respbody)
|
||||
|
||||
return response
|
||||
finally:
|
||||
connection.close()
|
||||
#-------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft. 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.
|
||||
# You may obtain a copy of the License at
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License 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.
|
||||
#--------------------------------------------------------------------------
|
||||
import base64
|
||||
import os
|
||||
import sys
|
||||
|
||||
if sys.version_info < (3,):
|
||||
from httplib import (
|
||||
HTTPSConnection,
|
||||
HTTPConnection,
|
||||
HTTP_PORT,
|
||||
HTTPS_PORT,
|
||||
)
|
||||
from urlparse import urlparse
|
||||
else:
|
||||
from http.client import (
|
||||
HTTPSConnection,
|
||||
HTTPConnection,
|
||||
HTTP_PORT,
|
||||
HTTPS_PORT,
|
||||
)
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from azure.http import HTTPError, HTTPResponse
|
||||
from azure import _USER_AGENT_STRING, _update_request_uri_query
|
||||
|
||||
DEBUG_REQUESTS = False
|
||||
DEBUG_RESPONSES = False
|
||||
|
||||
class _HTTPClient(object):
|
||||
|
||||
'''
|
||||
Takes the request and sends it to cloud service and returns the response.
|
||||
'''
|
||||
|
||||
def __init__(self, service_instance, cert_file=None, account_name=None,
|
||||
account_key=None, protocol='https', request_session=None):
|
||||
'''
|
||||
service_instance: service client instance.
|
||||
cert_file:
|
||||
certificate file name/location. This is only used in hosted
|
||||
service management.
|
||||
account_name: the storage account.
|
||||
account_key:
|
||||
the storage account access key.
|
||||
request_session:
|
||||
session object created with requests library (or compatible).
|
||||
'''
|
||||
self.service_instance = service_instance
|
||||
self.status = None
|
||||
self.respheader = None
|
||||
self.message = None
|
||||
self.cert_file = cert_file
|
||||
self.account_name = account_name
|
||||
self.account_key = account_key
|
||||
self.protocol = protocol
|
||||
self.proxy_host = None
|
||||
self.proxy_port = None
|
||||
self.proxy_user = None
|
||||
self.proxy_password = None
|
||||
self.request_session = request_session
|
||||
if request_session:
|
||||
self.use_httplib = True
|
||||
else:
|
||||
self.use_httplib = self.should_use_httplib()
|
||||
|
||||
def should_use_httplib(self):
|
||||
if sys.platform.lower().startswith('win') and self.cert_file:
|
||||
# On Windows, auto-detect between Windows Store Certificate
|
||||
# (winhttp) and OpenSSL .pem certificate file (httplib).
|
||||
#
|
||||
# We used to only support certificates installed in the Windows
|
||||
# Certificate Store.
|
||||
# cert_file example: CURRENT_USER\my\CertificateName
|
||||
#
|
||||
# We now support using an OpenSSL .pem certificate file,
|
||||
# for a consistent experience across all platforms.
|
||||
# cert_file example: account\certificate.pem
|
||||
#
|
||||
# When using OpenSSL .pem certificate file on Windows, make sure
|
||||
# you are on CPython 2.7.4 or later.
|
||||
|
||||
# If it's not an existing file on disk, then treat it as a path in
|
||||
# the Windows Certificate Store, which means we can't use httplib.
|
||||
if not os.path.isfile(self.cert_file):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def set_proxy(self, host, port, user, password):
|
||||
'''
|
||||
Sets the proxy server host and port for the HTTP CONNECT Tunnelling.
|
||||
|
||||
host: Address of the proxy. Ex: '192.168.0.100'
|
||||
port: Port of the proxy. Ex: 6000
|
||||
user: User for proxy authorization.
|
||||
password: Password for proxy authorization.
|
||||
'''
|
||||
self.proxy_host = host
|
||||
self.proxy_port = port
|
||||
self.proxy_user = user
|
||||
self.proxy_password = password
|
||||
|
||||
def get_uri(self, request):
|
||||
''' Return the target uri for the request.'''
|
||||
protocol = request.protocol_override \
|
||||
if request.protocol_override else self.protocol
|
||||
port = HTTP_PORT if protocol == 'http' else HTTPS_PORT
|
||||
return protocol + '://' + request.host + ':' + str(port) + request.path
|
||||
|
||||
def get_connection(self, request):
|
||||
''' Create connection for the request. '''
|
||||
protocol = request.protocol_override \
|
||||
if request.protocol_override else self.protocol
|
||||
target_host = request.host
|
||||
target_port = HTTP_PORT if protocol == 'http' else HTTPS_PORT
|
||||
|
||||
if self.request_session:
|
||||
import azure.http.requestsclient
|
||||
connection = azure.http.requestsclient._RequestsConnection(
|
||||
target_host, protocol, self.request_session)
|
||||
#TODO: proxy stuff
|
||||
elif not self.use_httplib:
|
||||
import azure.http.winhttp
|
||||
connection = azure.http.winhttp._HTTPConnection(
|
||||
target_host, cert_file=self.cert_file, protocol=protocol)
|
||||
proxy_host = self.proxy_host
|
||||
proxy_port = self.proxy_port
|
||||
else:
|
||||
if ':' in target_host:
|
||||
target_host, _, target_port = target_host.rpartition(':')
|
||||
if self.proxy_host:
|
||||
proxy_host = target_host
|
||||
proxy_port = target_port
|
||||
host = self.proxy_host
|
||||
port = self.proxy_port
|
||||
else:
|
||||
host = target_host
|
||||
port = target_port
|
||||
|
||||
if protocol == 'http':
|
||||
connection = HTTPConnection(host, int(port))
|
||||
else:
|
||||
connection = HTTPSConnection(
|
||||
host, int(port), cert_file=self.cert_file)
|
||||
|
||||
if self.proxy_host:
|
||||
headers = None
|
||||
if self.proxy_user and self.proxy_password:
|
||||
auth = base64.encodestring(
|
||||
"{0}:{1}".format(self.proxy_user, self.proxy_password))
|
||||
headers = {'Proxy-Authorization': 'Basic {0}'.format(auth)}
|
||||
connection.set_tunnel(proxy_host, int(proxy_port), headers)
|
||||
|
||||
return connection
|
||||
|
||||
def send_request_headers(self, connection, request_headers):
|
||||
if self.use_httplib:
|
||||
if self.proxy_host:
|
||||
for i in connection._buffer:
|
||||
if i.startswith("Host: "):
|
||||
connection._buffer.remove(i)
|
||||
connection.putheader(
|
||||
'Host', "{0}:{1}".format(connection._tunnel_host,
|
||||
connection._tunnel_port))
|
||||
|
||||
for name, value in request_headers:
|
||||
if value:
|
||||
connection.putheader(name, value)
|
||||
|
||||
connection.putheader('User-Agent', _USER_AGENT_STRING)
|
||||
connection.endheaders()
|
||||
|
||||
def send_request_body(self, connection, request_body):
|
||||
if request_body:
|
||||
assert isinstance(request_body, bytes)
|
||||
connection.send(request_body)
|
||||
elif (not isinstance(connection, HTTPSConnection) and
|
||||
not isinstance(connection, HTTPConnection)):
|
||||
connection.send(None)
|
||||
|
||||
def perform_request(self, request):
|
||||
''' Sends request to cloud service server and return the response. '''
|
||||
connection = self.get_connection(request)
|
||||
try:
|
||||
connection.putrequest(request.method, request.path)
|
||||
|
||||
if not self.use_httplib:
|
||||
if self.proxy_host and self.proxy_user:
|
||||
connection.set_proxy_credentials(
|
||||
self.proxy_user, self.proxy_password)
|
||||
|
||||
self.send_request_headers(connection, request.headers)
|
||||
self.send_request_body(connection, request.body)
|
||||
|
||||
if DEBUG_REQUESTS and request.body:
|
||||
print('request:')
|
||||
try:
|
||||
print(request.body)
|
||||
except:
|
||||
pass
|
||||
|
||||
resp = connection.getresponse()
|
||||
self.status = int(resp.status)
|
||||
self.message = resp.reason
|
||||
self.respheader = headers = resp.getheaders()
|
||||
|
||||
# for consistency across platforms, make header names lowercase
|
||||
for i, value in enumerate(headers):
|
||||
headers[i] = (value[0].lower(), value[1])
|
||||
|
||||
respbody = None
|
||||
if resp.length is None:
|
||||
respbody = resp.read()
|
||||
elif resp.length > 0:
|
||||
respbody = resp.read(resp.length)
|
||||
|
||||
if DEBUG_RESPONSES and respbody:
|
||||
print('response:')
|
||||
try:
|
||||
print(respbody)
|
||||
except:
|
||||
pass
|
||||
|
||||
response = HTTPResponse(
|
||||
int(resp.status), resp.reason, headers, respbody)
|
||||
if self.status == 307:
|
||||
new_url = urlparse(dict(headers)['location'])
|
||||
request.host = new_url.hostname
|
||||
request.path = new_url.path
|
||||
request.path, request.query = _update_request_uri_query(request)
|
||||
return self.perform_request(request)
|
||||
if self.status >= 300:
|
||||
raise HTTPError(self.status, self.message,
|
||||
self.respheader, respbody)
|
||||
|
||||
return response
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
74
awx/lib/site-packages/azure/http/requestsclient.py
Normal file
74
awx/lib/site-packages/azure/http/requestsclient.py
Normal file
@ -0,0 +1,74 @@
|
||||
#-------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft. 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.
|
||||
# You may obtain a copy of the License at
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License 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.
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
class _Response(object):
|
||||
|
||||
''' Response class corresponding to the response returned from httplib
|
||||
HTTPConnection. '''
|
||||
|
||||
def __init__(self, response):
|
||||
self.status = response.status_code
|
||||
self.reason = response.reason
|
||||
self.respbody = response.content
|
||||
self.length = len(response.content)
|
||||
self.headers = []
|
||||
for key, name in response.headers.items():
|
||||
self.headers.append((key.lower(), name))
|
||||
|
||||
def getheaders(self):
|
||||
'''Returns response headers.'''
|
||||
return self.headers
|
||||
|
||||
def read(self, _length):
|
||||
'''Returns response body. '''
|
||||
return self.respbody[:_length]
|
||||
|
||||
|
||||
class _RequestsConnection(object):
|
||||
|
||||
def __init__(self, host, protocol, session):
|
||||
self.host = host
|
||||
self.protocol = protocol
|
||||
self.session = session
|
||||
self.headers = {}
|
||||
self.method = None
|
||||
self.body = None
|
||||
self.response = None
|
||||
self.uri = None
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
def set_tunnel(self, host, port=None, headers=None):
|
||||
pass
|
||||
|
||||
def set_proxy_credentials(self, user, password):
|
||||
pass
|
||||
|
||||
def putrequest(self, method, uri):
|
||||
self.method = method
|
||||
self.uri = self.protocol + '://' + self.host + uri
|
||||
|
||||
def putheader(self, name, value):
|
||||
self.headers[name] = value
|
||||
|
||||
def endheaders(self):
|
||||
pass
|
||||
|
||||
def send(self, request_body):
|
||||
self.response = self.session.request(self.method, self.uri, data=request_body, headers=self.headers)
|
||||
|
||||
def getresponse(self):
|
||||
return _Response(self.response)
|
||||
@ -1,471 +1,471 @@
|
||||
#-------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft. 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.
|
||||
# You may obtain a copy of the License at
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License 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.
|
||||
#--------------------------------------------------------------------------
|
||||
from ctypes import (
|
||||
c_void_p,
|
||||
c_long,
|
||||
c_ulong,
|
||||
c_longlong,
|
||||
c_ulonglong,
|
||||
c_short,
|
||||
c_ushort,
|
||||
c_wchar_p,
|
||||
c_byte,
|
||||
byref,
|
||||
Structure,
|
||||
Union,
|
||||
POINTER,
|
||||
WINFUNCTYPE,
|
||||
HRESULT,
|
||||
oledll,
|
||||
WinDLL,
|
||||
)
|
||||
import ctypes
|
||||
import sys
|
||||
|
||||
if sys.version_info >= (3,):
|
||||
def unicode(text):
|
||||
return text
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Constants that are used in COM operations
|
||||
VT_EMPTY = 0
|
||||
VT_NULL = 1
|
||||
VT_I2 = 2
|
||||
VT_I4 = 3
|
||||
VT_BSTR = 8
|
||||
VT_BOOL = 11
|
||||
VT_I1 = 16
|
||||
VT_UI1 = 17
|
||||
VT_UI2 = 18
|
||||
VT_UI4 = 19
|
||||
VT_I8 = 20
|
||||
VT_UI8 = 21
|
||||
VT_ARRAY = 8192
|
||||
|
||||
HTTPREQUEST_PROXYSETTING_PROXY = 2
|
||||
HTTPREQUEST_SETCREDENTIALS_FOR_PROXY = 1
|
||||
|
||||
HTTPREQUEST_PROXY_SETTING = c_long
|
||||
HTTPREQUEST_SETCREDENTIALS_FLAGS = c_long
|
||||
#------------------------------------------------------------------------------
|
||||
# Com related APIs that are used.
|
||||
_ole32 = oledll.ole32
|
||||
_oleaut32 = WinDLL('oleaut32')
|
||||
_CLSIDFromString = _ole32.CLSIDFromString
|
||||
_CoInitialize = _ole32.CoInitialize
|
||||
_CoInitialize.argtypes = [c_void_p]
|
||||
|
||||
_CoCreateInstance = _ole32.CoCreateInstance
|
||||
|
||||
_SysAllocString = _oleaut32.SysAllocString
|
||||
_SysAllocString.restype = c_void_p
|
||||
_SysAllocString.argtypes = [c_wchar_p]
|
||||
|
||||
_SysFreeString = _oleaut32.SysFreeString
|
||||
_SysFreeString.argtypes = [c_void_p]
|
||||
|
||||
# SAFEARRAY*
|
||||
# SafeArrayCreateVector(_In_ VARTYPE vt,_In_ LONG lLbound,_In_ ULONG
|
||||
# cElements);
|
||||
_SafeArrayCreateVector = _oleaut32.SafeArrayCreateVector
|
||||
_SafeArrayCreateVector.restype = c_void_p
|
||||
_SafeArrayCreateVector.argtypes = [c_ushort, c_long, c_ulong]
|
||||
|
||||
# HRESULT
|
||||
# SafeArrayAccessData(_In_ SAFEARRAY *psa, _Out_ void **ppvData);
|
||||
_SafeArrayAccessData = _oleaut32.SafeArrayAccessData
|
||||
_SafeArrayAccessData.argtypes = [c_void_p, POINTER(c_void_p)]
|
||||
|
||||
# HRESULT
|
||||
# SafeArrayUnaccessData(_In_ SAFEARRAY *psa);
|
||||
_SafeArrayUnaccessData = _oleaut32.SafeArrayUnaccessData
|
||||
_SafeArrayUnaccessData.argtypes = [c_void_p]
|
||||
|
||||
# HRESULT
|
||||
# SafeArrayGetUBound(_In_ SAFEARRAY *psa, _In_ UINT nDim, _Out_ LONG
|
||||
# *plUbound);
|
||||
_SafeArrayGetUBound = _oleaut32.SafeArrayGetUBound
|
||||
_SafeArrayGetUBound.argtypes = [c_void_p, c_ulong, POINTER(c_long)]
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
class BSTR(c_wchar_p):
|
||||
|
||||
''' BSTR class in python. '''
|
||||
|
||||
def __init__(self, value):
|
||||
super(BSTR, self).__init__(_SysAllocString(value))
|
||||
|
||||
def __del__(self):
|
||||
_SysFreeString(self)
|
||||
|
||||
|
||||
class VARIANT(Structure):
|
||||
|
||||
'''
|
||||
VARIANT structure in python. Does not match the definition in
|
||||
MSDN exactly & it is only mapping the used fields. Field names are also
|
||||
slighty different.
|
||||
'''
|
||||
|
||||
class _tagData(Union):
|
||||
|
||||
class _tagRecord(Structure):
|
||||
_fields_ = [('pvoid', c_void_p), ('precord', c_void_p)]
|
||||
|
||||
_fields_ = [('llval', c_longlong),
|
||||
('ullval', c_ulonglong),
|
||||
('lval', c_long),
|
||||
('ulval', c_ulong),
|
||||
('ival', c_short),
|
||||
('boolval', c_ushort),
|
||||
('bstrval', BSTR),
|
||||
('parray', c_void_p),
|
||||
('record', _tagRecord)]
|
||||
|
||||
_fields_ = [('vt', c_ushort),
|
||||
('wReserved1', c_ushort),
|
||||
('wReserved2', c_ushort),
|
||||
('wReserved3', c_ushort),
|
||||
('vdata', _tagData)]
|
||||
|
||||
@staticmethod
|
||||
def create_empty():
|
||||
variant = VARIANT()
|
||||
variant.vt = VT_EMPTY
|
||||
variant.vdata.llval = 0
|
||||
return variant
|
||||
|
||||
@staticmethod
|
||||
def create_safearray_from_str(text):
|
||||
variant = VARIANT()
|
||||
variant.vt = VT_ARRAY | VT_UI1
|
||||
|
||||
length = len(text)
|
||||
variant.vdata.parray = _SafeArrayCreateVector(VT_UI1, 0, length)
|
||||
pvdata = c_void_p()
|
||||
_SafeArrayAccessData(variant.vdata.parray, byref(pvdata))
|
||||
ctypes.memmove(pvdata, text, length)
|
||||
_SafeArrayUnaccessData(variant.vdata.parray)
|
||||
|
||||
return variant
|
||||
|
||||
@staticmethod
|
||||
def create_bstr_from_str(text):
|
||||
variant = VARIANT()
|
||||
variant.vt = VT_BSTR
|
||||
variant.vdata.bstrval = BSTR(text)
|
||||
return variant
|
||||
|
||||
@staticmethod
|
||||
def create_bool_false():
|
||||
variant = VARIANT()
|
||||
variant.vt = VT_BOOL
|
||||
variant.vdata.boolval = 0
|
||||
return variant
|
||||
|
||||
def is_safearray_of_bytes(self):
|
||||
return self.vt == VT_ARRAY | VT_UI1
|
||||
|
||||
def str_from_safearray(self):
|
||||
assert self.vt == VT_ARRAY | VT_UI1
|
||||
pvdata = c_void_p()
|
||||
count = c_long()
|
||||
_SafeArrayGetUBound(self.vdata.parray, 1, byref(count))
|
||||
count = c_long(count.value + 1)
|
||||
_SafeArrayAccessData(self.vdata.parray, byref(pvdata))
|
||||
text = ctypes.string_at(pvdata, count)
|
||||
_SafeArrayUnaccessData(self.vdata.parray)
|
||||
return text
|
||||
|
||||
def __del__(self):
|
||||
_VariantClear(self)
|
||||
|
||||
# HRESULT VariantClear(_Inout_ VARIANTARG *pvarg);
|
||||
_VariantClear = _oleaut32.VariantClear
|
||||
_VariantClear.argtypes = [POINTER(VARIANT)]
|
||||
|
||||
|
||||
class GUID(Structure):
|
||||
|
||||
''' GUID structure in python. '''
|
||||
|
||||
_fields_ = [("data1", c_ulong),
|
||||
("data2", c_ushort),
|
||||
("data3", c_ushort),
|
||||
("data4", c_byte * 8)]
|
||||
|
||||
def __init__(self, name=None):
|
||||
if name is not None:
|
||||
_CLSIDFromString(unicode(name), byref(self))
|
||||
|
||||
|
||||
class _WinHttpRequest(c_void_p):
|
||||
|
||||
'''
|
||||
Maps the Com API to Python class functions. Not all methods in
|
||||
IWinHttpWebRequest are mapped - only the methods we use.
|
||||
'''
|
||||
_AddRef = WINFUNCTYPE(c_long) \
|
||||
(1, 'AddRef')
|
||||
_Release = WINFUNCTYPE(c_long) \
|
||||
(2, 'Release')
|
||||
_SetProxy = WINFUNCTYPE(HRESULT,
|
||||
HTTPREQUEST_PROXY_SETTING,
|
||||
VARIANT,
|
||||
VARIANT) \
|
||||
(7, 'SetProxy')
|
||||
_SetCredentials = WINFUNCTYPE(HRESULT,
|
||||
BSTR,
|
||||
BSTR,
|
||||
HTTPREQUEST_SETCREDENTIALS_FLAGS) \
|
||||
(8, 'SetCredentials')
|
||||
_Open = WINFUNCTYPE(HRESULT, BSTR, BSTR, VARIANT) \
|
||||
(9, 'Open')
|
||||
_SetRequestHeader = WINFUNCTYPE(HRESULT, BSTR, BSTR) \
|
||||
(10, 'SetRequestHeader')
|
||||
_GetResponseHeader = WINFUNCTYPE(HRESULT, BSTR, POINTER(c_void_p)) \
|
||||
(11, 'GetResponseHeader')
|
||||
_GetAllResponseHeaders = WINFUNCTYPE(HRESULT, POINTER(c_void_p)) \
|
||||
(12, 'GetAllResponseHeaders')
|
||||
_Send = WINFUNCTYPE(HRESULT, VARIANT) \
|
||||
(13, 'Send')
|
||||
_Status = WINFUNCTYPE(HRESULT, POINTER(c_long)) \
|
||||
(14, 'Status')
|
||||
_StatusText = WINFUNCTYPE(HRESULT, POINTER(c_void_p)) \
|
||||
(15, 'StatusText')
|
||||
_ResponseText = WINFUNCTYPE(HRESULT, POINTER(c_void_p)) \
|
||||
(16, 'ResponseText')
|
||||
_ResponseBody = WINFUNCTYPE(HRESULT, POINTER(VARIANT)) \
|
||||
(17, 'ResponseBody')
|
||||
_ResponseStream = WINFUNCTYPE(HRESULT, POINTER(VARIANT)) \
|
||||
(18, 'ResponseStream')
|
||||
_WaitForResponse = WINFUNCTYPE(HRESULT, VARIANT, POINTER(c_ushort)) \
|
||||
(21, 'WaitForResponse')
|
||||
_Abort = WINFUNCTYPE(HRESULT) \
|
||||
(22, 'Abort')
|
||||
_SetTimeouts = WINFUNCTYPE(HRESULT, c_long, c_long, c_long, c_long) \
|
||||
(23, 'SetTimeouts')
|
||||
_SetClientCertificate = WINFUNCTYPE(HRESULT, BSTR) \
|
||||
(24, 'SetClientCertificate')
|
||||
|
||||
def open(self, method, url):
|
||||
'''
|
||||
Opens the request.
|
||||
|
||||
method: the request VERB 'GET', 'POST', etc.
|
||||
url: the url to connect
|
||||
'''
|
||||
_WinHttpRequest._SetTimeouts(self, 0, 65000, 65000, 65000)
|
||||
|
||||
flag = VARIANT.create_bool_false()
|
||||
_method = BSTR(method)
|
||||
_url = BSTR(url)
|
||||
_WinHttpRequest._Open(self, _method, _url, flag)
|
||||
|
||||
def set_request_header(self, name, value):
|
||||
''' Sets the request header. '''
|
||||
|
||||
_name = BSTR(name)
|
||||
_value = BSTR(value)
|
||||
_WinHttpRequest._SetRequestHeader(self, _name, _value)
|
||||
|
||||
def get_all_response_headers(self):
|
||||
''' Gets back all response headers. '''
|
||||
|
||||
bstr_headers = c_void_p()
|
||||
_WinHttpRequest._GetAllResponseHeaders(self, byref(bstr_headers))
|
||||
bstr_headers = ctypes.cast(bstr_headers, c_wchar_p)
|
||||
headers = bstr_headers.value
|
||||
_SysFreeString(bstr_headers)
|
||||
return headers
|
||||
|
||||
def send(self, request=None):
|
||||
''' Sends the request body. '''
|
||||
|
||||
# Sends VT_EMPTY if it is GET, HEAD request.
|
||||
if request is None:
|
||||
var_empty = VARIANT.create_empty()
|
||||
_WinHttpRequest._Send(self, var_empty)
|
||||
else: # Sends request body as SAFEArray.
|
||||
_request = VARIANT.create_safearray_from_str(request)
|
||||
_WinHttpRequest._Send(self, _request)
|
||||
|
||||
def status(self):
|
||||
''' Gets status of response. '''
|
||||
|
||||
status = c_long()
|
||||
_WinHttpRequest._Status(self, byref(status))
|
||||
return int(status.value)
|
||||
|
||||
def status_text(self):
|
||||
''' Gets status text of response. '''
|
||||
|
||||
bstr_status_text = c_void_p()
|
||||
_WinHttpRequest._StatusText(self, byref(bstr_status_text))
|
||||
bstr_status_text = ctypes.cast(bstr_status_text, c_wchar_p)
|
||||
status_text = bstr_status_text.value
|
||||
_SysFreeString(bstr_status_text)
|
||||
return status_text
|
||||
|
||||
def response_body(self):
|
||||
'''
|
||||
Gets response body as a SAFEARRAY and converts the SAFEARRAY to str.
|
||||
If it is an xml file, it always contains 3 characters before <?xml,
|
||||
so we remove them.
|
||||
'''
|
||||
var_respbody = VARIANT()
|
||||
_WinHttpRequest._ResponseBody(self, byref(var_respbody))
|
||||
if var_respbody.is_safearray_of_bytes():
|
||||
respbody = var_respbody.str_from_safearray()
|
||||
if respbody[3:].startswith(b'<?xml') and\
|
||||
respbody.startswith(b'\xef\xbb\xbf'):
|
||||
respbody = respbody[3:]
|
||||
return respbody
|
||||
else:
|
||||
return ''
|
||||
|
||||
def set_client_certificate(self, certificate):
|
||||
'''Sets client certificate for the request. '''
|
||||
_certificate = BSTR(certificate)
|
||||
_WinHttpRequest._SetClientCertificate(self, _certificate)
|
||||
|
||||
def set_tunnel(self, host, port):
|
||||
''' Sets up the host and the port for the HTTP CONNECT Tunnelling.'''
|
||||
url = host
|
||||
if port:
|
||||
url = url + u':' + port
|
||||
|
||||
var_host = VARIANT.create_bstr_from_str(url)
|
||||
var_empty = VARIANT.create_empty()
|
||||
|
||||
_WinHttpRequest._SetProxy(
|
||||
self, HTTPREQUEST_PROXYSETTING_PROXY, var_host, var_empty)
|
||||
|
||||
def set_proxy_credentials(self, user, password):
|
||||
_WinHttpRequest._SetCredentials(
|
||||
self, BSTR(user), BSTR(password),
|
||||
HTTPREQUEST_SETCREDENTIALS_FOR_PROXY)
|
||||
|
||||
def __del__(self):
|
||||
if self.value is not None:
|
||||
_WinHttpRequest._Release(self)
|
||||
|
||||
|
||||
class _Response(object):
|
||||
|
||||
''' Response class corresponding to the response returned from httplib
|
||||
HTTPConnection. '''
|
||||
|
||||
def __init__(self, _status, _status_text, _length, _headers, _respbody):
|
||||
self.status = _status
|
||||
self.reason = _status_text
|
||||
self.length = _length
|
||||
self.headers = _headers
|
||||
self.respbody = _respbody
|
||||
|
||||
def getheaders(self):
|
||||
'''Returns response headers.'''
|
||||
return self.headers
|
||||
|
||||
def read(self, _length):
|
||||
'''Returns resonse body. '''
|
||||
return self.respbody[:_length]
|
||||
|
||||
|
||||
class _HTTPConnection(object):
|
||||
|
||||
''' Class corresponding to httplib HTTPConnection class. '''
|
||||
|
||||
def __init__(self, host, cert_file=None, key_file=None, protocol='http'):
|
||||
''' initialize the IWinHttpWebRequest Com Object.'''
|
||||
self.host = unicode(host)
|
||||
self.cert_file = cert_file
|
||||
self._httprequest = _WinHttpRequest()
|
||||
self.protocol = protocol
|
||||
clsid = GUID('{2087C2F4-2CEF-4953-A8AB-66779B670495}')
|
||||
iid = GUID('{016FE2EC-B2C8-45F8-B23B-39E53A75396B}')
|
||||
_CoInitialize(None)
|
||||
_CoCreateInstance(byref(clsid), 0, 1, byref(iid),
|
||||
byref(self._httprequest))
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
def set_tunnel(self, host, port=None, headers=None):
|
||||
''' Sets up the host and the port for the HTTP CONNECT Tunnelling. '''
|
||||
self._httprequest.set_tunnel(unicode(host), unicode(str(port)))
|
||||
|
||||
def set_proxy_credentials(self, user, password):
|
||||
self._httprequest.set_proxy_credentials(
|
||||
unicode(user), unicode(password))
|
||||
|
||||
def putrequest(self, method, uri):
|
||||
''' Connects to host and sends the request. '''
|
||||
|
||||
protocol = unicode(self.protocol + '://')
|
||||
url = protocol + self.host + unicode(uri)
|
||||
self._httprequest.open(unicode(method), url)
|
||||
|
||||
# sets certificate for the connection if cert_file is set.
|
||||
if self.cert_file is not None:
|
||||
self._httprequest.set_client_certificate(unicode(self.cert_file))
|
||||
|
||||
def putheader(self, name, value):
|
||||
''' Sends the headers of request. '''
|
||||
if sys.version_info < (3,):
|
||||
name = str(name).decode('utf-8')
|
||||
value = str(value).decode('utf-8')
|
||||
self._httprequest.set_request_header(name, value)
|
||||
|
||||
def endheaders(self):
|
||||
''' No operation. Exists only to provide the same interface of httplib
|
||||
HTTPConnection.'''
|
||||
pass
|
||||
|
||||
def send(self, request_body):
|
||||
''' Sends request body. '''
|
||||
if not request_body:
|
||||
self._httprequest.send()
|
||||
else:
|
||||
self._httprequest.send(request_body)
|
||||
|
||||
def getresponse(self):
|
||||
''' Gets the response and generates the _Response object'''
|
||||
status = self._httprequest.status()
|
||||
status_text = self._httprequest.status_text()
|
||||
|
||||
resp_headers = self._httprequest.get_all_response_headers()
|
||||
fixed_headers = []
|
||||
for resp_header in resp_headers.split('\n'):
|
||||
if (resp_header.startswith('\t') or\
|
||||
resp_header.startswith(' ')) and fixed_headers:
|
||||
# append to previous header
|
||||
fixed_headers[-1] += resp_header
|
||||
else:
|
||||
fixed_headers.append(resp_header)
|
||||
|
||||
headers = []
|
||||
for resp_header in fixed_headers:
|
||||
if ':' in resp_header:
|
||||
pos = resp_header.find(':')
|
||||
headers.append(
|
||||
(resp_header[:pos].lower(), resp_header[pos + 1:].strip()))
|
||||
|
||||
body = self._httprequest.response_body()
|
||||
length = len(body)
|
||||
|
||||
return _Response(status, status_text, length, headers, body)
|
||||
#-------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft. 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.
|
||||
# You may obtain a copy of the License at
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License 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.
|
||||
#--------------------------------------------------------------------------
|
||||
from ctypes import (
|
||||
c_void_p,
|
||||
c_long,
|
||||
c_ulong,
|
||||
c_longlong,
|
||||
c_ulonglong,
|
||||
c_short,
|
||||
c_ushort,
|
||||
c_wchar_p,
|
||||
c_byte,
|
||||
byref,
|
||||
Structure,
|
||||
Union,
|
||||
POINTER,
|
||||
WINFUNCTYPE,
|
||||
HRESULT,
|
||||
oledll,
|
||||
WinDLL,
|
||||
)
|
||||
import ctypes
|
||||
import sys
|
||||
|
||||
if sys.version_info >= (3,):
|
||||
def unicode(text):
|
||||
return text
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Constants that are used in COM operations
|
||||
VT_EMPTY = 0
|
||||
VT_NULL = 1
|
||||
VT_I2 = 2
|
||||
VT_I4 = 3
|
||||
VT_BSTR = 8
|
||||
VT_BOOL = 11
|
||||
VT_I1 = 16
|
||||
VT_UI1 = 17
|
||||
VT_UI2 = 18
|
||||
VT_UI4 = 19
|
||||
VT_I8 = 20
|
||||
VT_UI8 = 21
|
||||
VT_ARRAY = 8192
|
||||
|
||||
HTTPREQUEST_PROXYSETTING_PROXY = 2
|
||||
HTTPREQUEST_SETCREDENTIALS_FOR_PROXY = 1
|
||||
|
||||
HTTPREQUEST_PROXY_SETTING = c_long
|
||||
HTTPREQUEST_SETCREDENTIALS_FLAGS = c_long
|
||||
#------------------------------------------------------------------------------
|
||||
# Com related APIs that are used.
|
||||
_ole32 = oledll.ole32
|
||||
_oleaut32 = WinDLL('oleaut32')
|
||||
_CLSIDFromString = _ole32.CLSIDFromString
|
||||
_CoInitialize = _ole32.CoInitialize
|
||||
_CoInitialize.argtypes = [c_void_p]
|
||||
|
||||
_CoCreateInstance = _ole32.CoCreateInstance
|
||||
|
||||
_SysAllocString = _oleaut32.SysAllocString
|
||||
_SysAllocString.restype = c_void_p
|
||||
_SysAllocString.argtypes = [c_wchar_p]
|
||||
|
||||
_SysFreeString = _oleaut32.SysFreeString
|
||||
_SysFreeString.argtypes = [c_void_p]
|
||||
|
||||
# SAFEARRAY*
|
||||
# SafeArrayCreateVector(_In_ VARTYPE vt,_In_ LONG lLbound,_In_ ULONG
|
||||
# cElements);
|
||||
_SafeArrayCreateVector = _oleaut32.SafeArrayCreateVector
|
||||
_SafeArrayCreateVector.restype = c_void_p
|
||||
_SafeArrayCreateVector.argtypes = [c_ushort, c_long, c_ulong]
|
||||
|
||||
# HRESULT
|
||||
# SafeArrayAccessData(_In_ SAFEARRAY *psa, _Out_ void **ppvData);
|
||||
_SafeArrayAccessData = _oleaut32.SafeArrayAccessData
|
||||
_SafeArrayAccessData.argtypes = [c_void_p, POINTER(c_void_p)]
|
||||
|
||||
# HRESULT
|
||||
# SafeArrayUnaccessData(_In_ SAFEARRAY *psa);
|
||||
_SafeArrayUnaccessData = _oleaut32.SafeArrayUnaccessData
|
||||
_SafeArrayUnaccessData.argtypes = [c_void_p]
|
||||
|
||||
# HRESULT
|
||||
# SafeArrayGetUBound(_In_ SAFEARRAY *psa, _In_ UINT nDim, _Out_ LONG
|
||||
# *plUbound);
|
||||
_SafeArrayGetUBound = _oleaut32.SafeArrayGetUBound
|
||||
_SafeArrayGetUBound.argtypes = [c_void_p, c_ulong, POINTER(c_long)]
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
class BSTR(c_wchar_p):
|
||||
|
||||
''' BSTR class in python. '''
|
||||
|
||||
def __init__(self, value):
|
||||
super(BSTR, self).__init__(_SysAllocString(value))
|
||||
|
||||
def __del__(self):
|
||||
_SysFreeString(self)
|
||||
|
||||
|
||||
class VARIANT(Structure):
|
||||
|
||||
'''
|
||||
VARIANT structure in python. Does not match the definition in
|
||||
MSDN exactly & it is only mapping the used fields. Field names are also
|
||||
slighty different.
|
||||
'''
|
||||
|
||||
class _tagData(Union):
|
||||
|
||||
class _tagRecord(Structure):
|
||||
_fields_ = [('pvoid', c_void_p), ('precord', c_void_p)]
|
||||
|
||||
_fields_ = [('llval', c_longlong),
|
||||
('ullval', c_ulonglong),
|
||||
('lval', c_long),
|
||||
('ulval', c_ulong),
|
||||
('ival', c_short),
|
||||
('boolval', c_ushort),
|
||||
('bstrval', BSTR),
|
||||
('parray', c_void_p),
|
||||
('record', _tagRecord)]
|
||||
|
||||
_fields_ = [('vt', c_ushort),
|
||||
('wReserved1', c_ushort),
|
||||
('wReserved2', c_ushort),
|
||||
('wReserved3', c_ushort),
|
||||
('vdata', _tagData)]
|
||||
|
||||
@staticmethod
|
||||
def create_empty():
|
||||
variant = VARIANT()
|
||||
variant.vt = VT_EMPTY
|
||||
variant.vdata.llval = 0
|
||||
return variant
|
||||
|
||||
@staticmethod
|
||||
def create_safearray_from_str(text):
|
||||
variant = VARIANT()
|
||||
variant.vt = VT_ARRAY | VT_UI1
|
||||
|
||||
length = len(text)
|
||||
variant.vdata.parray = _SafeArrayCreateVector(VT_UI1, 0, length)
|
||||
pvdata = c_void_p()
|
||||
_SafeArrayAccessData(variant.vdata.parray, byref(pvdata))
|
||||
ctypes.memmove(pvdata, text, length)
|
||||
_SafeArrayUnaccessData(variant.vdata.parray)
|
||||
|
||||
return variant
|
||||
|
||||
@staticmethod
|
||||
def create_bstr_from_str(text):
|
||||
variant = VARIANT()
|
||||
variant.vt = VT_BSTR
|
||||
variant.vdata.bstrval = BSTR(text)
|
||||
return variant
|
||||
|
||||
@staticmethod
|
||||
def create_bool_false():
|
||||
variant = VARIANT()
|
||||
variant.vt = VT_BOOL
|
||||
variant.vdata.boolval = 0
|
||||
return variant
|
||||
|
||||
def is_safearray_of_bytes(self):
|
||||
return self.vt == VT_ARRAY | VT_UI1
|
||||
|
||||
def str_from_safearray(self):
|
||||
assert self.vt == VT_ARRAY | VT_UI1
|
||||
pvdata = c_void_p()
|
||||
count = c_long()
|
||||
_SafeArrayGetUBound(self.vdata.parray, 1, byref(count))
|
||||
count = c_long(count.value + 1)
|
||||
_SafeArrayAccessData(self.vdata.parray, byref(pvdata))
|
||||
text = ctypes.string_at(pvdata, count)
|
||||
_SafeArrayUnaccessData(self.vdata.parray)
|
||||
return text
|
||||
|
||||
def __del__(self):
|
||||
_VariantClear(self)
|
||||
|
||||
# HRESULT VariantClear(_Inout_ VARIANTARG *pvarg);
|
||||
_VariantClear = _oleaut32.VariantClear
|
||||
_VariantClear.argtypes = [POINTER(VARIANT)]
|
||||
|
||||
|
||||
class GUID(Structure):
|
||||
|
||||
''' GUID structure in python. '''
|
||||
|
||||
_fields_ = [("data1", c_ulong),
|
||||
("data2", c_ushort),
|
||||
("data3", c_ushort),
|
||||
("data4", c_byte * 8)]
|
||||
|
||||
def __init__(self, name=None):
|
||||
if name is not None:
|
||||
_CLSIDFromString(unicode(name), byref(self))
|
||||
|
||||
|
||||
class _WinHttpRequest(c_void_p):
|
||||
|
||||
'''
|
||||
Maps the Com API to Python class functions. Not all methods in
|
||||
IWinHttpWebRequest are mapped - only the methods we use.
|
||||
'''
|
||||
_AddRef = WINFUNCTYPE(c_long) \
|
||||
(1, 'AddRef')
|
||||
_Release = WINFUNCTYPE(c_long) \
|
||||
(2, 'Release')
|
||||
_SetProxy = WINFUNCTYPE(HRESULT,
|
||||
HTTPREQUEST_PROXY_SETTING,
|
||||
VARIANT,
|
||||
VARIANT) \
|
||||
(7, 'SetProxy')
|
||||
_SetCredentials = WINFUNCTYPE(HRESULT,
|
||||
BSTR,
|
||||
BSTR,
|
||||
HTTPREQUEST_SETCREDENTIALS_FLAGS) \
|
||||
(8, 'SetCredentials')
|
||||
_Open = WINFUNCTYPE(HRESULT, BSTR, BSTR, VARIANT) \
|
||||
(9, 'Open')
|
||||
_SetRequestHeader = WINFUNCTYPE(HRESULT, BSTR, BSTR) \
|
||||
(10, 'SetRequestHeader')
|
||||
_GetResponseHeader = WINFUNCTYPE(HRESULT, BSTR, POINTER(c_void_p)) \
|
||||
(11, 'GetResponseHeader')
|
||||
_GetAllResponseHeaders = WINFUNCTYPE(HRESULT, POINTER(c_void_p)) \
|
||||
(12, 'GetAllResponseHeaders')
|
||||
_Send = WINFUNCTYPE(HRESULT, VARIANT) \
|
||||
(13, 'Send')
|
||||
_Status = WINFUNCTYPE(HRESULT, POINTER(c_long)) \
|
||||
(14, 'Status')
|
||||
_StatusText = WINFUNCTYPE(HRESULT, POINTER(c_void_p)) \
|
||||
(15, 'StatusText')
|
||||
_ResponseText = WINFUNCTYPE(HRESULT, POINTER(c_void_p)) \
|
||||
(16, 'ResponseText')
|
||||
_ResponseBody = WINFUNCTYPE(HRESULT, POINTER(VARIANT)) \
|
||||
(17, 'ResponseBody')
|
||||
_ResponseStream = WINFUNCTYPE(HRESULT, POINTER(VARIANT)) \
|
||||
(18, 'ResponseStream')
|
||||
_WaitForResponse = WINFUNCTYPE(HRESULT, VARIANT, POINTER(c_ushort)) \
|
||||
(21, 'WaitForResponse')
|
||||
_Abort = WINFUNCTYPE(HRESULT) \
|
||||
(22, 'Abort')
|
||||
_SetTimeouts = WINFUNCTYPE(HRESULT, c_long, c_long, c_long, c_long) \
|
||||
(23, 'SetTimeouts')
|
||||
_SetClientCertificate = WINFUNCTYPE(HRESULT, BSTR) \
|
||||
(24, 'SetClientCertificate')
|
||||
|
||||
def open(self, method, url):
|
||||
'''
|
||||
Opens the request.
|
||||
|
||||
method: the request VERB 'GET', 'POST', etc.
|
||||
url: the url to connect
|
||||
'''
|
||||
_WinHttpRequest._SetTimeouts(self, 0, 65000, 65000, 65000)
|
||||
|
||||
flag = VARIANT.create_bool_false()
|
||||
_method = BSTR(method)
|
||||
_url = BSTR(url)
|
||||
_WinHttpRequest._Open(self, _method, _url, flag)
|
||||
|
||||
def set_request_header(self, name, value):
|
||||
''' Sets the request header. '''
|
||||
|
||||
_name = BSTR(name)
|
||||
_value = BSTR(value)
|
||||
_WinHttpRequest._SetRequestHeader(self, _name, _value)
|
||||
|
||||
def get_all_response_headers(self):
|
||||
''' Gets back all response headers. '''
|
||||
|
||||
bstr_headers = c_void_p()
|
||||
_WinHttpRequest._GetAllResponseHeaders(self, byref(bstr_headers))
|
||||
bstr_headers = ctypes.cast(bstr_headers, c_wchar_p)
|
||||
headers = bstr_headers.value
|
||||
_SysFreeString(bstr_headers)
|
||||
return headers
|
||||
|
||||
def send(self, request=None):
|
||||
''' Sends the request body. '''
|
||||
|
||||
# Sends VT_EMPTY if it is GET, HEAD request.
|
||||
if request is None:
|
||||
var_empty = VARIANT.create_empty()
|
||||
_WinHttpRequest._Send(self, var_empty)
|
||||
else: # Sends request body as SAFEArray.
|
||||
_request = VARIANT.create_safearray_from_str(request)
|
||||
_WinHttpRequest._Send(self, _request)
|
||||
|
||||
def status(self):
|
||||
''' Gets status of response. '''
|
||||
|
||||
status = c_long()
|
||||
_WinHttpRequest._Status(self, byref(status))
|
||||
return int(status.value)
|
||||
|
||||
def status_text(self):
|
||||
''' Gets status text of response. '''
|
||||
|
||||
bstr_status_text = c_void_p()
|
||||
_WinHttpRequest._StatusText(self, byref(bstr_status_text))
|
||||
bstr_status_text = ctypes.cast(bstr_status_text, c_wchar_p)
|
||||
status_text = bstr_status_text.value
|
||||
_SysFreeString(bstr_status_text)
|
||||
return status_text
|
||||
|
||||
def response_body(self):
|
||||
'''
|
||||
Gets response body as a SAFEARRAY and converts the SAFEARRAY to str.
|
||||
If it is an xml file, it always contains 3 characters before <?xml,
|
||||
so we remove them.
|
||||
'''
|
||||
var_respbody = VARIANT()
|
||||
_WinHttpRequest._ResponseBody(self, byref(var_respbody))
|
||||
if var_respbody.is_safearray_of_bytes():
|
||||
respbody = var_respbody.str_from_safearray()
|
||||
if respbody[3:].startswith(b'<?xml') and\
|
||||
respbody.startswith(b'\xef\xbb\xbf'):
|
||||
respbody = respbody[3:]
|
||||
return respbody
|
||||
else:
|
||||
return ''
|
||||
|
||||
def set_client_certificate(self, certificate):
|
||||
'''Sets client certificate for the request. '''
|
||||
_certificate = BSTR(certificate)
|
||||
_WinHttpRequest._SetClientCertificate(self, _certificate)
|
||||
|
||||
def set_tunnel(self, host, port):
|
||||
''' Sets up the host and the port for the HTTP CONNECT Tunnelling.'''
|
||||
url = host
|
||||
if port:
|
||||
url = url + u':' + port
|
||||
|
||||
var_host = VARIANT.create_bstr_from_str(url)
|
||||
var_empty = VARIANT.create_empty()
|
||||
|
||||
_WinHttpRequest._SetProxy(
|
||||
self, HTTPREQUEST_PROXYSETTING_PROXY, var_host, var_empty)
|
||||
|
||||
def set_proxy_credentials(self, user, password):
|
||||
_WinHttpRequest._SetCredentials(
|
||||
self, BSTR(user), BSTR(password),
|
||||
HTTPREQUEST_SETCREDENTIALS_FOR_PROXY)
|
||||
|
||||
def __del__(self):
|
||||
if self.value is not None:
|
||||
_WinHttpRequest._Release(self)
|
||||
|
||||
|
||||
class _Response(object):
|
||||
|
||||
''' Response class corresponding to the response returned from httplib
|
||||
HTTPConnection. '''
|
||||
|
||||
def __init__(self, _status, _status_text, _length, _headers, _respbody):
|
||||
self.status = _status
|
||||
self.reason = _status_text
|
||||
self.length = _length
|
||||
self.headers = _headers
|
||||
self.respbody = _respbody
|
||||
|
||||
def getheaders(self):
|
||||
'''Returns response headers.'''
|
||||
return self.headers
|
||||
|
||||
def read(self, _length):
|
||||
'''Returns resonse body. '''
|
||||
return self.respbody[:_length]
|
||||
|
||||
|
||||
class _HTTPConnection(object):
|
||||
|
||||
''' Class corresponding to httplib HTTPConnection class. '''
|
||||
|
||||
def __init__(self, host, cert_file=None, key_file=None, protocol='http'):
|
||||
''' initialize the IWinHttpWebRequest Com Object.'''
|
||||
self.host = unicode(host)
|
||||
self.cert_file = cert_file
|
||||
self._httprequest = _WinHttpRequest()
|
||||
self.protocol = protocol
|
||||
clsid = GUID('{2087C2F4-2CEF-4953-A8AB-66779B670495}')
|
||||
iid = GUID('{016FE2EC-B2C8-45F8-B23B-39E53A75396B}')
|
||||
_CoInitialize(None)
|
||||
_CoCreateInstance(byref(clsid), 0, 1, byref(iid),
|
||||
byref(self._httprequest))
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
def set_tunnel(self, host, port=None, headers=None):
|
||||
''' Sets up the host and the port for the HTTP CONNECT Tunnelling. '''
|
||||
self._httprequest.set_tunnel(unicode(host), unicode(str(port)))
|
||||
|
||||
def set_proxy_credentials(self, user, password):
|
||||
self._httprequest.set_proxy_credentials(
|
||||
unicode(user), unicode(password))
|
||||
|
||||
def putrequest(self, method, uri):
|
||||
''' Connects to host and sends the request. '''
|
||||
|
||||
protocol = unicode(self.protocol + '://')
|
||||
url = protocol + self.host + unicode(uri)
|
||||
self._httprequest.open(unicode(method), url)
|
||||
|
||||
# sets certificate for the connection if cert_file is set.
|
||||
if self.cert_file is not None:
|
||||
self._httprequest.set_client_certificate(unicode(self.cert_file))
|
||||
|
||||
def putheader(self, name, value):
|
||||
''' Sends the headers of request. '''
|
||||
if sys.version_info < (3,):
|
||||
name = str(name).decode('utf-8')
|
||||
value = str(value).decode('utf-8')
|
||||
self._httprequest.set_request_header(name, value)
|
||||
|
||||
def endheaders(self):
|
||||
''' No operation. Exists only to provide the same interface of httplib
|
||||
HTTPConnection.'''
|
||||
pass
|
||||
|
||||
def send(self, request_body):
|
||||
''' Sends request body. '''
|
||||
if not request_body:
|
||||
self._httprequest.send()
|
||||
else:
|
||||
self._httprequest.send(request_body)
|
||||
|
||||
def getresponse(self):
|
||||
''' Gets the response and generates the _Response object'''
|
||||
status = self._httprequest.status()
|
||||
status_text = self._httprequest.status_text()
|
||||
|
||||
resp_headers = self._httprequest.get_all_response_headers()
|
||||
fixed_headers = []
|
||||
for resp_header in resp_headers.split('\n'):
|
||||
if (resp_header.startswith('\t') or\
|
||||
resp_header.startswith(' ')) and fixed_headers:
|
||||
# append to previous header
|
||||
fixed_headers[-1] += resp_header
|
||||
else:
|
||||
fixed_headers.append(resp_header)
|
||||
|
||||
headers = []
|
||||
for resp_header in fixed_headers:
|
||||
if ':' in resp_header:
|
||||
pos = resp_header.find(':')
|
||||
headers.append(
|
||||
(resp_header[:pos].lower(), resp_header[pos + 1:].strip()))
|
||||
|
||||
body = self._httprequest.response_body()
|
||||
length = len(body)
|
||||
|
||||
return _Response(status, status_text, length, headers, body)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,70 @@
|
||||
#-------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft. 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.
|
||||
# You may obtain a copy of the License at
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License 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.
|
||||
#--------------------------------------------------------------------------
|
||||
from azure import (
|
||||
MANAGEMENT_HOST,
|
||||
_str
|
||||
)
|
||||
from azure.servicemanagement import (
|
||||
CloudServices,
|
||||
)
|
||||
from azure.servicemanagement.servicemanagementclient import (
|
||||
_ServiceManagementClient,
|
||||
)
|
||||
|
||||
class SchedulerManagementService(_ServiceManagementClient):
|
||||
''' Note that this class is a preliminary work on Scheduler
|
||||
management. Since it lack a lot a features, final version
|
||||
can be slightly different from the current one.
|
||||
'''
|
||||
|
||||
def __init__(self, subscription_id=None, cert_file=None,
|
||||
host=MANAGEMENT_HOST, request_session=None):
|
||||
'''
|
||||
Initializes the scheduler management service.
|
||||
|
||||
subscription_id: Subscription to manage.
|
||||
cert_file:
|
||||
Path to .pem certificate file (httplib), or location of the
|
||||
certificate in your Personal certificate store (winhttp) in the
|
||||
CURRENT_USER\my\CertificateName format.
|
||||
If a request_session is specified, then this is unused.
|
||||
host: Live ServiceClient URL. Defaults to Azure public cloud.
|
||||
request_session:
|
||||
Session object to use for http requests. If this is specified, it
|
||||
replaces the default use of httplib or winhttp. Also, the cert_file
|
||||
parameter is unused when a session is passed in.
|
||||
The session object handles authentication, and as such can support
|
||||
multiple types of authentication: .pem certificate, oauth.
|
||||
For example, you can pass in a Session instance from the requests
|
||||
library. To use .pem certificate authentication with requests
|
||||
library, set the path to the .pem file on the session.cert
|
||||
attribute.
|
||||
'''
|
||||
super(SchedulerManagementService, self).__init__(
|
||||
subscription_id, cert_file, host, request_session)
|
||||
|
||||
#--Operations for scheduler ----------------------------------------
|
||||
def list_cloud_services(self):
|
||||
'''
|
||||
List the cloud services for scheduling defined on the account.
|
||||
'''
|
||||
return self._perform_get(self._get_list_cloud_services_path(),
|
||||
CloudServices)
|
||||
|
||||
|
||||
#--Helper functions --------------------------------------------------
|
||||
def _get_list_cloud_services_path(self):
|
||||
return self._get_path('cloudservices', None)
|
||||
|
||||
@ -1,113 +1,534 @@
|
||||
#-------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft. 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.
|
||||
# You may obtain a copy of the License at
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License 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.
|
||||
#--------------------------------------------------------------------------
|
||||
from azure import (
|
||||
MANAGEMENT_HOST,
|
||||
_convert_response_to_feeds,
|
||||
_str,
|
||||
_validate_not_none,
|
||||
)
|
||||
from azure.servicemanagement import (
|
||||
_ServiceBusManagementXmlSerializer,
|
||||
)
|
||||
from azure.servicemanagement.servicemanagementclient import (
|
||||
_ServiceManagementClient,
|
||||
)
|
||||
|
||||
|
||||
class ServiceBusManagementService(_ServiceManagementClient):
|
||||
|
||||
def __init__(self, subscription_id=None, cert_file=None,
|
||||
host=MANAGEMENT_HOST):
|
||||
super(ServiceBusManagementService, self).__init__(
|
||||
subscription_id, cert_file, host)
|
||||
|
||||
#--Operations for service bus ----------------------------------------
|
||||
def get_regions(self):
|
||||
'''
|
||||
Get list of available service bus regions.
|
||||
'''
|
||||
response = self._perform_get(
|
||||
self._get_path('services/serviceBus/Regions/', None),
|
||||
None)
|
||||
|
||||
return _convert_response_to_feeds(
|
||||
response,
|
||||
_ServiceBusManagementXmlSerializer.xml_to_region)
|
||||
|
||||
def list_namespaces(self):
|
||||
'''
|
||||
List the service bus namespaces defined on the account.
|
||||
'''
|
||||
response = self._perform_get(
|
||||
self._get_path('services/serviceBus/Namespaces/', None),
|
||||
None)
|
||||
|
||||
return _convert_response_to_feeds(
|
||||
response,
|
||||
_ServiceBusManagementXmlSerializer.xml_to_namespace)
|
||||
|
||||
def get_namespace(self, name):
|
||||
'''
|
||||
Get details about a specific namespace.
|
||||
|
||||
name: Name of the service bus namespace.
|
||||
'''
|
||||
response = self._perform_get(
|
||||
self._get_path('services/serviceBus/Namespaces', name),
|
||||
None)
|
||||
|
||||
return _ServiceBusManagementXmlSerializer.xml_to_namespace(
|
||||
response.body)
|
||||
|
||||
def create_namespace(self, name, region):
|
||||
'''
|
||||
Create a new service bus namespace.
|
||||
|
||||
name: Name of the service bus namespace to create.
|
||||
region: Region to create the namespace in.
|
||||
'''
|
||||
_validate_not_none('name', name)
|
||||
|
||||
return self._perform_put(
|
||||
self._get_path('services/serviceBus/Namespaces', name),
|
||||
_ServiceBusManagementXmlSerializer.namespace_to_xml(region))
|
||||
|
||||
def delete_namespace(self, name):
|
||||
'''
|
||||
Delete a service bus namespace.
|
||||
|
||||
name: Name of the service bus namespace to delete.
|
||||
'''
|
||||
_validate_not_none('name', name)
|
||||
|
||||
return self._perform_delete(
|
||||
self._get_path('services/serviceBus/Namespaces', name),
|
||||
None)
|
||||
|
||||
def check_namespace_availability(self, name):
|
||||
'''
|
||||
Checks to see if the specified service bus namespace is available, or
|
||||
if it has already been taken.
|
||||
|
||||
name: Name of the service bus namespace to validate.
|
||||
'''
|
||||
_validate_not_none('name', name)
|
||||
|
||||
response = self._perform_get(
|
||||
self._get_path('services/serviceBus/CheckNamespaceAvailability',
|
||||
None) + '/?namespace=' + _str(name), None)
|
||||
|
||||
return _ServiceBusManagementXmlSerializer.xml_to_namespace_availability(
|
||||
response.body)
|
||||
#-------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft. 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.
|
||||
# You may obtain a copy of the License at
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License 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.
|
||||
#--------------------------------------------------------------------------
|
||||
from azure import (
|
||||
MANAGEMENT_HOST,
|
||||
_convert_response_to_feeds,
|
||||
_str,
|
||||
_validate_not_none,
|
||||
_convert_xml_to_windows_azure_object,
|
||||
)
|
||||
from azure.servicemanagement import (
|
||||
_ServiceBusManagementXmlSerializer,
|
||||
QueueDescription,
|
||||
TopicDescription,
|
||||
NotificationHubDescription,
|
||||
RelayDescription,
|
||||
MetricProperties,
|
||||
MetricValues,
|
||||
MetricRollups,
|
||||
)
|
||||
from azure.servicemanagement.servicemanagementclient import (
|
||||
_ServiceManagementClient,
|
||||
)
|
||||
|
||||
from functools import partial
|
||||
|
||||
X_MS_VERSION = '2012-03-01'
|
||||
|
||||
class ServiceBusManagementService(_ServiceManagementClient):
|
||||
|
||||
def __init__(self, subscription_id=None, cert_file=None,
|
||||
host=MANAGEMENT_HOST, request_session=None):
|
||||
'''
|
||||
Initializes the service bus management service.
|
||||
|
||||
subscription_id: Subscription to manage.
|
||||
cert_file:
|
||||
Path to .pem certificate file (httplib), or location of the
|
||||
certificate in your Personal certificate store (winhttp) in the
|
||||
CURRENT_USER\my\CertificateName format.
|
||||
If a request_session is specified, then this is unused.
|
||||
host: Live ServiceClient URL. Defaults to Azure public cloud.
|
||||
request_session:
|
||||
Session object to use for http requests. If this is specified, it
|
||||
replaces the default use of httplib or winhttp. Also, the cert_file
|
||||
parameter is unused when a session is passed in.
|
||||
The session object handles authentication, and as such can support
|
||||
multiple types of authentication: .pem certificate, oauth.
|
||||
For example, you can pass in a Session instance from the requests
|
||||
library. To use .pem certificate authentication with requests
|
||||
library, set the path to the .pem file on the session.cert
|
||||
attribute.
|
||||
'''
|
||||
super(ServiceBusManagementService, self).__init__(
|
||||
subscription_id, cert_file, host, request_session)
|
||||
self.x_ms_version = X_MS_VERSION
|
||||
|
||||
# Operations for service bus ----------------------------------------
|
||||
def get_regions(self):
|
||||
'''
|
||||
Get list of available service bus regions.
|
||||
'''
|
||||
response = self._perform_get(
|
||||
self._get_path('services/serviceBus/Regions/', None),
|
||||
None)
|
||||
|
||||
return _convert_response_to_feeds(
|
||||
response,
|
||||
_ServiceBusManagementXmlSerializer.xml_to_region)
|
||||
|
||||
def list_namespaces(self):
|
||||
'''
|
||||
List the service bus namespaces defined on the account.
|
||||
'''
|
||||
response = self._perform_get(
|
||||
self._get_path('services/serviceBus/Namespaces/', None),
|
||||
None)
|
||||
|
||||
return _convert_response_to_feeds(
|
||||
response,
|
||||
_ServiceBusManagementXmlSerializer.xml_to_namespace)
|
||||
|
||||
def get_namespace(self, name):
|
||||
'''
|
||||
Get details about a specific namespace.
|
||||
|
||||
name: Name of the service bus namespace.
|
||||
'''
|
||||
response = self._perform_get(
|
||||
self._get_path('services/serviceBus/Namespaces', name),
|
||||
None)
|
||||
|
||||
return _ServiceBusManagementXmlSerializer.xml_to_namespace(
|
||||
response.body)
|
||||
|
||||
def create_namespace(self, name, region):
|
||||
'''
|
||||
Create a new service bus namespace.
|
||||
|
||||
name: Name of the service bus namespace to create.
|
||||
region: Region to create the namespace in.
|
||||
'''
|
||||
_validate_not_none('name', name)
|
||||
|
||||
return self._perform_put(
|
||||
self._get_path('services/serviceBus/Namespaces', name),
|
||||
_ServiceBusManagementXmlSerializer.namespace_to_xml(region))
|
||||
|
||||
def delete_namespace(self, name):
|
||||
'''
|
||||
Delete a service bus namespace.
|
||||
|
||||
name: Name of the service bus namespace to delete.
|
||||
'''
|
||||
_validate_not_none('name', name)
|
||||
|
||||
return self._perform_delete(
|
||||
self._get_path('services/serviceBus/Namespaces', name),
|
||||
None)
|
||||
|
||||
def check_namespace_availability(self, name):
|
||||
'''
|
||||
Checks to see if the specified service bus namespace is available, or
|
||||
if it has already been taken.
|
||||
|
||||
name: Name of the service bus namespace to validate.
|
||||
'''
|
||||
_validate_not_none('name', name)
|
||||
|
||||
response = self._perform_get(
|
||||
self._get_path('services/serviceBus/CheckNamespaceAvailability',
|
||||
None) + '/?namespace=' + _str(name), None)
|
||||
|
||||
return _ServiceBusManagementXmlSerializer.xml_to_namespace_availability(
|
||||
response.body)
|
||||
|
||||
def list_queues(self, name):
|
||||
'''
|
||||
Enumerates the queues in the service namespace.
|
||||
|
||||
name: Name of the service bus namespace.
|
||||
'''
|
||||
_validate_not_none('name', name)
|
||||
|
||||
response = self._perform_get(
|
||||
self._get_list_queues_path(name),
|
||||
None)
|
||||
|
||||
return _convert_response_to_feeds(response,
|
||||
partial(_convert_xml_to_windows_azure_object,
|
||||
azure_type=QueueDescription))
|
||||
|
||||
def list_topics(self, name):
|
||||
'''
|
||||
Retrieves the topics in the service namespace.
|
||||
|
||||
name: Name of the service bus namespace.
|
||||
'''
|
||||
response = self._perform_get(
|
||||
self._get_list_topics_path(name),
|
||||
None)
|
||||
|
||||
return _convert_response_to_feeds(response,
|
||||
partial(_convert_xml_to_windows_azure_object,
|
||||
azure_type=TopicDescription))
|
||||
|
||||
def list_notification_hubs(self, name):
|
||||
'''
|
||||
Retrieves the notification hubs in the service namespace.
|
||||
|
||||
name: Name of the service bus namespace.
|
||||
'''
|
||||
response = self._perform_get(
|
||||
self._get_list_notification_hubs_path(name),
|
||||
None)
|
||||
|
||||
return _convert_response_to_feeds(response,
|
||||
partial(_convert_xml_to_windows_azure_object,
|
||||
azure_type=NotificationHubDescription))
|
||||
|
||||
def list_relays(self, name):
|
||||
'''
|
||||
Retrieves the relays in the service namespace.
|
||||
|
||||
name: Name of the service bus namespace.
|
||||
'''
|
||||
response = self._perform_get(
|
||||
self._get_list_relays_path(name),
|
||||
None)
|
||||
|
||||
return _convert_response_to_feeds(response,
|
||||
partial(_convert_xml_to_windows_azure_object,
|
||||
azure_type=RelayDescription))
|
||||
|
||||
def get_supported_metrics_queue(self, name, queue_name):
|
||||
'''
|
||||
Retrieves the list of supported metrics for this namespace and queue
|
||||
|
||||
name: Name of the service bus namespace.
|
||||
queue_name: Name of the service bus queue in this namespace.
|
||||
'''
|
||||
response = self._perform_get(
|
||||
self._get_get_supported_metrics_queue_path(name, queue_name),
|
||||
None)
|
||||
|
||||
return _convert_response_to_feeds(response,
|
||||
partial(_ServiceBusManagementXmlSerializer.xml_to_metrics,
|
||||
object_type=MetricProperties))
|
||||
|
||||
def get_supported_metrics_topic(self, name, topic_name):
|
||||
'''
|
||||
Retrieves the list of supported metrics for this namespace and topic
|
||||
|
||||
name: Name of the service bus namespace.
|
||||
topic_name: Name of the service bus queue in this namespace.
|
||||
'''
|
||||
response = self._perform_get(
|
||||
self._get_get_supported_metrics_topic_path(name, topic_name),
|
||||
None)
|
||||
|
||||
return _convert_response_to_feeds(response,
|
||||
partial(_ServiceBusManagementXmlSerializer.xml_to_metrics,
|
||||
object_type=MetricProperties))
|
||||
|
||||
def get_supported_metrics_notification_hub(self, name, hub_name):
|
||||
'''
|
||||
Retrieves the list of supported metrics for this namespace and topic
|
||||
|
||||
name: Name of the service bus namespace.
|
||||
hub_name: Name of the service bus notification hub in this namespace.
|
||||
'''
|
||||
response = self._perform_get(
|
||||
self._get_get_supported_metrics_hub_path(name, hub_name),
|
||||
None)
|
||||
|
||||
return _convert_response_to_feeds(response,
|
||||
partial(_ServiceBusManagementXmlSerializer.xml_to_metrics,
|
||||
object_type=MetricProperties))
|
||||
|
||||
def get_supported_metrics_relay(self, name, relay_name):
|
||||
'''
|
||||
Retrieves the list of supported metrics for this namespace and relay
|
||||
|
||||
name: Name of the service bus namespace.
|
||||
relay_name: Name of the service bus relay in this namespace.
|
||||
'''
|
||||
response = self._perform_get(
|
||||
self._get_get_supported_metrics_relay_path(name, relay_name),
|
||||
None)
|
||||
|
||||
return _convert_response_to_feeds(response,
|
||||
partial(_ServiceBusManagementXmlSerializer.xml_to_metrics,
|
||||
object_type=MetricProperties))
|
||||
|
||||
def get_metrics_data_queue(self, name, queue_name, metric, rollup, filter_expresssion):
|
||||
'''
|
||||
Retrieves the list of supported metrics for this namespace and queue
|
||||
|
||||
name: Name of the service bus namespace.
|
||||
queue_name: Name of the service bus queue in this namespace.
|
||||
metric: name of a supported metric
|
||||
rollup: name of a supported rollup
|
||||
filter_expression: filter, for instance "$filter=Timestamp gt datetime'2014-10-01T00:00:00Z'"
|
||||
'''
|
||||
response = self._perform_get(
|
||||
self._get_get_metrics_data_queue_path(name, queue_name, metric, rollup, filter_expresssion),
|
||||
None)
|
||||
|
||||
return _convert_response_to_feeds(response,
|
||||
partial(_ServiceBusManagementXmlSerializer.xml_to_metrics,
|
||||
object_type=MetricValues))
|
||||
|
||||
def get_metrics_data_topic(self, name, topic_name, metric, rollup, filter_expresssion):
|
||||
'''
|
||||
Retrieves the list of supported metrics for this namespace and topic
|
||||
|
||||
name: Name of the service bus namespace.
|
||||
topic_name: Name of the service bus queue in this namespace.
|
||||
metric: name of a supported metric
|
||||
rollup: name of a supported rollup
|
||||
filter_expression: filter, for instance "$filter=Timestamp gt datetime'2014-10-01T00:00:00Z'"
|
||||
'''
|
||||
response = self._perform_get(
|
||||
self._get_get_metrics_data_topic_path(name, topic_name, metric, rollup, filter_expresssion),
|
||||
None)
|
||||
|
||||
return _convert_response_to_feeds(response,
|
||||
partial(_ServiceBusManagementXmlSerializer.xml_to_metrics,
|
||||
object_type=MetricValues))
|
||||
|
||||
def get_metrics_data_notification_hub(self, name, hub_name, metric, rollup, filter_expresssion):
|
||||
'''
|
||||
Retrieves the list of supported metrics for this namespace and topic
|
||||
|
||||
name: Name of the service bus namespace.
|
||||
hub_name: Name of the service bus notification hub in this namespace.
|
||||
metric: name of a supported metric
|
||||
rollup: name of a supported rollup
|
||||
filter_expression: filter, for instance "$filter=Timestamp gt datetime'2014-10-01T00:00:00Z'"
|
||||
'''
|
||||
response = self._perform_get(
|
||||
self._get_get_metrics_data_hub_path(name, hub_name, metric, rollup, filter_expresssion),
|
||||
None)
|
||||
|
||||
return _convert_response_to_feeds(response,
|
||||
partial(_ServiceBusManagementXmlSerializer.xml_to_metrics,
|
||||
object_type=MetricValues))
|
||||
|
||||
def get_metrics_data_relay(self, name, relay_name, metric, rollup, filter_expresssion):
|
||||
'''
|
||||
Retrieves the list of supported metrics for this namespace and relay
|
||||
|
||||
name: Name of the service bus namespace.
|
||||
relay_name: Name of the service bus relay in this namespace.
|
||||
metric: name of a supported metric
|
||||
rollup: name of a supported rollup
|
||||
filter_expression: filter, for instance "$filter=Timestamp gt datetime'2014-10-01T00:00:00Z'"
|
||||
'''
|
||||
response = self._perform_get(
|
||||
self._get_get_metrics_data_relay_path(name, relay_name, metric, rollup, filter_expresssion),
|
||||
None)
|
||||
|
||||
return _convert_response_to_feeds(response,
|
||||
partial(_ServiceBusManagementXmlSerializer.xml_to_metrics,
|
||||
object_type=MetricValues))
|
||||
|
||||
def get_metrics_rollups_queue(self, name, queue_name, metric):
|
||||
'''
|
||||
This operation gets rollup data for Service Bus metrics queue.
|
||||
Rollup data includes the time granularity for the telemetry aggregation as well as
|
||||
the retention settings for each time granularity.
|
||||
|
||||
name: Name of the service bus namespace.
|
||||
queue_name: Name of the service bus queue in this namespace.
|
||||
metric: name of a supported metric
|
||||
'''
|
||||
response = self._perform_get(
|
||||
self._get_get_metrics_rollup_queue_path(name, queue_name, metric),
|
||||
None)
|
||||
|
||||
return _convert_response_to_feeds(response,
|
||||
partial(_ServiceBusManagementXmlSerializer.xml_to_metrics,
|
||||
object_type=MetricRollups))
|
||||
|
||||
def get_metrics_rollups_topic(self, name, topic_name, metric):
|
||||
'''
|
||||
This operation gets rollup data for Service Bus metrics topic.
|
||||
Rollup data includes the time granularity for the telemetry aggregation as well as
|
||||
the retention settings for each time granularity.
|
||||
|
||||
name: Name of the service bus namespace.
|
||||
topic_name: Name of the service bus queue in this namespace.
|
||||
metric: name of a supported metric
|
||||
'''
|
||||
response = self._perform_get(
|
||||
self._get_get_metrics_rollup_topic_path(name, topic_name, metric),
|
||||
None)
|
||||
|
||||
return _convert_response_to_feeds(response,
|
||||
partial(_ServiceBusManagementXmlSerializer.xml_to_metrics,
|
||||
object_type=MetricRollups))
|
||||
|
||||
def get_metrics_rollups_notification_hub(self, name, hub_name, metric):
|
||||
'''
|
||||
This operation gets rollup data for Service Bus metrics notification hub.
|
||||
Rollup data includes the time granularity for the telemetry aggregation as well as
|
||||
the retention settings for each time granularity.
|
||||
|
||||
name: Name of the service bus namespace.
|
||||
hub_name: Name of the service bus notification hub in this namespace.
|
||||
metric: name of a supported metric
|
||||
'''
|
||||
response = self._perform_get(
|
||||
self._get_get_metrics_rollup_hub_path(name, hub_name, metric),
|
||||
None)
|
||||
|
||||
return _convert_response_to_feeds(response,
|
||||
partial(_ServiceBusManagementXmlSerializer.xml_to_metrics,
|
||||
object_type=MetricRollups))
|
||||
|
||||
def get_metrics_rollups_relay(self, name, relay_name, metric):
|
||||
'''
|
||||
This operation gets rollup data for Service Bus metrics relay.
|
||||
Rollup data includes the time granularity for the telemetry aggregation as well as
|
||||
the retention settings for each time granularity.
|
||||
|
||||
name: Name of the service bus namespace.
|
||||
relay_name: Name of the service bus relay in this namespace.
|
||||
metric: name of a supported metric
|
||||
'''
|
||||
response = self._perform_get(
|
||||
self._get_get_metrics_rollup_relay_path(name, relay_name, metric),
|
||||
None)
|
||||
|
||||
return _convert_response_to_feeds(response,
|
||||
partial(_ServiceBusManagementXmlSerializer.xml_to_metrics,
|
||||
object_type=MetricRollups))
|
||||
|
||||
|
||||
# Helper functions --------------------------------------------------
|
||||
def _get_list_queues_path(self, namespace_name):
|
||||
return self._get_path('services/serviceBus/Namespaces/',
|
||||
namespace_name) + '/Queues'
|
||||
|
||||
def _get_list_topics_path(self, namespace_name):
|
||||
return self._get_path('services/serviceBus/Namespaces/',
|
||||
namespace_name) + '/Topics'
|
||||
|
||||
def _get_list_notification_hubs_path(self, namespace_name):
|
||||
return self._get_path('services/serviceBus/Namespaces/',
|
||||
namespace_name) + '/NotificationHubs'
|
||||
|
||||
def _get_list_relays_path(self, namespace_name):
|
||||
return self._get_path('services/serviceBus/Namespaces/',
|
||||
namespace_name) + '/Relays'
|
||||
|
||||
def _get_get_supported_metrics_queue_path(self, namespace_name, queue_name):
|
||||
return self._get_path('services/serviceBus/Namespaces/',
|
||||
namespace_name) + '/Queues/' + _str(queue_name) + '/Metrics'
|
||||
|
||||
def _get_get_supported_metrics_topic_path(self, namespace_name, topic_name):
|
||||
return self._get_path('services/serviceBus/Namespaces/',
|
||||
namespace_name) + '/Topics/' + _str(topic_name) + '/Metrics'
|
||||
|
||||
def _get_get_supported_metrics_hub_path(self, namespace_name, hub_name):
|
||||
return self._get_path('services/serviceBus/Namespaces/',
|
||||
namespace_name) + '/NotificationHubs/' + _str(hub_name) + '/Metrics'
|
||||
|
||||
def _get_get_supported_metrics_relay_path(self, namespace_name, queue_name):
|
||||
return self._get_path('services/serviceBus/Namespaces/',
|
||||
namespace_name) + '/Relays/' + _str(queue_name) + '/Metrics'
|
||||
|
||||
def _get_get_metrics_data_queue_path(self, namespace_name, queue_name, metric, rollup, filter_expr):
|
||||
return "".join([
|
||||
self._get_path('services/serviceBus/Namespaces/', namespace_name),
|
||||
'/Queues/',
|
||||
_str(queue_name),
|
||||
'/Metrics/',
|
||||
_str(metric),
|
||||
'/Rollups/',
|
||||
_str(rollup),
|
||||
'/Values?',
|
||||
filter_expr
|
||||
])
|
||||
|
||||
def _get_get_metrics_data_topic_path(self, namespace_name, queue_name, metric, rollup, filter_expr):
|
||||
return "".join([
|
||||
self._get_path('services/serviceBus/Namespaces/', namespace_name),
|
||||
'/Topics/',
|
||||
_str(queue_name),
|
||||
'/Metrics/',
|
||||
_str(metric),
|
||||
'/Rollups/',
|
||||
_str(rollup),
|
||||
'/Values?',
|
||||
filter_expr
|
||||
])
|
||||
|
||||
def _get_get_metrics_data_hub_path(self, namespace_name, queue_name, metric, rollup, filter_expr):
|
||||
return "".join([
|
||||
self._get_path('services/serviceBus/Namespaces/', namespace_name),
|
||||
'/NotificationHubs/',
|
||||
_str(queue_name),
|
||||
'/Metrics/',
|
||||
_str(metric),
|
||||
'/Rollups/',
|
||||
_str(rollup),
|
||||
'/Values?',
|
||||
filter_expr
|
||||
])
|
||||
|
||||
def _get_get_metrics_data_relay_path(self, namespace_name, queue_name, metric, rollup, filter_expr):
|
||||
return "".join([
|
||||
self._get_path('services/serviceBus/Namespaces/', namespace_name),
|
||||
'/Relays/',
|
||||
_str(queue_name),
|
||||
'/Metrics/',
|
||||
_str(metric),
|
||||
'/Rollups/',
|
||||
_str(rollup),
|
||||
'/Values?',
|
||||
filter_expr
|
||||
])
|
||||
|
||||
def _get_get_metrics_rollup_queue_path(self, namespace_name, queue_name, metric):
|
||||
return "".join([
|
||||
self._get_path('services/serviceBus/Namespaces/', namespace_name),
|
||||
'/Queues/',
|
||||
_str(queue_name),
|
||||
'/Metrics/',
|
||||
_str(metric),
|
||||
'/Rollups',
|
||||
])
|
||||
|
||||
def _get_get_metrics_rollup_topic_path(self, namespace_name, queue_name, metric):
|
||||
return "".join([
|
||||
self._get_path('services/serviceBus/Namespaces/', namespace_name),
|
||||
'/Topics/',
|
||||
_str(queue_name),
|
||||
'/Metrics/',
|
||||
_str(metric),
|
||||
'/Rollups',
|
||||
])
|
||||
|
||||
def _get_get_metrics_rollup_hub_path(self, namespace_name, queue_name, metric):
|
||||
return "".join([
|
||||
self._get_path('services/serviceBus/Namespaces/', namespace_name),
|
||||
'/NotificationHubs/',
|
||||
_str(queue_name),
|
||||
'/Metrics/',
|
||||
_str(metric),
|
||||
'/Rollups',
|
||||
])
|
||||
|
||||
def _get_get_metrics_rollup_relay_path(self, namespace_name, queue_name, metric):
|
||||
return "".join([
|
||||
self._get_path('services/serviceBus/Namespaces/', namespace_name),
|
||||
'/Relays/',
|
||||
_str(queue_name),
|
||||
'/Metrics/',
|
||||
_str(metric),
|
||||
'/Rollups',
|
||||
])
|
||||
|
||||
@ -1,166 +1,258 @@
|
||||
#-------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft. 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.
|
||||
# You may obtain a copy of the License at
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License 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.
|
||||
#--------------------------------------------------------------------------
|
||||
import os
|
||||
|
||||
from azure import (
|
||||
WindowsAzureError,
|
||||
MANAGEMENT_HOST,
|
||||
_get_request_body,
|
||||
_parse_response,
|
||||
_str,
|
||||
_update_request_uri_query,
|
||||
)
|
||||
from azure.http import (
|
||||
HTTPError,
|
||||
HTTPRequest,
|
||||
)
|
||||
from azure.http.httpclient import _HTTPClient
|
||||
from azure.servicemanagement import (
|
||||
AZURE_MANAGEMENT_CERTFILE,
|
||||
AZURE_MANAGEMENT_SUBSCRIPTIONID,
|
||||
_management_error_handler,
|
||||
_parse_response_for_async_op,
|
||||
_update_management_header,
|
||||
)
|
||||
|
||||
|
||||
class _ServiceManagementClient(object):
|
||||
|
||||
def __init__(self, subscription_id=None, cert_file=None,
|
||||
host=MANAGEMENT_HOST):
|
||||
self.requestid = None
|
||||
self.subscription_id = subscription_id
|
||||
self.cert_file = cert_file
|
||||
self.host = host
|
||||
|
||||
if not self.cert_file:
|
||||
if AZURE_MANAGEMENT_CERTFILE in os.environ:
|
||||
self.cert_file = os.environ[AZURE_MANAGEMENT_CERTFILE]
|
||||
|
||||
if not self.subscription_id:
|
||||
if AZURE_MANAGEMENT_SUBSCRIPTIONID in os.environ:
|
||||
self.subscription_id = os.environ[
|
||||
AZURE_MANAGEMENT_SUBSCRIPTIONID]
|
||||
|
||||
if not self.cert_file or not self.subscription_id:
|
||||
raise WindowsAzureError(
|
||||
'You need to provide subscription id and certificate file')
|
||||
|
||||
self._httpclient = _HTTPClient(
|
||||
service_instance=self, cert_file=self.cert_file)
|
||||
self._filter = self._httpclient.perform_request
|
||||
|
||||
def with_filter(self, filter):
|
||||
'''Returns a new service which will process requests with the
|
||||
specified filter. Filtering operations can include logging, automatic
|
||||
retrying, etc... The filter is a lambda which receives the HTTPRequest
|
||||
and another lambda. The filter can perform any pre-processing on the
|
||||
request, pass it off to the next lambda, and then perform any
|
||||
post-processing on the response.'''
|
||||
res = type(self)(self.subscription_id, self.cert_file, self.host)
|
||||
old_filter = self._filter
|
||||
|
||||
def new_filter(request):
|
||||
return filter(request, old_filter)
|
||||
|
||||
res._filter = new_filter
|
||||
return res
|
||||
|
||||
def set_proxy(self, host, port, user=None, password=None):
|
||||
'''
|
||||
Sets the proxy server host and port for the HTTP CONNECT Tunnelling.
|
||||
|
||||
host: Address of the proxy. Ex: '192.168.0.100'
|
||||
port: Port of the proxy. Ex: 6000
|
||||
user: User for proxy authorization.
|
||||
password: Password for proxy authorization.
|
||||
'''
|
||||
self._httpclient.set_proxy(host, port, user, password)
|
||||
|
||||
#--Helper functions --------------------------------------------------
|
||||
def _perform_request(self, request):
|
||||
try:
|
||||
resp = self._filter(request)
|
||||
except HTTPError as ex:
|
||||
return _management_error_handler(ex)
|
||||
|
||||
return resp
|
||||
|
||||
def _perform_get(self, path, response_type):
|
||||
request = HTTPRequest()
|
||||
request.method = 'GET'
|
||||
request.host = self.host
|
||||
request.path = path
|
||||
request.path, request.query = _update_request_uri_query(request)
|
||||
request.headers = _update_management_header(request)
|
||||
response = self._perform_request(request)
|
||||
|
||||
if response_type is not None:
|
||||
return _parse_response(response, response_type)
|
||||
|
||||
return response
|
||||
|
||||
def _perform_put(self, path, body, async=False):
|
||||
request = HTTPRequest()
|
||||
request.method = 'PUT'
|
||||
request.host = self.host
|
||||
request.path = path
|
||||
request.body = _get_request_body(body)
|
||||
request.path, request.query = _update_request_uri_query(request)
|
||||
request.headers = _update_management_header(request)
|
||||
response = self._perform_request(request)
|
||||
|
||||
if async:
|
||||
return _parse_response_for_async_op(response)
|
||||
|
||||
return None
|
||||
|
||||
def _perform_post(self, path, body, response_type=None, async=False):
|
||||
request = HTTPRequest()
|
||||
request.method = 'POST'
|
||||
request.host = self.host
|
||||
request.path = path
|
||||
request.body = _get_request_body(body)
|
||||
request.path, request.query = _update_request_uri_query(request)
|
||||
request.headers = _update_management_header(request)
|
||||
response = self._perform_request(request)
|
||||
|
||||
if response_type is not None:
|
||||
return _parse_response(response, response_type)
|
||||
|
||||
if async:
|
||||
return _parse_response_for_async_op(response)
|
||||
|
||||
return None
|
||||
|
||||
def _perform_delete(self, path, async=False):
|
||||
request = HTTPRequest()
|
||||
request.method = 'DELETE'
|
||||
request.host = self.host
|
||||
request.path = path
|
||||
request.path, request.query = _update_request_uri_query(request)
|
||||
request.headers = _update_management_header(request)
|
||||
response = self._perform_request(request)
|
||||
|
||||
if async:
|
||||
return _parse_response_for_async_op(response)
|
||||
|
||||
return None
|
||||
|
||||
def _get_path(self, resource, name):
|
||||
path = '/' + self.subscription_id + '/' + resource
|
||||
if name is not None:
|
||||
path += '/' + _str(name)
|
||||
return path
|
||||
#-------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft. 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.
|
||||
# You may obtain a copy of the License at
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License 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.
|
||||
#--------------------------------------------------------------------------
|
||||
import os
|
||||
|
||||
from azure import (
|
||||
WindowsAzureError,
|
||||
MANAGEMENT_HOST,
|
||||
_get_request_body,
|
||||
_parse_response,
|
||||
_str,
|
||||
_update_request_uri_query,
|
||||
)
|
||||
from azure.http import (
|
||||
HTTPError,
|
||||
HTTPRequest,
|
||||
)
|
||||
from azure.http.httpclient import _HTTPClient
|
||||
from azure.servicemanagement import (
|
||||
AZURE_MANAGEMENT_CERTFILE,
|
||||
AZURE_MANAGEMENT_SUBSCRIPTIONID,
|
||||
_management_error_handler,
|
||||
parse_response_for_async_op,
|
||||
X_MS_VERSION,
|
||||
)
|
||||
|
||||
|
||||
class _ServiceManagementClient(object):
|
||||
|
||||
def __init__(self, subscription_id=None, cert_file=None,
|
||||
host=MANAGEMENT_HOST, request_session=None):
|
||||
self.requestid = None
|
||||
self.subscription_id = subscription_id
|
||||
self.cert_file = cert_file
|
||||
self.host = host
|
||||
self.request_session = request_session
|
||||
self.x_ms_version = X_MS_VERSION
|
||||
self.content_type = 'application/atom+xml;type=entry;charset=utf-8'
|
||||
|
||||
if not self.cert_file and not request_session:
|
||||
if AZURE_MANAGEMENT_CERTFILE in os.environ:
|
||||
self.cert_file = os.environ[AZURE_MANAGEMENT_CERTFILE]
|
||||
|
||||
if not self.subscription_id:
|
||||
if AZURE_MANAGEMENT_SUBSCRIPTIONID in os.environ:
|
||||
self.subscription_id = os.environ[
|
||||
AZURE_MANAGEMENT_SUBSCRIPTIONID]
|
||||
|
||||
if not self.request_session:
|
||||
if not self.cert_file or not self.subscription_id:
|
||||
raise WindowsAzureError(
|
||||
'You need to provide subscription id and certificate file')
|
||||
|
||||
self._httpclient = _HTTPClient(
|
||||
service_instance=self, cert_file=self.cert_file,
|
||||
request_session=self.request_session)
|
||||
self._filter = self._httpclient.perform_request
|
||||
|
||||
def with_filter(self, filter):
|
||||
'''Returns a new service which will process requests with the
|
||||
specified filter. Filtering operations can include logging, automatic
|
||||
retrying, etc... The filter is a lambda which receives the HTTPRequest
|
||||
and another lambda. The filter can perform any pre-processing on the
|
||||
request, pass it off to the next lambda, and then perform any
|
||||
post-processing on the response.'''
|
||||
res = type(self)(self.subscription_id, self.cert_file, self.host,
|
||||
self.request_session)
|
||||
old_filter = self._filter
|
||||
|
||||
def new_filter(request):
|
||||
return filter(request, old_filter)
|
||||
|
||||
res._filter = new_filter
|
||||
return res
|
||||
|
||||
def set_proxy(self, host, port, user=None, password=None):
|
||||
'''
|
||||
Sets the proxy server host and port for the HTTP CONNECT Tunnelling.
|
||||
|
||||
host: Address of the proxy. Ex: '192.168.0.100'
|
||||
port: Port of the proxy. Ex: 6000
|
||||
user: User for proxy authorization.
|
||||
password: Password for proxy authorization.
|
||||
'''
|
||||
self._httpclient.set_proxy(host, port, user, password)
|
||||
|
||||
def perform_get(self, path, x_ms_version=None):
|
||||
'''
|
||||
Performs a GET request and returns the response.
|
||||
|
||||
path:
|
||||
Path to the resource.
|
||||
Ex: '/<subscription-id>/services/hostedservices/<service-name>'
|
||||
x_ms_version:
|
||||
If specified, this is used for the x-ms-version header.
|
||||
Otherwise, self.x_ms_version is used.
|
||||
'''
|
||||
request = HTTPRequest()
|
||||
request.method = 'GET'
|
||||
request.host = self.host
|
||||
request.path = path
|
||||
request.path, request.query = _update_request_uri_query(request)
|
||||
request.headers = self._update_management_header(request, x_ms_version)
|
||||
response = self._perform_request(request)
|
||||
|
||||
return response
|
||||
|
||||
def perform_put(self, path, body, x_ms_version=None):
|
||||
'''
|
||||
Performs a PUT request and returns the response.
|
||||
|
||||
path:
|
||||
Path to the resource.
|
||||
Ex: '/<subscription-id>/services/hostedservices/<service-name>'
|
||||
body:
|
||||
Body for the PUT request.
|
||||
x_ms_version:
|
||||
If specified, this is used for the x-ms-version header.
|
||||
Otherwise, self.x_ms_version is used.
|
||||
'''
|
||||
request = HTTPRequest()
|
||||
request.method = 'PUT'
|
||||
request.host = self.host
|
||||
request.path = path
|
||||
request.body = _get_request_body(body)
|
||||
request.path, request.query = _update_request_uri_query(request)
|
||||
request.headers = self._update_management_header(request, x_ms_version)
|
||||
response = self._perform_request(request)
|
||||
|
||||
return response
|
||||
|
||||
def perform_post(self, path, body, x_ms_version=None):
|
||||
'''
|
||||
Performs a POST request and returns the response.
|
||||
|
||||
path:
|
||||
Path to the resource.
|
||||
Ex: '/<subscription-id>/services/hostedservices/<service-name>'
|
||||
body:
|
||||
Body for the POST request.
|
||||
x_ms_version:
|
||||
If specified, this is used for the x-ms-version header.
|
||||
Otherwise, self.x_ms_version is used.
|
||||
'''
|
||||
request = HTTPRequest()
|
||||
request.method = 'POST'
|
||||
request.host = self.host
|
||||
request.path = path
|
||||
request.body = _get_request_body(body)
|
||||
request.path, request.query = _update_request_uri_query(request)
|
||||
request.headers = self._update_management_header(request, x_ms_version)
|
||||
response = self._perform_request(request)
|
||||
|
||||
return response
|
||||
|
||||
def perform_delete(self, path, x_ms_version=None):
|
||||
'''
|
||||
Performs a DELETE request and returns the response.
|
||||
|
||||
path:
|
||||
Path to the resource.
|
||||
Ex: '/<subscription-id>/services/hostedservices/<service-name>'
|
||||
x_ms_version:
|
||||
If specified, this is used for the x-ms-version header.
|
||||
Otherwise, self.x_ms_version is used.
|
||||
'''
|
||||
request = HTTPRequest()
|
||||
request.method = 'DELETE'
|
||||
request.host = self.host
|
||||
request.path = path
|
||||
request.path, request.query = _update_request_uri_query(request)
|
||||
request.headers = self._update_management_header(request, x_ms_version)
|
||||
response = self._perform_request(request)
|
||||
|
||||
return response
|
||||
|
||||
#--Helper functions --------------------------------------------------
|
||||
def _perform_request(self, request):
|
||||
try:
|
||||
resp = self._filter(request)
|
||||
except HTTPError as ex:
|
||||
return _management_error_handler(ex)
|
||||
|
||||
return resp
|
||||
|
||||
def _update_management_header(self, request, x_ms_version):
|
||||
''' Add additional headers for management. '''
|
||||
|
||||
if request.method in ['PUT', 'POST', 'MERGE', 'DELETE']:
|
||||
request.headers.append(('Content-Length', str(len(request.body))))
|
||||
|
||||
# append additional headers base on the service
|
||||
request.headers.append(('x-ms-version', x_ms_version or self.x_ms_version))
|
||||
|
||||
# if it is not GET or HEAD request, must set content-type.
|
||||
if not request.method in ['GET', 'HEAD']:
|
||||
for name, _ in request.headers:
|
||||
if 'content-type' == name.lower():
|
||||
break
|
||||
else:
|
||||
request.headers.append(
|
||||
('Content-Type',
|
||||
self.content_type))
|
||||
|
||||
return request.headers
|
||||
|
||||
def _perform_get(self, path, response_type, x_ms_version=None):
|
||||
response = self.perform_get(path, x_ms_version)
|
||||
|
||||
if response_type is not None:
|
||||
return _parse_response(response, response_type)
|
||||
|
||||
return response
|
||||
|
||||
def _perform_put(self, path, body, async=False, x_ms_version=None):
|
||||
response = self.perform_put(path, body, x_ms_version)
|
||||
|
||||
if async:
|
||||
return parse_response_for_async_op(response)
|
||||
|
||||
return None
|
||||
|
||||
def _perform_post(self, path, body, response_type=None, async=False,
|
||||
x_ms_version=None):
|
||||
response = self.perform_post(path, body, x_ms_version)
|
||||
|
||||
if response_type is not None:
|
||||
return _parse_response(response, response_type)
|
||||
|
||||
if async:
|
||||
return parse_response_for_async_op(response)
|
||||
|
||||
return None
|
||||
|
||||
def _perform_delete(self, path, async=False, x_ms_version=None):
|
||||
response = self.perform_delete(path, x_ms_version)
|
||||
|
||||
if async:
|
||||
return parse_response_for_async_op(response)
|
||||
|
||||
return None
|
||||
|
||||
def _get_path(self, resource, name):
|
||||
path = '/' + self.subscription_id + '/' + resource
|
||||
if name is not None:
|
||||
path += '/' + _str(name)
|
||||
return path
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,390 @@
|
||||
#-------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft. 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.
|
||||
# You may obtain a copy of the License at
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License 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.
|
||||
#--------------------------------------------------------------------------
|
||||
from azure import (
|
||||
MANAGEMENT_HOST,
|
||||
_parse_service_resources_response,
|
||||
_validate_not_none,
|
||||
)
|
||||
from azure.servicemanagement import (
|
||||
EventLog,
|
||||
ServerQuota,
|
||||
Servers,
|
||||
ServiceObjective,
|
||||
Database,
|
||||
FirewallRule,
|
||||
_SqlManagementXmlSerializer,
|
||||
)
|
||||
from azure.servicemanagement.servicemanagementclient import (
|
||||
_ServiceManagementClient,
|
||||
)
|
||||
|
||||
class SqlDatabaseManagementService(_ServiceManagementClient):
|
||||
''' Note that this class is a preliminary work on SQL Database
|
||||
management. Since it lack a lot a features, final version
|
||||
can be slightly different from the current one.
|
||||
'''
|
||||
|
||||
def __init__(self, subscription_id=None, cert_file=None,
|
||||
host=MANAGEMENT_HOST, request_session=None):
|
||||
'''
|
||||
Initializes the sql database management service.
|
||||
|
||||
subscription_id: Subscription to manage.
|
||||
cert_file:
|
||||
Path to .pem certificate file (httplib), or location of the
|
||||
certificate in your Personal certificate store (winhttp) in the
|
||||
CURRENT_USER\my\CertificateName format.
|
||||
If a request_session is specified, then this is unused.
|
||||
host: Live ServiceClient URL. Defaults to Azure public cloud.
|
||||
request_session:
|
||||
Session object to use for http requests. If this is specified, it
|
||||
replaces the default use of httplib or winhttp. Also, the cert_file
|
||||
parameter is unused when a session is passed in.
|
||||
The session object handles authentication, and as such can support
|
||||
multiple types of authentication: .pem certificate, oauth.
|
||||
For example, you can pass in a Session instance from the requests
|
||||
library. To use .pem certificate authentication with requests
|
||||
library, set the path to the .pem file on the session.cert
|
||||
attribute.
|
||||
'''
|
||||
super(SqlDatabaseManagementService, self).__init__(
|
||||
subscription_id, cert_file, host, request_session)
|
||||
self.content_type = 'application/xml'
|
||||
|
||||
#--Operations for sql servers ----------------------------------------
|
||||
def create_server(self, admin_login, admin_password, location):
|
||||
'''
|
||||
Create a new Azure SQL Database server.
|
||||
|
||||
admin_login: The administrator login name for the new server.
|
||||
admin_password: The administrator login password for the new server.
|
||||
location: The region to deploy the new server.
|
||||
'''
|
||||
_validate_not_none('admin_login', admin_login)
|
||||
_validate_not_none('admin_password', admin_password)
|
||||
_validate_not_none('location', location)
|
||||
response = self.perform_post(
|
||||
self._get_servers_path(),
|
||||
_SqlManagementXmlSerializer.create_server_to_xml(
|
||||
admin_login,
|
||||
admin_password,
|
||||
location
|
||||
)
|
||||
)
|
||||
|
||||
return _SqlManagementXmlSerializer.xml_to_create_server_response(
|
||||
response.body)
|
||||
|
||||
def set_server_admin_password(self, server_name, admin_password):
|
||||
'''
|
||||
Reset the administrator password for a server.
|
||||
|
||||
server_name: Name of the server to change the password.
|
||||
admin_password: The new administrator password for the server.
|
||||
'''
|
||||
_validate_not_none('server_name', server_name)
|
||||
_validate_not_none('admin_password', admin_password)
|
||||
return self._perform_post(
|
||||
self._get_servers_path(server_name) + '?op=ResetPassword',
|
||||
_SqlManagementXmlSerializer.set_server_admin_password_to_xml(
|
||||
admin_password
|
||||
)
|
||||
)
|
||||
|
||||
def delete_server(self, server_name):
|
||||
'''
|
||||
Deletes an Azure SQL Database server (including all its databases).
|
||||
|
||||
server_name: Name of the server you want to delete.
|
||||
'''
|
||||
_validate_not_none('server_name', server_name)
|
||||
return self._perform_delete(
|
||||
self._get_servers_path(server_name))
|
||||
|
||||
def list_servers(self):
|
||||
'''
|
||||
List the SQL servers defined on the account.
|
||||
'''
|
||||
return self._perform_get(self._get_servers_path(),
|
||||
Servers)
|
||||
|
||||
def list_quotas(self, server_name):
|
||||
'''
|
||||
Gets quotas for an Azure SQL Database Server.
|
||||
|
||||
server_name: Name of the server.
|
||||
'''
|
||||
_validate_not_none('server_name', server_name)
|
||||
response = self._perform_get(self._get_quotas_path(server_name),
|
||||
None)
|
||||
return _parse_service_resources_response(response, ServerQuota)
|
||||
|
||||
def get_server_event_logs(self, server_name, start_date,
|
||||
interval_size_in_minutes, event_types=''):
|
||||
'''
|
||||
Gets the event logs for an Azure SQL Database Server.
|
||||
|
||||
server_name: Name of the server to retrieve the event logs from.
|
||||
start_date:
|
||||
The starting date and time of the events to retrieve in UTC format,
|
||||
for example '2011-09-28 16:05:00'.
|
||||
interval_size_in_minutes:
|
||||
Size of the event logs to retrieve (in minutes).
|
||||
Valid values are: 5, 60, or 1440.
|
||||
event_types:
|
||||
The event type of the log entries you want to retrieve.
|
||||
Valid values are:
|
||||
- connection_successful
|
||||
- connection_failed
|
||||
- connection_terminated
|
||||
- deadlock
|
||||
- throttling
|
||||
- throttling_long_transaction
|
||||
To return all event types pass in an empty string.
|
||||
'''
|
||||
_validate_not_none('server_name', server_name)
|
||||
_validate_not_none('start_date', start_date)
|
||||
_validate_not_none('interval_size_in_minutes', interval_size_in_minutes)
|
||||
_validate_not_none('event_types', event_types)
|
||||
path = self._get_server_event_logs_path(server_name) + \
|
||||
'?startDate={0}&intervalSizeInMinutes={1}&eventTypes={2}'.format(
|
||||
start_date, interval_size_in_minutes, event_types)
|
||||
response = self._perform_get(path, None)
|
||||
return _parse_service_resources_response(response, EventLog)
|
||||
|
||||
#--Operations for firewall rules ------------------------------------------
|
||||
def create_firewall_rule(self, server_name, name, start_ip_address,
|
||||
end_ip_address):
|
||||
'''
|
||||
Creates an Azure SQL Database server firewall rule.
|
||||
|
||||
server_name: Name of the server to set the firewall rule on.
|
||||
name: The name of the new firewall rule.
|
||||
start_ip_address:
|
||||
The lowest IP address in the range of the server-level firewall
|
||||
setting. IP addresses equal to or greater than this can attempt to
|
||||
connect to the server. The lowest possible IP address is 0.0.0.0.
|
||||
end_ip_address:
|
||||
The highest IP address in the range of the server-level firewall
|
||||
setting. IP addresses equal to or less than this can attempt to
|
||||
connect to the server. The highest possible IP address is
|
||||
255.255.255.255.
|
||||
'''
|
||||
_validate_not_none('server_name', server_name)
|
||||
_validate_not_none('name', name)
|
||||
_validate_not_none('start_ip_address', start_ip_address)
|
||||
_validate_not_none('end_ip_address', end_ip_address)
|
||||
return self._perform_post(
|
||||
self._get_firewall_rules_path(server_name),
|
||||
_SqlManagementXmlSerializer.create_firewall_rule_to_xml(
|
||||
name, start_ip_address, end_ip_address
|
||||
)
|
||||
)
|
||||
|
||||
def update_firewall_rule(self, server_name, name, start_ip_address,
|
||||
end_ip_address):
|
||||
'''
|
||||
Update a firewall rule for an Azure SQL Database server.
|
||||
|
||||
server_name: Name of the server to set the firewall rule on.
|
||||
name: The name of the firewall rule to update.
|
||||
start_ip_address:
|
||||
The lowest IP address in the range of the server-level firewall
|
||||
setting. IP addresses equal to or greater than this can attempt to
|
||||
connect to the server. The lowest possible IP address is 0.0.0.0.
|
||||
end_ip_address:
|
||||
The highest IP address in the range of the server-level firewall
|
||||
setting. IP addresses equal to or less than this can attempt to
|
||||
connect to the server. The highest possible IP address is
|
||||
255.255.255.255.
|
||||
'''
|
||||
_validate_not_none('server_name', server_name)
|
||||
_validate_not_none('name', name)
|
||||
_validate_not_none('start_ip_address', start_ip_address)
|
||||
_validate_not_none('end_ip_address', end_ip_address)
|
||||
return self._perform_put(
|
||||
self._get_firewall_rules_path(server_name, name),
|
||||
_SqlManagementXmlSerializer.update_firewall_rule_to_xml(
|
||||
name, start_ip_address, end_ip_address
|
||||
)
|
||||
)
|
||||
|
||||
def delete_firewall_rule(self, server_name, name):
|
||||
'''
|
||||
Deletes an Azure SQL Database server firewall rule.
|
||||
|
||||
server_name:
|
||||
Name of the server with the firewall rule you want to delete.
|
||||
name:
|
||||
Name of the firewall rule you want to delete.
|
||||
'''
|
||||
_validate_not_none('server_name', server_name)
|
||||
_validate_not_none('name', name)
|
||||
return self._perform_delete(
|
||||
self._get_firewall_rules_path(server_name, name))
|
||||
|
||||
def list_firewall_rules(self, server_name):
|
||||
'''
|
||||
Retrieves the set of firewall rules for an Azure SQL Database Server.
|
||||
|
||||
server_name: Name of the server.
|
||||
'''
|
||||
_validate_not_none('server_name', server_name)
|
||||
response = self._perform_get(self._get_firewall_rules_path(server_name),
|
||||
None)
|
||||
return _parse_service_resources_response(response, FirewallRule)
|
||||
|
||||
def list_service_level_objectives(self, server_name):
|
||||
'''
|
||||
Gets the service level objectives for an Azure SQL Database server.
|
||||
|
||||
server_name: Name of the server.
|
||||
'''
|
||||
_validate_not_none('server_name', server_name)
|
||||
response = self._perform_get(
|
||||
self._get_service_objectives_path(server_name), None)
|
||||
return _parse_service_resources_response(response, ServiceObjective)
|
||||
|
||||
#--Operations for sql databases ----------------------------------------
|
||||
def create_database(self, server_name, name, service_objective_id,
|
||||
edition=None, collation_name=None,
|
||||
max_size_bytes=None):
|
||||
'''
|
||||
Creates a new Azure SQL Database.
|
||||
|
||||
server_name: Name of the server to contain the new database.
|
||||
name:
|
||||
Required. The name for the new database. See Naming Requirements
|
||||
in Azure SQL Database General Guidelines and Limitations and
|
||||
Database Identifiers for more information.
|
||||
service_objective_id:
|
||||
Required. The GUID corresponding to the performance level for
|
||||
Edition. See List Service Level Objectives for current values.
|
||||
edition:
|
||||
Optional. The Service Tier (Edition) for the new database. If
|
||||
omitted, the default is Web. Valid values are Web, Business,
|
||||
Basic, Standard, and Premium. See Azure SQL Database Service Tiers
|
||||
(Editions) and Web and Business Edition Sunset FAQ for more
|
||||
information.
|
||||
collation_name:
|
||||
Optional. The database collation. This can be any collation
|
||||
supported by SQL. If omitted, the default collation is used. See
|
||||
SQL Server Collation Support in Azure SQL Database General
|
||||
Guidelines and Limitations for more information.
|
||||
max_size_bytes:
|
||||
Optional. Sets the maximum size, in bytes, for the database. This
|
||||
value must be within the range of allowed values for Edition. If
|
||||
omitted, the default value for the edition is used. See Azure SQL
|
||||
Database Service Tiers (Editions) for current maximum databases
|
||||
sizes. Convert MB or GB values to bytes.
|
||||
1 MB = 1048576 bytes. 1 GB = 1073741824 bytes.
|
||||
'''
|
||||
_validate_not_none('server_name', server_name)
|
||||
_validate_not_none('name', name)
|
||||
_validate_not_none('service_objective_id', service_objective_id)
|
||||
return self._perform_post(
|
||||
self._get_databases_path(server_name),
|
||||
_SqlManagementXmlSerializer.create_database_to_xml(
|
||||
name, service_objective_id, edition, collation_name,
|
||||
max_size_bytes
|
||||
)
|
||||
)
|
||||
|
||||
def update_database(self, server_name, name, new_database_name=None,
|
||||
service_objective_id=None, edition=None,
|
||||
max_size_bytes=None):
|
||||
'''
|
||||
Updates existing database details.
|
||||
|
||||
server_name: Name of the server to contain the new database.
|
||||
name:
|
||||
Required. The name for the new database. See Naming Requirements
|
||||
in Azure SQL Database General Guidelines and Limitations and
|
||||
Database Identifiers for more information.
|
||||
new_database_name:
|
||||
Optional. The new name for the new database.
|
||||
service_objective_id:
|
||||
Optional. The new service level to apply to the database. For more
|
||||
information about service levels, see Azure SQL Database Service
|
||||
Tiers and Performance Levels. Use List Service Level Objectives to
|
||||
get the correct ID for the desired service objective.
|
||||
edition:
|
||||
Optional. The new edition for the new database.
|
||||
max_size_bytes:
|
||||
Optional. The new size of the database in bytes. For information on
|
||||
available sizes for each edition, see Azure SQL Database Service
|
||||
Tiers (Editions).
|
||||
'''
|
||||
_validate_not_none('server_name', server_name)
|
||||
_validate_not_none('name', name)
|
||||
return self._perform_put(
|
||||
self._get_databases_path(server_name, name),
|
||||
_SqlManagementXmlSerializer.update_database_to_xml(
|
||||
new_database_name, service_objective_id, edition,
|
||||
max_size_bytes
|
||||
)
|
||||
)
|
||||
|
||||
def delete_database(self, server_name, name):
|
||||
'''
|
||||
Deletes an Azure SQL Database.
|
||||
|
||||
server_name: Name of the server where the database is located.
|
||||
name: Name of the database to delete.
|
||||
'''
|
||||
return self._perform_delete(self._get_databases_path(server_name, name))
|
||||
|
||||
def list_databases(self, name):
|
||||
'''
|
||||
List the SQL databases defined on the specified server name
|
||||
'''
|
||||
response = self._perform_get(self._get_list_databases_path(name),
|
||||
None)
|
||||
return _parse_service_resources_response(response, Database)
|
||||
|
||||
|
||||
#--Helper functions --------------------------------------------------
|
||||
def _get_servers_path(self, server_name=None):
|
||||
return self._get_path('services/sqlservers/servers', server_name)
|
||||
|
||||
def _get_firewall_rules_path(self, server_name, name=None):
|
||||
path = self._get_servers_path(server_name) + '/firewallrules'
|
||||
if name:
|
||||
path = path + '/' + name
|
||||
return path
|
||||
|
||||
def _get_databases_path(self, server_name, name=None):
|
||||
path = self._get_servers_path(server_name) + '/databases'
|
||||
if name:
|
||||
path = path + '/' + name
|
||||
return path
|
||||
|
||||
def _get_server_event_logs_path(self, server_name):
|
||||
return self._get_servers_path(server_name) + '/events'
|
||||
|
||||
def _get_service_objectives_path(self, server_name):
|
||||
return self._get_servers_path(server_name) + '/serviceobjectives'
|
||||
|
||||
def _get_quotas_path(self, server_name, name=None):
|
||||
path = self._get_servers_path(server_name) + '/serverquotas'
|
||||
if name:
|
||||
path = path + '/' + name
|
||||
return path
|
||||
|
||||
def _get_list_databases_path(self, name):
|
||||
# *contentview=generic is mandatory*
|
||||
return self._get_path('services/sqlservers/servers/',
|
||||
name) + '/databases?contentview=generic'
|
||||
|
||||
@ -0,0 +1,256 @@
|
||||
#-------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft. 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.
|
||||
# You may obtain a copy of the License at
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License 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.
|
||||
#--------------------------------------------------------------------------
|
||||
from azure import (
|
||||
MANAGEMENT_HOST,
|
||||
_str,
|
||||
)
|
||||
from azure.servicemanagement import (
|
||||
WebSpaces,
|
||||
WebSpace,
|
||||
Sites,
|
||||
Site,
|
||||
MetricResponses,
|
||||
MetricDefinitions,
|
||||
PublishData,
|
||||
_XmlSerializer,
|
||||
)
|
||||
from azure.servicemanagement.servicemanagementclient import (
|
||||
_ServiceManagementClient,
|
||||
)
|
||||
|
||||
class WebsiteManagementService(_ServiceManagementClient):
|
||||
''' Note that this class is a preliminary work on WebSite
|
||||
management. Since it lack a lot a features, final version
|
||||
can be slightly different from the current one.
|
||||
'''
|
||||
|
||||
def __init__(self, subscription_id=None, cert_file=None,
|
||||
host=MANAGEMENT_HOST, request_session=None):
|
||||
'''
|
||||
Initializes the website management service.
|
||||
|
||||
subscription_id: Subscription to manage.
|
||||
cert_file:
|
||||
Path to .pem certificate file (httplib), or location of the
|
||||
certificate in your Personal certificate store (winhttp) in the
|
||||
CURRENT_USER\my\CertificateName format.
|
||||
If a request_session is specified, then this is unused.
|
||||
host: Live ServiceClient URL. Defaults to Azure public cloud.
|
||||
request_session:
|
||||
Session object to use for http requests. If this is specified, it
|
||||
replaces the default use of httplib or winhttp. Also, the cert_file
|
||||
parameter is unused when a session is passed in.
|
||||
The session object handles authentication, and as such can support
|
||||
multiple types of authentication: .pem certificate, oauth.
|
||||
For example, you can pass in a Session instance from the requests
|
||||
library. To use .pem certificate authentication with requests
|
||||
library, set the path to the .pem file on the session.cert
|
||||
attribute.
|
||||
'''
|
||||
super(WebsiteManagementService, self).__init__(
|
||||
subscription_id, cert_file, host, request_session)
|
||||
|
||||
#--Operations for web sites ----------------------------------------
|
||||
def list_webspaces(self):
|
||||
'''
|
||||
List the webspaces defined on the account.
|
||||
'''
|
||||
return self._perform_get(self._get_list_webspaces_path(),
|
||||
WebSpaces)
|
||||
|
||||
def get_webspace(self, webspace_name):
|
||||
'''
|
||||
Get details of a specific webspace.
|
||||
|
||||
webspace_name: The name of the webspace.
|
||||
'''
|
||||
return self._perform_get(self._get_webspace_details_path(webspace_name),
|
||||
WebSpace)
|
||||
|
||||
def list_sites(self, webspace_name):
|
||||
'''
|
||||
List the web sites defined on this webspace.
|
||||
|
||||
webspace_name: The name of the webspace.
|
||||
'''
|
||||
return self._perform_get(self._get_sites_path(webspace_name),
|
||||
Sites)
|
||||
|
||||
def get_site(self, webspace_name, website_name):
|
||||
'''
|
||||
List the web sites defined on this webspace.
|
||||
|
||||
webspace_name: The name of the webspace.
|
||||
website_name: The name of the website.
|
||||
'''
|
||||
return self._perform_get(self._get_sites_details_path(webspace_name,
|
||||
website_name),
|
||||
Site)
|
||||
|
||||
def create_site(self, webspace_name, website_name, geo_region, host_names,
|
||||
plan='VirtualDedicatedPlan', compute_mode='Shared',
|
||||
server_farm=None, site_mode=None):
|
||||
'''
|
||||
Create a website.
|
||||
|
||||
webspace_name: The name of the webspace.
|
||||
website_name: The name of the website.
|
||||
geo_region:
|
||||
The geographical region of the webspace that will be created.
|
||||
host_names:
|
||||
An array of fully qualified domain names for website. Only one
|
||||
hostname can be specified in the azurewebsites.net domain.
|
||||
The hostname should match the name of the website. Custom domains
|
||||
can only be specified for Shared or Standard websites.
|
||||
plan:
|
||||
This value must be 'VirtualDedicatedPlan'.
|
||||
compute_mode:
|
||||
This value should be 'Shared' for the Free or Paid Shared
|
||||
offerings, or 'Dedicated' for the Standard offering. The default
|
||||
value is 'Shared'. If you set it to 'Dedicated', you must specify
|
||||
a value for the server_farm parameter.
|
||||
server_farm:
|
||||
The name of the Server Farm associated with this website. This is
|
||||
a required value for Standard mode.
|
||||
site_mode:
|
||||
Can be None, 'Limited' or 'Basic'. This value is 'Limited' for the
|
||||
Free offering, and 'Basic' for the Paid Shared offering. Standard
|
||||
mode does not use the site_mode parameter; it uses the compute_mode
|
||||
parameter.
|
||||
'''
|
||||
xml = _XmlSerializer.create_website_to_xml(webspace_name, website_name, geo_region, plan, host_names, compute_mode, server_farm, site_mode)
|
||||
return self._perform_post(
|
||||
self._get_sites_path(webspace_name),
|
||||
xml,
|
||||
Site)
|
||||
|
||||
def delete_site(self, webspace_name, website_name,
|
||||
delete_empty_server_farm=False, delete_metrics=False):
|
||||
'''
|
||||
Delete a website.
|
||||
|
||||
webspace_name: The name of the webspace.
|
||||
website_name: The name of the website.
|
||||
delete_empty_server_farm:
|
||||
If the site being deleted is the last web site in a server farm,
|
||||
you can delete the server farm by setting this to True.
|
||||
delete_metrics:
|
||||
To also delete the metrics for the site that you are deleting, you
|
||||
can set this to True.
|
||||
'''
|
||||
path = self._get_sites_details_path(webspace_name, website_name)
|
||||
query = ''
|
||||
if delete_empty_server_farm:
|
||||
query += '&deleteEmptyServerFarm=true'
|
||||
if delete_metrics:
|
||||
query += '&deleteMetrics=true'
|
||||
if query:
|
||||
path = path + '?' + query.lstrip('&')
|
||||
return self._perform_delete(path)
|
||||
|
||||
def restart_site(self, webspace_name, website_name):
|
||||
'''
|
||||
Restart a web site.
|
||||
|
||||
webspace_name: The name of the webspace.
|
||||
website_name: The name of the website.
|
||||
'''
|
||||
return self._perform_post(
|
||||
self._get_restart_path(webspace_name, website_name),
|
||||
'')
|
||||
|
||||
def get_historical_usage_metrics(self, webspace_name, website_name,
|
||||
metrics = None, start_time=None, end_time=None, time_grain=None):
|
||||
'''
|
||||
Get historical usage metrics.
|
||||
|
||||
webspace_name: The name of the webspace.
|
||||
website_name: The name of the website.
|
||||
metrics: Optional. List of metrics name. Otherwise, all metrics returned.
|
||||
start_time: Optional. An ISO8601 date. Otherwise, current hour is used.
|
||||
end_time: Optional. An ISO8601 date. Otherwise, current time is used.
|
||||
time_grain: Optional. A rollup name, as P1D. OTherwise, default rollup for the metrics is used.
|
||||
More information and metrics name at:
|
||||
http://msdn.microsoft.com/en-us/library/azure/dn166964.aspx
|
||||
'''
|
||||
metrics = ('names='+','.join(metrics)) if metrics else ''
|
||||
start_time = ('StartTime='+start_time) if start_time else ''
|
||||
end_time = ('EndTime='+end_time) if end_time else ''
|
||||
time_grain = ('TimeGrain='+time_grain) if time_grain else ''
|
||||
parameters = ('&'.join(v for v in (metrics, start_time, end_time, time_grain) if v))
|
||||
parameters = '?'+parameters if parameters else ''
|
||||
return self._perform_get(self._get_historical_usage_metrics_path(webspace_name, website_name) + parameters,
|
||||
MetricResponses)
|
||||
|
||||
def get_metric_definitions(self, webspace_name, website_name):
|
||||
'''
|
||||
Get metric definitions of metrics available of this web site.
|
||||
|
||||
webspace_name: The name of the webspace.
|
||||
website_name: The name of the website.
|
||||
'''
|
||||
return self._perform_get(self._get_metric_definitions_path(webspace_name, website_name),
|
||||
MetricDefinitions)
|
||||
|
||||
def get_publish_profile_xml(self, webspace_name, website_name):
|
||||
'''
|
||||
Get a site's publish profile as a string
|
||||
|
||||
webspace_name: The name of the webspace.
|
||||
website_name: The name of the website.
|
||||
'''
|
||||
return self._perform_get(self._get_publishxml_path(webspace_name, website_name),
|
||||
None).body.decode("utf-8")
|
||||
|
||||
def get_publish_profile(self, webspace_name, website_name):
|
||||
'''
|
||||
Get a site's publish profile as an object
|
||||
|
||||
webspace_name: The name of the webspace.
|
||||
website_name: The name of the website.
|
||||
'''
|
||||
return self._perform_get(self._get_publishxml_path(webspace_name, website_name),
|
||||
PublishData)
|
||||
|
||||
#--Helper functions --------------------------------------------------
|
||||
def _get_list_webspaces_path(self):
|
||||
return self._get_path('services/webspaces', None)
|
||||
|
||||
def _get_webspace_details_path(self, webspace_name):
|
||||
return self._get_path('services/webspaces/', webspace_name)
|
||||
|
||||
def _get_sites_path(self, webspace_name):
|
||||
return self._get_path('services/webspaces/',
|
||||
webspace_name) + '/sites'
|
||||
|
||||
def _get_sites_details_path(self, webspace_name, website_name):
|
||||
return self._get_path('services/webspaces/',
|
||||
webspace_name) + '/sites/' + _str(website_name)
|
||||
|
||||
def _get_restart_path(self, webspace_name, website_name):
|
||||
return self._get_path('services/webspaces/',
|
||||
webspace_name) + '/sites/' + _str(website_name) + '/restart/'
|
||||
|
||||
def _get_historical_usage_metrics_path(self, webspace_name, website_name):
|
||||
return self._get_path('services/webspaces/',
|
||||
webspace_name) + '/sites/' + _str(website_name) + '/metrics/'
|
||||
|
||||
def _get_metric_definitions_path(self, webspace_name, website_name):
|
||||
return self._get_path('services/webspaces/',
|
||||
webspace_name) + '/sites/' + _str(website_name) + '/metricdefinitions/'
|
||||
|
||||
def _get_publishxml_path(self, webspace_name, website_name):
|
||||
return self._get_path('services/webspaces/',
|
||||
webspace_name) + '/sites/' + _str(website_name) + '/publishxml/'
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,39 +1,39 @@
|
||||
#-------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft. 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.
|
||||
# You may obtain a copy of the License at
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License 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.
|
||||
#--------------------------------------------------------------------------
|
||||
from azure.storage.blobservice import BlobService
|
||||
from azure.storage.tableservice import TableService
|
||||
from azure.storage.queueservice import QueueService
|
||||
|
||||
|
||||
class CloudStorageAccount(object):
|
||||
|
||||
"""
|
||||
Provides a factory for creating the blob, queue, and table services
|
||||
with a common account name and account key. Users can either use the
|
||||
factory or can construct the appropriate service directly.
|
||||
"""
|
||||
|
||||
def __init__(self, account_name=None, account_key=None):
|
||||
self.account_name = account_name
|
||||
self.account_key = account_key
|
||||
|
||||
def create_blob_service(self):
|
||||
return BlobService(self.account_name, self.account_key)
|
||||
|
||||
def create_table_service(self):
|
||||
return TableService(self.account_name, self.account_key)
|
||||
|
||||
def create_queue_service(self):
|
||||
return QueueService(self.account_name, self.account_key)
|
||||
#-------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft. 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.
|
||||
# You may obtain a copy of the License at
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License 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.
|
||||
#--------------------------------------------------------------------------
|
||||
from azure.storage.blobservice import BlobService
|
||||
from azure.storage.tableservice import TableService
|
||||
from azure.storage.queueservice import QueueService
|
||||
|
||||
|
||||
class CloudStorageAccount(object):
|
||||
|
||||
"""
|
||||
Provides a factory for creating the blob, queue, and table services
|
||||
with a common account name and account key. Users can either use the
|
||||
factory or can construct the appropriate service directly.
|
||||
"""
|
||||
|
||||
def __init__(self, account_name=None, account_key=None):
|
||||
self.account_name = account_name
|
||||
self.account_key = account_key
|
||||
|
||||
def create_blob_service(self):
|
||||
return BlobService(self.account_name, self.account_key)
|
||||
|
||||
def create_table_service(self):
|
||||
return TableService(self.account_name, self.account_key)
|
||||
|
||||
def create_queue_service(self):
|
||||
return QueueService(self.account_name, self.account_key)
|
||||
|
||||
@ -1,458 +1,458 @@
|
||||
#-------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft. 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.
|
||||
# You may obtain a copy of the License at
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License 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.
|
||||
#--------------------------------------------------------------------------
|
||||
from azure import (
|
||||
WindowsAzureConflictError,
|
||||
WindowsAzureError,
|
||||
DEV_QUEUE_HOST,
|
||||
QUEUE_SERVICE_HOST_BASE,
|
||||
xml_escape,
|
||||
_convert_class_to_xml,
|
||||
_dont_fail_not_exist,
|
||||
_dont_fail_on_exist,
|
||||
_get_request_body,
|
||||
_int_or_none,
|
||||
_parse_enum_results_list,
|
||||
_parse_response,
|
||||
_parse_response_for_dict_filter,
|
||||
_parse_response_for_dict_prefix,
|
||||
_str,
|
||||
_str_or_none,
|
||||
_update_request_uri_query_local_storage,
|
||||
_validate_not_none,
|
||||
_ERROR_CONFLICT,
|
||||
)
|
||||
from azure.http import (
|
||||
HTTPRequest,
|
||||
HTTP_RESPONSE_NO_CONTENT,
|
||||
)
|
||||
from azure.storage import (
|
||||
Queue,
|
||||
QueueEnumResults,
|
||||
QueueMessagesList,
|
||||
StorageServiceProperties,
|
||||
_update_storage_queue_header,
|
||||
)
|
||||
from azure.storage.storageclient import _StorageClient
|
||||
|
||||
|
||||
class QueueService(_StorageClient):
|
||||
|
||||
'''
|
||||
This is the main class managing queue resources.
|
||||
'''
|
||||
|
||||
def __init__(self, account_name=None, account_key=None, protocol='https',
|
||||
host_base=QUEUE_SERVICE_HOST_BASE, dev_host=DEV_QUEUE_HOST):
|
||||
'''
|
||||
account_name: your storage account name, required for all operations.
|
||||
account_key: your storage account key, required for all operations.
|
||||
protocol: Optional. Protocol. Defaults to http.
|
||||
host_base:
|
||||
Optional. Live host base url. Defaults to Azure url. Override this
|
||||
for on-premise.
|
||||
dev_host: Optional. Dev host url. Defaults to localhost.
|
||||
'''
|
||||
super(QueueService, self).__init__(
|
||||
account_name, account_key, protocol, host_base, dev_host)
|
||||
|
||||
def get_queue_service_properties(self, timeout=None):
|
||||
'''
|
||||
Gets the properties of a storage account's Queue Service, including
|
||||
Windows Azure Storage Analytics.
|
||||
|
||||
timeout: Optional. The timeout parameter is expressed in seconds.
|
||||
'''
|
||||
request = HTTPRequest()
|
||||
request.method = 'GET'
|
||||
request.host = self._get_host()
|
||||
request.path = '/?restype=service&comp=properties'
|
||||
request.query = [('timeout', _int_or_none(timeout))]
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_queue_header(
|
||||
request, self.account_name, self.account_key)
|
||||
response = self._perform_request(request)
|
||||
|
||||
return _parse_response(response, StorageServiceProperties)
|
||||
|
||||
def list_queues(self, prefix=None, marker=None, maxresults=None,
|
||||
include=None):
|
||||
'''
|
||||
Lists all of the queues in a given storage account.
|
||||
|
||||
prefix:
|
||||
Filters the results to return only queues with names that begin
|
||||
with the specified prefix.
|
||||
marker:
|
||||
A string value that identifies the portion of the list to be
|
||||
returned with the next list operation. The operation returns a
|
||||
NextMarker element within the response body if the list returned
|
||||
was not complete. This value may then be used as a query parameter
|
||||
in a subsequent call to request the next portion of the list of
|
||||
queues. The marker value is opaque to the client.
|
||||
maxresults:
|
||||
Specifies the maximum number of queues to return. If maxresults is
|
||||
not specified, the server will return up to 5,000 items.
|
||||
include:
|
||||
Optional. Include this parameter to specify that the container's
|
||||
metadata be returned as part of the response body.
|
||||
'''
|
||||
request = HTTPRequest()
|
||||
request.method = 'GET'
|
||||
request.host = self._get_host()
|
||||
request.path = '/?comp=list'
|
||||
request.query = [
|
||||
('prefix', _str_or_none(prefix)),
|
||||
('marker', _str_or_none(marker)),
|
||||
('maxresults', _int_or_none(maxresults)),
|
||||
('include', _str_or_none(include))
|
||||
]
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_queue_header(
|
||||
request, self.account_name, self.account_key)
|
||||
response = self._perform_request(request)
|
||||
|
||||
return _parse_enum_results_list(
|
||||
response, QueueEnumResults, "Queues", Queue)
|
||||
|
||||
def create_queue(self, queue_name, x_ms_meta_name_values=None,
|
||||
fail_on_exist=False):
|
||||
'''
|
||||
Creates a queue under the given account.
|
||||
|
||||
queue_name: name of the queue.
|
||||
x_ms_meta_name_values:
|
||||
Optional. A dict containing name-value pairs to associate with the
|
||||
queue as metadata.
|
||||
fail_on_exist: Specify whether throw exception when queue exists.
|
||||
'''
|
||||
_validate_not_none('queue_name', queue_name)
|
||||
request = HTTPRequest()
|
||||
request.method = 'PUT'
|
||||
request.host = self._get_host()
|
||||
request.path = '/' + _str(queue_name) + ''
|
||||
request.headers = [('x-ms-meta-name-values', x_ms_meta_name_values)]
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_queue_header(
|
||||
request, self.account_name, self.account_key)
|
||||
if not fail_on_exist:
|
||||
try:
|
||||
response = self._perform_request(request)
|
||||
if response.status == HTTP_RESPONSE_NO_CONTENT:
|
||||
return False
|
||||
return True
|
||||
except WindowsAzureError as ex:
|
||||
_dont_fail_on_exist(ex)
|
||||
return False
|
||||
else:
|
||||
response = self._perform_request(request)
|
||||
if response.status == HTTP_RESPONSE_NO_CONTENT:
|
||||
raise WindowsAzureConflictError(
|
||||
_ERROR_CONFLICT.format(response.message))
|
||||
return True
|
||||
|
||||
def delete_queue(self, queue_name, fail_not_exist=False):
|
||||
'''
|
||||
Permanently deletes the specified queue.
|
||||
|
||||
queue_name: Name of the queue.
|
||||
fail_not_exist:
|
||||
Specify whether throw exception when queue doesn't exist.
|
||||
'''
|
||||
_validate_not_none('queue_name', queue_name)
|
||||
request = HTTPRequest()
|
||||
request.method = 'DELETE'
|
||||
request.host = self._get_host()
|
||||
request.path = '/' + _str(queue_name) + ''
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_queue_header(
|
||||
request, self.account_name, self.account_key)
|
||||
if not fail_not_exist:
|
||||
try:
|
||||
self._perform_request(request)
|
||||
return True
|
||||
except WindowsAzureError as ex:
|
||||
_dont_fail_not_exist(ex)
|
||||
return False
|
||||
else:
|
||||
self._perform_request(request)
|
||||
return True
|
||||
|
||||
def get_queue_metadata(self, queue_name):
|
||||
'''
|
||||
Retrieves user-defined metadata and queue properties on the specified
|
||||
queue. Metadata is associated with the queue as name-values pairs.
|
||||
|
||||
queue_name: Name of the queue.
|
||||
'''
|
||||
_validate_not_none('queue_name', queue_name)
|
||||
request = HTTPRequest()
|
||||
request.method = 'GET'
|
||||
request.host = self._get_host()
|
||||
request.path = '/' + _str(queue_name) + '?comp=metadata'
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_queue_header(
|
||||
request, self.account_name, self.account_key)
|
||||
response = self._perform_request(request)
|
||||
|
||||
return _parse_response_for_dict_prefix(
|
||||
response,
|
||||
prefixes=['x-ms-meta', 'x-ms-approximate-messages-count'])
|
||||
|
||||
def set_queue_metadata(self, queue_name, x_ms_meta_name_values=None):
|
||||
'''
|
||||
Sets user-defined metadata on the specified queue. Metadata is
|
||||
associated with the queue as name-value pairs.
|
||||
|
||||
queue_name: Name of the queue.
|
||||
x_ms_meta_name_values:
|
||||
Optional. A dict containing name-value pairs to associate with the
|
||||
queue as metadata.
|
||||
'''
|
||||
_validate_not_none('queue_name', queue_name)
|
||||
request = HTTPRequest()
|
||||
request.method = 'PUT'
|
||||
request.host = self._get_host()
|
||||
request.path = '/' + _str(queue_name) + '?comp=metadata'
|
||||
request.headers = [('x-ms-meta-name-values', x_ms_meta_name_values)]
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_queue_header(
|
||||
request, self.account_name, self.account_key)
|
||||
self._perform_request(request)
|
||||
|
||||
def put_message(self, queue_name, message_text, visibilitytimeout=None,
|
||||
messagettl=None):
|
||||
'''
|
||||
Adds a new message to the back of the message queue. A visibility
|
||||
timeout can also be specified to make the message invisible until the
|
||||
visibility timeout expires. A message must be in a format that can be
|
||||
included in an XML request with UTF-8 encoding. The encoded message can
|
||||
be up to 64KB in size for versions 2011-08-18 and newer, or 8KB in size
|
||||
for previous versions.
|
||||
|
||||
queue_name: Name of the queue.
|
||||
message_text: Message content.
|
||||
visibilitytimeout:
|
||||
Optional. If not specified, the default value is 0. Specifies the
|
||||
new visibility timeout value, in seconds, relative to server time.
|
||||
The new value must be larger than or equal to 0, and cannot be
|
||||
larger than 7 days. The visibility timeout of a message cannot be
|
||||
set to a value later than the expiry time. visibilitytimeout
|
||||
should be set to a value smaller than the time-to-live value.
|
||||
messagettl:
|
||||
Optional. Specifies the time-to-live interval for the message, in
|
||||
seconds. The maximum time-to-live allowed is 7 days. If this
|
||||
parameter is omitted, the default time-to-live is 7 days.
|
||||
'''
|
||||
_validate_not_none('queue_name', queue_name)
|
||||
_validate_not_none('message_text', message_text)
|
||||
request = HTTPRequest()
|
||||
request.method = 'POST'
|
||||
request.host = self._get_host()
|
||||
request.path = '/' + _str(queue_name) + '/messages'
|
||||
request.query = [
|
||||
('visibilitytimeout', _str_or_none(visibilitytimeout)),
|
||||
('messagettl', _str_or_none(messagettl))
|
||||
]
|
||||
request.body = _get_request_body(
|
||||
'<?xml version="1.0" encoding="utf-8"?> \
|
||||
<QueueMessage> \
|
||||
<MessageText>' + xml_escape(_str(message_text)) + '</MessageText> \
|
||||
</QueueMessage>')
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_queue_header(
|
||||
request, self.account_name, self.account_key)
|
||||
self._perform_request(request)
|
||||
|
||||
def get_messages(self, queue_name, numofmessages=None,
|
||||
visibilitytimeout=None):
|
||||
'''
|
||||
Retrieves one or more messages from the front of the queue.
|
||||
|
||||
queue_name: Name of the queue.
|
||||
numofmessages:
|
||||
Optional. A nonzero integer value that specifies the number of
|
||||
messages to retrieve from the queue, up to a maximum of 32. If
|
||||
fewer are visible, the visible messages are returned. By default,
|
||||
a single message is retrieved from the queue with this operation.
|
||||
visibilitytimeout:
|
||||
Specifies the new visibility timeout value, in seconds, relative
|
||||
to server time. The new value must be larger than or equal to 1
|
||||
second, and cannot be larger than 7 days, or larger than 2 hours
|
||||
on REST protocol versions prior to version 2011-08-18. The
|
||||
visibility timeout of a message can be set to a value later than
|
||||
the expiry time.
|
||||
'''
|
||||
_validate_not_none('queue_name', queue_name)
|
||||
request = HTTPRequest()
|
||||
request.method = 'GET'
|
||||
request.host = self._get_host()
|
||||
request.path = '/' + _str(queue_name) + '/messages'
|
||||
request.query = [
|
||||
('numofmessages', _str_or_none(numofmessages)),
|
||||
('visibilitytimeout', _str_or_none(visibilitytimeout))
|
||||
]
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_queue_header(
|
||||
request, self.account_name, self.account_key)
|
||||
response = self._perform_request(request)
|
||||
|
||||
return _parse_response(response, QueueMessagesList)
|
||||
|
||||
def peek_messages(self, queue_name, numofmessages=None):
|
||||
'''
|
||||
Retrieves one or more messages from the front of the queue, but does
|
||||
not alter the visibility of the message.
|
||||
|
||||
queue_name: Name of the queue.
|
||||
numofmessages:
|
||||
Optional. A nonzero integer value that specifies the number of
|
||||
messages to peek from the queue, up to a maximum of 32. By default,
|
||||
a single message is peeked from the queue with this operation.
|
||||
'''
|
||||
_validate_not_none('queue_name', queue_name)
|
||||
request = HTTPRequest()
|
||||
request.method = 'GET'
|
||||
request.host = self._get_host()
|
||||
request.path = '/' + _str(queue_name) + '/messages?peekonly=true'
|
||||
request.query = [('numofmessages', _str_or_none(numofmessages))]
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_queue_header(
|
||||
request, self.account_name, self.account_key)
|
||||
response = self._perform_request(request)
|
||||
|
||||
return _parse_response(response, QueueMessagesList)
|
||||
|
||||
def delete_message(self, queue_name, message_id, popreceipt):
|
||||
'''
|
||||
Deletes the specified message.
|
||||
|
||||
queue_name: Name of the queue.
|
||||
message_id: Message to delete.
|
||||
popreceipt:
|
||||
Required. A valid pop receipt value returned from an earlier call
|
||||
to the Get Messages or Update Message operation.
|
||||
'''
|
||||
_validate_not_none('queue_name', queue_name)
|
||||
_validate_not_none('message_id', message_id)
|
||||
_validate_not_none('popreceipt', popreceipt)
|
||||
request = HTTPRequest()
|
||||
request.method = 'DELETE'
|
||||
request.host = self._get_host()
|
||||
request.path = '/' + \
|
||||
_str(queue_name) + '/messages/' + _str(message_id) + ''
|
||||
request.query = [('popreceipt', _str_or_none(popreceipt))]
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_queue_header(
|
||||
request, self.account_name, self.account_key)
|
||||
self._perform_request(request)
|
||||
|
||||
def clear_messages(self, queue_name):
|
||||
'''
|
||||
Deletes all messages from the specified queue.
|
||||
|
||||
queue_name: Name of the queue.
|
||||
'''
|
||||
_validate_not_none('queue_name', queue_name)
|
||||
request = HTTPRequest()
|
||||
request.method = 'DELETE'
|
||||
request.host = self._get_host()
|
||||
request.path = '/' + _str(queue_name) + '/messages'
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_queue_header(
|
||||
request, self.account_name, self.account_key)
|
||||
self._perform_request(request)
|
||||
|
||||
def update_message(self, queue_name, message_id, message_text, popreceipt,
|
||||
visibilitytimeout):
|
||||
'''
|
||||
Updates the visibility timeout of a message. You can also use this
|
||||
operation to update the contents of a message.
|
||||
|
||||
queue_name: Name of the queue.
|
||||
message_id: Message to update.
|
||||
message_text: Content of message.
|
||||
popreceipt:
|
||||
Required. A valid pop receipt value returned from an earlier call
|
||||
to the Get Messages or Update Message operation.
|
||||
visibilitytimeout:
|
||||
Required. Specifies the new visibility timeout value, in seconds,
|
||||
relative to server time. The new value must be larger than or equal
|
||||
to 0, and cannot be larger than 7 days. The visibility timeout of a
|
||||
message cannot be set to a value later than the expiry time. A
|
||||
message can be updated until it has been deleted or has expired.
|
||||
'''
|
||||
_validate_not_none('queue_name', queue_name)
|
||||
_validate_not_none('message_id', message_id)
|
||||
_validate_not_none('message_text', message_text)
|
||||
_validate_not_none('popreceipt', popreceipt)
|
||||
_validate_not_none('visibilitytimeout', visibilitytimeout)
|
||||
request = HTTPRequest()
|
||||
request.method = 'PUT'
|
||||
request.host = self._get_host()
|
||||
request.path = '/' + \
|
||||
_str(queue_name) + '/messages/' + _str(message_id) + ''
|
||||
request.query = [
|
||||
('popreceipt', _str_or_none(popreceipt)),
|
||||
('visibilitytimeout', _str_or_none(visibilitytimeout))
|
||||
]
|
||||
request.body = _get_request_body(
|
||||
'<?xml version="1.0" encoding="utf-8"?> \
|
||||
<QueueMessage> \
|
||||
<MessageText>' + xml_escape(_str(message_text)) + '</MessageText> \
|
||||
</QueueMessage>')
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_queue_header(
|
||||
request, self.account_name, self.account_key)
|
||||
response = self._perform_request(request)
|
||||
|
||||
return _parse_response_for_dict_filter(
|
||||
response,
|
||||
filter=['x-ms-popreceipt', 'x-ms-time-next-visible'])
|
||||
|
||||
def set_queue_service_properties(self, storage_service_properties,
|
||||
timeout=None):
|
||||
'''
|
||||
Sets the properties of a storage account's Queue service, including
|
||||
Windows Azure Storage Analytics.
|
||||
|
||||
storage_service_properties: StorageServiceProperties object.
|
||||
timeout: Optional. The timeout parameter is expressed in seconds.
|
||||
'''
|
||||
_validate_not_none('storage_service_properties',
|
||||
storage_service_properties)
|
||||
request = HTTPRequest()
|
||||
request.method = 'PUT'
|
||||
request.host = self._get_host()
|
||||
request.path = '/?restype=service&comp=properties'
|
||||
request.query = [('timeout', _int_or_none(timeout))]
|
||||
request.body = _get_request_body(
|
||||
_convert_class_to_xml(storage_service_properties))
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_queue_header(
|
||||
request, self.account_name, self.account_key)
|
||||
self._perform_request(request)
|
||||
#-------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft. 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.
|
||||
# You may obtain a copy of the License at
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License 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.
|
||||
#--------------------------------------------------------------------------
|
||||
from azure import (
|
||||
WindowsAzureConflictError,
|
||||
WindowsAzureError,
|
||||
DEV_QUEUE_HOST,
|
||||
QUEUE_SERVICE_HOST_BASE,
|
||||
xml_escape,
|
||||
_convert_class_to_xml,
|
||||
_dont_fail_not_exist,
|
||||
_dont_fail_on_exist,
|
||||
_get_request_body,
|
||||
_int_or_none,
|
||||
_parse_enum_results_list,
|
||||
_parse_response,
|
||||
_parse_response_for_dict_filter,
|
||||
_parse_response_for_dict_prefix,
|
||||
_str,
|
||||
_str_or_none,
|
||||
_update_request_uri_query_local_storage,
|
||||
_validate_not_none,
|
||||
_ERROR_CONFLICT,
|
||||
)
|
||||
from azure.http import (
|
||||
HTTPRequest,
|
||||
HTTP_RESPONSE_NO_CONTENT,
|
||||
)
|
||||
from azure.storage import (
|
||||
Queue,
|
||||
QueueEnumResults,
|
||||
QueueMessagesList,
|
||||
StorageServiceProperties,
|
||||
_update_storage_queue_header,
|
||||
)
|
||||
from azure.storage.storageclient import _StorageClient
|
||||
|
||||
|
||||
class QueueService(_StorageClient):
|
||||
|
||||
'''
|
||||
This is the main class managing queue resources.
|
||||
'''
|
||||
|
||||
def __init__(self, account_name=None, account_key=None, protocol='https',
|
||||
host_base=QUEUE_SERVICE_HOST_BASE, dev_host=DEV_QUEUE_HOST):
|
||||
'''
|
||||
account_name: your storage account name, required for all operations.
|
||||
account_key: your storage account key, required for all operations.
|
||||
protocol: Optional. Protocol. Defaults to http.
|
||||
host_base:
|
||||
Optional. Live host base url. Defaults to Azure url. Override this
|
||||
for on-premise.
|
||||
dev_host: Optional. Dev host url. Defaults to localhost.
|
||||
'''
|
||||
super(QueueService, self).__init__(
|
||||
account_name, account_key, protocol, host_base, dev_host)
|
||||
|
||||
def get_queue_service_properties(self, timeout=None):
|
||||
'''
|
||||
Gets the properties of a storage account's Queue Service, including
|
||||
Windows Azure Storage Analytics.
|
||||
|
||||
timeout: Optional. The timeout parameter is expressed in seconds.
|
||||
'''
|
||||
request = HTTPRequest()
|
||||
request.method = 'GET'
|
||||
request.host = self._get_host()
|
||||
request.path = '/?restype=service&comp=properties'
|
||||
request.query = [('timeout', _int_or_none(timeout))]
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_queue_header(
|
||||
request, self.account_name, self.account_key)
|
||||
response = self._perform_request(request)
|
||||
|
||||
return _parse_response(response, StorageServiceProperties)
|
||||
|
||||
def list_queues(self, prefix=None, marker=None, maxresults=None,
|
||||
include=None):
|
||||
'''
|
||||
Lists all of the queues in a given storage account.
|
||||
|
||||
prefix:
|
||||
Filters the results to return only queues with names that begin
|
||||
with the specified prefix.
|
||||
marker:
|
||||
A string value that identifies the portion of the list to be
|
||||
returned with the next list operation. The operation returns a
|
||||
NextMarker element within the response body if the list returned
|
||||
was not complete. This value may then be used as a query parameter
|
||||
in a subsequent call to request the next portion of the list of
|
||||
queues. The marker value is opaque to the client.
|
||||
maxresults:
|
||||
Specifies the maximum number of queues to return. If maxresults is
|
||||
not specified, the server will return up to 5,000 items.
|
||||
include:
|
||||
Optional. Include this parameter to specify that the container's
|
||||
metadata be returned as part of the response body.
|
||||
'''
|
||||
request = HTTPRequest()
|
||||
request.method = 'GET'
|
||||
request.host = self._get_host()
|
||||
request.path = '/?comp=list'
|
||||
request.query = [
|
||||
('prefix', _str_or_none(prefix)),
|
||||
('marker', _str_or_none(marker)),
|
||||
('maxresults', _int_or_none(maxresults)),
|
||||
('include', _str_or_none(include))
|
||||
]
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_queue_header(
|
||||
request, self.account_name, self.account_key)
|
||||
response = self._perform_request(request)
|
||||
|
||||
return _parse_enum_results_list(
|
||||
response, QueueEnumResults, "Queues", Queue)
|
||||
|
||||
def create_queue(self, queue_name, x_ms_meta_name_values=None,
|
||||
fail_on_exist=False):
|
||||
'''
|
||||
Creates a queue under the given account.
|
||||
|
||||
queue_name: name of the queue.
|
||||
x_ms_meta_name_values:
|
||||
Optional. A dict containing name-value pairs to associate with the
|
||||
queue as metadata.
|
||||
fail_on_exist: Specify whether throw exception when queue exists.
|
||||
'''
|
||||
_validate_not_none('queue_name', queue_name)
|
||||
request = HTTPRequest()
|
||||
request.method = 'PUT'
|
||||
request.host = self._get_host()
|
||||
request.path = '/' + _str(queue_name) + ''
|
||||
request.headers = [('x-ms-meta-name-values', x_ms_meta_name_values)]
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_queue_header(
|
||||
request, self.account_name, self.account_key)
|
||||
if not fail_on_exist:
|
||||
try:
|
||||
response = self._perform_request(request)
|
||||
if response.status == HTTP_RESPONSE_NO_CONTENT:
|
||||
return False
|
||||
return True
|
||||
except WindowsAzureError as ex:
|
||||
_dont_fail_on_exist(ex)
|
||||
return False
|
||||
else:
|
||||
response = self._perform_request(request)
|
||||
if response.status == HTTP_RESPONSE_NO_CONTENT:
|
||||
raise WindowsAzureConflictError(
|
||||
_ERROR_CONFLICT.format(response.message))
|
||||
return True
|
||||
|
||||
def delete_queue(self, queue_name, fail_not_exist=False):
|
||||
'''
|
||||
Permanently deletes the specified queue.
|
||||
|
||||
queue_name: Name of the queue.
|
||||
fail_not_exist:
|
||||
Specify whether throw exception when queue doesn't exist.
|
||||
'''
|
||||
_validate_not_none('queue_name', queue_name)
|
||||
request = HTTPRequest()
|
||||
request.method = 'DELETE'
|
||||
request.host = self._get_host()
|
||||
request.path = '/' + _str(queue_name) + ''
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_queue_header(
|
||||
request, self.account_name, self.account_key)
|
||||
if not fail_not_exist:
|
||||
try:
|
||||
self._perform_request(request)
|
||||
return True
|
||||
except WindowsAzureError as ex:
|
||||
_dont_fail_not_exist(ex)
|
||||
return False
|
||||
else:
|
||||
self._perform_request(request)
|
||||
return True
|
||||
|
||||
def get_queue_metadata(self, queue_name):
|
||||
'''
|
||||
Retrieves user-defined metadata and queue properties on the specified
|
||||
queue. Metadata is associated with the queue as name-values pairs.
|
||||
|
||||
queue_name: Name of the queue.
|
||||
'''
|
||||
_validate_not_none('queue_name', queue_name)
|
||||
request = HTTPRequest()
|
||||
request.method = 'GET'
|
||||
request.host = self._get_host()
|
||||
request.path = '/' + _str(queue_name) + '?comp=metadata'
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_queue_header(
|
||||
request, self.account_name, self.account_key)
|
||||
response = self._perform_request(request)
|
||||
|
||||
return _parse_response_for_dict_prefix(
|
||||
response,
|
||||
prefixes=['x-ms-meta', 'x-ms-approximate-messages-count'])
|
||||
|
||||
def set_queue_metadata(self, queue_name, x_ms_meta_name_values=None):
|
||||
'''
|
||||
Sets user-defined metadata on the specified queue. Metadata is
|
||||
associated with the queue as name-value pairs.
|
||||
|
||||
queue_name: Name of the queue.
|
||||
x_ms_meta_name_values:
|
||||
Optional. A dict containing name-value pairs to associate with the
|
||||
queue as metadata.
|
||||
'''
|
||||
_validate_not_none('queue_name', queue_name)
|
||||
request = HTTPRequest()
|
||||
request.method = 'PUT'
|
||||
request.host = self._get_host()
|
||||
request.path = '/' + _str(queue_name) + '?comp=metadata'
|
||||
request.headers = [('x-ms-meta-name-values', x_ms_meta_name_values)]
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_queue_header(
|
||||
request, self.account_name, self.account_key)
|
||||
self._perform_request(request)
|
||||
|
||||
def put_message(self, queue_name, message_text, visibilitytimeout=None,
|
||||
messagettl=None):
|
||||
'''
|
||||
Adds a new message to the back of the message queue. A visibility
|
||||
timeout can also be specified to make the message invisible until the
|
||||
visibility timeout expires. A message must be in a format that can be
|
||||
included in an XML request with UTF-8 encoding. The encoded message can
|
||||
be up to 64KB in size for versions 2011-08-18 and newer, or 8KB in size
|
||||
for previous versions.
|
||||
|
||||
queue_name: Name of the queue.
|
||||
message_text: Message content.
|
||||
visibilitytimeout:
|
||||
Optional. If not specified, the default value is 0. Specifies the
|
||||
new visibility timeout value, in seconds, relative to server time.
|
||||
The new value must be larger than or equal to 0, and cannot be
|
||||
larger than 7 days. The visibility timeout of a message cannot be
|
||||
set to a value later than the expiry time. visibilitytimeout
|
||||
should be set to a value smaller than the time-to-live value.
|
||||
messagettl:
|
||||
Optional. Specifies the time-to-live interval for the message, in
|
||||
seconds. The maximum time-to-live allowed is 7 days. If this
|
||||
parameter is omitted, the default time-to-live is 7 days.
|
||||
'''
|
||||
_validate_not_none('queue_name', queue_name)
|
||||
_validate_not_none('message_text', message_text)
|
||||
request = HTTPRequest()
|
||||
request.method = 'POST'
|
||||
request.host = self._get_host()
|
||||
request.path = '/' + _str(queue_name) + '/messages'
|
||||
request.query = [
|
||||
('visibilitytimeout', _str_or_none(visibilitytimeout)),
|
||||
('messagettl', _str_or_none(messagettl))
|
||||
]
|
||||
request.body = _get_request_body(
|
||||
'<?xml version="1.0" encoding="utf-8"?> \
|
||||
<QueueMessage> \
|
||||
<MessageText>' + xml_escape(_str(message_text)) + '</MessageText> \
|
||||
</QueueMessage>')
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_queue_header(
|
||||
request, self.account_name, self.account_key)
|
||||
self._perform_request(request)
|
||||
|
||||
def get_messages(self, queue_name, numofmessages=None,
|
||||
visibilitytimeout=None):
|
||||
'''
|
||||
Retrieves one or more messages from the front of the queue.
|
||||
|
||||
queue_name: Name of the queue.
|
||||
numofmessages:
|
||||
Optional. A nonzero integer value that specifies the number of
|
||||
messages to retrieve from the queue, up to a maximum of 32. If
|
||||
fewer are visible, the visible messages are returned. By default,
|
||||
a single message is retrieved from the queue with this operation.
|
||||
visibilitytimeout:
|
||||
Specifies the new visibility timeout value, in seconds, relative
|
||||
to server time. The new value must be larger than or equal to 1
|
||||
second, and cannot be larger than 7 days, or larger than 2 hours
|
||||
on REST protocol versions prior to version 2011-08-18. The
|
||||
visibility timeout of a message can be set to a value later than
|
||||
the expiry time.
|
||||
'''
|
||||
_validate_not_none('queue_name', queue_name)
|
||||
request = HTTPRequest()
|
||||
request.method = 'GET'
|
||||
request.host = self._get_host()
|
||||
request.path = '/' + _str(queue_name) + '/messages'
|
||||
request.query = [
|
||||
('numofmessages', _str_or_none(numofmessages)),
|
||||
('visibilitytimeout', _str_or_none(visibilitytimeout))
|
||||
]
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_queue_header(
|
||||
request, self.account_name, self.account_key)
|
||||
response = self._perform_request(request)
|
||||
|
||||
return _parse_response(response, QueueMessagesList)
|
||||
|
||||
def peek_messages(self, queue_name, numofmessages=None):
|
||||
'''
|
||||
Retrieves one or more messages from the front of the queue, but does
|
||||
not alter the visibility of the message.
|
||||
|
||||
queue_name: Name of the queue.
|
||||
numofmessages:
|
||||
Optional. A nonzero integer value that specifies the number of
|
||||
messages to peek from the queue, up to a maximum of 32. By default,
|
||||
a single message is peeked from the queue with this operation.
|
||||
'''
|
||||
_validate_not_none('queue_name', queue_name)
|
||||
request = HTTPRequest()
|
||||
request.method = 'GET'
|
||||
request.host = self._get_host()
|
||||
request.path = '/' + _str(queue_name) + '/messages?peekonly=true'
|
||||
request.query = [('numofmessages', _str_or_none(numofmessages))]
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_queue_header(
|
||||
request, self.account_name, self.account_key)
|
||||
response = self._perform_request(request)
|
||||
|
||||
return _parse_response(response, QueueMessagesList)
|
||||
|
||||
def delete_message(self, queue_name, message_id, popreceipt):
|
||||
'''
|
||||
Deletes the specified message.
|
||||
|
||||
queue_name: Name of the queue.
|
||||
message_id: Message to delete.
|
||||
popreceipt:
|
||||
Required. A valid pop receipt value returned from an earlier call
|
||||
to the Get Messages or Update Message operation.
|
||||
'''
|
||||
_validate_not_none('queue_name', queue_name)
|
||||
_validate_not_none('message_id', message_id)
|
||||
_validate_not_none('popreceipt', popreceipt)
|
||||
request = HTTPRequest()
|
||||
request.method = 'DELETE'
|
||||
request.host = self._get_host()
|
||||
request.path = '/' + \
|
||||
_str(queue_name) + '/messages/' + _str(message_id) + ''
|
||||
request.query = [('popreceipt', _str_or_none(popreceipt))]
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_queue_header(
|
||||
request, self.account_name, self.account_key)
|
||||
self._perform_request(request)
|
||||
|
||||
def clear_messages(self, queue_name):
|
||||
'''
|
||||
Deletes all messages from the specified queue.
|
||||
|
||||
queue_name: Name of the queue.
|
||||
'''
|
||||
_validate_not_none('queue_name', queue_name)
|
||||
request = HTTPRequest()
|
||||
request.method = 'DELETE'
|
||||
request.host = self._get_host()
|
||||
request.path = '/' + _str(queue_name) + '/messages'
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_queue_header(
|
||||
request, self.account_name, self.account_key)
|
||||
self._perform_request(request)
|
||||
|
||||
def update_message(self, queue_name, message_id, message_text, popreceipt,
|
||||
visibilitytimeout):
|
||||
'''
|
||||
Updates the visibility timeout of a message. You can also use this
|
||||
operation to update the contents of a message.
|
||||
|
||||
queue_name: Name of the queue.
|
||||
message_id: Message to update.
|
||||
message_text: Content of message.
|
||||
popreceipt:
|
||||
Required. A valid pop receipt value returned from an earlier call
|
||||
to the Get Messages or Update Message operation.
|
||||
visibilitytimeout:
|
||||
Required. Specifies the new visibility timeout value, in seconds,
|
||||
relative to server time. The new value must be larger than or equal
|
||||
to 0, and cannot be larger than 7 days. The visibility timeout of a
|
||||
message cannot be set to a value later than the expiry time. A
|
||||
message can be updated until it has been deleted or has expired.
|
||||
'''
|
||||
_validate_not_none('queue_name', queue_name)
|
||||
_validate_not_none('message_id', message_id)
|
||||
_validate_not_none('message_text', message_text)
|
||||
_validate_not_none('popreceipt', popreceipt)
|
||||
_validate_not_none('visibilitytimeout', visibilitytimeout)
|
||||
request = HTTPRequest()
|
||||
request.method = 'PUT'
|
||||
request.host = self._get_host()
|
||||
request.path = '/' + \
|
||||
_str(queue_name) + '/messages/' + _str(message_id) + ''
|
||||
request.query = [
|
||||
('popreceipt', _str_or_none(popreceipt)),
|
||||
('visibilitytimeout', _str_or_none(visibilitytimeout))
|
||||
]
|
||||
request.body = _get_request_body(
|
||||
'<?xml version="1.0" encoding="utf-8"?> \
|
||||
<QueueMessage> \
|
||||
<MessageText>' + xml_escape(_str(message_text)) + '</MessageText> \
|
||||
</QueueMessage>')
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_queue_header(
|
||||
request, self.account_name, self.account_key)
|
||||
response = self._perform_request(request)
|
||||
|
||||
return _parse_response_for_dict_filter(
|
||||
response,
|
||||
filter=['x-ms-popreceipt', 'x-ms-time-next-visible'])
|
||||
|
||||
def set_queue_service_properties(self, storage_service_properties,
|
||||
timeout=None):
|
||||
'''
|
||||
Sets the properties of a storage account's Queue service, including
|
||||
Windows Azure Storage Analytics.
|
||||
|
||||
storage_service_properties: StorageServiceProperties object.
|
||||
timeout: Optional. The timeout parameter is expressed in seconds.
|
||||
'''
|
||||
_validate_not_none('storage_service_properties',
|
||||
storage_service_properties)
|
||||
request = HTTPRequest()
|
||||
request.method = 'PUT'
|
||||
request.host = self._get_host()
|
||||
request.path = '/?restype=service&comp=properties'
|
||||
request.query = [('timeout', _int_or_none(timeout))]
|
||||
request.body = _get_request_body(
|
||||
_convert_class_to_xml(storage_service_properties))
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_queue_header(
|
||||
request, self.account_name, self.account_key)
|
||||
self._perform_request(request)
|
||||
|
||||
@ -1,230 +1,231 @@
|
||||
#-------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft. 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.
|
||||
# You may obtain a copy of the License at
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License 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.
|
||||
#--------------------------------------------------------------------------
|
||||
from azure import url_quote
|
||||
from azure.storage import _sign_string, X_MS_VERSION
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Constants for the share access signature
|
||||
SIGNED_START = 'st'
|
||||
SIGNED_EXPIRY = 'se'
|
||||
SIGNED_RESOURCE = 'sr'
|
||||
SIGNED_PERMISSION = 'sp'
|
||||
SIGNED_IDENTIFIER = 'si'
|
||||
SIGNED_SIGNATURE = 'sig'
|
||||
SIGNED_VERSION = 'sv'
|
||||
RESOURCE_BLOB = 'b'
|
||||
RESOURCE_CONTAINER = 'c'
|
||||
SIGNED_RESOURCE_TYPE = 'resource'
|
||||
SHARED_ACCESS_PERMISSION = 'permission'
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
|
||||
class WebResource(object):
|
||||
|
||||
'''
|
||||
Class that stands for the resource to get the share access signature
|
||||
|
||||
path: the resource path.
|
||||
properties: dict of name and values. Contains 2 item: resource type and
|
||||
permission
|
||||
request_url: the url of the webresource include all the queries.
|
||||
'''
|
||||
|
||||
def __init__(self, path=None, request_url=None, properties=None):
|
||||
self.path = path
|
||||
self.properties = properties or {}
|
||||
self.request_url = request_url
|
||||
|
||||
|
||||
class Permission(object):
|
||||
|
||||
'''
|
||||
Permission class. Contains the path and query_string for the path.
|
||||
|
||||
path: the resource path
|
||||
query_string: dict of name, values. Contains SIGNED_START, SIGNED_EXPIRY
|
||||
SIGNED_RESOURCE, SIGNED_PERMISSION, SIGNED_IDENTIFIER,
|
||||
SIGNED_SIGNATURE name values.
|
||||
'''
|
||||
|
||||
def __init__(self, path=None, query_string=None):
|
||||
self.path = path
|
||||
self.query_string = query_string
|
||||
|
||||
|
||||
class SharedAccessPolicy(object):
|
||||
|
||||
''' SharedAccessPolicy class. '''
|
||||
|
||||
def __init__(self, access_policy, signed_identifier=None):
|
||||
self.id = signed_identifier
|
||||
self.access_policy = access_policy
|
||||
|
||||
|
||||
class SharedAccessSignature(object):
|
||||
|
||||
'''
|
||||
The main class used to do the signing and generating the signature.
|
||||
|
||||
account_name:
|
||||
the storage account name used to generate shared access signature
|
||||
account_key: the access key to genenerate share access signature
|
||||
permission_set: the permission cache used to signed the request url.
|
||||
'''
|
||||
|
||||
def __init__(self, account_name, account_key, permission_set=None):
|
||||
self.account_name = account_name
|
||||
self.account_key = account_key
|
||||
self.permission_set = permission_set
|
||||
|
||||
def generate_signed_query_string(self, path, resource_type,
|
||||
shared_access_policy,
|
||||
version=X_MS_VERSION):
|
||||
'''
|
||||
Generates the query string for path, resource type and shared access
|
||||
policy.
|
||||
|
||||
path: the resource
|
||||
resource_type: could be blob or container
|
||||
shared_access_policy: shared access policy
|
||||
version:
|
||||
x-ms-version for storage service, or None to get a signed query
|
||||
string compatible with pre 2012-02-12 clients, where the version
|
||||
is not included in the query string.
|
||||
'''
|
||||
|
||||
query_string = {}
|
||||
if shared_access_policy.access_policy.start:
|
||||
query_string[
|
||||
SIGNED_START] = shared_access_policy.access_policy.start
|
||||
|
||||
if version:
|
||||
query_string[SIGNED_VERSION] = version
|
||||
query_string[SIGNED_EXPIRY] = shared_access_policy.access_policy.expiry
|
||||
query_string[SIGNED_RESOURCE] = resource_type
|
||||
query_string[
|
||||
SIGNED_PERMISSION] = shared_access_policy.access_policy.permission
|
||||
|
||||
if shared_access_policy.id:
|
||||
query_string[SIGNED_IDENTIFIER] = shared_access_policy.id
|
||||
|
||||
query_string[SIGNED_SIGNATURE] = self._generate_signature(
|
||||
path, shared_access_policy, version)
|
||||
return query_string
|
||||
|
||||
def sign_request(self, web_resource):
|
||||
''' sign request to generate request_url with sharedaccesssignature
|
||||
info for web_resource.'''
|
||||
|
||||
if self.permission_set:
|
||||
for shared_access_signature in self.permission_set:
|
||||
if self._permission_matches_request(
|
||||
shared_access_signature, web_resource,
|
||||
web_resource.properties[
|
||||
SIGNED_RESOURCE_TYPE],
|
||||
web_resource.properties[SHARED_ACCESS_PERMISSION]):
|
||||
if web_resource.request_url.find('?') == -1:
|
||||
web_resource.request_url += '?'
|
||||
else:
|
||||
web_resource.request_url += '&'
|
||||
|
||||
web_resource.request_url += self._convert_query_string(
|
||||
shared_access_signature.query_string)
|
||||
break
|
||||
return web_resource
|
||||
|
||||
def _convert_query_string(self, query_string):
|
||||
''' Converts query string to str. The order of name, values is very
|
||||
important and can't be wrong.'''
|
||||
|
||||
convert_str = ''
|
||||
if SIGNED_START in query_string:
|
||||
convert_str += SIGNED_START + '=' + \
|
||||
url_quote(query_string[SIGNED_START]) + '&'
|
||||
convert_str += SIGNED_EXPIRY + '=' + \
|
||||
url_quote(query_string[SIGNED_EXPIRY]) + '&'
|
||||
convert_str += SIGNED_PERMISSION + '=' + \
|
||||
query_string[SIGNED_PERMISSION] + '&'
|
||||
convert_str += SIGNED_RESOURCE + '=' + \
|
||||
query_string[SIGNED_RESOURCE] + '&'
|
||||
|
||||
if SIGNED_IDENTIFIER in query_string:
|
||||
convert_str += SIGNED_IDENTIFIER + '=' + \
|
||||
query_string[SIGNED_IDENTIFIER] + '&'
|
||||
if SIGNED_VERSION in query_string:
|
||||
convert_str += SIGNED_VERSION + '=' + \
|
||||
query_string[SIGNED_VERSION] + '&'
|
||||
convert_str += SIGNED_SIGNATURE + '=' + \
|
||||
url_quote(query_string[SIGNED_SIGNATURE]) + '&'
|
||||
return convert_str
|
||||
|
||||
def _generate_signature(self, path, shared_access_policy, version):
|
||||
''' Generates signature for a given path and shared access policy. '''
|
||||
|
||||
def get_value_to_append(value, no_new_line=False):
|
||||
return_value = ''
|
||||
if value:
|
||||
return_value = value
|
||||
if not no_new_line:
|
||||
return_value += '\n'
|
||||
return return_value
|
||||
|
||||
if path[0] != '/':
|
||||
path = '/' + path
|
||||
|
||||
canonicalized_resource = '/' + self.account_name + path
|
||||
|
||||
# Form the string to sign from shared_access_policy and canonicalized
|
||||
# resource. The order of values is important.
|
||||
string_to_sign = \
|
||||
(get_value_to_append(shared_access_policy.access_policy.permission) +
|
||||
get_value_to_append(shared_access_policy.access_policy.start) +
|
||||
get_value_to_append(shared_access_policy.access_policy.expiry) +
|
||||
get_value_to_append(canonicalized_resource))
|
||||
|
||||
if version:
|
||||
string_to_sign += get_value_to_append(shared_access_policy.id)
|
||||
string_to_sign += get_value_to_append(version, True)
|
||||
else:
|
||||
string_to_sign += get_value_to_append(shared_access_policy.id, True)
|
||||
|
||||
return self._sign(string_to_sign)
|
||||
|
||||
def _permission_matches_request(self, shared_access_signature,
|
||||
web_resource, resource_type,
|
||||
required_permission):
|
||||
''' Check whether requested permission matches given
|
||||
shared_access_signature, web_resource and resource type. '''
|
||||
|
||||
required_resource_type = resource_type
|
||||
if required_resource_type == RESOURCE_BLOB:
|
||||
required_resource_type += RESOURCE_CONTAINER
|
||||
|
||||
for name, value in shared_access_signature.query_string.items():
|
||||
if name == SIGNED_RESOURCE and \
|
||||
required_resource_type.find(value) == -1:
|
||||
return False
|
||||
elif name == SIGNED_PERMISSION and \
|
||||
required_permission.find(value) == -1:
|
||||
return False
|
||||
|
||||
return web_resource.path.find(shared_access_signature.path) != -1
|
||||
|
||||
def _sign(self, string_to_sign):
|
||||
''' use HMAC-SHA256 to sign the string and convert it as base64
|
||||
encoded string. '''
|
||||
|
||||
return _sign_string(self.account_key, string_to_sign)
|
||||
#-------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft. 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.
|
||||
# You may obtain a copy of the License at
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License 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.
|
||||
#--------------------------------------------------------------------------
|
||||
from azure import _sign_string, url_quote
|
||||
from azure.storage import X_MS_VERSION
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Constants for the share access signature
|
||||
SIGNED_VERSION = 'sv'
|
||||
SIGNED_START = 'st'
|
||||
SIGNED_EXPIRY = 'se'
|
||||
SIGNED_RESOURCE = 'sr'
|
||||
SIGNED_PERMISSION = 'sp'
|
||||
SIGNED_IDENTIFIER = 'si'
|
||||
SIGNED_SIGNATURE = 'sig'
|
||||
SIGNED_VERSION = 'sv'
|
||||
RESOURCE_BLOB = 'b'
|
||||
RESOURCE_CONTAINER = 'c'
|
||||
SIGNED_RESOURCE_TYPE = 'resource'
|
||||
SHARED_ACCESS_PERMISSION = 'permission'
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
|
||||
class WebResource(object):
|
||||
|
||||
'''
|
||||
Class that stands for the resource to get the share access signature
|
||||
|
||||
path: the resource path.
|
||||
properties: dict of name and values. Contains 2 item: resource type and
|
||||
permission
|
||||
request_url: the url of the webresource include all the queries.
|
||||
'''
|
||||
|
||||
def __init__(self, path=None, request_url=None, properties=None):
|
||||
self.path = path
|
||||
self.properties = properties or {}
|
||||
self.request_url = request_url
|
||||
|
||||
|
||||
class Permission(object):
|
||||
|
||||
'''
|
||||
Permission class. Contains the path and query_string for the path.
|
||||
|
||||
path: the resource path
|
||||
query_string: dict of name, values. Contains SIGNED_START, SIGNED_EXPIRY
|
||||
SIGNED_RESOURCE, SIGNED_PERMISSION, SIGNED_IDENTIFIER,
|
||||
SIGNED_SIGNATURE name values.
|
||||
'''
|
||||
|
||||
def __init__(self, path=None, query_string=None):
|
||||
self.path = path
|
||||
self.query_string = query_string
|
||||
|
||||
|
||||
class SharedAccessPolicy(object):
|
||||
|
||||
''' SharedAccessPolicy class. '''
|
||||
|
||||
def __init__(self, access_policy, signed_identifier=None):
|
||||
self.id = signed_identifier
|
||||
self.access_policy = access_policy
|
||||
|
||||
|
||||
class SharedAccessSignature(object):
|
||||
|
||||
'''
|
||||
The main class used to do the signing and generating the signature.
|
||||
|
||||
account_name:
|
||||
the storage account name used to generate shared access signature
|
||||
account_key: the access key to genenerate share access signature
|
||||
permission_set: the permission cache used to signed the request url.
|
||||
'''
|
||||
|
||||
def __init__(self, account_name, account_key, permission_set=None):
|
||||
self.account_name = account_name
|
||||
self.account_key = account_key
|
||||
self.permission_set = permission_set
|
||||
|
||||
def generate_signed_query_string(self, path, resource_type,
|
||||
shared_access_policy,
|
||||
version=X_MS_VERSION):
|
||||
'''
|
||||
Generates the query string for path, resource type and shared access
|
||||
policy.
|
||||
|
||||
path: the resource
|
||||
resource_type: could be blob or container
|
||||
shared_access_policy: shared access policy
|
||||
version:
|
||||
x-ms-version for storage service, or None to get a signed query
|
||||
string compatible with pre 2012-02-12 clients, where the version
|
||||
is not included in the query string.
|
||||
'''
|
||||
|
||||
query_string = {}
|
||||
if shared_access_policy.access_policy.start:
|
||||
query_string[
|
||||
SIGNED_START] = shared_access_policy.access_policy.start
|
||||
|
||||
if version:
|
||||
query_string[SIGNED_VERSION] = version
|
||||
query_string[SIGNED_EXPIRY] = shared_access_policy.access_policy.expiry
|
||||
query_string[SIGNED_RESOURCE] = resource_type
|
||||
query_string[
|
||||
SIGNED_PERMISSION] = shared_access_policy.access_policy.permission
|
||||
|
||||
if shared_access_policy.id:
|
||||
query_string[SIGNED_IDENTIFIER] = shared_access_policy.id
|
||||
|
||||
query_string[SIGNED_SIGNATURE] = self._generate_signature(
|
||||
path, shared_access_policy, version)
|
||||
return query_string
|
||||
|
||||
def sign_request(self, web_resource):
|
||||
''' sign request to generate request_url with sharedaccesssignature
|
||||
info for web_resource.'''
|
||||
|
||||
if self.permission_set:
|
||||
for shared_access_signature in self.permission_set:
|
||||
if self._permission_matches_request(
|
||||
shared_access_signature, web_resource,
|
||||
web_resource.properties[
|
||||
SIGNED_RESOURCE_TYPE],
|
||||
web_resource.properties[SHARED_ACCESS_PERMISSION]):
|
||||
if web_resource.request_url.find('?') == -1:
|
||||
web_resource.request_url += '?'
|
||||
else:
|
||||
web_resource.request_url += '&'
|
||||
|
||||
web_resource.request_url += self._convert_query_string(
|
||||
shared_access_signature.query_string)
|
||||
break
|
||||
return web_resource
|
||||
|
||||
def _convert_query_string(self, query_string):
|
||||
''' Converts query string to str. The order of name, values is very
|
||||
important and can't be wrong.'''
|
||||
|
||||
convert_str = ''
|
||||
if SIGNED_START in query_string:
|
||||
convert_str += SIGNED_START + '=' + \
|
||||
url_quote(query_string[SIGNED_START]) + '&'
|
||||
convert_str += SIGNED_EXPIRY + '=' + \
|
||||
url_quote(query_string[SIGNED_EXPIRY]) + '&'
|
||||
convert_str += SIGNED_PERMISSION + '=' + \
|
||||
query_string[SIGNED_PERMISSION] + '&'
|
||||
convert_str += SIGNED_RESOURCE + '=' + \
|
||||
query_string[SIGNED_RESOURCE] + '&'
|
||||
|
||||
if SIGNED_IDENTIFIER in query_string:
|
||||
convert_str += SIGNED_IDENTIFIER + '=' + \
|
||||
query_string[SIGNED_IDENTIFIER] + '&'
|
||||
if SIGNED_VERSION in query_string:
|
||||
convert_str += SIGNED_VERSION + '=' + \
|
||||
query_string[SIGNED_VERSION] + '&'
|
||||
convert_str += SIGNED_SIGNATURE + '=' + \
|
||||
url_quote(query_string[SIGNED_SIGNATURE]) + '&'
|
||||
return convert_str
|
||||
|
||||
def _generate_signature(self, path, shared_access_policy, version):
|
||||
''' Generates signature for a given path and shared access policy. '''
|
||||
|
||||
def get_value_to_append(value, no_new_line=False):
|
||||
return_value = ''
|
||||
if value:
|
||||
return_value = value
|
||||
if not no_new_line:
|
||||
return_value += '\n'
|
||||
return return_value
|
||||
|
||||
if path[0] != '/':
|
||||
path = '/' + path
|
||||
|
||||
canonicalized_resource = '/' + self.account_name + path
|
||||
|
||||
# Form the string to sign from shared_access_policy and canonicalized
|
||||
# resource. The order of values is important.
|
||||
string_to_sign = \
|
||||
(get_value_to_append(shared_access_policy.access_policy.permission) +
|
||||
get_value_to_append(shared_access_policy.access_policy.start) +
|
||||
get_value_to_append(shared_access_policy.access_policy.expiry) +
|
||||
get_value_to_append(canonicalized_resource))
|
||||
|
||||
if version:
|
||||
string_to_sign += get_value_to_append(shared_access_policy.id)
|
||||
string_to_sign += get_value_to_append(version, True)
|
||||
else:
|
||||
string_to_sign += get_value_to_append(shared_access_policy.id, True)
|
||||
|
||||
return self._sign(string_to_sign)
|
||||
|
||||
def _permission_matches_request(self, shared_access_signature,
|
||||
web_resource, resource_type,
|
||||
required_permission):
|
||||
''' Check whether requested permission matches given
|
||||
shared_access_signature, web_resource and resource type. '''
|
||||
|
||||
required_resource_type = resource_type
|
||||
if required_resource_type == RESOURCE_BLOB:
|
||||
required_resource_type += RESOURCE_CONTAINER
|
||||
|
||||
for name, value in shared_access_signature.query_string.items():
|
||||
if name == SIGNED_RESOURCE and \
|
||||
required_resource_type.find(value) == -1:
|
||||
return False
|
||||
elif name == SIGNED_PERMISSION and \
|
||||
required_permission.find(value) == -1:
|
||||
return False
|
||||
|
||||
return web_resource.path.find(shared_access_signature.path) != -1
|
||||
|
||||
def _sign(self, string_to_sign):
|
||||
''' use HMAC-SHA256 to sign the string and convert it as base64
|
||||
encoded string. '''
|
||||
|
||||
return _sign_string(self.account_key, string_to_sign)
|
||||
|
||||
@ -1,152 +1,152 @@
|
||||
#-------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft. 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.
|
||||
# You may obtain a copy of the License at
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License 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.
|
||||
#--------------------------------------------------------------------------
|
||||
import os
|
||||
import sys
|
||||
|
||||
from azure import (
|
||||
WindowsAzureError,
|
||||
DEV_ACCOUNT_NAME,
|
||||
DEV_ACCOUNT_KEY,
|
||||
_ERROR_STORAGE_MISSING_INFO,
|
||||
)
|
||||
from azure.http import HTTPError
|
||||
from azure.http.httpclient import _HTTPClient
|
||||
from azure.storage import _storage_error_handler
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# constants for azure app setting environment variables
|
||||
AZURE_STORAGE_ACCOUNT = 'AZURE_STORAGE_ACCOUNT'
|
||||
AZURE_STORAGE_ACCESS_KEY = 'AZURE_STORAGE_ACCESS_KEY'
|
||||
EMULATED = 'EMULATED'
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
|
||||
class _StorageClient(object):
|
||||
|
||||
'''
|
||||
This is the base class for BlobManager, TableManager and QueueManager.
|
||||
'''
|
||||
|
||||
def __init__(self, account_name=None, account_key=None, protocol='https',
|
||||
host_base='', dev_host=''):
|
||||
'''
|
||||
account_name: your storage account name, required for all operations.
|
||||
account_key: your storage account key, required for all operations.
|
||||
protocol: Optional. Protocol. Defaults to http.
|
||||
host_base:
|
||||
Optional. Live host base url. Defaults to Azure url. Override this
|
||||
for on-premise.
|
||||
dev_host: Optional. Dev host url. Defaults to localhost.
|
||||
'''
|
||||
self.account_name = account_name
|
||||
self.account_key = account_key
|
||||
self.requestid = None
|
||||
self.protocol = protocol
|
||||
self.host_base = host_base
|
||||
self.dev_host = dev_host
|
||||
|
||||
# the app is not run in azure emulator or use default development
|
||||
# storage account and key if app is run in emulator.
|
||||
self.use_local_storage = False
|
||||
|
||||
# check whether it is run in emulator.
|
||||
if EMULATED in os.environ:
|
||||
self.is_emulated = os.environ[EMULATED].lower() != 'false'
|
||||
else:
|
||||
self.is_emulated = False
|
||||
|
||||
# get account_name and account key. If they are not set when
|
||||
# constructing, get the account and key from environment variables if
|
||||
# the app is not run in azure emulator or use default development
|
||||
# storage account and key if app is run in emulator.
|
||||
if not self.account_name or not self.account_key:
|
||||
if self.is_emulated:
|
||||
self.account_name = DEV_ACCOUNT_NAME
|
||||
self.account_key = DEV_ACCOUNT_KEY
|
||||
self.protocol = 'http'
|
||||
self.use_local_storage = True
|
||||
else:
|
||||
self.account_name = os.environ.get(AZURE_STORAGE_ACCOUNT)
|
||||
self.account_key = os.environ.get(AZURE_STORAGE_ACCESS_KEY)
|
||||
|
||||
if not self.account_name or not self.account_key:
|
||||
raise WindowsAzureError(_ERROR_STORAGE_MISSING_INFO)
|
||||
|
||||
self._httpclient = _HTTPClient(
|
||||
service_instance=self,
|
||||
account_key=self.account_key,
|
||||
account_name=self.account_name,
|
||||
protocol=self.protocol)
|
||||
self._batchclient = None
|
||||
self._filter = self._perform_request_worker
|
||||
|
||||
def with_filter(self, filter):
|
||||
'''
|
||||
Returns a new service which will process requests with the specified
|
||||
filter. Filtering operations can include logging, automatic retrying,
|
||||
etc... The filter is a lambda which receives the HTTPRequest and
|
||||
another lambda. The filter can perform any pre-processing on the
|
||||
request, pass it off to the next lambda, and then perform any
|
||||
post-processing on the response.
|
||||
'''
|
||||
res = type(self)(self.account_name, self.account_key, self.protocol)
|
||||
old_filter = self._filter
|
||||
|
||||
def new_filter(request):
|
||||
return filter(request, old_filter)
|
||||
|
||||
res._filter = new_filter
|
||||
return res
|
||||
|
||||
def set_proxy(self, host, port, user=None, password=None):
|
||||
'''
|
||||
Sets the proxy server host and port for the HTTP CONNECT Tunnelling.
|
||||
|
||||
host: Address of the proxy. Ex: '192.168.0.100'
|
||||
port: Port of the proxy. Ex: 6000
|
||||
user: User for proxy authorization.
|
||||
password: Password for proxy authorization.
|
||||
'''
|
||||
self._httpclient.set_proxy(host, port, user, password)
|
||||
|
||||
def _get_host(self):
|
||||
if self.use_local_storage:
|
||||
return self.dev_host
|
||||
else:
|
||||
return self.account_name + self.host_base
|
||||
|
||||
def _perform_request_worker(self, request):
|
||||
return self._httpclient.perform_request(request)
|
||||
|
||||
def _perform_request(self, request, text_encoding='utf-8'):
|
||||
'''
|
||||
Sends the request and return response. Catches HTTPError and hand it
|
||||
to error handler
|
||||
'''
|
||||
try:
|
||||
if self._batchclient is not None:
|
||||
return self._batchclient.insert_request_to_batch(request)
|
||||
else:
|
||||
resp = self._filter(request)
|
||||
|
||||
if sys.version_info >= (3,) and isinstance(resp, bytes) and \
|
||||
text_encoding:
|
||||
resp = resp.decode(text_encoding)
|
||||
|
||||
except HTTPError as ex:
|
||||
_storage_error_handler(ex)
|
||||
|
||||
return resp
|
||||
#-------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft. 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.
|
||||
# You may obtain a copy of the License at
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License 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.
|
||||
#--------------------------------------------------------------------------
|
||||
import os
|
||||
import sys
|
||||
|
||||
from azure import (
|
||||
WindowsAzureError,
|
||||
DEV_ACCOUNT_NAME,
|
||||
DEV_ACCOUNT_KEY,
|
||||
_ERROR_STORAGE_MISSING_INFO,
|
||||
)
|
||||
from azure.http import HTTPError
|
||||
from azure.http.httpclient import _HTTPClient
|
||||
from azure.storage import _storage_error_handler
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# constants for azure app setting environment variables
|
||||
AZURE_STORAGE_ACCOUNT = 'AZURE_STORAGE_ACCOUNT'
|
||||
AZURE_STORAGE_ACCESS_KEY = 'AZURE_STORAGE_ACCESS_KEY'
|
||||
EMULATED = 'EMULATED'
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
|
||||
class _StorageClient(object):
|
||||
|
||||
'''
|
||||
This is the base class for BlobManager, TableManager and QueueManager.
|
||||
'''
|
||||
|
||||
def __init__(self, account_name=None, account_key=None, protocol='https',
|
||||
host_base='', dev_host=''):
|
||||
'''
|
||||
account_name: your storage account name, required for all operations.
|
||||
account_key: your storage account key, required for all operations.
|
||||
protocol: Optional. Protocol. Defaults to http.
|
||||
host_base:
|
||||
Optional. Live host base url. Defaults to Azure url. Override this
|
||||
for on-premise.
|
||||
dev_host: Optional. Dev host url. Defaults to localhost.
|
||||
'''
|
||||
self.account_name = account_name
|
||||
self.account_key = account_key
|
||||
self.requestid = None
|
||||
self.protocol = protocol
|
||||
self.host_base = host_base
|
||||
self.dev_host = dev_host
|
||||
|
||||
# the app is not run in azure emulator or use default development
|
||||
# storage account and key if app is run in emulator.
|
||||
self.use_local_storage = False
|
||||
|
||||
# check whether it is run in emulator.
|
||||
if EMULATED in os.environ:
|
||||
self.is_emulated = os.environ[EMULATED].lower() != 'false'
|
||||
else:
|
||||
self.is_emulated = False
|
||||
|
||||
# get account_name and account key. If they are not set when
|
||||
# constructing, get the account and key from environment variables if
|
||||
# the app is not run in azure emulator or use default development
|
||||
# storage account and key if app is run in emulator.
|
||||
if not self.account_name or not self.account_key:
|
||||
if self.is_emulated:
|
||||
self.account_name = DEV_ACCOUNT_NAME
|
||||
self.account_key = DEV_ACCOUNT_KEY
|
||||
self.protocol = 'http'
|
||||
self.use_local_storage = True
|
||||
else:
|
||||
self.account_name = os.environ.get(AZURE_STORAGE_ACCOUNT)
|
||||
self.account_key = os.environ.get(AZURE_STORAGE_ACCESS_KEY)
|
||||
|
||||
if not self.account_name or not self.account_key:
|
||||
raise WindowsAzureError(_ERROR_STORAGE_MISSING_INFO)
|
||||
|
||||
self._httpclient = _HTTPClient(
|
||||
service_instance=self,
|
||||
account_key=self.account_key,
|
||||
account_name=self.account_name,
|
||||
protocol=self.protocol)
|
||||
self._batchclient = None
|
||||
self._filter = self._perform_request_worker
|
||||
|
||||
def with_filter(self, filter):
|
||||
'''
|
||||
Returns a new service which will process requests with the specified
|
||||
filter. Filtering operations can include logging, automatic retrying,
|
||||
etc... The filter is a lambda which receives the HTTPRequest and
|
||||
another lambda. The filter can perform any pre-processing on the
|
||||
request, pass it off to the next lambda, and then perform any
|
||||
post-processing on the response.
|
||||
'''
|
||||
res = type(self)(self.account_name, self.account_key, self.protocol)
|
||||
old_filter = self._filter
|
||||
|
||||
def new_filter(request):
|
||||
return filter(request, old_filter)
|
||||
|
||||
res._filter = new_filter
|
||||
return res
|
||||
|
||||
def set_proxy(self, host, port, user=None, password=None):
|
||||
'''
|
||||
Sets the proxy server host and port for the HTTP CONNECT Tunnelling.
|
||||
|
||||
host: Address of the proxy. Ex: '192.168.0.100'
|
||||
port: Port of the proxy. Ex: 6000
|
||||
user: User for proxy authorization.
|
||||
password: Password for proxy authorization.
|
||||
'''
|
||||
self._httpclient.set_proxy(host, port, user, password)
|
||||
|
||||
def _get_host(self):
|
||||
if self.use_local_storage:
|
||||
return self.dev_host
|
||||
else:
|
||||
return self.account_name + self.host_base
|
||||
|
||||
def _perform_request_worker(self, request):
|
||||
return self._httpclient.perform_request(request)
|
||||
|
||||
def _perform_request(self, request, text_encoding='utf-8'):
|
||||
'''
|
||||
Sends the request and return response. Catches HTTPError and hand it
|
||||
to error handler
|
||||
'''
|
||||
try:
|
||||
if self._batchclient is not None:
|
||||
return self._batchclient.insert_request_to_batch(request)
|
||||
else:
|
||||
resp = self._filter(request)
|
||||
|
||||
if sys.version_info >= (3,) and isinstance(resp, bytes) and \
|
||||
text_encoding:
|
||||
resp = resp.decode(text_encoding)
|
||||
|
||||
except HTTPError as ex:
|
||||
_storage_error_handler(ex)
|
||||
|
||||
return resp
|
||||
|
||||
@ -1,491 +1,491 @@
|
||||
#-------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft. 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.
|
||||
# You may obtain a copy of the License at
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License 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.
|
||||
#--------------------------------------------------------------------------
|
||||
from azure import (
|
||||
WindowsAzureError,
|
||||
TABLE_SERVICE_HOST_BASE,
|
||||
DEV_TABLE_HOST,
|
||||
_convert_class_to_xml,
|
||||
_convert_response_to_feeds,
|
||||
_dont_fail_not_exist,
|
||||
_dont_fail_on_exist,
|
||||
_get_request_body,
|
||||
_int_or_none,
|
||||
_parse_response,
|
||||
_parse_response_for_dict,
|
||||
_parse_response_for_dict_filter,
|
||||
_str,
|
||||
_str_or_none,
|
||||
_update_request_uri_query_local_storage,
|
||||
_validate_not_none,
|
||||
)
|
||||
from azure.http import HTTPRequest
|
||||
from azure.http.batchclient import _BatchClient
|
||||
from azure.storage import (
|
||||
StorageServiceProperties,
|
||||
_convert_entity_to_xml,
|
||||
_convert_response_to_entity,
|
||||
_convert_table_to_xml,
|
||||
_convert_xml_to_entity,
|
||||
_convert_xml_to_table,
|
||||
_sign_storage_table_request,
|
||||
_update_storage_table_header,
|
||||
)
|
||||
from azure.storage.storageclient import _StorageClient
|
||||
|
||||
|
||||
class TableService(_StorageClient):
|
||||
|
||||
'''
|
||||
This is the main class managing Table resources.
|
||||
'''
|
||||
|
||||
def __init__(self, account_name=None, account_key=None, protocol='https',
|
||||
host_base=TABLE_SERVICE_HOST_BASE, dev_host=DEV_TABLE_HOST):
|
||||
'''
|
||||
account_name: your storage account name, required for all operations.
|
||||
account_key: your storage account key, required for all operations.
|
||||
protocol: Optional. Protocol. Defaults to http.
|
||||
host_base:
|
||||
Optional. Live host base url. Defaults to Azure url. Override this
|
||||
for on-premise.
|
||||
dev_host: Optional. Dev host url. Defaults to localhost.
|
||||
'''
|
||||
super(TableService, self).__init__(
|
||||
account_name, account_key, protocol, host_base, dev_host)
|
||||
|
||||
def begin_batch(self):
|
||||
if self._batchclient is None:
|
||||
self._batchclient = _BatchClient(
|
||||
service_instance=self,
|
||||
account_key=self.account_key,
|
||||
account_name=self.account_name)
|
||||
return self._batchclient.begin_batch()
|
||||
|
||||
def commit_batch(self):
|
||||
try:
|
||||
ret = self._batchclient.commit_batch()
|
||||
finally:
|
||||
self._batchclient = None
|
||||
return ret
|
||||
|
||||
def cancel_batch(self):
|
||||
self._batchclient = None
|
||||
|
||||
def get_table_service_properties(self):
|
||||
'''
|
||||
Gets the properties of a storage account's Table service, including
|
||||
Windows Azure Storage Analytics.
|
||||
'''
|
||||
request = HTTPRequest()
|
||||
request.method = 'GET'
|
||||
request.host = self._get_host()
|
||||
request.path = '/?restype=service&comp=properties'
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_table_header(request)
|
||||
response = self._perform_request(request)
|
||||
|
||||
return _parse_response(response, StorageServiceProperties)
|
||||
|
||||
def set_table_service_properties(self, storage_service_properties):
|
||||
'''
|
||||
Sets the properties of a storage account's Table Service, including
|
||||
Windows Azure Storage Analytics.
|
||||
|
||||
storage_service_properties: StorageServiceProperties object.
|
||||
'''
|
||||
_validate_not_none('storage_service_properties',
|
||||
storage_service_properties)
|
||||
request = HTTPRequest()
|
||||
request.method = 'PUT'
|
||||
request.host = self._get_host()
|
||||
request.path = '/?restype=service&comp=properties'
|
||||
request.body = _get_request_body(
|
||||
_convert_class_to_xml(storage_service_properties))
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_table_header(request)
|
||||
response = self._perform_request(request)
|
||||
|
||||
return _parse_response_for_dict(response)
|
||||
|
||||
def query_tables(self, table_name=None, top=None, next_table_name=None):
|
||||
'''
|
||||
Returns a list of tables under the specified account.
|
||||
|
||||
table_name: Optional. The specific table to query.
|
||||
top: Optional. Maximum number of tables to return.
|
||||
next_table_name:
|
||||
Optional. When top is used, the next table name is stored in
|
||||
result.x_ms_continuation['NextTableName']
|
||||
'''
|
||||
request = HTTPRequest()
|
||||
request.method = 'GET'
|
||||
request.host = self._get_host()
|
||||
if table_name is not None:
|
||||
uri_part_table_name = "('" + table_name + "')"
|
||||
else:
|
||||
uri_part_table_name = ""
|
||||
request.path = '/Tables' + uri_part_table_name + ''
|
||||
request.query = [
|
||||
('$top', _int_or_none(top)),
|
||||
('NextTableName', _str_or_none(next_table_name))
|
||||
]
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_table_header(request)
|
||||
response = self._perform_request(request)
|
||||
|
||||
return _convert_response_to_feeds(response, _convert_xml_to_table)
|
||||
|
||||
def create_table(self, table, fail_on_exist=False):
|
||||
'''
|
||||
Creates a new table in the storage account.
|
||||
|
||||
table:
|
||||
Name of the table to create. Table name may contain only
|
||||
alphanumeric characters and cannot begin with a numeric character.
|
||||
It is case-insensitive and must be from 3 to 63 characters long.
|
||||
fail_on_exist: Specify whether throw exception when table exists.
|
||||
'''
|
||||
_validate_not_none('table', table)
|
||||
request = HTTPRequest()
|
||||
request.method = 'POST'
|
||||
request.host = self._get_host()
|
||||
request.path = '/Tables'
|
||||
request.body = _get_request_body(_convert_table_to_xml(table))
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_table_header(request)
|
||||
if not fail_on_exist:
|
||||
try:
|
||||
self._perform_request(request)
|
||||
return True
|
||||
except WindowsAzureError as ex:
|
||||
_dont_fail_on_exist(ex)
|
||||
return False
|
||||
else:
|
||||
self._perform_request(request)
|
||||
return True
|
||||
|
||||
def delete_table(self, table_name, fail_not_exist=False):
|
||||
'''
|
||||
table_name: Name of the table to delete.
|
||||
fail_not_exist:
|
||||
Specify whether throw exception when table doesn't exist.
|
||||
'''
|
||||
_validate_not_none('table_name', table_name)
|
||||
request = HTTPRequest()
|
||||
request.method = 'DELETE'
|
||||
request.host = self._get_host()
|
||||
request.path = '/Tables(\'' + _str(table_name) + '\')'
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_table_header(request)
|
||||
if not fail_not_exist:
|
||||
try:
|
||||
self._perform_request(request)
|
||||
return True
|
||||
except WindowsAzureError as ex:
|
||||
_dont_fail_not_exist(ex)
|
||||
return False
|
||||
else:
|
||||
self._perform_request(request)
|
||||
return True
|
||||
|
||||
def get_entity(self, table_name, partition_key, row_key, select=''):
|
||||
'''
|
||||
Get an entity in a table; includes the $select options.
|
||||
|
||||
partition_key: PartitionKey of the entity.
|
||||
row_key: RowKey of the entity.
|
||||
select: Property names to select.
|
||||
'''
|
||||
_validate_not_none('table_name', table_name)
|
||||
_validate_not_none('partition_key', partition_key)
|
||||
_validate_not_none('row_key', row_key)
|
||||
_validate_not_none('select', select)
|
||||
request = HTTPRequest()
|
||||
request.method = 'GET'
|
||||
request.host = self._get_host()
|
||||
request.path = '/' + _str(table_name) + \
|
||||
'(PartitionKey=\'' + _str(partition_key) + \
|
||||
'\',RowKey=\'' + \
|
||||
_str(row_key) + '\')?$select=' + \
|
||||
_str(select) + ''
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_table_header(request)
|
||||
response = self._perform_request(request)
|
||||
|
||||
return _convert_response_to_entity(response)
|
||||
|
||||
def query_entities(self, table_name, filter=None, select=None, top=None,
|
||||
next_partition_key=None, next_row_key=None):
|
||||
'''
|
||||
Get entities in a table; includes the $filter and $select options.
|
||||
|
||||
table_name: Table to query.
|
||||
filter:
|
||||
Optional. Filter as described at
|
||||
http://msdn.microsoft.com/en-us/library/windowsazure/dd894031.aspx
|
||||
select: Optional. Property names to select from the entities.
|
||||
top: Optional. Maximum number of entities to return.
|
||||
next_partition_key:
|
||||
Optional. When top is used, the next partition key is stored in
|
||||
result.x_ms_continuation['NextPartitionKey']
|
||||
next_row_key:
|
||||
Optional. When top is used, the next partition key is stored in
|
||||
result.x_ms_continuation['NextRowKey']
|
||||
'''
|
||||
_validate_not_none('table_name', table_name)
|
||||
request = HTTPRequest()
|
||||
request.method = 'GET'
|
||||
request.host = self._get_host()
|
||||
request.path = '/' + _str(table_name) + '()'
|
||||
request.query = [
|
||||
('$filter', _str_or_none(filter)),
|
||||
('$select', _str_or_none(select)),
|
||||
('$top', _int_or_none(top)),
|
||||
('NextPartitionKey', _str_or_none(next_partition_key)),
|
||||
('NextRowKey', _str_or_none(next_row_key))
|
||||
]
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_table_header(request)
|
||||
response = self._perform_request(request)
|
||||
|
||||
return _convert_response_to_feeds(response, _convert_xml_to_entity)
|
||||
|
||||
def insert_entity(self, table_name, entity,
|
||||
content_type='application/atom+xml'):
|
||||
'''
|
||||
Inserts a new entity into a table.
|
||||
|
||||
table_name: Table name.
|
||||
entity:
|
||||
Required. The entity object to insert. Could be a dict format or
|
||||
entity object.
|
||||
content_type: Required. Must be set to application/atom+xml
|
||||
'''
|
||||
_validate_not_none('table_name', table_name)
|
||||
_validate_not_none('entity', entity)
|
||||
_validate_not_none('content_type', content_type)
|
||||
request = HTTPRequest()
|
||||
request.method = 'POST'
|
||||
request.host = self._get_host()
|
||||
request.path = '/' + _str(table_name) + ''
|
||||
request.headers = [('Content-Type', _str_or_none(content_type))]
|
||||
request.body = _get_request_body(_convert_entity_to_xml(entity))
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_table_header(request)
|
||||
response = self._perform_request(request)
|
||||
|
||||
return _convert_response_to_entity(response)
|
||||
|
||||
def update_entity(self, table_name, partition_key, row_key, entity,
|
||||
content_type='application/atom+xml', if_match='*'):
|
||||
'''
|
||||
Updates an existing entity in a table. The Update Entity operation
|
||||
replaces the entire entity and can be used to remove properties.
|
||||
|
||||
table_name: Table name.
|
||||
partition_key: PartitionKey of the entity.
|
||||
row_key: RowKey of the entity.
|
||||
entity:
|
||||
Required. The entity object to insert. Could be a dict format or
|
||||
entity object.
|
||||
content_type: Required. Must be set to application/atom+xml
|
||||
if_match:
|
||||
Optional. Specifies the condition for which the merge should be
|
||||
performed. To force an unconditional merge, set to the wildcard
|
||||
character (*).
|
||||
'''
|
||||
_validate_not_none('table_name', table_name)
|
||||
_validate_not_none('partition_key', partition_key)
|
||||
_validate_not_none('row_key', row_key)
|
||||
_validate_not_none('entity', entity)
|
||||
_validate_not_none('content_type', content_type)
|
||||
request = HTTPRequest()
|
||||
request.method = 'PUT'
|
||||
request.host = self._get_host()
|
||||
request.path = '/' + \
|
||||
_str(table_name) + '(PartitionKey=\'' + \
|
||||
_str(partition_key) + '\',RowKey=\'' + _str(row_key) + '\')'
|
||||
request.headers = [
|
||||
('Content-Type', _str_or_none(content_type)),
|
||||
('If-Match', _str_or_none(if_match))
|
||||
]
|
||||
request.body = _get_request_body(_convert_entity_to_xml(entity))
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_table_header(request)
|
||||
response = self._perform_request(request)
|
||||
|
||||
return _parse_response_for_dict_filter(response, filter=['etag'])
|
||||
|
||||
def merge_entity(self, table_name, partition_key, row_key, entity,
|
||||
content_type='application/atom+xml', if_match='*'):
|
||||
'''
|
||||
Updates an existing entity by updating the entity's properties. This
|
||||
operation does not replace the existing entity as the Update Entity
|
||||
operation does.
|
||||
|
||||
table_name: Table name.
|
||||
partition_key: PartitionKey of the entity.
|
||||
row_key: RowKey of the entity.
|
||||
entity:
|
||||
Required. The entity object to insert. Can be a dict format or
|
||||
entity object.
|
||||
content_type: Required. Must be set to application/atom+xml
|
||||
if_match:
|
||||
Optional. Specifies the condition for which the merge should be
|
||||
performed. To force an unconditional merge, set to the wildcard
|
||||
character (*).
|
||||
'''
|
||||
_validate_not_none('table_name', table_name)
|
||||
_validate_not_none('partition_key', partition_key)
|
||||
_validate_not_none('row_key', row_key)
|
||||
_validate_not_none('entity', entity)
|
||||
_validate_not_none('content_type', content_type)
|
||||
request = HTTPRequest()
|
||||
request.method = 'MERGE'
|
||||
request.host = self._get_host()
|
||||
request.path = '/' + \
|
||||
_str(table_name) + '(PartitionKey=\'' + \
|
||||
_str(partition_key) + '\',RowKey=\'' + _str(row_key) + '\')'
|
||||
request.headers = [
|
||||
('Content-Type', _str_or_none(content_type)),
|
||||
('If-Match', _str_or_none(if_match))
|
||||
]
|
||||
request.body = _get_request_body(_convert_entity_to_xml(entity))
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_table_header(request)
|
||||
response = self._perform_request(request)
|
||||
|
||||
return _parse_response_for_dict_filter(response, filter=['etag'])
|
||||
|
||||
def delete_entity(self, table_name, partition_key, row_key,
|
||||
content_type='application/atom+xml', if_match='*'):
|
||||
'''
|
||||
Deletes an existing entity in a table.
|
||||
|
||||
table_name: Table name.
|
||||
partition_key: PartitionKey of the entity.
|
||||
row_key: RowKey of the entity.
|
||||
content_type: Required. Must be set to application/atom+xml
|
||||
if_match:
|
||||
Optional. Specifies the condition for which the delete should be
|
||||
performed. To force an unconditional delete, set to the wildcard
|
||||
character (*).
|
||||
'''
|
||||
_validate_not_none('table_name', table_name)
|
||||
_validate_not_none('partition_key', partition_key)
|
||||
_validate_not_none('row_key', row_key)
|
||||
_validate_not_none('content_type', content_type)
|
||||
_validate_not_none('if_match', if_match)
|
||||
request = HTTPRequest()
|
||||
request.method = 'DELETE'
|
||||
request.host = self._get_host()
|
||||
request.path = '/' + \
|
||||
_str(table_name) + '(PartitionKey=\'' + \
|
||||
_str(partition_key) + '\',RowKey=\'' + _str(row_key) + '\')'
|
||||
request.headers = [
|
||||
('Content-Type', _str_or_none(content_type)),
|
||||
('If-Match', _str_or_none(if_match))
|
||||
]
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_table_header(request)
|
||||
self._perform_request(request)
|
||||
|
||||
def insert_or_replace_entity(self, table_name, partition_key, row_key,
|
||||
entity, content_type='application/atom+xml'):
|
||||
'''
|
||||
Replaces an existing entity or inserts a new entity if it does not
|
||||
exist in the table. Because this operation can insert or update an
|
||||
entity, it is also known as an "upsert" operation.
|
||||
|
||||
table_name: Table name.
|
||||
partition_key: PartitionKey of the entity.
|
||||
row_key: RowKey of the entity.
|
||||
entity:
|
||||
Required. The entity object to insert. Could be a dict format or
|
||||
entity object.
|
||||
content_type: Required. Must be set to application/atom+xml
|
||||
'''
|
||||
_validate_not_none('table_name', table_name)
|
||||
_validate_not_none('partition_key', partition_key)
|
||||
_validate_not_none('row_key', row_key)
|
||||
_validate_not_none('entity', entity)
|
||||
_validate_not_none('content_type', content_type)
|
||||
request = HTTPRequest()
|
||||
request.method = 'PUT'
|
||||
request.host = self._get_host()
|
||||
request.path = '/' + \
|
||||
_str(table_name) + '(PartitionKey=\'' + \
|
||||
_str(partition_key) + '\',RowKey=\'' + _str(row_key) + '\')'
|
||||
request.headers = [('Content-Type', _str_or_none(content_type))]
|
||||
request.body = _get_request_body(_convert_entity_to_xml(entity))
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_table_header(request)
|
||||
response = self._perform_request(request)
|
||||
|
||||
return _parse_response_for_dict_filter(response, filter=['etag'])
|
||||
|
||||
def insert_or_merge_entity(self, table_name, partition_key, row_key,
|
||||
entity, content_type='application/atom+xml'):
|
||||
'''
|
||||
Merges an existing entity or inserts a new entity if it does not exist
|
||||
in the table. Because this operation can insert or update an entity,
|
||||
it is also known as an "upsert" operation.
|
||||
|
||||
table_name: Table name.
|
||||
partition_key: PartitionKey of the entity.
|
||||
row_key: RowKey of the entity.
|
||||
entity:
|
||||
Required. The entity object to insert. Could be a dict format or
|
||||
entity object.
|
||||
content_type: Required. Must be set to application/atom+xml
|
||||
'''
|
||||
_validate_not_none('table_name', table_name)
|
||||
_validate_not_none('partition_key', partition_key)
|
||||
_validate_not_none('row_key', row_key)
|
||||
_validate_not_none('entity', entity)
|
||||
_validate_not_none('content_type', content_type)
|
||||
request = HTTPRequest()
|
||||
request.method = 'MERGE'
|
||||
request.host = self._get_host()
|
||||
request.path = '/' + \
|
||||
_str(table_name) + '(PartitionKey=\'' + \
|
||||
_str(partition_key) + '\',RowKey=\'' + _str(row_key) + '\')'
|
||||
request.headers = [('Content-Type', _str_or_none(content_type))]
|
||||
request.body = _get_request_body(_convert_entity_to_xml(entity))
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_table_header(request)
|
||||
response = self._perform_request(request)
|
||||
|
||||
return _parse_response_for_dict_filter(response, filter=['etag'])
|
||||
|
||||
def _perform_request_worker(self, request):
|
||||
auth = _sign_storage_table_request(request,
|
||||
self.account_name,
|
||||
self.account_key)
|
||||
request.headers.append(('Authorization', auth))
|
||||
return self._httpclient.perform_request(request)
|
||||
#-------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft. 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.
|
||||
# You may obtain a copy of the License at
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License 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.
|
||||
#--------------------------------------------------------------------------
|
||||
from azure import (
|
||||
WindowsAzureError,
|
||||
TABLE_SERVICE_HOST_BASE,
|
||||
DEV_TABLE_HOST,
|
||||
_convert_class_to_xml,
|
||||
_convert_response_to_feeds,
|
||||
_dont_fail_not_exist,
|
||||
_dont_fail_on_exist,
|
||||
_get_request_body,
|
||||
_int_or_none,
|
||||
_parse_response,
|
||||
_parse_response_for_dict,
|
||||
_parse_response_for_dict_filter,
|
||||
_str,
|
||||
_str_or_none,
|
||||
_update_request_uri_query_local_storage,
|
||||
_validate_not_none,
|
||||
)
|
||||
from azure.http import HTTPRequest
|
||||
from azure.http.batchclient import _BatchClient
|
||||
from azure.storage import (
|
||||
StorageServiceProperties,
|
||||
_convert_entity_to_xml,
|
||||
_convert_response_to_entity,
|
||||
_convert_table_to_xml,
|
||||
_convert_xml_to_entity,
|
||||
_convert_xml_to_table,
|
||||
_sign_storage_table_request,
|
||||
_update_storage_table_header,
|
||||
)
|
||||
from azure.storage.storageclient import _StorageClient
|
||||
|
||||
|
||||
class TableService(_StorageClient):
|
||||
|
||||
'''
|
||||
This is the main class managing Table resources.
|
||||
'''
|
||||
|
||||
def __init__(self, account_name=None, account_key=None, protocol='https',
|
||||
host_base=TABLE_SERVICE_HOST_BASE, dev_host=DEV_TABLE_HOST):
|
||||
'''
|
||||
account_name: your storage account name, required for all operations.
|
||||
account_key: your storage account key, required for all operations.
|
||||
protocol: Optional. Protocol. Defaults to http.
|
||||
host_base:
|
||||
Optional. Live host base url. Defaults to Azure url. Override this
|
||||
for on-premise.
|
||||
dev_host: Optional. Dev host url. Defaults to localhost.
|
||||
'''
|
||||
super(TableService, self).__init__(
|
||||
account_name, account_key, protocol, host_base, dev_host)
|
||||
|
||||
def begin_batch(self):
|
||||
if self._batchclient is None:
|
||||
self._batchclient = _BatchClient(
|
||||
service_instance=self,
|
||||
account_key=self.account_key,
|
||||
account_name=self.account_name)
|
||||
return self._batchclient.begin_batch()
|
||||
|
||||
def commit_batch(self):
|
||||
try:
|
||||
ret = self._batchclient.commit_batch()
|
||||
finally:
|
||||
self._batchclient = None
|
||||
return ret
|
||||
|
||||
def cancel_batch(self):
|
||||
self._batchclient = None
|
||||
|
||||
def get_table_service_properties(self):
|
||||
'''
|
||||
Gets the properties of a storage account's Table service, including
|
||||
Windows Azure Storage Analytics.
|
||||
'''
|
||||
request = HTTPRequest()
|
||||
request.method = 'GET'
|
||||
request.host = self._get_host()
|
||||
request.path = '/?restype=service&comp=properties'
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_table_header(request)
|
||||
response = self._perform_request(request)
|
||||
|
||||
return _parse_response(response, StorageServiceProperties)
|
||||
|
||||
def set_table_service_properties(self, storage_service_properties):
|
||||
'''
|
||||
Sets the properties of a storage account's Table Service, including
|
||||
Windows Azure Storage Analytics.
|
||||
|
||||
storage_service_properties: StorageServiceProperties object.
|
||||
'''
|
||||
_validate_not_none('storage_service_properties',
|
||||
storage_service_properties)
|
||||
request = HTTPRequest()
|
||||
request.method = 'PUT'
|
||||
request.host = self._get_host()
|
||||
request.path = '/?restype=service&comp=properties'
|
||||
request.body = _get_request_body(
|
||||
_convert_class_to_xml(storage_service_properties))
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_table_header(request)
|
||||
response = self._perform_request(request)
|
||||
|
||||
return _parse_response_for_dict(response)
|
||||
|
||||
def query_tables(self, table_name=None, top=None, next_table_name=None):
|
||||
'''
|
||||
Returns a list of tables under the specified account.
|
||||
|
||||
table_name: Optional. The specific table to query.
|
||||
top: Optional. Maximum number of tables to return.
|
||||
next_table_name:
|
||||
Optional. When top is used, the next table name is stored in
|
||||
result.x_ms_continuation['NextTableName']
|
||||
'''
|
||||
request = HTTPRequest()
|
||||
request.method = 'GET'
|
||||
request.host = self._get_host()
|
||||
if table_name is not None:
|
||||
uri_part_table_name = "('" + table_name + "')"
|
||||
else:
|
||||
uri_part_table_name = ""
|
||||
request.path = '/Tables' + uri_part_table_name + ''
|
||||
request.query = [
|
||||
('$top', _int_or_none(top)),
|
||||
('NextTableName', _str_or_none(next_table_name))
|
||||
]
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_table_header(request)
|
||||
response = self._perform_request(request)
|
||||
|
||||
return _convert_response_to_feeds(response, _convert_xml_to_table)
|
||||
|
||||
def create_table(self, table, fail_on_exist=False):
|
||||
'''
|
||||
Creates a new table in the storage account.
|
||||
|
||||
table:
|
||||
Name of the table to create. Table name may contain only
|
||||
alphanumeric characters and cannot begin with a numeric character.
|
||||
It is case-insensitive and must be from 3 to 63 characters long.
|
||||
fail_on_exist: Specify whether throw exception when table exists.
|
||||
'''
|
||||
_validate_not_none('table', table)
|
||||
request = HTTPRequest()
|
||||
request.method = 'POST'
|
||||
request.host = self._get_host()
|
||||
request.path = '/Tables'
|
||||
request.body = _get_request_body(_convert_table_to_xml(table))
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_table_header(request)
|
||||
if not fail_on_exist:
|
||||
try:
|
||||
self._perform_request(request)
|
||||
return True
|
||||
except WindowsAzureError as ex:
|
||||
_dont_fail_on_exist(ex)
|
||||
return False
|
||||
else:
|
||||
self._perform_request(request)
|
||||
return True
|
||||
|
||||
def delete_table(self, table_name, fail_not_exist=False):
|
||||
'''
|
||||
table_name: Name of the table to delete.
|
||||
fail_not_exist:
|
||||
Specify whether throw exception when table doesn't exist.
|
||||
'''
|
||||
_validate_not_none('table_name', table_name)
|
||||
request = HTTPRequest()
|
||||
request.method = 'DELETE'
|
||||
request.host = self._get_host()
|
||||
request.path = '/Tables(\'' + _str(table_name) + '\')'
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_table_header(request)
|
||||
if not fail_not_exist:
|
||||
try:
|
||||
self._perform_request(request)
|
||||
return True
|
||||
except WindowsAzureError as ex:
|
||||
_dont_fail_not_exist(ex)
|
||||
return False
|
||||
else:
|
||||
self._perform_request(request)
|
||||
return True
|
||||
|
||||
def get_entity(self, table_name, partition_key, row_key, select=''):
|
||||
'''
|
||||
Get an entity in a table; includes the $select options.
|
||||
|
||||
partition_key: PartitionKey of the entity.
|
||||
row_key: RowKey of the entity.
|
||||
select: Property names to select.
|
||||
'''
|
||||
_validate_not_none('table_name', table_name)
|
||||
_validate_not_none('partition_key', partition_key)
|
||||
_validate_not_none('row_key', row_key)
|
||||
_validate_not_none('select', select)
|
||||
request = HTTPRequest()
|
||||
request.method = 'GET'
|
||||
request.host = self._get_host()
|
||||
request.path = '/' + _str(table_name) + \
|
||||
'(PartitionKey=\'' + _str(partition_key) + \
|
||||
'\',RowKey=\'' + \
|
||||
_str(row_key) + '\')?$select=' + \
|
||||
_str(select) + ''
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_table_header(request)
|
||||
response = self._perform_request(request)
|
||||
|
||||
return _convert_response_to_entity(response)
|
||||
|
||||
def query_entities(self, table_name, filter=None, select=None, top=None,
|
||||
next_partition_key=None, next_row_key=None):
|
||||
'''
|
||||
Get entities in a table; includes the $filter and $select options.
|
||||
|
||||
table_name: Table to query.
|
||||
filter:
|
||||
Optional. Filter as described at
|
||||
http://msdn.microsoft.com/en-us/library/windowsazure/dd894031.aspx
|
||||
select: Optional. Property names to select from the entities.
|
||||
top: Optional. Maximum number of entities to return.
|
||||
next_partition_key:
|
||||
Optional. When top is used, the next partition key is stored in
|
||||
result.x_ms_continuation['NextPartitionKey']
|
||||
next_row_key:
|
||||
Optional. When top is used, the next partition key is stored in
|
||||
result.x_ms_continuation['NextRowKey']
|
||||
'''
|
||||
_validate_not_none('table_name', table_name)
|
||||
request = HTTPRequest()
|
||||
request.method = 'GET'
|
||||
request.host = self._get_host()
|
||||
request.path = '/' + _str(table_name) + '()'
|
||||
request.query = [
|
||||
('$filter', _str_or_none(filter)),
|
||||
('$select', _str_or_none(select)),
|
||||
('$top', _int_or_none(top)),
|
||||
('NextPartitionKey', _str_or_none(next_partition_key)),
|
||||
('NextRowKey', _str_or_none(next_row_key))
|
||||
]
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_table_header(request)
|
||||
response = self._perform_request(request)
|
||||
|
||||
return _convert_response_to_feeds(response, _convert_xml_to_entity)
|
||||
|
||||
def insert_entity(self, table_name, entity,
|
||||
content_type='application/atom+xml'):
|
||||
'''
|
||||
Inserts a new entity into a table.
|
||||
|
||||
table_name: Table name.
|
||||
entity:
|
||||
Required. The entity object to insert. Could be a dict format or
|
||||
entity object.
|
||||
content_type: Required. Must be set to application/atom+xml
|
||||
'''
|
||||
_validate_not_none('table_name', table_name)
|
||||
_validate_not_none('entity', entity)
|
||||
_validate_not_none('content_type', content_type)
|
||||
request = HTTPRequest()
|
||||
request.method = 'POST'
|
||||
request.host = self._get_host()
|
||||
request.path = '/' + _str(table_name) + ''
|
||||
request.headers = [('Content-Type', _str_or_none(content_type))]
|
||||
request.body = _get_request_body(_convert_entity_to_xml(entity))
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_table_header(request)
|
||||
response = self._perform_request(request)
|
||||
|
||||
return _convert_response_to_entity(response)
|
||||
|
||||
def update_entity(self, table_name, partition_key, row_key, entity,
|
||||
content_type='application/atom+xml', if_match='*'):
|
||||
'''
|
||||
Updates an existing entity in a table. The Update Entity operation
|
||||
replaces the entire entity and can be used to remove properties.
|
||||
|
||||
table_name: Table name.
|
||||
partition_key: PartitionKey of the entity.
|
||||
row_key: RowKey of the entity.
|
||||
entity:
|
||||
Required. The entity object to insert. Could be a dict format or
|
||||
entity object.
|
||||
content_type: Required. Must be set to application/atom+xml
|
||||
if_match:
|
||||
Optional. Specifies the condition for which the merge should be
|
||||
performed. To force an unconditional merge, set to the wildcard
|
||||
character (*).
|
||||
'''
|
||||
_validate_not_none('table_name', table_name)
|
||||
_validate_not_none('partition_key', partition_key)
|
||||
_validate_not_none('row_key', row_key)
|
||||
_validate_not_none('entity', entity)
|
||||
_validate_not_none('content_type', content_type)
|
||||
request = HTTPRequest()
|
||||
request.method = 'PUT'
|
||||
request.host = self._get_host()
|
||||
request.path = '/' + \
|
||||
_str(table_name) + '(PartitionKey=\'' + \
|
||||
_str(partition_key) + '\',RowKey=\'' + _str(row_key) + '\')'
|
||||
request.headers = [
|
||||
('Content-Type', _str_or_none(content_type)),
|
||||
('If-Match', _str_or_none(if_match))
|
||||
]
|
||||
request.body = _get_request_body(_convert_entity_to_xml(entity))
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_table_header(request)
|
||||
response = self._perform_request(request)
|
||||
|
||||
return _parse_response_for_dict_filter(response, filter=['etag'])
|
||||
|
||||
def merge_entity(self, table_name, partition_key, row_key, entity,
|
||||
content_type='application/atom+xml', if_match='*'):
|
||||
'''
|
||||
Updates an existing entity by updating the entity's properties. This
|
||||
operation does not replace the existing entity as the Update Entity
|
||||
operation does.
|
||||
|
||||
table_name: Table name.
|
||||
partition_key: PartitionKey of the entity.
|
||||
row_key: RowKey of the entity.
|
||||
entity:
|
||||
Required. The entity object to insert. Can be a dict format or
|
||||
entity object.
|
||||
content_type: Required. Must be set to application/atom+xml
|
||||
if_match:
|
||||
Optional. Specifies the condition for which the merge should be
|
||||
performed. To force an unconditional merge, set to the wildcard
|
||||
character (*).
|
||||
'''
|
||||
_validate_not_none('table_name', table_name)
|
||||
_validate_not_none('partition_key', partition_key)
|
||||
_validate_not_none('row_key', row_key)
|
||||
_validate_not_none('entity', entity)
|
||||
_validate_not_none('content_type', content_type)
|
||||
request = HTTPRequest()
|
||||
request.method = 'MERGE'
|
||||
request.host = self._get_host()
|
||||
request.path = '/' + \
|
||||
_str(table_name) + '(PartitionKey=\'' + \
|
||||
_str(partition_key) + '\',RowKey=\'' + _str(row_key) + '\')'
|
||||
request.headers = [
|
||||
('Content-Type', _str_or_none(content_type)),
|
||||
('If-Match', _str_or_none(if_match))
|
||||
]
|
||||
request.body = _get_request_body(_convert_entity_to_xml(entity))
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_table_header(request)
|
||||
response = self._perform_request(request)
|
||||
|
||||
return _parse_response_for_dict_filter(response, filter=['etag'])
|
||||
|
||||
def delete_entity(self, table_name, partition_key, row_key,
|
||||
content_type='application/atom+xml', if_match='*'):
|
||||
'''
|
||||
Deletes an existing entity in a table.
|
||||
|
||||
table_name: Table name.
|
||||
partition_key: PartitionKey of the entity.
|
||||
row_key: RowKey of the entity.
|
||||
content_type: Required. Must be set to application/atom+xml
|
||||
if_match:
|
||||
Optional. Specifies the condition for which the delete should be
|
||||
performed. To force an unconditional delete, set to the wildcard
|
||||
character (*).
|
||||
'''
|
||||
_validate_not_none('table_name', table_name)
|
||||
_validate_not_none('partition_key', partition_key)
|
||||
_validate_not_none('row_key', row_key)
|
||||
_validate_not_none('content_type', content_type)
|
||||
_validate_not_none('if_match', if_match)
|
||||
request = HTTPRequest()
|
||||
request.method = 'DELETE'
|
||||
request.host = self._get_host()
|
||||
request.path = '/' + \
|
||||
_str(table_name) + '(PartitionKey=\'' + \
|
||||
_str(partition_key) + '\',RowKey=\'' + _str(row_key) + '\')'
|
||||
request.headers = [
|
||||
('Content-Type', _str_or_none(content_type)),
|
||||
('If-Match', _str_or_none(if_match))
|
||||
]
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_table_header(request)
|
||||
self._perform_request(request)
|
||||
|
||||
def insert_or_replace_entity(self, table_name, partition_key, row_key,
|
||||
entity, content_type='application/atom+xml'):
|
||||
'''
|
||||
Replaces an existing entity or inserts a new entity if it does not
|
||||
exist in the table. Because this operation can insert or update an
|
||||
entity, it is also known as an "upsert" operation.
|
||||
|
||||
table_name: Table name.
|
||||
partition_key: PartitionKey of the entity.
|
||||
row_key: RowKey of the entity.
|
||||
entity:
|
||||
Required. The entity object to insert. Could be a dict format or
|
||||
entity object.
|
||||
content_type: Required. Must be set to application/atom+xml
|
||||
'''
|
||||
_validate_not_none('table_name', table_name)
|
||||
_validate_not_none('partition_key', partition_key)
|
||||
_validate_not_none('row_key', row_key)
|
||||
_validate_not_none('entity', entity)
|
||||
_validate_not_none('content_type', content_type)
|
||||
request = HTTPRequest()
|
||||
request.method = 'PUT'
|
||||
request.host = self._get_host()
|
||||
request.path = '/' + \
|
||||
_str(table_name) + '(PartitionKey=\'' + \
|
||||
_str(partition_key) + '\',RowKey=\'' + _str(row_key) + '\')'
|
||||
request.headers = [('Content-Type', _str_or_none(content_type))]
|
||||
request.body = _get_request_body(_convert_entity_to_xml(entity))
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_table_header(request)
|
||||
response = self._perform_request(request)
|
||||
|
||||
return _parse_response_for_dict_filter(response, filter=['etag'])
|
||||
|
||||
def insert_or_merge_entity(self, table_name, partition_key, row_key,
|
||||
entity, content_type='application/atom+xml'):
|
||||
'''
|
||||
Merges an existing entity or inserts a new entity if it does not exist
|
||||
in the table. Because this operation can insert or update an entity,
|
||||
it is also known as an "upsert" operation.
|
||||
|
||||
table_name: Table name.
|
||||
partition_key: PartitionKey of the entity.
|
||||
row_key: RowKey of the entity.
|
||||
entity:
|
||||
Required. The entity object to insert. Could be a dict format or
|
||||
entity object.
|
||||
content_type: Required. Must be set to application/atom+xml
|
||||
'''
|
||||
_validate_not_none('table_name', table_name)
|
||||
_validate_not_none('partition_key', partition_key)
|
||||
_validate_not_none('row_key', row_key)
|
||||
_validate_not_none('entity', entity)
|
||||
_validate_not_none('content_type', content_type)
|
||||
request = HTTPRequest()
|
||||
request.method = 'MERGE'
|
||||
request.host = self._get_host()
|
||||
request.path = '/' + \
|
||||
_str(table_name) + '(PartitionKey=\'' + \
|
||||
_str(partition_key) + '\',RowKey=\'' + _str(row_key) + '\')'
|
||||
request.headers = [('Content-Type', _str_or_none(content_type))]
|
||||
request.body = _get_request_body(_convert_entity_to_xml(entity))
|
||||
request.path, request.query = _update_request_uri_query_local_storage(
|
||||
request, self.use_local_storage)
|
||||
request.headers = _update_storage_table_header(request)
|
||||
response = self._perform_request(request)
|
||||
|
||||
return _parse_response_for_dict_filter(response, filter=['etag'])
|
||||
|
||||
def _perform_request_worker(self, request):
|
||||
auth = _sign_storage_table_request(request,
|
||||
self.account_name,
|
||||
self.account_key)
|
||||
request.headers.append(('Authorization', auth))
|
||||
return self._httpclient.perform_request(request)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user