Dx služba Chatskripty pro pokročilý provisioning zařízení a sítě

Chatskripty jsou vlastně předpisy, jak má  MANGO  konfigurovat zařízení. Jsou psány jednoduchým programovacím jazykem syntakticky podobným JavaScriptu. Chatskripty jsou spuštěné naŕíklad při požadavku na aktualizaci zařízení. V Chatskriptech lze pracovat s informacemi o evidovaných zařízení a jejich parametrech v Mango a provádět konfiguraci (provisioning) fyzických zařízení ve vaší síti pomocí protokolů SNMP, ssh, DHCP/TFTP nebo třeba telnet. Chatskript umí pracovat s kmenovými atributy zařízení i jejich parametry, může z nich číst i do nich zapisovat. Umí také vytvořit soubor a odeslat ho nějakým protokolem na zařízení nebo ho uložit třeba zase do parametru zařízení v  MANGO .

1. Úvod

 MANGO  aktuálně disponuje chatskripty pro 2 druhy úloh:
  • aktualizace zařízení
  • konfigurace shapingu

Správu chatskriptů může provádět uživatel s odpovídajícími právy v modulu
mod_submenu_chatscripts CHATSKRIPTY
.

1.1. Aktualizace zařízení

Po provedení změn u služeb zákazníka nebo u zařízení je v Mango zavolána tzv. aktualizace zařízení. Tou se provede aktualizace zařízení a jeho parametrů. Součástí aktualizace může být také přenos předem definovaných parametrů od služeb zákazníka na jeho koncová zařízení nebo přenos pareetrů mezi zaŕízeními v hierarchii sítě. Například se může jednat o přenos nastavení rychlostí datové služby do parametrů koncového zařízení. Poté je proveden provisioning fyzického zařízení v síti partnera. Pokud je pro zařízení v Mango nastaven nějaký Chatskript, tak v tento okamžik systém naplánuje spuštění tohoto Chatskriptu definovaného pro dané zařízení. Chatskript může být přiřazen přímo ke konkrétnímu zařízení, nebo k množině zařízení např. podle typu nebo třídy zaŕízení.

1.2. Konfigurace shapingu

Podobně jako u aktualizace zařízení, umí Mango pomocí Chatskriptů nastavit do zařízení konfiguraci pro shaping v okamžiku kdy dojde k její změně. Pokud je např. dané zařízení označeno jako shaper a jsou upraveny parametry pro shaping, které se ho týkají, je zavolána jeho aktualizace, Mango naplánuje spuštění přiřazeného Chatskriptu pro úlohu konfigurace shapingu.

2. Referenční popis jazyka Chatskript

2.1. Ahoj světe

Tradiční nejjednodušší program vypadá takto:
println("Hello world!");

Funkce `println` vypíše svůj argument do výstupu skriptu a odřádkuje. Existuje také `print`, který dělá totéž, ale neodřádkovává.

Text ve dvojitých uvozovkách v příkladu výše je řetězec. Téměř všechno v jazyce je schopné konverze na řetězec.
// Máme-li v proměnné cokoliv:
println("Tohle musíte vidět: " + cokoliv);

2.2. Syntaxe jazyka

Syntakticky je jazyk Chatskript podobný JavaScriptu.

2.2.1. Komentáře

Jednořádkové komentáře mohou začínat `//` nebo `#`. Je možné psát i víceřádkové komentáře jako v C: `/* Toto je komentář */`.
// Toto je komentář"
# No, a toto také
/*
Jupííí, takto můžeme
psát i víceřádkové
komentáře
*/

2.2.2. Operátory

Logické operátory jsou and, or a not. Číslo 0 nebo prázdný řetězec '' mají v boolovských výrazech (například v podmínce if) hodnotu NEPRAVDA, číslo 1 nebo neprázdný řetězec hodnotu PRAVDA.

Operátory pro porovnávání a přiřazování jsou stejné jako v rodině jazyků vycházejících z C. Operátor = přiřadí hodnotu do proměnné. Operátory =, ==, <, <= (je menší nebo rovno), > a >= (je větší nebo rovno) se používají pro porovnání.

Aritmetické operátory jsou klasické, včetně += (přičti k proměnné hodnotu). Neexistuje však operátor ++.

