Mitä opin testaamalla 200 000 riviä infrastruktuurikoodia

Mitä opin testaamalla 200 000 riviä infrastruktuurikoodia

Lähestymistapa IaC (Infrastruktuuri koodina) ei koostu vain arkistoon tallennetusta koodista, vaan myös tätä koodia ympäröivistä ihmisistä ja prosesseista. Onko mahdollista käyttää uudelleen lähestymistapoja ohjelmistokehityksestä infrastruktuurin hallintaan ja kuvaukseen? Tämä idea olisi hyvä pitää mielessä, kun luet artikkelia.

Englanti versio

Tämä on transkriptio minun esitykset päälle DevopsConf 2019.

Diat ja videot

Infrastruktuuri bash-historiana

Mitä opin testaamalla 200 000 riviä infrastruktuurikoodia

Oletetaan, että tulet uuteen projektiin ja he sanovat sinulle: "Meillä on Infrastruktuuri koodina". Todellisuudessa se käy ilmi Infrastruktuuri bash-historiana tai esimerkiksi Dokumentaatio bash-historiana. Tämä on hyvin todellinen tilanne, esimerkiksi Denis Lysenko kuvaili samanlaista tapausta puheessaan Kuinka korvata koko infrastruktuuri ja alkaa nukkua sikeästi, hän kertoi, kuinka he saivat projektille yhtenäisen infrastruktuurin bash historiasta.

Tietyllä halulla voimme sanoa sen Infrastruktuuri bash-historiana tämä on kuin koodi:

  1. toistettavuus: Voit ottaa bash-historian, suorittaa komennot sieltä ja saatat muuten saada toimivan konfiguraation tulosteena.
  2. versiointi: tiedät kuka tuli sisään ja mitä he tekivät, taaskaan ei ole tosiasia, että tämä johtaa toimivaan kokoonpanoon uloskäynnissä.
  3. historia: tarina siitä, kuka teki mitä. vain et voi käyttää sitä, jos menetät palvelimen.

Mitä tehdä?

Infrastruktuuri koodina

Mitä opin testaamalla 200 000 riviä infrastruktuurikoodia

Jopa niin outo tapaus kuin Infrastruktuuri bash-historiana voit vetää sitä korvista Infrastruktuuri koodina, mutta kun haluamme tehdä jotain monimutkaisempaa kuin vanha kunnon LAMP-palvelin, tulemme siihen tulokseen, että tätä koodia on jotenkin muokattava, muutettava, parannettava. Seuraavaksi haluamme tarkastella yhtäläisyyksiä Infrastruktuuri koodina ja ohjelmistokehitys.

KUIVA.

Mitä opin testaamalla 200 000 riviä infrastruktuurikoodia

Eräässä tallennusjärjestelmän kehitysprojektissa oli osatehtävä määritä SDS säännöllisesti: julkaisemme uuden julkaisun - se on julkaistava lisätestausta varten. Tehtävä on erittäin yksinkertainen:

  • kirjaudu sisään tänne ssh:n kautta ja suorita komento.
  • kopioi tiedosto sinne.
  • korjaa asetukset tästä.
  • aloita palvelu siellä
  • ...
  • VOITTO!

Kuvatulle logiikalle bash on enemmän kuin tarpeeksi, varsinkin projektin alkuvaiheessa, kun se on vasta alkamassa. Tämä ei ole paha, että käytät bashia, mutta ajan mittaan tulee pyyntöjä ottaa käyttöön jotain samanlaista, mutta hieman erilaista. Ensimmäinen asia, joka tulee mieleen, on copy-paste. Ja nyt meillä on jo kaksi hyvin samanlaista skriptiä, jotka tekevät melkein saman asian. Ajan myötä komentosarjojen määrä kasvoi, ja kohtasimme sen tosiasian, että asennuksen käyttöönotossa on tietty liiketoimintalogiikka, joka on synkronoitava eri komentosarjojen välillä, tämä on melko monimutkaista.

Mitä opin testaamalla 200 000 riviä infrastruktuurikoodia

Osoittautuu, että on olemassa sellainen käytäntö kuin D.R.Y. (Älä toista itseäsi). Ideana on käyttää olemassa olevaa koodia uudelleen. Se kuulostaa yksinkertaiselta, mutta emme päässeet tähän heti. Meidän tapauksessamme se oli banaali idea: erottaa konfiguraatiot skripteistä. Nuo. liiketoimintalogiikka siitä, kuinka asennus otetaan käyttöön erikseen, konfiguraatiot erikseen.

