Add support for load balancers

This commit is contained in:
Alexandru Coman 2017-02-08 16:21:43 +02:00
parent 0c5f95414c
commit faebe85829
No known key found for this signature in database
GPG Key ID: A7B6A9021F704507
14 changed files with 1540 additions and 34 deletions

View File

@ -121,7 +121,7 @@ single-line-if-stmt=no
no-space-check=trailing-comma,dict-separator
# Maximum number of lines in a module
max-module-lines=2000
max-module-lines=5000
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
@ -394,7 +394,7 @@ max-attributes=7
min-public-methods=1
# Maximum number of public methods for a class (see R0904).
max-public-methods=20
max-public-methods=30
# Maximum number of boolean expressions in a if statement
max-bool-expr=5

View File

@ -131,6 +131,33 @@ class _BaseHNVModel(model.Model):
allow_insecure=CONFIG.HNV.https_allow_insecure,
ca_bundle=CONFIG.HNV.https_ca_bundle)
@classmethod
def _get_all(cls, parent_id=None, grandparent_id=None):
"""Retrives all the required resources."""
client = cls._get_client()
endpoint = cls._endpoint.format(resource_id="",
parent_id=parent_id or "",
grandparent_id=grandparent_id or "")
resources = []
while True:
raw_data = client.get_resource(endpoint)
for item in raw_data.get("value", []):
resources.append(cls.from_raw_data(item))
endpoint = raw_data.get("nextLink")
if not endpoint:
break
return resources
@classmethod
def _get(cls, resource_id, parent_id, grandparent_id):
""""Retrieves the required resource."""
client = cls._get_client()
endpoint = cls._endpoint.format(resource_id=resource_id or "",
parent_id=parent_id or "",
grandparent_id=grandparent_id or "")
raw_data = client.get_resource(endpoint)
return cls.from_raw_data(raw_data)
@classmethod
def get(cls, resource_id=None, parent_id=None, grandparent_id=None):
"""Retrieves the required resources.
@ -143,15 +170,11 @@ class _BaseHNVModel(model.Model):
network objects that are ancestors of the
parent of the necessary resource.
"""
client = cls._get_client()
endpoint = cls._endpoint.format(resource_id=resource_id or "",
parent_id=parent_id or "",
grandparent_id=grandparent_id or "")
raw_data = client.get_resource(endpoint)
if resource_id is None:
return [cls.from_raw_data(item) for item in raw_data["value"]]
if not resource_id:
return cls._get_all(parent_id, grandparent_id)
else:
return cls.from_raw_data(raw_data)
return cls._get(resource_id, parent_id, grandparent_id)
@classmethod
def remove(cls, resource_id, parent_id=None, grandparent_id=None,
@ -207,7 +230,7 @@ class _BaseHNVModel(model.Model):
response = client.get_resource(endpoint)
self._reset_model(response)
def commit(self, wait=True, timeout=None):
def commit(self, if_match=None, wait=True, timeout=None):
"""Apply all the changes on the current model.
:param wait: Whether to wait until the operation is completed
@ -231,7 +254,8 @@ class _BaseHNVModel(model.Model):
endpoint = self._endpoint.format(resource_id=self.resource_id or "",
parent_id=self.parent_id or "")
request_body = self.dump(include_read_only=False)
response = client.update_resource(endpoint, data=request_body)
response = client.update_resource(endpoint, data=request_body,
if_match=if_match)
elapsed_time = 0
while wait:
@ -252,6 +276,10 @@ class _BaseHNVModel(model.Model):
else:
self._reset_model(response)
# NOTE(alexcoman): In order to keep backwards compatibility the
# `method: commit` will return a reference to itself.
# An example for that can be the following use case:
# label = client.Model().commit()
return self
@classmethod
@ -573,7 +601,7 @@ class IPConfiguration(_BaseHNVModel):
backend_address_pools = model.Field(
name="backend_address_pools", key="loadBalancerBackendAddressPools",
is_required=False, is_read_only=True)
is_required=False, is_read_only=False)
"""Reference to backendAddressPools child resource of loadBalancers
resource."""
@ -1177,7 +1205,7 @@ class VirtualSwitchManager(_BaseHNVModel):
is_required=False)
def __init__(self, **fields):
qos_settings = fields.pop("qos_settings", {})
qos_settings = fields.get("qos_settings", {})
if not isinstance(qos_settings, VirtualSwtichQosSettings):
fields["qos_settings"] = VirtualSwtichQosSettings.from_raw_data(
raw_data=qos_settings)
@ -1469,7 +1497,7 @@ class IPSecConfiguration(model.Model):
@classmethod
def from_raw_data(cls, raw_data):
"""Create a new model using raw API response."""
raw_main = raw_data.pop("mainMode", None)
raw_main = raw_data.get("mainMode", None)
if raw_main is not None:
main_mode = MainMode.from_raw_data(raw_main)
raw_data["mainMode"] = main_mode
@ -1838,3 +1866,682 @@ class PublicIPAddresses(_BaseHNVModel):
associated. Private ip can be defined on NIC, loadBalancers, or
gateways.
"""
class BackendAddressPools(_BaseHNVModel):
"""Model for backend address pools.
This resource represents the list of IPs that can receive network traffic
that comes via the front-end IPs. The Load Balancing MUX handles incoming
traffic via the front-end IPs and distributes them to backend IPs based
on load balancing configuration.
"""
_endpoint = ("/networking/v1/loadBalancers/{parent_id}"
"/backendAddressPools/{resource_id}")
parent_id = model.Field(
name="parent_id", key="parentResourceID",
is_property=False, is_required=True, is_read_only=True)
"""The parent resource ID field contains the resource ID that is
associated with network objects that are ancestors of the necessary
resource.
"""
backend_ip_configurations = model.Field(
name="backend_ip_configurations", key="backendIPConfigurations",
is_required=False, is_read_only=False)
"""Indicates an array of references to ipConfiguration Resources.
There is no restriction on having the same IP configurations in multiple
backendAddressPools. An IpConfiguration can become a part of a
backendAddressPool by setting a reference to a backendAddressPool resource
in the loadBalancerBackendAddressPools array field on the IpConfiguration
resource.
"""
load_balancing_rules = model.Field(name="load_balancing_rules",
key="loadBalancingRules",
is_required=False, is_read_only=False)
"""Indicates an array of references to the set of loadBalancingRules
resources that use this backend address pool.
"""
outbound_nat_rules = model.Field(name="outbound_nat_rules",
key="outboundNatRules",
is_required=False, is_read_only=False)
"""Indicates an array of references to the set of outboundNatRules
resources that use this backend address pool."""
@classmethod
def from_raw_data(cls, raw_data):
"""Create a new model using raw API response."""
properties = raw_data.get("properties", {})
backend_ip_configurations = []
for raw_content in properties.get("backendIPConfigurations", []):
resource = Resource.from_raw_data(raw_content)
backend_ip_configurations.append(resource)
properties["backendIPConfigurations"] = backend_ip_configurations
load_balancing_rules = []
for raw_content in properties.get("loadBalancingRules", []):
resource = Resource.from_raw_data(raw_content)
load_balancing_rules.append(resource)
properties["loadBalancingRules"] = load_balancing_rules
outbound_nat_rules = []
for raw_content in properties.get("outboundNatRules", []):
resource = Resource.from_raw_data(raw_content)
outbound_nat_rules.append(resource)
properties["outboundNatRules"] = outbound_nat_rules
return super(BackendAddressPools, cls).from_raw_data(raw_data)
class FrontendIPConfigurations(_BaseHNVModel):
"""Model for frontend ip configurations.
This resource represents the frontend IP addresses of the load balancer.
Either a publicIPAddress or a privateIPAddress and subnet must
be configured.
"""
_endpoint = ("/networking/v1/loadBalancers/{parent_id}"
"/frontendIpConfigurations/{resource_id}")
parent_id = model.Field(
name="parent_id", key="parentResourceID",
is_property=False, is_required=True, is_read_only=True)
"""The parent resource ID field contains the resource ID that is
associated with network objects that are ancestors of the necessary
resource.
"""
inbound_nat_rules = model.Field(
name="inbound_nat_rules", key="inboundNatRules",
is_required=False, is_read_only=True)
"""Indicates a reference to the inboundNatRules resource used by
the frontEndIpConfiguration."""
load_balancing_rules = model.Field(
name="load_balancing_rules", key="loadBalancingRules",
is_required=False, is_read_only=False)
"""Indicates a reference to the loadBalancingRules resource used
by the frontEndIpConfiguration."""
outbound_nat_rules = model.Field(
name="outbound_nat_rules", key="outboundNatRules",
is_required=False, is_read_only=True)
"""Indicates a reference to the outboundNatRules resource used by
the frontEndIpConfiguration."""
public_ip_address = model.Field(
name="public_ip_address", key="publicIPAddress",
is_required=False, is_read_only=False)
"""Indicates a reference to the publicIPAddresses resource used by
the frontEndIpConfiguration. If a publicIPAddress is specified,
then a privateIPaddress is not specified. When a
publicIPAddress is specified, the privateIpAllocationMethod is
set to Dynamic.
"""
private_ip_address = model.Field(name="private_ip_address",
key="privateIPAddress",
is_required=False, is_read_only=False)
"""This is only specified if a specific private IP address identifies an
IP address which is statically configured for use with this
frontendIpConfiguration.
PrivateIPAllocation method MUST be allocated static for this case.
If a privateIPAddress is specified, a reference to a publicIPaddress
cannot be specified at the same time. privateIPAddresses can be either
from the infrastructure address space or from a tenant address space,
in either case they MUST be accompanied with a valid subnet specified in
subnet element reference.
"""
private_ip_allocation_method = model.Field(
name="private_ip_allocation_method", key="privateIPAllocationMethod",
is_required=False, is_read_only=False)
"""Static or Dynamic."""
subnet = model.Field(name="subnet", key="subnet",
is_required=False, is_read_only=False)
"""Indicates a references to the subnet resource used by the
frontendIpConfiguration resource. MUST be specified if a
privateIPaddress is specified.
A subnet reference to a logical network subnet is needed if the
privateIpAddress is from the infrastructure address space. A
subnet reference to a virtual network subnet is needed if the
privateIpAddress is from a tenant address space.
The subnet MUST include the IP address specified in
privateIpAddress.
"""
@classmethod
def from_raw_data(cls, raw_data):
"""Create a new model using raw API response."""
properties = raw_data.get("properties", {})
load_balancing_rules = []
for raw_content in properties.get("loadBalancingRules", []):
resource = Resource.from_raw_data(raw_content)
load_balancing_rules.append(resource)
properties["loadBalancingRules"] = load_balancing_rules
inbound_nat_rules = []
for raw_content in properties.get("inboundNatRules", []):
resource = Resource.from_raw_data(raw_content)
inbound_nat_rules.append(resource)
properties["inboundNatRules"] = inbound_nat_rules
outbound_nat_rules = []
for raw_content in properties.get("outboundNatRules", []):
resource = Resource.from_raw_data(raw_content)
outbound_nat_rules.append(resource)
properties["outboundNatRules"] = outbound_nat_rules
raw_content = properties.get("subnet", None)
if raw_content is not None:
resource = Resource.from_raw_data(raw_content)
properties["subnet"] = resource
return super(FrontendIPConfigurations, cls).from_raw_data(raw_data)
class InboundNATRules(_BaseHNVModel):
"""Model for inbound nat rules.
This resource is used to configure the load balancer to apply
Network Address Translation of inbound traffic.
"""
_endpoint = ("/networking/v1/loadBalancers/{parent_id}"
"/inboundNatRules/{resource_id}")
parent_id = model.Field(
name="parent_id", key="parentResourceID",
is_property=False, is_required=True, is_read_only=True)
"""The parent resource ID field contains the resource ID that is
associated with network objects that are ancestors of the necessary
resource.
"""
backend_ip_configuration = model.Field(
name="backend_ip_configuration", key="backendIPConfiguration",
is_required=False, is_read_only=False)
"""Indicates a references to backendAddressPool resource. Traffic
sent to frontendPort of each of the frontendIPConfigurations is
forwarded to the backend IP.
"""
backend_port = model.Field(name="backend_port", key="backendPort",
is_required=False, is_read_only=False)
"""Indicates a port used for internal connections on the endpoint.
The localPort attribute maps the external port on the endpoint
to an internal port on a role. This is useful in scenarios where a
role has to communicate to an internal component on a port
that different from the one that is exposed externally.
Possible values range between 1 and 65535, inclusive.
This parameter is required if the protocol is TCP or UDP.
"""
frontend_ip_configurations = model.Field(
name="frontend_ip_configurations", key="frontendIPConfigurations",
is_required=True, is_read_only=False)
"""Indicates an array of references to frontendIPConfigurations
resources."""
frontend_port = model.Field(name="frontend_port", key="frontendPort",
is_required=False, is_read_only=False)
"""The port for the external endpoint. Any port number can be
specified, but the port numbers specified for each role in the
service MUST be unique. Possible values range between 1 and
65535, inclusive.
This parameter must be specified if protocol is TCP or UDP.
"""
protocol = model.Field(name="protocol", key="protocol",
is_required=True, is_read_only=False)
"""Indicates the inbound transport protocol for the external
endpoint. Valid values include `UDP`, `TCP`, `GRE`, `ESP` or `ALL`.
`ALL` indicates a wildcard.
"""
idle_timeout = model.Field(name="idle_timeout",
key="idleTimeoutInMinutes",
is_required=False, is_read_only=False)
"""Specifies the timeout for the TCP idle connection.
The value can be set between 4 and 30 minutes. The default is 4
minutes. If public IP is used as a frontend IP of a Load Balancer
this value is ignored.
"""
floating_ip = model.Field(name="floating_ip", key="enableFloatingIP",
is_required=False, is_read_only=False)
"""
This specifies that a floating IP will be used on the available servers
behind a load balancer. Floating IP (VIP) will be forwarded by the load
balancer to the backend server. The back-end server will be configured
with that VIP, a datacenter IP and weakhost forwarding.
Floating IP configuration is required if you are using the SQL AlwaysOn
Availability Group feature. This setting can't be changed after you create
the endpoint.
"""
@classmethod
def from_raw_data(cls, raw_data):
"""Create a new model using raw API response."""
properties = raw_data.get("properties", {})
raw_ip_configuration = properties.get("backendIPConfiguration", [])
if isinstance(raw_ip_configuration, dict):
raw_ip_configuration = [raw_ip_configuration]
for raw_content in raw_ip_configuration:
backend_ip_configuration = Resource.from_raw_data(raw_content)
properties["backendIPConfiguration"] = backend_ip_configuration
frontend_ip_configurations = []
for raw_content in properties.get("frontendIPConfigurations", []):
resource = Resource.from_raw_data(raw_content)
frontend_ip_configurations.append(resource)
properties["frontendIPConfigurations"] = frontend_ip_configurations
return super(InboundNATRules, cls).from_raw_data(raw_data)
class LoadBalancingRules(_BaseHNVModel):
"""Model for load balancing rules.
This resource is used to configure load balancing policies. The policies
dictate the kind of traffic that is load-balanced, and port mapping
between frontend IPs and backend IPs.
"""
_endpoint = ("/networking/v1/loadBalancers/{parent_id}"
"/loadBalancingRules/{resource_id}")
parent_id = model.Field(
name="parent_id", key="parentResourceID",
is_property=False, is_required=True, is_read_only=True)
"""The parent resource ID field contains the resource ID that is
associated with network objects that are ancestors of the necessary
resource.
"""
backend_address_pool = model.Field(
name="backend_address_pool", key="backendAddressPool",
is_required=False, is_read_only=False)
"""Indicates an array of references to a BackendAddressPool resource.
Inbound traffic is randomly load balanced across IPs in the backend pool.
"""
backend_port = model.Field(name="backend_port", key="backendPort",
is_required=False, is_read_only=False)
"""Indicates the port used for internal connections on the endpoint.
The localPort attribute maps the external port on the endpoint to an
internal port on a role. This is useful in scenarios where a role has
to communicate to an internal component on a port that different from
the one that is exposed externally. If not specified, the value of
localPort is the same as the port attribute. Set the value of localPort
to "*" to automatically assign an unallocated port that is discoverable
using the runtime API.
Possible values range between 1 and 65535, inclusive.
This parameter is required if the protocol is TCP or UDP.
"""
frontend_ip_configurations = model.Field(
name="frontend_ip_configurations", key="frontendIPConfigurations",
is_required=True, is_read_only=False)
"""Indicates an array of references to FrontendIPAddress resources."""
frontend_port = model.Field(name="frontend_port", key="frontendPort",
is_required=False, is_read_only=False)
"""Indicates the port for the external endpoint.
Possible values range between 1 and 65535, inclusive. This value MUST
be unique for the loadbalancer resource.
This parameter is required if the protocol is TCP or UDP.
"""
idle_timeout = model.Field(
name="idle_timeout", key="idleTimeoutInMinutes",
is_required=False, is_read_only=False)
"""Indicates the timeout for the Tcp idle connection in the inbound
direction, i.e. a connection initiated by an internet client to a VIP.
The value can be set between 4 and 30 minutes. The default value is
4 minutes.
"""
protocol = model.Field(name="protocol", key="protocol",
is_required=True, is_read_only=False)
"""Indicates the inbound transport protocol for the external endpoint.
Valid values include `UDP`, `TCP`, `GRE`, `ESP` or `ALL`.
"""
probe = model.Field(name="probe", key="probe",
is_required=False, is_read_only=False)
"""Indicates a reference to the probe resource used by this
LoadBalancingRule.
"""
floating_ip = model.Field(name="floating_ip", key="enableFloatingIP",
is_required=False, is_read_only=False)
"""
This specifies that a floating IP will be used on the available servers
behind a load balancer. Floating IP (VIP) will be forwarded by the load
balancer to the backend server. The back-end server will be configured
with that VIP, a datacenter IP and weakhost forwarding.
Floating IP configuration is required if you are using the SQL AlwaysOn
Availability Group feature. This setting can't be changed after you create
the endpoint.
"""
load_distribution = model.Field(
name="load_distribution", key="loadDistribution",
is_required=False, is_read_only=False)
"""This specifies the load balancing distribution type to be used by
the load balancer. The loadBalancer uses a distribution algorithm which
is a 5 tuple (source IP, source port, destination IP, destination port,
protocol type) hash to map traffic to available servers. It provides
stickiness only within a transport session, which is a feature that routes
the requests for a particular session to the same physical machine that
serviced the first request for that session. Packets in the same TCP or
UDP session will be directed to the same datacenter IP instance behind the
load balanced endpoint. When the client closes and re-opens the connection
or starts a new session from the same source IP, the source port changes
and causes the traffic to go to a different datacenter IP endpoint.
The loadBalancer can be configured to use a 2 tuple (Source IP,
Destination IP) or 3 tuple (Source IP, Destination IP, Protocol) to map
traffic to the available servers. By using SourceIPProtocol, connections
initiated from the same client computer goes to the same datacenter IP
endpoint.
* Default - The load balancer is configured to use a 5 tuple hash
to map traffic to available servers
* SourceIP - The load balancer is configured to use a 2 tuple hash
to map traffic to available servers
* SourceIPProtocol - The load balancer is configured to use a 3 tuple
hash to map traffic to available servers
"""
@classmethod
def from_raw_data(cls, raw_data):
"""Create a new model using raw API response."""
properties = raw_data.get("properties", {})
frontend_ip_configurations = []
for raw_content in properties.get("frontendIPConfigurations", []):
resource = Resource.from_raw_data(raw_content)
frontend_ip_configurations.append(resource)
properties["frontendIPConfigurations"] = frontend_ip_configurations
raw_content = properties.get("backendAddressPool", None)
if raw_content is not None:
resource = Resource.from_raw_data(raw_content)
properties["backendAddressPool"] = resource
raw_content = properties.get("probe", None)
if raw_content is not None:
resource = Resource.from_raw_data(raw_content)
properties["probe"] = resource
return super(LoadBalancingRules, cls).from_raw_data(raw_data)
class OutboundNATRules(_BaseHNVModel):
"""Model for outbound NAT rules.
This resource is used to configure the load balancer to apply
Network Address Translation of outbound traffic.
"""
_endpoint = ("/networking/v1/loadBalancers/{parent_id}"
"/outboundNatRules/{resource_id}")
parent_id = model.Field(
name="parent_id", key="parentResourceID",
is_property=False, is_required=True, is_read_only=True)
"""The parent resource ID field contains the resource ID that is
associated with network objects that are ancestors of the necessary
resource.
"""
frontend_ip_configurations = model.Field(
name="frontend_ip_configurations", key="frontendIPConfigurations",
is_required=True, is_read_only=False)
"""Indicates an array of frontendIpConfigurations resources.
Indicates an array of references to frontendIpAddress resources.
"""
backend_address_pool = model.Field(
name="backend_address_pool", key="backendAddressPool",
is_required=True, is_read_only=False)
"""Indicates a reference to the backendAddressPool resource.
This is the pool of IP addresses where outbound traffic originates.
"""
protocol = model.Field(
name="protocol", key="protocol",
is_required=True, is_read_only=False)
"""Protocol for outbound traffic. For transparent outbound NAT
specify "all".
Valid values include `TCP`, `UDP`, `GRE`, `ESP` or `All`.
"""
@classmethod
def from_raw_data(cls, raw_data):
"""Create a new model using raw API response."""
properties = raw_data.get("properties", {})
frontend_ip_configurations = []
for raw_content in properties.get("frontendIPConfigurations", []):
resource = Resource.from_raw_data(raw_content)
frontend_ip_configurations.append(resource)
properties["frontendIPConfigurations"] = frontend_ip_configurations
raw_content = properties.get("backendAddressPool", None)
if raw_content is not None:
resource = Resource.from_raw_data(raw_content)
properties["backendAddressPool"] = resource
return super(OutboundNATRules, cls).from_raw_data(raw_data)
class Probes(_BaseHNVModel):
"""Model for probes."""
_endpoint = ("/networking/v1/loadBalancers/{parent_id}"
"/probes/{resource_id}")
parent_id = model.Field(
name="parent_id", key="parentResourceID",
is_property=False, is_required=True, is_read_only=True)
"""The parent resource ID field contains the resource ID that is
associated with network objects that are ancestors of the necessary
resource.
"""
interval = model.Field(name="interval", key="intervalInSeconds",
is_required=False, is_read_only=False)
"""Indicates the interval, in seconds, for how frequently to probe the
endpoint for health status. Typically, the interval is slightly less than
half the allocated timeout period (in seconds) which allows two full
probes before taking the instance out of rotation. The default value is
15, the minimum value is 5.
"""
load_balancing_rules = model.Field(
name="load_balancing_rules", key="loadBalancingRules",
is_required=False, is_read_only=True)
"""Indicates an array of references to loadBalancingRule resources that
use this probe.
"""
number_of_probes = model.Field(
name="number_of_probes", key="numberOfProbes",
is_required=False, is_read_only=False)
"""Indicates the timeout period, in seconds, applied to the probe where
no response will result in stopping further traffic from being delivered
to the endpoint. This value allows endpoints to be taken out of rotation
faster or slower than the typical times (which are the defaults).
The default value is 31, the minimum value is 11.
"""
protocol = model.Field(name="protocol", key="protocol",
is_required=True, is_read_only=False)
"""Indicates the protocol of the end point.
Valid values are `HTTP` or `TCP`. If `TCP` is specified, a received ACK
is required for the probe to be successful. If `HTTP` is specified,
a 200 OK response from the specified URI is required for the probe to
be successful.
"""
port = model.Field(name="port", key="port",
is_required=True, is_read_only=False)
"""Indicates the port for communicating the probe. Possible values range
from 1 to 65535, inclusive.
"""
request_path = model.Field(name="request_path", key="requestPath",
is_required=True, is_read_only=False)
"""Indicates the URI used for requesting health status from the VM.
The path is required if protocol is set to HTTP. Otherwise, it is not
allowed. There is no default value.
"""
@classmethod
def from_raw_data(cls, raw_data):
"""Create a new model using raw API response."""
properties = raw_data.get("properties", {})
load_balancing_rules = []
for raw_content in properties.get("loadBalancingRules", []):
resource = Resource.from_raw_data(raw_content)
load_balancing_rules.append(resource)
properties["loadBalancingRules"] = load_balancing_rules
return super(Probes, cls).from_raw_data(raw_data)
class LoadBalancers(_BaseHNVModel):
"""Model for load balancers.
The loadBalancers resource allows fine-grained configuration of the
distribution of incoming traffic across VM instances that are hosted
in a Windows Server and System Center cloud. This resource has two
main parts: a frontend and a backend configuration.
The frontend configuration exposes the IP address of the load balancer.
For example, this address can be a reserved public or private IP address
previously provided to the client, or it can be an IP address that is
dynamically allocated from a subnet of a virtual network.
"""
_endpoint = "/networking/v1/loadBalancers/{resource_id}"
backend_address_pools = model.Field(name="backend_address_pools",
key="backendAddressPools",
is_required=False, is_read_only=False)
"""Indicates the backend Address Pool of the load balancer."""
frontend_ip_configurations = model.Field(
name="frontend_ip_configurations", key="frontendIPConfigurations",
is_required=True, is_read_only=False)
"""Indicates the frontend IP addresses of the load balancer."""
load_balancing_rules = model.Field(name="load_balancing_rules",
key="loadBalancingRules",
is_required=False, is_read_only=False)
"""A list of load balancing configurations.
Each configuration describes what traffic and how it gets load balanced
between backend IPs.
"""
inbound_nat_rules = model.Field(name="inbound_nat_rules",
key="inboundNatRules",
is_required=False, is_read_only=False)
"""Indicates an array of inbound NAT rules configured for the
load balancer.
"""
outbound_nat_rules = model.Field(name="outbound_nat_rules",
key="outboundNatRules",
is_required=False, is_read_only=False)
"""Indicates an array of outbound NAT rules configured for the
load balancer.
"""
probes = model.Field(name="probes", key="probes",
is_required=False, is_read_only=False)
"""Indicates an array of probes configured for the
load balancer.
"""
@classmethod
def from_raw_data(cls, raw_data):
properties = raw_data.get("properties", {})
backend_address_pools = []
for raw_content in properties.get("backendAddressPools", []):
raw_content["parentResourceID"] = raw_data["resourceId"]
address_pool = BackendAddressPools.from_raw_data(raw_content)
backend_address_pools.append(address_pool)
properties["backendAddressPools"] = backend_address_pools
frontend_ip_configurations = []
for raw_content in properties.get("frontendIPConfigurations", []):
raw_content["parentResourceID"] = raw_data["resourceId"]
ip_configurations = FrontendIPConfigurations.from_raw_data(
raw_content)
frontend_ip_configurations.append(ip_configurations)
properties["frontendIPConfigurations"] = frontend_ip_configurations
inbound_nat_rules = []
for raw_content in properties.get("inboundNatRules", []):
raw_content["parentResourceID"] = raw_data["resourceId"]
inbound_nat_rule = InboundNATRules.from_raw_data(raw_content)
inbound_nat_rules.append(inbound_nat_rule)
properties["inboundNatRules"] = inbound_nat_rules
outbound_nat_rules = []
for raw_content in properties.get("outboundNatRules", []):
raw_content["parentResourceID"] = raw_data["resourceId"]
inbound_nat_rule = OutboundNATRules.from_raw_data(raw_content)
outbound_nat_rules.append(inbound_nat_rule)
properties["outboundNatRules"] = outbound_nat_rules
load_balancing_rules = []
for raw_content in properties.get("loadBalancingRules", []):
raw_content["parentResourceID"] = raw_data["resourceId"]
balancing_rule = LoadBalancingRules.from_raw_data(raw_content)
load_balancing_rules.append(balancing_rule)
properties["loadBalancingRules"] = load_balancing_rules
probes = []
for raw_content in properties.get("probes", []):
raw_content["parentResourceID"] = raw_data["resourceId"]
probe = Probes.from_raw_data(raw_content)
probes.append(probe)
properties["probes"] = probes
return super(LoadBalancers, cls).from_raw_data(raw_data)

View File

@ -317,7 +317,7 @@ class Model(object):
self._changes[field.key] = fields[field_name]
self._data.update(self._changes)
def commit(self, wait=False, timeout=None):
def commit(self, if_match=None, wait=False, timeout=None):
"""Apply all the changes on the current model."""
# pylint: disable=unused-argument
self._data.update(self._changes)

View File

@ -104,13 +104,19 @@ class _HNVClient(object):
else:
return not self._https_allow_insecure
def _http_request(self, resource, method=constant.GET, body=None):
url = requests.compat.urljoin(self._base_url, resource)
def _http_request(self, resource, method=constant.GET, body=None,
if_match=None):
if not resource.startswith("http"):
url = requests.compat.urljoin(self._base_url, resource)
else:
url = resource
headers = self._get_headers()
if method in (constant.PUT, constant.PATCH):
etag = (body or {}).get("etag", None)
if etag is not None:
headers["If-Match"] = etag
if not isinstance(if_match, six.string_types):
if_match = (body or {}).get("etag", None)
if if_match is not None:
headers["If-Match"] = if_match
attemts = 0
while True:
@ -162,9 +168,10 @@ class _HNVClient(object):
except ValueError:
raise exception.ServiceException("Invalid service response.")
def update_resource(self, path, data):
def update_resource(self, path, data, if_match=None):
"""Update the required resource."""
response = self._http_request(path, method="PUT", body=data)
response = self._http_request(resource=path, method="PUT", body=data,
if_match=if_match)
try:
return response.json()
except ValueError:

View File

@ -105,31 +105,31 @@ class TestHNVClient(unittest.TestCase):
if isinstance(expected_response, requests.exceptions.SSLError):
self.assertRaises(exception.CertificateVerifyFailed,
client._http_request,
mock.sentinel.resource, method, body)
"/fake/resource", method, body)
return
elif isinstance(expected_response, requests.ConnectionError):
self.assertRaises(requests.ConnectionError,
client._http_request,
mock.sentinel.resource, method, body)
"/fake/resource", method, body)
return
elif status_code == 400:
self.assertRaises(exception.ServiceException,
client._http_request,
mock.sentinel.resource, method, body)
"/fake/resource", method, body)
elif status_code == 404:
self.assertRaises(exception.NotFound,
client._http_request,
mock.sentinel.resource, method, body)
"/fake/resource", method, body)
elif status_code != 200:
self.assertRaises(requests.HTTPError,
client._http_request,
mock.sentinel.resource, method, body)
"/fake/resource", method, body)
else:
client_response = client._http_request(mock.sentinel.resource,
client_response = client._http_request("/fake/resource",
method, body)
mock_join.assert_called_once_with(mock.sentinel.url,
mock.sentinel.resource)
"/fake/resource")
mock_headers.assert_called_once_with()
if not method == constant.GET:
etag = (body or {}).get("etag", None)
@ -236,9 +236,9 @@ class TestHNVClient(unittest.TestCase):
mock.sentinel.data)
self.assertIs(response, mock.sentinel.response)
mock_http_request.assert_called_once_with(mock.sentinel.path,
method="PUT",
body=mock.sentinel.data)
mock_http_request.assert_called_once_with(
resource=mock.sentinel.path, method="PUT", body=mock.sentinel.data,
if_match=None)
self.assertRaises(exception.ServiceException,
client.update_resource,
mock.sentinel.path, mock.sentinel.data)

