def __init__(self, addr, net_bssid_addr, lvap_bssid_addr): # read only params self.addr = addr self.net_bssid = net_bssid_addr # lvap bssid, this is the bssid to which the client is currently # attached, it can only be updated as a result of an auth request # message self._lvap_bssid = lvap_bssid_addr # the following parameters are only updated upon RX of a lvap status # update message from an agent self.authentication_state = False self.association_state = False # the following parameters are only updated by the controller, which # will then dispatch an add lvap message in order to propagate the # change to the agent self._ssids = [] self._encap = None # the following parameters can be updated by both agent and # controller. The controller sets them when a client successfully # associate. The agent sets them upon disassociation. self._assoc_id = 0 self._tenant = None # only one block supported, default block points to this self._downlink = DownlinkPort() # multiple blocks supported, no port configuration supported self._uplink = UplinkPort() # counters self.tx_samples = [] self.rx_samples = [] # virtual ports (VNFs) self.ports = {} # downlink intent uuid self.poa_uuid = None # supported resource blocks self.supported_band = None # this is set before clearing the DL blocks, so that the del_lvap # message can be filled with the target block information self.target_block = None # module id incremental counter self.__module_id = 0 # pending ids self.pending = []
def __init__(self, addr, net_bssid_addr, lvap_bssid_addr): # read only params self.addr = addr self.net_bssid = net_bssid_addr self.supports = ResourcePool() # lvap bssid, this is the bssid to which the client is currently # attached self._lvap_bssid = lvap_bssid_addr # the following parameters are only updated upon RX of a lvap status # update message from an agent self.authentication_state = False self.association_state = False # the following parameters are only updated by the controller, which # will then dispatch an add lvap message in order to propagate the # change to the agent self._ssids = [] self._encap = None # the following parameters can be updated by both agent and # controller. The controller sets them when a client successfully # associate. The agent sets them upon disassociation. self._assoc_id = 0 self._tenant = None # only one block supported, default block points to this self._downlink = DownlinkPort() # multiple blocks supported, no port configuration supported self._uplink = UplinkPort() # counters self.tx_samples = [] self.rx_samples = [] # rates statistics self.rates = {} # virtual ports (VNFs) self.__ports = {}
class LVAP: """ The EmPOWER Light Virtual Access Point One LVAP is created for every station probing the network (unless the MAC was blocked or if the MAC was not in the allowed list). An LVAP can be hosted by multiple WTPs. More preciselly an LVAP can be scheduled on one, and only one resource block in the downlink direction and on multiple resource blocks in the uplink direction. The downlink resource block is automatically also the default uplink resource block. The default uplink resource block is the resource block in charge of generating WiFi acks. Additional uplink resource blocks do not generate acks but can opportunistically receive and forward traffic. An unbound LVAP, i.e. an LVAP not hosted by any WTP, is not admissible. The association between LVAP and ResourceBlock(s) is called TX Policy and models the parameters of the link between WTP and LVAP. The TX Policy abstraction is used to specify control policies at the WTP level. Example are the rate control algorithm which cannot be managed at the controller level due to timing constraints. Handover can be performed by setting the wtp property of an lvap object to another wtp, e.g.: lvap.wtp = new_wtp where lvap is an instance of LVAP object and new_wtp is an instance of wtp object. The controller takes care of removing the lvap from the old wtp and spawing a new lvap on the target wtp. Stats are cleared on handovers. The new_wtp must support the same channel and band of the old wtp otherwise no handover is performed. An handover can also be perfomed by assigning a valid list of ResourceBlocks to the blocks property of an LVAP object. The first block in the list will be the downlink block while the other (if any) will be uplink blocks: lvap.blocks = blocks Notice how the blocks variable must be either a non empty list of ResourceBlocks or it must be a single ResourceBlock. The TX Polocy configuration the downlink resource block can be changed in the following way: port = lvap.downlink[block] port.mcs = [1,2,3,4,5,6,7] Where block is the ResourceBlock previously assigned. A new port configuration can be assigned in a single step with: lvap.downlink[block] = port where port is an instance of the RadioPort class. The last line will trigger a Port update message if the entry already exists. If the entry does not exists a ValueError message will be triggered. This is because the property blocks cannot be empty, then if the block is not found it means that the LVAP already has a downlink block and a second one cannot be created. Attributes: addr: The client's MAC Address as an EtherAddress instance. tx_samples: array of 2-tuples of the TX'ed packets rx_samples: array of 2-tuples of the RX'ed packets net_bssid: The LVAP's MAC Address as an EtherAddress instance. This address is dynamically generated by the Access Controller. The BSSID is supposed to be unique in the entire network. lvap_bssid: The LVAP's active bssid as EtherAddress instance. This is the same of the net_bssid if the client is attached to a Unique Lvap tenant. If the client is attached to a Shared Lvap Tenant (i.e. to a VAP), then the lvap_bssid is the bssid of the VAP. ssids: The list of SSIDs to be broadcasted for this LVAP. assoc_id: association id for this LVAP (this cannot change after the LVAP has been spawned authentication_state: set to True if the LVAP has already completed the open authentication handshake . association_state: set to True if the LVAP has already completed the association handshake. ssid: The currently associated SSID. tx_samples: the transmitted packets rx_samples: the received packets encap: encapsulate data traffic into an ethernet frame with the specified destationation address tenant: the tenant to which this LVAP is associate (can be None) supported: the resource block advertised by the LVAP during the attachment procedure target_block: the target block of an handover procedure pending: the list of pending handover operations blocks: the concatenation of the downlink and uplink blocks (settable) downlink: the downlink block (as a dictionary, cannot be set) uplink: the uplink blocks (as a dictionary, cannot be set) """ def __init__(self, addr, net_bssid_addr, lvap_bssid_addr): # read only params self.addr = addr self.net_bssid = net_bssid_addr # lvap bssid, this is the bssid to which the client is currently # attached, it can only be updated as a result of an auth request # message self._lvap_bssid = lvap_bssid_addr # the following parameters are only updated upon RX of a lvap status # update message from an agent self.authentication_state = False self.association_state = False # the following parameters are only updated by the controller, which # will then dispatch an add lvap message in order to propagate the # change to the agent self._ssids = [] self._encap = None # the following parameters can be updated by both agent and # controller. The controller sets them when a client successfully # associate. The agent sets them upon disassociation. self._assoc_id = 0 self._tenant = None # only one block supported, default block points to this self._downlink = DownlinkPort() # multiple blocks supported, no port configuration supported self._uplink = UplinkPort() # counters self.tx_samples = [] self.rx_samples = [] # virtual ports (VNFs) self.ports = {} # downlink intent uuid self.poa_uuid = None # supported resource blocks self.supported_band = None # this is set before clearing the DL blocks, so that the del_lvap # message can be filled with the target block information self.target_block = None # module id incremental counter self.__module_id = 0 # pending ids self.pending = [] @property def module_id(self): """Return new sequence id.""" self.__module_id += 1 self.pending.append(self.__module_id) return self.__module_id def __set_ports(self): """Set virtual ports. This method is called everytime an LVAP is moved to another WTP. More preciselly it is called every time an assignment to the downlink property is made. """ # Delete all outgoing virtual link and then remove the entire port if self.ports: self.ports[0].clear() del self.ports[0] # Create a new port from scratch self.ports[0] = VirtualPortLvap(phy_port=self.wtp.port(), virtual_port_id=0, lvap=self) # set/update intent intent = { 'version': '1.0', 'dpid': self.ports[0].dpid, 'port': self.ports[0].ovs_port_id, 'hwaddr': self.addr } intent_server = RUNTIME.components[IntentServer.__module__] if self.poa_uuid: intent_server.update_poa(intent, self.poa_uuid) else: self.poa_uuid = intent_server.add_poa(intent) def refresh_lvap(self): """Send add lvap message on the selected port.""" for port in self._downlink.values(): port.block.radio.connection.send_add_lvap(port.lvap, port.block, self._downlink.SET_MASK) for port in self._uplink.values(): port.block.radio.connection.send_add_lvap(port.lvap, port.block, self._uplink.SET_MASK) @property def encap(self): """Get the encap.""" return self._encap @encap.setter def encap(self, encap): """ Set the encap. """ if self._encap == encap: return self._encap = encap self.refresh_lvap() @property def assoc_id(self): """Get the assoc_id.""" return self._assoc_id @assoc_id.setter def assoc_id(self, assoc_id): """Set the assoc id.""" if self._assoc_id == assoc_id: return self._assoc_id = assoc_id self.refresh_lvap() @property def lvap_bssid(self): """Get the lvap_bssid.""" return self._lvap_bssid @lvap_bssid.setter def lvap_bssid(self, lvap_bssid): """Set the assoc id.""" if self._lvap_bssid == lvap_bssid: return self._lvap_bssid = lvap_bssid self.refresh_lvap() @property def ssids(self): """Get the ssids assigned to this LVAP.""" return self._ssids @ssids.setter def ssids(self, ssids): """Set the ssids assigned to this LVAP.""" if self._ssids == ssids: return self._ssids = ssids self.refresh_lvap() def set_ssids(self, ssids): """Set the ssids assigned to this LVAP without seding messages.""" self._ssids = ssids @property def ssid(self): """ Get the SSID assigned to this LVAP. """ if not self._tenant: return None return self._tenant.tenant_name @property def tenant(self): """ Get the tenant assigned to this LVAP. """ return self._tenant @tenant.setter def tenant(self, tenant): """ Set the SSID. """ if self._tenant == tenant: return self._tenant = tenant self.refresh_lvap() @property def downlink(self): """ Get the resource blocks assigned to this LVAP in the downlink. """ return self._downlink @property def uplink(self): """ Get the resource blocks assigned to this LVAP in the uplink. """ return self._uplink @property def blocks(self): """ Get the resource blocks assigned to this LVAP in the uplink. """ return list(self._downlink.keys()) + list(self._uplink.keys()) @blocks.setter def blocks(self, blocks): """Assign a list of block to the LVAP. Assign a list of block to the LVAP. Accepts as input either a list or a ResourceBlock. If the list has more than one ResourceBlocks, then the first one is assigned to the downlink and the remaining are assigned to the uplink. Args: blocks: A list of ResourceBlocks or a ResourceBlock """ if self.pending: raise ValueError("Handover alredy in progress") if not blocks: return if isinstance(blocks, list): pool = blocks elif isinstance(blocks, ResourceBlock): pool = [] pool.append(blocks) else: raise TypeError("Invalid type: %s", type(blocks)) for block in pool: if not isinstance(block, ResourceBlock): raise TypeError("Invalid type: %s", type(block)) # Set downlink block if different. self.__assign_downlink(pool[0]) # set uplink blocks self.__assign_uplink(pool[1:]) # send intents self.__set_ports() def __assign_downlink(self, dl_block): """Set the downlink block. Set the downlink block. Notice how this is always called before assigning the uplink and that if the specified dl_block is already defined as uplink then the agent will automatically promote the block to downlink.""" # null operation if self.blocks and self.blocks[0] == dl_block: return # If LVAP is associated to a shared tenant, then reset LVAP if self._tenant and self._tenant.bssid_type == T_TYPE_SHARED: # check if tenant is available at target block base_bssid = self._tenant.get_prefix() net_bssid = generate_bssid(base_bssid, dl_block.hwaddr) # if not ignore request if net_bssid not in self._tenant.vaps: return # otherwise reset lvap self._tenant = None self.association_state = False self.authentication_state = False self._assoc_id = 0 self._lvap_bssid = net_bssid # save target block self.target_block = dl_block # clear downlink blocks for block in list(self._downlink.keys()): # this will add a new id to pending del self._downlink[block] # reset target block self.target_block = None # assign default port policy to downlink resource block, this will # trigger a send_add_lvap and a set_port (radio) message # this will add a new id to pending self._downlink[dl_block] = RadioPort(self, dl_block) def reset_downlink_port(self): """Reset downlink radio port.""" dl_block = self.blocks[0] self._downlink[dl_block] = RadioPort(self, dl_block) def __assign_uplink(self, ul_blocks): """Set the downlink blocks.""" # this will remove duplicate blocks ul_blocks = set(ul_blocks) # clear uplink blocks for block in list(self._uplink.keys()): # this will add a new id to pending del self._uplink[block] if self.blocks and self.blocks[0]: ul_blocks = ul_blocks - set([self.blocks[0]]) # assign uplink blocks for block in ul_blocks: # this will add a new id to pending self._uplink[block] = RadioPort(self, block) @property def wtp(self): """Return the wtp on which this LVAP is scheduled on.""" if self.blocks and self.blocks[0]: return self.blocks[0].radio return None @wtp.setter def wtp(self, wtp): """Assigns LVAP to new wtp.""" for block in wtp.supports: if self.blocks and block.channel != self.blocks[0].channel: continue if self.blocks and block.band != self.blocks[0].band: continue self.blocks = block self.block = [] def clear_lvap(self): """Clear all downlink blocks.""" # remove downlink for block in list(self._downlink.keys()): del self._downlink[block] for block in list(self._uplink.keys()): del self._uplink[block] # remove intent if self.poa_uuid: intent_server = RUNTIME.components[IntentServer.__module__] intent_server.remove_poa(self.poa_uuid) def to_dict(self): """ Return a JSON-serializable dictionary representing the LVAP """ return { 'addr': self.addr, 'net_bssid': self.net_bssid, 'lvap_bssid': self.lvap_bssid, 'ports': self.ports, 'wtp': self.wtp, 'blocks': self.blocks, 'downlink': [k for k in self._downlink.keys()], 'uplink': [k for k in self._uplink.keys()], 'supported_band': BANDS[self.supported_band], 'ssids': self.ssids, 'assoc_id': self.assoc_id, 'ssid': self.ssid, 'pending': self.pending, 'encap': self.encap, 'tx_samples': self.tx_samples, 'rx_samples': self.rx_samples, 'authentication_state': self.authentication_state, 'association_state': self.association_state } def __str__(self): accum = [] accum.append("addr ") accum.append(str(self.addr)) accum.append(" net_bssid ") accum.append(str(self.net_bssid)) accum.append(" lvap_bssid ") accum.append(str(self.lvap_bssid)) accum.append(" ssid ") accum.append(str(self.ssid)) if self.ssids: accum.append(" ssids [ ") accum.append(str(self.ssids[0])) for ssid in self.ssids[1:]: accum.append(", ") accum.append(str(ssid)) accum.append(" ]") accum.append(" assoc_id ") accum.append(str(self.assoc_id)) if self.association_state: accum.append(" ASSOC") if self.authentication_state: accum.append(" AUTH") return ''.join(accum) def __hash__(self): return hash(self.addr) def __eq__(self, other): if isinstance(other, LVAP): return self.addr == other.addr return False def __ne__(self, other): return not self.__eq__(other)
class LVAP(object): """ The EmPOWER Light Virtual Access Point One LVAP is created for every station probing the network (unless the MAC was blocked or if the MAC was not in the allowed list). An LVAP can be hosted by multiple WTPs. More preciselly an LVAP can be scheduled on one, and onyl one resource block on the downlink direction and on multiple resource blocks on the uplink direction. The downlink resource block is automatically also the default uplink resource block. The default uplink resource block is the resource block in charge of generating WiFi acks. Additional uplink resource blocks do not generate acks but can opportunistically receive and forward traffic. An unbound LVAP, i.e. an LVAP not hosted by any WTP, is not admissible. The association between LVAP and ResourceBlock(s) is called Port and models the parameters of the link between WTP and LVAP. The Port abstraction is used to specify control policies at the WTP level. Example are the rate control algorithm which cannot be managed at the controller level due to timing constraints. Due to implementation constraints uplink ports CAN be define but are ignored. Handover can be performed by setting the wtp property of an lvap object to another wtp, e.g.: lvap.wtp = new_wtp where lvap is an instance of LVAP object and new_wtp is an instance of wtp object. The controller takes care of removing the lvap from the old wtp and spawing a new lvap on the target wtp. Stats are cleared on handovers. The new_wtp must support the same channel and band of the old wtp otherwise no handover is performed. An handover can also be perfomed by assigning a valid RasourcePool to an LVAP block property. First build the intersection and available resource pools pool = wtp.supports & lvap.supports lvap.block = pool This results in one resource block in the pool to be configured as downlink resource block (and thus also as default uplink resource block) while the others will be configured as uplink resource blocks. A short cut is also provided: lvap.scheduled_on = block The operation above will assign the default port policy to the LVAP. To change the port configuration the downlink resource block property must be used: port = lvap.downlink[block] port.tx_power = 20 Where block is the ResourceBlock previously assigned. A new port configuration can be assigned in a single step with: lvap.downlink[block] = port where port is an instance of the RadioPort class. The last line will trigger a Port update message if the entry already exists. If the entry does not exists and there are no other entries in the structure, then a new entry will be created and an add LVAP massage will be sent before sending the port update message. If an entry is already available in the structure then a ValueError is raised. Applications can also manually delete resource blocks (this will trigger a del lvap message) and create new ones (this will trigger an add lvap message): del lvap.downlink[old_block] lvap.downlink[new_block] = port Attributes: addr: The client's MAC Address as an EtherAddress instance. tx_samples: array of 2-tuples of the TX'ed packets rx_samples: array of 2-tuples of the RX'ed packets net_bssid: The LVAP's MAC Address as an EtherAddress instance. This address is dynamically generated by the Access Controller. The BSSID is supposed to be unique in the entire network. lvap_bssid: The LVAP's active bssid as EtherAddress instance. ssids: The list of SSIDs to be broadcasted for this LVAP. assoc_id: association id for this LVAP (this cannot change after the LVAP has been spawned authentication_state: set to True if the LVAP has already completed the open authentication handshake . association_state: set to True if the LVAP has already completed the association handshake. ssid: The currently associated SSID. tx_samples: the transmitted packets rx_samples: the received packets block: the resource blocks to which this LVAP is assigned. downlink: the resource block assigned to this LVAP on the downlink direction. uplink: the resource block assigned to this LVAP on the uplink direction. scheduled_on: union of the downlink and uplink resource blocks """ def __init__(self, addr, net_bssid_addr, lvap_bssid_addr): # read only params self.addr = addr self.net_bssid = net_bssid_addr # lvap bssid, this is the bssid to which the client is currently # attached, it can only be updated as a result of an auth request # message self._lvap_bssid = lvap_bssid_addr # the following parameters are only updated upon RX of a lvap status # update message from an agent self.authentication_state = False self.association_state = False # the following parameters are only updated by the controller, which # will then dispatch an add lvap message in order to propagate the # change to the agent self._ssids = [] self._encap = None self._group = 6000 # the following parameters can be updated by both agent and # controller. The controller sets them when a client successfully # associate. The agent sets them upon disassociation. self._assoc_id = 0 self._tenant = None # only one block supported, default block points to this self._downlink = DownlinkPort() # multiple blocks supported, no port configuration supported self._uplink = UplinkPort() # counters self.tx_samples = [] self.rx_samples = [] # rates statistics self.rates = {} # virtual ports (VNFs) self.__ports = {} # downlink intent uuid self.poa_uuid = None # supported resource blocks self.supported = ResourcePool() def set_ports(self): """Set virtual ports. This method is called everytime an LVAP is moved to another WTP. More preciselly it is called every time an assignment to the downlink property is made. """ # Delete all outgoing virtual link and then remove the entire port if self.__ports: self.__ports[0].clear() del self.__ports[0] if not self.wtp: return self.__ports[0] = VirtualPortLvap(phy_port=self.wtp.port(), virtual_port_id=0, lvap=self) # set/update intent intent = { 'version': '1.0', 'dpid': self.__ports[0].dpid, 'port': self.__ports[0].ovs_port_id, 'hwaddr': self.addr } intent_server = RUNTIME.components[IntentServer.__module__] if self.poa_uuid: intent_server.update_poa(intent, self.poa_uuid) else: self.poa_uuid = intent_server.add_poa(intent) @property def ports(self): """Get the virtual ports.""" return self.__ports def refresh_lvap(self): """Send add lvap message on the selected port.""" for port in self.downlink.values(): port.block.radio.connection.send_add_lvap(port.lvap, port.block, self.downlink.SET_MASK) for port in self.uplink.values(): port.block.radio.connection.send_add_lvap(port.lvap, port.block, self.uplink.SET_MASK) @property def group(self): """Get the group.""" return self._group @group.setter def group(self, group): """ Set the group. """ if self._group == group: return self._group = group self.refresh_lvap() @property def encap(self): """Get the encap.""" return self._encap @encap.setter def encap(self, encap): """ Set the encap. """ if self._encap == encap: return self._encap = encap self.refresh_lvap() @property def assoc_id(self): """Get the assoc_id.""" return self._assoc_id @assoc_id.setter def assoc_id(self, assoc_id): """Set the assoc id.""" if self._assoc_id == assoc_id: return self._assoc_id = assoc_id self.refresh_lvap() @property def lvap_bssid(self): """Get the lvap_bssid.""" return self._lvap_bssid @lvap_bssid.setter def lvap_bssid(self, lvap_bssid): """Set the assoc id.""" if self._lvap_bssid == lvap_bssid: return self._lvap_bssid = lvap_bssid self.refresh_lvap() @property def ssids(self): """Get the ssids assigned to this LVAP.""" return self._ssids @ssids.setter def ssids(self, ssids): """Set the ssids assigned to this LVAP.""" if self._ssids == ssids: return self._ssids = ssids self.refresh_lvap() def set_ssids(self, ssids): """Set the ssids assigned to this LVAP without seding messages.""" self._ssids = ssids @property def ssid(self): """ Get the SSID assigned to this LVAP. """ if not self._tenant: return None return self._tenant.tenant_name @property def tenant(self): """ Get the tenant assigned to this LVAP. """ return self._tenant @tenant.setter def tenant(self, tenant): """ Set the SSID. """ if self._tenant == tenant: return self._tenant = tenant self.refresh_lvap() @property def downlink(self): """ Get the resource blocks assigned to this LVAP in the downlink. """ return self._downlink @downlink.setter def downlink(self, blocks): """Set downlink block. Set the downlink block. Must receive as input a single resource block. """ if isinstance(blocks, ResourcePool): pool = blocks elif isinstance(blocks, ResourceBlock): pool = ResourcePool() pool.add(blocks) else: raise TypeError("Invalid type: %s", type(blocks)) if len(pool) > 1: raise ValueError("Downlink, too many blocks (%u)", len(blocks)) current = ResourcePool(list(self._downlink.keys())) # Null operation, just return, but before re-send intent configuration if current == pool: self.set_ports() return # clear downlink blocks for block in list(self._downlink.keys()): del self._downlink[block] # downlink block default_block = pool.pop() # check if block is also in the uplink, if so remove it if default_block in self._uplink: del self._uplink[default_block] # If lvap is associated to a shared tenant. I need to reset the lvap # before moving it. if self._tenant and self._tenant.bssid_type == T_TYPE_SHARED: # check if tenant is available at target block base_bssid = self._tenant.get_prefix() net_bssid = generate_bssid(base_bssid, default_block.hwaddr) # if not ignore request if net_bssid not in self._tenant.vaps: LOG.error("VAP %s not found on tenant %s", net_bssid, self._tenant.tenant_name) self.set_ports() return # check if vap is available at target block if net_bssid != self._tenant.vaps[net_bssid].net_bssid: LOG.error("VAP %s not available at target block %s", net_bssid, default_block) self.set_ports() return # otherwise reset lvap self._tenant = None self.association_state = False self.authentication_state = False self._assoc_id = 0 self._lvap_bssid = net_bssid # assign default port policy to downlink resource block, this will # trigger a send_add_lvap and a set_port (radio) message self._downlink[default_block] = RadioPort(self, default_block) # set ports self.set_ports() @property def uplink(self): """ Get the resource blocks assigned to this LVAP in the uplink. """ return self._uplink @uplink.setter def uplink(self, blocks): """Set downlink block. Set the downlink block. Must receive as input a single resource block. """ if isinstance(blocks, ResourcePool): pool = blocks elif isinstance(blocks, ResourceBlock): pool = ResourcePool() pool.add(blocks) else: raise TypeError("Invalid type: %s", type(blocks)) if len(pool) > 5: raise ValueError("Downlink, too many blocks (%u)", len(blocks)) current = ResourcePool(list(self._uplink.keys())) # Null operation, just return if current == pool: return # make sure we are not overwriting a downlink block downlink = ResourcePool(list(self._downlink.keys())) pool = pool - downlink # clear uplink blocks for block in list(self._uplink.keys()): del self._uplink[block] # assign uplink blocks for block in pool: self._uplink[block] = RadioPort(self, block) @property def scheduled_on(self): """ Get the resource blocks assigned to this LVAP in the uplink. """ return ResourcePool( list(self._downlink.keys()) + list(self._uplink.keys())) @scheduled_on.setter def scheduled_on(self, blocks): """Assign default resource block to LVAP. Assign default resource block to LVAP. Accepts as input either a ResourcePool or a ResourceBlock. If the resource pool has more than one resource block then one random resource block is assigned as both downlink and default uplink. The remaining resource blocks are assigned as uplink only. Args: downlink: A ResourcePool or a ResourceBlock """ if not blocks: return if isinstance(blocks, ResourcePool): pool = blocks elif isinstance(blocks, ResourceBlock): pool = ResourcePool() pool.add(blocks) else: raise TypeError("Invalid type: %s", type(blocks)) # set downlink block self.downlink = pool.pop() # set uplink blocks self.uplink = pool @property def default_block(self): """Return the default block on which this LVAP is scheduled on.""" if not self.downlink: return None default_block = next(iter(self.downlink.keys())) return default_block @property def wtp(self): """Return the wtp on which this LVAP is scheduled on.""" if not self.default_block: return None return self.default_block.radio @wtp.setter def wtp(self, wtp): """Assigns LVAP to new wtp.""" if not self.default_block: return None matching = wtp.supports & self.supported self.scheduled_on = matching.pop() if matching else None def clear_downlink(self): """ Clear all downlink blocks.""" # remove downlink for block in list(self._downlink.keys()): del self._downlink[block] # remove intent if self.poa_uuid: intent_server = RUNTIME.components[IntentServer.__module__] intent_server.remove_poa(self.poa_uuid) def clear_uplink(self): """ Clear all downlink blocks.""" for block in list(self._uplink.keys()): del self._uplink[block] def to_dict(self): """ Return a JSON-serializable dictionary representing the LVAP """ return { 'addr': self.addr, 'net_bssid': self.net_bssid, 'lvap_bssid': self.lvap_bssid, 'ports': self.ports, 'wtp': self.wtp, 'scheduled_on': self.scheduled_on, 'downlink': [k for k in self._downlink.keys()], 'uplink': [k for k in self._uplink.keys()], 'supported': self.supported, 'ssids': self.ssids, 'assoc_id': self.assoc_id, 'ssid': self.ssid, 'encap': self.encap, 'tx_samples': self.tx_samples, 'rx_samples': self.rx_samples, 'rates': self.rates, 'authentication_state': self.authentication_state, 'association_state': self.association_state } def __str__(self): accum = [] accum.append("addr ") accum.append(str(self.addr)) accum.append(" net_bssid ") accum.append(str(self.net_bssid)) accum.append(" lvap_bssid ") accum.append(str(self.lvap_bssid)) accum.append(" ssid ") accum.append(str(self.ssid)) if self.ssids: accum.append(" ssids [ ") accum.append(str(self.ssids[0])) for ssid in self.ssids[1:]: accum.append(", ") accum.append(str(ssid)) accum.append(" ]") accum.append(" assoc_id ") accum.append(str(self.assoc_id)) if self.association_state: accum.append(" ASSOC") if self.authentication_state: accum.append(" AUTH") return ''.join(accum) def __hash__(self): return hash(self.addr) def __eq__(self, other): if isinstance(other, LVAP): return self.addr == other.addr return False def __ne__(self, other): return not self.__eq__(other)