Operátor + plní také funkci spojování řetězců, pokud alespoň jeden z argumentů je řetězec. Pokud toto chování není to, co potřebujeme, můžeme jednak využít závorky a druhak v případě potřeby explicitně konvertovat řetězcový argument na číslo.
println("Tohle bude 23: " + 2 + 3);
println("Tohle bude 5: " + (2 + parseInt("3")));

2.2.3. Proměnné

Proměnné nemusíme nijak deklarovat (budou pak všechny globální). Volitelně však lze proměnné lokální ve funkci nebo bloku deklarovat klíčovým slovem var. Můžeme deklarovat více proměnných najednou, při deklaraci můžeme rovnou přidělit hodnotu.
// Takto deklarujeme jednu proměnnou bez hodnoty.
var a;

// Takto deklarujeme proměnnou a vložíme do ní řetězec "Jedna".
var a = "Jedna";

// Takto deklarujeme a bez hodnoty a b a c s výchozí hodnotou.
var a, b = "foo", c = "bar";

Např. narozdíl od JavaScriptu nejsou proměnné lexikální, ale dynamické. To znamená, že nemají hodnotu podle toho, kde v kódu byly deklarovány, ale podle toho, kdy jsou skutečně přiřazené při běhu skriptu. Chovají se tak obdobně jako např. proměnné ve skriptech v UNIXovém shellu.

2.2.4 Funkce

