උපදෙස්: Ansible භූමිකාවන් පරීක්ෂා කරන්නේ කෙසේද සහ නිෂ්පාදනයට පෙර ගැටළු සොයා ගන්නේ කෙසේද

ආයුබෝවන් හැමෝටම!

මම හෝටල් වෙන්කරවා ගැනීමේ සේවාවක DevOps ඉංජිනේරුවෙකු ලෙස සේවය කරමි. Ostrovok.ru. මෙම ලිපියෙන් මම Ansible භූමිකාවන් පරීක්ෂා කිරීමේදී අපගේ අත්දැකීම් ගැන කතා කිරීමට අවශ්යයි.

Ostrovok.ru හි අපි වින්‍යාස කළමණාකරුවෙකු ලෙස ansible භාවිතා කරමු. මෑතකදී අපි භූමිකාවන් පරීක්ෂා කිරීමේ අවශ්‍යතාවයට පැමිණියෙමු, නමුත්, එය සිදු වූ පරිදි, මේ සඳහා බොහෝ මෙවලම් නොමැත - වඩාත්ම ජනප්‍රිය, සමහර විට, අණු රාමුව වේ, එබැවින් අපි එය භාවිතා කිරීමට තීරණය කළෙමු. නමුත් ඔහුගේ ලියකියවිලි බොහෝ අන්තරායන් ගැන නිහඬ බව පෙනී ගියේය. අපට රුසියානු භාෂාවෙන් ප්රමාණවත් සවිස්තරාත්මක මාර්ගෝපදේශයක් සොයාගත නොහැකි විය, එබැවින් අපි මෙම ලිපිය ලිවීමට තීරණය කළෙමු.

උපදෙස්: Ansible භූමිකාවන් පරීක්ෂා කරන්නේ කෙසේද සහ නිෂ්පාදනයට පෙර ගැටළු සොයා ගන්නේ කෙසේද

අණුවක්

අණුව - Ansible භූමිකාවන් පරීක්ෂා කිරීමට උපකාර වන රාමුවක්.

සරල කළ විස්තරය: ඔබ සඳහන් කරන වේදිකාවේ අණුවක් නිර්මාණය කරයි (වලාකුළු, අතථ්‍ය යන්ත්‍රය, බහාලුම්; වැඩි විස්තර සඳහා, කොටස බලන්න රියදුරු), එය මත ඔබේ භූමිකාව ධාවනය කරන්න, පසුව පරීක්ෂණ ධාවනය කර නිදර්ශනය මකා දමයි. එක් පියවරක අසාර්ථක වීමක් සිදුවුවහොත්, අණුව ඒ පිළිබඳව ඔබට දැනුම් දෙනු ඇත.

දැන් තවත්.

න්යායන් ටිකක්

අපි අණුවේ ප්‍රධාන ආයතන දෙකක් සලකා බලමු: Scenario සහ Driver.

සිද්ධිය

ස්ක්‍රිප්ටයේ කුමක්, කොතැනක, කෙසේද සහ කුමන අනුපිළිවෙලකින් සිදු කරන්නේද යන්න පිළිබඳ විස්තරයක් අඩංගු වේ. එක් භූමිකාවකට ස්ක්‍රිප්ට් කිහිපයක් තිබිය හැකි අතර, ඒ සෑම එකක්ම මාර්ගය දිගේ නාමාවලියකි <role>/molecule/<scenario>, පරීක්ෂණය සඳහා අවශ්ය ක්රියාවන් පිළිබඳ විස්තර අඩංගු වේ. පිටපතක් තිබිය යුතුය default, ඔබ Molecule භාවිතයෙන් භූමිකාව ආරම්භ කළහොත් එය ස්වයංක්‍රීයව නිර්මාණය වේ. පහත ස්ක්‍රිප්ට් වල නම් ඔබගේ අභිමතය පරිදි වේ.

ස්ක්‍රිප්ට් එකක ක්‍රියා පරීක්‍ෂා කිරීමේ අනුපිළිවෙල හැඳින්වේ අනුකෘතියයි, සහ පෙරනිමියෙන් එය මේ වගේ ය:

