Speciális tesztkörnyezet, avagy SNMP a Dockerdőben

Nyomtató kép a pixabay.com-ról

Minden szoftvernek biztosítani kell egy tesztkörnyezetet. Ez néha nagyon egyszerű, néha pedig igazán elgondolkodtató. Mi van, ha a tesztelendő szoftvernek egy olyan eszközzel kell kommunikálnia, amit nem lehet minden sarkon beszerezni, ráadásul helyhez kötött, a fejlesztés viszont nem? Ebben a cikkben nyomtatókkal történő SNMP kommunikációról fogok mesélni Docker környezetben. Mivel tud segíteni és mi okoz némi fejtörést a konténerek világában.

Idő- és helyfüggetlen fejlesztés

Jelenleg a munkám részeként egy olyan projektet vezetek, aminek célja az összes webes szolgáltatás konténerizációja. Ez azzal jár, hogy a legkülönfélébb technológiákkal és szoftverigényekkel találkozom, amikre megoldást kell találnom úgy, hogy az a szoftver hordozható legyen. Még akkor is, ha fizikailag egyébként egy igen konkrét helyen kell, hogy működjön élesben, hiszen a próbálkozásokkal, tesztelésekkel érdemes elkerülni az éles rendszert, ameddig csak lehet.

Így találkoztam a következő igénnyel. Adott nagyságrendileg 40-50 olyan eszköz, amik SNMP-n keresztül szolgáltatnak magukról állapotinformációkat, illetve az azonosításukhoz szükséges adatokat. Ezeket IP címen keresztül lehet elérni egy zárt hálózatban, tehát viszonylag kötött a fejlesztés és tesztelés helye. Legalábbis látszólag. A helyzetet nehezíti, hogy az SNMP egy UDP portot használó protokoll, tehát a távoli munka SSH tunnel segítségével sem triviális, bár elvileg van rá mód.

Mit tehet az ember ebben az esetben? Nos... használjon VPN-t! Ezzel el is értünk a cikk végére. Köszönöm a figyelmet!

Azért tételezzük fel, hogy VPN-re nincs lehetőség, de ha van is, egy ilyen eset nem csak a fejlesztés helyét, de az idejét is behatárolja, mivel nem biztos, hogy az érintett eszközök a fejlesztés ideje alatt folyamatosan elérhetőek. Így tehát a következő két dologra biztosan figyelni kell.

  • A programnak legyenek minél inkább szétválasztható bemenetei és kimenetei. Ha a szoftver legmélyebb zugaiba van beágyazva az eszközök elérhetősége, akkor az átírása nélkül nem nagyon fog menni a tesztelés. Azaz egy minél általánosabb megoldás kell, ami bármilyen eszközre ráereszthető és biztosítani kell a konfigurálhatóságot. Innentől már csak az a kérdés, honnan szerzel olyan eszközt, amire ráeresztheted.
  • A kulcsszó a virtualizáció vagy szimuláció. Ha az eszköznek is egyértelmű bemenetei és kimenetei vannak, akár saját barkács szimulátort is össze lehet hozni, de érdemesebb egy célszoftvert keresni. Az SNMP szimulátort így alig fél perc alatt találtam meg és ebben benne volt a böngésző megnyitása, a gépelés és néhány ásítás is.

SNMP szimulátor

A fejlesztői környezet természetesen egy Docker Compose projekt. A "docker-compose.yml" tartalmazza az állapotinformációkat lekérő szoftver definícióját, és a "docker-compose.snmpsim.yml" a szimulátort. A futtatás pedig az alábbi módon történik a végén:

docker-compose -f docker-compose.yml -f docker-compose.snmpsim.yml up -d

Nálam az egyik első gondolat mindig az, hogy "és van-e letölthető docker image hozzá?". A válasz legtöbbször az, hogy igen. tandrup snmpsim image-ét lehet használni, de a Docker Hub-on több találat is van az snmpsim kulcsszóra.

A használata pofon egyszerű. Lényegében kell egy szövegfájl, amit a konténerben fel kell csatolni a "/usr/local/snmpsim/data" mappába. A fájl SNMP rekordokat tartalmaz. Objektumazonosítókat, típust és értéket. Ezt be lehet gépelni kézzel is, ha csak néhány fix információt kell tudni lekérdezni ismert azonosítókkal. De akár le is klónozható az eredeti eszköz az snmprec programmal. Én manuálisan az alábbiakat vettem fel a fájlban.

public.snmprec

