Add list and show pool commands to Pool Manager
This patch add the support to get the current status of the pools. It can list the existing pools and their number of available subports, as well as to show the ids of the ports associated to a given pool. Implements: blueprint kuryr-manager-cli-tool Change-Id: I9332ffa259c4651b69c788985e5bbd1f98cb38cc
This commit is contained in:
parent
54ef9dcd10
commit
3de89337f7
@ -245,6 +245,62 @@ Or from all the pools at once::
|
||||
$ # returns nothing
|
||||
|
||||
|
||||
List pools for nested environment
|
||||
---------------------------------
|
||||
|
||||
There is a `list` command available to show information about the existing
|
||||
pools, i.e., it prints out the pool keys (trunk_ip, project_id,
|
||||
[security_groups]) and the amount of available ports in each one of them::
|
||||
|
||||
$ python contrib/pools-management/subports.py list -h
|
||||
usage: subports.py list [-h] [-t TIMEOUT]
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-t TIMEOUT, --timeout TIMEOUT
|
||||
set timeout for operation. Default is 180 sec
|
||||
|
||||
|
||||
As an example::
|
||||
|
||||
$ python contrib/pools-management/subports.py list
|
||||
Content-length: 150
|
||||
|
||||
Pools:
|
||||
["10.0.0.6", "9d2b45c4efaa478481c30340b49fd4d2", ["00efc78c-f11c-414a-bfcd-a82e16dc07d1", "fd6b13dc-7230-4cbe-9237-36b4614bc6b5"]] has 4 ports
|
||||
|
||||
|
||||
Show pool for nested environment
|
||||
--------------------------------
|
||||
|
||||
There is a `show` command available to print out information about a given
|
||||
pool. It prints the ids of the ports associated to that pool:::
|
||||
|
||||
$ python contrib/pools-management/subports.py show -h
|
||||
usage: subports.py show [-h] --trunk TRUNK_IP -p PROJECT_ID --sg SG [SG ...]
|
||||
[-t TIMEOUT]
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--trunk TRUNK_IP Trunk IP of the desired pool
|
||||
-p PROJECT_ID, --project-id PROJECT_ID
|
||||
project id of the pool
|
||||
--sg SG [SG ...] Security group ids of the pool
|
||||
-t TIMEOUT, --timeout TIMEOUT
|
||||
set timeout for operation. Default is 180 sec
|
||||
|
||||
As an example::
|
||||
|
||||
$ python contrib/pools-management/subports.py show --trunk 10.0.0.6 -p 9d2b45c4efaa478481c30340b49fd4d2 --sg 00efc78c-f11c-414a-bfcd-a82e16dc07d1 fd6b13dc-7230-4cbe-9237-36b4614bc6b5
|
||||
Content-length: 299
|
||||
|
||||
Pool (u'10.0.0.6', u'9d2b45c4efaa478481c30340b49fd4d2', (u'00efc78c-f11c-414a-bfcd-a82e16dc07d1', u'fd6b13dc-7230-4cbe-9237-36b4614bc6b5')) ports are:
|
||||
4913fbde-5939-4aef-80c0-7fcca0348871
|
||||
864c8237-6ab4-4713-bec8-3d8bb6aa2144
|
||||
8138134b-44df-489c-a693-3defeb2adb58
|
||||
f5e107c6-f998-4416-8f17-a055269f2829
|
||||
|
||||
|
||||
Without the script
|
||||
------------------
|
||||
|
||||
@ -256,3 +312,9 @@ REST API with curl::
|
||||
|
||||
# To free the pool
|
||||
$ curl --unix-socket /run/kuryr/kuryr_manage.sock http://localhost/freePool -H "Content-Type: application/json" -X POST -d '{"trunks": ["10.0.4.6"]}'
|
||||
|
||||
# To list the existing pools
|
||||
$ curl --unix-socket /run/kuryr/kuryr_manage.sock http://localhost/listPools -H "Content-Type: application/json" -X GET -d '{}'
|
||||
|
||||
# To show a specific pool
|
||||
$ curl --unix-socket /run/kuryr/kuryr_manage.sock http://localhost/showPool -H "Content-Type: application/json" -X GET -d '{"pool_key": ["10.0.0.6", "9d2b45c4efaa478481c30340b49fd4d2", ["00efc78c-f11c-414a-bfcd-a82e16dc07d1", "fd6b13dc-7230-4cbe-9237-36b4614bc6b5"]]}'
|
||||
|
@ -62,6 +62,32 @@ def delete_subports(trunk_ips, timeout=180):
|
||||
print(resp.read())
|
||||
|
||||
|
||||
def list_pools(timeout=180):
|
||||
method = 'GET'
|
||||
body = jsonutils.dumps({})
|
||||
headers = {'Context-Type': 'application/json', 'Connection': 'close'}
|
||||
headers['Context-Length'] = len(body)
|
||||
path = 'http://localhost{0}'.format(constants.VIF_POOL_LIST)
|
||||
socket_path = constants.MANAGER_SOCKET_FILE
|
||||
conn = UnixDomainHttpConnection(socket_path, timeout)
|
||||
conn.request(method, path, body=body, headers=headers)
|
||||
resp = conn.getresponse()
|
||||
print(resp.read())
|
||||
|
||||
|
||||
def show_pool(trunk_ip, project_id, sg, timeout=180):
|
||||
method = 'GET'
|
||||
body = jsonutils.dumps({"pool_key": [trunk_ip, project_id, sg]})
|
||||
headers = {'Context-Type': 'application/json', 'Connection': 'close'}
|
||||
headers['Context-Length'] = len(body)
|
||||
path = 'http://localhost{0}'.format(constants.VIF_POOL_SHOW)
|
||||
socket_path = constants.MANAGER_SOCKET_FILE
|
||||
conn = UnixDomainHttpConnection(socket_path, timeout)
|
||||
conn.request(method, path, body=body, headers=headers)
|
||||
resp = conn.getresponse()
|
||||
print(resp.read())
|
||||
|
||||
|
||||
def _get_parser():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Tool to create/free subports from the subports pool')
|
||||
@ -104,6 +130,42 @@ def _get_parser():
|
||||
default=180,
|
||||
type=int)
|
||||
|
||||
list_pools_parser = subparser.add_parser(
|
||||
'list',
|
||||
help='List available pools and the number of ports they have')
|
||||
list_pools_parser.add_argument(
|
||||
'-t', '--timeout',
|
||||
help='set timeout for operation. Default is 180 sec',
|
||||
dest='timeout',
|
||||
default=180,
|
||||
type=int)
|
||||
|
||||
show_pool_parser = subparser.add_parser(
|
||||
'show',
|
||||
help='Show the ports associated to a given pool')
|
||||
show_pool_parser.add_argument(
|
||||
'--trunk',
|
||||
help='Trunk IP of the desired pool',
|
||||
dest='trunk_ip',
|
||||
required=True)
|
||||
show_pool_parser.add_argument(
|
||||
'-p', '--project-id',
|
||||
help='project id of the pool',
|
||||
dest='project_id',
|
||||
required=True)
|
||||
show_pool_parser.add_argument(
|
||||
'--sg',
|
||||
help='Security group ids of the pool',
|
||||
dest='sg',
|
||||
nargs='+',
|
||||
required=True)
|
||||
show_pool_parser.add_argument(
|
||||
'-t', '--timeout',
|
||||
help='set timeout for operation. Default is 180 sec',
|
||||
dest='timeout',
|
||||
default=180,
|
||||
type=int)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
@ -115,6 +177,10 @@ def main():
|
||||
create_subports(args.num, args.subports, args.timeout)
|
||||
elif args.command == 'free':
|
||||
delete_subports(args.subports, args.timeout)
|
||||
elif args.command == 'list':
|
||||
list_pools(args.timeout)
|
||||
elif args.command == 'show':
|
||||
show_pool(args.trunk_ip, args.project_id, args.sg, args.timeout)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -40,3 +40,5 @@ OCTAVIA_L3_MEMBER_MODE = "L3"
|
||||
|
||||
VIF_POOL_POPULATE = '/populatePool'
|
||||
VIF_POOL_FREE = '/freePool'
|
||||
VIF_POOL_LIST = '/listPools'
|
||||
VIF_POOL_SHOW = '/showPool'
|
||||
|
@ -205,6 +205,12 @@ class BaseVIFPool(base.VIFPoolDriver):
|
||||
annotations['versioned_object.data']['id'])
|
||||
return in_use_ports
|
||||
|
||||
def list_pools(self):
|
||||
return self._available_ports_pools
|
||||
|
||||
def show_pool(self, pool_key):
|
||||
return self._available_ports_pools.get(pool_key)
|
||||
|
||||
|
||||
class NeutronVIFPool(BaseVIFPool):
|
||||
"""Manages VIFs for Bare Metal Kubernetes Pods."""
|
||||
|
@ -102,6 +102,50 @@ class RequestHandler(BaseHTTPRequestHandler):
|
||||
self.end_headers()
|
||||
self.wfile.write(response.encode())
|
||||
|
||||
def do_GET(self):
|
||||
content_length = int(self.headers.get('Content-Length', 0))
|
||||
|
||||
body = self.rfile.read(content_length)
|
||||
params = dict(jsonutils.loads(body))
|
||||
|
||||
if self.path.endswith(constants.VIF_POOL_LIST):
|
||||
try:
|
||||
pools_info = self._list_pools()
|
||||
except Exception:
|
||||
response = 'Error listing the pools.'
|
||||
else:
|
||||
response = 'Pools:\n{0}'.format(pools_info)
|
||||
|
||||
self.send_header('Content-Length', len(response))
|
||||
self.end_headers()
|
||||
self.wfile.write(response.encode())
|
||||
|
||||
elif self.path.endswith(constants.VIF_POOL_SHOW):
|
||||
raw_key = params.get('pool_key', None)
|
||||
if len(raw_key) != 3:
|
||||
response = ('Invalid pool key. Proper format is:\n'
|
||||
'[trunk_ip, project_id, [security_groups]]\n')
|
||||
else:
|
||||
pool_key = (raw_key[0], raw_key[1], tuple(sorted(raw_key[2])))
|
||||
|
||||
try:
|
||||
pool_info = self._show_pool(pool_key)
|
||||
except Exception:
|
||||
response = 'Error showing pool: {0}.'.format(pool_key)
|
||||
else:
|
||||
response = 'Pool {0} ports are:\n{1}'.format(pool_key,
|
||||
pool_info)
|
||||
|
||||
self.send_header('Content-Length', len(response))
|
||||
self.end_headers()
|
||||
self.wfile.write(response.encode())
|
||||
|
||||
else:
|
||||
response = 'Method not allowed.'
|
||||
self.send_header('Content-Length', len(response))
|
||||
self.end_headers()
|
||||
self.wfile.write(response.encode())
|
||||
|
||||
def _create_subports(self, num_ports, trunk_ips):
|
||||
try:
|
||||
drv_project = drivers.PodProjectDriver.get_instance()
|
||||
@ -141,6 +185,44 @@ class RequestHandler(BaseHTTPRequestHandler):
|
||||
LOG.error("Invalid driver type")
|
||||
raise ex
|
||||
|
||||
def _list_pools(self):
|
||||
try:
|
||||
drv_vif = drivers.PodVIFDriver.get_instance()
|
||||
drv_vif_pool = drivers.VIFPoolDriver.get_instance()
|
||||
drv_vif_pool.set_vif_driver(drv_vif)
|
||||
|
||||
available_pools = drv_vif_pool.list_pools()
|
||||
except TypeError as ex:
|
||||
LOG.error("Invalid driver type")
|
||||
raise ex
|
||||
|
||||
pools_info = ""
|
||||
for pool_key, pool_items in available_pools.items():
|
||||
pools_info += (jsonutils.dumps(pool_key) + " has "
|
||||
+ str(len(pool_items)) + " ports\n")
|
||||
if pools_info:
|
||||
return pools_info
|
||||
return "There are no pools"
|
||||
|
||||
def _show_pool(self, pool_key):
|
||||
try:
|
||||
drv_vif = drivers.PodVIFDriver.get_instance()
|
||||
drv_vif_pool = drivers.VIFPoolDriver.get_instance()
|
||||
drv_vif_pool.set_vif_driver(drv_vif)
|
||||
|
||||
pool = drv_vif_pool.show_pool(pool_key)
|
||||
except TypeError as ex:
|
||||
LOG.error("Invalid driver type")
|
||||
raise ex
|
||||
|
||||
if pool:
|
||||
pool_info = ""
|
||||
for pool_id in pool:
|
||||
pool_info += str(pool_id) + "\n"
|
||||
return pool_info
|
||||
else:
|
||||
return "Empty pool"
|
||||
|
||||
|
||||
class PoolManager(object):
|
||||
"""Manages the ports pool enabling population and free actions.
|
||||
|
@ -74,6 +74,47 @@ class TestRequestHandler(test_base.TestCase):
|
||||
m_end_headers.assert_called_once()
|
||||
m_write.assert_called_once_with(expected_resp)
|
||||
|
||||
def _do_GET_helper(self, method, method_resp, path, headers, body,
|
||||
expected_resp, trigger_exception, pool_key=None):
|
||||
self._req_handler.headers = headers
|
||||
self._req_handler.path = path
|
||||
|
||||
with mock.patch.object(self._req_handler.rfile, 'read') as m_read,\
|
||||
mock.patch.object(self._req_handler,
|
||||
'_list_pools') as m_list,\
|
||||
mock.patch.object(self._req_handler,
|
||||
'_show_pool') as m_show:
|
||||
m_read.return_value = body
|
||||
if trigger_exception:
|
||||
m_list.side_effect = Exception
|
||||
m_show.side_effect = Exception
|
||||
else:
|
||||
m_list.return_value = method_resp
|
||||
m_show.return_value = method_resp
|
||||
|
||||
with mock.patch.object(self._req_handler,
|
||||
'send_header') as m_send_header,\
|
||||
mock.patch.object(self._req_handler,
|
||||
'end_headers') as m_end_headers,\
|
||||
mock.patch.object(self._req_handler.wfile,
|
||||
'write') as m_write:
|
||||
self._req_handler.do_GET()
|
||||
|
||||
if method == 'list':
|
||||
m_list.assert_called_once()
|
||||
if method == 'show':
|
||||
if pool_key and len(pool_key) == 3:
|
||||
m_show.assert_called_once_with(
|
||||
(pool_key[0], pool_key[1],
|
||||
tuple(sorted(pool_key[2]))))
|
||||
else:
|
||||
m_show.assert_not_called()
|
||||
|
||||
m_send_header.assert_called_once_with('Content-Length',
|
||||
len(expected_resp))
|
||||
m_end_headers.assert_called_once()
|
||||
m_write.assert_called_once_with(expected_resp)
|
||||
|
||||
def test_do_POST_populate(self):
|
||||
method = 'create'
|
||||
path = "http://localhost/populatePool"
|
||||
@ -182,3 +223,137 @@ class TestRequestHandler(test_base.TestCase):
|
||||
|
||||
self._do_POST_helper(method, path, headers, body, expected_resp,
|
||||
trigger_exception, trunk_ips)
|
||||
|
||||
def test_do_GET_list(self):
|
||||
method = 'list'
|
||||
method_resp = ('["10.0.0.6", "9d2b45c4efaa478481c30340b49fd4d2", '
|
||||
'["00efc78c-f11c-414a-bfcd-a82e16dc07d1", '
|
||||
'"fd6b13dc-7230-4cbe-9237-36b4614bc6b5"]] '
|
||||
'has 5 ports')
|
||||
|
||||
path = "http://localhost/listPools"
|
||||
body = jsonutils.dumps({})
|
||||
headers = {'Content-Type': 'application/json', 'Connection': 'close'}
|
||||
headers['Content-Length'] = len(body)
|
||||
trigger_exception = False
|
||||
|
||||
expected_resp = ('Pools:\n{0}'.format(method_resp)).encode()
|
||||
|
||||
self._do_GET_helper(method, method_resp, path, headers, body,
|
||||
expected_resp, trigger_exception)
|
||||
|
||||
def test_do_GET_list_exception(self):
|
||||
method = 'list'
|
||||
method_resp = ('["10.0.0.6", "9d2b45c4efaa478481c30340b49fd4d2", '
|
||||
'["00efc78c-f11c-414a-bfcd-a82e16dc07d1", '
|
||||
'"fd6b13dc-7230-4cbe-9237-36b4614bc6b5"]] '
|
||||
'has 5 ports')
|
||||
|
||||
path = "http://localhost/listPools"
|
||||
body = jsonutils.dumps({})
|
||||
headers = {'Content-Type': 'application/json', 'Connection': 'close'}
|
||||
headers['Content-Length'] = len(body)
|
||||
trigger_exception = True
|
||||
|
||||
expected_resp = ('Error listing the pools.').encode()
|
||||
|
||||
self._do_GET_helper(method, method_resp, path, headers, body,
|
||||
expected_resp, trigger_exception)
|
||||
|
||||
def test_do_GET_list_empty(self):
|
||||
method = 'list'
|
||||
method_resp = 'There are no pools'
|
||||
|
||||
path = "http://localhost/listPools"
|
||||
body = jsonutils.dumps({})
|
||||
headers = {'Content-Type': 'application/json', 'Connection': 'close'}
|
||||
headers['Content-Length'] = len(body)
|
||||
trigger_exception = False
|
||||
|
||||
expected_resp = ('Pools:\n{0}'.format(method_resp)).encode()
|
||||
|
||||
self._do_GET_helper(method, method_resp, path, headers, body,
|
||||
expected_resp, trigger_exception)
|
||||
|
||||
def test_do_GET_show(self):
|
||||
method = 'show'
|
||||
method_resp = "251f748d-2a0d-4143-bce8-2e616f7a6a4a"
|
||||
path = "http://localhost/showPool"
|
||||
pool_key = [u"10.0.0.6", u"9d2b45c4efaa478481c30340b49fd4d2",
|
||||
[u"00efc78c-f11c-414a-bfcd-a82e16dc07d1",
|
||||
u"fd6b13dc-7230-4cbe-9237-36b4614bc6b5"]]
|
||||
body = jsonutils.dumps({"pool_key": pool_key})
|
||||
headers = {'Content-Type': 'application/json', 'Connection': 'close'}
|
||||
headers['Content-Length'] = len(body)
|
||||
trigger_exception = False
|
||||
formated_key = (pool_key[0], pool_key[1], tuple(sorted(pool_key[2])))
|
||||
expected_resp = ('Pool {0} ports are:\n{1}'
|
||||
.format(formated_key, method_resp)).encode()
|
||||
|
||||
self._do_GET_helper(method, method_resp, path, headers, body,
|
||||
expected_resp, trigger_exception, pool_key)
|
||||
|
||||
def test_do_GET_show_exception(self):
|
||||
method = 'show'
|
||||
method_resp = "251f748d-2a0d-4143-bce8-2e616f7a6a4a"
|
||||
path = "http://localhost/showPool"
|
||||
pool_key = [u"10.0.0.6", u"9d2b45c4efaa478481c30340b49fd4d2",
|
||||
[u"00efc78c-f11c-414a-bfcd-a82e16dc07d1",
|
||||
u"fd6b13dc-7230-4cbe-9237-36b4614bc6b5"]]
|
||||
body = jsonutils.dumps({"pool_key": pool_key})
|
||||
headers = {'Content-Type': 'application/json', 'Connection': 'close'}
|
||||
headers['Content-Length'] = len(body)
|
||||
trigger_exception = True
|
||||
formated_key = (pool_key[0], pool_key[1], tuple(sorted(pool_key[2])))
|
||||
expected_resp = ('Error showing pool: {0}.'
|
||||
.format(formated_key)).encode()
|
||||
|
||||
self._do_GET_helper(method, method_resp, path, headers, body,
|
||||
expected_resp, trigger_exception, pool_key)
|
||||
|
||||
def test_do_GET_show_empty(self):
|
||||
method = 'show'
|
||||
method_resp = "Empty pool"
|
||||
path = "http://localhost/showPool"
|
||||
pool_key = [u"10.0.0.6", u"9d2b45c4efaa478481c30340b49fd4d2",
|
||||
[u"00efc78c-f11c-414a-bfcd-a82e16dc07d1",
|
||||
u"fd6b13dc-7230-4cbe-9237-36b4614bc6b5"]]
|
||||
body = jsonutils.dumps({"pool_key": pool_key})
|
||||
headers = {'Content-Type': 'application/json', 'Connection': 'close'}
|
||||
headers['Content-Length'] = len(body)
|
||||
trigger_exception = False
|
||||
formated_key = (pool_key[0], pool_key[1], tuple(sorted(pool_key[2])))
|
||||
expected_resp = ('Pool {0} ports are:\n{1}'
|
||||
.format(formated_key, method_resp)).encode()
|
||||
|
||||
self._do_GET_helper(method, method_resp, path, headers, body,
|
||||
expected_resp, trigger_exception, pool_key)
|
||||
|
||||
def test_do_GET_show_wrong_key(self):
|
||||
method = 'show'
|
||||
method_resp = ""
|
||||
path = "http://localhost/showPool"
|
||||
pool_key = [u"10.0.0.6", u"9d2b45c4efaa478481c30340b49fd4d2"]
|
||||
body = jsonutils.dumps({"pool_key": pool_key})
|
||||
headers = {'Content-Type': 'application/json', 'Connection': 'close'}
|
||||
headers['Content-Length'] = len(body)
|
||||
trigger_exception = False
|
||||
expected_resp = ('Invalid pool key. Proper format is:\n[trunk_ip, '
|
||||
'project_id, [security_groups]]\n').encode()
|
||||
|
||||
self._do_GET_helper(method, method_resp, path, headers, body,
|
||||
expected_resp, trigger_exception, pool_key)
|
||||
|
||||
def test_do_GET_wrong_action(self):
|
||||
method = 'fake'
|
||||
method_resp = ""
|
||||
path = "http://localhost/fakeMethod"
|
||||
body = jsonutils.dumps({})
|
||||
headers = {'Content-Type': 'application/json', 'Connection': 'close'}
|
||||
headers['Content-Length'] = len(body)
|
||||
trigger_exception = False
|
||||
|
||||
expected_resp = ('Method not allowed.').encode()
|
||||
|
||||
self._do_GET_helper(method, method_resp, path, headers, body,
|
||||
expected_resp, trigger_exception)
|
||||
|
Loading…
Reference in New Issue
Block a user