S.O.L.I.D. CFM:lle

Mitä opin testaamalla 200 000 riviä infrastruktuurikoodia

Ajan myötä projekti kasvoi ja luonnollinen jatko oli Ansiblen syntyminen. Pääsyy sen esiintymiseen on se, että tiimissä on asiantuntemusta ja että bashia ei ole suunniteltu monimutkaiseen logiikkaan. Ansible alkoi sisältää myös monimutkaista logiikkaa. Jotta monimutkainen logiikka ei muuttuisi kaaokseksi, ohjelmistokehityksessä on olemassa periaatteet koodin järjestämiselle S.O.L.I.D. Myös esimerkiksi Grigory Petrov esitti raportissa ”Miksi IT-asiantuntija tarvitsee henkilökohtaisen brändin” kysymyksen, että ihminen on suunniteltu niin, että hänen on helpompi toimia joidenkin sosiaalisten kokonaisuuksien kanssa, ohjelmistokehityksessä nämä ovat esineitä. Jos yhdistämme nämä kaksi ideaa ja jatkamme niiden kehittämistä, huomaamme, että voimme myös käyttää niitä S.O.L.I.D. jotta tätä logiikkaa olisi helpompi ylläpitää ja muokata tulevaisuudessa.

Yhden vastuun periaate

Mitä opin testaamalla 200 000 riviä infrastruktuurikoodia

Jokainen luokka suorittaa vain yhden tehtävän.

Ei tarvitse sekoittaa koodia ja tehdä monoliittisia jumalallisia spagettihirviöitä. Infrastruktuurin tulisi koostua yksinkertaisista tiilistä. Osoittautuu, että jos jaat Ansible-pelikirjan pieniksi paloiksi, luet Ansible-rooleja, niitä on helpompi ylläpitää.

Avoin suljettu periaate

Mitä opin testaamalla 200 000 riviä infrastruktuurikoodia

Avoin/kiinni -periaate.

  • Avoin laajennukselle: tarkoittaa, että entiteetin toimintaa voidaan laajentaa luomalla uusia entiteettityyppejä.
  • Suljettu muutokselle: entiteetin toiminnan laajentamisen seurauksena näitä entiteettejä käyttävään koodiin ei tule tehdä muutoksia.

Aluksi otimme testiinfrastruktuurin käyttöön virtuaalikoneilla, mutta koska käyttöönoton liikelogiikka oli erillään toteutuksesta, lisäsimme käyttöönoton ilman ongelmia baretalliin.

Liskovin substituutioperiaate

Mitä opin testaamalla 200 000 riviä infrastruktuurikoodia

Barbara Liskovin korvausperiaate. ohjelman objektien tulee olla korvattavissa niiden alatyyppien esiintymillä muuttamatta ohjelman oikeaa suoritusta

Jos asiaa tarkastellaan laajemmin, se ei ole minkään tietyn projektin ominaisuus, jota siellä voitaisiin soveltaa S.O.L.I.D., kyse on yleensä CFM:stä, esimerkiksi toisessa projektissa on tarpeen ottaa käyttöön paketoitu Java-sovellus eri Java-, sovelluspalvelimien, tietokantojen, käyttöjärjestelmän jne. päälle. Tämän esimerkin avulla tarkastelen muita periaatteita S.O.L.I.D.

Meidän tapauksessamme infrastruktuuritiimin sisällä on sopimus, että jos olemme asentaneet imbjava- tai oraclejava-roolin, meillä on Java-binäärisuoritettava tiedosto. Tämä on välttämätöntä, koska Ylävirran roolit riippuvat tästä käyttäytymisestä; he odottavat Javaa. Samalla voimme korvata yhden Java-toteutuksen/-version toisella muuttamatta sovelluksen käyttöönottologiikkaa.

Ongelma tässä on siinä, että tätä on mahdotonta toteuttaa Ansiblessa, minkä seurauksena joitain sopimuksia syntyy tiimin sisällä.

Liitännän erottelun periaate

Mitä opin testaamalla 200 000 riviä infrastruktuurikoodia

