| # Licensed to the Apache Software Foundation (ASF) under one or more |
| # contributor license agreements. See the NOTICE file distributed with |
| # this work for additional information regarding copyright ownership. |
| # The ASF licenses this file to You under the Apache License, Version 2.0 |
| # (the "License"); you may not use this file except in compliance with |
| # the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| """ |
| Dimension Data Driver |
| """ |
| |
| try: |
| from lxml import etree as ET |
| except ImportError: |
| from xml.etree import ElementTree as ET |
| |
| from libcloud.compute.base import NodeDriver, Node |
| from libcloud.compute.base import NodeSize, NodeImage, NodeLocation |
| from libcloud.common.dimensiondata import (DimensionDataConnection, |
| DimensionDataStatus) |
| from libcloud.common.dimensiondata import DimensionDataNetwork |
| from libcloud.common.dimensiondata import DimensionDataNetworkDomain |
| from libcloud.common.dimensiondata import DimensionDataVlan |
| from libcloud.common.dimensiondata import DimensionDataPublicIpBlock |
| from libcloud.common.dimensiondata import DimensionDataFirewallRule |
| from libcloud.common.dimensiondata import DimensionDataFirewallAddress |
| from libcloud.common.dimensiondata import DimensionDataNatRule |
| from libcloud.common.dimensiondata import NetworkDomainServicePlan |
| from libcloud.common.dimensiondata import API_ENDPOINTS |
| from libcloud.common.dimensiondata import DEFAULT_REGION |
| from libcloud.common.dimensiondata import TYPES_URN |
| from libcloud.common.dimensiondata import SERVER_NS |
| from libcloud.common.dimensiondata import NETWORK_NS |
| from libcloud.utils.xml import fixxpath, findtext, findall |
| from libcloud.compute.types import NodeState, Provider |
| |
| |
| class DimensionDataNodeDriver(NodeDriver): |
| """ |
| DimensionData node driver. |
| """ |
| |
| selected_region = None |
| connectionCls = DimensionDataConnection |
| name = 'DimensionData' |
| website = 'http://www.dimensiondata.com/' |
| type = Provider.DIMENSIONDATA |
| features = {'create_node': ['password']} |
| api_version = 1.0 |
| |
| def __init__(self, key, secret=None, secure=True, host=None, port=None, |
| api_version=None, region=DEFAULT_REGION, **kwargs): |
| |
| if region not in API_ENDPOINTS: |
| raise ValueError('Invalid region: %s' % (region)) |
| |
| self.selected_region = API_ENDPOINTS[region] |
| |
| super(DimensionDataNodeDriver, self).__init__(key=key, secret=secret, |
| secure=secure, host=host, |
| port=port, |
| api_version=api_version, |
| region=region, |
| **kwargs) |
| |
| def _ex_connection_class_kwargs(self): |
| """ |
| Add the region to the kwargs before the connection is instantiated |
| """ |
| |
| kwargs = super(DimensionDataNodeDriver, |
| self)._ex_connection_class_kwargs() |
| kwargs['region'] = self.selected_region |
| return kwargs |
| |
| def create_node(self, name, image, auth, ex_description, |
| ex_network=None, ex_network_domain=None, |
| ex_vlan=None, |
| ex_is_started=True, **kwargs): |
| """ |
| Create a new DimensionData node |
| |
| :keyword name: String with a name for this new node (required) |
| :type name: ``str`` |
| |
| :keyword image: OS Image to boot on node. (required) |
| :type image: :class:`NodeImage` |
| |
| :keyword auth: Initial authentication information for the |
| node (required) |
| :type auth: :class:`NodeAuthPassword` |
| |
| :keyword ex_description: description for this node (required) |
| :type ex_description: ``str`` |
| |
| :keyword ex_network: Network to create the node within (required, |
| unless using Network Domain) |
| :type ex_network: :class:`DimensionDataNetwork` |
| |
| :keyword ex_network_domain: Network Domain to create the node |
| (required unless using network) |
| :type ex_network_domain: :class:`DimensionDataNetworkDomain` |
| |
| :keyword ex_vlan: VLAN to create the node within |
| (required unless using network) |
| :type ex_vlan: :class:`DimensionDataVlan` |
| |
| :keyword ex_is_started: Start server after creation? default |
| true (required) |
| :type ex_is_started: ``bool`` |
| |
| :return: The newly created :class:`Node`. |
| :rtype: :class:`Node` |
| """ |
| |
| password = None |
| auth_obj = self._get_and_check_auth(auth) |
| password = auth_obj.password |
| |
| if not isinstance(ex_network, DimensionDataNetwork): |
| if not isinstance(ex_network_domain, DimensionDataNetworkDomain): |
| raise ValueError('ex_network must be of DimensionDataNetwork ' |
| 'type or ex_network_domain must be of ' |
| 'DimensionDataNetworkDomain type') |
| |
| server_elm = ET.Element('deployServer', {'xmlns': TYPES_URN}) |
| ET.SubElement(server_elm, "name").text = name |
| ET.SubElement(server_elm, "description").text = ex_description |
| ET.SubElement(server_elm, "imageId").text = image.id |
| ET.SubElement(server_elm, "start").text = str(ex_is_started).lower() |
| ET.SubElement(server_elm, "administratorPassword").text = password |
| |
| if ex_network is not None: |
| network_elm = ET.SubElement(server_elm, "network") |
| ET.SubElement(network_elm, "networkId").text = ex_network.id |
| if ex_network_domain is not None: |
| network_inf_elm = ET.SubElement(server_elm, "networkInfo", |
| {'networkDomainId': |
| ex_network_domain.id}) |
| pri_nic = ET.SubElement(network_inf_elm, "primaryNic") |
| ET.SubElement(pri_nic, "vlanId").text = ex_vlan.id |
| |
| response = self.connection.request_with_orgId_api_2( |
| 'server/deployServer', |
| method='POST', |
| data=ET.tostring(server_elm)).object |
| |
| node_id = None |
| for info in findall(response, 'info', TYPES_URN): |
| if info.get('name') == 'serverId': |
| node_id = info.get('value') |
| |
| node = self.ex_get_node_by_id(node_id) |
| |
| if getattr(auth_obj, "generated", False): |
| node.extra['password'] = auth_obj.password |
| |
| return node |
| |
| def destroy_node(self, node): |
| request_elm = ET.Element('deleteServer', |
| {'xmlns': TYPES_URN, 'id': node.id}) |
| body = self.connection.request_with_orgId_api_2( |
| 'server/deleteServer', |
| method='POST', |
| data=ET.tostring(request_elm)).object |
| response_code = findtext(body, 'responseCode', TYPES_URN) |
| return response_code in ['IN_PROGRESS', 'OK'] |
| |
| def reboot_node(self, node): |
| request_elm = ET.Element('rebootServer', |
| {'xmlns': TYPES_URN, 'id': node.id}) |
| body = self.connection.request_with_orgId_api_2( |
| 'server/rebootServer', |
| method='POST', |
| data=ET.tostring(request_elm)).object |
| response_code = findtext(body, 'responseCode', TYPES_URN) |
| return response_code in ['IN_PROGRESS', 'OK'] |
| |
| def list_nodes(self): |
| nodes = self._to_nodes( |
| self.connection.request_with_orgId_api_2('server/server').object) |
| |
| return nodes |
| |
| def list_images(self, location=None): |
| """ |
| return a list of available images |
| Currently only returns the default 'base OS images' provided by |
| DimensionData. Customer images (snapshots) are not yet supported. |
| |
| @inherits: :class:`NodeDriver.list_images` |
| """ |
| params = {} |
| if location is not None: |
| params['location'] = location.id |
| |
| return self._to_base_images( |
| self.connection.request_api_1('base/imageWithDiskSpeed', |
| params=params) |
| .object) |
| |
| def list_sizes(self, location=None): |
| """ |
| return a list of available sizes |
| Currently, the size of the node is dictated by the chosen OS base |
| image, they cannot be set explicitly. |
| |
| @inherits: :class:`NodeDriver.list_sizes` |
| """ |
| return [ |
| NodeSize(id=1, |
| name="default", |
| ram=0, |
| disk=0, |
| bandwidth=0, |
| price=0, |
| driver=self.connection.driver), |
| ] |
| |
| def list_locations(self): |
| """ |
| list locations (datacenters) available for instantiating servers and |
| networks. |
| |
| @inherits: :class:`NodeDriver.list_locations` |
| """ |
| return self._to_locations( |
| self.connection |
| .request_with_orgId_api_2('infrastructure/datacenter').object) |
| |
| def list_networks(self, location=None): |
| """ |
| List networks deployed across all data center locations for your |
| organization. The response includes the location of each network. |
| |
| |
| :keyword location: The location |
| :type location: :class:`NodeLocation` |
| |
| :return: a list of DimensionDataNetwork objects |
| :rtype: ``list`` of :class:`DimensionDataNetwork` |
| """ |
| url_ext = '' |
| if location is not None: |
| url_ext = '/' + location.id |
| |
| return self._to_networks( |
| self.connection |
| .request_with_orgId_api_1('networkWithLocation%s' % url_ext) |
| .object) |
| |
| def _to_base_images(self, object): |
| images = [] |
| locations = self.list_locations() |
| |
| for element in object.findall(fixxpath("image", SERVER_NS)): |
| images.append(self._to_base_image(element, locations)) |
| |
| return images |
| |
| def _to_base_image(self, element, locations): |
| # Eventually we will probably need multiple _to_image() functions |
| # that parse <ServerImage> differently than <DeployedImage>. |
| # DeployedImages are customer snapshot images, and ServerImages are |
| # 'base' images provided by DimensionData |
| location_id = element.get('location') |
| location = list(filter(lambda x: x.id == location_id, |
| locations))[0] |
| |
| extra = { |
| 'description': findtext(element, 'description', SERVER_NS), |
| 'OS_type': findtext(element, 'operatingSystem/type', SERVER_NS), |
| 'OS_displayName': findtext(element, 'operatingSystem/displayName', |
| SERVER_NS), |
| 'cpuCount': findtext(element, 'cpuCount', SERVER_NS), |
| 'resourcePath': findtext(element, 'resourcePath', SERVER_NS), |
| 'memory': findtext(element, 'memory', SERVER_NS), |
| 'osStorage': findtext(element, 'osStorage', SERVER_NS), |
| 'additionalStorage': findtext(element, 'additionalStorage', |
| SERVER_NS), |
| 'created': findtext(element, 'created', SERVER_NS), |
| 'location': location, |
| } |
| |
| return NodeImage(id=element.get('id'), |
| name=str(findtext(element, 'name', SERVER_NS)), |
| extra=extra, |
| driver=self.connection.driver) |
| |
| def ex_start_node(self, node): |
| """ |
| Powers on an existing deployed server |
| |
| :param node: Node which should be used |
| :type node: :class:`Node` |
| |
| :rtype: ``bool`` |
| """ |
| request_elm = ET.Element('startServer', |
| {'xmlns': TYPES_URN, 'id': node.id}) |
| body = self.connection.request_with_orgId_api_2( |
| 'server/startServer', |
| method='POST', |
| data=ET.tostring(request_elm)).object |
| response_code = findtext(body, 'responseCode', TYPES_URN) |
| return response_code in ['IN_PROGRESS', 'OK'] |
| |
| def ex_shutdown_graceful(self, node): |
| """ |
| This function will attempt to "gracefully" stop a server by |
| initiating a shutdown sequence within the guest operating system. |
| A successful response on this function means the system has |
| successfully passed the request into the operating system. |
| |
| :param node: Node which should be used |
| :type node: :class:`Node` |
| |
| :rtype: ``bool`` |
| """ |
| request_elm = ET.Element('shutdownServer', |
| {'xmlns': TYPES_URN, 'id': node.id}) |
| body = self.connection.request_with_orgId_api_2( |
| 'server/shutdownServer', |
| method='POST', |
| data=ET.tostring(request_elm)).object |
| response_code = findtext(body, 'responseCode', TYPES_URN) |
| return response_code in ['IN_PROGRESS', 'OK'] |
| |
| def ex_power_off(self, node): |
| """ |
| This function will abruptly power-off a server. Unlike |
| ex_shutdown_graceful, success ensures the node will stop but some OS |
| and application configurations may be adversely affected by the |
| equivalent of pulling the power plug out of the machine. |
| |
| :param node: Node which should be used |
| :type node: :class:`Node` |
| |
| :rtype: ``bool`` |
| """ |
| request_elm = ET.Element('powerOffServer', |
| {'xmlns': TYPES_URN, 'id': node.id}) |
| body = self.connection.request_with_orgId_api_2( |
| 'server/powerOffServer', |
| method='POST', |
| data=ET.tostring(request_elm)).object |
| response_code = findtext(body, 'responseCode', TYPES_URN) |
| return response_code in ['IN_PROGRESS', 'OK'] |
| |
| def ex_reset(self, node): |
| """ |
| This function will abruptly reset a server. Unlike |
| reboot_node, success ensures the node will restart but some OS |
| and application configurations may be adversely affected by the |
| equivalent of pulling the power plug out of the machine. |
| |
| :param node: Node which should be used |
| :type node: :class:`Node` |
| |
| :rtype: ``bool`` |
| """ |
| request_elm = ET.Element('resetServer', |
| {'xmlns': TYPES_URN, 'id': node.id}) |
| body = self.connection.request_with_orgId_api_2( |
| 'server/resetServer', |
| method='POST', |
| data=ET.tostring(request_elm)).object |
| response_code = findtext(body, 'responseCode', TYPES_URN) |
| return response_code in ['IN_PROGRESS', 'OK'] |
| |
| def ex_attach_node_to_vlan(self, node, vlan): |
| request = ET.Element('addNic', |
| {'xmlns': TYPES_URN}) |
| ET.SubElement(request, 'serverId').text = node.id |
| nic = ET.SubElement(request, 'nic') |
| ET.SubElement(nic, 'vlanId').text = vlan.id |
| |
| response = self.connection.request_with_orgId_api_2( |
| 'server/addNic', |
| method='POST', |
| data=ET.tostring(request)).object |
| response_code = findtext(response, 'responseCode', TYPES_URN) |
| return response_code in ['IN_PROGRESS', 'OK'] |
| |
| def ex_destroy_nic(self, nic_id): |
| request = ET.Element('removeNic', |
| {'xmlns': TYPES_URN, |
| 'id': nic_id}) |
| |
| response = self.connection.request_with_orgId_api_2( |
| 'server/removeNic', |
| method='POST', |
| data=ET.tostring(request)).object |
| response_code = findtext(response, 'responseCode', TYPES_URN) |
| return response_code in ['IN_PROGRESS', 'OK'] |
| |
| def ex_list_networks(self, location=None): |
| """ |
| List networks deployed across all data center locations for your |
| organization. The response includes the location of each network. |
| |
| :return: a list of DimensionDataNetwork objects |
| :rtype: ``list`` of :class:`DimensionDataNetwork` |
| """ |
| params = {} |
| if location is not None: |
| params['location'] = location.id |
| |
| response = self.connection \ |
| .request_with_orgId_api_1('networkWithLocation', |
| params=params).object |
| return self._to_networks(response) |
| |
| def ex_get_network_domain(self, network_domain_id): |
| locations = self.list_locations() |
| net = self.connection.request_with_orgId_api_2( |
| 'network/networkDomain/%s' % network_domain_id).object |
| return self._to_network_domain(net, locations) |
| |
| def ex_list_network_domains(self, location=None): |
| """ |
| List networks deployed across all data center locations for your |
| organization. The response includes the location of each network. |
| |
| :return: a list of DimensionDataNetwork objects |
| :rtype: ``list`` of :class:`DimensionDataNetwork` |
| """ |
| params = {} |
| if location is not None: |
| params['datacenterId'] = location.id |
| |
| response = self.connection \ |
| .request_with_orgId_api_2('network/networkDomain', |
| params=params).object |
| return self._to_network_domains(response) |
| |
| def ex_create_network_domain(self, location, name, service_plan, |
| description=None): |
| """ |
| Deploy a new network domain to a data center |
| """ |
| create_node = ET.Element('deployNetworkDomain', {'xmlns': TYPES_URN}) |
| ET.SubElement(create_node, "datacenterId").text = location.id |
| ET.SubElement(create_node, "name").text = name |
| if description is not None: |
| ET.SubElement(create_node, "description").text = description |
| ET.SubElement(create_node, "type").text = service_plan |
| |
| response = self.connection.request_with_orgId_api_2( |
| 'network/deployNetworkDomain', |
| method='POST', |
| data=ET.tostring(create_node)).object |
| |
| network_domain_id = None |
| |
| for info in findall(response, 'info', TYPES_URN): |
| if info.get('name') == 'networkDomainId': |
| network_domain_id = info.get('value') |
| |
| return DimensionDataNetworkDomain( |
| id=network_domain_id, |
| name=name, |
| description=description, |
| location=location, |
| status=NodeState.RUNNING, |
| plan=service_plan |
| ) |
| |
| def ex_update_network_domain(self, network_domain): |
| """ |
| Update the properties of a network domain |
| """ |
| edit_node = ET.Element('editNetworkDomain', {'xmlns': TYPES_URN}) |
| edit_node.set('id', network_domain.id) |
| ET.SubElement(edit_node, "name").text = network_domain.name |
| if network_domain.description is not None: |
| ET.SubElement(edit_node, "description").text \ |
| = network_domain.description |
| ET.SubElement(edit_node, "type").text = network_domain.plan |
| |
| self.connection.request_with_orgId_api_2( |
| 'network/editNetworkDomain', |
| method='POST', |
| data=ET.tostring(edit_node)).object |
| |
| return network_domain |
| |
| def ex_delete_network_domain(self, network_domain): |
| delete_node = ET.Element('deleteNetworkDomain', {'xmlns': TYPES_URN}) |
| delete_node.set('id', network_domain.id) |
| result = self.connection.request_with_orgId_api_2( |
| 'network/deleteNetworkDomain', |
| method='POST', |
| data=ET.tostring(delete_node)).object |
| |
| response_code = findtext(result, 'responseCode', TYPES_URN) |
| return response_code in ['IN_PROGRESS', 'OK'] |
| |
| def ex_create_vlan(self, |
| network_domain, |
| name, |
| private_ipv4_base_address, |
| description=None, |
| private_ipv4_prefix_size='24'): |
| """ |
| Deploy a new VLAN to a network domain |
| """ |
| create_node = ET.Element('deployVlan', {'xmlns': TYPES_URN}) |
| ET.SubElement(create_node, "networkDomainId").text = network_domain.id |
| ET.SubElement(create_node, "name").text = name |
| if description is not None: |
| ET.SubElement(create_node, "description").text = description |
| ET.SubElement(create_node, "privateIpv4BaseAddress").text = \ |
| private_ipv4_base_address |
| ET.SubElement(create_node, "privateIpv4PrefixSize").text = \ |
| private_ipv4_prefix_size |
| |
| response = self.connection.request_with_orgId_api_2( |
| 'network/deployVlan', |
| method='POST', |
| data=ET.tostring(create_node)).object |
| |
| vlan_id = None |
| |
| for info in findall(response, 'info', TYPES_URN): |
| if info.get('name') == 'vlanId': |
| vlan_id = info.get('value') |
| |
| return DimensionDataVlan( |
| id=vlan_id, |
| name=name, |
| description=description, |
| location=network_domain.location, |
| status=NodeState.RUNNING, |
| private_ipv4_range_address=private_ipv4_base_address, |
| private_ipv4_range_size=private_ipv4_prefix_size |
| ) |
| |
| def ex_get_vlan(self, vlan_id): |
| locations = self.list_locations() |
| vlan = self.connection.request_with_orgId_api_2( |
| 'network/vlan/%s' % vlan_id).object |
| return self._to_vlan(vlan, locations) |
| |
| def ex_update_vlan(self, vlan): |
| """ |
| Updates the properties of the given VLAN |
| Only name and description are updated |
| """ |
| edit_node = ET.Element('editVlan', {'xmlns': TYPES_URN}) |
| edit_node.set('id', vlan.id) |
| ET.SubElement(edit_node, "name").text = vlan.name |
| if vlan.description is not None: |
| ET.SubElement(edit_node, "description").text \ |
| = vlan.description |
| |
| self.connection.request_with_orgId_api_2( |
| 'network/editVlan', |
| method='POST', |
| data=ET.tostring(edit_node)).object |
| |
| return vlan |
| |
| def ex_expand_vlan(self, vlan): |
| """ |
| Expands the VLAN to the prefix size in private_ipv4_range_size |
| The expansion will |
| not be permitted if the proposed IP space overlaps with an |
| already deployed VLANs IP space. |
| """ |
| edit_node = ET.Element('expandVlan', {'xmlns': TYPES_URN}) |
| edit_node.set('id', vlan.id) |
| ET.SubElement(edit_node, "privateIpv4PrefixSize").text =\ |
| vlan.private_ipv4_range_size |
| |
| self.connection.request_with_orgId_api_2( |
| 'network/expandVlan', |
| method='POST', |
| data=ET.tostring(edit_node)).object |
| |
| return vlan |
| |
| def ex_delete_vlan(self, vlan): |
| delete_node = ET.Element('deleteVlan', {'xmlns': TYPES_URN}) |
| delete_node.set('id', vlan.id) |
| result = self.connection.request_with_orgId_api_2( |
| 'network/deleteVlan', |
| method='POST', |
| data=ET.tostring(delete_node)).object |
| |
| response_code = findtext(result, 'responseCode', TYPES_URN) |
| return response_code in ['IN_PROGRESS', 'OK'] |
| |
| def ex_list_vlans(self, location=None, network_domain=None): |
| """ |
| List VLANs available in a given networkDomain |
| |
| :return: a list of DimensionDataVlan objects |
| :rtype: ``list`` of :class:`DimensionDataVlan` |
| """ |
| params = {} |
| if location is not None: |
| params['datacenterId'] = location.id |
| if network_domain is not None: |
| params['networkDomainId'] = network_domain.id |
| response = self.connection.request_with_orgId_api_2('network/vlan', |
| params=params) \ |
| .object |
| return self._to_vlans(response) |
| |
| def ex_add_public_ip_block_to_network_domain(self, network_domain): |
| add_node = ET.Element('addPublicIpBlock', {'xmlns': TYPES_URN}) |
| ET.SubElement(add_node, "networkDomainId").text =\ |
| network_domain.id |
| |
| response = self.connection.request_with_orgId_api_2( |
| 'network/addPublicIpBlock', |
| method='POST', |
| data=ET.tostring(add_node)).object |
| |
| block_id = None |
| |
| for info in findall(response, 'info', TYPES_URN): |
| if info.get('name') == 'publicIpBlockId': |
| block_id = info.get('value') |
| return self.ex_get_public_ip_block(block_id) |
| |
| def ex_list_public_ip_blocks(self, network_domain): |
| params = {} |
| params['networkDomainId'] = network_domain.id |
| |
| response = self.connection \ |
| .request_with_orgId_api_2('network/publicIpBlock', |
| params=params).object |
| return self._to_ip_blocks(response) |
| |
| def ex_get_public_ip_block(self, block_id): |
| locations = self.list_locations() |
| block = self.connection.request_with_orgId_api_2( |
| 'network/publicIpBlock/%s' % block_id).object |
| return self._to_ip_block(block, locations) |
| |
| def ex_delete_public_ip_block(self, block): |
| delete_node = ET.Element('removePublicIpBlock', {'xmlns': TYPES_URN}) |
| delete_node.set('id', block.id) |
| result = self.connection.request_with_orgId_api_2( |
| 'network/removePublicIpBlock', |
| method='POST', |
| data=ET.tostring(delete_node)).object |
| |
| response_code = findtext(result, 'responseCode', TYPES_URN) |
| return response_code in ['IN_PROGRESS', 'OK'] |
| |
| def ex_get_node_by_id(self, id): |
| node = self.connection.request_with_orgId_api_2( |
| 'server/server/%s' % id).object |
| return self._to_node(node) |
| |
| def ex_list_firewall_rules(self, network_domain): |
| params = {} |
| params['networkDomainId'] = network_domain.id |
| |
| response = self.connection \ |
| .request_with_orgId_api_2('network/firewallRule', |
| params=params).object |
| return self._to_firewall_rules(response, network_domain) |
| |
| def ex_create_firewall_rule(self, network_domain, rule, position): |
| create_node = ET.Element('createFirewallRule', {'xmlns': TYPES_URN}) |
| ET.SubElement(create_node, "networkDomainId").text = network_domain.id |
| ET.SubElement(create_node, "name").text = rule.name |
| ET.SubElement(create_node, "action").text = rule.action |
| ET.SubElement(create_node, "ipVersion").text = rule.ip_version |
| ET.SubElement(create_node, "protocol").text = rule.protocol |
| # Setup source port rule |
| source = ET.SubElement(create_node, "source") |
| source_ip = ET.SubElement(source, 'ip') |
| if rule.source.any_ip: |
| source_ip.set('address', 'ANY') |
| else: |
| source_ip.set('address', rule.source.ip_address) |
| source_ip.set('prefixSize', rule.source.ip_prefix_size) |
| source_port = ET.SubElement(source, 'port') |
| source_port.set('begin', rule.source.port_begin) |
| if rule.source.port_end is not None: |
| source_port.set('end', rule.source.port_end) |
| # Setup destination port rule |
| dest = ET.SubElement(create_node, "destination") |
| dest_ip = ET.SubElement(dest, 'ip') |
| if rule.destination.any_ip: |
| dest_ip.set('address', 'ANY') |
| else: |
| dest_ip.set('address', rule.destination.ip_address) |
| dest_ip.set('prefixSize', rule.destination.ip_prefix_size) |
| dest_port = ET.SubElement(dest, 'port') |
| dest_port.set('begin', rule.destination.port_begin) |
| if rule.destination.port_end is not None: |
| dest_port.set('end', rule.destination.port_end) |
| ET.SubElement(create_node, "enabled").text = 'true' |
| placement = ET.SubElement(create_node, "placement") |
| placement.set('position', position) |
| |
| response = self.connection.request_with_orgId_api_2( |
| 'network/createFirewallRule', |
| method='POST', |
| data=ET.tostring(create_node)).object |
| |
| rule_id = None |
| for info in findall(response, 'info', TYPES_URN): |
| if info.get('name') == 'firewallRuleId': |
| rule_id = info.get('value') |
| rule.id = rule_id |
| return rule |
| |
| def ex_get_firewall_rule(self, network_domain, rule_id): |
| locations = self.list_locations() |
| rule = self.connection.request_with_orgId_api_2( |
| 'network/firewallRule/%s' % rule_id).object |
| return self._to_firewall_rule(rule, locations, network_domain) |
| |
| def ex_set_firewall_rule_state(self, rule, state): |
| """ |
| Change the state (enabled or disabled) of a rule |
| """ |
| update_node = ET.Element('editFirewallRule', {'xmlns': TYPES_URN}) |
| update_node.set('id', rule.id) |
| ET.SubElement(update_node, 'enabled').text = str(state).lower() |
| result = self.connection.request_with_orgId_api_2( |
| 'network/editFirewallRule', |
| method='POST', |
| data=ET.tostring(update_node)).object |
| |
| response_code = findtext(result, 'responseCode', TYPES_URN) |
| return response_code in ['IN_PROGRESS', 'OK'] |
| |
| def ex_delete_firewall_rule(self, rule): |
| update_node = ET.Element('deleteFirewallRule', {'xmlns': TYPES_URN}) |
| update_node.set('id', rule.id) |
| result = self.connection.request_with_orgId_api_2( |
| 'network/deleteFirewallRule', |
| method='POST', |
| data=ET.tostring(update_node)).object |
| |
| response_code = findtext(result, 'responseCode', TYPES_URN) |
| return response_code in ['IN_PROGRESS', 'OK'] |
| |
| def ex_create_nat_rule(self, network_domain, internal_ip, external_ip): |
| create_node = ET.Element('createNatRule', {'xmlns': TYPES_URN}) |
| ET.SubElement(create_node, 'networkDomainId').text = network_domain.id |
| ET.SubElement(create_node, 'internalIp').text = internal_ip |
| ET.SubElement(create_node, 'externalIp').text = external_ip |
| result = self.connection.request_with_orgId_api_2( |
| 'network/createNatRule', |
| method='POST', |
| data=ET.tostring(create_node)).object |
| |
| rule_id = None |
| for info in findall(result, 'info', TYPES_URN): |
| if info.get('name') == 'natRuleId': |
| rule_id = info.get('value') |
| |
| return DimensionDataNatRule( |
| id=rule_id, |
| network_domain=network_domain, |
| internal_ip=internal_ip, |
| external_ip=external_ip, |
| status=NodeState.RUNNING |
| ) |
| |
| def ex_list_nat_rules(self, network_domain): |
| params = {} |
| params['networkDomainId'] = network_domain.id |
| |
| response = self.connection \ |
| .request_with_orgId_api_2('network/natRule', |
| params=params).object |
| return self._to_nat_rules(response, network_domain) |
| |
| def ex_get_nat_rule(self, network_domain, rule_id): |
| rule = self.connection.request_with_orgId_api_2( |
| 'network/natRule/%s' % rule_id).object |
| return self._to_nat_rule(rule, network_domain) |
| |
| def ex_delete_nat_rule(self, rule): |
| update_node = ET.Element('deleteNatRule', {'xmlns': TYPES_URN}) |
| update_node.set('id', rule.id) |
| result = self.connection.request_with_orgId_api_2( |
| 'network/deleteNatRule', |
| method='POST', |
| data=ET.tostring(update_node)).object |
| |
| response_code = findtext(result, 'responseCode', TYPES_URN) |
| return response_code in ['IN_PROGRESS', 'OK'] |
| |
| def ex_get_location_by_id(self, id): |
| """ |
| Get location by ID. |
| |
| :param id: ID of the node location which should be used |
| :type id: ``str`` |
| |
| :rtype: :class:`NodeLocation` |
| """ |
| location = None |
| if id is not None: |
| location = list( |
| filter(lambda x: x.id == id, self.list_locations()))[0] |
| return location |
| |
| def _to_nat_rules(self, object, network_domain): |
| rules = [] |
| for element in findall(object, 'natRule', TYPES_URN): |
| rules.append( |
| self._to_nat_rule(element, network_domain)) |
| |
| return rules |
| |
| def _to_nat_rule(self, element, network_domain): |
| status = self._to_status(element.find(fixxpath('state', TYPES_URN))) |
| |
| return DimensionDataNatRule( |
| id=element.get('id'), |
| network_domain=network_domain, |
| internal_ip=findtext(element, 'internalIp', TYPES_URN), |
| external_ip=findtext(element, 'externalIp', TYPES_URN), |
| status=status) |
| |
| def _to_firewall_rules(self, object, network_domain): |
| rules = [] |
| locations = self.list_locations() |
| for element in findall(object, 'firewallRule', TYPES_URN): |
| rules.append( |
| self._to_firewall_rule(element, locations, network_domain)) |
| |
| return rules |
| |
| def _to_firewall_rule(self, element, locations, network_domain): |
| status = self._to_status(element.find(fixxpath('state', TYPES_URN))) |
| |
| location_id = element.get('datacenterId') |
| location = list(filter(lambda x: x.id == location_id, |
| locations))[0] |
| |
| return DimensionDataFirewallRule( |
| id=element.get('id'), |
| network_domain=network_domain, |
| name=findtext(element, 'name', TYPES_URN), |
| action=findtext(element, 'action', TYPES_URN), |
| ip_version=findtext(element, 'ipVersion', TYPES_URN), |
| protocol=findtext(element, 'protocol', TYPES_URN), |
| enabled=findtext(element, 'enabled', TYPES_URN), |
| source=self._to_firewall_address( |
| element.find(fixxpath('source', TYPES_URN))), |
| destination=self._to_firewall_address( |
| element.find(fixxpath('destination', TYPES_URN))), |
| location=location, |
| status=status) |
| |
| def _to_firewall_address(self, element): |
| ip = element.find(fixxpath('ip', TYPES_URN)) |
| port = element.find(fixxpath('port', TYPES_URN)) |
| return DimensionDataFirewallAddress( |
| any_ip=ip.get('address') == 'ANY', |
| ip_address=ip.get('address'), |
| ip_prefix_size=ip.get('prefixSize'), |
| port_begin=port.get('begin') if port is not None else None, |
| port_end=port.get('end') if port is not None else None |
| ) |
| |
| def _to_ip_blocks(self, object): |
| blocks = [] |
| locations = self.list_locations() |
| for element in findall(object, 'publicIpBlock', TYPES_URN): |
| blocks.append(self._to_ip_block(element, locations)) |
| |
| return blocks |
| |
| def _to_ip_block(self, element, locations): |
| status = self._to_status(element.find(fixxpath('state', TYPES_URN))) |
| |
| location_id = element.get('datacenterId') |
| location = list(filter(lambda x: x.id == location_id, |
| locations))[0] |
| |
| return DimensionDataPublicIpBlock( |
| id=element.get('id'), |
| network_domain=self.ex_get_network_domain( |
| findtext(element, 'networkDomainId', TYPES_URN) |
| ), |
| base_ip=findtext(element, 'baseIp', TYPES_URN), |
| size=findtext(element, 'size', TYPES_URN), |
| location=location, |
| status=status) |
| |
| def _to_networks(self, object): |
| networks = [] |
| locations = self.list_locations() |
| for element in findall(object, 'network', NETWORK_NS): |
| networks.append(self._to_network(element, locations)) |
| |
| return networks |
| |
| def _to_network(self, element, locations): |
| multicast = False |
| if findtext(element, 'multicast', NETWORK_NS) == 'true': |
| multicast = True |
| |
| status = self._to_status(element.find(fixxpath('status', NETWORK_NS))) |
| |
| location_id = findtext(element, 'location', NETWORK_NS) |
| location = list(filter(lambda x: x.id == location_id, |
| locations))[0] |
| |
| return DimensionDataNetwork( |
| id=findtext(element, 'id', NETWORK_NS), |
| name=findtext(element, 'name', NETWORK_NS), |
| description=findtext(element, 'description', |
| NETWORK_NS), |
| location=location, |
| private_net=findtext(element, 'privateNet', |
| NETWORK_NS), |
| multicast=multicast, |
| status=status) |
| |
| def _to_network_domains(self, object): |
| network_domains = [] |
| locations = self.list_locations() |
| for element in findall(object, 'networkDomain', TYPES_URN): |
| network_domains.append(self._to_network_domain(element, locations)) |
| |
| return network_domains |
| |
| def _to_network_domain(self, element, locations): |
| status = self._to_status(element.find(fixxpath('state', TYPES_URN))) |
| |
| location_id = element.get('datacenterId') |
| location = list(filter(lambda x: x.id == location_id, |
| locations))[0] |
| plan = findtext(element, 'type', TYPES_URN) |
| if plan is 'ESSENTIALS': |
| plan_type = NetworkDomainServicePlan.ESSENTIALS |
| else: |
| plan_type = NetworkDomainServicePlan.ADVANCED |
| return DimensionDataNetworkDomain( |
| id=element.get('id'), |
| name=findtext(element, 'name', TYPES_URN), |
| description=findtext(element, 'description', TYPES_URN), |
| plan=plan_type, |
| location=location, |
| status=status) |
| |
| def _to_vlans(self, object): |
| vlans = [] |
| locations = self.list_locations() |
| for element in findall(object, 'vlan', TYPES_URN): |
| vlans.append(self._to_vlan(element, locations=locations)) |
| |
| return vlans |
| |
| def _to_vlan(self, element, locations): |
| status = self._to_status(element.find(fixxpath('state', TYPES_URN))) |
| |
| location_id = element.get('datacenterId') |
| location = list(filter(lambda x: x.id == location_id, |
| locations))[0] |
| ip_range = element.find(fixxpath('privateIpv4Range', TYPES_URN)) |
| return DimensionDataVlan( |
| id=element.get('id'), |
| name=findtext(element, 'name', TYPES_URN), |
| description=findtext(element, 'description', |
| TYPES_URN), |
| private_ipv4_range_address=ip_range.get('address'), |
| private_ipv4_range_size=ip_range.get('prefixSize'), |
| location=location, |
| status=status) |
| |
| def _to_locations(self, object): |
| locations = [] |
| for element in object.findall(fixxpath('datacenter', TYPES_URN)): |
| locations.append(self._to_location(element)) |
| |
| return locations |
| |
| def _to_location(self, element): |
| l = NodeLocation(id=element.get('id'), |
| name=findtext(element, 'displayName', TYPES_URN), |
| country=findtext(element, 'country', TYPES_URN), |
| driver=self) |
| return l |
| |
| def _to_nodes(self, object): |
| node_elements = object.findall(fixxpath('Server', TYPES_URN)) |
| |
| return [self._to_node(el) for el in node_elements] |
| |
| def _to_node(self, element): |
| if findtext(element, 'started', TYPES_URN) == 'true': |
| state = NodeState.RUNNING |
| else: |
| state = NodeState.TERMINATED |
| |
| status = self._to_status(element.find(fixxpath('progress', TYPES_URN))) |
| |
| has_network_info \ |
| = element.find(fixxpath('networkInfo', TYPES_URN)) is not None |
| |
| extra = { |
| 'description': findtext(element, 'description', TYPES_URN), |
| 'sourceImageId': findtext(element, 'sourceImageId', TYPES_URN), |
| 'networkId': findtext(element, 'networkId', TYPES_URN), |
| 'networkDomainId': |
| element.find(fixxpath('networkInfo', TYPES_URN)) |
| .get('networkDomainId') |
| if has_network_info else None, |
| 'datacenterId': element.get('datacenterId'), |
| 'deployedTime': findtext(element, 'createTime', TYPES_URN), |
| 'cpuCount': int(findtext( |
| element, |
| 'cpuCount', |
| TYPES_URN)), |
| 'memoryMb': int(findtext( |
| element, |
| 'memoryGb', |
| TYPES_URN)) * 1024, |
| 'OS_id': element.find(fixxpath( |
| 'operatingSystem', |
| TYPES_URN)).get('id'), |
| 'OS_type': element.find(fixxpath( |
| 'operatingSystem', |
| TYPES_URN)).get('family'), |
| 'OS_displayName': element.find(fixxpath( |
| 'operatingSystem', |
| TYPES_URN)).get('displayName'), |
| 'status': status |
| } |
| |
| public_ip = findtext(element, 'publicIpAddress', TYPES_URN) |
| |
| private_ip = element.find( |
| fixxpath('networkInfo/primaryNic', TYPES_URN)) \ |
| .get('privateIpv4') \ |
| if has_network_info else \ |
| element.find(fixxpath('nic', TYPES_URN)).get('privateIpv4') |
| |
| n = Node(id=element.get('id'), |
| name=findtext(element, 'name', TYPES_URN), |
| state=state, |
| public_ips=[public_ip] if public_ip is not None else [], |
| private_ips=[private_ip] if private_ip is not None else [], |
| driver=self.connection.driver, |
| extra=extra) |
| return n |
| |
| def _to_status(self, element): |
| if element is None: |
| return DimensionDataStatus() |
| s = DimensionDataStatus(action=findtext(element, 'action', TYPES_URN), |
| request_time=findtext( |
| element, |
| 'requestTime', |
| TYPES_URN), |
| user_name=findtext( |
| element, |
| 'userName', |
| TYPES_URN), |
| number_of_steps=findtext( |
| element, |
| 'numberOfSteps', |
| TYPES_URN), |
| step_name=findtext( |
| element, |
| 'step/name', |
| TYPES_URN), |
| step_number=findtext( |
| element, |
| 'step_number', |
| TYPES_URN), |
| step_percent_complete=findtext( |
| element, |
| 'step/percentComplete', |
| TYPES_URN), |
| failure_reason=findtext( |
| element, |
| 'failureReason', |
| TYPES_URN)) |
| return s |