Săriți la conținut

Blog ·

Un null care a trecut prin sită

Fiecare container real spunea că acoperirea copiei de siguranță nu se poate stabili. Configurația se citea bine. Pe fir era null, în schemă lipsea cheia.

De Provibr

Construiam fila de componente pentru containere când o verificare în browser a produs o propoziție pe care nu ar fi trebuit să o producă niciodată: configurația acestui container nu a putut fi citită, așa că nu s-a stabilit ce cuprinde o copie de siguranță.

Configurația fusese citită. Se afla în răspuns, întreagă, cu fiecare punct de montare în ea. Ce eșuase era propria noastră analiză a ei, cu un strat mai încolo, iar mesajul arunca vina pe partea greșită a firului.

Mersul înapoi de acolo a durat o vreme, fiindcă nu era nimic de la care să pornim înapoi. Nicio excepție, nicio linie de jurnal, niciun test picat. Doar o propoziție liniștită pe un ecran, care spunea că un lucru nu fusese măsurat.

O cheie absentă și un null nu sunt același lucru

Agentul construiește rândurile punctelor de montare în Rust cu serde_json::json!, iar unul dintre câmpuri este un Option<String>: calea dinăuntrul containerului, care vine din mp=/data din configurație.

Un Option care este None se serializează acolo ca null. Nu ca o cheie lipsă. Acesta este comportamentul corect și documentat și este ceea ce ar aștepta oricine citește macroul acela, dacă s-ar gândi o clipă la el, ceea ce nu făcuse nimeni.

De cealaltă parte, schema spunea .optional(), iar în zod un optional simplu înseamnă că respectiva cheie poate să lipsească. Nu înseamnă că valoarea poate fi null. Sunt două afirmații diferite despre JSON, iar noi o scrisesem pe una și o trimiteam pe cealaltă.

Câmpul care a făcut lucrul acesta să pornească la fiecare container în parte este cel care, structural, este întotdeauna gol. Fiecare configurație de container are o linie rootfs, iar un rootfs prin definiție nu are mp=, fiindcă este montat la rădăcină. Așa că primul rând din fiecare observație de la fiecare container real purta "mountPath": null.

Un singur câmp stricat înseamnă zero date

Dacă eșecul s-ar fi mărginit la câmpul acela, ar fi fost un defect de suprafață. Nu a fost, iar motivul ține de forma analizei și nu de forma erorii.

Validăm întreaga observație ca pe un singur obiect. Un safeParse pe un obiect eșuează ca întreg; nu există rezultat parțial. Așa că un singur câmp respins însemna nicio listă de montări, nicio clasificare a acoperirii și nicio previzualizare a ceea ce ar înlocui o restaurare. Tot ce venea mai jos nu vedea nimic, iar tot ce vine mai jos este scris ca să fie sincer despre nimic.

Sinceritatea aceasta este voită și este motivul pentru care produsul nu vă spune că o copie de siguranță este completă atunci când nu s-a uitat. O configurație de container fără un rootfs care se poate citi este raportată ca necunoscută și nu ca deplină, tocmai pentru ca o măsurătoare goală să nu poată fi niciodată îmbrăcată în una bună.

Aici garda a lucrat exact așa cum fusese proiectată și a produs o propoziție care, în termenii ei, era adevărată: nu se stabilise nimic. Minciuna era cu un nivel mai sus, în susținerea despre de ce.

Nimic nu era roșu, iar motivul este incomod

Asta a plecat în producție cu runda care a introdus acoperirea copiilor de siguranță pentru containere, iar datele de test din trei runde au trecut peste același cod fără ca nimic să iasă roșu.

Motivul este că semințele fuseseră scrise de mână. Ca să pui la lucru tabelul de acoperire fără un Proxmox adevărat, scrii un rând care arată ca ceea ce trimite agentul, iar ceea ce scrii când vrei să spui acesta nu are cale este să lași cheia afară. Ceea ce se analizează fără probleme. Trei runde de date de test au codificat astfel presupunerea schemei și nu comportamentul agentului, iar fiecare dintre ele era verde.

Nu există un hipervizor adevărat în buclă care să îndrepte asta. Proxmoxul la care avem acces este mașina de producție a cuiva, iar niciun agent al nostru nu ajunge pe ea. Așa că singura descriere a firului față de care ar fi putut fi verificate amândouă părțile era sursa Rust, iar nimeni nu o citea.

Garda citește producătorul în loc să îl copieze

Reparația este un singur câmp. Partea interesantă este testul de lângă ea.

El deschide lxc.rs, găsește funcția care construiește rândurile, scoate numele cheilor din blocul json! și apoi susține două lucruri. Întâi, că mulțimea cheilor pe care le trimite agentul este exact mulțimea cheilor pe care le cunoaște schema, așa încât niciuna dintre părți nu poate crește singură un câmp. Al doilea, și acesta este cel care contează, că un rând în care fiecare câmp opțional este null trece prin sită.

