• source navigation  • diff markup  • identifier search  • freetext search  • 

Sources/procd/uxc-stack.uc

  1 'use strict';
  2 
  3 import { readfile, writefile, unlink, mkdir, rmdir, lsdir, chmod } from 'fs';
  4 const ubus = require('ubus');
  5 
  6 const REG_DIR   = '/tmp/run/uvol/.meta/uxc';
  7 const META_DIR  = '/tmp/run/uvol/.meta';
  8 const STACK_DIR = '/usr/share/uxc/stacks';
  9 
 10 let action = ARGV[0];
 11 let app    = ARGV[1];
 12 let hosts_file = null;
 13 
 14 if (action != 'up' && action != 'down' || !app)
 15         die('usage: uxc-stack <up|down> <app>');
 16 
 17 function read_json(path) {
 18         let raw = readfile(path);
 19         return raw ? json(raw) : null;
 20 }
 21 
 22 function uxc(...args) {
 23         let rc = system([ 'uxc', ...args ]);
 24         if (rc != 0)
 25                 printf('uxc-stack: uxc %s -> %d\n', join(' ', args), rc);
 26         return rc;
 27 }
 28 
 29 function qname(name) {
 30         return app + '.' + name;
 31 }
 32 
 33 
 34 let comp = { instances: [], secrets: {} };
 35 
 36 let api = {
 37         instance: function(name, spec) {
 38                 spec ??= {};
 39                 spec.name = name;
 40                 push(comp.instances, spec);
 41                 return spec;
 42         },
 43 
 44         generate: function(scope) {
 45                 comp.secrets[scope] = true;
 46                 return 'generate@' + app + '/' + scope;
 47         },
 48 };
 49 
 50 function compose_stack() {
 51         let tmpl_path = STACK_DIR + '/' + app + '.uc';
 52         let prog = loadfile(tmpl_path);
 53         if (!prog)
 54                 die('uxc-stack: cannot load stack template ' + tmpl_path);
 55         let compose = prog();
 56         if (type(compose) != 'function')
 57                 die('uxc-stack: ' + tmpl_path + ' must return a compose(api) function');
 58         compose(api);
 59 }
 60 
 61 function stack_members() {
 62         let out = [];
 63         for (let e in (lsdir(REG_DIR) ?? [])) {
 64                 if (substr(e, -5) != '.json')
 65                         continue;
 66                 let reg = read_json(REG_DIR + '/' + e);
 67                 if (reg && reg.origin == 'stack:' + app)
 68                         push(out, reg);
 69         }
 70         return out;
 71 }
 72 
 73 
 74 function fnv1a(s) {
 75         let h = 2166136261;
 76         for (let i = 0; i < length(s); i++) {
 77                 h ^= ord(s, i);
 78                 h = (h * 16777619) & 0xffffffff;
 79         }
 80         return h;
 81 }
 82 
 83 function idmap_offset(qn) {
 84         return ((fnv1a(qn) % 0x7000) + 1) * 0x10000;
 85 }
 86 
 87 function backhaul_net() {
 88         let slot = fnv1a(app) % 512;
 89         return sprintf('198.%d.%d', 18 + (slot >> 8), slot & 0xff);
 90 }
 91 
 92 function build_registration(inst) {
 93         if (!inst.image)
 94                 die('uxc-stack: instance ' + inst.name + ' declares no image');
 95 
 96         let img = read_json(REG_DIR + '/' + inst.image + '.json');
 97         if (!img)
 98                 die('uxc-stack: image "' + inst.image + '" not installed (need container-' + inst.image + ')');
 99 
100         let reg = {
101                 name: qname(inst.name),
102                 image: img.image,
103                 'image-digest': img['image-digest'],
104                 path: img.path,
105                 origin: 'stack:' + app,
106                 autostart: true,
107         };
108 
109         if (length(inst.volumes)) {
110                 let vols = [];
111                 for (let v in inst.volumes) {
112                         let p = split(v, ':');
113                         push(vols, { name: p[0], mountpoint: p[1], size: p[2] });
114                 }
115                 reg['data-volumes'] = vols;
116         }
117         if (inst.overlay)
118                 reg['temp-overlay-size'] = inst.overlay;
119         else if (img['temp-overlay-size'])
120                 reg['temp-overlay-size'] = img['temp-overlay-size'];
121         else if (img['overlay-size'])
122                 reg['overlay-size'] = img['overlay-size'];
123         if (inst.env)
124                 reg.initenv = inst.env;
125 
126         let prov = [];
127         if (inst.provision || inst.secrets) {
128                 let pdir = META_DIR + '/stacks/' + app + '/' + inst.name;
129                 mkdir(META_DIR + '/stacks/' + app, 0700);
130                 mkdir(pdir, 0700);
131                 for (let dest in (inst.provision ?? {})) {
132                         let src = pdir + '/' + replace(dest, /[^A-Za-z0-9._-]/g, '_');
133                         if (writefile(src, inst.provision[dest]) == null)
134                                 die('uxc-stack: cannot write provisioned file ' + src);
135                         chmod(src, 0644);
136                         push(prov, { source: src, destination: dest });
137                 }
138                 for (let dest in (inst.secrets ?? {}))
139                         push(prov, { source: META_DIR + '/secrets/' + app + '/' +
140                                      inst.secrets[dest] + '/value', destination: dest, secret: true });
141         }
142         if (length(prov))
143                 reg.provision = prov;
144 
145         reg['idmap-offset'] = sprintf('%d', idmap_offset(reg.name));
146 
147         if (hosts_file)
148                 reg['hosts-file'] = hosts_file;
149 
150         return reg;
151 }
152 
153 function instance_up(inst) {
154         let reg = build_registration(inst);
155         let path = REG_DIR + '/' + reg.name + '.json';
156         if (writefile(path, sprintf('%.J\n', reg)) == null)
157                 die('uxc-stack: cannot write ' + path);
158 }
159 
160 function bringup() {
161         ubus.call({ object: 'service', method: 'event',
162                     data: { type: 'uxc.bringup', data: {} } });
163         let err = ubus.error();
164         if (err)
165                 die('uxc-stack: cannot trigger bring-up: ' + err);
166 }
167 
168 
169 function backhaul_prepare() {
170         let net = backhaul_net();
171         let hdir = META_DIR + '/stacks/' + app;
172         let hosts = '127.0.0.1\tlocalhost\n::1\tlocalhost ip6-localhost ip6-loopback\n';
173         let i = 0, ordered, inst, side;
174 
175         ordered = sort(comp.instances, function(a, b) {
176                 return (a.name < b.name) ? -1 : (a.name > b.name) ? 1 : 0;
177         });
178         for (inst in ordered) {
179                 inst.bh_address = net + '.' + (i + 1);
180                 hosts += inst.bh_address + '\t' + inst.name + '\n';
181                 i++;
182         }
183 
184         mkdir(META_DIR + '/stacks', 0700);
185         mkdir(hdir, 0700);
186         writefile(hdir + '/hosts', hosts);
187         hosts_file = hdir + '/hosts';
188 
189         for (inst in comp.instances) {
190                 side = {
191                         'org.openwrt.network.backhaul': app,
192                         'org.openwrt.network.backhaul-address': inst.bh_address,
193                 };
194                 if (inst.access) {
195                         let f = split(inst.access, ' ');
196                         side['org.openwrt.network.attach'] = f[0];
197                         for (let n = 1; n < length(f); n++) {
198                                 let kv = split(f[n], '=');
199                                 if (kv[0] == 'egress')
200                                         side['org.openwrt.network.egress'] = kv[1];
201                                 else if (kv[0] == 'ingress')
202                                         side['org.openwrt.network.ingress'] = kv[1];
203                                 else if (kv[0] == 'host')
204                                         side['org.openwrt.network.host'] = kv[1];
205                         }
206                 }
207                 if (inst.proto)
208                         side['org.openwrt.network.proto'] = inst.proto;
209                 if (inst.proto6)
210                         side['org.openwrt.network.proto6'] = inst.proto6;
211                 if (inst.ip6ifaceid)
212                         side['org.openwrt.network.ip6ifaceid'] = inst.ip6ifaceid;
213                 writefile(REG_DIR + '/' + qname(inst.name) + '.annotations', sprintf('%J\n', side));
214         }
215 }
216 
217 
218 if (action == 'up') {
219         compose_stack();
220         backhaul_prepare();
221         for (let inst in comp.instances)
222                 instance_up(inst);
223         bringup();
224 } else {
225         let members = stack_members();
226         let kept = [];
227 
228         for (let reg in members) {
229                 let krc = system([ 'uxc', 'kill', reg.name ]);
230                 if (krc != 0 && krc != 254)
231                         printf('uxc-stack: uxc kill %s -> %d\n', reg.name, krc);
232                 uxc('delete', reg.name);
233                 unlink(REG_DIR + '/' + reg.name + '.json');
234                 unlink(REG_DIR + '/' + reg.name + '.annotations');
235                 system([ 'rm', '-rf', META_DIR + '/uxc/state/' + reg.name ]);
236                 for (let v in (reg['data-volumes'] ?? []))
237                         push(kept, reg.name + '.' + v.name);
238         }
239 
240         unlink(META_DIR + '/stacks/' + app + '/hosts');
241         rmdir(META_DIR + '/stacks/' + app);
242 
243         let secrets = lsdir(META_DIR + '/secrets/' + app) ?? [];
244 
245         if (length(kept)) {
246                 warn('uxc-stack: kept data volume(s) for ' + app +
247                      '; remove manually if the data is no longer needed:\n');
248                 for (let vn in kept)
249                         warn('  uvol remove ' + vn + '\n');
250         }
251         if (length(secrets)) {
252                 warn('uxc-stack: kept generated secret(s) for ' + app +
253                      ' (paired with the data above; reused on reinstall); remove with:\n');
254                 warn('  rm -rf ' + META_DIR + '/secrets/' + app + '\n');
255         }
256 }

This page was automatically generated by LXR 0.3.1.  •  OpenWrt