View File

@ -89,3 +89,31 @@ class FakeResponse(object):
def public_ip_addresses(self):
"""Fake GET(all) response for public IP addresses."""
return self._load_resource("public_ip_addresses.json")
def backend_address_pools(self):
"""Fake GET(all) response for backend address pools."""
return self._load_resource("backend_address_pools.json")
def frontend_ip_configurations(self):
"""Fake GET(all) response for frontend ip configurations."""
return self._load_resource("frontend_ip_configurations.json")
def inbound_nat_rules(self):
"""Fake GET(all) response for inbound nat rules."""
return self._load_resource("inbound_nat_rules.json")
def load_balancing_rules(self):
"""Fake GET(all) response for load balacing rules."""
return self._load_resource("load_balancing_rules.json")
def outbound_nat_rules(self):
"""Fake GET(all) response for outbound nat rules."""
return self._load_resource("outbound_nat_rules.json")
def probes(self):
"""Fake GET(all) response for probes."""
return self._load_resource("probes.json")
def load_balancers(self):
"""Fake GET(all) response for load balancers."""
return self._load_resource("load_balancers.json")

View File

@ -0,0 +1,51 @@
{
"value": [
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971- 2682d597e098/backendAddressPools/b32b5ef0-5332-49a8-b383-f91090135f71",
"resourceId": "b32b5ef0-5332-49a8-b383-f91090135f71",
"etag": "W/\"fb318cf6-9102-4e34-a684-5e25aee8d3f4\"",
"instanceId": "f980604c-258c-4d60-8be4-559edd085384",
"properties": {
"provisioningState": "Succeeded",
"backendIPConfigurations": [
{
"resourceRef": "/networkInterfaces/97c69782-f173-4793-a408-64074e601dd1/ipConfigurations/1b94ce74-b012-49a7-8e93-9315252c6ab2"
},
{
"resourceRef": "/networkInterfaces/e5ea0c14-ce85-4eb7-909a-993f0477f5ac/ipConfigurations/45af7ff3-555f-43b0-ae74-7fcce88c5197"
}
],
"outboundNatRules": [
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/outboundNatRules/49053c15-2d0f-45a2-8148-be8615282160"
}
],
"loadBalancingRules": []
}
},
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/backendAddressPools/b32b5ef0-5332-49a8-b383-f91090135f71",
"resourceId": "b32b5ef0-5332-49a8-b383-f91090135f71",
"etag": "W/\"fb318cf6-9102-4e34-a684-5e25aee8d3f4\"",
"instanceId": "f980604c-258c-4d60-8be4-559edd085384",
"properties": {
"provisioningState": "Succeeded",
"backendIPConfigurations": [
{
"resourceRef": "/networkInterfaces/97c69782-f173-4793-a408-64074e601dd1/ipConfigurations/1b94ce74-b012-49a7-8e93-9315252c6ab2"
},
{
"resourceRef": "/networkInterfaces/e5ea0c14-ce85-4eb7-909a-993f0477f5ac/ipConfigurations/45af7ff3-555f-43b0-ae74-7fcce88c5197"
}
],
"outboundNatRules": [
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/outboundNatRules/49053c15-2d0f-45a2-8148-be8615282160"
}
],
"loadBalancingRules": []
}
}
],
"nextLink": ""
}

