2 # Author: Peter K. Lee (peter@corenova.com)
4 # All rights reserved. This program and the accompanying materials
5 # are made available under the terms of the Apache License, Version 2.0
6 # which accompanies this distribution, and is available at
7 # http://www.apache.org/licenses/LICENSE-2.0
11 (input, output, done) ->
12 # 1. create the reservation record (empty)
13 reservation = @create 'ResourceReservation'
14 reservations = @access 'promise.reservations'
16 # 2. update the record with requested input
17 reservation.invoke 'update', input.get()
19 # 3. save the record and add to list
23 output.set result: 'ok', message: 'reservation request accepted'
24 output.set 'reservation-id', res.id
27 output.set result: 'error', message: err
30 output.set result: 'conflict', message: err
34 (input, output, done) ->
36 query.capacity = 'reserved'
37 @invoke 'query-capacity', query
39 output.set 'reservations', res.get 'collections'
40 output.set 'utilization', res.get 'utilization'
45 (input, output, done) ->
46 # TODO: we shouldn't need this... need to check why leaf mandatory: true not being enforced
47 unless (input.get 'reservation-id')?
48 output.set result: 'error', message: "must provide 'reservation-id' parameter"
51 # 1. find the reservation
52 reservation = @find 'ResourceReservation', input.get 'reservation-id'
54 output.set result: 'error', message: 'no reservation found for specified identifier'
57 # 2. update the record with requested input
58 reservation.invoke 'update', input.get()
60 # 3. save the updated record
63 output.set result: 'ok', message: 'reservation update successful'
66 output.set result: 'error', message: err
69 output.set result: 'conflict', message: err
73 (input, output, done) ->
74 # 1. find the reservation
75 reservation = @find 'ResourceReservation', input.get 'reservation-id'
77 output.set result: 'error', message: 'no reservation found for specified identifier'
80 # 2. destroy all traces of this reservation
83 (@access 'promise.reservations').remove reservation.id
84 output.set 'result', 'ok'
85 output.set 'message', 'reservation canceled'
88 output.set 'result', 'error'
89 output.set 'message', e
93 (input, output, done) ->
94 # 1. we gather up all collections that match the specified window
95 window = input.get 'window'
96 metric = input.get 'capacity'
98 collections = switch metric
99 when 'total' then [ 'ResourcePool' ]
100 when 'reserved' then [ 'ResourceReservation' ]
101 when 'usage' then [ 'ResourceAllocation' ]
102 when 'available' then [ 'ResourcePool', 'ResourceReservation', 'ResourceAllocation' ]
104 matches = collections.reduce ((a, name) =>
106 start: (value) -> (not window.end?) or (new Date value) <= (new Date window.end)
107 end: (value) -> (not window.start?) or (new Date value) >= (new Date window.start)
112 if window.scope is 'exclusive'
113 # yes, we CAN query filter in one shot above but this makes logic cleaner...
114 matches = matches.where
115 start: (value) -> (not window.start?) or (new Date value) >= (new Date window.start)
116 end: (value) -> (not window.end?) or (new Date value) <= (new Date window.end)
118 # exclude any identifiers specified
119 matches = matches.without id: (input.get 'without')
121 if metric is 'available'
122 # excludes allocations with reservation property set (to prevent double count)
123 matches = matches.without reservation: (v) -> v?
125 output.set 'collections', matches
126 unless (input.get 'show-utilization') is true
129 # 2. we calculate the deltas based on start/end times of each match item
130 deltas = matches.reduce ((a, entry) ->
133 [ skey, ekey ] = [ (b.start.split 'T')[0], (b.end.split 'T')[0] ]
134 a[skey] ?= count: 0, capacity: {}
135 a[ekey] ?= count: 0, capacity: {}
139 for k, v of b.capacity when v?
140 a[skey].capacity[k] ?= 0
141 a[ekey].capacity[k] ?= 0
142 if entry.name is 'ResourcePool'
143 a[skey].capacity[k] += v
144 a[ekey].capacity[k] -= v
146 a[skey].capacity[k] -= v
147 a[ekey].capacity[k] += v
151 # 3. we then sort the timestamps and aggregate the deltas
152 last = count: 0, capacity: {}
153 usages = for timestamp in Object.keys(deltas).sort() when timestamp isnt 'infinite'
154 entry = deltas[timestamp]
155 entry.timestamp = (new Date timestamp).toJSON()
156 entry.count += last.count
157 for k, v of entry.capacity
158 entry.capacity[k] += (last.capacity[k] ? 0)
162 output.set 'utilization', usages
166 (input, output, done) ->
167 pool = @create 'ResourcePool', input.get()
170 (@access 'promise.pools').push res
171 output.set result: 'ok', message: 'capacity increase successful'
172 output.set 'pool-id', res.id
175 output.set result: 'error', message: e
179 (input, output, done) ->
180 request = input.get()
181 for k, v of request.capacity
182 request.capacity[k] = -v
183 pool = @create 'ResourcePool', request
186 (@access 'promise.pools').push res
187 output.set result: 'ok', message: 'capacity decrease successful'
188 output.set 'pool-id', res.id
191 output.set result: 'error', message: e
194 # TEMPORARY (should go into VIM-specific module)
196 (input, output, done) ->
197 pid = input.get 'provider-id'
199 provider = @find 'ResourceProvider', pid
201 output.set result: 'error', message: "no matching provider found for specified identifier: #{pid}"
204 provider = (@find 'ResourceProvider')[0]
206 output.set result: 'error', message: "no available provider found for create-instance"
209 # calculate required capacity based on 'flavor' and other params
210 flavor = provider.access "services.compute.flavors.#{input.get 'flavor'}"
212 output.set result: 'error', message: "no such flavor found for specified identifier: #{pid}"
217 cores: flavor.get 'vcpus'
218 ram: flavor.get 'ram'
219 gigabytes: flavor.get 'disk'
221 rid = input.get 'reservation-id'
223 reservation = @find 'ResourceReservation', rid
225 output.set result: 'error', message: 'no valid reservation found for specified identifier'
227 unless (reservation.get 'active') is true
228 output.set result: 'error', message: "reservation is currently not active"
230 available = reservation.get 'remaining'
232 available = @get 'promise.capacity.available'
234 # TODO: need to verify whether 'provider' associated with this 'reservation'
236 for k, v of required when v? and !!v
237 unless available[k] >= v
238 output.set result: 'conflict', message: "required #{k}=#{v} exceeds available #{available[k]}"
241 @create 'ResourceAllocation',
246 url = provider.get 'services.compute.endpoint'
249 name: input.get 'name'
250 imageRef: input.get 'image'
251 flavorRef: input.get 'flavor'
252 networks = (input.get 'networks').filter (x) -> x? and !!x
253 if networks.length > 0
254 payload.server.networks = networks.map (x) -> uuid: x
256 request = @parent.require 'superagent'
258 .post "#{url}/servers"
260 .set 'X-Auth-Token', provider.get 'token'
261 .set 'Accept', 'application/json'
266 return done res.error
267 #console.log JSON.stringify res.body, null, 2
268 instance.set 'instance-ref',
270 server: res.body.server.id
271 (@access 'promise.allocations').push instance
272 output.set result: 'ok', message: 'create-instance request accepted'
273 output.set 'instance-id', instance.id
277 output.set result: 'error', mesage: err
281 (input, output, done) ->
282 # 1. find the instance
283 instance = @find 'ResourceAllocation', input.get 'instance-id'
285 output.set result: 'error', message: 'no allocation found for specified identifier'
288 # 2. destroy all traces of this instance
291 # always remove internally
292 (@access 'promise.allocations').remove instance.id
293 ref = instance.get 'instance-ref'
294 provider = (@access "promise.providers.#{ref.provider}")
295 url = provider.get 'services.compute.endpoint'
296 request = @parent.require 'superagent'
298 .delete "#{url}/servers/#{ref.server}"
299 .set 'X-Auth-Token', provider.get 'token'
300 .set 'Accept', 'application/json'
304 return done res.error
305 output.set 'result', 'ok'
306 output.set 'message', 'instance destroyed and resource released back to pool'
310 output.set 'result', 'error'
311 output.set 'message', e
314 # TEMPORARY (should go into VIM-specific module)
316 (input, output, done) ->
318 request = app.require 'superagent'
320 payload = switch input.get 'provider-type'
323 tenantId: input.get 'tenant.id'
324 tenantName: input.get 'tenant.name'
325 passwordCredentials: input.get 'username', 'password'
328 return done 'Sorry, only openstack supported at this time'
330 url = input.get 'endpoint'
331 switch input.get 'strategy'
332 when 'keystone', 'oauth'
333 url += '/tokens' unless /\/tokens$/.test url
335 providers = @access 'promise.providers'
339 .set 'Accept', 'application/json'
341 if err? or !res.ok then return done res.error
342 #console.log JSON.stringify res.body, null, 2
343 access = res.body.access
344 provider = @create 'ResourceProvider',
345 token: access?.token?.id
346 name: access?.token?.tenant?.name
347 provider.invoke 'update', access.serviceCatalog
352 output.set 'result', 'ok'
353 output.set 'provider-id', res.id
356 output.set 'error', message: err
359 output.set 'error', message: err
363 # @invoke 'add-provider', (input.get 'endpoint', 'region', 'username', 'password')
365 # (@access 'promise.providers').push res
366 # output.set 'result', 'ok'
367 # output.set 'provider-id', res.id