This file is indexed.

/usr/lib/one/ruby/quota.rb is in opennebula 3.4.1-4.1ubuntu1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
# -------------------------------------------------------------------------- #
# Copyright 2002-2012, OpenNebula Project Leads (OpenNebula.org)             #
#                                                                            #
# Licensed under the Apache License, Version 2.0 (the "License"); you may    #
# not use this file except in compliance with the License. You may obtain    #
# a copy of the License at                                                   #
#                                                                            #
# http://www.apache.org/licenses/LICENSE-2.0                                 #
#                                                                            #
# Unless required by applicable law or agreed to in writing, software        #
# distributed under the License is distributed on an "AS IS" BASIS,          #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #
# See the License for the specific language governing permissions and        #
# limitations under the License.                                             #
#--------------------------------------------------------------------------- #

require 'sequel'
require 'base64'
require 'yaml'
require 'uri'
require 'net/http'

class Quota
    ###########################################################################
    # Constants with paths to relevant files and defaults
    ###########################################################################
    ONE_LOCATION=ENV["ONE_LOCATION"]

    if !ONE_LOCATION
        VAR_LOCATION = "/var/lib/one"
        ETC_LOCATION = "/etc/one"
    else
        VAR_LOCATION = ONE_LOCATION + "/var"
        ETC_LOCATION = ONE_LOCATION + "/etc"
    end

    CONF_FILE = ETC_LOCATION + "/auth/quota.conf"

    CONF = {
        :db => "sqlite://#{VAR_LOCATION}/onequota.db",
        :defaults => {
            :CPU     => nil,
            :MEMORY  => nil,
            :NUM_VMS => nil,
            :STORAGE => nil
        }
    }

    ###########################################################################
    # Schema for the USAGE and QUOTA tables
    ###########################################################################
    DB_QUOTA_SCHEMA = {
        :CPU     => Float,
        :MEMORY  => Integer,
        :NUM_VMS => Integer,
        :STORAGE => Integer
    }

    QUOTA_TABLE = :quotas
    USAGE_TABLE = :usage

    ###########################################################################
    # Usage params to calculate each quota
    ###########################################################################
    VM_USAGE = {
        :CPU => {
            :proc_info  => lambda {|template| template['CPU']},
            :xpath => 'TEMPLATE/CPU'
        },
        :MEMORY => {
            :proc_info  => lambda {|template| template['MEMORY']},
            :xpath => 'TEMPLATE/MEMORY'
        },
        :NUM_VMS => {
            :proc_info  => lambda {|template| 1 },
            :xpath => 'ID',
            :count => true
        }
    }

    IMAGE_USAGE = {
        :STORAGE => {
            :proc_info  => lambda {|template|
                if template['TYPE'] == 'DATABLOCK'
                    template['SIZE'].to_i
                elsif template['PATH']
                    uri = URI.parse(template['PATH'])
                    size = if uri.scheme.nil?
                        File.size(template['PATH'])
                    else
                        Net::HTTP.start(uri.host,uri.port) { |http|
                            http.head(uri.path)
                        }.content_length
                    end
                    (size.to_f / 2**20).round
                elsif template['SAVED_VM_ID']
                    vm_id   = template['SAVED_VM_ID'].to_i
                    disk_id = template['SAVED_DISK_ID'].to_i

                    client = OpenNebula::Client.new
                    vm = OpenNebula::VirtualMachine.new_with_id(vm_id, client)
                    vm.info

                    im_id = vm["DISK[DISK_ID=#{disk_id}]/IMAGE_ID"].to_i

                    im = OpenNebula::Image.new_with_id(im_id, client)
                    im.info

                    im['SIZE'].to_i
                else
                    0
                end
            },
            :xpath => 'SIZE'
        }
    }

    RESOURCES = ["VM", "IMAGE"]

    ###########################################################################
    # DB handling
    ###########################################################################
    def initialize
        conf = YAML.load_file(CONF_FILE)
        @conf=CONF.merge(conf) {|key,h1,h2|
            if h1.instance_of?(Hash) && h2.instance_of?(Hash)
                h1.merge(h2)
            else
                if h2
                    h2
                else
                    h1
                end
            end
        }

        @client = OpenNebula::Client.new

        @db=Sequel.connect(@conf[:db])

        create_table(QUOTA_TABLE)
        create_table(USAGE_TABLE)
    end

    # Creates database quota table if it does not exist
    def create_table(table)
        @db.create_table?(table) do
            Integer     :UID

            DB_QUOTA_SCHEMA.each { |key,value|
                column key, value
            }

            primary_key :UID
            index       :UID
        end
    end

    # Adds new user limits
    def set(table, uid, quota={})
        data=quota.delete_if{|key,value| !DB_QUOTA_SCHEMA.keys.include?(key)}

        quotas=@db[table].filter(:UID => uid)

        if quotas.first
            quotas.update(data)
        else
            @db[table].insert(data.merge!(:UID => uid))
        end
    end

    # Gets user limits
    def get(table, uid=nil)
        if uid
            @db[table].filter(:UID => uid).first
        else
            @db[table].all
        end
    end

    # Delete user limits
    def delete(table, uid)
        quotas=@db[table].filter(:UID => uid)

        if quotas.first
            quotas.delete
        end
    end

    ###########################################################################
    # Quota Client
    ###########################################################################
    def set_quota(uid, quota={})
        set(QUOTA_TABLE, uid, quota)
    end

    # Retrieves quota information for a given user
    #
    # @param [Integer, nil] uid the user id from which get the quota
    #   information, if nil will retrieve the quotas for all users.
    # @return [Hash] Hash containing the quota information and the user id
    #
    #    {
    #        :uid     => 4,
    #        :cpu     => 8,
    #        :memory  => 8064,
    #        :num_vms => 4,
    #        :storage => 1240019
    #    }
    def get_quota(uid=nil)
        limit = get(QUOTA_TABLE, uid)
        limit ? limit : @conf[:defaults].merge!(:UID => uid)
    end

    def delete_quota(uid)
        delete(QUOTA_TABLE, uid)
    end

    ###########################################################################
    # Authorization
    ###########################################################################
    def authorize(user_id, request)
        obj, template_or_id, op, owner, acl_eval = request.split(':')

        if acl_eval.to_i == 0
            return "ACL evaluation denied"
        end

        # Check if this op needs to check the quota
        return false unless with_quota?(obj, op)

        template = ""

        if ( obj == "TEMPLATE" )
            obj = "VM"

            vm_template = OpenNebula::Template.new_with_id(template_or_id, @client)
            vm_template.info

            vm_template.each("TEMPLATE") { |xml_elem|
                template = xml_elem.to_xml
            }
        else
            template = Base64::decode64(template_or_id)
        end

        check_quotas(user_id.to_i, obj, template)
    end

    def check_quotas(user_id, obj, template)
        info  = get_resources(obj, template)
        total = get_usage(user_id, obj, true)
        quota = get_quota(user_id)

        msg = ""
        separator = ""
        info.each { |qname, quota_requested|
            unless quota[qname] || quota[qname]==0
                next
            end

            type = DB_QUOTA_SCHEMA[qname].name.to_sym

            used    = send(type, total[qname])
            request = send(type, quota_requested)
            limit   = send(type, quota[qname])
            spent   = used + request

            if spent > limit
                msg << separator
                msg << " #{qname.to_s.upcase} quota exceeded "
                msg << "(Quota: #{limit}, "
                msg << "Used: #{used}, "
                msg << "Requested: #{request})"

                separator = ";"
            end
        }

        if msg==""
            return false
        else
            return msg.strip
        end
    end

    def with_quota?(obj, op)
        return (obj == "VM"       && op == "CREATE") ||
               (obj == "IMAGE"    && op == "CREATE") ||
               (obj == "TEMPLATE" && op == "USE")
    end

    ###########################################################################
    # Usage
    ###########################################################################
    # Retrieves usage information for a given user
    #
    # @param [Integer] uid the user id from which get the usage information.
    # @param ["VM", "IMAGE"] resource kind of resource. If nil will return
    #   the usage for all kinds of resources
    # @param [true, false] force If true will force the usage calculation
    #   instead of retrieving it from the cache
    # @return [Hash] Hash containing the usage information and the user id
    #
    #    {
    #        :uid     => 4,
    #        :cpu     => 8,
    #        :memory  => 8064,
    #        :num_vms => 4,
    #        :storage => 1240019
    #    }
    def get_usage(user_id, resource=nil, force=false)
        if force
            if RESOURCES.include?(resource)
                resources = [resource]
            else
                resources = RESOURCES
            end

            usage = Hash.new

            resources.each{ |res|
                pool = get_pool(res, user_id)
                base_xpath = "/#{res}_POOL/#{resource}"
                Quota.const_get("#{res}_USAGE".to_sym).each { |key, params|
                    usage[key] ||= 0
                    pool.each_xpath("#{base_xpath}/#{params[:xpath]}") { |elem|
                        if elem
                            if params[:count]
                                usage[key] += 1
                            else
                                usage[key] += send(DB_QUOTA_SCHEMA[key].name.to_sym, elem)
                            end
                        end
                    }
                }

                set(USAGE_TABLE, user_id, usage) unless usage.empty?
                usage.merge!(:UID => user_id)
            }
        else
            usage = get(USAGE_TABLE, user_id)
            usage ||= {:UID => user_id}
        end

        usage
    end

    # Retrieve the useful information of the template for the specified
    # kind of resource
    def get_resources(resource, xml_template)
        template = OpenNebula::XMLElement.new
        template.initialize_xml(xml_template, 'TEMPLATE')

        info = Hash.new

        self.class.const_get("#{resource}_USAGE").each { |key, params|
            info[key] = params[:proc_info].call(template).to_i
        }

        info
    end

    # Returns a an Array than contains the elements of the resource Pool
    def get_pool(resource, user_id)
        pool = case resource
        when "VM"    then OpenNebula::VirtualMachinePool.new(@client, user_id)
        when "IMAGE" then OpenNebula::ImagePool.new(@client, user_id)
        end

        rc = pool.info
        return pool
    end
end