Liitäntöjen erotteluperiaate: ”Monet asiakaskohtaiset rajapinnat ovat parempia kuin yksi yleiskäyttöinen käyttöliittymä.

Aluksi yritimme laittaa kaiken sovellusten käyttöönoton vaihtelevuuden yhteen Ansible-pelikirjaan, mutta sitä oli vaikea tukea, ja lähestymistapaa, kun meillä on ulkoinen käyttöliittymä määritetty (asiakas odottaa porttia 443), sitten infrastruktuuri voidaan koota yksittäisistä tiilet tiettyä toteutusta varten.

Riippuvuuden inversioperiaate

Mitä opin testaamalla 200 000 riviä infrastruktuurikoodia

Riippuvuuden inversion periaate. Korkeampien tasojen moduulit eivät saa olla riippuvaisia ​​alempien tasojen moduuleista. Molempien moduulien on oltava riippuvaisia ​​abstraktioista. Abstraktien ei pitäisi olla riippuvaisia ​​yksityiskohdista. Yksityiskohtien tulee riippua abstraktioista.

Tässä esimerkki perustuu antikuvioon.

  1. Yhdellä asiakkaista oli yksityinen pilvi.
  2. Tilasimme virtuaalikoneita pilven sisään.
  3. Mutta pilven luonteen vuoksi sovellusten käyttöönotto oli sidottu siihen, mihin hypervisoriin VM oli päällä.

Nuo. Korkean tason sovellusten käyttöönottologiikka virtasi riippuvuuksien kanssa hypervisorin alemmille tasoille, mikä merkitsi ongelmia tämän logiikan uudelleenkäytössä. Älä tee näin.

Vuorovaikutus

Mitä opin testaamalla 200 000 riviä infrastruktuurikoodia

Infrastruktuuri koodina ei ole vain koodia, vaan myös koodin ja ihmisten välistä suhdetta, infrastruktuurin kehittäjien välistä vuorovaikutusta.

Bussitekijä

Mitä opin testaamalla 200 000 riviä infrastruktuurikoodia

Oletetaan, että projektissasi on Vasya. Vasya tietää kaiken infrastruktuuristasi, mitä tapahtuu, jos Vasya yhtäkkiä katoaa? Tämä on hyvin todellinen tilanne, koska hän voi törmätä bussiin. Joskus se tapahtuu. Jos näin tapahtuu ja tietoa koodista, sen rakenteesta, toimintatavoista, esiintymisistä ja salasanoista ei jaeta tiimin kesken, saatat kohdata useita epämiellyttäviä tilanteita. Näiden riskien minimoimiseksi ja tiedon jakamiseksi tiimin sisällä voit käyttää erilaisia ​​lähestymistapoja

Pari Devopsing

Mitä opin testaamalla 200 000 riviä infrastruktuurikoodia

Se ei ole kuin vitsinä, että järjestelmänvalvojat joivat olutta, vaihtoivat salasanoja ja pariohjelmoinnin analogia. Nuo. kaksi insinööriä istuu yhden tietokoneen, yhden näppäimistön ääreen ja alkavat yhdessä rakentaa infrastruktuuria: palvelimen perustaminen, Ansible-roolin kirjoittaminen jne. Se kuulostaa hyvältä, mutta se ei toiminut meillä. Mutta tämän käytännön erikoistapaukset toimi. Uusi työntekijä tulee, hänen mentorinsa ottaa todellisen tehtävän yhdessä hänen kanssaan, työskentelee ja siirtää tietoa.

Toinen erikoistapaus on hätäpuhelu. Ongelman aikana kokoontuu joukko päivystäviä ja asianosaisia, nimitetään yksi johtaja, joka jakaa näyttönsä ja kertoo ajatuksenjuoksun. Muut osallistujat seuraavat johtajan ajatuksia, vakoilevat konsolista tulevia temppuja, tarkistavat, etteivät he ole jättäneet lokista yhtään riviä ohi, ja oppivat uusia asioita järjestelmästä. Tämä lähestymistapa toimi useammin kuin ei.

Koodin tarkistus

Mitä opin testaamalla 200 000 riviä infrastruktuurikoodia

Subjektiivisesti oli tehokkaampaa levittää tietoa infrastruktuurista ja sen toiminnasta kooditarkistuksen avulla:

  • Infrastruktuuri kuvataan koodilla arkistossa.
  • Muutokset tapahtuvat erillisessä haarassa.
  • Yhdistämispyynnön aikana näet infrastruktuurin muutosten delta.

