= Il modulo p2p =
~+La classe '''{{{PartecipantNode}}}'''+~ rappresenta un potenziale nodo partecipante. Ha l'attributo {{{partecipant}}} che dice se effettivamente lo è (in un dato servizio P2P, cioè in una data {{{MapP2P}}})
~+La classe '''{{{MapP2P}}}'''+~ eredita dalla [[../ClasseMap|classe Map]] e ha come {{{dataclass}}} la {{{PartecipantNode}}}.
<
>
Il suo attributo pid viene specificato al momento della creazione, nel costruttore. E' l'ID del servizio P2P che questa mappa rappresenta.
<
>
Il metodo {{{partecipate}}} si segna che il nodo partecipa, cioè mette a {{{True}}} l'attributo {{{participant}}} delle istanze (una per livello) che rappresentano se stesso nella sua mappa.
<
>
Il metodo {{{node_del}}} viene overridato dalla classe base {{{Map}}} solo per rendere tale metodo una microfunc. Sarà dichiarato gestore dell'evento {{{NODE_DELETED}}} della Maproute del nodo.
<
>
Il metodo {{{me_changed}}} sostituisce il {{{me_change}}} della classe base {{{Map}}}. Rende tale metodo una microfunc ''con dispatcher''. Ha un nome diverso solo per accettare anche un altro parametro, il {{{old_me}}}, che non userà; questo per poterlo dichiarare come gestore dell'evento {{{ME_CHANGED}}} della Maproute del nodo.
== La classe P2P ==
La classe {{{P2P}}} è la base dalla quale erediteranno tutte le classi che implementano un servizio p2p.
Eredita a sua volta dalla classe {{{RPCDispatcher}}} (vedi il [[../ModuloRPC|modulo RPC]]). Viene istanziata passando le istanze di {{{Radar}}}, {{{Maproute}}}, e il {{{PID}}} (P2p ID) del servizio.
<
>
Nel suo costruttore si istanzia la {{{MapP2P}}} con le stesse dimensioni della Maproute e con il mio stesso NIP. Inoltre registra dei gestori (i metodi {{{node_del}}} e {{{me_changed}}} della classe {{{MapP2P}}} appena istanziata) per gli eventi {{{ME_CHANGED}}} e {{{NODE_DELETED}}} della Maproute. In questo modo si incarica di tenere sincronizzate queste due mappe.
<
>
Ha un attributo {{{partecipant}}} che valorizza inizialmente a {{{False}}}.
<
>
Dichiara metodi ''remotable'' il {{{partecipant_add}}} e il {{{msg_send}}}.
<
>
Essendo una derivata della classe {{{RPCDispatcher}}} richiama il suo costruttore indicando che la sua '''root_instance''' è se stessa ('''self''').
=== Partecipanti al servizio ===
Per dichiarare che il nodo corrente partecipa ad un servizio peer-to-peer uso il metodo '''{{{participate}}}''' dell'istanza di {{{P2P}}} relativa a quel servizio.
<
>
Questo per prima cosa valorizza a {{{True}}} l'attributo {{{participant}}}. Questo fa sì che in futuro il metodo {{{p2p_hook}}} (che resetta la mappa) richiami di nuovo automaticamente il metodo {{{participate}}}.
<
>
Il metodo poi richiama il metodo {{{participate}}} sulla mappa {{{mapP2P}}}.
<
>
Infine propaga l'informazione ai vicini attraverso il metodo remotable '''{{{partecipant_add}}}'''.
Il metodo {{{p2p.partecipant_add}}} (richiamato da un nodo remoto) si segna sulla mappa (se già non lo sapeva) che quel nodo partecipa, e propaga l'informazione ai vicini.
=== Richiesta di un servizio ===
Il metodo '''{{{h}}}''' serve per ricavare da una generica chiave un indirizzo NIP.
<
>
Nella classe base {{{P2P}}} la chiave è lo stesso NIP che viene restituito. Si tratta di una implementazione ovviamente poco utile.
<
>
Il metodo deve essere quindi ridefinito nel modulo che implementerà un certo servizio, con un algoritmo che partendo da una chiave significativa per quel servizio, restituisca un NIP.
<
>
Per esempio nel modulo ANDNA la chiave sarà la stringa {{{hostname}}}, per poter poi trovare il nip del register node di quell'{{{hostname}}}; nel modulo Coord la chiave sarà una tupla {{{(lvl, ip)}}}, per poter trovare il nip del coordinator node per il gnodo di livello {{{lvl}}} di quel {{{ip}}}; e così via.
Il NIP ottenuto con il metodo {{{h}}} potrebbe indicare un nodo non presente o che non partecipa a questo servizio P2P.
<
>
A questo punto va quindi utilizzato il metodo '''{{{H}}}''' per ottenere un indirizzo NIP valido.
<
>
Vediamo come funziona il metodo {{{H}}}.
<
>
Supponiamo che il nostro nodo abbia IP 1.2.3.4 ({{{self.mapp2p.me = [4,3,2,1]}}}) e che vogliamo trovare un IP valido per il IP 1.2.12.34 ({{{IP = [34,12,2,1]}}})
<
>
Partendo dal livello {{{l}}} più alto cerco nella mia mappa ({{{self.mapp2p}}}) un gnodo di livello {{{l}}} che sia un partecipante attivo. Il più vicino possibile all'identificativo richiesto, cioè {{{IP[l]}}}.
<
>
Partendo dal livello 3, in questo caso trovo di sicuro che il ID = 1 si presta bene, perché è lo stesso mio gnodo di livello 3 ed io sono un partecipante. Quindi passo al livello 2. Anche qui trovo che ID = 2 si presta bene, per lo stesso motivo.
<
>
Arrivato al livello 1 cerco un ID vicino a IP[1], che è 12. Posso vedere nella mia mappa se il gnodo 12, all'interno del ggnodo 2, all'interno del gggnodo 1, è partecipante al servizio. Se non lo è provo con 11, poi con 13, poi con 10, poi con 14, e così via.
<
>
Se arrivo ad un gnodo partecipante, mettiamo ad esempio 5, che è diverso da quello a cui appartengo io ({{{self.mapp2p.me[1] = 3}}}) allora non posso scendere ad un dettaglio più basso, perché nella mia mappa non ho informazioni sui nodi di quel gnodo. Quindi interrompo qui.
<
>
In questo caso, quindi, il valore ritornato da {{{H([34,12,2,1])}}} sarà la lista {{{[None,5,2,1]}}}. Il valore {{{None}}} all'indice 0 mi dice che non ho dettagli a quel livello.
Attenzione ora. Io so che devo chiedere informazioni o servizi al nodo 1.2.12.34, oppure al nodo partecipante più "vicino" (per vicino si intende qui in senso di numeri, concetto molto astratto). So che il gnodo di livello 1 e indirizzo 1.2.5 contiene alcuni nodi partecipanti, ma non posso sapere quale di questi sia il più "vicino" al 1.2.12.34.
<
>
Il mio messaggio deve quindi giungere al primo border node che incontro in quel gnodo. Ma il protocollo TCP/IP non mi consente di specificare una tale destinazione. Per questo motivo ho bisogno di un metodo che instradi i miei pacchetti passo passo verso il border node di cui parlavo.
<
>
Vediamo come si realizza questo.
<
>
Il metodo '''{{{msg_send}}}''' ha come argomenti il mittente del messaggio ({{{sender_nip=[4,3,2,1]}}}) il destinatario "perfetto" del messaggio ({{{hip=[34,12,2,1]}}}) e il messaggio ({{{msg}}}) che è una tupla {{{(func_name, args)}}}.
<
>
Per prima cosa trasforma il {{{hip}}} con il metodo {{{H}}} sulla base delle conoscenze di questo nodo (vedi sopra). In questo caso lo trasforma in {{{hip=[None,5,2,1]}}}
<
>
Poi usa il metodo '''{{{neigh_get}}}'''. Questo, a partire da un {{{hip}}} (che potrebbe avere i livelli più bassi a {{{None}}}) restituisce l'oggetto {{{Neigh}}} del vicino da usare come gateway. Trovato questo vicino richiama su di lui lo stesso metodo {{{msg_send}}}, '''che è remotable''', passando {{{sender_nip}}}, il nuovo {{{hip}}} e il messaggio {{{msg}}}. '''''TODO''': l'hip passato al metodo remoto è quello con dentro i possibili None. Questo non causa problemi la prossima volta che si esegue H? Forse andrebbe passato l'hip originale?''
<
>
''Notare'': Il metodo {{{neigh_get}}} mi dice il mio vicino migliore per raggiungere il gnodo 1.2.5. Questo vicino non è detto che sia partecipante al servizio. Nonostante ciò, di sicuro il vicino conosce l'esistenza di questo servizio (vedi più sotto la descrizione del metodo {{{p2p_hook}}} della classe {{{P2PAll}}}) quindi la chiamata '''"n.ntkd.p2p.PID_"+str(self.mapp2p.pid)+".msg_send(sender_nip, hip, msg)"''' funziona comunque.
<
>
Dopo alcuni passi il metodo {{{msg_send}}} verrà richiamato proprio sull'host destinatario (quello perfetto o il più vicino partecipante). Dopo aver richiamato il metodo {{{H}}} il nodo si accorge di essere lui stesso il destinatario e che quindi deve eseguire l'azione richiesta.
Per eseguire l'azione richiesta richiama il metodo '''{{{msg_exec}}}'''. Questo usa il messaggio (la tupla func_name e args) con il suo metodo {{{dispatch}}}, essendo la classe {{{P2P}}} una derivata di {{{RPCDispatcher}}}. In questo modo viene eseguito il metodo remotable indicato nella chiamata del servizio.
<
>
Il valore restituito dal metodo remotable indicato nella chiamata al servizio viene a sua volta restituito dal metodo {{{msg_exec}}}; poi viene a sua volta restituito dal metodo remotable {{{msg_send}}} chiamato nell'ultimo passo dell'instradamento; viene poi restituito a ritroso dallo stesso metodo chiamato nei vari passi dell'instradamento, fino ad essere restituito dal metodo {{{msg_send}}} chiamato in modo locale sul nodo mittente. '''''TODO''': Domanda: non sarebbe più performante e meno pesante per l'intera rete se la risposta avvenisse in modo diretto dal destinatario al chiamante con una connessione diretta visto che ora il destinatario ha l'IP preciso del mittente nell'argomento "sender_nip"?
<
>
Inoltre, anche nel caso in cui l'indirizzo restituito dal metodo {{{H}}} fosse completo, senza parti uguali a {{{None}}}, sarebbe meglio usare subito un TCPClient verso quel IP.''
La chiamata a {{{msg_send}}} non viene fatta direttamente, si vuole fare uso del meccanismo del {{{FakeRmt}}} (vedi il [[../ModuloRPC|modulo RPC]]). Si usa quindi il metodo '''{{{peer}}}''' per ottenere una istanza di {{{FakeRmt}}} che sia a conoscenza delle informazioni da passare al metodo {{{msg_send}}}.
<
>
Il metodo {{{peer}}} si può richiamare specificando l'argomento keyword {{{key}}} oppure direttamente l'argomento keyword {{{hip}}}. Di norma si usa specificando la chiave da cui calcolarsi l'hip a cui rivolgersi. '''''TODO''': anche in questo caso a partire da key si eseguono sia il metodo h sia il metodo H. Invece dovrebbe essere eseguito solo h per avere l'hip perfetto da passare al metodo msg_send. Giusto?''
<
>
Ad esempio il servizio {{{Coordinator}}} si usa così:
<
>
{{{ co2 = self.coordnode.peer(key = (lvl+1, newnip))}}}
<
>
{{{ newnip = co2.going_in(lvl)}}}
<
>
{{{ co2.close()}}}
'''''TODO''': Fino a che punto è stato implementato l'algoritmo descritto nel documento Ntk_p2p_over_ntk.pdf ?
<
>
Mi pare di non vedere nel codice l'implementazione dei 31 nodi backup, né del timeout di rimozione dei dati registrati, né dell'ingresso di un nuovo nodo nella rete che automaticamente sostituisce il precedente come più vicino all'hip perfetto.
<
>
Queste implementazioni si possono fare a livello della classe P2P o vanno per forza fatte sulle singole classi derivate di un servizio specifico?''
== La classe P2PAll ==
La classe {{{P2PAll}}} gestisce tutti i servizi p2p registrati.
<
>
Ha come membro un dizionario ({{{ self.service = {} }}}) nel quale memorizzerà tutte le istanze di {{{P2P}}} che si creerà per i servizi noti al nodo (non solo quelli in cui partecipa).
Quando un nodo ha completato un ''hook'' alla rete, ad esempio dopo la fase di startup ('''''TODO''': Anche questo gestore dell'evento HOOKED dovrebbe essere registrato solo dopo il primo hook che avviene nella fase di startup solo per l'ativazione delle interfacce di rete. Vedi la [[../NtkNodeStartup|fase di startup]]'') viene richiamato il metodo microfunc {{{p2p_hook}}}.
<
>
Questo metodo cerca un vicino, possibilmente del nostro stesso gnodo di livello 1, oppure del nostro stesso gnodo di livello 2, e così via. Al vicino "più vicino" (come gnodo) che trova, richiede l'elenco dei servizi noti e le reative mappe {{{MapP2P}}} usando il metodo remotable {{{pid_getall}}}.
<
>
Questo metodo restituisce una lista di tuple. Ogni tupla ha il PID del servizio e una struttura dati che descrive la mappa {{{MapP2P}}} relativa a quel servizio (vedi il metodo {{{map_data_pack}}} della [[../ClasseMap|classe Map]]).
<
>
Una volta ottenuta, per ogni servizio noto, la struttura descrivente la mappa {{{MapP2P}}} relativa a quel servizio, ne fa la ''copia'' sulla propria mappa {{{MapP2P}}} relativa a quel servizio (vedi il metodo {{{map_data_merge}}} della [[../ClasseMap|classe Map]]).
<
>
Notare che il servizio potrebbe non essere noto al nodo. Potrebbe trattarsi di un servizio opzionale di cui il nodo corrente non conosce l'uso, né i servizi supportati, né l'algoritmo di associazione key -> hip, e così via.
<
>
In questo caso non ci sarà ancora l'istanza della classe derivata di {{{P2P}}} relativa a questo servizio nel dizionario {{{service}}}. In questo caso viene creata al volo una istanza della classe base {{{P2P}}}. Di default questa istanza dirà che il nodo non è partecipante al servizio ({{{P2P.partecipant = False}}}). Questo è comunque sufficiente a inoltrare, con il meccanismo del metodo {{{msg_send}}} della classe {{{P2P}}} descritto sopra, i messaggi tra un sender e un hip.
<
>
Poi esegue il {{{P2P.participate}}} sulle istanze dei servizi a cui il nodo corrente è interessato. Il nodo dichiara di essere interessato ad un servizio nello stesso momento in cui ''registra'' la classe che implementa il servizio. La registrazione viene fatta con il metodo '''{{{p2p_register}}}''' della classe {{{P2PAll}}} e avviene di norma (ad esempio vedi il [[../ModuloCoord|modulo Coord]]) nel costruttore della classe derivata di {{{P2P}}}.
<
>
Infine emette il segnale {{{P2P_HOOKED}}}.