1.3.6.1.2.1.1.5.0|4|1234-5678
1.3.6.1.2.1.1.6.0|4|Ékezetes helyszín
1.3.6.1.2.1.25.3.2.1.3.1|4|Xerox WorkCentre 1234
1.3.6.1.2.1.43.5.1.1.17.1|4|SN123456789
1.3.6.1.2.1.43.18.1.1.8.1.0|4|fake-001 Definiálatlan teszt üzenet 1
1.3.6.1.2.1.43.18.1.1.8.1.1|4|fake-002 Definiálatlan teszt üzenet 2
1.3.6.1.2.1.43.18.1.1.8.1.2|4|fake-003 Definiálatlan teszt üzenet 3
1.3.6.1.2.1.43.18.1.1.8.1.3|4|071-450 warning
1.3.6.1.2.1.43.18.1.1.8.1.4|4|071-451 error
1.3.6.1.2.1.43.18.1.1.8.1.5|4|062-300 info
1.3.6.1.4.1.253.8.53.13.2.1.6.1.20.1|4|400
1.3.6.1.4.1.253.8.53.13.2.1.6.1.20.33|4|100
1.3.6.1.4.1.253.8.53.13.2.1.6.1.20.43|4|30

Fontos, hogy az OID-k növekvő sorrendben legyenek felsorolva, mert ellenkező esetben az SNMP kliens figyelmeztetést adhat vissza és leállítja a kért objektum keresését a duplikációk kiszűrése érdekében. A fenti példafájlban arra törekedtem, hogy lehetőleg minden, számomra lényeges eshetőség tesztelhető legyen. Így az ékezetet tartalmazó szövegek is, amik a klienssel lekérdezve egy HEXA számsorként jelennek meg, aminek a visszafejtéséről gondoskodni kell, nem beszélve az UTF-8 kompatibilitásról. Ez PHP-ban az alábbi függvénnyel megoldható azokban az esetekben, amikkel én találkoztam:

/**
 * UTF-8 string az snmp rekordból
 *
 * Ékezetes snmp rekord lehet Hex-String típusú is.
 * Abból is normál stringet készít és konvertálja UTF-8 kódolásúra, ha nem az volt.
 *
 * @param string $string
 * @return string
 */

function sanitizeSNMPString(string $string): string
{
    $matches = [];
    if (preg_match(/* @lang text */ '~(?<type>[^:]*): (?<message>.*)~s', $string, $matches)) {
        $string = $matches['type'] === 'Hex-STRING'
            ? hex2bin(str_replace([' ', "\n"], '', $matches['message']))
            : trim($matches['message'], '"')
        ;
    }
    if(!mb_check_encoding($string, 'UTF-8')) {
        $encoding = mb_detect_encoding($string, 'ISO-8859-1,ISO-8859-2');
        $string = mb_convert_encoding($string, 'UTF-8', $encoding);
    }
    return $string;
}

Egy docker-compose.snmpsim.yml fájlban valahogy így lehetne ezt leírni:

version: "3.4"

networks
:
  devices
:

services
:
  device
:
    image
: tandrup/snmpsim
    volumes
:
      - ./snmpsim/data/public.snmprec:/usr/local/snmpsim/data/public.snmprec
    networks
:
      - devices  

Egy csavar még a történetben, hogy a nyomtatónak webes felülete is van és annak elérhetőségét is kell vizsgálni a 80-as porton. Tehát illene egy 80-as portot is elérhetővé tenni a konténerben. Ehhez egy újabb szolgáltatást kell indítani, aminek a "device" hálózati névterére kell csatlakoznia. Így két konténerben, de mégis egy IP címen két port is kihasználható.

services:
  # ...
  device-httpd
:
    image
: httpd:2.4
    network_mode
: service:device

Automatikus nyomtatóészlelés Docker API-val

Oké. Ez volt tehát az a rész, amiben a Docker segített, de van egy probléma vele. A konténernek nem tudjuk biztosan előre az IP címét. Ezt persze IP helyett hoszt névvel könnyen át lehetne hidalni, de ha az éleshez leghasonlóbb környezetet akarod összeállítani, akkor IP címekkel kell dolgozni. Fix IP konfigurálásával megint csak rövidre zárhatnánk a történetet, de ha a hordozhatóság a cél és a lehetőleg automatikus működés akárhány szimulált nyomtatóra, akkor valahogy detektálni kell az elindult konténerek IP címeit. Nem is olyan nehéz, csak egy konténert kell indítani a projektben, ami eléri a Docker socketet a gazda gépen és egy docker kliens vagy API segítségével megadott feltételek alapján listázza a konténereket. De mik is a feltételek?