Kohokohta tässä oli, että arvostelijat valittiin yksitellen, aikataulun mukaan, ts. jollain todennäköisyydellä kiipeät uuteen infrastruktuuriin.

Koodityyli

Mitä opin testaamalla 200 000 riviä infrastruktuurikoodia

Ajan myötä riitoja alkoi ilmetä arvostelujen aikana, koska... arvioijilla oli oma tyylinsä ja arvostelijoiden vuorottelu pinotti heidät erilaisiin tyyleihin: 2 välilyöntiä tai 4, camelCase tai snake_case. Tätä ei ollut mahdollista toteuttaa heti.

  • Ensimmäinen idea oli suositella linterin käyttöä, kaikkihan ovat insinöörejä, kaikki ovat älykkäitä. Mutta erilaiset editorit, käyttöjärjestelmä, eivät ole käteviä
  • Tämä kehittyi robotiksi, joka kirjoitti löysälle jokaiselle ongelmalliselle sitoumukselle ja liitti linterin ulostulon. Mutta useimmissa tapauksissa oli tärkeämpiä asioita tehtävänä, ja koodi pysyi korjaamattomana.

Green Build Master

Mitä opin testaamalla 200 000 riviä infrastruktuurikoodia

Aika kuluu ja olemme tulleet siihen tulokseen, että sitoumuksia, jotka eivät läpäise tiettyjä testejä, ei voida päästää masteriin. Voila! Keksimme Green Build Masterin, jota on harjoitettu ohjelmistokehityksessä pitkään:

  • Kehitystyö on käynnissä erillisellä toimialalla.
  • Testit ovat käynnissä tässä ketjussa.
  • Jos testit epäonnistuvat, koodi ei pääse masteriin.

Tämän päätöksen tekeminen oli erittäin tuskallista, koska... aiheutti paljon keskustelua, mutta se oli sen arvoista, koska... Arvostelut alkoivat saada sulautumispyyntöjä ilman tyylieroja, ja ajan myötä ongelmakohtien määrä alkoi laskea.

IaC-testaus

Mitä opin testaamalla 200 000 riviä infrastruktuurikoodia

Tyylitarkistuksen lisäksi voit käyttää muita asioita, esimerkiksi tarkistaaksesi, että infrastruktuurisi voi todella ottaa käyttöön. Tai tarkista, että infrastruktuurin muutokset eivät johda rahan menetyksiin. Miksi tämä voi olla tarpeen? Kysymys on monimutkainen ja filosofinen, on parempi vastata tarinalla, että Powershellissä oli jotenkin automaattinen skaalaus, joka ei tarkistanut reunaehtoja => VM:itä luotiin enemmän kuin oli tarpeen => asiakas käytti enemmän rahaa kuin oli suunniteltu. Tämä ei ole kovin miellyttävää, mutta tämä virhe olisi täysin mahdollista havaita aikaisemmissa vaiheissa.

Voidaan kysyä, miksi monimutkaisesta infrastruktuurista tehdään vieläkin monimutkaisempi? Infrastruktuurin testeissä, kuten koodissa, ei ole kyse yksinkertaistamisesta, vaan infrastruktuurin toimivuudesta.

IaC-testauspyramidi

Mitä opin testaamalla 200 000 riviä infrastruktuurikoodia

IaC-testaus: Staattinen analyysi

Jos otat koko infrastruktuurin käyttöön kerralla ja tarkistat sen toimivuuden, saatat huomata, että se vie paljon aikaa ja vaatii paljon aikaa. Siksi perustan on oltava jotain, joka toimii nopeasti, sitä on paljon ja se kattaa paljon primitiivisiä paikkoja.

Bash on hankala

Katsotaanpa triviaalia esimerkkiä. valitse kaikki nykyisen hakemiston tiedostot ja kopioi toiseen paikkaan. Ensimmäisenä mieleen tuleva asia:

for i in * ; do 
    cp $i /some/path/$i.bak
done

Entä jos tiedoston nimessä on välilyönti? No, okei, olemme fiksuja, osaamme käyttää lainausmerkkejä:

for i in * ; do cp "$i" "/some/path/$i.bak" ; done

Hyvin tehty? Ei! Entä jos hakemistossa ei ole mitään, ts. ryyppyminen ei toimi.

