name: opnfv-promise description: Resource Management for Virtualized Infrastructure author: Peter K. Lee license: Apache-2.0 homepage: http://wiki.opnfv.org/promise repository: git://github.com/opnfv/promise.git yangforge: "0.11.x" keywords: - opnfv - promise - vim - nfvi - infrastructure - openstack - nbi - yangforge - resource - reservation - capacity - allocation schema: !yang schema/opnfv-promise.yang # below config provides default parameters # NOTE: uncomment locally for testing with pre-existing data #config: !json config/demo.json dependencies: access-control-models: !yang schema/access-control-models.yang nfv-infrastructure: !yang schema/nfv-infrastructure.yang # MODULE model active bindings module: opnfv-promise: # rebind to be a computed property promise.capacity.total: !coffee/function | (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 # rebind to be a computed property promise.capacity.reserved: !coffee/function | (prev) -> @computed (-> combine = (a, b) -> for k, v of b.remaining 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 promise.capacity.usage: !coffee/function | (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 promise.capacity.available: !coffee/function | (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 # RPC definitions (INTENT interfaces) rpc: !require spec/promise-intents.coffee # COMPLEX-TYPE model active bindings (controller logic) complex-type: ResourceElement: #properties id: !coffee/function | (prev) -> prev.set 'default', -> @uuid() ResourceCollection: # properties start: !coffee/function | (prev) -> prev.set 'default', -> (new Date).toJSON() active: !coffee/function | (prev) -> @computed (-> now = new Date start = new Date (@get 'start') end = switch when (@get 'end')? then new Date (@get 'end') else now (@get 'enabled') and (start <= now <= end) ), type: prev ResourceReservation: end: !coffee/function | (prev) -> prev.set 'default', -> end = (new Date @get 'start') max = @parent.get 'promise.policy.reservation.max-duration' return unless max? end.setTime (end.getTime() + (max*60*60*1000)) end.toJSON() allocations: !coffee/function | (prev) -> @computed (-> res = (@store.find 'ResourceAllocation', reservation: @id) res.map (x) -> x.get 'id' ), type: 'array' remaining: !coffee/function | (prev) -> @computed (-> total = @get 'capacity' records = @store.find 'ResourceAllocation', id: (@get 'allocations'), active: true for entry in records usage = entry.get 'capacity' for k, v of usage total[k] -= v total ), type: prev # methods validate: !coffee/function | (prev) -> (value={}, resolve, reject) -> # validate that request contains sufficient data for k, v of value.capacity when v? and !!v hasCapacity = true if (not hasCapacity) and value.elements.length is 0 return reject "unable to validate reservation record without anything being reserved" # time range verifications now = new Date start = (new Date value.start) if value.start? end = (new Date value.end) if value.end? # if start? and start < now # return reject "requested start time #{value.start} cannot be in the past" if end? and end < now return reject "requested end time #{value.end} cannot be in the past" if start? and end? and start > end retun reject "requested start time must be earlier than end time" resolve this update: !coffee/function | (prev) -> (req, resolve, reject) -> req.start ?= @get 'start' req.end ?= @get 'end' # TODO: should validate here... @parent.invoke 'query-capacity', start: req.start end: req.end capacity: 'available' without: @id .then (res) => collections = res.get 'collections' unless collections.length > 0 return reject 'no resource capacity available during requested start/end time' pools = collections.filter (e) -> /^ResourcePool/.test e # should do some policy or check to see if more than one pool acceptable to reservee entries = res.get 'utilization' start = new Date req.start end = new Date req.end for x in [0..entries.length-1] t1 = new Date entries[x].timestamp break unless t1 < end if x < entries.length-1 t2 = new Date entries[x+1].timestamp continue unless t2 > start available = entries[x].capacity for k, v of req.capacity when v? and !!v unless available[k] >= v return reject "requested #{k}=#{v} exceeds available #{available[k]} between #{t1} and #{t2}" @set req @set 'pools', pools resolve this .catch (err) -> reject err save: !coffee/function | (prev) -> (resolve, reject) -> @invoke 'validate', @get() .then (res) -> # should do something about this reservation record... now = (new Date).toJSON() unless (res.get 'created-on')? res.set 'created-on', now res.set 'modified-on', now resolve res .catch (e) -> reject e ResourceAllocation: # properties priority: !coffee/function | (prev) -> @computed (-> switch when not (@get 'reservation')? then 3 when not (@get 'active') then 2 else 1 ), type: prev ResourcePool: save: !coffee/function | (prev) -> (resolve, reject) -> # validate that record contains sufficient data value = @get() for k, v of value.capacity when v? and !!v hasCapacity = true if (not hasCapacity) and value.elements.length is 0 return reject "unable to save pool record without any capacity values" resolve this ResourceProvider: # properties token: !coffee/function | (prev) -> prev.set 'private', true pools: !coffee/function | (prev) -> @computed (-> (@store.find 'ResourcePool', source: (@get 'name')).map (x) -> x.get 'id' ), type: 'array' # methods # XXX - this method is OpenStack-specific only, will need to revise later update: !coffee/function | (prev) -> (services=[], resolve, reject) -> return reject "unable to update provider without list of services" unless services.length request = @store.parent.require 'superagent' services.forEach (service) => switch service.type when 'compute' url = service.endpoints[0].publicURL @set 'services.compute.endpoint', url request .get "#{url}/limits" .set 'X-Auth-Token', @get 'token' .set 'Accept', 'application/json' .end (err, res) => if err? or !res.ok console.warn "request to discover capacity limits failed" return capacity = res.body.limits?.absolute #console.log "\ndiscovered capacity:" #console.log capacity (@access 'capacity').set { cores: capacity.maxTotalCores ram: capacity.maxTotalRAMSize instances: capacity.maxTotalInstances addresses: capacity.maxTotalFloatingIps } request .get "#{url}/flavors/detail" .set 'X-Auth-Token', @get 'token' .set 'Accept', 'application/json' .end (err, res) => if err? or !res.ok console.warn "request to discover compute flavors failed" return flavors = res.body.flavors # console.log "\ndiscovered flavors:" # console.log flavors try flavors = flavors.map (x) -> ResourceFlavor: x (@access 'services.compute.flavors').push flavors... catch er console.warn "failed to update flavors into the provider due to validation errors" # XXX - update should do promise.all resolve this