(පියවර සලකුණු කර ඇත ?, පරිශීලකයා විසින් නිශ්චිතව දක්වා නොමැති නම් පෙරනිමියෙන් මඟ හරිනු ලැබේ)

  • lint - ධාවන ලින්ටර්. පෙරනිමියෙන් yamllint и flake8,
  • destroy - අණුවේ අවසාන දියත් කිරීමේ අවස්ථාවන් මකා දැමීම (ඇත්නම්)
  • dependency? - පරීක්ෂා කරන ලද භූමිකාවේ ප්‍රතිවිරෝධතා පරායත්තතාව ස්ථාපනය කිරීම,
  • syntax - භාවිතා කරමින් භූමිකා වාක්‍ය ඛණ්ඩය පරීක්ෂා කිරීම ansible-playbook --syntax-check,
  • create - උදාහරණයක් නිර්මාණය කිරීම,
  • prepare? - උදාහරණය සකස් කිරීම; උදාහරණයක් ලෙස python2 පරීක්ෂා කිරීම/ස්ථාපනය කිරීම
  • converge - පරීක්‍ෂා කළ ක්‍රීඩා පොත දියත් කිරීම,
  • idempotence - දුර්වලතා පරීක්ෂණය සඳහා ක්‍රීඩා පොත නැවත ධාවනය කරන්න,
  • side_effect? - භූමිකාවට සෘජුවම සම්බන්ධ නොවන ක්‍රියා, නමුත් පරීක්ෂණ සඳහා අවශ්‍ය,
  • verify - භාවිතයෙන් ලැබෙන වින්‍යාසය පිළිබඳ පරීක්ෂණ ධාවනය කිරීම testinfra(පෙරනිමි) /goss/inspec,
  • cleanup? - (නව අනුවාද වල) - දළ වශයෙන්, අණුව මගින් බලපෑමට ලක් වූ බාහිර යටිතල පහසුකම් "පිරිසිදු කිරීම",
  • destroy - උදාහරණයක් මකා දැමීම.

මෙම අනුපිළිවෙල බොහෝ අවස්ථාවන් ආවරණය කරයි, නමුත් අවශ්ය නම් වෙනස් කළ හැක.

ඉහත සෑම පියවරක්ම වෙන වෙනම භාවිතා කර ධාවනය කළ හැක molecule <command>. නමුත් එවැනි එක් එක් cli විධානය සඳහා තමන්ගේම ක්‍රියා අනුපිළිවෙලක් තිබිය හැකි බව ඔබ තේරුම් ගත යුතුය, එය ක්‍රියාත්මක කිරීමෙන් ඔබට සොයාගත හැකිය molecule matrix <command>. උදාහරණයක් ලෙස, විධානය ක්රියාත්මක කරන විට converge (පරීක්ෂා කරන ලද ක්‍රීඩා පොත ධාවනය කරමින්) පහත ක්‍රියා සිදු කරනු ලැබේ:

$ molecule matrix converge
...
└── default         # название сценария
    ├── dependency  # установка зависимостей
    ├── create      # создание инстанса
    ├── prepare     # преднастройка инстанса
    └── converge    # прогон плейбука

මෙම ක්රියාවන්ගේ අනුපිළිවෙල සංස්කරණය කළ හැකිය. ලැයිස්තුවෙන් යමක් දැනටමත් සම්පූර්ණ කර ඇත්නම්, එය මඟ හරිනු ලැබේ. වත්මන් තත්වය මෙන්ම නිදසුන් වින්‍යාසය ද අණු නාමාවලියෙහි ගබඩා කර ඇත $TMPDIR/molecule/<role>/<scenario>.

සමඟ පියවර එකතු කරන්න ? ඔබට අවශ්‍ය ක්‍රියාවන් Ansible playbook ආකෘතියෙන් විස්තර කළ හැකි අතර, පියවර අනුව ගොනු නාමය සෑදිය හැක: prepare.yml/side_effect.yml. මෙම Molecule ගොනු ස්ක්‍රිප්ට් ෆෝල්ඩරයේ තිබෙනු ඇතැයි අපේක්ෂා කරන්න.

රියදුරු

ධාවකයක් යනු පරීක්ෂණ සඳහා අවස්ථා නිර්මාණය කරන ආයතනයකි.
Molecule හි සූදානම් සැකිලි ඇති සම්මත ධාවක ලැයිස්තුව වන්නේ: Azure, Docker, EC2, GCE, LXC, LXD, OpenStack, Vagrant, Delegated.

