3 (input, output, done) ->
4 # 1. create the reservation record (empty)
5 reservation = @create 'ResourceReservation'
6 reservations = @access 'promise.reservations'
8 # 2. update the record with requested input
9 reservation.invoke 'update', input.get()
11 # 3. save the record and add to list
15 output.set result: 'ok', message: 'reservation request accepted'
16 output.set 'reservation-id', res.id
19 output.set result: 'error', message: err
22 output.set result: 'conflict', message: err
26 (input, output, done) ->
28 query.capacity = 'reserved'
29 @invoke 'query-capacity', query
31 output.set 'reservations', res.get 'collections'
32 output.set 'utilization', res.get 'utilization'
37 (input, output, done) ->
38 # TODO: we shouldn't need this... need to check why leaf mandatory: true not being enforced
39 unless (input.get 'reservation-id')?
40 output.set result: 'error', message: "must provide 'reservation-id' parameter"
43 # 1. find the reservation
44 reservation = @find 'ResourceReservation', input.get 'reservation-id'
46 output.set result: 'error', message: 'no reservation found for specified identifier'
49 # 2. update the record with requested input
50 reservation.invoke 'update', input.get()
52 # 3. save the updated record
55 output.set result: 'ok', message: 'reservation update successful'
58 output.set result: 'error', message: err
61 output.set result: 'conflict', message: err
65 (input, output, done) ->
66 # 1. find the reservation
67 reservation = @find 'ResourceReservation', input.get 'reservation-id'
69 output.set result: 'error', message: 'no reservation found for specified identifier'
72 # 2. destroy all traces of this reservation
75 (@access 'promise.reservations').remove reservation.id
76 output.set 'result', 'ok'
77 output.set 'message', 'reservation canceled'
80 output.set 'result', 'error'
81 output.set 'message', e
85 (input, output, done) ->
86 # 1. we gather up all collections that match the specified window
87 window = input.get 'window'
88 metric = input.get 'capacity'
90 collections = switch metric
91 when 'total' then [ 'ResourcePool' ]
92 when 'reserved' then [ 'ResourceReservation' ]
93 when 'usage' then [ 'ResourceAllocation' ]
94 when 'available' then [ 'ResourcePool', 'ResourceReservation', 'ResourceAllocation' ]
96 matches = collections.reduce ((a, name) =>
98 start: (value) -> (not window.end?) or (new Date value) <= (new Date window.end)
99 end: (value) -> (not window.start?) or (new Date value) >= (new Date window.start)
104 if window.scope is 'exclusive'
105 # yes, we CAN query filter in one shot above but this makes logic cleaner...
106 matches = matches.where
107 start: (value) -> (not window.start?) or (new Date value) >= (new Date window.start)
108 end: (value) -> (not window.end?) or (new Date value) <= (new Date window.end)
110 # exclude any identifiers specified
111 matches = matches.without id: (input.get 'without')
113 if metric is 'available'
114 # excludes allocations with reservation property set (to prevent double count)
115 matches = matches.without reservation: (v) -> v?
117 output.set 'collections', matches
118 unless (input.get 'show-utilization') is true
121 # 2. we calculate the deltas based on start/end times of each match item
122 deltas = matches.reduce ((a, entry) ->
125 [ skey, ekey ] = [ (b.start.split 'T')[0], (b.end.split 'T')[0] ]
126 a[skey] ?= count: 0, capacity: {}
127 a[ekey] ?= count: 0, capacity: {}
131 for k, v of b.capacity when v?
132 a[skey].capacity[k] ?= 0
133 a[ekey].capacity[k] ?= 0
134 if entry.name is 'ResourcePool'
135 a[skey].capacity[k] += v
136 a[ekey].capacity[k] -= v
138 a[skey].capacity[k] -= v
139 a[ekey].capacity[k] += v
143 # 3. we then sort the timestamps and aggregate the deltas
144 last = count: 0, capacity: {}
145 usages = for timestamp in Object.keys(deltas).sort() when timestamp isnt 'infinite'
146 entry = deltas[timestamp]
147 entry.timestamp = (new Date timestamp).toJSON()
148 entry.count += last.count
149 for k, v of entry.capacity
150 entry.capacity[k] += (last.capacity[k] ? 0)
154 output.set 'utilization', usages
158 (input, output, done) ->
159 pool = @create 'ResourcePool', input.get()
162 (@access 'promise.pools').push res
163 output.set result: 'ok', message: 'capacity increase successful'
164 output.set 'pool-id', res.id
167 output.set result: 'error', message: e
171 (input, output, done) ->
172 request = input.get()
173 for k, v of request.capacity
174 request.capacity[k] = -v
175 pool = @create 'ResourcePool', request
178 (@access 'promise.pools').push res
179 output.set result: 'ok', message: 'capacity decrease successful'
180 output.set 'pool-id', res.id
183 output.set result: 'error', message: e
186 # TEMPORARY (should go into VIM-specific module)
188 (input, output, done) ->
189 pid = input.get 'provider-id'
191 provider = @find 'ResourceProvider', pid
193 output.set result: 'error', message: "no matching provider found for specified identifier: #{pid}"
196 provider = (@find 'ResourceProvider')[0]
198 output.set result: 'error', message: "no available provider found for create-instance"
201 # calculate required capacity based on 'flavor' and other params
202 flavor = provider.access "services.compute.flavors.#{input.get 'flavor'}"
204 output.set result: 'error', message: "no such flavor found for specified identifier: #{pid}"
209 cores: flavor.get 'vcpus'
210 ram: flavor.get 'ram'
211 gigabytes: flavor.get 'disk'
213 rid = input.get 'reservation-id'
215 reservation = @find 'ResourceReservation', rid
217 output.set result: 'error', message: 'no valid reservation found for specified identifier'
219 unless (reservation.get 'active') is true
220 output.set result: 'error', message: "reservation is currently not active"
222 available = reservation.get 'remaining'
224 available = @get 'promise.capacity.available'
226 # TODO: need to verify whether 'provider' associated with this 'reservation'
228 for k, v of required when v? and !!v
229 unless available[k] >= v
230 output.set result: 'conflict', message: "required #{k}=#{v} exceeds available #{available[k]}"
233 @create 'ResourceAllocation',
238 url = provider.get 'services.compute.endpoint'
241 name: input.get 'name'
242 imageRef: input.get 'image'
243 flavorRef: input.get 'flavor'
244 networks = (input.get 'networks').filter (x) -> x? and !!x
245 if networks.length > 0
246 payload.server.networks = networks.map (x) -> uuid: x
248 request = @parent.require 'superagent'
250 .post "#{url}/servers"
252 .set 'X-Auth-Token', provider.get 'token'
253 .set 'Accept', 'application/json'
258 return done res.error
259 #console.log JSON.stringify res.body, null, 2
260 instance.set 'instance-ref',
262 server: res.body.server.id
263 (@access 'promise.allocations').push instance
264 output.set result: 'ok', message: 'create-instance request accepted'
265 output.set 'instance-id', instance.id
269 output.set result: 'error', mesage: err
273 (input, output, done) ->
274 # 1. find the instance
275 instance = @find 'ResourceAllocation', input.get 'instance-id'
277 output.set result: 'error', message: 'no allocation found for specified identifier'
280 # 2. destroy all traces of this instance
283 # always remove internally
284 (@access 'promise.allocations').remove instance.id
285 ref = instance.get 'instance-ref'
286 provider = (@access "promise.providers.#{ref.provider}")
287 url = provider.get 'services.compute.endpoint'
288 request = @parent.require 'superagent'
290 .delete "#{url}/servers/#{ref.server}"
291 .set 'X-Auth-Token', provider.get 'token'
292 .set 'Accept', 'application/json'
296 return done res.error
297 output.set 'result', 'ok'
298 output.set 'message', 'instance destroyed and resource released back to pool'
302 output.set 'result', 'error'
303 output.set 'message', e
306 # TEMPORARY (should go into VIM-specific module)
308 (input, output, done) ->
310 request = app.require 'superagent'
312 payload = switch input.get 'provider-type'
315 tenantId: input.get 'tenant.id'
316 tenantName: input.get 'tenant.name'
317 passwordCredentials: input.get 'username', 'password'
320 return done 'Sorry, only openstack supported at this time'
322 url = input.get 'endpoint'
323 switch input.get 'strategy'
324 when 'keystone', 'oauth'
325 url += '/tokens' unless /\/tokens$/.test url
327 providers = @access 'promise.providers'
331 .set 'Accept', 'application/json'
333 if err? or !res.ok then return done res.error
334 #console.log JSON.stringify res.body, null, 2
335 access = res.body.access
336 provider = @create 'ResourceProvider',
337 token: access?.token?.id
338 name: access?.token?.tenant?.name
339 provider.invoke 'update', access.serviceCatalog
344 output.set 'result', 'ok'
345 output.set 'provider-id', res.id
348 output.set 'error', message: err
351 output.set 'error', message: err
355 # @invoke 'add-provider', (input.get 'endpoint', 'region', 'username', 'password')
357 # (@access 'promise.providers').push res
358 # output.set 'result', 'ok'
359 # output.set 'provider-id', res.id