summaryrefslogtreecommitdiffstats
path: root/source/spec
diff options
context:
space:
mode:
Diffstat (limited to 'source/spec')
-rw-r--r--source/spec/openstack-intents.coffee6
-rw-r--r--source/spec/promise-intents.coffee360
-rw-r--r--source/spec/promise-module.coffee72
3 files changed, 438 insertions, 0 deletions
diff --git a/source/spec/openstack-intents.coffee b/source/spec/openstack-intents.coffee
new file mode 100644
index 0000000..f1a10d2
--- /dev/null
+++ b/source/spec/openstack-intents.coffee
@@ -0,0 +1,6 @@
+request = require 'superagent'
+
+module.exports =
+ 'create-tenant':
+ (input, output, done) ->
+ # TODO - this requires OS-KSADM extension
diff --git a/source/spec/promise-intents.coffee b/source/spec/promise-intents.coffee
new file mode 100644
index 0000000..6ad3ae7
--- /dev/null
+++ b/source/spec/promise-intents.coffee
@@ -0,0 +1,360 @@
+module.exports =
+ 'create-reservation':
+ (input, output, done) ->
+ # 1. create the reservation record (empty)
+ reservation = @create 'ResourceReservation'
+ reservations = @access 'promise.reservations'
+
+ # 2. update the record with requested input
+ reservation.invoke 'update', input.get()
+ .then (res) ->
+ # 3. save the record and add to list
+ res.save()
+ .then ->
+ reservations.push res
+ output.set result: 'ok', message: 'reservation request accepted'
+ output.set 'reservation-id', res.id
+ done()
+ .catch (err) ->
+ output.set result: 'error', message: err
+ done()
+ .catch (err) ->
+ output.set result: 'conflict', message: err
+ done()
+
+ 'query-reservation':
+ (input, output, done) ->
+ query = input.get()
+ query.capacity = 'reserved'
+ @invoke 'query-capacity', query
+ .then (res) ->
+ output.set 'reservations', res.get 'collections'
+ output.set 'utilization', res.get 'utilization'
+ done()
+ .catch (e) -> done e
+
+ 'update-reservation':
+ (input, output, done) ->
+ # TODO: we shouldn't need this... need to check why leaf mandatory: true not being enforced
+ unless (input.get 'reservation-id')?
+ output.set result: 'error', message: "must provide 'reservation-id' parameter"
+ return done()
+
+ # 1. find the reservation
+ reservation = @find 'ResourceReservation', input.get 'reservation-id'
+ unless reservation?
+ output.set result: 'error', message: 'no reservation found for specified identifier'
+ return done()
+
+ # 2. update the record with requested input
+ reservation.invoke 'update', input.get()
+ .then (res) ->
+ # 3. save the updated record
+ res.save()
+ .then ->
+ output.set result: 'ok', message: 'reservation update successful'
+ done()
+ .catch (err) ->
+ output.set result: 'error', message: err
+ done()
+ .catch (err) ->
+ output.set result: 'conflict', message: err
+ done()
+
+ 'cancel-reservation':
+ (input, output, done) ->
+ # 1. find the reservation
+ reservation = @find 'ResourceReservation', input.get 'reservation-id'
+ unless reservation?
+ output.set result: 'error', message: 'no reservation found for specified identifier'
+ return done()
+
+ # 2. destroy all traces of this reservation
+ reservation.destroy()
+ .then =>
+ (@access 'promise.reservations').remove reservation.id
+ output.set 'result', 'ok'
+ output.set 'message', 'reservation canceled'
+ done()
+ .catch (e) ->
+ output.set 'result', 'error'
+ output.set 'message', e
+ done()
+
+ 'query-capacity':
+ (input, output, done) ->
+ # 1. we gather up all collections that match the specified window
+ window = input.get 'window'
+ metric = input.get 'capacity'
+
+ collections = switch metric
+ when 'total' then [ 'ResourcePool' ]
+ when 'reserved' then [ 'ResourceReservation' ]
+ when 'usage' then [ 'ResourceAllocation' ]
+ when 'available' then [ 'ResourcePool', 'ResourceReservation', 'ResourceAllocation' ]
+
+ matches = collections.reduce ((a, name) =>
+ res = @find name,
+ start: (value) -> (not window.end?) or (new Date value) <= (new Date window.end)
+ end: (value) -> (not window.start?) or (new Date value) >= (new Date window.start)
+ enabled: true
+ a.concat res...
+ ), []
+
+ if window.scope is 'exclusive'
+ # yes, we CAN query filter in one shot above but this makes logic cleaner...
+ matches = matches.where
+ start: (value) -> (not window.start?) or (new Date value) >= (new Date window.start)
+ end: (value) -> (not window.end?) or (new Date value) <= (new Date window.end)
+
+ # exclude any identifiers specified
+ matches = matches.without id: (input.get 'without')
+
+ if metric is 'available'
+ # excludes allocations with reservation property set (to prevent double count)
+ matches = matches.without reservation: (v) -> v?
+
+ output.set 'collections', matches
+ unless (input.get 'show-utilization') is true
+ return done()
+
+ # 2. we calculate the deltas based on start/end times of each match item
+ deltas = matches.reduce ((a, entry) ->
+ b = entry.get()
+ b.end ?= 'infiniteT'
+ [ skey, ekey ] = [ (b.start.split 'T')[0], (b.end.split 'T')[0] ]
+ a[skey] ?= count: 0, capacity: {}
+ a[ekey] ?= count: 0, capacity: {}
+ a[skey].count += 1
+ a[ekey].count -= 1
+
+ for k, v of b.capacity when v?
+ a[skey].capacity[k] ?= 0
+ a[ekey].capacity[k] ?= 0
+ if entry.name is 'ResourcePool'
+ a[skey].capacity[k] += v
+ a[ekey].capacity[k] -= v
+ else
+ a[skey].capacity[k] -= v
+ a[ekey].capacity[k] += v
+ return a
+ ), {}
+
+ # 3. we then sort the timestamps and aggregate the deltas
+ last = count: 0, capacity: {}
+ usages = for timestamp in Object.keys(deltas).sort() when timestamp isnt 'infinite'
+ entry = deltas[timestamp]
+ entry.timestamp = (new Date timestamp).toJSON()
+ entry.count += last.count
+ for k, v of entry.capacity
+ entry.capacity[k] += (last.capacity[k] ? 0)
+ last = entry
+ entry
+
+ output.set 'utilization', usages
+ done()
+
+ 'increase-capacity':
+ (input, output, done) ->
+ pool = @create 'ResourcePool', input.get()
+ pool.save()
+ .then (res) =>
+ (@access 'promise.pools').push res
+ output.set result: 'ok', message: 'capacity increase successful'
+ output.set 'pool-id', res.id
+ done()
+ .catch (e) ->
+ output.set result: 'error', message: e
+ done()
+
+ 'decrease-capacity':
+ (input, output, done) ->
+ request = input.get()
+ for k, v of request.capacity
+ request.capacity[k] = -v
+ pool = @create 'ResourcePool', request
+ pool.save()
+ .then (res) =>
+ (@access 'promise.pools').push res
+ output.set result: 'ok', message: 'capacity decrease successful'
+ output.set 'pool-id', res.id
+ done()
+ .catch (e) ->
+ output.set result: 'error', message: e
+ done()
+
+ # TEMPORARY (should go into VIM-specific module)
+ 'create-instance':
+ (input, output, done) ->
+ pid = input.get 'provider-id'
+ if pid?
+ provider = @find 'ResourceProvider', pid
+ unless provider?
+ output.set result: 'error', message: "no matching provider found for specified identifier: #{pid}"
+ return done()
+ else
+ provider = (@find 'ResourceProvider')[0]
+ unless provider?
+ output.set result: 'error', message: "no available provider found for create-instance"
+ return done()
+
+ # calculate required capacity based on 'flavor' and other params
+ flavor = provider.access "services.compute.flavors.#{input.get 'flavor'}"
+ unless flavor?
+ output.set result: 'error', message: "no such flavor found for specified identifier: #{pid}"
+ return done()
+
+ required =
+ instances: 1
+ cores: flavor.get 'vcpus'
+ ram: flavor.get 'ram'
+ gigabytes: flavor.get 'disk'
+
+ rid = input.get 'reservation-id'
+ if rid?
+ reservation = @find 'ResourceReservation', rid
+ unless reservation?
+ output.set result: 'error', message: 'no valid reservation found for specified identifier'
+ return done()
+ unless (reservation.get 'active') is true
+ output.set result: 'error', message: "reservation is currently not active"
+ return done()
+ available = reservation.get 'remaining'
+ else
+ available = @get 'promise.capacity.available'
+
+ # TODO: need to verify whether 'provider' associated with this 'reservation'
+
+ for k, v of required when v? and !!v
+ unless available[k] >= v
+ output.set result: 'conflict', message: "required #{k}=#{v} exceeds available #{available[k]}"
+ return done()
+
+ @create 'ResourceAllocation',
+ reservation: rid
+ capacity: required
+ .save()
+ .then (instance) =>
+ url = provider.get 'services.compute.endpoint'
+ payload =
+ server:
+ name: input.get 'name'
+ imageRef: input.get 'image'
+ flavorRef: input.get 'flavor'
+ networks = (input.get 'networks').filter (x) -> x? and !!x
+ if networks.length > 0
+ payload.server.networks = networks.map (x) -> uuid: x
+
+ request = @parent.require 'superagent'
+ request
+ .post "#{url}/servers"
+ .send payload
+ .set 'X-Auth-Token', provider.get 'token'
+ .set 'Accept', 'application/json'
+ .end (err, res) =>
+ if err? or !res.ok
+ instance.destroy()
+ #console.error err
+ return done res.error
+ #console.log JSON.stringify res.body, null, 2
+ instance.set 'instance-ref',
+ provider: provider
+ server: res.body.server.id
+ (@access 'promise.allocations').push instance
+ output.set result: 'ok', message: 'create-instance request accepted'
+ output.set 'instance-id', instance.id
+ done()
+ return instance
+ .catch (err) ->
+ output.set result: 'error', mesage: err
+ done()
+
+ 'destroy-instance':
+ (input, output, done) ->
+ # 1. find the instance
+ instance = @find 'ResourceAllocation', input.get 'instance-id'
+ unless instance?
+ output.set result: 'error', message: 'no allocation found for specified identifier'
+ return done()
+
+ # 2. destroy all traces of this instance
+ instance.destroy()
+ .then =>
+ # always remove internally
+ (@access 'promise.allocations').remove instance.id
+ ref = instance.get 'instance-ref'
+ provider = (@access "promise.providers.#{ref.provider}")
+ url = provider.get 'services.compute.endpoint'
+ request = @parent.require 'superagent'
+ request
+ .delete "#{url}/servers/#{ref.server}"
+ .set 'X-Auth-Token', provider.get 'token'
+ .set 'Accept', 'application/json'
+ .end (err, res) =>
+ if err? or !res.ok
+ console.error err
+ return done res.error
+ output.set 'result', 'ok'
+ output.set 'message', 'instance destroyed and resource released back to pool'
+ done()
+ return instance
+ .catch (e) ->
+ output.set 'result', 'error'
+ output.set 'message', e
+ done()
+
+ # TEMPORARY (should go into VIM-specific module)
+ 'add-provider':
+ (input, output, done) ->
+ app = @parent
+ request = app.require 'superagent'
+
+ payload = switch input.get 'provider-type'
+ when 'openstack'
+ auth:
+ tenantId: input.get 'tenant.id'
+ tenantName: input.get 'tenant.name'
+ passwordCredentials: input.get 'username', 'password'
+
+ unless payload?
+ return done 'Sorry, only openstack supported at this time'
+
+ url = input.get 'endpoint'
+ switch input.get 'strategy'
+ when 'keystone', 'oauth'
+ url += '/tokens' unless /\/tokens$/.test url
+
+ providers = @access 'promise.providers'
+ request
+ .post url
+ .send payload
+ .set 'Accept', 'application/json'
+ .end (err, res) =>
+ if err? or !res.ok then return done res.error
+ #console.log JSON.stringify res.body, null, 2
+ access = res.body.access
+ provider = @create 'ResourceProvider',
+ token: access?.token?.id
+ name: access?.token?.tenant?.name
+ provider.invoke 'update', access.serviceCatalog
+ .then (res) ->
+ res.save()
+ .then ->
+ providers.push res
+ output.set 'result', 'ok'
+ output.set 'provider-id', res.id
+ done()
+ .catch (err) ->
+ output.set 'error', message: err
+ done()
+ .catch (err) ->
+ output.set 'error', message: err
+ done()
+
+ # @using 'mano', ->
+ # @invoke 'add-provider', (input.get 'endpoint', 'region', 'username', 'password')
+ # .then (res) =>
+ # (@access 'promise.providers').push res
+ # output.set 'result', 'ok'
+ # output.set 'provider-id', res.id
+ # done()
diff --git a/source/spec/promise-module.coffee b/source/spec/promise-module.coffee
new file mode 100644
index 0000000..3eea482
--- /dev/null
+++ b/source/spec/promise-module.coffee
@@ -0,0 +1,72 @@
+module.exports =
+ '/opnfv-promise/promise/capacity/total': (prev) ->
+ @computed (->
+ combine = (a, b) ->
+ for k, v of b.capacity when v?
+ a[k] ?= 0
+ a[k] += v
+ return a
+ (@parent.get 'pools')
+ .filter (entry) -> entry.active is true
+ .reduce combine, {}
+ ), type: prev
+
+ '/opnfv-promise/promise/capacity/reserved', (prev) ->
+ @computed (->
+ combine = (a, b) ->
+ for k, v of b.capacity when v?
+ a[k] ?= 0
+ a[k] += v
+ return a
+ (@parent.get 'reservations')
+ .filter (entry) -> entry.active is true
+ .reduce combine, {}
+ ), type: prev
+
+ # rebind to be a computed property
+ '/opnfv-promise/promise/capacity/usage': (prev) ->
+ @computed (->
+ combine = (a, b) ->
+ for k, v of b.capacity when v?
+ a[k] ?= 0
+ a[k] += v
+ return a
+ (@parent.get 'allocations')
+ .filter (entry) -> entry.active is true
+ .reduce combine, {}
+ ), type: prev
+
+ # rebind to be a computed property
+ '/opnfv-promise/promise/capacity/available': (prev) ->
+ @computed (->
+ total = @get 'total'
+ reserved = @get 'reserved'
+ usage = @get 'usage'
+ for k, v of total when v?
+ total[k] -= reserved[k] if reserved[k]?
+ total[k] -= usage[k] if usage[k]?
+ total
+ ), type: prev
+
+ '/opnfv-promise/create-reservation':
+ (input, output, done) ->
+ # 1. create the reservation record (empty)
+ reservation = @create 'ResourceReservation'
+ reservations = @access 'promise.reservations'
+
+ # 2. update the record with requested input
+ reservation.invoke 'update', input.get()
+ .then (res) ->
+ # 3. save the record and add to list
+ res.save()
+ .then ->
+ reservations.push res
+ output.set result: 'ok', message: 'reservation request accepted'
+ output.set 'reservation-id', res.id
+ done()
+ .catch (err) ->
+ output.set result: 'error', message: err
+ done()
+ .catch (err) ->
+ output.set result: 'conflict', message: err
+ done()