View File

@ -0,0 +1,80 @@
{
"value": [
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/frontendIPConfigurations/5187779d-c61c-44d2-87be-fa69ac2d9d57",
"resourceId": "5187779d-c61c-44d2-87be-fa69ac2d9d57",
"etag": "W/\"fb318cf6-9102-4e34-a684-5e25aee8d3f4\"",
"instanceId": "3902a530-9639-4759-9bbf-9bab6675593a",
"properties": {
"provisioningState": "Succeeded",
"privateIPAddress": "22.0.0.22",
"privateIPAllocationMethod": "Static",
"subnet": {
"resourceRef": "/logicalnetworks/ccb732ec-a3b5-4755-99ff-fddb91d50884/subnets/262b479f-0952-49b9-ad20-3d6732729389"
},
"loadBalancingRules": [],
"inboundNatRules": [
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/inboundNatRules/fc44af15-be82-46c5-b75a-3e89ccd792a9"
}
],
"outboundNatRules": [
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/outboundNatRules/49053c15-2d0f-45a2-8148-be8615282160"
}
]
}
},
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/frontendIPConfigurations/94c568d8-d839-431a-aed4-a5c178356018",
"resourceId": "94c568d8-d839-431a-aed4-a5c178356018",
"etag": "W/\"fb318cf6-9102-4e34-a684-5e25aee8d3f4\"",
"instanceId": "d896da12-37f2-4e36-b229-7278a672a0ac",
"properties": {
"provisioningState": "Succeeded",
"privateIPAddress": "22.0.0.23",
"privateIPAllocationMethod": "Static",
"subnet": {
"resourceRef": "/logicalnetworks/ccb732ec-a3b5-4755-99ff-fddb91d50884/subnets/262b479f-0952-49b9-ad20-3d6732729389"
},
"loadBalancingRules": [],
"inboundNatRules": [
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/inboundNatRules/0e5ed8cf-60fb-40f4-b02a-90932d4de000"
}
],
"outboundNatRules": [
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/outboundNatRules/49053c15-2d0f-45a2-8148-be8615282160"
}
]
}
},
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/frontendIPConfigurations/94c568d8-d839-431a-aed4-a5c178356018",
"resourceId": "94c568d8-d839-431a-aed4-a5c178356018",
"etag": "W/\"fb318cf6-9102-4e34-a684-5e25aee8d3f4\"",
"instanceId": "d896da12-37f2-4e36-b229-7278a672a0ac",
"properties": {
"provisioningState": "Succeeded",
"privateIPAddress": "22.0.0.23",
"privateIPAllocationMethod": "Static",
"subnet": {
"resourceRef": "/logicalnetworks/ccb732ec-a3b5-4755-99ff-fddb91d50884/subnets/262b479f-0952-49b9-ad20-3d6732729389"
},
"loadBalancingRules": [],
"inboundNatRules": [
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/inboundNatRules/0e5ed8cf-60fb-40f4-b02a-90932d4de000"
}
],
"outboundNatRules": [
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/outboundNatRules/49053c15-2d0f-45a2-8148-be8615282160"
}
]
}
}
],
"nextLink": ""
}

