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
.
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í.
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)
Zpět na: