Add network functions to API client

This commit is contained in:
Joshua Boniface 2019-12-29 16:13:32 -05:00
parent d0b6bb4cc3
commit d2f27cc8fe
2 changed files with 470 additions and 105 deletions

View File

@ -23,12 +23,22 @@
import difflib
import colorama
import click
import requests
import cli_lib.ansiprint as ansiprint
#
# Cluster search functions
#
def get_request_uri(config, endpoint):
"""
Return the fully-formed URI for {endpoint}
"""
uri = '{}://{}{}{}'.format(
config['api_scheme'],
config['api_host'],
config['api_prefix'],
endpoint
)
return uri
def isValidMAC(macaddr):
allowed = re.compile(r"""
(
@ -56,7 +66,399 @@ def isValidIP(ipaddr):
return False
#
# Direct functions
# Primary functions
#
def net_info(config, net):
"""
Get information about network
API endpoint: GET /api/v1/network/{net}
API arguments:
API schema: {json_data_object}
"""
request_uri = get_request_uri(config, '/network/{net}'.format(net=net))
response = requests.get(
request_uri
)
if config['debug']:
print('API endpoint: POST {}'.format(request_uri))
print('Response code: {}'.format(response.status_code))
print('Response headers: {}'.format(response.headers))
if response.status_code == 200:
return True, response.json()
else:
return False, response.json()['message']
def net_list(config, limit):
"""
Get list information about networks (limited by {limit})
API endpoint: GET /api/v1/network
API arguments: limit={limit}
API schema: [{json_data_object},{json_data_object},etc.]
"""
params = dict()
if limit:
params['limit'] = limit
request_uri = get_request_uri(config, '/network')
response = requests.get(
request_uri,
params=params
)
if config['debug']:
print('API endpoint: POST {}'.format(request_uri))
print('Response code: {}'.format(response.status_code))
print('Response headers: {}'.format(response.headers))
if response.status_code == 200:
return True, response.json()
else:
return False, response.json()['message']
def net_add(config, vni, description, nettype, domain, name_servers, ip4_network, ip4_gateway, ip6_network, ip6_gateway, dhcp4_flag, dhcp4_start, dhcp4_end):
"""
Add new network
API endpoint: POST /api/v1/network
API arguments: lots
API schema: {"message":"{data}"}
"""
request_uri = get_request_uri(config, '/network')
response = requests.post(
request_uri,
params={
'vni': vni,
'description': description,
'nettype': nettype,
'domain': domain,
'name_servers': name_servers,
'ip4_network': ip4_network,
'ip4_gateway': ip4_gateway,
'ip6_network': ip6_network,
'ip6_gateway': ip6_gateway,
'dhcp4': dhcp4_flag,
'dhcp4_start': dhcp4_start,
'dhcp4_end': dhcp4_end
}
)
if config['debug']:
print('API endpoint: POST {}'.format(request_uri))
print('Response code: {}'.format(response.status_code))
print('Response headers: {}'.format(response.headers))
if response.status_code == 200:
retstatus = True
else:
retstatus = False
return retstatus, response.json()['message']
def net_modify(config, net, description, domain, name_servers, ip4_network, ip4_gateway, ip6_network, ip6_gateway, dhcp4_flag, dhcp4_start, dhcp4_end):
"""
Modify a network
API endpoint: POST /api/v1/network/{net}
API arguments: lots
API schema: {"message":"{data}"}
"""
request_uri = get_request_uri(config, '/network/{net}'.format(net=net))
params = dict()
if description is not None:
params['description'] = description
if domain is not None:
params['domain'] = domain
if name_servers is not None:
params['name_servers'] = name_servers
if ip4_network is not None:
params['ip4_network'] = ip4_network
if ip4_gateway is not None:
params['ip4_gateway'] = ip4_gateway
if ip6_network is not None:
params['ip6_network'] = ip6_network
if ip6_gateway is not None:
params['ip6_gateway'] = ip6_gateway
if dhcp4_flag is not None:
params['dhcp4_flag'] = dhcp4_flag
if dhcp4_start is not None:
params['dhcp4_start'] = dhcp4_start
if dhcp4_end is not None:
params['dhcp4_end'] = dhcp4_end
response = requests.put(
request_uri,
params=params
)
if config['debug']:
print('API endpoint: POST {}'.format(request_uri))
print('Response code: {}'.format(response.status_code))
print('Response headers: {}'.format(response.headers))
if response.status_code == 200:
retstatus = True
else:
retstatus = False
return retstatus, response.json()['message']
def net_remove(config, net):
"""
Remove a network
API endpoint: DELETE /api/v1/network/{net}
API arguments:
API schema: {"message":"{data}"}
"""
request_uri = get_request_uri(config, '/network/{net}'.format(net=net))
response = requests.delete(
request_uri
)
if config['debug']:
print('API endpoint: DELETE {}'.format(request_uri))
print('Response code: {}'.format(response.status_code))
print('Response headers: {}'.format(response.headers))
if response.status_code == 200:
retstatus = True
else:
retstatus = False
return retstatus, response.json()['message']
#
# DHCP lease functions
#
def net_dhcp_info(config, net, mac):
"""A
Get information about network DHCP lease
API endpoint: GET /api/v1/network/{net}/lease/{mac}
API arguments:
API schema: {json_data_object}
"""
request_uri = get_request_uri(config, '/network/{net}/lease/{mac}'.format(net=net, mac=mac))
response = requests.get(
request_uri
)
if config['debug']:
print('API endpoint: POST {}'.format(request_uri))
print('Response code: {}'.format(response.status_code))
print('Response headers: {}'.format(response.headers))
if response.status_code == 200:
return True, response.json()
else:
return False, response.json()['message']
def net_dhcp_list(config, net, limit, only_static=False):
"""
Get list information about leases (limited by {limit})
API endpoint: GET /api/v1/network/{net}/lease
API arguments: limit={limit}, static={only_static}
API schema: [{json_data_object},{json_data_object},etc.]
"""
params = dict()
if limit:
params['limit'] = limit
if only_static:
params['static'] = True
request_uri = get_request_uri(config, '/network/{net}/lease'.format(net=net))
response = requests.get(
request_uri,
params=params
)
if config['debug']:
print('API endpoint: POST {}'.format(request_uri))
print('Response code: {}'.format(response.status_code))
print('Response headers: {}'.format(response.headers))
if response.status_code == 200:
return True, response.json()
else:
return False, response.json()['message']
def net_dhcp_add(config, net, ipaddr, macaddr, hostname):
"""
Add new network DHCP lease
API endpoint: POST /api/v1/network/{net}/lease
API arguments: macaddress=macaddr, ipaddress=ipaddr, hostname=hostname
API schema: {"message":"{data}"}
"""
request_uri = get_request_uri(config, '/network/{net}/lease'.format(net=net))
response = requests.post(
request_uri,
params={
'macaddress': macaddr,
'ipaddress': ipaddr,
'hostname': hostname
}
)
if config['debug']:
print('API endpoint: POST {}'.format(request_uri))
print('Response code: {}'.format(response.status_code))
print('Response headers: {}'.format(response.headers))
if response.status_code == 200:
retstatus = True
else:
retstatus = False
return retstatus, response.json()['message']
def net_dhcp_remove(config, net, mac):
"""
Remove a network DHCP lease
API endpoint: DELETE /api/v1/network/{vni}/lease/{mac}
API arguments:
API schema: {"message":"{data}"}
"""
request_uri = get_request_uri(config, '/network/{net}/lease/{mac}'.format(net=net, mac=mac))
response = requests.delete(
request_uri
)
if config['debug']:
print('API endpoint: DELETE {}'.format(request_uri))
print('Response code: {}'.format(response.status_code))
print('Response headers: {}'.format(response.headers))
if response.status_code == 200:
retstatus = True
else:
retstatus = False
return retstatus, response.json()['message']
#
# ACL functions
#
def net_acl_info(config, net, description):
"""
Get information about network ACL
API endpoint: GET /api/v1/network/{net}/acl/{description}
API arguments:
API schema: {json_data_object}
"""
request_uri = get_request_uri(config, '/network/{net}/acl/{description}'.format(net=net, description=description))
response = requests.get(
request_uri
)
if config['debug']:
print('API endpoint: POST {}'.format(request_uri))
print('Response code: {}'.format(response.status_code))
print('Response headers: {}'.format(response.headers))
if response.status_code == 200:
return True, response.json()
else:
return False, response.json()['message']
def net_acl_list(config, net, limit, direction):
"""
Get list information about ACLs (limited by {limit})
API endpoint: GET /api/v1/network/{net}/acl
API arguments: limit={limit}, direction={direction}
API schema: [{json_data_object},{json_data_object},etc.]
"""
params = dict()
if limit:
params['limit'] = limit
if direction is not None:
params['direction'] = direction
request_uri = get_request_uri(config, '/network/{net}/acl'.format(net=net))
response = requests.get(
request_uri,
params=params
)
if config['debug']:
print('API endpoint: POST {}'.format(request_uri))
print('Response code: {}'.format(response.status_code))
print('Response headers: {}'.format(response.headers))
if response.status_code == 200:
return True, response.json()
else:
return False, response.json()['message']
def net_acl_add(config, net, direction, description, rule, order):
"""
Add new network acl
API endpoint: POST /api/v1/network/{net}/acl
API arguments: description=description, direction=direction, order=order, rule=rule
API schema: {"message":"{data}"}
"""
params = dict()
params['description'] = description
params['direction'] = direction
params['rule'] = rule
if order is not None:
params['order'] = order
request_uri = get_request_uri(config, '/network/{net}/acl'.format(net=net))
response = requests.post(
request_uri,
params=params
)
if config['debug']:
print('API endpoint: POST {}'.format(request_uri))
print('Response code: {}'.format(response.status_code))
print('Response headers: {}'.format(response.headers))
if response.status_code == 200:
retstatus = True
else:
retstatus = False
return retstatus, response.json()['message']
def net_acl_remove(config, net, description):
"""
Remove a network ACL
API endpoint: DELETE /api/v1/network/{vni}/acl/{description}
API arguments:
API schema: {"message":"{data}"}
"""
request_uri = get_request_uri(config, '/network/{net}/acl/{description}'.format(net=net, description=description))
response = requests.delete(
request_uri
)
if config['debug']:
print('API endpoint: DELETE {}'.format(request_uri))
print('Response code: {}'.format(response.status_code))
print('Response headers: {}'.format(response.headers))
if response.status_code == 200:
retstatus = True
else:
retstatus = False
return retstatus, response.json()['message']
#
# Output display functions
#
def getOutputColours(network_information):
if network_information['ip6']['network'] != "None":
@ -79,7 +481,7 @@ def getOutputColours(network_information):
return v6_flag_colour, v4_flag_colour, dhcp6_flag_colour, dhcp4_flag_colour
def format_info(network_information, long_output):
def format_info(config, network_information, long_output):
if not network_information:
click.echo("No network found")
return
@ -131,7 +533,7 @@ def format_info(network_information, long_output):
# Join it all together
click.echo('\n'.join(ainformation))
def format_list(network_list):
def format_list(config, network_list):
if not network_list:
click.echo("No network found")
return
@ -314,6 +716,10 @@ def format_list_dhcp(dhcp_lease_list):
click.echo('\n'.join(sorted(dhcp_lease_list_output)))
def format_list_acl(acl_list):
# Handle when we get a single entry
if isinstance(acl_list, dict):
acl_list = [ acl_list ]
acl_list_output = []
# Determine optimal column widths
@ -376,4 +782,3 @@ def format_list_acl(acl_list):
)
click.echo('\n'.join(sorted(acl_list_output)))

View File

@ -744,9 +744,8 @@ def net_add(vni, description, nettype, domain, ip_network, ip_gateway, ip6_netwo
click.echo('Error: At least one of "-i" / "--ipnet" or "-i6" / "--ipnet6" must be specified.')
exit(1)
zk_conn = pvc_common.startZKConnection(zk_host)
retcode, retmsg = pvc_network.add_network(zk_conn, vni, description, nettype, domain, name_servers, ip_network, ip_gateway, ip6_network, ip6_gateway, dhcp_flag, dhcp_start, dhcp_end)
cleanup(retcode, retmsg, zk_conn)
retcode, retmsg = pvc_network.net_add(config, vni, description, nettype, domain, name_servers, ip_network, ip_gateway, ip6_network, ip6_gateway, dhcp_flag, dhcp_start, dhcp_end)
cleanup(retcode, retmsg)
###############################################################################
# pvc network modify
@ -814,9 +813,8 @@ def net_modify(vni, description, domain, name_servers, ip6_network, ip6_gateway,
pvc network modify 1001 --gateway 10.1.1.1 --dhcp
"""
zk_conn = pvc_common.startZKConnection(zk_host)
retcode, retmsg = pvc_network.modify_network(zk_conn, vni, description=description, domain=domain, name_servers=name_servers, ip6_network=ip6_network, ip6_gateway=ip6_gateway, ip4_network=ip4_network, ip4_gateway=ip4_gateway, dhcp_flag=dhcp_flag, dhcp_start=dhcp_start, dhcp_end=dhcp_end)
cleanup(retcode, retmsg, zk_conn)
retcode, retmsg = pvc_network.net_modify(config, vni, description, domain, name_servers, ip4_network, ip4_gateway, ip6_network, ip6_gateway, dhcp_flag, dhcp_start, dhcp_end)
cleanup(retcode, retmsg)
###############################################################################
# pvc network remove
@ -827,15 +825,14 @@ def net_modify(vni, description, domain, name_servers, ip6_network, ip6_gateway,
)
def net_remove(net):
"""
Remove an existing virtual network NET from the cluster; NET can be either a VNI or description.
Remove an existing virtual network NET from the cluster; NET must be a VNI.
WARNING: PVC does not verify whether clients are still present in this network. Before removing, ensure
that all client VMs have been removed from the network or undefined behaviour may occur.
"""
zk_conn = pvc_common.startZKConnection(zk_host)
retcode, retmsg = pvc_network.remove_network(zk_conn, net)
cleanup(retcode, retmsg, zk_conn)
retcode, retmsg = pvc_network.net_remove(config, net)
cleanup(retcode, retmsg)
###############################################################################
# pvc network info
@ -853,14 +850,11 @@ def net_info(vni, long_output):
Show information about virtual network VNI.
"""
# Open a Zookeeper connection
zk_conn = pvc_common.startZKConnection(zk_host)
retcode, retdata = pvc_network.get_info(zk_conn, vni)
retcode, retdata = pvc_network.net_info(config, vni)
if retcode:
pvc_network.format_info(retdata, long_output)
pvc_network.format_info(config, retdata, long_output)
retdata = ''
cleanup(retcode, retdata, zk_conn)
cleanup(retcode, retdata)
###############################################################################
# pvc network list
@ -874,12 +868,11 @@ def net_list(limit):
List all virtual networks in the cluster; optionally only match VNIs or Descriptions matching regex LIMIT.
"""
zk_conn = pvc_common.startZKConnection(zk_host)
retcode, retdata = pvc_network.get_list(zk_conn, limit)
retcode, retdata = pvc_network.net_list(config, limit)
if retcode:
pvc_network.format_list(retdata)
pvc_network.format_list(config, retdata)
retdata = ''
cleanup(retcode, retdata, zk_conn)
cleanup(retcode, retdata)
###############################################################################
# pvc network dhcp
@ -892,39 +885,7 @@ def net_dhcp():
pass
###############################################################################
# pvc network dhcp list
###############################################################################
@click.command(name='list', short_help='List active DHCP leases.')
@click.argument(
'net'
)
@click.argument(
'limit', default=None, required=False
)
def net_dhcp_list(net, limit):
"""
List all DHCP leases in virtual network NET; optionally only match elements matching regex LIMIT; NET can be either a VNI or description.
"""
zk_conn = pvc_common.startZKConnection(zk_host)
retcode, retdata = pvc_network.get_list_dhcp(zk_conn, net, limit, only_static=False)
if retcode:
pvc_network.format_list_dhcp(retdata)
retdata = ''
cleanup(retcode, retdata, zk_conn)
###############################################################################
# pvc network dhcp static
###############################################################################
@click.group(name='static', short_help='Manage DHCP static reservations in a PVC virtual network.', context_settings=CONTEXT_SETTINGS)
def net_dhcp_static():
"""
Manage host DHCP static reservations of a VXLAN network in the PVC cluster.
"""
pass
###############################################################################
# pvc network dhcp static add
# pvc network dhcp add
###############################################################################
@click.command(name='add', short_help='Add a DHCP static reservation.')
@click.argument(
@ -939,55 +900,56 @@ def net_dhcp_static():
@click.argument(
'macaddr'
)
def net_dhcp_static_add(net, ipaddr, macaddr, hostname):
def net_dhcp_add(net, ipaddr, macaddr, hostname):
"""
Add a new DHCP static reservation of IP address IPADDR with hostname HOSTNAME for MAC address MACADDR to virtual network NET; NET can be either a VNI or description.
Add a new DHCP static reservation of IP address IPADDR with hostname HOSTNAME for MAC address MACADDR to virtual network NET; NET must be a VNI.
"""
zk_conn = pvc_common.startZKConnection(zk_host)
retcode, retmsg = pvc_network.add_dhcp_reservation(zk_conn, net, ipaddr, macaddr, hostname)
cleanup(retcode, retmsg, zk_conn)
retcode, retmsg = pvc_network.net_dhcp_add(config, net, ipaddr, macaddr, hostname)
cleanup(retcode, retmsg)
###############################################################################
# pvc network dhcp static remove
# pvc network dhcp remove
###############################################################################
@click.command(name='remove', short_help='Remove a DHCP static reservation.')
@click.argument(
'net'
)
@click.argument(
'reservation'
'macaddr'
)
def net_dhcp_static_remove(net, reservation):
def net_dhcp_remove(net, reservation):
"""
Remove a DHCP static reservation RESERVATION from virtual network NET; RESERVATION can be either a MAC address, an IP address, or a hostname; NET can be either a VNI or description.
Remove a DHCP static reservation for MACADDR from virtual network NET; NET must be a VNI.
"""
zk_conn = pvc_common.startZKConnection(zk_host)
retcode, retmsg = pvc_network.remove_dhcp_reservation(zk_conn, net, reservation)
cleanup(retcode, retmsg, zk_conn)
retcode, retmsg = pvc_network.net_dhcp_remove(config, net, reservation)
cleanup(retcode, retmsg)
###############################################################################
# pvc network dhcp static list
# pvc network dhcp list
###############################################################################
@click.command(name='list', short_help='List DHCP static reservations.')
@click.command(name='list', short_help='List active DHCP leases.')
@click.argument(
'net'
)
@click.argument(
'limit', default=None, required=False
)
def net_dhcp_static_list(net, limit):
@click.option(
'-s', '--static', 'only_static', is_flag=True, default=False,
help='Show only static leases.'
)
def net_dhcp_list(net, limit, only_static):
"""
List all DHCP static reservations in virtual network NET; optionally only match elements matching regex LIMIT; NET can be either a VNI or description.
List all DHCP leases in virtual network NET; optionally only match elements matching regex LIMIT; NET must be a VNI.
"""
zk_conn = pvc_common.startZKConnection(zk_host)
retcode, retdata = pvc_network.get_list_dhcp(zk_conn, net, limit, only_static=True)
retcode, retdata = pvc_network.net_dhcp_list(config, net, limit, only_static)
if retcode:
pvc_network.format_list_dhcp(retdata)
retdata = ''
cleanup(retcode, retdata, zk_conn)
cleanup(retcode, retdata)
###############################################################################
# pvc network acl
@ -1006,8 +968,7 @@ def net_acl():
@click.option(
'--in/--out', 'direction',
is_flag=True,
required=True,
default=None,
default=True, #inbound
help='Inbound or outbound ruleset.'
)
@click.option(
@ -1030,46 +991,44 @@ def net_acl():
)
def net_acl_add(net, direction, description, rule, order):
"""
Add a new NFT firewall rule to network NET; the rule is a literal NFT rule belonging to the forward table for the client network; NET can be either a VNI or description.
Add a new NFT firewall rule to network NET; the rule is a literal NFT rule belonging to the forward table for the client network; NET must be a VNI.
NOTE: All client networks are default-allow in both directions; deny rules MUST be added here at the end of the sequence for a default-deny setup.
NOTE: Ordering places the rule at the specified ID, not before it; the old rule of that ID and all subsequent rules will be moved down.
NOTE: Descriptions are used as names, and must be unique within a network (both directions).
Example:
pvc network acl add 1001 --in --rule "tcp dport 22 ct state new accept" --description "ssh-in" --order 3
"""
if direction:
direction = 'in'
else:
direction = 'out'
zk_conn = pvc_common.startZKConnection(zk_host)
retcode, retmsg = pvc_network.add_acl(zk_conn, net, direction, description, rule, order)
cleanup(retcode, retmsg, zk_conn)
retcode, retmsg = pvc_network.net_acl_add(config, net, direction, description, rule, order)
cleanup(retcode, retmsg)
###############################################################################
# pvc network acl remove
###############################################################################
@click.command(name='remove', short_help='Remove firewall ACL.')
@click.option(
'--in/--out', 'direction',
is_flag=True,
required=True,
default=None,
help='Inbound or outbound rule set.'
)
@click.argument(
'net'
)
@click.argument(
'rule',
)
def net_acl_remove(net, rule, direction):
def net_acl_remove(net, rule):
"""
Remove an NFT firewall rule RULE from network NET; RULE can be either a sequence order identifier or description; NET can be either a VNI or description."
Remove an NFT firewall rule RULE from network NET; RULE must be a description; NET must be a VNI.
"""
zk_conn = pvc_common.startZKConnection(zk_host)
retcode, retmsg = pvc_network.remove_acl(zk_conn, net, rule, direction)
cleanup(retcode, retmsg, zk_conn)
retcode, retmsg = pvc_network.net_acl_remove(config, net, rule)
cleanup(retcode, retmsg)
###############################################################################
# pvc network acl list
@ -1092,13 +1051,17 @@ def net_acl_list(net, limit, direction):
"""
List all NFT firewall rules in network NET; optionally only match elements matching description regex LIMIT; NET can be either a VNI or description.
"""
if direction is not None:
if direction:
direction = 'in'
else:
direction = 'out'
zk_conn = pvc_common.startZKConnection(zk_host)
retcode, retdata = pvc_network.get_list_acl(zk_conn, net, limit, direction)
retcode, retdata = pvc_network.net_acl_list(config, net, limit, direction)
if retcode:
pvc_network.format_list_acl(retdata)
retdata = ''
cleanup(retcode, retdata, zk_conn)
cleanup(retcode, retdata)
###############################################################################
# pvc storage
@ -1838,11 +1801,8 @@ cli_network.add_command(net_dhcp)
cli_network.add_command(net_acl)
net_dhcp.add_command(net_dhcp_list)
net_dhcp.add_command(net_dhcp_static)
net_dhcp_static.add_command(net_dhcp_static_add)
net_dhcp_static.add_command(net_dhcp_static_remove)
net_dhcp_static.add_command(net_dhcp_static_list)
net_dhcp.add_command(net_dhcp_add)
net_dhcp.add_command(net_dhcp_remove)
net_acl.add_command(net_acl_add)
net_acl.add_command(net_acl_remove)