Al doilea test își construiește rândul din cheile pe care tocmai le-a citit, nu dintr-o listă scrisă în fișierul de test. Un câmp opțional nou, adăugat cu un .optional() simplu, este roșu în ziua în care este adăugat, fără să fie nevoie ca cineva să își amintească articolul acesta.

Există un al treilea test pentru cealaltă direcție: un rând de la un agent mai vechi, care nu poartă deloc câmpurile mai noi, se analizează în continuare. Amândouă formele sunt reale, amândouă se află în bază de date, iar o reparație care ar fi acceptat-o doar pe cea nouă ar fi stricat rândurile vechi arătând totodată corect.

Reparația însăși este un preprocess care duce null în undefined, și nu un .nullable(). Aceasta este o alegere voită despre tipul derivat: cu nullable, tipul devine string | null | undefined, iar fiecare cititor capătă o a treia stare care nu înseamnă nimic pentru el. Cu preprocess, tipul rămâne cum era, cei patru cititori păstrează codul pe care îl aveau deja, iar toleranța de pe fir este exprimată într-un singur loc.

Două greșeli în măsurătoare, nu în produs

Stabilirea faptului că reparația a mers a produs două rezultate verzi false ale ei, iar acestea sunt jumătatea mai ușor de dus mai departe a rundei.

Prima: o schemă scrisă cu .strict() ne-a respins în tăcere propria sămânță, iar ecranul a rămas gol în loc să fie greșit. Un ecran gol în timpul unei reparații arată exact ca un ecran care încă nu s-a încărcat.

A doua este mai rea și este o capcană cunoscută aici, în care am intrat oricum. Tabelul copiilor de siguranță trăiește într-un panou care este întotdeauna montat în document, fiindcă așa se face să meargă întoarcerea focalizării dintr-un dialog. Așa că o verificare care citește tabelul cu un selector de interogare îl citește fie că este deschis, fie că nu. Susținerea noastră că două file spuneau același lucru trecea pe două tabele care erau amândouă goale și amândouă nevăzute.

Amândouă au fost închise la fel: cereți ca dialogul să fie cu adevărat deschis și puneți o limită de jos pe numărul de rânduri înainte să le comparați. O comparație a nimicului cu nimicul nu este o comparație.

Ce am consemna

  1. O cheie absentă și un null sunt afirmații diferite, iar serializatoarele JSON aleg în locul vostru.
  2. O schemă care acceptă doar o cheie lipsă nu și-a citit niciodată producătorul.
  3. Validarea întregului obiect preface un singur câmp stricat în zero date, ceea ce este proiectarea corectă și un eșec anevoios de depanat.
  4. Cel mai primejdios mesaj de eroare este unul liniștit. Al nostru era sincer despre rezultat și greșit despre cauză.
  5. Datele de test scrise de mână codifică presupunerile voastre, inclusiv pe cea pe care sunteți pe cale să o greșiți.
  6. Lăsați garda să citească producătorul. O listă de chei copiată într-un test alunecă; una scoasă din sursă nu poate.
  7. Testați explicit rândul cu totul null, fiindcă acela este rândul pe care producătorul vostru adevărat îl trimite primul.
  8. Acceptați și forma veche. Rândurile scrise de agenți mai vechi sunt în continuare în bază de date.
  9. Fiți îngăduitori pe fir, nu în tip. Lărgiți la graniță și lăsați cititorii în pace.
  10. Un panou întotdeauna montat face o verificare de DOM verde în gol. Susțineți că este deschis și că are rânduri.

Ca să fie limpede ce este aceasta: defectul se afla în acoperirea fiecărui container real de la runda care a livrat copiile de siguranță pentru containere, găsit de o verificare în browser și nu de vreo suită, reparat cu un singur preprocess și păzit de un test care citește sursa Rust; runda în care a fost găsit a mutat modulul de teste pentru containere de la treizeci și cinci de teste la treizeci și nouă, cu musl și formatarea curate, și s-a încheiat cu pachetele partajate la 5347 verde, gateway-ul la 1467, uneltele de tip stub la 489 și suita web la 4593, fără eșecuri nicăieri, și cu cincizeci de verificări în browser verzi în două limbi, fără constatări de accesibilitate. Nedovedite: nimic din toate acestea nu a rulat față de un Proxmox adevărat, așa că forma firului este stabilită din sursă și nu dintr-un răspuns viu, iar lanțul în funcțiune a fost pus la încercare față de propriul nostru stub.

Din munca de platformă la Provibr. Pentru ce este tabelul de acoperire se află în ce nu conține o copie de siguranță, runda care a citit prima dată configurația unui container se află în reinstalarea unui container este o înlocuire, iar o altă verificare care era verde fiindcă nu ajunsese niciodată acolo se află în verde fiindcă nu putea ajunge acolo.

Începeți cu o gazdă și un agent

Creați un cont, instalați agentul cu o singură comandă și conectați primul dumneavoastră host Proxmox. Perioada de probă durează 14 zile și nu cere date de plată.