1# Licensed to the Apache Software Foundation (ASF) under one or more
2# contributor license agreements.  See the NOTICE file distributed with
3# this work for additional information regarding copyright ownership.
4# The ASF licenses this file to You under the Apache License, Version 2.0
5# (the "License"); you may not use this file except in compliance with
6# the License.  You may obtain a copy of the License at
7#
8#     http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16"""
17Driver for Microsoft Azure Virtual Machines service.
18
19http://azure.microsoft.com/en-us/services/virtual-machines/
20"""
21
22import re
23import time
24import collections
25import random
26import sys
27import copy
28import base64
29
30from datetime import datetime
31from xml.dom import minidom
32from xml.sax.saxutils import escape as xml_escape
33
34from libcloud.utils.py3 import ET
35from libcloud.common.azure import AzureServiceManagementConnection
36from libcloud.common.azure import AzureRedirectException
37from libcloud.compute.providers import Provider
38from libcloud.compute.base import Node, NodeDriver, NodeLocation, NodeSize
39from libcloud.compute.base import NodeImage, StorageVolume
40from libcloud.compute.types import NodeState
41from libcloud.common.types import LibcloudError
42from libcloud.utils.py3 import _real_unicode
43from libcloud.utils.py3 import httplib
44from libcloud.utils.py3 import urlparse
45from libcloud.utils.py3 import ensure_string
46from libcloud.utils.py3 import urlquote as url_quote
47from libcloud.utils.misc import ReprMixin
48
49HTTPSConnection = httplib.HTTPSConnection
50
51if sys.version_info < (3,):
52    _unicode_type = unicode  # NOQA  pylint: disable=undefined-variable
53
54    def _str(value):
55        if isinstance(value, unicode):  # NOQA  pylint: disable=undefined-variable
56            return value.encode('utf-8')
57
58        return str(value)
59else:
60    _str = str
61    _unicode_type = str
62
63
64AZURE_SERVICE_MANAGEMENT_HOST = 'management.core.windows.net'
65X_MS_VERSION = '2013-08-01'
66
67WINDOWS_SERVER_REGEX = re.compile(
68    r'Win|SQL|SharePoint|Visual|Dynamics|DynGP|BizTalk'
69)
70
71"""
72Sizes must be hardcoded because Microsoft doesn't provide an API to fetch them
73From http://msdn.microsoft.com/en-us/library/windowsazure/dn197896.aspx
74
75Prices are for Linux instances in East US data center. To see what pricing will
76actually be, visit:
77http://azure.microsoft.com/en-gb/pricing/details/virtual-machines/
78"""
79AZURE_COMPUTE_INSTANCE_TYPES = {
80    'A0': {
81        'id': 'ExtraSmall',
82        'name': 'Extra Small Instance',
83        'ram': 768,
84        'disk': 127,
85        'bandwidth': None,
86        'price': '0.0211',
87        'max_data_disks': 1,
88        'cores': 'Shared'
89    },
90    'A1': {
91        'id': 'Small',
92        'name': 'Small Instance',
93        'ram': 1792,
94        'disk': 127,
95        'bandwidth': None,
96        'price': '0.0633',
97        'max_data_disks': 2,
98        'cores': 1
99    },
100    'A2': {
101        'id': 'Medium',
102        'name': 'Medium Instance',
103        'ram': 3584,
104        'disk': 127,
105        'bandwidth': None,
106        'price': '0.1266',
107        'max_data_disks': 4,
108        'cores': 2
109    },
110    'A3': {
111        'id': 'Large',
112        'name': 'Large Instance',
113        'ram': 7168,
114        'disk': 127,
115        'bandwidth': None,
116        'price': '0.2531',
117        'max_data_disks': 8,
118        'cores': 4
119    },
120    'A4': {
121        'id': 'ExtraLarge',
122        'name': 'Extra Large Instance',
123        'ram': 14336,
124        'disk': 127,
125        'bandwidth': None,
126        'price': '0.5062',
127        'max_data_disks': 16,
128        'cores': 8
129    },
130    'A5': {
131        'id': 'A5',
132        'name': 'Memory Intensive Instance',
133        'ram': 14336,
134        'disk': 127,
135        'bandwidth': None,
136        'price': '0.2637',
137        'max_data_disks': 4,
138        'cores': 2
139    },
140    'A6': {
141        'id': 'A6',
142        'name': 'A6 Instance',
143        'ram': 28672,
144        'disk': 127,
145        'bandwidth': None,
146        'price': '0.5273',
147        'max_data_disks': 8,
148        'cores': 4
149    },
150    'A7': {
151        'id': 'A7',
152        'name': 'A7 Instance',
153        'ram': 57344,
154        'disk': 127,
155        'bandwidth': None,
156        'price': '1.0545',
157        'max_data_disks': 16,
158        'cores': 8
159    },
160    'A8': {
161        'id': 'A8',
162        'name': 'A8 Instance',
163        'ram': 57344,
164        'disk': 127,
165        'bandwidth': None,
166        'price': '2.0774',
167        'max_data_disks': 16,
168        'cores': 8
169    },
170    'A9': {
171        'id': 'A9',
172        'name': 'A9 Instance',
173        'ram': 114688,
174        'disk': 127,
175        'bandwidth': None,
176        'price': '4.7137',
177        'max_data_disks': 16,
178        'cores': 16
179    },
180    'A10': {
181        'id': 'A10',
182        'name': 'A10 Instance',
183        'ram': 57344,
184        'disk': 127,
185        'bandwidth': None,
186        'price': '1.2233',
187        'max_data_disks': 16,
188        'cores': 8
189    },
190    'A11': {
191        'id': 'A11',
192        'name': 'A11 Instance',
193        'ram': 114688,
194        'disk': 127,
195        'bandwidth': None,
196        'price': '2.1934',
197        'max_data_disks': 16,
198        'cores': 16
199    },
200    'D1': {
201        'id': 'Standard_D1',
202        'name': 'D1 Faster Compute Instance',
203        'ram': 3584,
204        'disk': 127,
205        'bandwidth': None,
206        'price': '0.0992',
207        'max_data_disks': 2,
208        'cores': 1
209    },
210    'D2': {
211        'id': 'Standard_D2',
212        'name': 'D2 Faster Compute Instance',
213        'ram': 7168,
214        'disk': 127,
215        'bandwidth': None,
216        'price': '0.1983',
217        'max_data_disks': 4,
218        'cores': 2
219    },
220    'D3': {
221        'id': 'Standard_D3',
222        'name': 'D3 Faster Compute Instance',
223        'ram': 14336,
224        'disk': 127,
225        'bandwidth': None,
226        'price': '0.3965',
227        'max_data_disks': 8,
228        'cores': 4
229    },
230    'D4': {
231        'id': 'Standard_D4',
232        'name': 'D4 Faster Compute Instance',
233        'ram': 28672,
234        'disk': 127,
235        'bandwidth': None,
236        'price': '0.793',
237        'max_data_disks': 16,
238        'cores': 8
239    },
240    'D11': {
241        'id': 'Standard_D11',
242        'name': 'D11 Faster Compute Instance',
243        'ram': 14336,
244        'disk': 127,
245        'bandwidth': None,
246        'price': '0.251',
247        'max_data_disks': 4,
248        'cores': 2
249    },
250    'D12': {
251        'id': 'Standard_D12',
252        'name': 'D12 Faster Compute Instance',
253        'ram': 28672,
254        'disk': 127,
255        'bandwidth': None,
256        'price': '0.502',
257        'max_data_disks': 8,
258        'cores': 4
259    },
260    'D13': {
261        'id': 'Standard_D13',
262        'name': 'D13 Faster Compute Instance',
263        'ram': 57344,
264        'disk': 127,
265        'bandwidth': None,
266        'price': '0.9038',
267        'max_data_disks': 16,
268        'cores': 8
269    },
270    'D14': {
271        'id': 'Standard_D14',
272        'name': 'D14 Faster Compute Instance',
273        'ram': 114688,
274        'disk': 127,
275        'bandwidth': None,
276        'price': '1.6261',
277        'max_data_disks': 32,
278        'cores': 16
279    }
280}
281
282_KNOWN_SERIALIZATION_XFORMS = {
283    'include_apis': 'IncludeAPIs',
284    'message_id': 'MessageId',
285    'content_md5': 'Content-MD5',
286    'last_modified': 'Last-Modified',
287    'cache_control': 'Cache-Control',
288    'account_admin_live_email_id': 'AccountAdminLiveEmailId',
289    'service_admin_live_email_id': 'ServiceAdminLiveEmailId',
290    'subscription_id': 'SubscriptionID',
291    'fqdn': 'FQDN',
292    'private_id': 'PrivateID',
293    'os_virtual_hard_disk': 'OSVirtualHardDisk',
294    'logical_disk_size_in_gb': 'LogicalDiskSizeInGB',
295    'logical_size_in_gb': 'LogicalSizeInGB',
296    'os': 'OS',
297    'persistent_vm_downtime_info': 'PersistentVMDowntimeInfo',
298    'copy_id': 'CopyId',
299    'os_disk_configuration': 'OSDiskConfiguration',
300    'is_dns_programmed': 'IsDnsProgrammed'
301}
302
303
304class AzureNodeDriver(NodeDriver):
305    connectionCls = AzureServiceManagementConnection
306    name = 'Azure Virtual machines'
307    website = 'http://azure.microsoft.com/en-us/services/virtual-machines/'
308    type = Provider.AZURE
309
310    _instance_types = AZURE_COMPUTE_INSTANCE_TYPES
311    _blob_url = ".blob.core.windows.net"
312    features = {'create_node': ['password']}
313    service_location = collections.namedtuple(
314        'service_location',
315        ['is_affinity_group', 'service_location']
316    )
317
318    NODE_STATE_MAP = {
319        'RoleStateUnknown': NodeState.UNKNOWN,
320        'CreatingVM': NodeState.PENDING,
321        'StartingVM': NodeState.PENDING,
322        'Provisioning': NodeState.PENDING,
323        'CreatingRole': NodeState.PENDING,
324        'StartingRole': NodeState.PENDING,
325        'ReadyRole': NodeState.RUNNING,
326        'BusyRole': NodeState.PENDING,
327        'StoppingRole': NodeState.PENDING,
328        'StoppingVM': NodeState.PENDING,
329        'DeletingVM': NodeState.PENDING,
330        'StoppedVM': NodeState.STOPPED,
331        'RestartingRole': NodeState.REBOOTING,
332        'CyclingRole': NodeState.TERMINATED,
333        'FailedStartingRole': NodeState.TERMINATED,
334        'FailedStartingVM': NodeState.TERMINATED,
335        'UnresponsiveRole': NodeState.TERMINATED,
336        'StoppedDeallocated': NodeState.TERMINATED,
337    }
338
339    def __init__(self, subscription_id=None, key_file=None, **kwargs):
340        """
341        subscription_id contains the Azure subscription id in the form of GUID
342        key_file contains the Azure X509 certificate in .pem form
343        """
344        self.subscription_id = subscription_id
345        self.key_file = key_file
346        self.follow_redirects = kwargs.get('follow_redirects', True)
347        super(AzureNodeDriver, self).__init__(
348            self.subscription_id,
349            self.key_file,
350            secure=True,
351            **kwargs
352        )
353
354    def list_sizes(self):
355        """
356        Lists all sizes
357
358        :rtype: ``list`` of :class:`NodeSize`
359        """
360        sizes = []
361
362        for _, values in self._instance_types.items():
363            node_size = self._to_node_size(copy.deepcopy(values))
364            sizes.append(node_size)
365
366        return sizes
367
368    def list_images(self, location=None):
369        """
370        Lists all images
371
372        :rtype: ``list`` of :class:`NodeImage`
373        """
374        data = self._perform_get(self._get_image_path(), Images)
375
376        custom_image_data = self._perform_get(
377            self._get_vmimage_path(),
378            VMImages
379        )
380
381        images = [self._to_image(i) for i in data]
382        images.extend(self._vm_to_image(j) for j in custom_image_data)
383
384        if location is not None:
385            images = [
386                image
387                for image in images
388                if location in image.extra["location"]
389            ]
390
391        return images
392
393    def list_locations(self):
394        """
395        Lists all locations
396
397        :rtype: ``list`` of :class:`NodeLocation`
398        """
399        data = self._perform_get(
400            '/' + self.subscription_id + '/locations',
401            Locations
402        )
403
404        return [self._to_location(location) for location in data]
405
406    def list_nodes(self, ex_cloud_service_name):
407        """
408        List all nodes
409
410        ex_cloud_service_name parameter is used to scope the request
411        to a specific Cloud Service. This is a required parameter as
412        nodes cannot exist outside of a Cloud Service nor be shared
413        between a Cloud Service within Azure.
414
415        :param      ex_cloud_service_name: Cloud Service name
416        :type       ex_cloud_service_name: ``str``
417
418        :rtype: ``list`` of :class:`Node`
419        """
420        response = self._perform_get(
421            self._get_hosted_service_path(ex_cloud_service_name) +
422            '?embed-detail=True',
423            None
424        )
425        self.raise_for_response(response, 200)
426
427        data = self._parse_response(response, HostedService)
428
429        vips = None
430
431        if (len(data.deployments) > 0 and
432                data.deployments[0].virtual_ips is not None):
433            vips = [vip.address for vip in data.deployments[0].virtual_ips]
434
435        try:
436            return [
437                self._to_node(n, ex_cloud_service_name, vips)
438                for n in data.deployments[0].role_instance_list
439            ]
440        except IndexError:
441            return []
442
443    def reboot_node(self, node, ex_cloud_service_name=None,
444                    ex_deployment_slot=None):
445        """
446        Reboots a node.
447
448        ex_cloud_service_name parameter is used to scope the request
449        to a specific Cloud Service. This is a required parameter as
450        nodes cannot exist outside of a Cloud Service nor be shared
451        between a Cloud Service within Azure.
452
453        :param      ex_cloud_service_name: Cloud Service name
454        :type       ex_cloud_service_name: ``str``
455
456        :param      ex_deployment_slot: Options are "production" (default)
457                                         or "Staging". (Optional)
458        :type       ex_deployment_slot: ``str``
459
460        :rtype: ``bool``
461        """
462        if ex_cloud_service_name is None:
463            if node.extra is not None:
464                ex_cloud_service_name = node.extra.get(
465                    'ex_cloud_service_name'
466                )
467
468        if not ex_cloud_service_name:
469            raise ValueError("ex_cloud_service_name is required.")
470
471        if not ex_deployment_slot:
472            ex_deployment_slot = "Production"
473
474        _deployment_name = self._get_deployment(
475            service_name=ex_cloud_service_name,
476            deployment_slot=ex_deployment_slot
477        ).name
478
479        try:
480            response = self._perform_post(
481                self._get_deployment_path_using_name(
482                    ex_cloud_service_name,
483                    _deployment_name
484                ) + '/roleinstances/' + _str(node.id) + '?comp=reboot',
485                ''
486            )
487
488            self.raise_for_response(response, 202)
489
490            if self._parse_response_for_async_op(response):
491                return True
492            else:
493                return False
494        except Exception:
495            return False
496
497    def list_volumes(self, node=None):
498        """
499        Lists volumes of the disks in the image repository that are
500        associated with the specified subscription.
501
502        Pass Node object to scope the list of volumes to a single
503        instance.
504
505        :rtype: ``list`` of :class:`StorageVolume`
506        """
507
508        data = self._perform_get(self._get_disk_path(), Disks)
509        volumes = [self._to_volume(volume=v, node=node) for v in data]
510        return volumes
511
512    def create_node(self, name, size, image, ex_cloud_service_name,
513                    ex_storage_service_name=None, ex_new_deployment=False,
514                    ex_deployment_slot="Production", ex_deployment_name=None,
515                    ex_admin_user_id="azureuser", ex_custom_data=None,
516                    ex_virtual_network_name=None, ex_network_config=None,
517                    auth=None, **kwargs):
518        """
519        Create Azure Virtual Machine
520
521        Reference: http://bit.ly/1fIsCb7
522        [www.windowsazure.com/en-us/documentation/]
523
524        We default to:
525
526        + 3389/TCP - RDP - 1st Microsoft instance.
527        + RANDOM/TCP - RDP - All succeeding Microsoft instances.
528
529        + 22/TCP - SSH - 1st Linux instance
530        + RANDOM/TCP - SSH - All succeeding Linux instances.
531
532        The above replicates the standard behavior of the Azure UI.
533        You can retrieve the assigned ports to each instance by
534        using the following private function:
535
536        _get_endpoint_ports(service_name)
537        Returns public,private port key pair.
538
539        @inherits: :class:`NodeDriver.create_node`
540
541        :keyword     image: The image to use when creating this node
542        :type        image:  `NodeImage`
543
544        :keyword     size: The size of the instance to create
545        :type        size: `NodeSize`
546
547        :keyword     ex_cloud_service_name: Required.
548                     Name of the Azure Cloud Service.
549        :type        ex_cloud_service_name:  ``str``
550
551        :keyword     ex_storage_service_name: Optional:
552                     Name of the Azure Storage Service.
553        :type        ex_storage_service_name:  ``str``
554
555        :keyword     ex_new_deployment: Optional. Tells azure to create a
556                                        new deployment rather than add to an
557                                        existing one.
558        :type        ex_new_deployment: ``boolean``
559
560        :keyword     ex_deployment_slot: Optional: Valid values: production|
561                                         staging.
562                                         Defaults to production.
563        :type        ex_deployment_slot:  ``str``
564
565        :keyword     ex_deployment_name: Optional. The name of the
566                                         deployment.
567                                         If this is not passed in we default
568                                         to using the Cloud Service name.
569        :type        ex_deployment_name: ``str``
570
571        :type        ex_custom_data: ``str``
572        :keyword     ex_custom_data: Optional script or other data which is
573                                     injected into the VM when it's beginning
574                                     provisioned.
575
576        :keyword     ex_admin_user_id: Optional. Defaults to 'azureuser'.
577        :type        ex_admin_user_id:  ``str``
578
579        :keyword     ex_virtual_network_name: Optional. If this is not passed
580                                              in no virtual network is used.
581        :type        ex_virtual_network_name:  ``str``
582
583        :keyword     ex_network_config: Optional. The ConfigurationSet to use
584                                        for network configuration
585        :type        ex_network_config:  `ConfigurationSet`
586
587        """
588        # TODO: Refactor this method to make it more readable, split it into
589        # multiple smaller methods
590        auth = self._get_and_check_auth(auth)
591        password = auth.password
592
593        if not isinstance(size, NodeSize):
594            raise ValueError('Size must be an instance of NodeSize')
595
596        if not isinstance(image, NodeImage):
597            raise ValueError(
598                "Image must be an instance of NodeImage, "
599                "produced by list_images()"
600            )
601
602        # Retrieve a list of currently available nodes for the provided cloud
603        # service
604        node_list = self.list_nodes(
605            ex_cloud_service_name=ex_cloud_service_name
606        )
607
608        if ex_network_config is None:
609            network_config = ConfigurationSet()
610        else:
611            network_config = ex_network_config
612        network_config.configuration_set_type = 'NetworkConfiguration'
613
614        # Base64 encode custom data if provided
615        if ex_custom_data:
616            ex_custom_data = self._encode_base64(data=ex_custom_data)
617
618        # We do this because we need to pass a Configuration to the
619        # method. This will be either Linux or Windows.
620        if WINDOWS_SERVER_REGEX.search(image.id, re.I):
621            machine_config = WindowsConfigurationSet(
622                computer_name=name,
623                admin_password=password,
624                admin_user_name=ex_admin_user_id
625            )
626
627            machine_config.domain_join = None
628
629            if not node_list or ex_new_deployment:
630                port = "3389"
631            else:
632                port = random.randint(41952, 65535)
633                endpoints = self._get_deployment(
634                    service_name=ex_cloud_service_name,
635                    deployment_slot=ex_deployment_slot
636                )
637
638                for instances in endpoints.role_instance_list:
639                    ports = [ep.public_port for ep in
640                             instances.instance_endpoints]
641
642                    while port in ports:
643                        port = random.randint(41952, 65535)
644
645            endpoint = ConfigurationSetInputEndpoint(
646                name='Remote Desktop',
647                protocol='tcp',
648                port=port,
649                local_port='3389',
650                load_balanced_endpoint_set_name=None,
651                enable_direct_server_return=False
652            )
653        else:
654            if not node_list or ex_new_deployment:
655                port = "22"
656            else:
657                port = random.randint(41952, 65535)
658                endpoints = self._get_deployment(
659                    service_name=ex_cloud_service_name,
660                    deployment_slot=ex_deployment_slot
661                )
662
663                for instances in endpoints.role_instance_list:
664                    ports = []
665                    if instances.instance_endpoints is not None:
666                        for ep in instances.instance_endpoints:
667                            ports += [ep.public_port]
668
669                    while port in ports:
670                        port = random.randint(41952, 65535)
671
672            endpoint = ConfigurationSetInputEndpoint(
673                name='SSH',
674                protocol='tcp',
675                port=port,
676                local_port='22',
677                load_balanced_endpoint_set_name=None,
678                enable_direct_server_return=False
679            )
680            machine_config = LinuxConfigurationSet(
681                name,
682                ex_admin_user_id,
683                password,
684                False,
685                ex_custom_data
686            )
687
688        network_config.input_endpoints.items.append(endpoint)
689
690        _storage_location = self._get_cloud_service_location(
691            service_name=ex_cloud_service_name
692        )
693
694        if ex_storage_service_name is None:
695            ex_storage_service_name = ex_cloud_service_name
696            ex_storage_service_name = re.sub(
697                r'[\W_-]+',
698                '',
699                ex_storage_service_name.lower(),
700                flags=re.UNICODE
701            )
702
703            if self._is_storage_service_unique(
704                    service_name=ex_storage_service_name):
705
706                self._create_storage_account(
707                    service_name=ex_storage_service_name,
708                    location=_storage_location.service_location,
709                    is_affinity_group=_storage_location.is_affinity_group
710                )
711
712        # OK, bit annoying here. You must create a deployment before
713        # you can create an instance; however, the deployment function
714        # creates the first instance, but all subsequent instances
715        # must be created using the add_role function.
716        #
717        # So, yeah, annoying.
718        if not node_list or ex_new_deployment:
719            # This is the first node in this cloud service.
720
721            if not ex_deployment_name:
722                ex_deployment_name = ex_cloud_service_name
723
724            vm_image_id = None
725            disk_config = None
726
727            if image.extra.get('vm_image', False):
728                vm_image_id = image.id
729                #  network_config = None
730            else:
731                blob_url = "http://%s.blob.core.windows.net" % (
732                    ex_storage_service_name)
733
734                # Azure's pattern in the UI.
735                disk_name = "%s-%s-%s.vhd" % (
736                    ex_cloud_service_name,
737                    name,
738                    time.strftime("%Y-%m-%d")
739                )
740
741                media_link = "%s/vhds/%s" % (blob_url, disk_name)
742
743                disk_config = OSVirtualHardDisk(image.id, media_link)
744
745            response = self._perform_post(
746                self._get_deployment_path_using_name(ex_cloud_service_name),
747                AzureXmlSerializer.virtual_machine_deployment_to_xml(
748                    ex_deployment_name,
749                    ex_deployment_slot,
750                    name,
751                    name,
752                    machine_config,
753                    disk_config,
754                    'PersistentVMRole',
755                    network_config,
756                    None,
757                    None,
758                    size.id,
759                    ex_virtual_network_name,
760                    vm_image_id
761                )
762            )
763            self.raise_for_response(response, 202)
764            self._ex_complete_async_azure_operation(response)
765        else:
766            _deployment_name = self._get_deployment(
767                service_name=ex_cloud_service_name,
768                deployment_slot=ex_deployment_slot
769            ).name
770
771            vm_image_id = None
772            disk_config = None
773
774            if image.extra.get('vm_image', False):
775                vm_image_id = image.id
776                #  network_config = None
777            else:
778                blob_url = "http://%s.blob.core.windows.net" % (
779                    ex_storage_service_name
780                )
781                disk_name = "%s-%s-%s.vhd" % (
782                    ex_cloud_service_name,
783                    name,
784                    time.strftime("%Y-%m-%d")
785                )
786                media_link = "%s/vhds/%s" % (blob_url, disk_name)
787                disk_config = OSVirtualHardDisk(image.id, media_link)
788
789            path = self._get_role_path(ex_cloud_service_name, _deployment_name)
790            body = AzureXmlSerializer.add_role_to_xml(
791                name,  # role_name
792                machine_config,  # system_config
793                disk_config,  # os_virtual_hard_disk
794                'PersistentVMRole',  # role_type
795                network_config,  # network_config
796                None,  # availability_set_name
797                None,  # data_virtual_hard_disks
798                vm_image_id,  # vm_image
799                size.id  # role_size
800            )
801
802            response = self._perform_post(path, body)
803            self.raise_for_response(response, 202)
804            self._ex_complete_async_azure_operation(response)
805
806        return Node(
807            id=name,
808            name=name,
809            state=NodeState.PENDING,
810            public_ips=[],
811            private_ips=[],
812            driver=self.connection.driver,
813            extra={
814                'ex_cloud_service_name': ex_cloud_service_name
815            }
816        )
817
818    def destroy_node(self, node, ex_cloud_service_name=None,
819                     ex_deployment_slot="Production"):
820        """
821        Remove Azure Virtual Machine
822
823        This removes the instance, but does not
824        remove the disk. You will need to use destroy_volume.
825        Azure sometimes has an issue where it will hold onto
826        a blob lease for an extended amount of time.
827
828        :keyword     ex_cloud_service_name: Required.
829                     Name of the Azure Cloud Service.
830        :type        ex_cloud_service_name:  ``str``
831
832        :keyword     ex_deployment_slot: Optional: The name of the deployment
833                                         slot. If this is not passed in we
834                                         default to production.
835        :type        ex_deployment_slot:  ``str``
836        """
837
838        if not isinstance(node, Node):
839            raise ValueError("A libcloud Node object is required.")
840
841        if ex_cloud_service_name is None and node.extra is not None:
842            ex_cloud_service_name = node.extra.get('ex_cloud_service_name')
843
844        if not ex_cloud_service_name:
845            raise ValueError("Unable to get ex_cloud_service_name from Node.")
846
847        _deployment = self._get_deployment(
848            service_name=ex_cloud_service_name,
849            deployment_slot=ex_deployment_slot
850        )
851
852        _deployment_name = _deployment.name
853
854        _server_deployment_count = len(_deployment.role_instance_list)
855
856        if _server_deployment_count > 1:
857            path = self._get_role_path(
858                ex_cloud_service_name,
859                _deployment_name,
860                node.id
861            )
862        else:
863            path = self._get_deployment_path_using_name(
864                ex_cloud_service_name,
865                _deployment_name
866            )
867
868        path += '?comp=media'
869
870        self._perform_delete(path)
871
872        return True
873
874    def ex_list_cloud_services(self):
875        return self._perform_get(
876            self._get_hosted_service_path(),
877            HostedServices
878        )
879
880    def ex_create_cloud_service(self, name, location, description=None,
881                                extended_properties=None):
882        """
883        Create an azure cloud service.
884
885        :param      name: Name of the service to create
886        :type       name: ``str``
887
888        :param      location: Standard azure location string
889        :type       location: ``str``
890
891        :param      description: Optional description
892        :type       description: ``str``
893
894        :param      extended_properties: Optional extended_properties
895        :type       extended_properties: ``dict``
896
897        :rtype: ``bool``
898        """
899
900        response = self._perform_cloud_service_create(
901            self._get_hosted_service_path(),
902            AzureXmlSerializer.create_hosted_service_to_xml(
903                name,
904                self._encode_base64(name),
905                description,
906                location,
907                None,
908                extended_properties
909            )
910        )
911
912        self.raise_for_response(response, 201)
913
914        return True
915
916    def ex_destroy_cloud_service(self, name):
917        """
918        Delete an azure cloud service.
919
920        :param      name: Name of the cloud service to destroy.
921        :type       name: ``str``
922
923        :rtype: ``bool``
924        """
925        response = self._perform_cloud_service_delete(
926            self._get_hosted_service_path(name)
927        )
928
929        self.raise_for_response(response, 200)
930
931        return True
932
933    def ex_add_instance_endpoints(self, node, endpoints,
934                                  ex_deployment_slot="Production"):
935        all_endpoints = [
936            {
937                "name": endpoint.name,
938                "protocol": endpoint.protocol,
939                "port": endpoint.public_port,
940                "local_port": endpoint.local_port,
941
942            }
943            for endpoint in node.extra['instance_endpoints']
944        ]
945
946        all_endpoints.extend(endpoints)
947        # pylint: disable=assignment-from-no-return
948        result = self.ex_set_instance_endpoints(node, all_endpoints,
949                                                ex_deployment_slot)
950        return result
951
952    def ex_set_instance_endpoints(self, node, endpoints,
953                                  ex_deployment_slot="Production"):
954
955        """
956        For example::
957
958            endpoint = ConfigurationSetInputEndpoint(
959                name='SSH',
960                protocol='tcp',
961                port=port,
962                local_port='22',
963                load_balanced_endpoint_set_name=None,
964                enable_direct_server_return=False
965            )
966            {
967                'name': 'SSH',
968                'protocol': 'tcp',
969                'port': port,
970                'local_port': '22'
971            }
972        """
973        ex_cloud_service_name = node.extra['ex_cloud_service_name']
974        vm_role_name = node.name
975
976        network_config = ConfigurationSet()
977        network_config.configuration_set_type = 'NetworkConfiguration'
978
979        for endpoint in endpoints:
980            new_endpoint = ConfigurationSetInputEndpoint(**endpoint)
981            network_config.input_endpoints.items.append(new_endpoint)
982
983        _deployment_name = self._get_deployment(
984            service_name=ex_cloud_service_name,
985            deployment_slot=ex_deployment_slot
986        ).name
987
988        response = self._perform_put(
989            self._get_role_path(
990                ex_cloud_service_name,
991                _deployment_name,
992                vm_role_name
993            ),
994            AzureXmlSerializer.add_role_to_xml(
995                None,  # role_name
996                None,  # system_config
997                None,  # os_virtual_hard_disk
998                'PersistentVMRole',  # role_type
999                network_config,  # network_config
1000                None,  # availability_set_name
1001                None,  # data_virtual_hard_disks
1002                None,  # vm_image
1003                None  # role_size
1004            )
1005        )
1006
1007        self.raise_for_response(response, 202)
1008
1009    def ex_create_storage_service(self, name, location,
1010                                  description=None, affinity_group=None,
1011                                  extended_properties=None):
1012        """
1013        Create an azure storage service.
1014
1015        :param      name: Name of the service to create
1016        :type       name: ``str``
1017
1018        :param      location: Standard azure location string
1019        :type       location: ``str``
1020
1021        :param      description: (Optional) Description of storage service.
1022        :type       description: ``str``
1023
1024        :param      affinity_group: (Optional) Azure affinity group.
1025        :type       affinity_group: ``str``
1026
1027        :param      extended_properties: (Optional) Additional configuration
1028                                         options support by Azure.
1029        :type       extended_properties: ``dict``
1030
1031        :rtype: ``bool``
1032        """
1033
1034        response = self._perform_storage_service_create(
1035            self._get_storage_service_path(),
1036            AzureXmlSerializer.create_storage_service_to_xml(
1037                service_name=name,
1038                label=self._encode_base64(name),
1039                description=description,
1040                location=location,
1041                affinity_group=affinity_group,
1042                extended_properties=extended_properties
1043            )
1044        )
1045
1046        self.raise_for_response(response, 202)
1047
1048        return True
1049
1050    def ex_destroy_storage_service(self, name):
1051        """
1052        Destroy storage service. Storage service must not have any active
1053        blobs. Sometimes Azure likes to hold onto volumes after they are
1054        deleted for an inordinate amount of time, so sleep before calling
1055        this method after volume deletion.
1056
1057        :param name: Name of storage service.
1058        :type  name: ``str``
1059
1060        :rtype: ``bool``
1061        """
1062
1063        response = self._perform_storage_service_delete(
1064            self._get_storage_service_path(name)
1065        )
1066        self.raise_for_response(response, 200)
1067
1068        return True
1069
1070    """
1071    Functions not implemented
1072    """
1073
1074    def create_volume_snapshot(self):
1075        raise NotImplementedError(
1076            'You cannot create snapshots of '
1077            'Azure VMs at this time.'
1078        )
1079
1080    def attach_volume(self):
1081        raise NotImplementedError(
1082            'attach_volume is not supported '
1083            'at this time.'
1084        )
1085
1086    def create_volume(self):
1087        raise NotImplementedError(
1088            'create_volume is not supported '
1089            'at this time.'
1090        )
1091
1092    def detach_volume(self):
1093        raise NotImplementedError(
1094            'detach_volume is not supported '
1095            'at this time.'
1096        )
1097
1098    def destroy_volume(self):
1099        raise NotImplementedError(
1100            'destroy_volume is not supported '
1101            'at this time.'
1102        )
1103
1104    """
1105    Private Functions
1106    """
1107
1108    def _perform_cloud_service_create(self, path, data):
1109        request = AzureHTTPRequest()
1110        request.method = 'POST'
1111        request.host = AZURE_SERVICE_MANAGEMENT_HOST
1112        request.path = path
1113        request.body = data
1114        request.path, request.query = self._update_request_uri_query(request)
1115        request.headers = self._update_management_header(request)
1116        response = self._perform_request(request)
1117
1118        return response
1119
1120    def _perform_cloud_service_delete(self, path):
1121        request = AzureHTTPRequest()
1122        request.method = 'DELETE'
1123        request.host = AZURE_SERVICE_MANAGEMENT_HOST
1124        request.path = path
1125        request.path, request.query = self._update_request_uri_query(request)
1126        request.headers = self._update_management_header(request)
1127        response = self._perform_request(request)
1128
1129        return response
1130
1131    def _perform_storage_service_create(self, path, data):
1132        request = AzureHTTPRequest()
1133        request.method = 'POST'
1134        request.host = AZURE_SERVICE_MANAGEMENT_HOST
1135        request.path = path
1136        request.body = data
1137        request.path, request.query = self._update_request_uri_query(request)
1138        request.headers = self._update_management_header(request)
1139        response = self._perform_request(request)
1140
1141        return response
1142
1143    def _perform_storage_service_delete(self, path):
1144        request = AzureHTTPRequest()
1145        request.method = 'DELETE'
1146        request.host = AZURE_SERVICE_MANAGEMENT_HOST
1147        request.path = path
1148        request.path, request.query = self._update_request_uri_query(request)
1149        request.headers = self._update_management_header(request)
1150        response = self._perform_request(request)
1151
1152        return response
1153
1154    def _to_node(self, data, ex_cloud_service_name=None, virtual_ips=None):
1155        """
1156        Convert the data from a Azure response object into a Node
1157        """
1158
1159        remote_desktop_port = ''
1160        ssh_port = ''
1161        public_ips = virtual_ips or []
1162
1163        if data.instance_endpoints is not None:
1164            if len(data.instance_endpoints) >= 1:
1165                public_ips = [data.instance_endpoints[0].vip]
1166
1167            for port in data.instance_endpoints:
1168                if port.name == 'Remote Desktop':
1169                    remote_desktop_port = port.public_port
1170
1171                if port.name == "SSH":
1172                    ssh_port = port.public_port
1173
1174        return Node(
1175            id=data.role_name,
1176            name=data.role_name,
1177            state=self.NODE_STATE_MAP.get(
1178                data.instance_status,
1179                NodeState.UNKNOWN
1180            ),
1181            public_ips=public_ips,
1182            private_ips=[data.ip_address],
1183            driver=self.connection.driver,
1184            extra={
1185                'instance_endpoints': data.instance_endpoints,
1186                'remote_desktop_port': remote_desktop_port,
1187                'ssh_port': ssh_port,
1188                'power_state': data.power_state,
1189                'instance_size': data.instance_size,
1190                'ex_cloud_service_name': ex_cloud_service_name
1191            }
1192        )
1193
1194    def _to_location(self, data):
1195        """
1196        Convert the data from a Azure response object into a location
1197        """
1198        country = data.display_name
1199
1200        if "Asia" in data.display_name:
1201            country = "Asia"
1202
1203        if "Europe" in data.display_name:
1204            country = "Europe"
1205
1206        if "US" in data.display_name:
1207            country = "US"
1208
1209        if "Japan" in data.display_name:
1210            country = "Japan"
1211
1212        if "Brazil" in data.display_name:
1213            country = "Brazil"
1214
1215        vm_role_sizes = data.compute_capabilities.virtual_machines_role_sizes
1216
1217        return AzureNodeLocation(
1218            id=data.name,
1219            name=data.display_name,
1220            country=country,
1221            driver=self.connection.driver,
1222            available_services=data.available_services,
1223            virtual_machine_role_sizes=vm_role_sizes
1224        )
1225
1226    def _to_node_size(self, data):
1227        """
1228        Convert the AZURE_COMPUTE_INSTANCE_TYPES into NodeSize
1229        """
1230        return NodeSize(
1231            id=data["id"],
1232            name=data["name"],
1233            ram=data["ram"],
1234            disk=data["disk"],
1235            bandwidth=data["bandwidth"],
1236            price=data["price"],
1237            driver=self.connection.driver,
1238            extra={
1239                'max_data_disks': data["max_data_disks"],
1240                'cores': data["cores"]
1241            }
1242        )
1243
1244    def _to_image(self, data):
1245        return NodeImage(
1246            id=data.name,
1247            name=data.label,
1248            driver=self.connection.driver,
1249            extra={
1250                'os': data.os,
1251                'category': data.category,
1252                'description': data.description,
1253                'location': data.location,
1254                'affinity_group': data.affinity_group,
1255                'media_link': data.media_link,
1256                'vm_image': False
1257            }
1258        )
1259
1260    def _vm_to_image(self, data):
1261        return NodeImage(
1262            id=data.name,
1263            name=data.label,
1264            driver=self.connection.driver,
1265            extra={
1266                'os': data.os_disk_configuration.os,
1267                'category': data.category,
1268                'location': data.location,
1269                'media_link': data.os_disk_configuration.media_link,
1270                'affinity_group': data.affinity_group,
1271                'deployment_name': data.deployment_name,
1272                'vm_image': True
1273            }
1274        )
1275
1276    def _to_volume(self, volume, node):
1277        extra = {
1278            'affinity_group': volume.affinity_group,
1279            'os': volume.os,
1280            'location': volume.location,
1281            'media_link': volume.media_link,
1282            'source_image_name': volume.source_image_name
1283        }
1284
1285        role_name = getattr(volume.attached_to, 'role_name', None)
1286        hosted_service_name = getattr(
1287            volume.attached_to,
1288            'hosted_service_name',
1289            None
1290        )
1291
1292        deployment_name = getattr(
1293            volume.attached_to,
1294            'deployment_name',
1295            None
1296        )
1297
1298        if role_name is not None:
1299            extra['role_name'] = role_name
1300
1301        if hosted_service_name is not None:
1302            extra['hosted_service_name'] = hosted_service_name
1303
1304        if deployment_name is not None:
1305            extra['deployment_name'] = deployment_name
1306
1307        if node:
1308            if role_name is not None and role_name == node.id:
1309                return StorageVolume(
1310                    id=volume.name,
1311                    name=volume.name,
1312                    size=int(volume.logical_disk_size_in_gb),
1313                    driver=self.connection.driver,
1314                    extra=extra
1315                )
1316        else:
1317            return StorageVolume(
1318                id=volume.name,
1319                name=volume.name,
1320                size=int(volume.logical_disk_size_in_gb),
1321                driver=self.connection.driver,
1322                extra=extra
1323            )
1324
1325    def _get_deployment(self, **kwargs):
1326        _service_name = kwargs['service_name']
1327        _deployment_slot = kwargs['deployment_slot']
1328
1329        response = self._perform_get(
1330            self._get_deployment_path_using_slot(
1331                _service_name,
1332                _deployment_slot
1333            ),
1334            None
1335        )
1336
1337        self.raise_for_response(response, 200)
1338
1339        return self._parse_response(response, Deployment)
1340
1341    def _get_cloud_service_location(self, service_name=None):
1342        if not service_name:
1343            raise ValueError("service_name is required.")
1344
1345        res = self._perform_get(
1346            '%s?embed-detail=False' % (
1347                self._get_hosted_service_path(service_name)
1348            ),
1349            HostedService
1350        )
1351
1352        _affinity_group = res.hosted_service_properties.affinity_group
1353        _cloud_service_location = res.hosted_service_properties.location
1354
1355        if _affinity_group is not None and _affinity_group != '':
1356            return self.service_location(True, _affinity_group)
1357        elif _cloud_service_location is not None:
1358            return self.service_location(False, _cloud_service_location)
1359        else:
1360            return None
1361
1362    def _is_storage_service_unique(self, service_name=None):
1363        if not service_name:
1364            raise ValueError("service_name is required.")
1365
1366        _check_availability = self._perform_get(
1367            '%s/operations/isavailable/%s%s' % (
1368                self._get_storage_service_path(),
1369                _str(service_name),
1370                ''
1371            ),
1372            AvailabilityResponse
1373        )
1374
1375        self.raise_for_response(_check_availability, 200)
1376
1377        return _check_availability.result
1378
1379    def _create_storage_account(self, **kwargs):
1380        if kwargs['is_affinity_group'] is True:
1381            response = self._perform_post(
1382                self._get_storage_service_path(),
1383                AzureXmlSerializer.create_storage_service_input_to_xml(
1384                    kwargs['service_name'],
1385                    kwargs['service_name'],
1386                    self._encode_base64(kwargs['service_name']),
1387                    kwargs['location'],
1388                    None,  # Location
1389                    True,  # geo_replication_enabled
1390                    None  # extended_properties
1391                )
1392            )
1393
1394            self.raise_for_response(response, 202)
1395
1396        else:
1397            response = self._perform_post(
1398                self._get_storage_service_path(),
1399                AzureXmlSerializer.create_storage_service_input_to_xml(
1400                    kwargs['service_name'],
1401                    kwargs['service_name'],
1402                    self._encode_base64(kwargs['service_name']),
1403                    None,  # Affinity Group
1404                    kwargs['location'],  # Location
1405                    True,  # geo_replication_enabled
1406                    None  # extended_properties
1407                )
1408            )
1409
1410            self.raise_for_response(response, 202)
1411
1412        # We need to wait for this to be created before we can
1413        # create the storage container and the instance.
1414        self._ex_complete_async_azure_operation(
1415            response,
1416            "create_storage_account"
1417        )
1418
1419    def _get_operation_status(self, request_id):
1420        return self._perform_get(
1421            '/' + self.subscription_id + '/operations/' + _str(request_id),
1422            Operation
1423        )
1424
1425    def _perform_get(self, path, response_type):
1426        request = AzureHTTPRequest()
1427        request.method = 'GET'
1428        request.host = AZURE_SERVICE_MANAGEMENT_HOST
1429        request.path = path
1430        request.path, request.query = self._update_request_uri_query(request)
1431        request.headers = self._update_management_header(request)
1432        response = self._perform_request(request)
1433
1434        if response_type is not None:
1435            return self._parse_response(response, response_type)
1436
1437        return response
1438
1439    def _perform_post(self, path, body, response_type=None):
1440        request = AzureHTTPRequest()
1441        request.method = 'POST'
1442        request.host = AZURE_SERVICE_MANAGEMENT_HOST
1443        request.path = path
1444        request.body = ensure_string(self._get_request_body(body))
1445        request.path, request.query = self._update_request_uri_query(request)
1446        request.headers = self._update_management_header(request)
1447        response = self._perform_request(request)
1448
1449        return response
1450
1451    def _perform_put(self, path, body, response_type=None):
1452        request = AzureHTTPRequest()
1453        request.method = 'PUT'
1454        request.host = AZURE_SERVICE_MANAGEMENT_HOST
1455        request.path = path
1456        request.body = ensure_string(self._get_request_body(body))
1457        request.path, request.query = self._update_request_uri_query(request)
1458        request.headers = self._update_management_header(request)
1459        response = self._perform_request(request)
1460
1461        return response
1462
1463    def _perform_delete(self, path):
1464        request = AzureHTTPRequest()
1465        request.method = 'DELETE'
1466        request.host = AZURE_SERVICE_MANAGEMENT_HOST
1467        request.path = path
1468        request.path, request.query = self._update_request_uri_query(request)
1469        request.headers = self._update_management_header(request)
1470        response = self._perform_request(request)
1471
1472        self.raise_for_response(response, 202)
1473
1474    def _perform_request(self, request):
1475        try:
1476            return self.connection.request(
1477                action=request.path,
1478                data=request.body,
1479                headers=request.headers,
1480                method=request.method
1481            )
1482        except AzureRedirectException as e:
1483            parsed_url = urlparse.urlparse(e.location)
1484            request.host = parsed_url.netloc
1485            return self._perform_request(request)
1486        except Exception as e:
1487            raise e
1488
1489    def _update_request_uri_query(self, request):
1490        """
1491        pulls the query string out of the URI and moves it into
1492        the query portion of the request object.  If there are already
1493        query parameters on the request the parameters in the URI will
1494        appear after the existing parameters
1495        """
1496        if '?' in request.path:
1497            request.path, _, query_string = request.path.partition('?')
1498            if query_string:
1499                query_params = query_string.split('&')
1500                for query in query_params:
1501                    if '=' in query:
1502                        name, _, value = query.partition('=')
1503                        request.query.append((name, value))
1504
1505        request.path = url_quote(request.path, '/()$=\',')
1506
1507        # add encoded queries to request.path.
1508        if request.query:
1509            request.path += '?'
1510            for name, value in request.query:
1511                if value is not None:
1512                    request.path += '%s=%s%s' % (
1513                        name,
1514                        url_quote(value, '/()$=\','),
1515                        '&'
1516                    )
1517            request.path = request.path[:-1]
1518
1519        return request.path, request.query
1520
1521    def _update_management_header(self, request):
1522        """
1523        Add additional headers for management.
1524        """
1525
1526        if request.method in ['PUT', 'POST', 'MERGE', 'DELETE']:
1527            request.headers['Content-Length'] = str(len(request.body))
1528
1529        # append additional headers base on the service
1530        #  request.headers.append(('x-ms-version', X_MS_VERSION))
1531
1532        # if it is not GET or HEAD request, must set content-type.
1533        if request.method not in ['GET', 'HEAD']:
1534            for key in request.headers:
1535                if 'content-type' == key.lower():
1536                    break
1537            else:
1538                request.headers['Content-Type'] = 'application/xml'
1539
1540        return request.headers
1541
1542    def _parse_response(self, response, return_type):
1543        """
1544        Parse the HTTPResponse's body and fill all the data into a class of
1545        return_type.
1546        """
1547
1548        return self._parse_response_body_from_xml_text(
1549            response=response,
1550            return_type=return_type
1551        )
1552
1553    def _parse_response_body_from_xml_text(self, response, return_type):
1554        """
1555        parse the xml and fill all the data into a class of return_type
1556        """
1557        respbody = response.body
1558
1559        doc = minidom.parseString(respbody)
1560        return_obj = return_type()
1561        for node in self._get_child_nodes(doc, return_type.__name__):
1562            self._fill_data_to_return_object(node, return_obj)
1563
1564        # Note: We always explicitly assign status code to the custom return
1565        # type object
1566        return_obj.status = response.status
1567
1568        return return_obj
1569
1570    def _get_child_nodes(self, node, tag_name):
1571        return [childNode for childNode in node.getElementsByTagName(tag_name)
1572                if childNode.parentNode == node]
1573
1574    def _fill_data_to_return_object(self, node, return_obj):
1575        members = dict(vars(return_obj))
1576        for name, value in members.items():
1577            if isinstance(value, _ListOf):
1578                setattr(
1579                    return_obj,
1580                    name,
1581                    self._fill_list_of(
1582                        node,
1583                        value.list_type,
1584                        value.xml_element_name
1585                    )
1586                )
1587            elif isinstance(value, ScalarListOf):
1588                setattr(
1589                    return_obj,
1590                    name,
1591                    self._fill_scalar_list_of(
1592                        node,
1593                        value.list_type,
1594                        self._get_serialization_name(name),
1595                        value.xml_element_name
1596                    )
1597                )
1598            elif isinstance(value, _DictOf):
1599                setattr(
1600                    return_obj,
1601                    name,
1602                    self._fill_dict_of(
1603                        node,
1604                        self._get_serialization_name(name),
1605                        value.pair_xml_element_name,
1606                        value.key_xml_element_name,
1607                        value.value_xml_element_name
1608                    )
1609                )
1610            elif isinstance(value, WindowsAzureData):
1611                setattr(
1612                    return_obj,
1613                    name,
1614                    self._fill_instance_child(node, name, value.__class__)
1615                )
1616            elif isinstance(value, dict):
1617                setattr(
1618                    return_obj,
1619                    name,
1620                    self._fill_dict(
1621                        node,
1622                        self._get_serialization_name(name)
1623                    )
1624                )
1625            elif isinstance(value, _Base64String):
1626                value = self._fill_data_minidom(node, name, '')
1627                if value is not None:
1628                    value = self._decode_base64_to_text(value)
1629                # always set the attribute,
1630                # so we don't end up returning an object
1631                # with type _Base64String
1632                setattr(return_obj, name, value)
1633            else:
1634                value = self._fill_data_minidom(node, name, value)
1635                if value is not None:
1636                    setattr(return_obj, name, value)
1637
1638    def _fill_list_of(self, xmldoc, element_type, xml_element_name):
1639        xmlelements = self._get_child_nodes(xmldoc, xml_element_name)
1640        return [
1641            self._parse_response_body_from_xml_node(xmlelement, element_type)
1642            for xmlelement in xmlelements
1643        ]
1644
1645    def _parse_response_body_from_xml_node(self, node, return_type):
1646        """
1647        parse the xml and fill all the data into a class of return_type
1648        """
1649        return_obj = return_type()
1650        self._fill_data_to_return_object(node, return_obj)
1651
1652        return return_obj
1653
1654    def _fill_scalar_list_of(self,
1655                             xmldoc,
1656                             element_type,
1657                             parent_xml_element_name,
1658                             xml_element_name):
1659        xmlelements = self._get_child_nodes(xmldoc, parent_xml_element_name)
1660
1661        if xmlelements:
1662            xmlelements = self._get_child_nodes(
1663                xmlelements[0],
1664                xml_element_name
1665            )
1666            return [
1667                self._get_node_value(xmlelement, element_type)
1668                for xmlelement in xmlelements
1669            ]
1670
1671    def _get_node_value(self, xmlelement, data_type):
1672        value = xmlelement.firstChild.nodeValue
1673        if data_type is datetime:
1674            return self._to_datetime(value)
1675        elif data_type is bool:
1676            return value.lower() != 'false'
1677        else:
1678            return data_type(value)
1679
1680    def _get_serialization_name(self, element_name):
1681        """
1682        Converts a Python name into a serializable name.
1683        """
1684
1685        known = _KNOWN_SERIALIZATION_XFORMS.get(element_name)
1686        if known is not None:
1687            return known
1688
1689        if element_name.startswith('x_ms_'):
1690            return element_name.replace('_', '-')
1691
1692        if element_name.endswith('_id'):
1693            element_name = element_name.replace('_id', 'ID')
1694
1695        for name in ['content_', 'last_modified', 'if_', 'cache_control']:
1696            if element_name.startswith(name):
1697                element_name = element_name.replace('_', '-_')
1698
1699        return ''.join(name.capitalize() for name in element_name.split('_'))
1700
1701    def _fill_dict_of(self, xmldoc, parent_xml_element_name,
1702                      pair_xml_element_name, key_xml_element_name,
1703                      value_xml_element_name):
1704        return_obj = {}
1705
1706        xmlelements = self._get_child_nodes(xmldoc, parent_xml_element_name)
1707
1708        if xmlelements:
1709            xmlelements = self._get_child_nodes(
1710                xmlelements[0],
1711                pair_xml_element_name
1712            )
1713            for pair in xmlelements:
1714                keys = self._get_child_nodes(pair, key_xml_element_name)
1715                values = self._get_child_nodes(pair, value_xml_element_name)
1716                if keys and values:
1717                    key = keys[0].firstChild.nodeValue
1718                    value = values[0].firstChild.nodeValue
1719                    return_obj[key] = value
1720
1721        return return_obj
1722
1723    def _fill_instance_child(self, xmldoc, element_name, return_type):
1724        """
1725        Converts a child of the current dom element to the specified type.
1726        """
1727        xmlelements = self._get_child_nodes(
1728            xmldoc,
1729            self._get_serialization_name(element_name)
1730        )
1731
1732        if not xmlelements:
1733            return None
1734
1735        return_obj = return_type()
1736        self._fill_data_to_return_object(xmlelements[0], return_obj)
1737
1738        return return_obj
1739
1740    def _fill_dict(self, xmldoc, element_name):
1741        xmlelements = self._get_child_nodes(xmldoc, element_name)
1742
1743        if xmlelements:
1744            return_obj = {}
1745            for child in xmlelements[0].childNodes:
1746                if child.firstChild:
1747                    return_obj[child.nodeName] = child.firstChild.nodeValue
1748            return return_obj
1749
1750    def _encode_base64(self, data):
1751        if isinstance(data, _unicode_type):
1752            data = data.encode('utf-8')
1753        encoded = base64.b64encode(data)
1754        return encoded.decode('utf-8')
1755
1756    def _decode_base64_to_bytes(self, data):
1757        if isinstance(data, _unicode_type):
1758            data = data.encode('utf-8')
1759        return base64.b64decode(data)
1760
1761    def _decode_base64_to_text(self, data):
1762        decoded_bytes = self._decode_base64_to_bytes(data)
1763        return decoded_bytes.decode('utf-8')
1764
1765    def _fill_data_minidom(self, xmldoc, element_name, data_member):
1766        xmlelements = self._get_child_nodes(
1767            xmldoc,
1768            self._get_serialization_name(element_name)
1769        )
1770
1771        if not xmlelements or not xmlelements[0].childNodes:
1772            return None
1773
1774        value = xmlelements[0].firstChild.nodeValue
1775
1776        if data_member is None:
1777            return value
1778        elif isinstance(data_member, datetime):
1779            return self._to_datetime(value)
1780        elif type(data_member) is bool:
1781            return value.lower() != 'false'
1782        elif type(data_member) is str:
1783            return _real_unicode(value)
1784        else:
1785            return type(data_member)(value)
1786
1787    def _to_datetime(self, strtime):
1788        return datetime.strptime(strtime, "%Y-%m-%dT%H:%M:%S.%f")
1789
1790    def _get_request_body(self, request_body):
1791        if request_body is None:
1792            return b''
1793
1794        if isinstance(request_body, WindowsAzureData):
1795            request_body = self._convert_class_to_xml(request_body)
1796
1797        if isinstance(request_body, bytes):
1798            return request_body
1799
1800        if isinstance(request_body, _unicode_type):
1801            return request_body.encode('utf-8')
1802
1803        request_body = str(request_body)
1804        if isinstance(request_body, _unicode_type):
1805            return request_body.encode('utf-8')
1806
1807        return request_body
1808
1809    def _convert_class_to_xml(self, source, xml_prefix=True):
1810        root = ET.Element()
1811        doc = self._construct_element_tree(source, root)
1812
1813        result = ensure_string(ET.tostring(doc, encoding='utf-8',
1814                                           method='xml'))
1815        return result
1816
1817    def _construct_element_tree(self, source, etree):
1818        if source is None:
1819            return ET.Element()
1820
1821        if isinstance(source, list):
1822            for value in source:
1823                etree.append(self._construct_element_tree(value, etree))
1824
1825        elif isinstance(source, WindowsAzureData):
1826            class_name = source.__class__.__name__
1827            etree.append(ET.Element(class_name))
1828
1829            for name, value in vars(source).items():
1830                if value is not None:
1831                    if (isinstance(value, list) or
1832                            isinstance(value, WindowsAzureData)):
1833                        etree.append(
1834                            self._construct_element_tree(value, etree)
1835                        )
1836                    else:
1837                        ele = ET.Element(self._get_serialization_name(name))
1838                        ele.text = xml_escape(str(value))
1839                        etree.append(ele)
1840
1841            etree.append(ET.Element(class_name))
1842        return etree
1843
1844    def _parse_response_for_async_op(self, response):
1845        if response is None:
1846            return None
1847
1848        result = AsynchronousOperationResult()
1849        if response.headers:
1850            for name, value in response.headers.items():
1851                if name.lower() == 'x-ms-request-id':
1852                    result.request_id = value
1853
1854        return result
1855
1856    def _get_deployment_path_using_name(self, service_name,
1857                                        deployment_name=None):
1858        components = [
1859            'services/hostedservices/',
1860            _str(service_name),
1861            '/deployments'
1862        ]
1863        resource = ''.join(components)
1864        return self._get_path(resource, deployment_name)
1865
1866    def _get_path(self, resource, name):
1867        path = '/' + self.subscription_id + '/' + resource
1868        if name is not None:
1869            path += '/' + _str(name)
1870        return path
1871
1872    def _get_image_path(self, image_name=None):
1873        return self._get_path('services/images', image_name)
1874
1875    def _get_vmimage_path(self, image_name=None):
1876        return self._get_path('services/vmimages', image_name)
1877
1878    def _get_hosted_service_path(self, service_name=None):
1879        return self._get_path('services/hostedservices', service_name)
1880
1881    def _get_deployment_path_using_slot(self, service_name, slot=None):
1882        return self._get_path(
1883            'services/hostedservices/%s/deploymentslots' % (
1884                _str(service_name)
1885            ),
1886            slot
1887        )
1888
1889    def _get_disk_path(self, disk_name=None):
1890        return self._get_path('services/disks', disk_name)
1891
1892    def _get_role_path(self, service_name, deployment_name, role_name=None):
1893        components = [
1894            'services/hostedservices/',
1895            _str(service_name),
1896            '/deployments/',
1897            deployment_name,
1898            '/roles'
1899        ]
1900        resource = ''.join(components)
1901        return self._get_path(resource, role_name)
1902
1903    def _get_storage_service_path(self, service_name=None):
1904        return self._get_path('services/storageservices', service_name)
1905
1906    def _ex_complete_async_azure_operation(self, response=None,
1907                                           operation_type='create_node'):
1908        request_id = self._parse_response_for_async_op(response)
1909        operation_status = self._get_operation_status(request_id.request_id)
1910
1911        timeout = 60 * 5
1912        waittime = 0
1913        interval = 5
1914
1915        while operation_status.status == "InProgress" and waittime < timeout:
1916            operation_status = self._get_operation_status(request_id)
1917            if operation_status.status == "Succeeded":
1918                break
1919
1920            waittime += interval
1921            time.sleep(interval)
1922
1923        if operation_status.status == 'Failed':
1924            raise LibcloudError(
1925                'Message: Async request for operation %s has failed' %
1926                operation_type,
1927                driver=self.connection.driver
1928            )
1929
1930    def raise_for_response(self, response, valid_response):
1931        if response.status != valid_response:
1932            values = (response.error, response.body, response.status)
1933            message = 'Message: %s, Body: %s, Status code: %s' % (values)
1934            raise LibcloudError(message, driver=self)
1935
1936
1937"""
1938XML Serializer
1939
1940Borrowed from the Azure SDK for Python which is licensed under Apache 2.0.
1941
1942https://github.com/Azure/azure-sdk-for-python
1943"""
1944
1945
1946def _lower(text):
1947    return text.lower()
1948
1949
1950class AzureXmlSerializer(object):
1951
1952    @staticmethod
1953    def create_storage_service_input_to_xml(service_name,
1954                                            description,
1955                                            label,
1956                                            affinity_group,
1957                                            location,
1958                                            geo_replication_enabled,
1959                                            extended_properties):
1960        return AzureXmlSerializer.doc_from_data(
1961            'CreateStorageServiceInput',
1962            [
1963                ('ServiceName', service_name),
1964                ('Description', description),
1965                ('Label', label),
1966                ('AffinityGroup', affinity_group),
1967                ('Location', location),
1968                ('GeoReplicationEnabled', geo_replication_enabled, _lower)
1969            ],
1970            extended_properties
1971        )
1972
1973    @staticmethod
1974    def update_storage_service_input_to_xml(description,
1975                                            label,
1976                                            geo_replication_enabled,
1977                                            extended_properties):
1978        return AzureXmlSerializer.doc_from_data(
1979            'UpdateStorageServiceInput',
1980            [
1981                ('Description', description),
1982                ('Label', label, AzureNodeDriver._encode_base64),
1983                ('GeoReplicationEnabled', geo_replication_enabled, _lower)
1984            ],
1985            extended_properties
1986        )
1987
1988    @staticmethod
1989    def regenerate_keys_to_xml(key_type):
1990        return AzureXmlSerializer.doc_from_data(
1991            'RegenerateKeys',
1992            [('KeyType', key_type)]
1993        )
1994
1995    @staticmethod
1996    def update_hosted_service_to_xml(label, description, extended_properties):
1997        return AzureXmlSerializer.doc_from_data(
1998            'UpdateHostedService',
1999            [
2000                ('Label', label, AzureNodeDriver._encode_base64),
2001                ('Description', description)
2002            ],
2003            extended_properties
2004        )
2005
2006    @staticmethod
2007    def create_hosted_service_to_xml(service_name,
2008                                     label,
2009                                     description,
2010                                     location,
2011                                     affinity_group=None,
2012                                     extended_properties=None):
2013        if affinity_group:
2014            return AzureXmlSerializer.doc_from_data(
2015                'CreateHostedService',
2016                [
2017                    ('ServiceName', service_name),
2018                    ('Label', label),
2019                    ('Description', description),
2020                    ('AffinityGroup', affinity_group),
2021                ],
2022                extended_properties
2023            )
2024
2025        return AzureXmlSerializer.doc_from_data(
2026            'CreateHostedService',
2027            [
2028                ('ServiceName', service_name),
2029                ('Label', label),
2030                ('Description', description),
2031                ('Location', location),
2032            ],
2033            extended_properties
2034        )
2035
2036    @staticmethod
2037    def create_storage_service_to_xml(service_name,
2038                                      label,
2039                                      description,
2040                                      location,
2041                                      affinity_group,
2042                                      extended_properties=None):
2043
2044        return AzureXmlSerializer.doc_from_data(
2045            'CreateStorageServiceInput',
2046            [
2047                ('ServiceName', service_name),
2048                ('Label', label),
2049                ('Description', description),
2050                ('Location', location),
2051                ('AffinityGroup', affinity_group)
2052            ],
2053            extended_properties
2054        )
2055
2056    @staticmethod
2057    def create_deployment_to_xml(name,
2058                                 package_url,
2059                                 label,
2060                                 configuration,
2061                                 start_deployment,
2062                                 treat_warnings_as_error,
2063                                 extended_properties):
2064        return AzureXmlSerializer.doc_from_data(
2065            'CreateDeployment',
2066            [
2067                ('Name', name),
2068                ('PackageUrl', package_url),
2069                ('Label', label, AzureNodeDriver._encode_base64),
2070                ('Configuration', configuration),
2071                ('StartDeployment', start_deployment, _lower),
2072                ('TreatWarningsAsError', treat_warnings_as_error, _lower)
2073            ],
2074            extended_properties
2075        )
2076
2077    @staticmethod
2078    def swap_deployment_to_xml(production, source_deployment):
2079        return AzureXmlSerializer.doc_from_data(
2080            'Swap',
2081            [
2082                ('Production', production),
2083                ('SourceDeployment', source_deployment)
2084            ]
2085        )
2086
2087    @staticmethod
2088    def update_deployment_status_to_xml(status):
2089        return AzureXmlSerializer.doc_from_data(
2090            'UpdateDeploymentStatus',
2091            [('Status', status)]
2092        )
2093
2094    @staticmethod
2095    def change_deployment_to_xml(configuration,
2096                                 treat_warnings_as_error,
2097                                 mode,
2098                                 extended_properties):
2099        return AzureXmlSerializer.doc_from_data(
2100            'ChangeConfiguration',
2101            [
2102                ('Configuration', configuration),
2103                ('TreatWarningsAsError', treat_warnings_as_error, _lower),
2104                ('Mode', mode)
2105            ],
2106            extended_properties
2107        )
2108
2109    @staticmethod
2110    def upgrade_deployment_to_xml(mode,
2111                                  package_url,
2112                                  configuration,
2113                                  label,
2114                                  role_to_upgrade,
2115                                  force,
2116                                  extended_properties):
2117        return AzureXmlSerializer.doc_from_data(
2118            'UpgradeDeployment',
2119            [
2120                ('Mode', mode),
2121                ('PackageUrl', package_url),
2122                ('Configuration', configuration),
2123                ('Label', label, AzureNodeDriver._encode_base64),
2124                ('RoleToUpgrade', role_to_upgrade),
2125                ('Force', force, _lower)
2126            ],
2127            extended_properties
2128        )
2129
2130    @staticmethod
2131    def rollback_upgrade_to_xml(mode, force):
2132        return AzureXmlSerializer.doc_from_data(
2133            'RollbackUpdateOrUpgrade',
2134            [
2135                ('Mode', mode),
2136                ('Force', force, _lower)
2137            ]
2138        )
2139
2140    @staticmethod
2141    def walk_upgrade_domain_to_xml(upgrade_domain):
2142        return AzureXmlSerializer.doc_from_data(
2143            'WalkUpgradeDomain',
2144            [('UpgradeDomain', upgrade_domain)]
2145        )
2146
2147    @staticmethod
2148    def certificate_file_to_xml(data, certificate_format, password):
2149        return AzureXmlSerializer.doc_from_data(
2150            'CertificateFile',
2151            [
2152                ('Data', data),
2153                ('CertificateFormat', certificate_format),
2154                ('Password', password)
2155            ]
2156        )
2157
2158    @staticmethod
2159    def create_affinity_group_to_xml(name, label, description, location):
2160        return AzureXmlSerializer.doc_from_data(
2161            'CreateAffinityGroup',
2162            [
2163                ('Name', name),
2164                ('Label', label, AzureNodeDriver._encode_base64),
2165                ('Description', description),
2166                ('Location', location)
2167            ]
2168        )
2169
2170    @staticmethod
2171    def update_affinity_group_to_xml(label, description):
2172        return AzureXmlSerializer.doc_from_data(
2173            'UpdateAffinityGroup',
2174            [
2175                ('Label', label, AzureNodeDriver._encode_base64),
2176                ('Description', description)
2177            ]
2178        )
2179
2180    @staticmethod
2181    def subscription_certificate_to_xml(public_key, thumbprint, data):
2182        return AzureXmlSerializer.doc_from_data(
2183            'SubscriptionCertificate',
2184            [
2185                ('SubscriptionCertificatePublicKey', public_key),
2186                ('SubscriptionCertificateThumbprint', thumbprint),
2187                ('SubscriptionCertificateData', data)
2188            ]
2189        )
2190
2191    @staticmethod
2192    def os_image_to_xml(label, media_link, name, os):
2193        return AzureXmlSerializer.doc_from_data(
2194            'OSImage',
2195            [
2196                ('Label', label),
2197                ('MediaLink', media_link),
2198                ('Name', name),
2199                ('OS', os)
2200            ]
2201        )
2202
2203    @staticmethod
2204    def data_virtual_hard_disk_to_xml(host_caching,
2205                                      disk_label,
2206                                      disk_name,
2207                                      lun,
2208                                      logical_disk_size_in_gb,
2209                                      media_link,
2210                                      source_media_link):
2211        return AzureXmlSerializer.doc_from_data(
2212            'DataVirtualHardDisk',
2213            [
2214                ('HostCaching', host_caching),
2215                ('DiskLabel', disk_label),
2216                ('DiskName', disk_name),
2217                ('Lun', lun),
2218                ('LogicalDiskSizeInGB', logical_disk_size_in_gb),
2219                ('MediaLink', media_link),
2220                ('SourceMediaLink', source_media_link)
2221            ]
2222        )
2223
2224    @staticmethod
2225    def disk_to_xml(has_operating_system, label, media_link, name, os):
2226        return AzureXmlSerializer.doc_from_data(
2227            'Disk',
2228            [
2229                ('HasOperatingSystem', has_operating_system, _lower),
2230                ('Label', label),
2231                ('MediaLink', media_link),
2232                ('Name', name),
2233                ('OS', os)
2234            ]
2235        )
2236
2237    @staticmethod
2238    def restart_role_operation_to_xml():
2239        xml = ET.Element("OperationType")
2240        xml.text = "RestartRoleOperation"
2241        doc = AzureXmlSerializer.doc_from_xml(
2242            'RestartRoleOperation',
2243            xml
2244        )
2245        result = ensure_string(ET.tostring(doc, encoding='utf-8'))
2246        return result
2247
2248    @staticmethod
2249    def shutdown_role_operation_to_xml():
2250        xml = ET.Element("OperationType")
2251        xml.text = "ShutdownRoleOperation"
2252        doc = AzureXmlSerializer.doc_from_xml(
2253            'ShutdownRoleOperation',
2254            xml
2255        )
2256        result = ensure_string(ET.tostring(doc, encoding='utf-8'))
2257        return result
2258
2259    @staticmethod
2260    def start_role_operation_to_xml():
2261        xml = ET.Element("OperationType")
2262        xml.text = "StartRoleOperation"
2263        doc = AzureXmlSerializer.doc_from_xml(
2264            'StartRoleOperation',
2265            xml
2266        )
2267        result = ensure_string(ET.tostring(doc, encoding='utf-8'))
2268        return result
2269
2270    @staticmethod
2271    def windows_configuration_to_xml(configuration, xml):
2272        AzureXmlSerializer.data_to_xml(
2273            [('ConfigurationSetType', configuration.configuration_set_type)],
2274            xml
2275        )
2276        AzureXmlSerializer.data_to_xml(
2277            [('ComputerName', configuration.computer_name)],
2278            xml
2279        )
2280        AzureXmlSerializer.data_to_xml(
2281            [('AdminPassword', configuration.admin_password)],
2282            xml
2283        )
2284        AzureXmlSerializer.data_to_xml(
2285            [
2286                (
2287                    'ResetPasswordOnFirstLogon',
2288                    configuration.reset_password_on_first_logon,
2289                    _lower
2290                )
2291            ],
2292            xml
2293        )
2294
2295        AzureXmlSerializer.data_to_xml(
2296            [
2297                (
2298                    'EnableAutomaticUpdates',
2299                    configuration.enable_automatic_updates,
2300                    _lower
2301                )
2302            ],
2303            xml
2304        )
2305
2306        AzureXmlSerializer.data_to_xml(
2307            [('TimeZone', configuration.time_zone)],
2308            xml
2309        )
2310
2311        if configuration.domain_join is not None:
2312            domain = ET.xml("DomainJoin")  # pylint: disable=no-member
2313            creds = ET.xml("Credentials")  # pylint: disable=no-member
2314            domain.appemnd(creds)
2315            xml.append(domain)
2316
2317            AzureXmlSerializer.data_to_xml(
2318                [('Domain', configuration.domain_join.credentials.domain)],
2319                creds
2320            )
2321
2322            AzureXmlSerializer.data_to_xml(
2323                [
2324                    (
2325                        'Username',
2326                        configuration.domain_join.credentials.username
2327                    )
2328                ],
2329                creds
2330            )
2331            AzureXmlSerializer.data_to_xml(
2332                [
2333                    (
2334                        'Password',
2335                        configuration.domain_join.credentials.password
2336                    )
2337                ],
2338                creds
2339            )
2340
2341            AzureXmlSerializer.data_to_xml(
2342                [('JoinDomain', configuration.domain_join.join_domain)],
2343                domain
2344            )
2345
2346            AzureXmlSerializer.data_to_xml(
2347                [
2348                    (
2349                        'MachineObjectOU',
2350                        configuration.domain_join.machine_object_ou
2351                    )
2352                ],
2353                domain
2354            )
2355
2356        if configuration.stored_certificate_settings is not None:
2357            cert_settings = ET.Element("StoredCertificateSettings")
2358            xml.append(cert_settings)
2359            for cert in configuration.stored_certificate_settings:
2360                cert_setting = ET.Element("CertificateSetting")
2361                cert_settings.append(cert_setting)
2362
2363                cert_setting.append(AzureXmlSerializer.data_to_xml(
2364                    [('StoreLocation', cert.store_location)])
2365                )
2366                AzureXmlSerializer.data_to_xml(
2367                    [('StoreName', cert.store_name)],
2368                    cert_setting
2369                )
2370                AzureXmlSerializer.data_to_xml(
2371                    [('Thumbprint', cert.thumbprint)],
2372                    cert_setting
2373                )
2374
2375        AzureXmlSerializer.data_to_xml(
2376            [('AdminUsername', configuration.admin_user_name)],
2377            xml
2378        )
2379        return xml
2380
2381    @staticmethod
2382    def linux_configuration_to_xml(configuration, xml):
2383        AzureXmlSerializer.data_to_xml(
2384            [('ConfigurationSetType', configuration.configuration_set_type)],
2385            xml
2386        )
2387        AzureXmlSerializer.data_to_xml(
2388            [('HostName', configuration.host_name)],
2389            xml
2390        )
2391        AzureXmlSerializer.data_to_xml(
2392            [('UserName', configuration.user_name)],
2393            xml
2394        )
2395        AzureXmlSerializer.data_to_xml(
2396            [('UserPassword', configuration.user_password)],
2397            xml
2398        )
2399        AzureXmlSerializer.data_to_xml(
2400            [
2401                (
2402                    'DisableSshPasswordAuthentication',
2403                    configuration.disable_ssh_password_authentication,
2404                    _lower
2405                )
2406            ],
2407            xml
2408        )
2409
2410        if configuration.ssh is not None:
2411            ssh = ET.Element("SSH")
2412            pkeys = ET.Element("PublicKeys")
2413            kpairs = ET.Element("KeyPairs")
2414            ssh.append(pkeys)
2415            ssh.append(kpairs)
2416            xml.append(ssh)
2417
2418            for key in configuration.ssh.public_keys:
2419                pkey = ET.Element("PublicKey")
2420                pkeys.append(pkey)
2421                AzureXmlSerializer.data_to_xml(
2422                    [('Fingerprint', key.fingerprint)],
2423                    pkey
2424                )
2425                AzureXmlSerializer.data_to_xml([('Path', key.path)], pkey)
2426
2427            for key in configuration.ssh.key_pairs:
2428                kpair = ET.Element("KeyPair")
2429                kpairs.append(kpair)
2430                AzureXmlSerializer.data_to_xml(
2431                    [('Fingerprint', key.fingerprint)],
2432                    kpair
2433                )
2434                AzureXmlSerializer.data_to_xml([('Path', key.path)], kpair)
2435
2436        if configuration.custom_data is not None:
2437            AzureXmlSerializer.data_to_xml(
2438                [('CustomData', configuration.custom_data)],
2439                xml
2440            )
2441
2442        return xml
2443
2444    @staticmethod
2445    def network_configuration_to_xml(configuration, xml):
2446        AzureXmlSerializer.data_to_xml(
2447            [('ConfigurationSetType', configuration.configuration_set_type)],
2448            xml
2449        )
2450
2451        input_endpoints = ET.Element("InputEndpoints")
2452        xml.append(input_endpoints)
2453
2454        for endpoint in configuration.input_endpoints:
2455            input_endpoint = ET.Element("InputEndpoint")
2456            input_endpoints.append(input_endpoint)
2457
2458            AzureXmlSerializer.data_to_xml(
2459                [
2460                    (
2461                        'LoadBalancedEndpointSetName',
2462                        endpoint.load_balanced_endpoint_set_name
2463                    )
2464                ],
2465                input_endpoint
2466            )
2467
2468            AzureXmlSerializer.data_to_xml(
2469                [('LocalPort', endpoint.local_port)],
2470                input_endpoint
2471            )
2472            AzureXmlSerializer.data_to_xml(
2473                [('Name', endpoint.name)],
2474                input_endpoint
2475            )
2476            AzureXmlSerializer.data_to_xml(
2477                [('Port', endpoint.port)],
2478                input_endpoint
2479            )
2480
2481            if (endpoint.load_balancer_probe.path or
2482                    endpoint.load_balancer_probe.port or
2483                    endpoint.load_balancer_probe.protocol):
2484
2485                load_balancer_probe = ET.Element("LoadBalancerProbe")
2486                input_endpoint.append(load_balancer_probe)
2487                AzureXmlSerializer.data_to_xml(
2488                    [('Path', endpoint.load_balancer_probe.path)],
2489                    load_balancer_probe
2490                )
2491                AzureXmlSerializer.data_to_xml(
2492                    [('Port', endpoint.load_balancer_probe.port)],
2493                    load_balancer_probe
2494                )
2495                AzureXmlSerializer.data_to_xml(
2496                    [('Protocol', endpoint.load_balancer_probe.protocol)],
2497                    load_balancer_probe
2498                )
2499
2500            AzureXmlSerializer.data_to_xml(
2501                [('Protocol', endpoint.protocol)],
2502                input_endpoint
2503            )
2504            AzureXmlSerializer.data_to_xml(
2505                [
2506                    (
2507                        'EnableDirectServerReturn',
2508                        endpoint.enable_direct_server_return,
2509                        _lower
2510                    )
2511                ],
2512                input_endpoint
2513            )
2514
2515        subnet_names = ET.Element("SubnetNames")
2516        xml.append(subnet_names)
2517        for name in configuration.subnet_names:
2518            AzureXmlSerializer.data_to_xml(
2519                [('SubnetName', name)],
2520                subnet_names
2521            )
2522
2523        return xml
2524
2525    @staticmethod
2526    def role_to_xml(availability_set_name,
2527                    data_virtual_hard_disks,
2528                    network_configuration_set,
2529                    os_virtual_hard_disk,
2530                    vm_image_name,
2531                    role_name,
2532                    role_size,
2533                    role_type,
2534                    system_configuration_set,
2535                    xml):
2536
2537        AzureXmlSerializer.data_to_xml([('RoleName', role_name)], xml)
2538        AzureXmlSerializer.data_to_xml([('RoleType', role_type)], xml)
2539
2540        config_sets = ET.Element("ConfigurationSets")
2541        xml.append(config_sets)
2542
2543        if system_configuration_set is not None:
2544            config_set = ET.Element("ConfigurationSet")
2545            config_sets.append(config_set)
2546
2547            if isinstance(system_configuration_set, WindowsConfigurationSet):
2548                AzureXmlSerializer.windows_configuration_to_xml(
2549                    system_configuration_set,
2550                    config_set
2551                )
2552            elif isinstance(system_configuration_set, LinuxConfigurationSet):
2553                AzureXmlSerializer.linux_configuration_to_xml(
2554                    system_configuration_set,
2555                    config_set
2556                )
2557
2558        if network_configuration_set is not None:
2559            config_set = ET.Element("ConfigurationSet")
2560            config_sets.append(config_set)
2561
2562            AzureXmlSerializer.network_configuration_to_xml(
2563                network_configuration_set,
2564                config_set
2565            )
2566
2567        if availability_set_name is not None:
2568            AzureXmlSerializer.data_to_xml(
2569                [('AvailabilitySetName', availability_set_name)],
2570                xml
2571            )
2572
2573        if data_virtual_hard_disks is not None:
2574            vhds = ET.Element("DataVirtualHardDisks")
2575            xml.append(vhds)
2576
2577            for hd in data_virtual_hard_disks:
2578                vhd = ET.Element("DataVirtualHardDisk")
2579                vhds.append(vhd)
2580                AzureXmlSerializer.data_to_xml(
2581                    [('HostCaching', hd.host_caching)],
2582                    vhd
2583                )
2584                AzureXmlSerializer.data_to_xml(
2585                    [('DiskLabel', hd.disk_label)],
2586                    vhd
2587                )
2588                AzureXmlSerializer.data_to_xml(
2589                    [('DiskName', hd.disk_name)],
2590                    vhd
2591                )
2592                AzureXmlSerializer.data_to_xml(
2593                    [('Lun', hd.lun)],
2594                    vhd
2595                )
2596                AzureXmlSerializer.data_to_xml(
2597                    [('LogicalDiskSizeInGB', hd.logical_disk_size_in_gb)],
2598                    vhd
2599                )
2600                AzureXmlSerializer.data_to_xml(
2601                    [('MediaLink', hd.media_link)],
2602                    vhd
2603                )
2604
2605        if os_virtual_hard_disk is not None:
2606            hd = ET.Element("OSVirtualHardDisk")
2607            xml.append(hd)
2608            AzureXmlSerializer.data_to_xml(
2609                [('HostCaching', os_virtual_hard_disk.host_caching)],
2610                hd
2611            )
2612            AzureXmlSerializer.data_to_xml(
2613                [('DiskLabel', os_virtual_hard_disk.disk_label)],
2614                hd
2615            )
2616            AzureXmlSerializer.data_to_xml(
2617                [('DiskName', os_virtual_hard_disk.disk_name)],
2618                hd
2619            )
2620            AzureXmlSerializer.data_to_xml(
2621                [('MediaLink', os_virtual_hard_disk.media_link)],
2622                hd
2623            )
2624            AzureXmlSerializer.data_to_xml(
2625                [('SourceImageName', os_virtual_hard_disk.source_image_name)],
2626                hd
2627            )
2628
2629        if vm_image_name is not None:
2630            AzureXmlSerializer.data_to_xml(
2631                [('VMImageName', vm_image_name)],
2632                xml
2633            )
2634
2635        if role_size is not None:
2636            AzureXmlSerializer.data_to_xml([('RoleSize', role_size)], xml)
2637
2638        return xml
2639
2640    @staticmethod
2641    def add_role_to_xml(role_name,
2642                        system_configuration_set,
2643                        os_virtual_hard_disk,
2644                        role_type,
2645                        network_configuration_set,
2646                        availability_set_name,
2647                        data_virtual_hard_disks,
2648                        vm_image_name,
2649                        role_size):
2650        doc = AzureXmlSerializer.doc_from_xml('PersistentVMRole')
2651        xml = AzureXmlSerializer.role_to_xml(
2652            availability_set_name,
2653            data_virtual_hard_disks,
2654            network_configuration_set,
2655            os_virtual_hard_disk,
2656            vm_image_name,
2657            role_name,
2658            role_size,
2659            role_type,
2660            system_configuration_set,
2661            doc
2662        )
2663        result = ensure_string(ET.tostring(xml, encoding='utf-8'))
2664        return result
2665
2666    @staticmethod
2667    def update_role_to_xml(role_name,
2668                           os_virtual_hard_disk,
2669                           role_type,
2670                           network_configuration_set,
2671                           availability_set_name,
2672                           data_virtual_hard_disks,
2673                           vm_image_name,
2674                           role_size):
2675
2676        doc = AzureXmlSerializer.doc_from_xml('PersistentVMRole')
2677        AzureXmlSerializer.role_to_xml(
2678            availability_set_name,
2679            data_virtual_hard_disks,
2680            network_configuration_set,
2681            os_virtual_hard_disk,
2682            vm_image_name,
2683            role_name,
2684            role_size,
2685            role_type,
2686            None,
2687            doc
2688        )
2689
2690        result = ensure_string(ET.tostring(doc, encoding='utf-8'))
2691        return result
2692
2693    @staticmethod
2694    def capture_role_to_xml(post_capture_action,
2695                            target_image_name,
2696                            target_image_label,
2697                            provisioning_configuration):
2698        xml = AzureXmlSerializer.data_to_xml(
2699            [('OperationType', 'CaptureRoleOperation')]
2700        )
2701        AzureXmlSerializer.data_to_xml(
2702            [('PostCaptureAction', post_capture_action)],
2703            xml
2704        )
2705
2706        if provisioning_configuration is not None:
2707            provisioning_config = ET.Element("ProvisioningConfiguration")
2708            xml.append(provisioning_config)
2709
2710            if isinstance(provisioning_configuration, WindowsConfigurationSet):
2711                AzureXmlSerializer.windows_configuration_to_xml(
2712                    provisioning_configuration,
2713                    provisioning_config
2714                )
2715            elif isinstance(provisioning_configuration, LinuxConfigurationSet):
2716                AzureXmlSerializer.linux_configuration_to_xml(
2717                    provisioning_configuration,
2718                    provisioning_config
2719                )
2720
2721        AzureXmlSerializer.data_to_xml(
2722            [('TargetImageLabel', target_image_label)],
2723            xml
2724        )
2725        AzureXmlSerializer.data_to_xml(
2726            [('TargetImageName', target_image_name)],
2727            xml
2728        )
2729        doc = AzureXmlSerializer.doc_from_xml('CaptureRoleOperation', xml)
2730        result = ensure_string(ET.tostring(doc, encoding='utf-8'))
2731        return result
2732
2733    @staticmethod
2734    def virtual_machine_deployment_to_xml(deployment_name,
2735                                          deployment_slot,
2736                                          label,
2737                                          role_name,
2738                                          system_configuration_set,
2739                                          os_virtual_hard_disk,
2740                                          role_type,
2741                                          network_configuration_set,
2742                                          availability_set_name,
2743                                          data_virtual_hard_disks,
2744                                          role_size,
2745                                          virtual_network_name,
2746                                          vm_image_name):
2747
2748        doc = AzureXmlSerializer.doc_from_xml('Deployment')
2749        AzureXmlSerializer.data_to_xml([('Name', deployment_name)], doc)
2750        AzureXmlSerializer.data_to_xml(
2751            [('DeploymentSlot', deployment_slot)],
2752            doc
2753        )
2754        AzureXmlSerializer.data_to_xml([('Label', label)], doc)
2755
2756        role_list = ET.Element("RoleList")
2757        role = ET.Element("Role")
2758        role_list.append(role)
2759        doc.append(role_list)
2760
2761        AzureXmlSerializer.role_to_xml(
2762            availability_set_name,
2763            data_virtual_hard_disks,
2764            network_configuration_set,
2765            os_virtual_hard_disk,
2766            vm_image_name,
2767            role_name,
2768            role_size,
2769            role_type,
2770            system_configuration_set,
2771            role
2772        )
2773
2774        if virtual_network_name is not None:
2775            doc.append(
2776                AzureXmlSerializer.data_to_xml(
2777                    [('VirtualNetworkName', virtual_network_name)]
2778                )
2779            )
2780
2781        result = ensure_string(ET.tostring(doc, encoding='utf-8'))
2782        return result
2783
2784    @staticmethod
2785    def data_to_xml(data, xml=None):
2786        """
2787        Creates an xml fragment from the specified data.
2788           data: Array of tuples, where first: xml element name
2789                                        second: xml element text
2790                                        third: conversion function
2791        """
2792
2793        for element in data:
2794            name = element[0]
2795            val = element[1]
2796            if len(element) > 2:
2797                converter = element[2]
2798            else:
2799                converter = None
2800
2801            if val is not None:
2802                if converter is not None:
2803                    text = _str(converter(_str(val)))
2804                else:
2805                    text = _str(val)
2806
2807                entry = ET.Element(name)
2808                entry.text = text
2809                if xml is not None:
2810                    xml.append(entry)
2811                else:
2812                    return entry
2813        return xml
2814
2815    @staticmethod
2816    def doc_from_xml(document_element_name, inner_xml=None):
2817        """
2818        Wraps the specified xml in an xml root element with default azure
2819        namespaces
2820        """
2821        # Note: Namespaces don't work consistency in Python 2 and 3.
2822        """
2823        nsmap = {
2824            None: "http://www.w3.org/2001/XMLSchema-instance",
2825            "i": "http://www.w3.org/2001/XMLSchema-instance"
2826        }
2827
2828        xml.attrib["xmlns:i"] = "http://www.w3.org/2001/XMLSchema-instance"
2829        xml.attrib["xmlns"] = "http://schemas.microsoft.com/windowsazure"
2830        """
2831        xml = ET.Element(document_element_name)
2832        xml.set("xmlns", "http://schemas.microsoft.com/windowsazure")
2833
2834        if inner_xml is not None:
2835            xml.append(inner_xml)
2836
2837        return xml
2838
2839    @staticmethod
2840    def doc_from_data(document_element_name, data, extended_properties=None):
2841        doc = AzureXmlSerializer.doc_from_xml(document_element_name)
2842        AzureXmlSerializer.data_to_xml(data, doc)
2843        if extended_properties is not None:
2844            doc.append(
2845                AzureXmlSerializer.extended_properties_dict_to_xml_fragment(
2846                    extended_properties
2847                )
2848            )
2849
2850        result = ensure_string(ET.tostring(doc, encoding='utf-8'))
2851        return result
2852
2853    @staticmethod
2854    def extended_properties_dict_to_xml_fragment(extended_properties):
2855
2856        if extended_properties is not None and len(extended_properties) > 0:
2857            xml = ET.Element("ExtendedProperties")
2858            for key, val in extended_properties.items():
2859                extended_property = ET.Element("ExtendedProperty")
2860                name = ET.Element("Name")
2861                name.text = _str(key)
2862                value = ET.Element("Value")
2863                value.text = _str(val)
2864
2865                extended_property.append(name)
2866                extended_property.append(value)
2867                xml.append(extended_property)
2868
2869            return xml
2870
2871
2872"""
2873Data Classes
2874
2875Borrowed from the Azure SDK for Python.
2876"""
2877
2878
2879class WindowsAzureData(object):
2880
2881    """
2882    This is the base of data class.
2883    It is only used to check whether it is instance or not.
2884    """
2885    pass
2886
2887
2888class WindowsAzureDataTypedList(WindowsAzureData):
2889
2890    list_type = None
2891    xml_element_name = None
2892
2893    def __init__(self):
2894        self.items = _ListOf(self.list_type, self.xml_element_name)
2895
2896    def __iter__(self):
2897        return iter(self.items)
2898
2899    def __len__(self):
2900        return len(self.items)
2901
2902    def __getitem__(self, index):
2903        return self.items[index]
2904
2905
2906class OSVirtualHardDisk(WindowsAzureData):
2907
2908    def __init__(self, source_image_name=None, media_link=None,
2909                 host_caching=None, disk_label=None, disk_name=None):
2910        self.source_image_name = source_image_name
2911        self.media_link = media_link
2912        self.host_caching = host_caching
2913        self.disk_label = disk_label
2914        self.disk_name = disk_name
2915        self.os = ''  # undocumented, not used when adding a role
2916
2917
2918class LinuxConfigurationSet(WindowsAzureData):
2919
2920    def __init__(self,
2921                 host_name=None,
2922                 user_name=None,
2923                 user_password=None,
2924                 disable_ssh_password_authentication=None,
2925                 custom_data=None):
2926        self.configuration_set_type = 'LinuxProvisioningConfiguration'
2927        self.host_name = host_name
2928        self.user_name = user_name
2929        self.user_password = user_password
2930        self.disable_ssh_password_authentication = \
2931            disable_ssh_password_authentication
2932        self.ssh = SSH()
2933        self.custom_data = custom_data
2934
2935
2936class WindowsConfigurationSet(WindowsAzureData):
2937
2938    def __init__(self,
2939                 computer_name=None,
2940                 admin_password=None,
2941                 reset_password_on_first_logon=None,
2942                 enable_automatic_updates=None,
2943                 time_zone=None,
2944                 admin_user_name=None):
2945        self.configuration_set_type = 'WindowsProvisioningConfiguration'
2946        self.computer_name = computer_name
2947        self.admin_password = admin_password
2948        self.reset_password_on_first_logon = reset_password_on_first_logon
2949        self.enable_automatic_updates = enable_automatic_updates
2950        self.time_zone = time_zone
2951        self.admin_user_name = admin_user_name
2952        self.domain_join = DomainJoin()
2953        self.stored_certificate_settings = StoredCertificateSettings()
2954
2955
2956class DomainJoin(WindowsAzureData):
2957
2958    def __init__(self):
2959        self.credentials = Credentials()
2960        self.join_domain = ''
2961        self.machine_object_ou = ''
2962
2963
2964class Credentials(WindowsAzureData):
2965
2966    def __init__(self):
2967        self.domain = ''
2968        self.username = ''
2969        self.password = ''
2970
2971
2972class CertificateSetting(WindowsAzureData):
2973
2974    """
2975    Initializes a certificate setting.
2976
2977    thumbprint:
2978        Specifies the thumbprint of the certificate to be provisioned. The
2979        thumbprint must specify an existing service certificate.
2980    store_name:
2981        Specifies the name of the certificate store from which retrieve
2982        certificate.
2983    store_location:
2984        Specifies the target certificate store location on the virtual machine
2985        The only supported value is LocalMachine.
2986    """
2987
2988    def __init__(self, thumbprint='', store_name='', store_location=''):
2989        self.thumbprint = thumbprint
2990        self.store_name = store_name
2991        self.store_location = store_location
2992
2993
2994class StoredCertificateSettings(WindowsAzureDataTypedList):
2995    list_type = CertificateSetting
2996
2997    _repr_attributes = [
2998        'items'
2999    ]
3000
3001
3002class SSH(WindowsAzureData):
3003
3004    def __init__(self):
3005        self.public_keys = PublicKeys()
3006        self.key_pairs = KeyPairs()
3007
3008
3009class PublicKey(WindowsAzureData):
3010
3011    def __init__(self, fingerprint='', path=''):
3012        self.fingerprint = fingerprint
3013        self.path = path
3014
3015
3016class PublicKeys(WindowsAzureDataTypedList):
3017    list_type = PublicKey
3018
3019    _repr_attributes = [
3020        'items'
3021    ]
3022
3023
3024class AzureKeyPair(WindowsAzureData):
3025
3026    def __init__(self, fingerprint='', path=''):
3027        self.fingerprint = fingerprint
3028        self.path = path
3029
3030
3031class KeyPairs(WindowsAzureDataTypedList):
3032    list_type = AzureKeyPair
3033
3034    _repr_attributes = [
3035        'items'
3036    ]
3037
3038
3039class LoadBalancerProbe(WindowsAzureData):
3040
3041    def __init__(self):
3042        self.path = ''
3043        self.port = ''
3044        self.protocol = ''
3045
3046
3047class ConfigurationSet(WindowsAzureData):
3048
3049    def __init__(self):
3050        self.configuration_set_type = ''
3051        self.role_type = ''
3052        self.input_endpoints = ConfigurationSetInputEndpoints()
3053        self.subnet_names = ScalarListOf(str, 'SubnetName')
3054
3055
3056class ConfigurationSets(WindowsAzureDataTypedList):
3057    list_type = ConfigurationSet
3058
3059    _repr_attributes = [
3060        'items'
3061    ]
3062
3063
3064class ConfigurationSetInputEndpoint(WindowsAzureData):
3065
3066    def __init__(self,
3067                 name='',
3068                 protocol='',
3069                 port='',
3070                 local_port='',
3071                 load_balanced_endpoint_set_name='',
3072                 enable_direct_server_return=False):
3073        self.enable_direct_server_return = enable_direct_server_return
3074        self.load_balanced_endpoint_set_name = load_balanced_endpoint_set_name
3075        self.local_port = local_port
3076        self.name = name
3077        self.port = port
3078        self.load_balancer_probe = LoadBalancerProbe()
3079        self.protocol = protocol
3080
3081
3082class ConfigurationSetInputEndpoints(WindowsAzureDataTypedList):
3083    list_type = ConfigurationSetInputEndpoint
3084    xml_element_name = 'InputEndpoint'
3085
3086    _repr_attributes = [
3087        'items'
3088    ]
3089
3090
3091class Location(WindowsAzureData):
3092
3093    def __init__(self):
3094        self.name = ''
3095        self.display_name = ''
3096        self.available_services = ScalarListOf(str, 'AvailableService')
3097        self.compute_capabilities = ComputeCapability()
3098
3099
3100class Locations(WindowsAzureDataTypedList):
3101    list_type = Location
3102
3103    _repr_attributes = [
3104        'items'
3105    ]
3106
3107
3108class ComputeCapability(WindowsAzureData):
3109
3110    def __init__(self):
3111        self.virtual_machines_role_sizes = ScalarListOf(str, 'RoleSize')
3112
3113
3114class VirtualMachinesRoleSizes(WindowsAzureData):
3115
3116    def __init__(self):
3117        self.role_size = ScalarListOf(str, 'RoleSize')
3118
3119
3120class OSImage(WindowsAzureData):
3121
3122    def __init__(self):
3123        self.affinity_group = ''
3124        self.category = ''
3125        self.location = ''
3126        self.logical_size_in_gb = 0
3127        self.label = ''
3128        self.media_link = ''
3129        self.name = ''
3130        self.os = ''
3131        self.eula = ''
3132        self.description = ''
3133
3134
3135class Images(WindowsAzureDataTypedList):
3136    list_type = OSImage
3137
3138    _repr_attributes = [
3139        'items'
3140    ]
3141
3142
3143class VMImage(WindowsAzureData):
3144
3145    def __init__(self):
3146        self.name = ''
3147        self.label = ''
3148        self.category = ''
3149        self.os_disk_configuration = OSDiskConfiguration()
3150        self.service_name = ''
3151        self.deployment_name = ''
3152        self.role_name = ''
3153        self.location = ''
3154        self.affinity_group = ''
3155
3156
3157class VMImages(WindowsAzureDataTypedList):
3158    list_type = VMImage
3159
3160    _repr_attributes = [
3161        'items'
3162    ]
3163
3164
3165class VirtualIP(WindowsAzureData):
3166
3167    def __init__(self):
3168        self.address = ''
3169        self.is_dns_programmed = ''
3170        self.name = ''
3171
3172
3173class VirtualIPs(WindowsAzureDataTypedList):
3174    list_type = VirtualIP
3175
3176    _repr_attributes = [
3177        'items'
3178    ]
3179
3180
3181class HostedService(WindowsAzureData, ReprMixin):
3182    _repr_attributes = [
3183        'service_name',
3184        'url'
3185    ]
3186
3187    def __init__(self):
3188        self.url = ''
3189        self.service_name = ''
3190        self.hosted_service_properties = HostedServiceProperties()
3191        self.deployments = Deployments()
3192
3193
3194class HostedServices(WindowsAzureDataTypedList, ReprMixin):
3195    list_type = HostedService
3196
3197    _repr_attributes = [
3198        'items'
3199    ]
3200
3201
3202class HostedServiceProperties(WindowsAzureData):
3203
3204    def __init__(self):
3205        self.description = ''
3206        self.location = ''
3207        self.affinity_group = ''
3208        self.label = _Base64String()
3209        self.status = ''
3210        self.date_created = ''
3211        self.date_last_modified = ''
3212        self.extended_properties = _DictOf(
3213            'ExtendedProperty',
3214            'Name',
3215            'Value'
3216        )
3217
3218
3219class Deployment(WindowsAzureData):
3220
3221    def __init__(self):
3222        self.name = ''
3223        self.deployment_slot = ''
3224        self.private_id = ''
3225        self.status = ''
3226        self.label = _Base64String()
3227        self.url = ''
3228        self.configuration = _Base64String()
3229        self.role_instance_list = RoleInstanceList()
3230        self.upgrade_status = UpgradeStatus()
3231        self.upgrade_domain_count = ''
3232        self.role_list = RoleList()
3233        self.sdk_version = ''
3234        self.input_endpoint_list = InputEndpoints()
3235        self.locked = False
3236        self.rollback_allowed = False
3237        self.persistent_vm_downtime_info = PersistentVMDowntimeInfo()
3238        self.created_time = ''
3239        self.last_modified_time = ''
3240        self.extended_properties = _DictOf(
3241            'ExtendedProperty',
3242            'Name',
3243            'Value'
3244        )
3245        self.virtual_ips = VirtualIPs()
3246
3247
3248class Deployments(WindowsAzureDataTypedList):
3249    list_type = Deployment
3250
3251    _repr_attributes = [
3252        'items'
3253    ]
3254
3255
3256class UpgradeStatus(WindowsAzureData):
3257
3258    def __init__(self):
3259        self.upgrade_type = ''
3260        self.current_upgrade_domain_state = ''
3261        self.current_upgrade_domain = ''
3262
3263
3264class RoleInstance(WindowsAzureData):
3265
3266    def __init__(self):
3267        self.role_name = ''
3268        self.instance_name = ''
3269        self.instance_status = ''
3270        self.instance_upgrade_domain = 0
3271        self.instance_fault_domain = 0
3272        self.instance_size = ''
3273        self.instance_state_details = ''
3274        self.instance_error_code = ''
3275        self.ip_address = ''
3276        self.instance_endpoints = InstanceEndpoints()
3277        self.power_state = ''
3278        self.fqdn = ''
3279        self.host_name = ''
3280
3281
3282class RoleInstanceList(WindowsAzureDataTypedList):
3283    list_type = RoleInstance
3284
3285    _repr_attributes = [
3286        'items'
3287    ]
3288
3289
3290class InstanceEndpoint(WindowsAzureData):
3291
3292    def __init__(self):
3293        self.name = ''
3294        self.vip = ''
3295        self.public_port = ''
3296        self.local_port = ''
3297        self.protocol = ''
3298
3299
3300class InstanceEndpoints(WindowsAzureDataTypedList):
3301    list_type = InstanceEndpoint
3302
3303    _repr_attributes = [
3304        'items'
3305    ]
3306
3307
3308class InputEndpoint(WindowsAzureData):
3309
3310    def __init__(self):
3311        self.role_name = ''
3312        self.vip = ''
3313        self.port = ''
3314
3315
3316class InputEndpoints(WindowsAzureDataTypedList):
3317    list_type = InputEndpoint
3318
3319    _repr_attributes = [
3320        'items'
3321    ]
3322
3323
3324class Role(WindowsAzureData):
3325
3326    def __init__(self):
3327        self.role_name = ''
3328        self.os_version = ''
3329
3330
3331class RoleList(WindowsAzureDataTypedList):
3332    list_type = Role
3333
3334    _repr_attributes = [
3335        'items'
3336    ]
3337
3338
3339class PersistentVMDowntimeInfo(WindowsAzureData):
3340
3341    def __init__(self):
3342        self.start_time = ''
3343        self.end_time = ''
3344        self.status = ''
3345
3346
3347class AsynchronousOperationResult(WindowsAzureData):
3348
3349    def __init__(self, request_id=None):
3350        self.request_id = request_id
3351
3352
3353class Disk(WindowsAzureData):
3354
3355    def __init__(self):
3356        self.affinity_group = ''
3357        self.attached_to = AttachedTo()
3358        self.has_operating_system = ''
3359        self.is_corrupted = ''
3360        self.location = ''
3361        self.logical_disk_size_in_gb = 0
3362        self.label = ''
3363        self.media_link = ''
3364        self.name = ''
3365        self.os = ''
3366        self.source_image_name = ''
3367
3368
3369class Disks(WindowsAzureDataTypedList):
3370    list_type = Disk
3371
3372    _repr_attributes = [
3373        'items'
3374    ]
3375
3376
3377class AttachedTo(WindowsAzureData):
3378
3379    def __init__(self):
3380        self.hosted_service_name = ''
3381        self.deployment_name = ''
3382        self.role_name = ''
3383
3384
3385class OperationError(WindowsAzureData):
3386
3387    def __init__(self):
3388        self.code = ''
3389        self.message = ''
3390
3391
3392class Operation(WindowsAzureData):
3393
3394    def __init__(self):
3395        self.id = ''
3396        self.status = ''
3397        self.http_status_code = ''
3398        self.error = OperationError()
3399
3400
3401class OperatingSystem(WindowsAzureData):
3402
3403    def __init__(self):
3404        self.version = ''
3405        self.label = _Base64String()
3406        self.is_default = True
3407        self.is_active = True
3408        self.family = 0
3409        self.family_label = _Base64String()
3410
3411
3412class OSDiskConfiguration(WindowsAzureData):
3413
3414    def __init__(self):
3415        self.name = ''
3416        self.host_caching = ''
3417        self.os_state = ''
3418        self.os = ''
3419        self.media_link = ''
3420        self.logical_disk_size_in_gb = 0
3421
3422
3423class OperatingSystems(WindowsAzureDataTypedList):
3424    list_type = OperatingSystem
3425
3426    _repr_attributes = [
3427        'items'
3428    ]
3429
3430
3431class OperatingSystemFamily(WindowsAzureData):
3432
3433    def __init__(self):
3434        self.name = ''
3435        self.label = _Base64String()
3436        self.operating_systems = OperatingSystems()
3437
3438
3439class OperatingSystemFamilies(WindowsAzureDataTypedList):
3440    list_type = OperatingSystemFamily
3441
3442    _repr_attributes = [
3443        'items'
3444    ]
3445
3446
3447class Subscription(WindowsAzureData):
3448
3449    def __init__(self):
3450        self.subscription_id = ''
3451        self.subscription_name = ''
3452        self.subscription_status = ''
3453        self.account_admin_live_email_id = ''
3454        self.service_admin_live_email_id = ''
3455        self.max_core_count = 0
3456        self.max_storage_accounts = 0
3457        self.max_hosted_services = 0
3458        self.current_core_count = 0
3459        self.current_hosted_services = 0
3460        self.current_storage_accounts = 0
3461        self.max_virtual_network_sites = 0
3462        self.max_local_network_sites = 0
3463        self.max_dns_servers = 0
3464
3465
3466class AvailabilityResponse(WindowsAzureData):
3467
3468    def __init__(self):
3469        self.result = False
3470
3471
3472class SubscriptionCertificate(WindowsAzureData):
3473
3474    def __init__(self):
3475        self.subscription_certificate_public_key = ''
3476        self.subscription_certificate_thumbprint = ''
3477        self.subscription_certificate_data = ''
3478        self.created = ''
3479
3480
3481class SubscriptionCertificates(WindowsAzureDataTypedList):
3482    list_type = SubscriptionCertificate
3483
3484    _repr_attributes = [
3485        'items'
3486    ]
3487
3488
3489class AzureHTTPRequest(object):
3490    def __init__(self):
3491        self.host = ''
3492        self.method = ''
3493        self.path = ''
3494        self.query = []      # list of (name, value)
3495        self.headers = {}    # list of (header name, header value)
3496        self.body = ''
3497        self.protocol_override = None
3498
3499
3500class AzureHTTPResponse(object):
3501    def __init__(self, status, message, headers, body):
3502        self.status = status
3503        self.message = message
3504        self.headers = headers
3505        self.body = body
3506
3507
3508"""
3509Helper classes and functions.
3510"""
3511
3512
3513class _Base64String(str):
3514    pass
3515
3516
3517class _ListOf(list):
3518
3519    """
3520    A list which carries with it the type that's expected to go in it.
3521    Used for deserializaion and construction of the lists
3522    """
3523
3524    def __init__(self, list_type, xml_element_name=None):
3525        self.list_type = list_type
3526        if xml_element_name is None:
3527            self.xml_element_name = list_type.__name__
3528        else:
3529            self.xml_element_name = xml_element_name
3530        super(_ListOf, self).__init__()
3531
3532
3533class ScalarListOf(list):
3534
3535    """
3536    A list of scalar types which carries with it the type that's
3537    expected to go in it along with its xml element name.
3538    Used for deserializaion and construction of the lists
3539    """
3540
3541    def __init__(self, list_type, xml_element_name):
3542        self.list_type = list_type
3543        self.xml_element_name = xml_element_name
3544        super(ScalarListOf, self).__init__()
3545
3546
3547class _DictOf(dict):
3548
3549    """
3550    A dict which carries with it the xml element names for key,val.
3551    Used for deserializaion and construction of the lists
3552    """
3553
3554    def __init__(self,
3555                 pair_xml_element_name,
3556                 key_xml_element_name,
3557                 value_xml_element_name):
3558        self.pair_xml_element_name = pair_xml_element_name
3559        self.key_xml_element_name = key_xml_element_name
3560        self.value_xml_element_name = value_xml_element_name
3561        super(_DictOf, self).__init__()
3562
3563
3564class AzureNodeLocation(NodeLocation):
3565    # we can also have something in here for available services which is an
3566    # extra to the API with Azure
3567
3568    def __init__(self, id, name, country, driver, available_services,
3569                 virtual_machine_role_sizes):
3570        super(AzureNodeLocation, self).__init__(id, name, country, driver)
3571        self.available_services = available_services
3572        self.virtual_machine_role_sizes = virtual_machine_role_sizes
3573
3574    def __repr__(self):
3575        return (
3576            (
3577                '<AzureNodeLocation: id=%s, name=%s, country=%s, '
3578                'driver=%s services=%s virtualMachineRoleSizes=%s >'
3579            ) % (
3580                self.id,
3581                self.name,
3582                self.country,
3583                self.driver.name,
3584                ','.join(self.available_services),
3585                ','.join(self.virtual_machine_role_sizes)
3586            )
3587        )
3588