View File

@ -0,0 +1,85 @@
{
"value": [
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/inboundNatRules/fc44af15-be82-46c5-b75a-3e89ccd792a9",
"resourceId": "fc44af15-be82-46c5-b75a-3e89ccd792a9",
"etag": "W/\"fb318cf6-9102-4e34-a684-5e25aee8d3f4\"",
"instanceId": "a748c5db-e2fd-4335-8c89-280b78d2511c",
"properties": {
"provisioningState": "Succeeded",
"frontendIPConfigurations": [
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/frontendIPConfigurations/5187779d-c61c-44d2-87be-fa69ac2d9d57"
}
],
"protocol": "Tcp",
"frontendPort": 2003,
"backendPort": 2003,
"backendIPConfiguration": {
"resourceRef": "/networkInterfaces/e5ea0c14-ce85-4eb7-909a-993f0477f5ac/ipConfigurations/45af7ff3-555f-43b0-ae74-7fcce88c5197"
}
}
},
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/inboundNatRules/0e5ed8cf-60fb-40f4-b02a-90932d4de000",
"resourceId": "0e5ed8cf-60fb-40f4-b02a-90932d4de000",
"etag": "W/\"fb318cf6-9102-4e34-a684-5e25aee8d3f4\"",
"instanceId": "e8c59538-e641-4796-968d-50c4e11225e7",
"properties": {
"provisioningState": "Succeeded",
"frontendIPConfigurations": [
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/frontendIPConfigurations/94c568d8-d839-431a-aed4-a5c178356018"
}
],
"protocol": "Tcp",
"frontendPort": 2003,
"backendPort": 2003,
"backendIPConfiguration": {
"resourceRef": "/networkInterfaces/97c69782-f173-4793-a408-64074e601dd1/ipConfigurations/1b94ce74-b012-49a7-8e93-9315252c6ab2"
}
}
},
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/inboundNatRules/fc44af15-be82-46c5-b75a-3e89ccd792a9",
"resourceId": "fc44af15-be82-46c5-b75a-3e89ccd792a9",
"etag": "W/\"fb318cf6-9102-4e34-a684-5e25aee8d3f4\"",
"instanceId": "a748c5db-e2fd-4335-8c89-280b78d2511c",
"properties": {
"provisioningState": "Succeeded",
"frontendIPConfigurations": [
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/frontendIPConfigurations/5187779d-c61c-44d2-87be-fa69ac2d9d57"
}
],
"protocol": "Tcp",
"frontendPort": 2003,
"backendPort": 2003,
"backendIPConfiguration": {
"resourceRef": "/networkInterfaces/e5ea0c14-ce85-4eb7-909a-993f0477f5ac/ipConfigurations/45af7ff3-555f-43b0-ae74-7fcce88c5197"
}
}
},
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/inboundNatRules/fc44af15-be82-46c5-b75a-3e89ccd792a9",
"resourceId": "fc44af15-be82-46c5-b75a-3e89ccd792a9",
"etag": "W/\"fb318cf6-9102-4e34-a684-5e25aee8d3f4\"",
"instanceId": "a748c5db-e2fd-4335-8c89-280b78d2511c",
"properties": {
"provisioningState": "Succeeded",
"frontendIPConfigurations": [
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/frontendIPConfigurations/5187779d-c61c-44d2-87be-fa69ac2d9d57"
}
],
"protocol": "Tcp",
"frontendPort": 2003,
"backendPort": 2003,
"backendIPConfiguration": {
"resourceRef": "/networkInterfaces/e5ea0c14-ce85-4eb7-909a-993f0477f5ac/ipConfigurations/45af7ff3-555f-43b0-ae74-7fcce88c5197"
}
}
}
],
"nextLink": ""
}