බොහෝ අවස්ථාවලදී, සැකිලි යනු ගොනු වේ create.yml и destroy.yml ස්ක්‍රිප්ට් ෆෝල්ඩරය තුළ, පිළිවෙලින් අවස්ථාව නිර්මාණය කිරීම සහ මකා දැමීම විස්තර කරයි.
ව්‍යතිරේක වන්නේ Docker සහ Vagrant, ඉහත ගොනු නොමැතිව ඔවුන්ගේ මොඩියුල සමඟ අන්තර්ක්‍රියා සිදු විය හැකි බැවිනි.

පැවරී ඇති ධාවකය ඉස්මතු කිරීම වටී, මන්ද එය භාවිතා කරන්නේ නම්, උදාහරණ වින්‍යාසය සහිත කාර්යය පමණක් උදාහරණ නිර්මාණය සහ මකාදැමීමේ ගොනු වල විස්තර කර ඇත; ඉතිරිය ඉංජිනේරුවරයා විසින් විස්තර කළ යුතුය.

පෙරනිමි ධාවකය Docker වේ.

දැන් අපි පුහුණු වීමට සහ එහි තවත් විශේෂාංග සලකා බලමු.

ආරම්භ කිරීම

"හෙලෝ වර්ල්ඩ්" ලෙස අපි සරල nginx ස්ථාපන භූමිකාවක් පරීක්ෂා කරන්නෙමු. අපි ධාවකය ලෙස ඩොකර් තෝරා ගනිමු - මම හිතන්නේ ඔබ බොහෝ දෙනෙක් එය ස්ථාපනය කර ඇති බවයි (සහ ඩොකර් පෙරනිමි ධාවකය බව මතක තබා ගන්න).

සූදානම් වන්න virtualenv සහ එය තුළ එය ස්ථාපනය කරන්න molecule:

> pip install virtualenv
> virtualenv -p `which python2` venv
> source venv/bin/activate
> pip install molecule docker  # molecule установит ansible как зависимость; docker для драйвера

ඊළඟ පියවර වන්නේ නව භූමිකාවක් ආරම්භ කිරීමයි.
නව භූමිකාවක් මෙන්ම නව පිටපතක් ආරම්භ කිරීම විධානය භාවිතයෙන් සිදු කෙරේ molecule init <params>:

> molecule init role -r nginx
--> Initializing new role nginx...
Initialized role in <path>/nginx successfully.
> cd nginx
> tree -L 1
.
├── README.md
├── defaults
├── handlers
├── meta
├── molecule
├── tasks
└── vars

6 directories, 1 file

ප්රතිඵලය සාමාන්ය ඇන්සිබල් භූමිකාවකි. තවද, CLI අණු සමඟ ඇති සියලුම අන්තර්ක්‍රියා සිදු කරනු ලබන්නේ භූමිකා මූලයෙනි.

භූමිකාව නාමාවලියෙහි ඇති දේ බලමු:

> tree molecule/default/
molecule/default/
├── Dockerfile.j2  # Jinja-шаблон для Dockerfile
├── INSTALL.rst.   # Немного информации об установке зависимостей сценария
├── molecule.yml   # Файл конфигурации
├── playbook.yml   # Плейбук запуска роли
└── tests          # Директория с тестами стадии verify
    └── test_default.py

1 directory, 6 files

අපි config එක බලමු molecule/default/molecule.yml (අපි ඩොකර් රූපය පමණක් ප්‍රතිස්ථාපනය කරන්නෙමු):

---
dependency:
  name: galaxy
driver:
  name: docker
lint:
  name: yamllint
platforms:
  - name: instance
    image: centos:7
provisioner:
  name: ansible
  lint:
    name: ansible-lint
scenario:
  name: default
verifier:
  name: testinfra
  lint:
    name: flake8

යැපීම

මෙම කොටස පරායත්තතා මූලාශ්‍රය විස්තර කරයි.

හැකි විකල්ප: මන්දාකිනි, අදාළ වේ,කවචය.

Shell යනු මන්දාකිණි සහ ගිල්ට් ඔබේ අවශ්‍යතා ආවරණය නොකරන්නේ නම් භාවිතා කිරීමට විධාන කවචයකි.

මම වැඩි කාලයක් මෙහි නොසිටිමි, එය ප්රමාණවත් ලෙස විස්තර කර ඇත ලියකියවිලි.

රියදුරු

රියදුරු නම. අපට මෙය ඩොකර් ය.

ලින්ට්

