From 4d350ced7e82b34cc9d4688a22d96df3fb3bb80a Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Sun, 30 Sep 2018 12:43:56 -0400 Subject: [PATCH] Some major tweaks to make DHCP reservations work with the server --- cli-client/pvc.py | 14 ++++---- client-common/client_lib/network.py | 56 ++++++++++++++--------------- router-daemon/pvcrd/DHCPServer.py | 48 ++++++++++++++++--------- 3 files changed, 63 insertions(+), 55 deletions(-) diff --git a/cli-client/pvc.py b/cli-client/pvc.py index 482a55c8..de45a026 100755 --- a/cli-client/pvc.py +++ b/cli-client/pvc.py @@ -747,11 +747,6 @@ def net_dhcp(): # pvc network dhcp add ############################################################################### @click.command(name='add', short_help='Add a DHCP reservation to a virtual network.') -@click.option( - '-d', '--description', 'description', - default=None, - help='Description of the DHCP reservation; defaults to MACADDR if unspecified; should not contain whitespace.' -) @click.argument( 'net' ) @@ -761,13 +756,16 @@ def net_dhcp(): @click.argument( 'macaddr' ) -def net_dhcp_add(net, ipaddr, macaddr, description): +@click.argument( + 'hostname' +) +def net_dhcp_add(net, ipaddr, macaddr, hostname): """ - Add a new DHCP reservation of IP address IPADDR for MAC address MACADDR to virtual network NET; NET can be either a VNI or description. + Add a new DHCP reservation of host HOSTNAME with IP address IPADDR for MAC address MACADDR to virtual network NET; NET can be either a VNI or description. """ zk_conn = pvc_common.startZKConnection(zk_host) - retcode, retmsg = pvc_network.add_dhcp_reservation(zk_conn, net, ipaddr, macaddr, description) + retcode, retmsg = pvc_network.add_dhcp_reservation(zk_conn, net, ipaddr, macaddr, hostname) cleanup(retcode, retmsg, zk_conn) ############################################################################### diff --git a/client-common/client_lib/network.py b/client-common/client_lib/network.py index 79b5d42c..70b01bb5 100644 --- a/client-common/client_lib/network.py +++ b/client-common/client_lib/network.py @@ -101,7 +101,7 @@ def getNetworkDescription(zk_conn, network): return net_description def getNetworkDHCPReservations(zk_conn, vni): - # Get a list of VNIs by listing the children of /networks//dhcp_reservations + # Get a list of VNIs by listing the children of /networks//dhcp_leases dhcp_reservations = [] dhcp_leases = zk_conn.get_children('/networks/{}/dhcp_leases'.format(vni)) for lease in dhcp_leases: @@ -125,10 +125,10 @@ def getNetworkInformation(zk_conn, vni): return description, domain, ip_network, ip_gateway, dhcp_flag, dhcp_start, dhcp_end def getDHCPReservationInformation(zk_conn, vni, macaddr): - description = zkhandler.readdata(zk_conn, '/networks/{}/dhcp_leases/{}/description'.format(vni, macaddr)) + hostname = zkhandler.readdata(zk_conn, '/networks/{}/dhcp_leases/{}/hostname'.format(vni, macaddr)) ip_address = zkhandler.readdata(zk_conn, '/networks/{}/dhcp_leases/{}/ipaddr'.format(vni, macaddr)) mac_address = macaddr - return description, ip_address, mac_address + return hostname, ip_address, mac_address def formatNetworkInformation(zk_conn, vni, long_output): description, domain, ip_network, ip_gateway, dhcp_flag, dhcp_start, dhcp_end = getNetworkInformation(zk_conn, vni) @@ -301,26 +301,26 @@ def formatNetworkList(zk_conn, net_list): def formatDHCPReservationList(zk_conn, vni, dhcp_reservations_list): dhcp_reservation_list_output = [] - description = {} + hostname = {} ip_address = {} mac_address = {} # Gather information for printing for dhcp_reservation in dhcp_reservations_list: # get info - description[dhcp_reservation], ip_address[dhcp_reservation], mac_address[dhcp_reservation] = getDHCPReservationInformation(zk_conn, vni, dhcp_reservation) + hostname[dhcp_reservation], ip_address[dhcp_reservation], mac_address[dhcp_reservation] = getDHCPReservationInformation(zk_conn, vni, dhcp_reservation) # Determine optimal column widths # Dynamic columns: node_name, hypervisor, migrated - reservation_description_length = 13 + reservation_hostname_length = 13 reservation_ip_address_length = 11 reservation_mac_address_length = 13 for dhcp_reservation in dhcp_reservations_list: - # description column - _reservation_description_length = len(description[dhcp_reservation]) + 1 - if _reservation_description_length > reservation_description_length: - reservation_description_length = _reservation_description_length + # hostname column + _reservation_hostname_length = len(hostname[dhcp_reservation]) + 1 + if _reservation_hostname_length > reservation_hostname_length: + reservation_hostname_length = _reservation_hostname_length # ip_network column _reservation_ip_address_length = len(ip_address[dhcp_reservation]) + 1 if _reservation_ip_address_length > reservation_ip_address_length: @@ -332,32 +332,32 @@ def formatDHCPReservationList(zk_conn, vni, dhcp_reservations_list): # Format the string (header) dhcp_reservation_list_output_header = '{bold}\ -{reservation_description: <{reservation_description_length}} \ +{reservation_hostname: <{reservation_hostname_length}} \ {reservation_ip_address: <{reservation_ip_address_length}} \ {reservation_mac_address: <{reservation_mac_address_length}} \ {end_bold}'.format( bold=ansiiprint.bold(), end_bold=ansiiprint.end(), - reservation_description_length=reservation_description_length, + reservation_hostname_length=reservation_hostname_length, reservation_ip_address_length=reservation_ip_address_length, reservation_mac_address_length=reservation_mac_address_length, - reservation_description='Description', + reservation_hostname='Hostname', reservation_ip_address='IP Address', reservation_mac_address='MAC Address' ) for dhcp_reservation in dhcp_reservations_list: dhcp_reservation_list_output.append('{bold}\ -{reservation_description: <{reservation_description_length}} \ +{reservation_hostname: <{reservation_hostname_length}} \ {reservation_ip_address: <{reservation_ip_address_length}} \ {reservation_mac_address: <{reservation_mac_address_length}} \ {end_bold}'.format( bold='', end_bold='', - reservation_description_length=reservation_description_length, + reservation_hostname_length=reservation_hostname_length, reservation_ip_address_length=reservation_ip_address_length, reservation_mac_address_length=reservation_mac_address_length, - reservation_description=description[dhcp_reservation], + reservation_hostname=hostname[dhcp_reservation], reservation_ip_address=ip_address[dhcp_reservation], reservation_mac_address=mac_address[dhcp_reservation] ) @@ -415,7 +415,7 @@ def add_network(zk_conn, vni, description, domain, ip_network, ip_gateway, dhcp_ '/networks/{}/dhcp_flag'.format(vni): str(dhcp_flag), '/networks/{}/dhcp_start'.format(vni): dhcp_start, '/networks/{}/dhcp_end'.format(vni): dhcp_end, - '/networks/{}/dhcp_reservations'.format(vni): '', + '/networks/{}/dhcp_leases'.format(vni): '', '/networks/{}/firewall_rules'.format(vni): '' }) @@ -434,7 +434,6 @@ def modify_network(zk_conn, vni, **parameters): if parameters['dhcp_flag'] != None: zk_data.update({'/networks/{}/dhcp_flag'.format(vni): str(parameters['dhcp_flag'])}) - print(zk_data) zkhandler.writedata(zk_conn, zk_data) return True, 'Network "{}" modified successfully!'.format(vni) @@ -455,7 +454,7 @@ def remove_network(zk_conn, network): return True, 'Network "{}" removed successfully!'.format(description) -def add_dhcp_reservation(zk_conn, network, ipaddress, macaddress, description): +def add_dhcp_reservation(zk_conn, network, ipaddress, macaddress, hostname): # Validate and obtain standard passed value net_vni = getNetworkVNI(zk_conn, network) if net_vni == None: @@ -470,23 +469,20 @@ def add_dhcp_reservation(zk_conn, network, ipaddress, macaddress, description): if not isValidIP(ipaddress): return False, 'ERROR: IP address "{}" is not valid!'.format(macaddress) - if not description: - description = macaddress - if zk_conn.exists('/networks/{}/dhcp_leases/{}'.format(net_vni, macaddress)): return False, 'ERROR: A reservation with MAC "{}" already exists!'.format(macaddress) - # Add the new network to ZK + # Add the new static lease to ZK try: zkhandler.writedata(zk_conn, { - '/networks/{}/dhcp_reservations/{}'.format(net_vni, macaddr): description, - '/networks/{}/dhcp_reservations/{}/description'.format(net_vni, macaddr): description, - '/networks/{}/dhcp_reservations/{}/ipaddr'.format(net_vni, macaddr): ipaddress + '/networks/{}/dhcp_leases/{}'.format(net_vni, macaddress): 'static', + '/networks/{}/dhcp_leases/{}/hostname'.format(net_vni, macaddress): hostname, + '/networks/{}/dhcp_leases/{}/ipaddr'.format(net_vni, macaddress): ipaddress }) except Exception as e: return False, 'ERROR: Failed to write to Zookeeper! Exception: "{}".'.format(e) - return True, 'DHCP reservation "{}" added successfully!'.format(description) + return True, 'DHCP reservation "{}" added successfully!'.format(macaddress) def remove_dhcp_reservation(zk_conn, network, reservation): # Validate and obtain standard passed value @@ -499,12 +495,12 @@ def remove_dhcp_reservation(zk_conn, network, reservation): # Check if the reservation matches a description, a mac, or an IP address currently in the database reservation_list = zk_conn.get_children('/networks/{}/dhcp_leases'.format(net_vni)) for macaddr in reservation_list: - timestamp = zkhandler.readdata(zk_conn, macaddr) + timestamp = zkhandler.readdata(zk_conn, '/networks/{}/dhcp_leases/{}'.format(net_vni, macaddr)) if timestamp != 'static': continue - description = zkhandler.readdata(zk_conn, '/networks/{}/dhcp_leases/{}/description'.format(net_vni, macaddr)) + hostname = zkhandler.readdata(zk_conn, '/networks/{}/dhcp_leases/{}/hostname'.format(net_vni, macaddr)) ipaddress = zkhandler.readdata(zk_conn, '/networks/{}/dhcp_leases/{}/ipaddr'.format(net_vni, macaddr)) - if reservation == macaddr or reservation == description or reservation == ipaddress: + if reservation == macaddr or reservation == hostname or reservation == ipaddress: match_description = macaddr if not match_description: diff --git a/router-daemon/pvcrd/DHCPServer.py b/router-daemon/pvcrd/DHCPServer.py index 48ab1e3e..d7de801f 100644 --- a/router-daemon/pvcrd/DHCPServer.py +++ b/router-daemon/pvcrd/DHCPServer.py @@ -610,50 +610,58 @@ class CSVDatabase(object): class ZKDatabase(object): # Store DHCP leases in zookeeper - # /networks//dhcp_leases/:/{ipaddr,description/hostname} + # /networks//dhcp_leases/:/{ipaddr,hostname} # Line: # ['52:54:00:21:34:11', '10.10.10.6', 'test1', '1538287572'] def __init__(self, zk_conn, key): self.zk_conn = zk_conn self.key = key - zkhandler.writedata(self.zk_conn, { self.key: '' }) # Create base key def get(self, pattern): - pattern = list(pattern) - return [line for line in self.all() if pattern == line] + pattern = list(pattern) + return [line for line in self.all() if pattern[0] == line[0] or pattern[1] == line[1] or pattern[2] == line[2]] + + def isstatic(self, pattern): + macaddr = pattern[0] + try: + timestamp = zkhandler.readdata(self.zk_conn, '{}/{}'.format(self.key, macaddr)) + if timestamp == 'static': + return True + else: + return False + except Exception: + return False def add(self, line): macaddr = line[0] ipaddr = line[1] - description = line[2] + hostname = line[2] timestamp = line[3] zkhandler.writedata(self.zk_conn, { '{}/{}'.format(self.key, macaddr): timestamp, '{}/{}/ipaddr'.format(self.key, macaddr): ipaddr, - '{}/{}/description'.format(self.key, macaddr): description + '{}/{}/hostname'.format(self.key, macaddr): hostname }) def delete(self, pattern): macaddr = pattern[0] try: - timestamp = zkhandler.readdata(self.zk_conn, '{}/{}'.format(self.key, macaddr)) - if timestamp != 'static': - zkhandler.delete(self.zk_conn, '{}/{}'.format(self.key, macaddr)) - return True + zkhandler.delete(self.zk_conn, '{}/{}'.format(self.key, macaddr)) except Exception: pass - return False def all(self): leases = [] mac_list = zkhandler.listchildren(self.zk_conn, self.key) for macaddr in mac_list: timestamp = zkhandler.readdata(self.zk_conn, '{}/{}'.format(self.key, macaddr)) + if timestamp == 'static': + timestamp = 0 ipaddr = zkhandler.readdata(self.zk_conn, '{}/{}/ipaddr'.format(self.key, macaddr)) - description = zkhandler.readdata(self.zk_conn, '{}/{}/description'.format(self.key, macaddr)) - leases.append([macaddr, ipaddr, description, timestamp]) + hostname = zkhandler.readdata(self.zk_conn, '{}/{}/hostname'.format(self.key, macaddr)) + leases.append([macaddr, ipaddr, hostname, timestamp]) return leases @@ -716,11 +724,15 @@ class HostDatabase(object): pattern = host.to_pattern() self.db.delete(pattern) + def isstatic(self, host): + return self.db.isstatic(host.to_tuple()) + def all(self): return list(map(Host.from_tuple, self.db.all())) def replace(self, host): - if self.delete(host): + if not self.isstatic(host): + self.delete(host) self.add(host) def sorted_hosts(hosts): @@ -752,14 +764,16 @@ class DHCPServer(object): def update(self, timeout = 0): try: reads = select.select([self.socket], [], [], timeout)[0] - except ValueError: + except ValueError as e: # ValueError: file descriptor cannot be a negative integer (-1) + print(e) return for socket in reads: try: packet = ReadBootProtocolPacket(*socket.recvfrom(4096)) - except OSError: + except OSError as e: # OSError: [WinError 10038] An operation was attempted on something that is not a socket + print(e) pass else: self.received(packet) @@ -793,7 +807,7 @@ class DHCPServer(object): known_hosts = self.hosts.get(mac = CASEINSENSITIVE(mac_address)) ip = None if known_hosts: - # 1. choose known ip address + # 1. choose known ip address (including static lease) for host in known_hosts: if self.is_valid_client_address(host.ip): ip = host.ip