Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions libcloud/common/dimensiondata.py
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,50 @@ def request_with_orgId_api_2(self, action, params=None, data='',
params=params, data=data,
method=method, headers=headers)

def paginated_request_with_orgId_api_2(self, action, params=None, data='',
headers=None, method='GET',
page_size=250):
"""
A paginated request to the MCP2.0 API
This essentially calls out to request_with_orgId_api_2 for each page
and yields the response to make a generator
This generator can be looped through to grab all the pages.

:param action: The resource to access (i.e. 'network/vlan')
:type action: ``str``

:param params: Parameters to give to the action
:type params: ``dict`` or ``None``

:param data: The data payload to be added to the request
:type data: ``str``

:param headers: Additional header to be added to the request
:type headers: ``str`` or ``dict`` or ``None``

:param method: HTTP Method for the request (i.e. 'GET', 'POST')
:type method: ``str``

:param page_size: The size of each page to be returned
Note: Max page size in MCP2.0 is currently 250
:type page_size: ``int``
"""
if params is None:
params = {}
params['pageSize'] = page_size

paged_resp = self.request_with_orgId_api_2(action, params,
data, headers,
method).object
yield paged_resp

while paged_resp.get('pageCount') >= paged_resp.get('pageSize'):
params['pageNumber'] = int(paged_resp.get('pageNumber')) + 1
paged_resp = self.request_with_orgId_api_2(action, params,
data, headers,
method).object
yield paged_resp

def get_resource_path_api_1(self):
"""
This method returns a resource path which is necessary for referencing
Expand Down Expand Up @@ -731,6 +775,31 @@ def __repr__(self):
% (self.id, self.status))


class DimensionDataAntiAffinityRule(object):
"""
Anti-Affinity rule for DimensionData

An Anti-Affinity rule ensures that servers in the rule will
not reside on the same VMware ESX host.
"""
def __init__(self, id, node_list):
"""
Instantiate a new :class:`DimensionDataAntiAffinityRule`

:param id: The ID of the Anti-Affinity rule
:type id: ``str``

