Improve the model for resource references
This commit is contained in:
parent
f383efc2e3
commit
0c5f95414c
hnv
138
hnv/client.py
138
hnv/client.py
@ -14,6 +14,7 @@
|
||||
|
||||
"""This module contains all the available HNV resources."""
|
||||
|
||||
import re
|
||||
import time
|
||||
import uuid
|
||||
|
||||
@ -103,6 +104,24 @@ class _BaseHNVModel(model.Model):
|
||||
""""Configuration state indicates any failures in processing state
|
||||
corresponding to the resource it is contained in."""
|
||||
|
||||
def _reset_model(self, response):
|
||||
"""Update the fields value with the received information."""
|
||||
|
||||
# pylint: disable=no-member
|
||||
|
||||
# Reset the model to the initial state
|
||||
self._data = self._meta.get_defaults()
|
||||
self._provision_done = False # Set back the provision flag
|
||||
self._changes.clear() # Clear the changes
|
||||
|
||||
# Process the raw data from the update response
|
||||
fields = self.process_raw_data(response)
|
||||
# Update the current model representation
|
||||
self._set_fields(fields)
|
||||
|
||||
# Lock the current model
|
||||
self._provision_done = True
|
||||
|
||||
@staticmethod
|
||||
def _get_client():
|
||||
"""Create a new client for the HNV REST API."""
|
||||
@ -113,17 +132,21 @@ class _BaseHNVModel(model.Model):
|
||||
ca_bundle=CONFIG.HNV.https_ca_bundle)
|
||||
|
||||
@classmethod
|
||||
def get(cls, resource_id=None, parent_id=None):
|
||||
def get(cls, resource_id=None, parent_id=None, grandparent_id=None):
|
||||
"""Retrieves the required resources.
|
||||
|
||||
:param resource_id: The identifier for the specific resource
|
||||
within the resource type.
|
||||
:param parent_id: The identifier for the specific ancestor
|
||||
resource within the resource type.
|
||||
:param grandparent_id: The identifier that is associated with
|
||||
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 "")
|
||||
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"]]
|
||||
@ -131,16 +154,21 @@ class _BaseHNVModel(model.Model):
|
||||
return cls.from_raw_data(raw_data)
|
||||
|
||||
@classmethod
|
||||
def remove(cls, resource_id, parent_id=None, wait=True, timeout=None):
|
||||
def remove(cls, resource_id, parent_id=None, grandparent_id=None,
|
||||
wait=True, timeout=None):
|
||||
"""Delete the required resource.
|
||||
|
||||
:param resource_id: The identifier for the specific resource
|
||||
within the resource type.
|
||||
:param parent_id: The identifier for the specific ancestor
|
||||
resource within the resource type.
|
||||
:param wait: Whether to wait until the operation is completed
|
||||
:param timeout: The maximum amount of time required for this
|
||||
operation to be completed.
|
||||
:param resource_id: The identifier for the specific resource
|
||||
within the resource type.
|
||||
:param parent_id: The identifier for the specific ancestor
|
||||
resource within the resource type.
|
||||
:param grandparent_id: The identifier that is associated with
|
||||
network objects that are ancestors of the
|
||||
parent of the necessary resource.
|
||||
:param wait: Whether to wait until the operation is
|
||||
completed
|
||||
:param timeout: The maximum amount of time required for this
|
||||
operation to be completed.
|
||||
|
||||
If optional :param wait: is True and timeout is None (the default),
|
||||
block if necessary until the resource is available. If timeout is a
|
||||
@ -153,7 +181,8 @@ class _BaseHNVModel(model.Model):
|
||||
"""
|
||||
client = cls._get_client()
|
||||
endpoint = cls._endpoint.format(resource_id=resource_id or "",
|
||||
parent_id=parent_id or "")
|
||||
parent_id=parent_id or "",
|
||||
grandparent_id=grandparent_id or "")
|
||||
client.remove_resource(endpoint)
|
||||
|
||||
elapsed_time = 0
|
||||
@ -168,6 +197,16 @@ class _BaseHNVModel(model.Model):
|
||||
raise exception.TimeOut("The request timed out.")
|
||||
time.sleep(CONFIG.HNV.retry_interval)
|
||||
|
||||
def refresh(self):
|
||||
"""Get the latest representation of the current model."""
|
||||
client = self._get_client()
|
||||
endpoint = self._endpoint.format(
|
||||
resource_id=self.resource_id or "", parent_id=self.parent_id or "",
|
||||
grandparent_id=self.grandparent_id or "")
|
||||
|
||||
response = client.get_resource(endpoint)
|
||||
self._reset_model(response)
|
||||
|
||||
def commit(self, wait=True, timeout=None):
|
||||
"""Apply all the changes on the current model.
|
||||
|
||||
@ -184,6 +223,9 @@ class _BaseHNVModel(model.Model):
|
||||
available, else raise the `NotFound` exception (timeout is ignored
|
||||
in that case).
|
||||
"""
|
||||
if not self._changes:
|
||||
return
|
||||
|
||||
super(_BaseHNVModel, self).commit(wait=wait, timeout=timeout)
|
||||
client = self._get_client()
|
||||
endpoint = self._endpoint.format(resource_id=self.resource_id or "",
|
||||
@ -193,31 +235,24 @@ class _BaseHNVModel(model.Model):
|
||||
|
||||
elapsed_time = 0
|
||||
while wait:
|
||||
response = client.get_resource(endpoint)
|
||||
properties = response.get("properties", {})
|
||||
provisioning_state = properties.get("provisioningState", None)
|
||||
if not provisioning_state:
|
||||
self.refresh() # Update the representation of the current model
|
||||
if not self.provisioning_state:
|
||||
raise exception.ServiceException("The object doesn't contain "
|
||||
"`provisioningState`.")
|
||||
if provisioning_state == constant.FAILED:
|
||||
elif self.provisioning_state == constant.FAILED:
|
||||
raise exception.ServiceException(
|
||||
"Failed to complete the required operation.")
|
||||
elif provisioning_state == constant.SUCCEEDED:
|
||||
elif self.provisioning_state == constant.SUCCEEDED:
|
||||
break
|
||||
|
||||
elapsed_time += CONFIG.HNV.retry_interval
|
||||
if timeout and elapsed_time > timeout:
|
||||
raise exception.TimeOut("The request timed out.")
|
||||
time.sleep(CONFIG.HNV.retry_interval)
|
||||
else:
|
||||
self._reset_model(response)
|
||||
|
||||
# Process the raw data from the update response
|
||||
fields = self.process_raw_data(response)
|
||||
# Set back the provision flag
|
||||
self._provision_done = False
|
||||
# Update the current model representation
|
||||
self._set_fields(fields)
|
||||
# Lock the current model
|
||||
self._provision_done = True
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def from_raw_data(cls, raw_data):
|
||||
@ -241,10 +276,42 @@ class Resource(model.Model):
|
||||
|
||||
"""Model for the resource references."""
|
||||
|
||||
_regexp = {}
|
||||
|
||||
resource_ref = model.Field(name="resource_ref", key="resourceRef",
|
||||
is_property=False, is_required=True)
|
||||
"""A relative URI to an associated resource."""
|
||||
|
||||
def __init__(self, **fields):
|
||||
super(Resource, self).__init__(**fields)
|
||||
if not self._regexp:
|
||||
self._load_models()
|
||||
|
||||
def _load_models(self):
|
||||
models = globals().copy()
|
||||
for _, model_cls in models.iteritems():
|
||||
endpoint = getattr(model_cls, "_endpoint", None)
|
||||
if endpoint is not None:
|
||||
regexp = endpoint.format(
|
||||
resource_id="(?P<resource_id>[^/]+)",
|
||||
parent_id="(?P<parent_id>[^/]+)",
|
||||
grandparent_id="(?P<grandparent_id>[^/]+)")
|
||||
regexp = re.sub("(/networking/v[0-9]+)/", "", regexp)
|
||||
self._regexp[model_cls] = re.compile(regexp)
|
||||
|
||||
def get_resource(self):
|
||||
"""Return the associated resource."""
|
||||
references = {"resource_id": None, "parent_id": None,
|
||||
"grandparent_id": None}
|
||||
for model_cls, regexp in self._regexp.iteritems():
|
||||
match = regexp.search(self.resource_ref)
|
||||
if match is not None:
|
||||
references.update(match.groupdict())
|
||||
return model_cls.get(**references)
|
||||
|
||||
raise exception.NotFound("No model available for %(resource_ref)r",
|
||||
resource_ref=self.resource_ref)
|
||||
|
||||
|
||||
class ResourceMetadata(model.Model):
|
||||
|
||||
@ -379,7 +446,7 @@ class LogicalSubnetworks(_BaseHNVModel):
|
||||
"""Indicates the IP Pools that are contained in the logical subnet."""
|
||||
|
||||
dns_servers = model.Field(name="dns_servers", key="dnsServers",
|
||||
is_required=False)
|
||||
is_required=False, default=list)
|
||||
"""Indicates one or more DNS servers that are used for resolving DNS
|
||||
queries by devices or host connected to this logical subnet."""
|
||||
|
||||
@ -442,7 +509,7 @@ class LogicalNetworks(_BaseHNVModel):
|
||||
_endpoint = "/networking/v1/logicalNetworks/{resource_id}"
|
||||
|
||||
subnetworks = model.Field(name="subnetworks", key="subnets",
|
||||
is_required=False, default=[])
|
||||
is_required=False, default=list)
|
||||
"""Indicates the subnets that are contained in the logical network."""
|
||||
|
||||
network_virtualization_enabled = model.Field(
|
||||
@ -452,9 +519,9 @@ class LogicalNetworks(_BaseHNVModel):
|
||||
for one or more virtual networks. Valid values are `True` or `False`.
|
||||
The default is `False`."""
|
||||
|
||||
virtual_networks = model.Field(name="virtual_networks",
|
||||
key="virtualNetworks",
|
||||
is_read_only=True)
|
||||
virtual_networks = model.Field(
|
||||
name="virtual_networks", key="virtualNetworks",
|
||||
is_read_only=True, default=list)
|
||||
"""Indicates an array of virtualNetwork resources that are using
|
||||
the network."""
|
||||
|
||||
@ -527,7 +594,7 @@ class IPConfiguration(_BaseHNVModel):
|
||||
"""Indicates the allocation method (Static or Dynamic)."""
|
||||
|
||||
public_ip_address = model.Field(
|
||||
name="public_ip_address", key="privateIpAddress",
|
||||
name="public_ip_address", key="publicIpAddress",
|
||||
is_required=False)
|
||||
"""Indicates the public IP address of the IP Configuration."""
|
||||
|
||||
@ -878,7 +945,7 @@ class VirtualNetworks(_BaseHNVModel):
|
||||
network."""
|
||||
|
||||
subnetworks = model.Field(name="subnetworks", key="subnets",
|
||||
is_required=False)
|
||||
is_required=False, default=list)
|
||||
"""Indicates the subnets that are on the virtual network."""
|
||||
|
||||
logical_network = model.Field(name="logical_network",
|
||||
@ -1126,7 +1193,8 @@ class VirtualSwitchManager(_BaseHNVModel):
|
||||
return super(VirtualSwitchManager, cls).from_raw_data(raw_data)
|
||||
|
||||
@classmethod
|
||||
def remove(cls, resource_id, parent_id=None, wait=True, timeout=None):
|
||||
def remove(cls, resource_id, parent_id=None, grandparent_id=None,
|
||||
wait=True, timeout=None):
|
||||
"""Delete the required resource."""
|
||||
raise exception.NotSupported(feature="DELETE",
|
||||
context="VirtualSwitchManager")
|
||||
@ -1192,7 +1260,7 @@ class RouteTables(_BaseHNVModel):
|
||||
_endpoint = "/networking/v1/routeTables/{resource_id}"
|
||||
|
||||
routes = model.Field(name="routes", key="routes", is_required=False,
|
||||
default=[])
|
||||
default=list)
|
||||
"""Indicates the routes in a route table, see routes resource for full
|
||||
details on this element."""
|
||||
|
||||
@ -1723,7 +1791,7 @@ class PublicIPAddresses(_BaseHNVModel):
|
||||
can be used to communicate with the virtual network from outside it.
|
||||
"""
|
||||
|
||||
_endpoint = "/networking/v1/publicIpAddresses/{resource_i}"
|
||||
_endpoint = "/networking/v1/publicIpAddresses/{resource_id}"
|
||||
|
||||
ip_address = model.Field(name="ip_address", key="ipAddress",
|
||||
is_required=False, is_read_only=False)
|
||||
|
@ -36,6 +36,19 @@ class TestBaseHNVModel(unittest.TestCase):
|
||||
def setUp(self):
|
||||
client._BaseHNVModel._endpoint = "{parent_id}/{resource_id}"
|
||||
|
||||
@mock.patch("hnv.client._BaseHNVModel.process_raw_data")
|
||||
@mock.patch("hnv.client._BaseHNVModel._set_fields")
|
||||
def test_reset_model(self, mock_set_fields, mock_process):
|
||||
resource = client._BaseHNVModel()
|
||||
|
||||
mock_process.return_value = mock.sentinel.fields
|
||||
mock_set_fields.reset_mock()
|
||||
|
||||
resource._reset_model(mock.sentinel.response)
|
||||
|
||||
mock_process.assert_called_once_with(mock.sentinel.response)
|
||||
mock_set_fields.assert_called_once_with(mock.sentinel.fields)
|
||||
|
||||
@mock.patch("hnv.client._BaseHNVModel.from_raw_data")
|
||||
@mock.patch("hnv.client._BaseHNVModel._get_client")
|
||||
def test_get(self, mock_get_client, mock_from_raw_data):
|
||||
@ -101,16 +114,14 @@ class TestBaseHNVModel(unittest.TestCase):
|
||||
return {"properties": {"provisioningState": provisioning_state}}
|
||||
|
||||
@mock.patch("time.sleep")
|
||||
@mock.patch("hnv.client._BaseHNVModel.process_raw_data")
|
||||
@mock.patch("hnv.client._BaseHNVModel.dump")
|
||||
@mock.patch("hnv.client._BaseHNVModel._get_client")
|
||||
def _test_commit(self, mock_get_client, mock_dump, mock_process,
|
||||
def _test_commit(self, mock_get_client, mock_dump,
|
||||
mock_sleep,
|
||||
loop_count, timeout, failed, invalid_response):
|
||||
http_client = mock_get_client.return_value = mock.Mock()
|
||||
update_resource = http_client.update_resource = mock.Mock()
|
||||
mock_dump.return_value = mock.sentinel.request_body
|
||||
mock_process.return_value = {}
|
||||
|
||||
get_resource = http_client.get_resource = mock.Mock()
|
||||
side_effect = [self._get_provisioning(constant.UPDATING)
|
||||
@ -167,6 +178,20 @@ class TestBaseHNVModel(unittest.TestCase):
|
||||
self._test_commit(loop_count=1, timeout=False,
|
||||
failed=False, invalid_response=True)
|
||||
|
||||
@mock.patch("hnv.client._BaseHNVModel._reset_model")
|
||||
@mock.patch("hnv.client._BaseHNVModel._get_client")
|
||||
def test_refresh(self, mock_get_client, mock_reset_model):
|
||||
http_client = mock_get_client.return_value = mock.Mock()
|
||||
get_resource = http_client.get_resource = mock.Mock()
|
||||
get_resource.return_value = mock.sentinel.response
|
||||
|
||||
model = client._BaseHNVModel(resource_id="hnv-client",
|
||||
parent_id="test")
|
||||
model.refresh()
|
||||
|
||||
get_resource.assert_called_once_with("test/hnv-client")
|
||||
mock_reset_model.assert_called_once_with(mock.sentinel.response)
|
||||
|
||||
|
||||
class TestClient(unittest.TestCase):
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user