Vlastní funkce se deklarují klíčovým slovem func.
func pozdrav(koho) {
  println("Ahoj " + koho + "!";
}
// Vypíše Ahoj Pepo!
pozdrav("Pepo");

Deklarace funkcí mohou případně být i vnořené do sebe. Může se to hodit, když chcete přehledněji členit nějaký složitější kód například pro parsování výstupu zařízení.
func neco() {
  println("Toto je globalni funkce neco().");
}

func vetsi_funkce() {
  func neco() {
    println("Toto neco() je neco uplne jineho.");
  }
  neco();
}
neco();
vetsi_funkce();
/*
Vypíše:
Toto je globalni funkce neco().
Toto neco() je neco uplne jineho.
*/

2.2.5. Numerické funkce

parseInt(str)
Převede řetězec na číslo.

min(a, b)
Vrátí menší z obou hodnot.

max(a, b)
Vrátí větší z obou hodnot.

2.2.6. Bitová aritmetika

bitAnd(num, mask)
Bitový součin.

bitOr(a, b)
Bitový součet.

bitXor(a, b)
Bitová nonekvivalence.

bitShiftL(a, b)
Bitový posun vlevo.

bitShiftR(a, b)
Bitový posun vpravo.

2.2.7. Objekty

Jsou podobné jako objekty v JavaScriptu nebo dictionary v Perlu. Oproti kterým ale není třeba řešit reference a syntaxe je tak jednodušší. Některé objekty jsou předdefinované nebo vracené předdefinovanými funkcemi. Můžete také vytvářet vlastní, buď funkcí Object nebo objektovým literálem podobně jako v JavaScriptu.

Object()
Vytvoří nový prázdný objekt

var muj_objekt = Object();
muj_objekt.jmeno = "Pepa";

var jiny_objekt = {};

var dalsi_objekt = { "jmeno": "Pepa", "vek": 37 };

println("Hle objekty: " + muj_objekt + ", " + jiny_objekt + ", " + dalsi_objekt);

2.2.8. Příkazy jazyka, řízení běhu

Jednotlivé příkazy se ukončují středníky.

Pro podmíněné provádění existuje konstrukce if - elif - else (elif a else větve jsou samozřejmě nepovinné).
if (podmínka) {
  kód;
} elif (jiná_podmínka) {
  další kód;
} else {
  jiný kód;
}

Pro obecný cyklus se používá while.
while (podmínka) {
  // Dokud podmínka platí, provádí se tento kód
}

Cyklus přes prvky pole (o polích viz dále) se píše:
each (řídící_proměnná: pole) {
  // V proměnné řídící_proměnná mám postupně všechny prvky pole
}

Cyklus přes atributy objektu (o objektech viz dále) se píše:
every (klíč, hodnota: objekt) {
  // Kód se postupně provede pro každý název atributu v proměnné klíč a hodnotu v atributu hodnota.
}

Když chcete použít jen některé prvky pole, buď můžete klasicky použít if nebo můžete použít metodu na filtrování a procházení přefiltrovaného pole (viz níže).

2.2.9. Obecné funkce

typeof(proměnná)
Vrátí řetězec, udávající typ proměnné: "string" pro řetězec, "int" pro celé číslo, "array" pro pole, "Object" pro obecný objekt, případně název typu objektu (např. "Device" pro zařízení, "Error" pro objekt popisující chybu vrácenou některými funkcemi. Nejčastější použití je právě detekce chyb těchto funkcí.
// Restart DOCSIS modemu s detekcí chyby (jestliže modem máte v proměnné target)
if (typeof(target.snmpset({"1.3.6.1.2.1.69.1.1.3.0": 1})) == "Error") {
  println("Tak tohle nevyšlo...");
}

2.2.10. Práce s řetězci

Spolu s metodami polí zjednoduší parsování odpovědí zařízení.

String.match(pat)
Vrátí true, pokud řetězec odpovídá regulárnímu výrazu pat

String.split(delim)
Vrátí pole s kusy řetězce, rozděleného oddělovačem delim

String.splitByRe(pattern)
Vrátí pole s kusy řetězce, rozděleného podle regulárního výrazu pattern

String.replace(pattern, subst)
Nahradí v řetězci výskyty podřetězce pattern náhradou subst

String.replaceRe(pattern, subst)
Nahradí v řetězci výskyty regulárního výrazu pattern náhradou subst

String.deleteRe(pattern)
Vyhází z řetězce všechny výskyty regulárního výrazu pattern

String.trim(what)
Ořízne ze začátku a konce řetězce znaky ve "what"

String.findAll(pattern)
Vrátí pole všech výskytů regulárního výrazu pattern

String.decodeHex(pattern)
Vrátí pole všech výskytů regulárního výrazu pattern

String.decodeDec()
Převede vstup z decimální do textové podoby

String.toUpperCase()
Nahradí v řetězci malé písmena velkými

String.toLowerCase()
Nahradí v řetězci velké písmena malými

String.DectoHex()
Převede vstup z hexadecimální do textové podoby

String.removeAccents()
Nahradí v řetězci diakritické znaménka

2.2.11. Práce s poli

Prázdné podle se vytvoří pomocí závorek []. Pole lze naplnit elementy ["jedna", "dva", "tři"]. Pole je zároveň i objektem. Počet prvků získáte pomocí atributu length. Pole jsou indexována od nuly.
var pole = ["jedna", "dva", "tři"];
println("Tohle pole má " + pole.length + " prvky: " + pole);
println("První z nich je " + pole[0] + " a poslední " + pole[(pole.length - 1)]);

Array.append(elem)
Přidá prvek na konec pole.

Array.grepByRe(pattern)
V poli řetězců hledá ty, které odpovídají regulárnímu výrazu, a vrátí je v novém poli.

Array.grepByElemPropRe(property, pattern)
V poli objektů najde ty, jejichž vlastnost property odpovídá výrazu pattern a vrátí je v novém poli.

Array.dictionaryByElemProp(prop_name)
"Roztřídí" pole objektů podle jejich vlastnosti. Např. pole_zařízení.dictionaryByElemProp("kind") vrátí objekt o, kde v o.NIC budou všechna síťová zakončení a v o.DS_MODEM všechny docsisové modemy atd.

Array.filterEmpty()
Vynechá elementy, které jsou řetězci s nulovou délkou nebo mají hodnotu false.

2.3. Zařízení

Zařízení jsou reprezentována jako objekty typu Device.

Device(node_id)
Vytvoří objekt typu zařízení pro dané zařízení z MANGO
var jinyobjekt = Device(12345);
var tv_proxy = Device(jinyobjekt.par.IPTV_PROXY_ID);

target
Předdefinovaná proměnná s objektem pro zařízení, pro které byl chatskript zavolán. Synonymum je script_dev.

Každé zařízení (předpokládejme objekt dev) obsahuje následující entity (vlastnosti, objekty, funkce):

name (dev.name)
Jméno z MANGO.

kind (dev.kind)
Třída zařízení.

device_type (dev.device_type)
Typ zařízení.

customer_node_ct (dev.customer_node_ct)
ID lokace koncového zařízení.

par (dev.par)
Objekt s parametry zařízení. K jednotlivým parametrům lze přistupovat pomocí jejich interních názvů z MANGO
// Výpis hodnoty jednoho parametru
println("Ip adresa zařízení je v jeho parametru MANAG_IP: " + dev.par.MANAG_IP);

// Výpis všech parametrů
println("Tohle je moje zařízení: " + dev);

/*
Výstup pak může vypadat následovně:
Tohle je moje zařízení: {"id": "634636", "name": "HG8310 - 1 PORT_ONT", "kind": "ONT", "device_type": "ONT_OG_TEST", "customer_node_ct": "123", "parent_id": "548759", "parent_interface_id": "00000000000000986184", "par": {"ADR_LIST_NAME": , "CHATSCRIPT_UPDATE": "1", "DHCP_REMOTE_ID": , "INFRA_LOC": , "MAC_ADDRESS": , "MANAG_IP": "10.0.200.249", "NUMBER_AGREEMENT": , "NUMBER_PORT_ONT": "0", "ONT_DESCRIPTION": "TEST_1_PORT", "ONT_ID": "122", "ONT_INTEGRATED_SIP": , "ONT_ROUTER": , "PREV_PORT_NUMBER": , "SERIAL_NUMBER_ONT": "48575443CE74458B", "SERVICE_PORT_INDEX": , "SERVICE_PORT_INDEX_INT_SIP": , "SERVICE_PROFILE": , "SERVICE_STATE": "1", "SHAPER_ID": , "TRAFFIC_TABLE_DOWN_ID": "40", "TRAFFIC_TABLE_UP_ID": "40"}}
*/

children() (dev.children())
Vrátí pole připojených zařízení.

interfaces() (dev.interfaces())
Vrátí pole rozhraní na zařízení.

Rozhraní (předpokládejme objekt interface), jako jeden prvek pole vráceného funkcí interfaces() má následující entity:

par (interface.par)
Objekt s parametry rozhraní zařízení.

children() (interface.children())
Vrátí pole zařízení připojených na rozhraní.

2.4. Komunikace se zařízením, řízení skriptu

ask(str)
Pošle přes ssh nebo telnet příkaz na zařízení, vrátí jeho odpověď

say(str, expect)
Pošle přes ssh nebo telnet příkaz na zařízení, pokud jeho odpověď neodpovídá (nepovinnému) regulárnímu výrazu v expect, ukončí skript s chybou.

die(msg)
Explicitní ukončení skriptu s chybou.

exit()
Explicitní úspěšné ukončení skriptu.

success_event_name
Událost tohoto jména se vytvoří při úspěšném ukončení skriptu,

failure_event_name
Událost tohoto jména se vytvoří při chybovém ukončení skriptu,

2.5. SNMP funkce

Pro SNMP komunikaci používáme následující metody objektu reprezentujícího zařízení, s nímž chceme komunikovat.

dev.snmpget(oid, volby)
Vrátí výsledek SNMP GET OID oid na zařízení dev.
Parametr volby je nepovinný a lze ho vynechat; jeho popis je níže.

dev.snmpwalk(oid, volby)
Vrátí výsledek SNMP WALK OID oid na zařízení dev v podobě objektu, jehož atributy odpovídají OID.
Parametr volby je nepovinný a lze ho vynechat; jeho popis je níže.

Příklad:
println(target.snmpwalk("IF-MIB:ifName"));
/*
Vypíše něco jako:
{"IF-MIB::ifName.128": "InLoopBack0", "IF-MIB::ifName.262": "null0", "IF-MIB::ifName.263": "meth0", "IF-MIB::ifName.234930176": "ethernet0/6/0", "IF-MIB::ifName.234930240": "ethernet0/6/1"}
*/

Zajímavý příklad:
// Nadefinuje funkci, která vrátí objekt, který můžeme použít jako překladovou tabulku z jména rozhraní na jeho index.
func make_interface_lookup_table(dev) {
  var vysledek = {};
  every (oid, value: dev.snmpwalk("IF-MIB:ifName")) {
    // Následující řádek pomocí funkce split vrácené OID rozdělí oddělovačem "." na pole a vezme druhý prvek toho pole - tedy ten index rozhraní, který potřebujeme.
    var if_index = oid.split(".")[1];
    vysledek[if_index] = value;
  }
  return vysledek;
}

// Vypíše něco jako: "Index roxhrani meth0 je 263"
var if_indexy = make_interface_lookup_table(target);
println("Index rozhrani meth0 je " + if_indexy["meth0"]);

dev.snmpbulkget(oids, volby)
Vrátí objekt s výsledkem hromadného SNMP v2c GET. V parametru oids očekává objekt v podobě {"klíč1": "oid1", "klíč2": "oid2"}.
Vrátí pak objekt ve formátu {"klíč1": "výsledek1", "klíč2": "výsledek2"}.
Kromě vyjímečných případů je pravděpodobně lepší používat normální snmpget. Parametr volby je nepovinný a lze ho vynechat; jeho popis je níže.

dev.snmpset(oids, volby)
Provede SNMP set jednoho nebo více OID. V případě neúspěchu vrací objekt typu Error. Parametr volby je nepovinný a lze ho vynechat; jeho popis je níže.
// Pokus o restart DOCSIS modemu
if (typeof(target.snmpset({"1.3.6.1.2.1.69.1.1.3.0": 1})) == "Error") {
  println("Tak tohle nevyšlo...");
}

// Alternativně s udáním typu nastavované hodnoty (jako v příkazové řádce)
if (typeof(target.snmpset({"1.3.6.1.2.1.69.1.1.3.0": ["i", 1]})) == "Error") {
  println("Tak tohle nevyšlo...");
}

2.5.1. Společné volby pro snmp funkce

Nepovinný parametr volby SNMP metod popsaných výše umožňuje ovlivnit detaily jejich chování. Jde o objekt, může obsahovat níže popsané klíče.

Příklad:
target.snmpget("1.3.6.1.2.1.1.1.0", {"version": "1", "community": "public"});

version
Verze protokolu SNMP. Není-li zadána, výchozí je 2c.

community
SNMP komunita. Výchozí je "private" pro snmpset a "public" pro ostatní funkce.

binary
Boolean, je-li zadán a pravdivý, hodnoty typu HEX-STRING nejsou konvertovány do hexadecimální podoby, ale jsou vráceny binárně.

noCheckIndices
Vypne kontrolu rostoucích indexů na straně klienta. (Obdobné jako -Ir u příkazořádkového snmpget.) Může být potřeba při komunikaci s bugovitými zařízeními - symptom je, že snmpwalk vrátí hodnoty, ale když se pokusíme zeptat na nějakou konkrétní, dostaneme chybu "Index out of bonds". Za normálních okolností nepoužívat.

Pravděpodobně budou přibývat další volby.

2.6. Podpora pro tvorbu konfiguračních souborů

Buď můžeme konfiguraci sestavovat do řetězce v proměnné, nebo je možné použít objektu typu ConfigWriter. Příklad použití následuje.
var conf = ConfigWriter();
conf.append("root:x:0:0:root:/root:/bin/bash\n");
conf.append("daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin");

conf.writeToFile("/etc/passwd");
// ;-) Ve skutečnosti tento příklad v žádném případě neprojde (tedy jeho poslední řádek), protože metoda writeToFile umožňuje zapisovat pouze soubory v daných "bezpečných" cestách. Lze však vytvářet soubory v cestě /tftpboot, která je zároveň na DX serveru pomocí protokolu TFTP zpřístupněna pro zařízení.

note Až bude někdo chtít tuto funkcionalitu využít pro generování něčeho binárního (jako "MD5" pro modemy, kde je textový konfigurační soubor něčím "zkompilován"), pravděpodobně dojde k nějakému rozšíření.

Umíme také toto, ale bez té kompilace to není moc užitečné:
var jmeno_docasneho_souboru = conf.writeToTempFile();

2.7. Práce s IP adresami

parseIp(str)
Převede řetězec obsahují IP adresu na číslo (long integer).

ipToStr(num)
Převede IP adresu ve formě čísla (long integer) na textovou reprezentaci s tečkovým oddělovačem.

maskIpAddr(addr, mask)
Maskuje IP adresu dle zadaných parametrů.

ipPoolGet()
Dohledá pool IP adres podle IP adresy zařízení.

2.8. Specifické pro shaping

Device.shapingInfo()
 

Device.childrenShapingInfo(qtn_type)
 

ShapingInfo.childrenShapingInfo(qtn_type)
 

normalizeSpeed(s)
 

arrowbleft Zpět na:
Topic revision: r21 - 10 Jun 2020, JaroslavKopeck
This site is powered by FoswikiCopyright © by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding Foswiki? Send feedback