find . -type f -exec mv -v {} dst/{}.bak ;

Hyvin tehty nyt? Ei... Unohdin mitä tiedostonimessä voi olla n.

touch x
mv x  "$(printf "foonbar")"
find . -type f -print0 | xargs -0 mv -t /path/to/target-dir

Staattisen analyysin työkalut

Edellisen vaiheen ongelma saattoi tarttua, kun unohdimme lainaukset, tähän on luonnossa monia parannuskeinoja Shellcheck, yleensä niitä on paljon, ja mitä todennäköisimmin löydät pinollesi linterin IDE:n alta.

Kieli
Työkalu

kemut
Shellcheck

Rubiini
RuboCop

pytonkäärme
Pylint

ansible
Ansible Lint

IaC-testaus: Yksikkötestit

Mitä opin testaamalla 200 000 riviä infrastruktuurikoodia

Kuten edellisestä esimerkistä näimme, linterit eivät ole kaikkivoivia eivätkä ne voi osoittaa kaikkia ongelma-alueita. Lisäksi analogisesti ohjelmistokehityksen testauksen kanssa voimme muistaa yksikkötestejä. Se mikä tulee heti mieleen shunit, junit, rspec, kysymys. Mutta mitä tehdä ansiblelle, chefille, saltstackille ja muille heidän kaltaisilleen?

Puhuimme aivan alussa S.O.L.I.D. ja että infrastruktuurimme tulisi koostua pienistä tiilistä. Heidän aikansa on tullut.

  1. Infrastruktuuri on jaettu pieniin tiileihin, esimerkiksi Ansible-rooleihin.
  2. Jonkinlainen ympäristö on otettu käyttöön, olipa se sitten telakointiasema tai virtuaalikone.
  3. Käytämme Ansible-rooliamme tässä testiympäristössä.
  4. Tarkistamme, että kaikki toimi odotetulla tavalla (suoritamme testejä).
  5. Päätämme ok vai ei.

IaC-testaus: Yksikkötestaustyökalut

Kysymys, mitä ovat CFM-testit? Voit yksinkertaisesti ajaa komentosarjan tai käyttää valmiita ratkaisuja tähän:

CFM
Työkalu

Ansible
Testinfra

Kokki
Tarkastus

Kokki
Palvelinspec

suolapino
Goss

Esimerkki testinfrasta, joka tarkistaa, että käyttäjät test1, test2 ovat olemassa ja ovat ryhmässä sshusers:

def test_default_users(host):
    users = ['test1', 'test2' ]
    for login in users:
        assert host.user(login).exists
        assert 'sshusers' in host.user(login).groups

Mitä valita? Kysymys on monimutkainen ja moniselitteinen, tässä on esimerkki githubin projektien muutoksista vuosille 2018-2019:

Mitä opin testaamalla 200 000 riviä infrastruktuurikoodia

IaC-testauskehykset

Herää kysymys: kuinka yhdistää kaikki ja käynnistää se? Voi ota ja tee se itse jos insinöörejä on riittävästi. Tai voit ottaa valmiita ratkaisuja, vaikka niitä ei ole kovin monta:

CFM
Työkalu

Ansible
molekyyli

Kokki
Testaa keittiö

terraform
Hirvein

Esimerkki githubin projektien muutoksista vuosille 2018-2019:

Mitä opin testaamalla 200 000 riviä infrastruktuurikoodia

Molekyyli vs. Testikeittiö

Mitä opin testaamalla 200 000 riviä infrastruktuurikoodia

Aluksi me kokeili testikeittiön käyttöä:

  1. Luo virtuaalikone rinnakkain.
  2. Käytä mahdollisia rooleja.
  3. Suorita tarkastus.

25-35 roolissa se toimi 40-70 minuuttia, mikä oli pitkä.

Mitä opin testaamalla 200 000 riviä infrastruktuurikoodia

Seuraava askel oli siirtyminen jenkins/docker/ansible/moleculiin. Idiologisesti kaikki on samanlaista

  1. Lint leikkikirjoja.
  2. Järjestä roolit.
  3. Käynnistä kontti
  4. Käytä mahdollisia rooleja.
  5. Suorita testi.
  6. Tarkista idempotenssi.

Mitä opin testaamalla 200 000 riviä infrastruktuurikoodia

