UNPKG

3.63 kBJavaScriptView Raw
1import fs from 'fs'
2import yaml from 'js-yaml'
3import scheduler from '@zombiec0rn/zombie-scheduler'
4import zdiff from '@zombiec0rn/zombie-service-diff'
5import assign from 'object.assign'
6import uniq from 'lodash.uniq'
7import find from 'lodash.find'
8import randomString from 'random-string'
9import * as utils from './utils'
10
11function scrambleFingerprint(service) {
12 service.previousFingerprint = service.fingerprint
13 service.fingerprint = randomString()
14}
15
16function getCurrent(nodes) {
17 let services = nodes.reduce((services, node) => {
18 let nodeServices = utils.extractServices(node)
19 delete node.services
20 return services.concat(nodeServices)
21 }, [])
22
23 // Detect duplicate fingerprints
24 // Randomize duplicate fingerprint (make sure they are re-evaluated)
25 let duplicates = utils.detectDuplicateFingerprints(services)
26 services.forEach(s => {
27 if (duplicates.indexOf(s.fingerprint) >= 0) {
28 scrambleFingerprint(s)
29 }
30 })
31
32 return services
33}
34
35export default function makePlan(nodes, _wanted) {
36 /* PREPARING */
37 // Extracting current services from nodes (with fingerprints)
38 // Adding fingerprints to wanted
39 let current = getCurrent(nodes)
40 let wanted = _wanted.map(s => {
41 s.fingerprint = zdiff.fingerprint(s)
42 return s
43 })
44
45 /* TAGS & PLACEMENTS */
46 let tags = uniq(nodes.reduce((t, n) => {
47 return t.concat(n.tags || [])
48 },[]).concat(wanted.reduce((t,s) => {
49 return t.concat(s.placement || [])
50 },[])))
51
52 let tagmap = tags.reduce((m, t) => {
53 m[t] = {}
54 m[t].nodes = nodes.filter(n => (n.tags || []).indexOf(t) >= 0)
55 m[t].wanted = wanted.filter(s => (s.placement || []).indexOf(t) >= 0)
56 return m
57 },{})
58
59 let wantedmap = wanted.reduce((m, s) => {
60 m[s.id] = s
61 return m
62 },{})
63
64 // TODO: Sometimes we need to place on all tags, or.. ?
65 // Perhaps we need to add another property or expand tag (stick:gateway)
66 // to support both "on one of these" and "on all of these"
67 // Right now I think we only support "on one of these"
68
69 /* TAG & PLACEMENT SCHEDULING */
70 var tagadds = []
71 tags.forEach(t => {
72 let tagplan = scheduler.spread(tagmap[t].nodes, tagmap[t].wanted)
73 tagplan.add.forEach(s => {
74 assign(s, wantedmap[s.id])
75 })
76 tagadds = tagadds.concat(tagplan.add)
77 })
78
79 /* FINAL SCHEDULING */
80
81 // We trust tagadds over current (we might have changed tags)
82 // If a tagadd is in current we randomize the fingerprint to ensure re-eval
83 current.forEach(s => {
84 let tagadd = find(tagadds, { id: s.id })
85 if (!tagadd) return
86 if (tagadd.host.hostname != s.host.hostname) {
87 // Host mismatch
88 scrambleFingerprint(s)
89 }
90 else if (tagadd.fingerprint != s.fingerprint) {
91 // Fingerprint mismatch
92 // Modifications to the same service - still tagged to same host
93 scrambleFingerprint(s)
94 }
95 else {
96 // No mismatch - keep in current / remove from tagadds
97 tagadds = tagadds.filter(ta => ta.id != s.id)
98 }
99 })
100
101 let plan = scheduler.spread(nodes, wanted, current.concat(tagadds))
102 let tagaddids = tagadds.map(s => s.id)
103 plan.keep = plan.keep.filter(s => {
104 let istagadd = tagaddids.indexOf(s.id) >= 0
105 if (istagadd) plan.add.push(s) // unshift ? make room for tagged services first ??
106 return !istagadd
107 })
108
109 /* CLEANUPS */
110 // Add the fingerprint as env variable and remove property
111 plan.add.forEach(s => {
112 if (!s.env) s.env = []
113 s.env.push(`ZOMBIE_SWARM_FINGERPRINT=${s.fingerprint}`)
114 delete s.fingerprint
115 })
116 //console.log('PLAN', JSON.stringify(plan, null, 2))
117
118 return plan
119}