View File

@ -0,0 +1,347 @@
{
"value": [
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098",
"resourceId": "0cac5f8a-9d5c-455a-a971-2682d597e098",
"etag": "W/\"fb318cf6-9102-4e34-a684-5e25aee8d3f4\"",
"instanceId": "d91f4951-faf7-4a15-a84a-8a9f6dffaff8",
"properties": {
"provisioningState": "Succeeded",
"frontendIPConfigurations": [
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/frontendIPConfigurations/5187779d-c61c-44d2-87be-fa69ac2d9d57",
"resourceId": "5187779d-c61c-44d2-87be-fa69ac2d9d57",
"etag": "W/\"fb318cf6-9102-4e34-a684-5e25aee8d3f4\"",
"instanceId": "3902a530-9639-4759-9bbf-9bab6675593a",
"properties": {
"provisioningState": "Succeeded",
"privateIPAddress": "22.0.0.22",
"privateIPAllocationMethod": "Static",
"subnet": {
"resourceRef": "/logicalnetworks/ccb732ec-a3b5-4755-99ff-fddb91d50884/subnets/262b479f-0952-49b9-ad20-3d6732729389"
},
"loadBalancingRules": [],
"inboundNatRules": [
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/inboundNatRules/fc44af15-be82-46c5-b75a-3e89ccd792a9"
}
],
"outboundNatRules": [
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/outboundNatRules/49053c15-2d0f-45a2-8148-be8615282160"
}
]
}
},
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/frontendIPConfigurations/94c568d8-d839-431a-aed4-a5c178356018",
"resourceId": "94c568d8-d839-431a-aed4-a5c178356018",
"etag": "W/\"fb318cf6-9102-4e34-a684-5e25aee8d3f4\"",
"instanceId": "d896da12-37f2-4e36-b229-7278a672a0ac",
"properties": {
"provisioningState": "Succeeded",
"privateIPAddress": "22.0.0.23",
"privateIPAllocationMethod": "Static",
"subnet": {
"resourceRef": "/logicalnetworks/ccb732ec-a3b5-4755-99ff-fddb91d50884/subnets/262b479f-0952-49b9-ad20-3d6732729389"
},
"loadBalancingRules": [],
"inboundNatRules": [
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/inboundNatRules/0e5ed8cf-60fb-40f4-b02a-90932d4de000"
}
],
"outboundNatRules": [
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/outboundNatRules/49053c15-2d0f-45a2-8148-be8615282160"
}
]
}
}
],
"backendAddressPools": [
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/backendAddressPools/b32b5ef0-5332-49a8-b383-f91090135f71",
"resourceId": "b32b5ef0-5332-49a8-b383-f91090135f71",
"etag": "W/\"fb318cf6-9102-4e34-a684-5e25aee8d3f4\"",
"instanceId": "f980604c-258c-4d60-8be4-559edd085384",
"properties": {
"provisioningState": "Succeeded",
"backendIPConfigurations": [
{
"resourceRef": "/networkInterfaces/97c69782-f173-4793-a408-64074e601dd1/ipConfigurations/1b94ce74-b012-49a7-8e93-9315252c6ab2"
},
{
"resourceRef": "/networkInterfaces/e5ea0c14-ce85-4eb7-909a-993f0477f5ac/ipConfigurations/45af7ff3-555f-43b0-ae74-7fcce88c5197"
}
],
"outboundNatRules": [
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/outboundNatRules/49053c15-2d0f-45a2-8148-be8615282160"
}
],
"loadBalancingRules": []
}
}
],
"probes": [
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/probes/9f940e29-1d25-44fc-88d3-c81151a0344e",
"resourceId": "9f940e29-1d25-44fc-88d3-c81151a0344e",
"etag": "W/\"fb318cf6-9102-4e34-a684-5e25aee8d3f4\"",
"instanceId": "0da65588-247b-475b-bd1a-7ead0ba1a182",
"properties": {
"provisioningState": "Succeeded",
"protocol": "Tcp",
"port": 55555,
"intervalInSeconds": 30,
"numberOfProbes": 1,
"loadBalancingRules": []
}
}
],
"inboundNatRules": [
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/inboundNatRules/fc44af15-be82-46c5-b75a-3e89ccd792a9",
"resourceId": "fc44af15-be82-46c5-b75a-3e89ccd792a9",
"etag": "W/\"fb318cf6-9102-4e34-a684-5e25aee8d3f4\"",
"instanceId": "a748c5db-e2fd-4335-8c89-280b78d2511c",
"properties": {
"provisioningState": "Succeeded",
"frontendIPConfigurations": [
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/frontendIPConfigurations/5187779d-c61c-44d2-87be-fa69ac2d9d57"
}
],
"protocol": "Tcp",
"frontendPort": 2003,
"backendPort": 2003,
"enableFloatingIP": false,
"idleTimeoutInMinutes": 4,
"backendIPConfiguration": {
"resourceRef": "/networkInterfaces/e5ea0c14-ce85-4eb7-909a-993f0477f5ac/ipConfigurations/45af7ff3-555f-43b0-ae74-7fcce88c5197"
}
}
},
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/inboundNatRules/0e5ed8cf-60fb-40f4-b02a-90932d4de000",
"resourceId": "0e5ed8cf-60fb-40f4-b02a-90932d4de000",
"etag": "W/\"fb318cf6-9102-4e34-a684-5e25aee8d3f4\"",
"instanceId": "e8c59538-e641-4796-968d-50c4e11225e7",
"properties": {
"provisioningState": "Succeeded",
"frontendIPConfigurations": [
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/frontendIPConfigurations/94c568d8-d839-431a-aed4-a5c178356018"
}
],
"protocol": "Tcp",
"frontendPort": 2003,
"backendPort": 2003,
"enableFloatingIP": false,
"idleTimeoutInMinutes": 4,
"backendIPConfiguration": {
"resourceRef": "/networkInterfaces/97c69782-f173-4793-a408-64074e601dd1/ipConfigurations/1b94ce74-b012-49a7-8e93-9315252c6ab2"
}
}
}
],
"outboundNatRules": [
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/outboundNatRules/49053c15-2d0f-45a2-8148-be8615282160",
"resourceId": "49053c15-2d0f-45a2-8148-be8615282160",
"etag": "W/\"fb318cf6-9102-4e34-a684-5e25aee8d3f4\"",
"instanceId": "c4000c95-7f90-4bb4-b68d-b2bc9c1dfc3e",
"properties": {
"provisioningState": "Succeeded",
"frontendIPConfigurations": [
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/frontendIPConfigurations/5187779d-c61c-44d2-87be-fa69ac2d9d57"
},
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/frontendIPConfigurations/94c568d8-d839-431a-aed4-a5c178356018"
}
],
"protocol": "All",
"backendAddressPool": {
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/backendAddressPools/b32b5ef0-5332-49a8-b383-f91090135f71"
}
}
}
]
}
},
{
"resourceRef": "/loadBalancers/d2251a0d-32d2-457e-b3aa-e0fe1f42cce1",
"resourceId": "d2251a0d-32d2-457e-b3aa-e0fe1f42cce1",
"etag": "W/\"72fdfa3d-34f4-4c90-ae94-d97ed73c9cf7\"",
"instanceId": "b32d0db3-13db-431a-a265-32185aa5a905",
"properties": {
"provisioningState": "Succeeded",
"frontendIPConfigurations": [
{
"resourceRef": "/loadBalancers/d2251a0d-32d2-457e-b3aa-e0fe1f42cce1/frontendIPConfigurations/9f37a479-7d60-489a-aab6-d7eb2200306f",
"resourceId": "9f37a479-7d60-489a-aab6-d7eb2200306f",
"etag": "W/\"72fdfa3d-34f4-4c90-ae94-d97ed73c9cf7\"",
"instanceId": "51b57d2a-80da-464a-988a-4a805bd1d875",
"properties": {
"provisioningState": "Succeeded",
"privateIPAddress": "21.0.0.23",
"privateIPAllocationMethod": "Static",
"subnet": {
"resourceRef": "/logicalnetworks/9c1b2b61-dec2-49e3-b573-c2ecff57893d/subnets/a4f7c90b-6056-4dff-97fb-f46211ecdc10"
},
"loadBalancingRules": [],
"inboundNatRules": [
{
"resourceRef": "/loadBalancers/d2251a0d-32d2-457e-b3aa-e0fe1f42cce1/inboundNatRules/d076eae7-926a-457a-a60c-0a713a02977d"
}
],
"outboundNatRules": [
{
"resourceRef": "/loadBalancers/d2251a0d-32d2-457e-b3aa-e0fe1f42cce1/outboundNatRules/f3f3291d-b26c-44d3-8d55-99b644b70388"
}
]
}
},
{
"resourceRef": "/loadBalancers/d2251a0d-32d2-457e-b3aa-e0fe1f42cce1/frontendIPConfigurations/ab5ccbe7-2ce9-4cdf-a0da-e4e5d81479d8",
"resourceId": "ab5ccbe7-2ce9-4cdf-a0da-e4e5d81479d8",
"etag": "W/\"72fdfa3d-34f4-4c90-ae94-d97ed73c9cf7\"",
"instanceId": "fe6adbed-8b73-4fc2-82cd-191143753c4a",
"properties": {
"provisioningState": "Succeeded",
"privateIPAddress": "21.0.0.24",
"privateIPAllocationMethod": "Static",
"subnet": {
"resourceRef": "/logicalnetworks/9c1b2b61-dec2-49e3-b573-c2ecff57893d/subnets/a4f7c90b-6056-4dff-97fb-f46211ecdc10"
},
"loadBalancingRules": [],
"inboundNatRules": [
{
"resourceRef": "/loadBalancers/d2251a0d-32d2-457e-b3aa-e0fe1f42cce1/inboundNatRules/425eea91-5a9e-4777-b2c3-0442dfc20344"
}
],
"outboundNatRules": [
{
"resourceRef": "/loadBalancers/d2251a0d-32d2-457e-b3aa-e0fe1f42cce1/outboundNatRules/f3f3291d-b26c-44d3-8d55-99b644b70388"
}
]
}
}
],
"backendAddressPools": [
{
"resourceRef": "/loadBalancers/d2251a0d-32d2-457e-b3aa-e0fe1f42cce1/backendAddressPools/db1fa644-bd00-4c05-b11b-f5f07bfed86b",
"resourceId": "db1fa644-bd00-4c05-b11b-f5f07bfed86b",
"etag": "W/\"72fdfa3d-34f4-4c90-ae94-d97ed73c9cf7\"",
"instanceId": "b638b320-5569-444f-9adf-78a683072269",
"properties": {
"provisioningState": "Succeeded",
"backendIPConfigurations": [
{
"resourceRef": "/networkInterfaces/add9dac6-ddcc-4108-8543-e167c0a8d9dc/ipConfigurations/2e8a0316-66a6-4a3e-bd86-89b0e43b080f"
},
{
"resourceRef": "/networkInterfaces/b3dc7295-7144-4f6e-8235-35d88b917482/ipConfigurations/581ab448-8e6f-436c-9dec-43366a9817dd"
}
],
"outboundNatRules": [
{
"resourceRef": "/loadBalancers/d2251a0d-32d2-457e-b3aa-e0fe1f42cce1/outboundNatRules/f3f3291d-b26c-44d3-8d55-99b644b70388"
}
],
"loadBalancingRules": []
}
}
],
"probes": [
{
"resourceRef": "/loadBalancers/d2251a0d-32d2-457e-b3aa-e0fe1f42cce1/probes/ddb4dab8-b1eb-4476-90ca-948697240317",
"resourceId": "ddb4dab8-b1eb-4476-90ca-948697240317",
"etag": "W/\"72fdfa3d-34f4-4c90-ae94-d97ed73c9cf7\"",
"instanceId": "18336b2f-8b2e-4bf2-a196-99009ec8feb8",
"properties": {
"provisioningState": "Succeeded",
"protocol": "Tcp",
"port": 55555,
"intervalInSeconds": 30,
"numberOfProbes": 1,
"loadBalancingRules": []
}
}
],
"inboundNatRules": [
{
"resourceRef": "/loadBalancers/d2251a0d-32d2-457e-b3aa-e0fe1f42cce1/inboundNatRules/d076eae7-926a-457a-a60c-0a713a02977d",
"resourceId": "d076eae7-926a-457a-a60c-0a713a02977d",
"etag": "W/\"72fdfa3d-34f4-4c90-ae94-d97ed73c9cf7\"",
"instanceId": "4be2c156-cbcb-466d-a8fe-865bc9f0045d",
"properties": {
"provisioningState": "Succeeded",
"frontendIPConfigurations": [
{
"resourceRef": "/loadBalancers/d2251a0d-32d2-457e-b3aa-e0fe1f42cce1/frontendIPConfigurations/9f37a479-7d60-489a-aab6-d7eb2200306f"
}
],
"protocol": "Tcp",
"frontendPort": 2003,
"backendPort": 2003,
"enableFloatingIP": false,
"idleTimeoutInMinutes": 4,
"backendIPConfiguration": {
"resourceRef": "/networkInterfaces/b3dc7295-7144-4f6e-8235-35d88b917482/ipConfigurations/581ab448-8e6f-436c-9dec-43366a9817dd"
}
}
},
{
"resourceRef": "/loadBalancers/d2251a0d-32d2-457e-b3aa-e0fe1f42cce1/inboundNatRules/425eea91-5a9e-4777-b2c3-0442dfc20344",
"resourceId": "425eea91-5a9e-4777-b2c3-0442dfc20344",
"etag": "W/\"72fdfa3d-34f4-4c90-ae94-d97ed73c9cf7\"",
"instanceId": "ae841775-a3b2-454e-bd69-b78a298ca7bf",
"properties": {
"provisioningState": "Succeeded",
"frontendIPConfigurations": [
{
"resourceRef": "/loadBalancers/d2251a0d-32d2-457e-b3aa-e0fe1f42cce1/frontendIPConfigurations/ab5ccbe7-2ce9-4cdf-a0da-e4e5d81479d8"
}
],
"protocol": "Tcp",
"frontendPort": 2003,
"backendPort": 2003,
"enableFloatingIP": false,
"idleTimeoutInMinutes": 4,
"backendIPConfiguration": {
"resourceRef": "/networkInterfaces/add9dac6-ddcc-4108-8543-e167c0a8d9dc/ipConfigurations/2e8a0316-66a6-4a3e-bd86-89b0e43b080f"
}
}
}
],
"outboundNatRules": [
{
"resourceRef": "/loadBalancers/d2251a0d-32d2-457e-b3aa-e0fe1f42cce1/outboundNatRules/f3f3291d-b26c-44d3-8d55-99b644b70388",
"resourceId": "f3f3291d-b26c-44d3-8d55-99b644b70388",
"etag": "W/\"72fdfa3d-34f4-4c90-ae94-d97ed73c9cf7\"",
"instanceId": "f5065c75-ab45-4e5b-bb76-fb69667bf5d6",
"properties": {
"provisioningState": "Succeeded",
"frontendIPConfigurations": [
{
"resourceRef": "/loadBalancers/d2251a0d-32d2-457e-b3aa-e0fe1f42cce1/frontendIPConfigurations/9f37a479-7d60-489a-aab6-d7eb2200306f"
},
{
"resourceRef": "/loadBalancers/d2251a0d-32d2-457e-b3aa-e0fe1f42cce1/frontendIPConfigurations/ab5ccbe7-2ce9-4cdf-a0da-e4e5d81479d8"
}
],
"protocol": "All",
"backendAddressPool": {
"resourceRef": "/loadBalancers/d2251a0d-32d2-457e-b3aa-e0fe1f42cce1/backendAddressPools/db1fa644-bd00-4c05-b11b-f5f07bfed86b"
}
}
}
]
}
}
],
"nextLink": ""
}