Yamllint ලින්ටර් ලෙස භාවිතා වේ.

වින්‍යාසයේ මෙම කොටසෙහි ඇති ප්‍රයෝජනවත් විකල්ප වන්නේ yamllint සඳහා වින්‍යාස ගොනුවක් නියම කිරීමේ හැකියාව, ඉදිරි පරිසර විචල්‍යයන් හෝ ලින්ටරය අක්‍රිය කිරීමයි:

lint:
  name: yamllint
  options:
    config-file: foo/bar
  env:
    FOO: bar
  enabled: False

වේදිකා වේ

අවස්ථා වින්‍යාසය විස්තර කරයි.
ධාවකයක් ලෙස ඩොකර් සම්බන්ධයෙන් ගත් කල, අණුව මෙම කොටස හරහා පුනරාවර්තනය වන අතර ලැයිස්තුවේ සෑම අංගයක්ම ලබා ගත හැකිය Dockerfile.j2 විචල්‍යයක් ලෙස item.

එහි රියදුරෙකු සම්බන්ධයෙන් create.yml и destroy.yml, කොටස ඔවුන් තුළ ලබා ගත හැකිය molecule_yml.platforms, සහ එය මත පුනරාවර්තන දැනටමත් මෙම ගොනු වල විස්තර කර ඇත.

Ansible මොඩියුල සඳහා Molecule නිදසුන් කළමනාකරණය සපයන බැවින්, ඔබ එහි ඇති හැකි සැකසුම් ලැයිස්තුව සෙවිය යුතුය. ඩොකර් සඳහා, උදාහරණයක් ලෙස, මොඩියුලය භාවිතා වේ docker_container_module. වෙනත් ධාවකවල භාවිතා කරන මොඩියුල සොයා ගත හැක ලියකියවිලි.

විවිධ ධාවක භාවිතා කිරීමේ උදාහරණ ද ඔබට සොයාගත හැකිය අණුවෙහිම පරීක්ෂණ වලදී.

අපි මෙතනින් ආදේශ කරමු සෙන්ටෝස්:7 මත උබුන්ටු.

සැපයුම්කරු

"සැපයුම්කරු" යනු අවස්ථා කළමනාකරණය කරන ආයතනයයි. Molecule සම්බන්ධයෙන් ගත් කල, මෙය ansible වේ; අන්‍යයන් සඳහා සහය සැළසුම් කර නැත, එබැවින් මෙම කොටස වෙන් කරවා ගැනීමත් සමඟම විස්තීරණ ansible configuration ලෙස හැඳින්විය හැක.
ඔබට මෙහි පෙන්වා දිය හැකි බොහෝ දේ ඇත, නමුත් මම මගේ මතය අනුව ප්‍රධාන කරුණු ඉස්මතු කරමි:

  • ක්‍රීඩා පොත්: යම් යම් අවධීන්හිදී භාවිත කළ යුතු ක්‍රීඩා පොත් මොනවාදැයි ඔබට සඳහන් කළ හැක.

provisioner:
  name: ansible
  playbooks:
    create: create.yml
    destroy: ../default/destroy.yml
    converge: playbook.yml
    side_effect: side_effect.yml
    cleanup: cleanup.yml

provisioner:
  name: ansible
  config_options:
    defaults:
      fact_caching: jsonfile
    ssh_connection:
      scp_if_ssh: True

provisioner:
  name: ansible  
  connection_options:
    ansible_ssh_common_args: "-o 'UserKnownHostsFile=/dev/null' -o 'ForwardAgent=yes'"

  • විකල්ප: Ansible පරාමිතීන් සහ පරිසර විචල්‍යයන්

provisioner:
  name: ansible  
  options:
    vvv: true
    diff: true
  env:
    FOO: BAR

රංගනය

ස්ක්‍රිප්ට් අනුපිළිවෙලෙහි මාතෘකාව සහ විස්තරය.
යතුර එක් කිරීමෙන් ඔබට විධානයක පෙරනිමි ක්‍රියා අනුකෘතිය වෙනස් කළ හැක <command>_sequence සහ එය සඳහා වටිනාකමක් ලෙස, අපට අවශ්ය පියවර ලැයිස්තුව නිර්වචනය කිරීම.
Playbook ධාවන විධානය ක්‍රියාත්මක කිරීමේදී ක්‍රියා අනුපිළිවෙල වෙනස් කිරීමට අපට අවශ්‍ය යැයි සිතමු: molecule converge