Lehet akár egy külön Docker Network-öt létrehozni és ennek nevét a programmal tudatni. A hálózatra csatlakozó konténerek pedig lekérdezhetők programnyelvtől függően, de akár a "docker network inspect" utasítással is. A biztonság kedvéért lehet szűrni erre is, de nem lesz elég, mert például már az a konténer is része lesz a hálózatnak, amelyik a lekérdezéseket végzi. Ezért az alábbi kiegészítés kell még a "docker-compose.snmpsim.yml" fájlba.

services:
  # ...
  php
:
    networks
:
      - default
      - devices

Bevett gyakorlat viszont a konténer címkézése.

Ehhez jwilder docker-gen image-e is használható egy IP listát tartalmazó fájl létrehozására, vagy az általam kiegészített változat. Néhány sorból viszont egy python programocska is megteszi a dolgát.

docker-compose.snmpsim.yml részlet

services:
  # ...
  ipgen
:
    build
: snmpsim/ipgen
    dockerfile
: Dockerfile
  volumes
:
    - /var/run/docker.sock:/var/run/docker.sock
  environment
:
    # egy .env fájlban lesz a változó értéke a compose fájl mellett.
    # Ez ebben az esetben projektneve_devices lesz.
    # A projektneve a mappanévből adódik.
    DEVICE_NETWORK
: ${DEVICE_NETWORK}

requirements.txt

docker==3.7.0
flask==1.0.2

Dockerfile

FROM python:3.7

COPY requirements.txt /
COPY www/web.py /var/www/html

RUN pip install -r /requirements.txt

ENV FLASK_APP=web.py
ENV FLASK_ENV=development
ENV DEVICE_NETWORK=devices

WORKDIR /var/www/html
CMD ["flask", "run", "--host", "0.0.0.0", "--port", "80"]

web.py

import docker
import os
import string
import random
from flask import Flask
from flask import Response

app = Flask(__name__)

@app.route("/")
def root():
    import docker
    import os

    client = docker.from_env()

    content = ''
    for container in client.networks.get(os.getenv('DEVICE_NETWORK')).containers:
        if container.labels.get('printerStatuses.device') is not None:
            ip = container.attrs\
                .get('NetworkSettings')\
                .get('Networks')\
                .get(os.getenv('DEVICE_NETWORK'))\
                .get('IPAddress')
            content += "{:s}\n".format(ip)

    return Response(content, mimetype='text/plain')

Fontos a "/var/run/docker.sock" felcsatolása, mert a python kliens így fogja elérni a Dockert. A címkével kiegészített konténerdefiníció pedig a következő:

services:
  device
:
    image
: tandrup/snmpsim
    volumes
:
      - ./snmpsim/data/public.snmprec:/usr/local/snmpsim/data/public.snmprec
    networks
:
      - devices  
    labels
:
      printerStatuses.device
: 1

Mindezek után már csak a programot kell megírni, ami az eszközöktől kérdezget. Parancssorban is meg lehet oldani, de a PHP-hez létezik egy "snmp" kiterjesztés, ami egyébként telepítve van az általam készített PHP image-ben is: itsziget/php. A nyomtatók IP címeit pedig már le lehet kérdezni curl-lel vagy PHP-ben egy file_get_contents() függvényhívással is. Amennyiben a programod tartalmaz konfigurációs fájlt, amiben megadható egy IP címeket tartalmazó szövegfájl, ezt ráadásul a Docker konténernek átadott környezeti változó értékével töltöd fel, egy URL-t is megadhatsz, ami jelen esetben a "http://ipgen" lenne.

Néhány további jó tanács

Ha sok eszközről, nyomtatóról van szó, akkor figyelni kell az időlimitek beállítására, illetve az engedélyezett újrapróbálkozások számára a hálózati műveleteknél. Az alapértelmezett timeout 1 másodperc, viszont 5-ször próbálkozik újra. Jó esetben a nyomtató a másodperc töredéke alatt, századmásodperces nagyságrendben válaszol, tehát a kikapcsolt, elérhetetlen nyomtatók száma fogja lassítani a programot. Ezt figyelembe véve kell kialakítani a paramétereket, és szükség esetén aszinkron módon, párhuzamosan lekérdezni az állapotokat.

Ha tetszett a cikk, oszd meg az ismerőseiddel, hogy ők se maradjanak le róla. Ha nem tetszett, azt is megoszthatod az ismerőseiddel, de velem is, hogy legközelebb jobb cikkel szolgálhassak.

Kategóriák: 
Megosztás/Mentés