added copy of github.com/opnfv/promise source into the source directory
[promise.git] / source / promise.yaml
1 name: opnfv-promise
2 description: Resource Management for Virtualized Infrastructure
3 author: Peter K. Lee <peter@intercloud.net>
4 license: Apache-2.0
5 homepage: http://wiki.opnfv.org/promise
6 repository: git://github.com/opnfv/promise.git
7 yangforge: "0.11.x"
8 keywords:
9   - opnfv
10   - promise
11   - vim
12   - nfvi
13   - infrastructure
14   - openstack
15   - nbi
16   - yangforge
17   - resource
18   - reservation
19   - capacity
20   - allocation
21
22 schema: !yang schema/opnfv-promise.yang
23
24 # below config provides default parameters
25 # NOTE: uncomment locally for testing with pre-existing data
26 #config: !json config/demo.json
27
28 dependencies:
29   access-control-models: !yang schema/access-control-models.yang
30   nfv-infrastructure:    !yang schema/nfv-infrastructure.yang
31
32 # MODULE model active bindings
33 module:
34   opnfv-promise:
35     # rebind to be a computed property
36     promise.capacity.total: !coffee/function |
37       (prev) -> @computed (->
38         combine = (a, b) ->
39           for k, v of b.capacity when v?
40             a[k] ?= 0
41             a[k] += v
42           return a
43         (@parent.get 'pools')
44         .filter (entry) -> entry.active is true
45         .reduce combine, {}
46       ), type: prev
47     # rebind to be a computed property
48     promise.capacity.reserved: !coffee/function |
49       (prev) -> @computed (->
50         combine = (a, b) ->
51           for k, v of b.remaining when v?
52             a[k] ?= 0
53             a[k] += v
54           return a
55         (@parent.get 'reservations')
56         .filter (entry) -> entry.active is true
57         .reduce combine, {}
58       ), type: prev
59     # rebind to be a computed property
60     promise.capacity.usage: !coffee/function |
61       (prev) -> @computed (->
62         combine = (a, b) ->
63           for k, v of b.capacity when v?
64             a[k] ?= 0
65             a[k] += v
66           return a
67         (@parent.get 'allocations')
68         .filter (entry) -> entry.active is true
69         .reduce combine, {}
70       ), type: prev
71     # rebind to be a computed property
72     promise.capacity.available: !coffee/function |
73       (prev) -> @computed (->
74         total = @get 'total'
75         reserved = @get 'reserved'
76         usage = @get 'usage'
77         for k, v of total when v?
78           total[k] -= reserved[k] if reserved[k]?
79           total[k] -= usage[k] if usage[k]?
80         total
81       ), type: prev
82
83 # RPC definitions (INTENT interfaces)
84 rpc: !require spec/promise-intents.coffee
85
86 # COMPLEX-TYPE model active bindings (controller logic)
87 complex-type:
88   ResourceElement:
89     #properties
90     id: !coffee/function |
91       (prev) -> prev.set 'default', -> @uuid()
92
93   ResourceCollection:
94     # properties
95     start: !coffee/function |
96       (prev) -> prev.set 'default', -> (new Date).toJSON()
97
98     active: !coffee/function |
99       (prev) -> @computed (->
100         now = new Date
101         start = new Date (@get 'start')
102         end = switch
103           when (@get 'end')? then new Date (@get 'end')
104           else now
105         (@get 'enabled') and (start <= now <= end)
106       ), type: prev
107
108   ResourceReservation:
109     end: !coffee/function |
110       (prev) -> prev.set 'default', ->
111         end = (new Date @get 'start')
112         max = @parent.get 'promise.policy.reservation.max-duration'
113         return unless max?
114         end.setTime (end.getTime() + (max*60*60*1000))
115         end.toJSON()
116
117     allocations: !coffee/function |
118       (prev) -> @computed (->
119         res = (@store.find 'ResourceAllocation', reservation: @id)
120         res.map (x) -> x.get 'id'
121       ), type: 'array'
122
123     remaining: !coffee/function |
124       (prev) -> @computed (->
125         total = @get 'capacity'
126         records = @store.find 'ResourceAllocation', id: (@get 'allocations'), active: true
127         for entry in records
128           usage = entry.get 'capacity'
129           for k, v of usage
130             total[k] -= v
131         total
132       ), type: prev
133
134     # methods
135     validate: !coffee/function |
136       (prev) -> (value={}, resolve, reject) ->
137         # validate that request contains sufficient data
138         for k, v of value.capacity when v? and !!v
139           hasCapacity = true
140         if (not hasCapacity) and value.elements.length is 0
141           return reject "unable to validate reservation record without anything being reserved"
142         # time range verifications
143         now = new Date
144         start = (new Date value.start) if value.start?
145         end   = (new Date value.end) if value.end?
146         # if start? and start < now
147         #   return reject "requested start time #{value.start} cannot be in the past"
148         if end? and end < now
149           return reject "requested end time #{value.end} cannot be in the past"
150         if start? and end? and start > end
151           retun reject "requested start time must be earlier than end time"
152         resolve this
153
154     update: !coffee/function |
155       (prev) -> (req, resolve, reject) ->
156         req.start ?= @get 'start'
157         req.end   ?= @get 'end'
158
159         # TODO: should validate here...
160         @parent.invoke 'query-capacity',
161           start: req.start
162           end: req.end
163           capacity: 'available'
164           without: @id
165         .then (res) =>
166           collections = res.get 'collections'
167           unless collections.length > 0
168             return reject 'no resource capacity available during requested start/end time'
169
170           pools = collections.filter (e) -> /^ResourcePool/.test e
171           # should do some policy or check to see if more than one pool acceptable to reservee
172
173           entries = res.get 'utilization'
174           start = new Date req.start
175           end   = new Date req.end
176
177           for x in [0..entries.length-1]
178             t1 = new Date entries[x].timestamp
179             break unless t1 < end
180
181             if x < entries.length-1
182               t2 = new Date entries[x+1].timestamp
183               continue unless t2 > start
184
185             available = entries[x].capacity
186             for k, v of req.capacity when v? and !!v
187               unless available[k] >= v
188                 return reject "requested #{k}=#{v} exceeds available #{available[k]} between #{t1} and #{t2}"
189
190           @set req
191           @set 'pools', pools
192           resolve this
193         .catch (err) -> reject err
194
195     save: !coffee/function |
196       (prev) -> (resolve, reject) ->
197         @invoke 'validate', @get()
198         .then (res) ->
199           # should do something about this reservation record...
200           now = (new Date).toJSON()
201           unless (res.get 'created-on')?
202             res.set 'created-on', now
203           res.set 'modified-on', now
204           resolve res
205         .catch (e) -> reject e
206
207   ResourceAllocation:
208     # properties
209     priority: !coffee/function |
210       (prev) -> @computed (->
211         switch
212           when not (@get 'reservation')? then 3
213           when not (@get 'active') then 2
214           else 1
215       ), type: prev
216
217   ResourcePool:
218     save: !coffee/function |
219       (prev) -> (resolve, reject) ->
220         # validate that record contains sufficient data
221         value = @get()
222
223         for k, v of value.capacity when v? and !!v
224           hasCapacity = true
225         if (not hasCapacity) and value.elements.length is 0
226           return reject "unable to save pool record without any capacity values"
227
228         resolve this
229
230   ResourceProvider:
231     # properties
232     token: !coffee/function |
233       (prev) -> prev.set 'private', true
234
235     pools: !coffee/function |
236       (prev) -> @computed (->
237         (@store.find 'ResourcePool', source: (@get 'name')).map (x) -> x.get 'id'
238       ), type: 'array'
239
240     # methods
241     # XXX - this method is OpenStack-specific only, will need to revise later
242     update: !coffee/function |
243       (prev) -> (services=[], resolve, reject) ->
244         return reject "unable to update provider without list of services" unless services.length
245         request = @store.parent.require 'superagent'
246         services.forEach (service) =>
247           switch service.type
248             when 'compute'
249               url = service.endpoints[0].publicURL
250               @set 'services.compute.endpoint', url
251               request
252                 .get "#{url}/limits"
253                   .set 'X-Auth-Token', @get 'token'
254                   .set 'Accept', 'application/json'
255                   .end (err, res) =>
256                     if err? or !res.ok
257                       console.warn "request to discover capacity limits failed"
258                       return
259
260                     capacity = res.body.limits?.absolute
261                     #console.log "\ndiscovered capacity:"
262                     #console.log capacity
263
264                     (@access 'capacity').set {
265                       cores: capacity.maxTotalCores
266                       ram: capacity.maxTotalRAMSize
267                       instances: capacity.maxTotalInstances
268                       addresses: capacity.maxTotalFloatingIps
269                     }
270               request
271                 .get "#{url}/flavors/detail"
272                   .set 'X-Auth-Token', @get 'token'
273                   .set 'Accept', 'application/json'
274                   .end (err, res) =>
275                     if err? or !res.ok
276                       console.warn "request to discover compute flavors failed"
277                       return
278
279                     flavors = res.body.flavors
280                     # console.log "\ndiscovered flavors:"
281                     # console.log flavors
282                     try
283                       flavors = flavors.map (x) -> ResourceFlavor: x
284                       (@access 'services.compute.flavors').push flavors...
285                     catch er
286                       console.warn "failed to update flavors into the provider due to validation errors"
287
288         # XXX - update should do promise.all
289         resolve this
290