View File

@ -0,0 +1,74 @@
{
"value": [
{
"resourceRef": "/loadBalancers/d2251a0d-32d2-457e-b3aa- e0fe1f42cce1/loadBalancingRules/6339de0b-5730-4057-b2ee-37e90d3e4470",
"resourceId": "6339de0b-5730-4057-b2ee-37e90d3e4470",
"etag": "W/\"87c5f43a-3d37-4955-b6ba-bc3037fcfefd\"",
"instanceId": "58b176c8-f4d1-4a5f-bfe4-623dcfe3ba2a",
"properties": {
"provisioningState": "Succeeded",
"frontendIPConfigurations": [
{
"resourceRef": "/loadBalancers/d2251a0d-32d2-457e-b3aa- e0fe1f42cce1/frontendIPConfigurations/6bad6ea2-eca8-4143-8925-55aa497d3882"
}
],
"protocol": "Tcp",
"frontendPort": 2003,
"backendPort": 2003,
"enableFloatingIP": false,
"idleTimeoutInMinutes": 4,
"backendAddressPool": {
"resourceRef": "/loadBalancers/d2251a0d-32d2-457e-b3aa- e0fe1f42cce1/backendAddressPools/9827f986-4606-4331-b63f-7cc39665e2c9"
},
"loadDistribution": "Default"
}
},
{
"resourceRef": "/loadBalancers/d2251a0d-32d2-457e-b3aa-e0fe1f42cce1/loadBalancingRules/6339de0b-5730-4057-b2ee-37e90d3e4470",
"resourceId": "6339de0b-5730-4057-b2ee-37e90d3e4470",
"etag": "W/\"87c5f43a-3d37-4955-b6ba-bc3037fcfefd\"",
"instanceId": "58b176c8-f4d1-4a5f-bfe4-623dcfe3ba2a",
"properties": {
"provisioningState": "Succeeded",
"frontendIPConfigurations": [
{
"resourceRef": "/loadBalancers/d2251a0d-32d2-457e-b3aa-e0fe1f42cce1/frontendIPConfigurations/6bad6ea2-eca8-4143-8925-55aa497d3882"
}
],
"protocol": "Tcp",
"frontendPort": 2003,
"backendPort": 2003,
"enableFloatingIP": false,
"idleTimeoutInMinutes": 4,
"backendAddressPool": {
"resourceRef": "/loadBalancers/d2251a0d-32d2-457e-b3aa-e0fe1f42cce1/backendAddressPools/9827f986-4606-4331-b63f-7cc39665e2c9"
},
"loadDistribution": "Default"
}
},
{
"resourceRef": "/loadBalancers/d2251a0d-32d2-457e-b3aa-e0fe1f42cce1/loadBalancingRules/6339de0b-5730-4057-b2ee-37e90d3e4470",
"resourceId": "6339de0b-5730-4057-b2ee-37e90d3e4470",
"etag": "W/\"87c5f43a-3d37-4955-b6ba-bc3037fcfefd\"",
"instanceId": "58b176c8-f4d1-4a5f-bfe4-623dcfe3ba2a",
"properties": {
"provisioningState": "Succeeded",
"frontendIPConfigurations": [
{
"resourceRef": "/loadBalancers/d2251a0d-32d2-457e-b3aa-e0fe1f42cce1/frontendIPConfigurations/6bad6ea2-eca8-4143-8925-55aa497d3882"
}
],
"protocol": "Tcp",
"frontendPort": 2003,
"backendPort": 2003,
"enableFloatingIP": false,
"idleTimeoutInMinutes": 4,
"backendAddressPool": {
"resourceRef": "/loadBalancers/d2251a0d-32d2-457e-b3aa-e0fe1f42cce1/backendAddressPools/9827f986-4606-4331-b63f-7cc39665e2c9"
},
"loadDistribution": "Default"
}
}
],
"nextLink": ""
}