:param node_list: List of node ids that belong in this rule
:type node_list: ``list`` of ``str``
"""
self.id = id
self.node_list = node_list
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you docstring what this does/type etc please.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops missed that one


def __repr__(self):
return (('<DimensionDataAntiAffinityRule: id=%s>')
% (self.id))


class DimensionDataVlan(object):
"""
DimensionData VLAN.
Expand Down
184 changes: 181 additions & 3 deletions libcloud/compute/drivers/dimensiondata.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
from libcloud.common.dimensiondata import DimensionDataFirewallRule
from libcloud.common.dimensiondata import DimensionDataFirewallAddress
from libcloud.common.dimensiondata import DimensionDataNatRule
from libcloud.common.dimensiondata import DimensionDataAntiAffinityRule
from libcloud.common.dimensiondata import NetworkDomainServicePlan
from libcloud.common.dimensiondata import API_ENDPOINTS, DEFAULT_REGION
from libcloud.common.dimensiondata import TYPES_URN
Expand Down Expand Up @@ -686,6 +687,112 @@ def ex_update_node(self, node, name=None, description=None,
response_code = findtext(body, 'result', GENERAL_NS)
return response_code in ['IN_PROGRESS', 'SUCCESS']

def ex_create_anti_affinity_rule(self, node_list):
"""
Create an anti affinity rule given a list of nodes
Anti affinity rules ensure that servers will not reside
on the same VMware ESX host

:param node_list: The list of nodes to create a rule for
:type node_list: ``list`` of :class:`Node` or
``list`` of ``str``

:rtype: ``bool``
"""
if not isinstance(node_list, (list, tuple)):
raise TypeError("Node list must be a list or a tuple.")
anti_affinity_xml_request = ET.Element('NewAntiAffinityRule',
{'xmlns': SERVER_NS})
for node in node_list:
ET.SubElement(anti_affinity_xml_request, 'serverId').text = \
self._node_to_node_id(node)
result = self.connection.request_with_orgId_api_1(
'antiAffinityRule',
method='POST',
data=ET.tostring(anti_affinity_xml_request)).object
response_code = findtext(result, 'result', GENERAL_NS)
return response_code in ['IN_PROGRESS', 'SUCCESS']

def ex_delete_anti_affinity_rule(self, anti_affinity_rule):
"""
Remove anti affinity rule

:param anti_affinity_rule: The anti affinity rule to delete
:type anti_affinity_rule: :class:`DimensionDataAntiAffinityRule` or
``str``

:rtype: ``bool``
"""
rule_id = self._anti_affinity_rule_to_anti_affinity_rule_id(
anti_affinity_rule)
result = self.connection.request_with_orgId_api_1(
'antiAffinityRule/%s?delete' % (rule_id),
method='GET').object
response_code = findtext(result, 'result', GENERAL_NS)
return response_code in ['IN_PROGRESS', 'SUCCESS']

def ex_list_anti_affinity_rules(self, network=None, network_domain=None,
node=None, filter_id=None,
filter_state=None):
"""
List anti affinity rules for a network, network domain, or node

:param network: The network to list anti affinity rules for
One of network, network_domain, or node is required
:type network: :class:`DimensionDataNetwork` or ``str``

:param network_domain: The network domain to list anti affinity rules
One of network, network_domain,
or node is required
:type network_domain: :class:`DimensionDataNetworkDomain` or ``str``

:param node: The node to list anti affinity rules for
One of network, netwok_domain, or node is required
:type node: :class:`Node` or ``str``

:param filter_id: This will allow you to filter the rules
by this node id
:type filter_id: ``str``

:type filter_state: This will allow you to filter rules by
node state (i.e. NORMAL)
:type filter_state: ``str``

:rtype: ``list`` of :class:`DimensionDataAntiAffinityRule`
"""
not_none_arguments = [key
for key in (network, network_domain, node)
if key is not None]
if len(not_none_arguments) != 1:
raise ValueError("One and ONLY one of network, "
"network_domain, or node must be set")

params = {}
if network_domain is not None:
params['networkDomainId'] = \
self._network_domain_to_network_domain_id(network_domain)
if network is not None:
params['networkId'] = \
self._network_to_network_id(network)
if node is not None:
params['serverId'] = \
self._node_to_node_id(node)
if filter_id is not None:
params['id'] = filter_id
if filter_state is not None:
params['state'] = filter_state

paged_result = self.connection.paginated_request_with_orgId_api_2(
'server/antiAffinityRule',
method='GET',
params=params
)

rules = []
for result in paged_result:
rules.extend(self._to_anti_affinity_rules(result))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would probably be quicker to use list comprehension here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I Think since our to methods are returning lists here themselves extend makes more sense than list comprehension. If you have a better way let me know because all of our functions will have the same type of style in them so we should get it right the first time.

return rules

def ex_attach_node_to_vlan(self, node, vlan):
"""
Attach a node to a VLAN by adding an additional NIC to
Expand Down Expand Up @@ -1205,9 +1312,38 @@ def ex_list_firewall_rules(self, network_domain, page_size=50,
params=params).object
return self._to_firewall_rules(response, network_domain)

def ex_create_firewall_rule(self, network_domain, rule, position):
def ex_create_firewall_rule(self, network_domain, rule, position,
position_relative_to_rule=None):
"""
Creates a firewall rule

:param network_domain: The network domain in which to create
the firewall rule
:type network_domain: :class:`DimensionDataNetworkDomain` or ``str``

:param rule: The rule in which to create
:type rule: :class:`DimensionDataFirewallRule`

:param position: The position in which to create the rule
There are two types of positions
with position_relative_to_rule arg and without it
With: 'BEFORE' or 'AFTER'
Without: 'FIRST' or 'LAST'
:type position: ``str``

:param position_relative_to_rule: The rule or rule name in
which to decide positioning by
:type position_relative_to_rule:
:class:`DimensionDataFirewallRule` or ``str``

:rtype: ``bool``
"""
positions_without_rule = ('FIRST', 'LAST')
positions_with_rule = ('BEFORE', 'AFTER')

create_node = ET.Element('createFirewallRule', {'xmlns': TYPES_URN})
ET.SubElement(create_node, "networkDomainId").text = network_domain.id
ET.SubElement(create_node, "networkDomainId").text = \
self._network_domain_to_network_domain_id(network_domain)
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
Expand Down Expand Up @@ -1240,8 +1376,26 @@ def ex_create_firewall_rule(self, network_domain, rule, position):
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'
ET.SubElement(create_node, "enabled").text = rule.enabled

# Set up positioning of rule
placement = ET.SubElement(create_node, "placement")
if position_relative_to_rule is not None:
if position not in positions_with_rule:
raise ValueError("When position_relative_to_rule is specified"
" position must be %s"
% ', '.join(positions_with_rule))
if isinstance(position_relative_to_rule,
DimensionDataFirewallRule):
rule_name = position_relative_to_rule.name
else:
rule_name = position_relative_to_rule
placement.set('relativeToRule', rule_name)
else:
if position not in positions_without_rule:
raise ValueError("When position_relative_to_rule is not"
" specified position must be %s"
% ', '.join(positions_without_rule))
placement.set('position', position)

response = self.connection.request_with_orgId_api_2(
Expand Down Expand Up @@ -1782,6 +1936,22 @@ def _to_nat_rule(self, element, network_domain):
external_ip=findtext(element, 'externalIp', TYPES_URN),
status=findtext(element, 'state', TYPES_URN))

def _to_anti_affinity_rules(self, object):
rules = []
for element in findall(object, 'antiAffinityRule', TYPES_URN):
rules.append(
self._to_anti_affinity_rule(element))
return rules

def _to_anti_affinity_rule(self, element):
node_list = []
for node in findall(element, 'serverSummary', TYPES_URN):
node_list.append(node.get('id'))
return DimensionDataAntiAffinityRule(
id=element.get('id'),
node_list=node_list
)

def _to_firewall_rules(self, object, network_domain):
rules = []
locations = self.list_locations()
Expand Down Expand Up @@ -2089,6 +2259,10 @@ def _get_node_state(state, started, action):
else:
return NodeState.TERMINATED

@staticmethod
def _node_to_node_id(node):
return dd_object_to_id(node, Node)

@staticmethod
def _location_to_location_id(location):
return dd_object_to_id(location, NodeLocation)
Expand All @@ -2105,6 +2279,10 @@ def _image_to_image_id(image):
def _network_to_network_id(network):
return dd_object_to_id(network, DimensionDataNetwork)

@staticmethod
def _anti_affinity_rule_to_anti_affinity_rule_id(rule):
return dd_object_to_id(rule, DimensionDataAntiAffinityRule)

@staticmethod
def _network_domain_to_network_domain_id(network_domain):
return dd_object_to_id(network_domain, DimensionDataNetworkDomain)
Original file line number Diff line number Diff line change
Expand Up @@ -214,12 +214,60 @@
<ipVersion>IPV6</ipVersion>
<protocol>TCP</protocol>
<source>
<ip address="2607:f480:111:1336:6503:544c:74a6:3a28"/>
<ip address="2607:f480:111:1336:6503:544c:74a6:3a28"/>
</source>
<destination>
<ip address="ANY"/>
</destination>
<enabled>true</enabled>
<state>NORMAL</state>
</firewallRule>
<firewallRule id="ce250bd3-0e45-4c13-a6d2-74e0657ef699" datacenterId="NA9" ruleType="CLIENT_RULE">
<networkDomainId>423c4386-87b4-43c4-9604-88ae237bfc7f</networkDomainId>
<name>RULE_WITH_SOURCE_AND_DEST_IP_ONLY</name>
<action>ACCEPT_DECISIVELY</action>
<ipVersion>IPV4</ipVersion>
<protocol>TCP</protocol>
<source>
<ip address="10.10.10.15"/>
</source>
<destination>
<ip address="10.10.10.14"/>
</destination>
<enabled>true</enabled>
<state>NORMAL</state>
</firewallRule>
<firewallRule id="ce250bd3-0e45-4c13-a6d2-74e0657ef699" datacenterId="NA9" ruleType="CLIENT_RULE">
<networkDomainId>423c4386-87b4-43c4-9604-88ae237bfc7f</networkDomainId>
<name>RULE_WITH_DEST_IP_NO_PORT</name>
<action>ACCEPT_DECISIVELY</action>
<ipVersion>IPV4</ipVersion>
<protocol>TCP</protocol>
<source>
<ip address="10.10.10.15"/>
</source>
<destination>
<ip address="10.10.10.14"/>
<port begin="40000" end="40005"/>
</destination>
<enabled>true</enabled>
<state>NORMAL</state>
</firewallRule>
<firewallRule id="ce250bd3-0e45-4c13-a6d2-74e0657ef700" datacenterId="NA9" ruleType="CLIENT_RULE">
<networkDomainId>423c4386-87b4-43c4-9604-88ae237bfc7f</networkDomainId>
<name>RULE_WITH_SOURCE_AND_DEST</name>
<action>ACCEPT_DECISIVELY</action>
<ipVersion>IPV4</ipVersion>
<protocol>TCP</protocol>
<source>
<ip address="10.10.10.0" prefixSize="24"/>
<port begin="40000" end="40005"/>
</source>
<destination>
<ip address="10.10.10.0" prefixSize="24"/>
<port begin="40000"/>
</destination>
<enabled>true</enabled>
<state>NORMAL</state>
</firewallRule>
</firewallRules>
</firewallRules>
Loading