40 roolin nukkaaminen ja tusinan kokeet alkoivat kestää noin 15 minuuttia.

Mitä opin testaamalla 200 000 riviä infrastruktuurikoodia

Valinta riippuu monista tekijöistä, kuten käytetystä pinosta, joukkueen asiantuntemuksesta jne. täällä jokainen päättää itse, miten yksikkötestauskysymys suljetaan

IaC-testaus: Integraatiotestit

Mitä opin testaamalla 200 000 riviä infrastruktuurikoodia

Seuraava askel infrastruktuurin testauspyramidissa on integraatiotestit. Ne ovat samanlaisia ​​kuin yksikkötestit:

  1. Infrastruktuuri on jaettu pieniin tiileihin, esimerkiksi Ansible-rooleihin.
  2. Jonkinlainen ympäristö on otettu käyttöön, olipa se sitten telakointiasema tai virtuaalikone.
  3. Tätä testiympäristöä sovelletaan monet Mahdolliset roolit.
  4. Tarkistamme, että kaikki toimi odotetulla tavalla (suoritamme testejä).
  5. Päätämme ok vai ei.

Karkeasti sanottuna emme tarkista järjestelmän yksittäisen elementin suorituskykyä kuten yksikkötesteissä, vaan tarkastamme kuinka palvelin on konfiguroitu kokonaisuutena.

IaC-testaus: End to End -testit

Mitä opin testaamalla 200 000 riviä infrastruktuurikoodia

Pyramidin huipulla meitä tervehtivät End to End -testit. Nuo. Emme tarkista erillisen palvelimen, erillisen komentosarjan tai erillisen infrastruktuurimme toimivuutta. Tarkistamme, että monet palvelimet ovat yhteydessä toisiinsa, infrastruktuurimme toimii odotetulla tavalla. Valitettavasti en ole koskaan nähnyt valmiita laatikkoratkaisuja, luultavasti koska... Infrastruktuuri on usein ainutlaatuinen, ja sitä on vaikea mallintaa ja luoda puitteet testausta varten. Tämän seurauksena jokainen luo omat ratkaisunsa. Kysyntää on, mutta vastausta ei ole. Siksi kerron teille, mitä on olemassa, jotta saamme muut ajatuksilleen tai hieromaan nenääni siihen tosiasiaan, että kaikki on keksitty kauan sitten ennen meitä.

Mitä opin testaamalla 200 000 riviä infrastruktuurikoodia

Projekti, jolla on rikas historia. Sitä käytetään suurissa organisaatioissa ja luultavasti jokainen teistä on epäsuorasti törmännyt sen kanssa. Sovellus tukee monia tietokantoja, integraatioita jne. Tietäen, miltä infrastruktuuri voi näyttää, on paljon telakointiaseman luomia tiedostoja, ja Jenkins tietää, mitkä testit suoritetaan missä ympäristössä.

Mitä opin testaamalla 200 000 riviä infrastruktuurikoodia

Tämä järjestelmä toimi melko pitkään, kunnes se sisälsi tutkimus Emme ole yrittäneet siirtää tätä Openshiftiin. Kontit pysyvät samoina, mutta laukaisuympäristö on muuttunut (hei D.R.Y. taas).

Mitä opin testaamalla 200 000 riviä infrastruktuurikoodia

Tutkimusidea meni pidemmälle, ja openshiftissä löydettiin sellainen asia kuin APB (Ansible Playbook Bundle), jonka avulla voit pakata tietoa infrastruktuurin käyttöönotosta konttiin. Nuo. on olemassa toistettavissa oleva, testattava tieto siitä, kuinka infrastruktuuria otetaan käyttöön.

Mitä opin testaamalla 200 000 riviä infrastruktuurikoodia

Kaikki tämä kuulosti hyvältä, kunnes törmäsimme heterogeeniseen infrastruktuuriin: tarvitsimme Windowsin testejä varten. Tämän seurauksena tieto siitä, mitä, missä, miten ottaa käyttöön ja testata, on jenkinsissä.

Yhteenveto

Mitä opin testaamalla 200 000 riviä infrastruktuurikoodia

Infrastruktuuri sellaisena kuin koodi on

  • Koodi arkistossa.
  • Ihmisen vuorovaikutusta.
  • Infrastruktuurin testaus.

linkit

Lähde: will.com

Lisää kommentti