View File

@ -0,0 +1,47 @@
{
"value": [
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/outboundNatRules/49053c15-2d0f-45a2-8148-be8615282160",
"resourceId": "49053c15-2d0f-45a2-8148-be8615282160",
"etag": "W/\"fb318cf6-9102-4e34-a684-5e25aee8d3f4\"",
"instanceId": "c4000c95-7f90-4bb4-b68d-b2bc9c1dfc3e",
"properties": {
"provisioningState": "Succeeded",
"frontendIPConfigurations": [
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/frontendIPConfigurations/5187779d-c61c-44d2-87be-fa69ac2d9d57"
},
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/frontendIPConfigurations/94c568d8-d839-431a-aed4-a5c178356018"
}
],
"protocol": "All",
"backendAddressPool": {
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/backendAddressPools/b32b5ef0-5332-49a8-b383-f91090135f71"
}
}
},
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/outboundNatRules/49053c15-2d0f-45a2-8148-be8615282160",
"resourceId": "49053c15-2d0f-45a2-8148-be8615282160",
"etag": "W/\"fb318cf6-9102-4e34-a684-5e25aee8d3f4\"",
"instanceId": "c4000c95-7f90-4bb4-b68d-b2bc9c1dfc3e",
"properties": {
"provisioningState": "Succeeded",
"frontendIPConfigurations": [
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/frontendIPConfigurations/5187779d-c61c-44d2-87be-fa69ac2d9d57"
},
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/frontendIPConfigurations/94c568d8-d839-431a-aed4-a5c178356018"
}
],
"protocol": "All",
"backendAddressPool": {
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/backendAddressPools/b32b5ef0-5332-49a8-b383-f91090135f71"
}
}
}
],
"nextLink": ""
}

