Merge "IpPools support"
This commit is contained in:
commit
fc115e4605
@ -160,3 +160,23 @@ FAKE_QOS_PROFILE = {
|
||||
"_create_user": "admin",
|
||||
"_revision": 0
|
||||
}
|
||||
|
||||
FAKE_IP_POOL_UUID = uuidutils.generate_uuid()
|
||||
FAKE_IP_POOL = {
|
||||
"_revision": 0,
|
||||
"id": FAKE_IP_POOL_UUID,
|
||||
"display_name": "IPPool-IPV6-1",
|
||||
"description": "IPPool-IPV6-1 Description",
|
||||
"resource_type": "IpPool",
|
||||
"subnets": [{
|
||||
"dns_nameservers": [
|
||||
"2002:a70:cbfa:1:1:1:1:1"
|
||||
],
|
||||
"allocation_ranges": [{
|
||||
"start": "2002:a70:cbfa:0:0:0:0:1",
|
||||
"end": "2002:a70:cbfa:0:0:0:0:5"
|
||||
}],
|
||||
"gateway_ip": "2002:a80:cbfa:0:0:0:0:255",
|
||||
"cidr": "2002:a70:cbfa:0:0:0:0:0/24"
|
||||
}],
|
||||
}
|
||||
|
@ -540,3 +540,189 @@ class LogicalRouterPortTestCase(nsxlib_testcase.NsxClientTestCase):
|
||||
'get', lrport,
|
||||
'https://1.2.3.4/api/v1/logical-router-ports/?'
|
||||
'logical_switch_id=%s' % switch_id)
|
||||
|
||||
|
||||
class IpPoolTestCase(nsxlib_testcase.NsxClientTestCase):
|
||||
|
||||
def _mocked_pool(self, session_response=None):
|
||||
return self.mocked_resource(
|
||||
resources.IpPool, session_response=session_response)
|
||||
|
||||
def test_create_ip_pool_all_args(self):
|
||||
"""Test creating an IP pool
|
||||
|
||||
returns the correct response and 201 status
|
||||
"""
|
||||
pool = self._mocked_pool()
|
||||
|
||||
display_name = 'dummy'
|
||||
gateway_ip = '1.1.1.1'
|
||||
ranges = [{'start': '2.2.2.0', 'end': '2.2.2.255'},
|
||||
{'start': '3.2.2.0', 'end': '3.2.2.255'}]
|
||||
cidr = '2.2.2.0/24'
|
||||
description = 'desc'
|
||||
dns_nameserver = '7.7.7.7'
|
||||
pool.create(cidr, ranges=ranges,
|
||||
display_name=display_name,
|
||||
gateway_ip=gateway_ip,
|
||||
description=description,
|
||||
dns_nameservers=[dns_nameserver])
|
||||
|
||||
data = {
|
||||
'display_name': display_name,
|
||||
'description': description,
|
||||
'subnets': [{
|
||||
'gateway_ip': gateway_ip,
|
||||
'allocation_ranges': ranges,
|
||||
'cidr': cidr,
|
||||
'dns_nameservers': [dns_nameserver]
|
||||
}]
|
||||
}
|
||||
|
||||
test_client.assert_json_call(
|
||||
'post', pool,
|
||||
'https://1.2.3.4/api/v1/pools/ip-pools',
|
||||
data=jsonutils.dumps(data, sort_keys=True))
|
||||
|
||||
def test_create_ip_pool_minimal_args(self):
|
||||
pool = self._mocked_pool()
|
||||
|
||||
ranges = [{'start': '2.2.2.0', 'end': '2.2.2.255'},
|
||||
{'start': '3.2.2.0', 'end': '3.2.2.255'}]
|
||||
cidr = '2.2.2.0/24'
|
||||
pool.create(cidr, ranges=ranges)
|
||||
|
||||
data = {
|
||||
'subnets': [{
|
||||
'allocation_ranges': ranges,
|
||||
'cidr': cidr,
|
||||
}]
|
||||
}
|
||||
|
||||
test_client.assert_json_call(
|
||||
'post', pool,
|
||||
'https://1.2.3.4/api/v1/pools/ip-pools',
|
||||
data=jsonutils.dumps(data, sort_keys=True))
|
||||
|
||||
def test_create_ip_pool_no_ranges_with_gateway(self):
|
||||
pool = self._mocked_pool()
|
||||
cidr = '2.2.2.0/30'
|
||||
gateway_ip = '2.2.2.1'
|
||||
pool.create(cidr, ranges=None, gateway_ip=gateway_ip)
|
||||
exp_ranges = [{'start': '2.2.2.0', 'end': '2.2.2.0'},
|
||||
{'start': '2.2.2.2', 'end': '2.2.2.3'}]
|
||||
|
||||
data = {
|
||||
'subnets': [{
|
||||
'gateway_ip': gateway_ip,
|
||||
'allocation_ranges': exp_ranges,
|
||||
'cidr': cidr,
|
||||
}]
|
||||
}
|
||||
|
||||
test_client.assert_json_call(
|
||||
'post', pool,
|
||||
'https://1.2.3.4/api/v1/pools/ip-pools',
|
||||
data=jsonutils.dumps(data, sort_keys=True))
|
||||
|
||||
def test_create_ip_pool_no_ranges_no_gateway(self):
|
||||
pool = self._mocked_pool()
|
||||
cidr = '2.2.2.0/30'
|
||||
pool.create(cidr, ranges=None)
|
||||
exp_ranges = [{'start': '2.2.2.0', 'end': '2.2.2.3'}]
|
||||
|
||||
data = {
|
||||
'subnets': [{
|
||||
'allocation_ranges': exp_ranges,
|
||||
'cidr': cidr,
|
||||
}]
|
||||
}
|
||||
|
||||
test_client.assert_json_call(
|
||||
'post', pool,
|
||||
'https://1.2.3.4/api/v1/pools/ip-pools',
|
||||
data=jsonutils.dumps(data, sort_keys=True))
|
||||
|
||||
def test_create_ip_pool_no_cidr(self):
|
||||
pool = self._mocked_pool()
|
||||
gateway_ip = '1.1.1.1'
|
||||
ranges = [{'start': '2.2.2.0', 'end': '2.2.2.255'},
|
||||
{'start': '3.2.2.0', 'end': '3.2.2.255'}]
|
||||
cidr = None
|
||||
|
||||
try:
|
||||
pool.create(cidr, ranges=ranges,
|
||||
gateway_ip=gateway_ip)
|
||||
except exceptions.InvalidInput:
|
||||
# This call should fail
|
||||
pass
|
||||
else:
|
||||
self.fail("shouldn't happen")
|
||||
|
||||
def test_get_ip_pool(self):
|
||||
"""Test getting a router port by router id"""
|
||||
fake_ip_pool = test_constants.FAKE_IP_POOL.copy()
|
||||
resp_resources = fake_ip_pool
|
||||
|
||||
pool = self._mocked_pool(
|
||||
session_response=mocks.MockRequestsResponse(
|
||||
200, jsonutils.dumps(resp_resources)))
|
||||
|
||||
uuid = fake_ip_pool['id']
|
||||
result = pool.get(uuid)
|
||||
self.assertEqual(fake_ip_pool, result)
|
||||
test_client.assert_json_call(
|
||||
'get', pool,
|
||||
'https://1.2.3.4/api/v1/pools/ip-pools/%s' % uuid)
|
||||
|
||||
def test_delete_ip_pool(self):
|
||||
"""Test deleting router port"""
|
||||
pool = self._mocked_pool()
|
||||
|
||||
uuid = test_constants.FAKE_IP_POOL['id']
|
||||
pool.delete(uuid)
|
||||
test_client.assert_json_call(
|
||||
'delete', pool,
|
||||
'https://1.2.3.4/api/v1/pools/ip-pools/%s' % uuid)
|
||||
|
||||
def test_allocate_ip_from_pool(self):
|
||||
pool = self._mocked_pool()
|
||||
|
||||
uuid = test_constants.FAKE_IP_POOL['id']
|
||||
addr = '1.1.1.1'
|
||||
pool.allocate(uuid, ip_addr=addr)
|
||||
|
||||
data = {'allocation_id': addr}
|
||||
test_client.assert_json_call(
|
||||
'post', pool,
|
||||
'https://1.2.3.4/api/v1/pools/ip-pools/%s?action=ALLOCATE' % uuid,
|
||||
data=jsonutils.dumps(data, sort_keys=True))
|
||||
|
||||
def test_release_ip_to_pool(self):
|
||||
pool = self._mocked_pool()
|
||||
|
||||
uuid = test_constants.FAKE_IP_POOL['id']
|
||||
addr = '1.1.1.1'
|
||||
pool.release(uuid, addr)
|
||||
|
||||
data = {'allocation_id': addr}
|
||||
test_client.assert_json_call(
|
||||
'post', pool,
|
||||
'https://1.2.3.4/api/v1/pools/ip-pools/%s?action=RELEASE' % uuid,
|
||||
data=jsonutils.dumps(data, sort_keys=True))
|
||||
|
||||
def test_get_ip_pool_allocations(self):
|
||||
"""Test getting a router port by router id"""
|
||||
fake_ip_pool = test_constants.FAKE_IP_POOL.copy()
|
||||
resp_resources = fake_ip_pool
|
||||
|
||||
pool = self._mocked_pool(
|
||||
session_response=mocks.MockRequestsResponse(
|
||||
200, jsonutils.dumps(resp_resources)))
|
||||
|
||||
uuid = fake_ip_pool['id']
|
||||
result = pool.get_allocations(uuid)
|
||||
self.assertEqual(fake_ip_pool, result)
|
||||
test_client.assert_json_call(
|
||||
'get', pool,
|
||||
'https://1.2.3.4/api/v1/pools/ip-pools/%s/allocations' % uuid)
|
||||
|
@ -94,9 +94,11 @@ class RESTClient(object):
|
||||
def url_post(self, url, body, headers=None):
|
||||
return self._rest_call(url, method='POST', body=body, headers=headers)
|
||||
|
||||
def _raise_error(self, status_code, operation, result_msg):
|
||||
def _raise_error(self, status_code, operation, result_msg,
|
||||
error_code=None):
|
||||
error = ERRORS.get(status_code, DEFAULT_ERROR)
|
||||
raise error(manager='', operation=operation, details=result_msg)
|
||||
raise error(manager='', operation=operation, details=result_msg,
|
||||
error_code=error_code)
|
||||
|
||||
def _validate_result(self, result, expected, operation):
|
||||
if result.status_code not in expected:
|
||||
@ -109,14 +111,17 @@ class RESTClient(object):
|
||||
for code in expected]),
|
||||
'body': result_msg})
|
||||
|
||||
error_code = None
|
||||
if isinstance(result_msg, dict) and 'error_message' in result_msg:
|
||||
error_code = result_msg.get('error_code')
|
||||
related_errors = [error['error_message'] for error in
|
||||
result_msg.get('related_errors', [])]
|
||||
result_msg = result_msg['error_message']
|
||||
if related_errors:
|
||||
result_msg += " relatedErrors: %s" % ' '.join(
|
||||
related_errors)
|
||||
self._raise_error(result.status_code, operation, result_msg)
|
||||
self._raise_error(result.status_code, operation, result_msg,
|
||||
error_code=error_code)
|
||||
|
||||
@classmethod
|
||||
def merge_headers(cls, *headers):
|
||||
@ -215,9 +220,11 @@ class NSX3Client(JSONRESTClient):
|
||||
default_headers=default_headers,
|
||||
client_obj=client_obj)
|
||||
|
||||
def _raise_error(self, status_code, operation, result_msg):
|
||||
def _raise_error(self, status_code, operation, result_msg,
|
||||
error_code=None):
|
||||
"""Override the Rest client errors to add the manager IPs"""
|
||||
error = ERRORS.get(status_code, DEFAULT_ERROR)
|
||||
raise error(manager=self.nsx_api_managers,
|
||||
operation=operation,
|
||||
details=result_msg)
|
||||
details=result_msg,
|
||||
error_code=error_code)
|
||||
|
@ -63,6 +63,7 @@ class ManagerError(NsxLibException):
|
||||
self.msg = self.message % kwargs
|
||||
except KeyError:
|
||||
self.msg = details
|
||||
self.error_code = kwargs.get('error_code')
|
||||
|
||||
|
||||
class ResourceNotFound(ManagerError):
|
||||
@ -70,6 +71,11 @@ class ResourceNotFound(ManagerError):
|
||||
"%(operation)s")
|
||||
|
||||
|
||||
class InvalidInput(ManagerError):
|
||||
message = _("%(operation)s failed: Invalid input %(arg_val)s "
|
||||
"for %(arg_name)s")
|
||||
|
||||
|
||||
class StaleRevision(ManagerError):
|
||||
pass
|
||||
|
||||
|
@ -101,3 +101,9 @@ EGRESS = 'egress'
|
||||
INGRESS = 'ingress'
|
||||
EGRESS_SHAPING = 'EgressRateShaper'
|
||||
INGRESS_SHAPING = 'IngressRateShaper'
|
||||
|
||||
# Error codes returned by the backend
|
||||
ERR_CODE_OBJECT_NOT_FOUND = 202
|
||||
ERR_CODE_IPAM_POOL_EXHAUSTED = 5109
|
||||
ERR_CODE_IPAM_SPECIFIC_IP = 5123
|
||||
ERR_CODE_IPAM_IP_NOT_IN_POOL = 5110
|
||||
|
@ -15,6 +15,7 @@
|
||||
#
|
||||
import abc
|
||||
import collections
|
||||
import netaddr
|
||||
import six
|
||||
|
||||
from vmware_nsxlib._i18n import _
|
||||
@ -578,3 +579,89 @@ class LogicalDhcpServer(AbstractRESTResource):
|
||||
def delete_binding(self, server_uuid, binding_uuid):
|
||||
url = "%s/static-bindings/%s" % (server_uuid, binding_uuid)
|
||||
return self._client.url_delete(url)
|
||||
|
||||
|
||||
class IpPool(AbstractRESTResource):
|
||||
#TODO(asarfaty): Check the DK api - could be different
|
||||
@property
|
||||
def uri_segment(self):
|
||||
return 'pools/ip-pools'
|
||||
|
||||
def _generate_ranges(self, cidr, gateway_ip):
|
||||
"""Create list of ranges from the given cidr.
|
||||
|
||||
Ignore the gateway_ip, if defined
|
||||
"""
|
||||
ip_set = netaddr.IPSet(netaddr.IPNetwork(cidr))
|
||||
if gateway_ip:
|
||||
ip_set.remove(gateway_ip)
|
||||
return [{"start": str(r[0]),
|
||||
"end": str(r[-1])} for r in ip_set.iter_ipranges()]
|
||||
|
||||
def create(self, cidr, ranges=None, display_name=None, description=None,
|
||||
gateway_ip=None, dns_nameservers=None):
|
||||
"""Create an IpPool.
|
||||
|
||||
Arguments:
|
||||
cidr: (required)
|
||||
ranges: (optional) a list of dictionaries, each with 'start'
|
||||
and 'end' keys, and IP values.
|
||||
If None: the cidr will be used to create the ranges,
|
||||
excluding the gateway.
|
||||
display_name: (optional)
|
||||
description: (optional)
|
||||
gateway_ip: (optional)
|
||||
dns_nameservers: (optional) list of addresses
|
||||
"""
|
||||
if not cidr:
|
||||
raise exceptions.InvalidInput(operation="IP Pool create",
|
||||
arg_name="cidr", arg_val=cidr)
|
||||
if not ranges:
|
||||
# generate ranges from (cidr - gateway)
|
||||
ranges = self._generate_ranges(cidr, gateway_ip)
|
||||
|
||||
subnet = {"allocation_ranges": ranges,
|
||||
"cidr": cidr}
|
||||
if gateway_ip:
|
||||
subnet["gateway_ip"] = gateway_ip
|
||||
if dns_nameservers:
|
||||
subnet["dns_nameservers"] = dns_nameservers
|
||||
|
||||
body = {"subnets": [subnet]}
|
||||
if description:
|
||||
body['description'] = description
|
||||
if display_name:
|
||||
body['display_name'] = display_name
|
||||
|
||||
return self._client.create(body=body)
|
||||
|
||||
def delete(self, pool_id):
|
||||
"""Delete an IPPool by its ID."""
|
||||
return self._client.delete(pool_id)
|
||||
|
||||
def update(self, uuid, *args, **kwargs):
|
||||
# Not supported yet
|
||||
pass
|
||||
|
||||
def get(self, pool_id):
|
||||
return self._client.get(pool_id)
|
||||
|
||||
def allocate(self, pool_id, ip_addr=None):
|
||||
"""Allocate an IP from a pool."""
|
||||
# Note: Currently the backend does not support allocation of a
|
||||
# specific IP, so an exception will be raised by the backend.
|
||||
# Depending on the backend version, this may be allowed in the future
|
||||
url = "%s?action=ALLOCATE" % pool_id
|
||||
body = {"allocation_id": ip_addr}
|
||||
return self._client.url_post(url, body=body)
|
||||
|
||||
def release(self, pool_id, ip_addr):
|
||||
"""Release an IP back to a pool."""
|
||||
url = "%s?action=RELEASE" % pool_id
|
||||
body = {"allocation_id": ip_addr}
|
||||
return self._client.url_post(url, body=body)
|
||||
|
||||
def get_allocations(self, pool_id):
|
||||
"""Return information about the allocated IPs in the pool."""
|
||||
url = "%s/allocations" % pool_id
|
||||
return self._client.url_get(url)
|
||||
|
Loading…
Reference in New Issue
Block a user