# изначально:
# - dependency
# - create
# - prepare
# - converge
scenario:
  name: default
  converge_sequence:
    - create
    - converge

සත්‍යාපකය

පරීක්ෂණ සඳහා රාමුවක් සහ ඒ සඳහා ලින්ටරයක් ​​සකස් කිරීම. පෙරනිමියෙන්, ලින්ටර් භාවිතා වේ testinfra и flake8. හැකි විකල්ප ඉහත ඒවාට සමාන වේ:

verifier:
  name: testinfra
  additional_files_or_dirs:
    - ../path/to/test_1.py
    - ../path/to/test_2.py
    - ../path/to/directory/*
  options:
    n: 1
  enabled: False
  env:
    FOO: bar
  lint:
    name: flake8
    options:
      benchmark: True
    enabled: False
    env:
      FOO: bar

අපි අපේ භූමිකාවට ආපසු යමු. අපි ගොනුව සංස්කරණය කරමු tasks/main.yml මෙම පෙනුමට:

---
- name: Install nginx
  apt:
    name: nginx
    state: present

- name: Start nginx
  service:
    name: nginx
    state: started

සහ පරීක්ෂණ එකතු කරන්න molecule/default/tests/test_default.py

def test_nginx_is_installed(host):
    nginx = host.package("nginx")
    assert nginx.is_installed

def test_nginx_running_and_enabled(host):
    nginx = host.service("nginx")
    assert nginx.is_running
    assert nginx.is_enabled

def test_nginx_config(host):
    host.run("nginx -t")

අවසන්, ඉතිරිව ඇත්තේ ධාවනය කිරීම පමණි (භූමිකාවේ මුල සිට, මම ඔබට මතක් කරමි):

> molecule test

ස්පොයිලර් යටතේ දිගු පිටාර ගැලීම:

--> Validating schema <path>/nginx/molecule/default/molecule.yml.
Validation completed successfully.
--> Test matrix

└── default
    ├── lint
    ├── destroy
    ├── dependency
    ├── syntax
    ├── create
    ├── prepare
    ├── converge
    ├── idempotence
    ├── side_effect
    ├── verify
    └── destroy

--> Scenario: 'default'
--> Action: 'lint'
--> Executing Yamllint on files found in <path>/nginx/...
Lint completed successfully.
--> Executing Flake8 on files found in <path>/nginx/molecule/default/tests/...
Lint completed successfully.
--> Executing Ansible Lint on <path>/nginx/molecule/default/playbook.yml...
Lint completed successfully.
--> Scenario: 'default'
--> Action: 'destroy'

    PLAY [Destroy] *****************************************************************

    TASK [Destroy molecule instance(s)] ********************************************
    changed: [localhost] => (item=None)
    changed: [localhost]

    TASK [Wait for instance(s) deletion to complete] *******************************
    ok: [localhost] => (item=None)
    ok: [localhost]

    TASK [Delete docker network(s)] ************************************************

    PLAY RECAP *********************************************************************
    localhost                  : ok=2    changed=1    unreachable=0    failed=0

--> Scenario: 'default'
--> Action: 'dependency'
Skipping, missing the requirements file.
--> Scenario: 'default'
--> Action: 'syntax'

    playbook: <path>/nginx/molecule/default/playbook.yml

--> Scenario: 'default'
--> Action: 'create'

    PLAY [Create] ******************************************************************

    TASK [Log into a Docker registry] **********************************************
    skipping: [localhost] => (item=None)

    TASK [Create Dockerfiles from image names] *************************************
    changed: [localhost] => (item=None)
    changed: [localhost]

    TASK [Discover local Docker images] ********************************************
    ok: [localhost] => (item=None)
    ok: [localhost]

    TASK [Build an Ansible compatible image] ***************************************
    changed: [localhost] => (item=None)
    changed: [localhost]

    TASK [Create docker network(s)] ************************************************

    TASK [Create molecule instance(s)] *********************************************
    changed: [localhost] => (item=None)
    changed: [localhost]

    TASK [Wait for instance(s) creation to complete] *******************************
    changed: [localhost] => (item=None)
    changed: [localhost]

    PLAY RECAP *********************************************************************
    localhost                  : ok=5    changed=4    unreachable=0    failed=0

--> Scenario: 'default'
--> Action: 'prepare'
Skipping, prepare playbook not configured.
--> Scenario: 'default'
--> Action: 'converge'

    PLAY [Converge] ****************************************************************

    TASK [Gathering Facts] *********************************************************
    ok: [instance]

    TASK [nginx : Install nginx] ***************************************************
    changed: [instance]

    TASK [nginx : Start nginx] *****************************************************
    changed: [instance]

    PLAY RECAP *********************************************************************
    instance                   : ok=3    changed=2    unreachable=0    failed=0

--> Scenario: 'default'
--> Action: 'idempotence'
Idempotence completed successfully.
--> Scenario: 'default'
--> Action: 'side_effect'
Skipping, side effect playbook not configured.
--> Scenario: 'default'
--> Action: 'verify'
--> Executing Testinfra tests found in <path>/nginx/molecule/default/tests/...
    ============================= test session starts ==============================
    platform darwin -- Python 2.7.15, pytest-4.3.0, py-1.8.0, pluggy-0.9.0
    rootdir: <path>/nginx/molecule/default, inifile:
    plugins: testinfra-1.16.0
collected 4 items

    tests/test_default.py ....                                               [100%]

    ========================== 4 passed in 27.23 seconds ===========================
Verifier completed successfully.
--> Scenario: 'default'
--> Action: 'destroy'

    PLAY [Destroy] *****************************************************************

    TASK [Destroy molecule instance(s)] ********************************************
    changed: [localhost] => (item=None)
    changed: [localhost]

    TASK [Wait for instance(s) deletion to complete] *******************************
    changed: [localhost] => (item=None)
    changed: [localhost]

    TASK [Delete docker network(s)] ************************************************

    PLAY RECAP *********************************************************************
    localhost                  : ok=2    changed=2    unreachable=0    failed=0

අපගේ සරල භූමිකාව ගැටළු නොමැතිව පරීක්ෂා කර ඇත.
මෙහෙයුම අතරතුර ගැටළු ඇති වුවහොත් එය මතක තබා ගැනීම වටී molecule test, එවිට ඔබ සම්මත අනුපිළිවෙල වෙනස් නොකළේ නම්, Molecule එම අවස්ථාව මකා දමයි.

නිදොස්කරණය සඳහා පහත විධානයන් ප්‍රයෝජනවත් වේ:

> molecule --debug <command> # debug info. При обычном запуске Молекула скрывает логи.
> molecule converge          # Оставляет инстанс после прогона тестируемой роли.
> molecule login             # Зайти в созданный инстанс.
> molecule --help            # Полный список команд.

පවතින භූමිකාව

පවතින භූමිකාවකට නව පිටපතක් එකතු කිරීම සිදුවේ භූමිකාව නාමාවලියෙන් පහත විධානයන් සමඟ:

# полный список доступных параметров
> molecule init scenarion --help
# создание нового сценария
> molecule init scenario -r <role_name> -s <scenario_name>

භූමිකාවේ පළමු පිටපත මෙය නම්, පරාමිතිය -s ස්ක්‍රිප්ට් එකක් සාදනු ලබන බැවින් මඟ හැරිය හැක default.

නිගමනය

ඔබට පෙනෙන පරිදි, Molecule ඉතා සංකීර්ණ නොවන අතර, ඔබේම සැකිලි භාවිතා කරන විට, ඔබට අවස්ථා නිර්මාණය කිරීම සහ මකා දැමීම සඳහා playbooks හි විචල්‍යයන් සංස්කරණය කිරීම සඳහා නව ස්ක්‍රිප්ට් යෙදවීම අඩු කළ හැක. අණුව CI පද්ධති සමඟ බාධාවකින් තොරව ඒකාබද්ධ වන අතර එමඟින් ක්‍රීඩා පොත් අතින් පරීක්ෂා කිරීමේ කාලය අඩු කිරීමෙන් සංවර්ධනයේ වේගය වැඩි කිරීමට ඔබට ඉඩ සලසයි.

ඔබගේ අවදානය පිළිබඳ ස්තූතියි. ඔබට ප්‍රධාන භූමිකාවන් පරීක්ෂා කිරීමේ අත්දැකීම් තිබේ නම් සහ එය අණුවට සම්බන්ධ නොවේ නම්, ඒ ගැන අදහස් දැක්වීමේදී අපට කියන්න!

මූලාශ්රය: www.habr.com

අදහස් එක් කරන්න