View File

@ -0,0 +1,37 @@
{
"value": [
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/probes/9f940e29- 1d25-44fc-88d3-c81151a0344e",
"resourceId": "9f940e29-1d25-44fc-88d3-c81151a0344e",
"etag": "W/\"fb318cf6-9102-4e34-a684-5e25aee8d3f4\"",
"instanceId": "0da65588-247b-475b-bd1a-7ead0ba1a182",
"properties": {
"provisioningState": "Succeeded",
"protocol": "Tcp",
"port": 55555,
"intervalInSeconds": 30,
"numberOfProbes": 1,
"loadBalancingRules": []
}
},
{
"resourceRef": "/loadBalancers/0cac5f8a-9d5c-455a-a971-2682d597e098/probes/9f940e29-1d25- 44fc-88d3-c81151a0344e",
"resourceId": "9f940e29-1d25-44fc-88d3-c81151a0344e",
"etag": "W/\"fb318cf6-9102-4e34-a684-5e25aee8d3f4\"",
"instanceId": "0da65588-247b-475b-bd1a-7ead0ba1a182",
"properties": {
"provisioningState": "Succeeded",
"protocol": "Tcp",
"port": 55555,
"intervalInSeconds": 30,
"numberOfProbes": 1,
"loadBalancingRules": [
{
"resourceRef": "/loadBalancers/ee396509-27d3-44f9-849c-f6ed28d59f66/loadBalancingRules/2ea746ea-968f-41f2-8bfa-71d2391ef752"
}
]
}
}
],
"nextLink": ""
}

View File

@ -153,7 +153,8 @@ class TestBaseHNVModel(unittest.TestCase):
mock_dump.assert_called_once_with(include_read_only=False)
update_resource.assert_called_once_with(
"test/hnv-client", data=mock.sentinel.request_body)
"test/hnv-client", data=mock.sentinel.request_body,
if_match=None)
if request_wait:
self.assertEqual(get_resource.call_count, loop_count + 1)
@ -325,3 +326,45 @@ class TestClient(unittest.TestCase):
for raw_data in resources.get("value", []):
self._test_get_resource(model=client.PublicIPAddresses,
raw_data=raw_data)
def test_backend_address_pools(self):
resources = self._response.backend_address_pools()
for raw_data in resources.get("value", []):
self._test_get_resource(model=client.BackendAddressPools,
raw_data=raw_data)
def test_frontend_ip_configurations(self):
resources = self._response.frontend_ip_configurations()
for raw_data in resources.get("value", []):
self._test_get_resource(model=client.FrontendIPConfigurations,
raw_data=raw_data)
def test_inbound_nat_rules(self):
resources = self._response.inbound_nat_rules()
for raw_data in resources.get("value", []):
self._test_get_resource(model=client.InboundNATRules,
raw_data=raw_data)
def test_load_balancing_rules(self):
resources = self._response.load_balancing_rules()
for raw_data in resources.get("value", []):
self._test_get_resource(model=client.LoadBalancingRules,
raw_data=raw_data)
def test_outbound_nat_rules(self):
resources = self._response.outbound_nat_rules()
for raw_data in resources.get("value", []):
self._test_get_resource(model=client.OutboundNATRules,
raw_data=raw_data)
def test_probes(self):
resources = self._response.probes()
for raw_data in resources.get("value", []):
self._test_get_resource(model=client.Probes,
raw_data=raw_data)
def test_load_balancers(self):
resources = self._response.load_balancers()
for raw_data in resources.get("value", []):
self._test_get_resource(model=client.LoadBalancers,
raw_data=raw_data)