def create(self, link_type, interface_a, interface_b, port_a, port_b, **kwargs): from peering_coord.api.client_connection import (ClientRegistry, create_link_update) link = super().create(link_type=link_type, interface_a=interface_a, interface_b=interface_b, port_a=port_a, port_b=port_b, **kwargs) update = create_link_update(LinkUpdate.Type.CREATE, link_type=link_type, local_interface=interface_a, local_port=port_a, remote_interface=interface_b, remote_port=port_b) ClientRegistry.send_link_update(interface_a.peering_client.asys.asn, update) update = create_link_update(LinkUpdate.Type.CREATE, link_type=link_type, local_interface=interface_b, local_port=port_b, remote_interface=interface_a, remote_port=port_a) ClientRegistry.send_link_update(interface_b.peering_client.asys.asn, update) return link
def is_connected(self) -> bool: """Returns whether the client is connected to the coordinator.""" from peering_coord.api.client_connection import ClientRegistry connections = ClientRegistry.get_clients(self.asys.asn) if connections is None: return False else: return self.name in connections.connections
def _create_links(vlan: VLAN, as_a: AS, as_b: AS): """Create links between all interfaces of `as_a` and `as_b` in `vlan`. The link type is determined from the AS types. """ # Figure out which link type to use. if as_a.is_core and as_b.is_core: link_type = Link.Type.CORE elif not as_a.is_core and not as_b.is_core: link_type = Link.Type.PEERING elif as_a.isd == as_b.isd: link_type = Link.Type.PROVIDER if not as_a.is_core and as_b.is_core: as_a, as_b = as_b, as_a else: error = AsyncError() error.code = AsyncError.Code.LINK_CREATION_FAILED error.message = "Cannot create a link between ASes {} and {} of incompatible type.".format( as_a, as_b) ClientRegistry.send_async_error(as_a.asn, error) ClientRegistry.send_async_error(as_b.asn, error) return for interface_a in as_a.query_interfaces().filter(vlan=vlan).all(): for interface_b in as_b.query_interfaces().filter(vlan=vlan).all(): port_a = port_b = None try: port_a = interface_a.get_unused_port() except Interface.NoUnusedPorts: error = AsyncError() error.code = AsyncError.Code.LINK_CREATION_FAILED error.message = "Allocated port range is exhausted on interface {}.".format( interface_a) ClientRegistry.send_async_error(as_a.asn, error) try: port_b = interface_b.get_unused_port() except Interface.NoUnusedPorts: error = AsyncError() error.code = AsyncError.Code.LINK_CREATION_FAILED error.message = "Allocated port range is exhausted on interface {}.".format( interface_b) ClientRegistry.send_async_error(as_b.asn, error) if port_a and port_b: Link.objects.create(link_type, interface_a=interface_a, interface_b=interface_b, port_a=port_a, port_b=port_b)
def delete_link_hook(sender, instance, using, **kwargs): from peering_coord.api.client_connection import (ClientRegistry, create_link_update) update = create_link_update(LinkUpdate.Type.DESTROY, link_type=instance.link_type, local_interface=instance.interface_a, local_port=instance.port_a, remote_interface=instance.interface_b, remote_port=instance.port_b) ClientRegistry.send_link_update( instance.interface_a.peering_client.asys.asn, update) update = create_link_update(LinkUpdate.Type.DESTROY, link_type=instance.link_type, local_interface=instance.interface_b, local_port=instance.port_b, remote_interface=instance.interface_a, remote_port=instance.port_a) ClientRegistry.send_link_update( instance.interface_b.peering_client.asys.asn, update)
def _assert_policy_write_permission(context, asn: ASN, client: str, vlan: Optional[str] = None): """Helper function for checking whether a client is allowed to alter the pering policies. Triggers an exception to abort the RPC if the client does not have sufficient permissions. :param context: gRPC service context :param asn: AS the client belongs to. :param client: Peering client name. :param vlan: VLAN for which write permissions are checked. If None, checks if the client has write access to every VLAN. """ try: if not ClientRegistry.has_policy_write_permissions(asn, client, vlan): context.abort(grpc.StatusCode.PERMISSION_DENIED, "Insufficient permissions") except: context.abort(grpc.StatusCode.PERMISSION_DENIED, "Insufficient permissions")
def StreamChannel(self, request_iterator, context): """Server side of the persistent bidirectional gRPC stream peering clients maintain with the coordinator. Since bidirectional gRPC streams in Python use blocking generators for sending and receiving, an additional thread just for reading request from the stream is created for every connection. The request listener threads forwards received requests to the main thread handling the connection via the associated ClientConnection object. """ asn, client_name = get_client_from_metadata( context.invocation_metadata()) asn = ASN(asn) # Register the connection try: conn = ClientRegistry.createConnection(asn, client_name) except KeyError as e: context.abort(grpc.StatusCode.NOT_FOUND, str(e)) except ClientConnections.AlreadyConnected as e: context.abort(grpc.StatusCode.ALREADY_EXISTS, str(e)) except: context.abort(grpc.StatusCode.INTERNAL, "Internal error") # Enqueue link create messages for all existing links. try: client = PeeringClient.objects.get(asys__asn=asn, name=client_name) for interface in Interface.objects.filter( peering_client=client).all(): for link in interface.query_links().all(): if link.interface_a == interface: update = create_link_update( peering_pb2.LinkUpdate.Type.CREATE, link_type=link.link_type, local_interface=link.interface_a, local_port=link.port_a, remote_interface=link.interface_b, remote_port=link.port_b) else: update = create_link_update( peering_pb2.LinkUpdate.Type.CREATE, link_type=link.link_type, local_interface=link.interface_b, local_port=link.port_b, remote_interface=link.interface_a, remote_port=link.port_a) conn.send_link_update(update) except: ClientRegistry.destroyConnection(conn) raise # Launch a new thread to listen for requests from the client. def stream_listener(): try: for request in request_iterator: conn.stream_request_received(request) except grpc.RpcError: pass finally: conn.request_stream_closed() listener = threading.Thread( target=stream_listener, name="gRPC stream listener for {}-{}".format(asn, client_name)) listener.start() # Run the event loop to process requests and generate responses. try: for response in conn.run(): yield response finally: ClientRegistry.destroyConnection(conn) listener.join()
def count_connected_clients(self) -> int: """Returns the number of active peering clients.""" from peering_coord.api.client_connection import ClientRegistry return len(ClientRegistry.get_clients(self.asn) or [])
def testArbitration(self): stub = peering_pb2_grpc.PeeringStub(self.channel) default_call_cred = [(ASN_HEADER_KEY, "ff00:0:0"), (CLIENT_NAME_HEADER_KEY, "default")] backup_call_cred = [(ASN_HEADER_KEY, "ff00:0:0"), (CLIENT_NAME_HEADER_KEY, "backup")] default_request_queue = queue.Queue() backup_request_queue = queue.Queue() self.assertIsNone(ClientRegistry.get_clients(ASN("ff00:0:0"))) # Connect the backup client backup_channel = stub.StreamChannel(iter(backup_request_queue.get, None), metadata=backup_call_cred) request = peering_pb2.StreamMessageRequest() request.arbitration.election_id = 0 backup_request_queue.put(request) response = next(backup_channel) self.assertEqual(response.arbitration.vlan, "prod") self.assertEqual(response.arbitration.status, peering_pb2.ArbitrationUpdate.Status.PRIMARY) clients = ClientRegistry.get_clients(ASN("ff00:0:0")) self.assertEqual(len(clients.connections), 1) self.assertTrue(clients.is_primary_client("backup", "prod")) self.assertFalse(clients.is_primary_client("default", "test")) # Connect the default client default_channel = stub.StreamChannel(iter(default_request_queue.get, None), metadata=default_call_cred) request = peering_pb2.StreamMessageRequest() request.arbitration.election_id = 100 default_request_queue.put(request) response = next(default_channel) self.assertEqual(response.arbitration.vlan, "prod") self.assertEqual(response.arbitration.status, peering_pb2.ArbitrationUpdate.Status.PRIMARY) response = next(default_channel) self.assertEqual(response.arbitration.vlan, "test") self.assertEqual(response.arbitration.status, peering_pb2.ArbitrationUpdate.Status.PRIMARY) response = next(backup_channel) self.assertEqual(response.arbitration.vlan, "prod") self.assertEqual(response.arbitration.status, peering_pb2.ArbitrationUpdate.Status.NOT_PRIMARY) self.assertEqual(len(clients.connections), 2) self.assertFalse(clients.is_primary_client("backup", "prod")) self.assertTrue(clients.is_primary_client("default", "prod")) self.assertTrue(clients.is_primary_client("default", "test")) # Disconnect the default client default_request_queue.put(None) for response in default_channel: self.assertTrue(False, "Unexpected additional messages on stream") response = next(backup_channel) self.assertEqual(response.arbitration.status, peering_pb2.ArbitrationUpdate.Status.PRIMARY) self.assertEqual(len(clients.connections), 1) self.assertTrue(clients.is_primary_client("backup", "prod")) self.assertFalse(clients.is_primary_client("default", "test")) # Disconnect the backup client backup_request_queue.put(None) for response in backup_channel: self.assertTrue(False, "Unexpected additional messages on stream") self.assertIsNone(ClientRegistry.get_clients(ASN("ff00:0:0")))
def delete_interface_hook(sender, instance, using, **kwargs): from peering_coord.api.client_connection import ClientRegistry ClientRegistry.remove_interface(instance.peering_client.asys.asn, instance.peering_client.name, instance.vlan.name)
def delete_peering_client_hook(sender, instance, using, **kwargs): from peering_coord.api.client_connection import ClientRegistry ClientRegistry.remove_client(instance.asn.asys, instance.name)