Statistics
| Branch: | Tag: | Revision:

one / src / onedb / onedb_live.rb @ 45bfe9aa

History | View | Annotate | Download (10.3 KB)

1

    
2
require 'opennebula'
3
require 'base64'
4

    
5
class OneDBLive
6
    def initialize
7
        @client = nil
8
        @system = nil
9
    end
10

    
11
    def client
12
        @client ||= OpenNebula::Client.new
13
    end
14

    
15
    def system
16
        @system ||= OpenNebula::System.new(client)
17
    end
18

    
19
    def db_escape(string)
20
        string.gsub("'", "''")
21
    end
22

    
23
    def delete_sql(table, where)
24
        "DELETE from #{table} WHERE #{where}"
25
    end
26

    
27
    def delete(table, where, federate)
28
        sql = delete_sql(table, where)
29
        db_exec(sql, "Error deleting record", federate)
30
    end
31

    
32
    def update_sql(table, values, where)
33
        str = "UPDATE #{table} SET "
34

    
35
        changes = []
36

    
37
        values.each do |key, value|
38
            change = "#{key.to_s} = "
39

    
40
            case value
41
            when String, Symbol
42
                change << "'#{db_escape(value.to_s)}'"
43
            when Numeric
44
                change << value.to_s
45
            else
46
                change << value.to_s
47
            end
48

    
49
            changes << change
50
        end
51

    
52
        str << changes.join(', ')
53
        str << " WHERE #{where}"
54

    
55
        str
56
    end
57

    
58
    def update(table, values, where, federate)
59
        sql = update_sql(table, values, where)
60
        db_exec(sql, "Error updating record", federate)
61
    end
62

    
63
    def update_body_sql(table, body, where)
64
        "UPDATE #{table} SET body = '#{db_escape(body)}' WHERE #{where}"
65
    end
66

    
67
    def update_body(table, body, where, federate)
68
        sql = update_body_sql(table, body, where)
69
        db_exec(sql, "Error updating record", federate)
70
    end
71

    
72
    def db_exec(sql, error_msg, federate = false)
73
        rc = system.sql_command(sql, federate)
74
        if OpenNebula.is_error?(rc)
75
            raise "#{error_msg}: #{rc.message}"
76
        end
77
    end
78

    
79
    def select(table, where)
80
        sql = "SELECT * FROM #{table} WHERE #{where}"
81
        res = db_query(sql, "Error querying database")
82

    
83
        element = OpenNebula::XMLElement.new(
84
            OpenNebula::XMLElement.build_xml(res, '/SQL_COMMAND'))
85

    
86
        hash = element.to_hash
87

    
88
        row = hash['SQL_COMMAND']['RESULT']['ROW'] rescue nil
89
        [row].flatten.compact
90
    end
91

    
92
    def db_query(sql, error_msg)
93
        rc = system.sql_query_command(sql)
94
        if OpenNebula.is_error?(rc)
95
            raise "#{error_msg}: #{rc.message}"
96
        end
97

    
98
        rc
99
    end
100

    
101
    def percentage_line(current, max, carriage_return = false)
102
        return_symbol = carriage_return ? "\r" : ""
103
        percentile = current.to_f / max.to_f * 100
104

    
105
        "#{current}/#{max} #{percentile.round(2)}%#{return_symbol}"
106
    end
107

    
108
    def purge_history(options = {})
109
        vmpool = OpenNebula::VirtualMachinePool.new(client)
110
        vmpool.info_all
111

    
112
        ops = {
113
            start_time: 0,
114
            end_time: Time.now
115
        }.merge(options)
116

    
117
        start_time  = ops[:start_time].to_i
118
        end_time    = ops[:end_time].to_i
119

    
120
        last_id = vmpool["/VM_POOL/VM[last()]/ID"]
121

    
122
        vmpool.each do |vm|
123
            print percentage_line(vm.id, last_id, true)
124

    
125
            time = vm["STIME"].to_i
126
            next unless time >= start_time && time < end_time
127

    
128
            # vmpool info only returns the last history record. We can check
129
            # if this VM can have more than one record using the sequence
130
            # number. If it's 0 or it does not exist we can skip the VM.
131
            # Also take tone that xpaths on VM info that comes from VMPool
132
            # or VM is different. We can not use absolute searches with
133
            # objects coming from pool.
134
            seq = vm['HISTORY_RECORDS/HISTORY/SEQ']
135
            next if !seq || seq == '0'
136

    
137
            # If the history can contain more than one record we get
138
            # all the info for two reasons:
139
            #
140
            #   * Make sure that all the info is written back
141
            #   * Refresh the information so it's less probable that the info
142
            #     was modified during this process
143
            vm.info
144

    
145
            hash = vm.to_hash
146
            val_history = hash['VM']['HISTORY_RECORDS']['HISTORY']
147

    
148
            history_num = 2
149

    
150
            if Array === val_history && val_history.size > history_num
151
                last_history = val_history.last(history_num)
152

    
153
                old_seq = []
154
                seq_num = last_history.first['SEQ']
155

    
156
                # Renumerate the sequence
157
                last_history.each_with_index do |history, index|
158
                    old_seq << history['SEQ'].to_i
159
                    history['SEQ'] = index
160
                end
161

    
162
                # Only the last history record is saved in vm_pool
163
                vm.delete_element('HISTORY_RECORDS/HISTORY')
164
                vm.add_element('HISTORY_RECORDS',
165
                               'HISTORY' => last_history.last)
166

    
167
                # Update VM body to leave only the last history record
168
                body = db_escape(vm.to_xml)
169
                update_body("vm_pool", vm.to_xml, "oid = #{vm.id}", false)
170

    
171
                # Delete any history record that does not have the same
172
                # SEQ number as the last history record
173
                delete("history", "vid = #{vm.id} and seq < #{seq_num}", false)
174

    
175
                # Get VM history
176
                history = select("history", "vid = #{vm.id}")
177

    
178
                # Renumerate sequence numbers
179
                old_seq.each_with_index do |seq, index|
180
                    row = history.find {|r| seq.to_s == r["seq"] }
181
                    body = Base64.decode64(row['body64'])
182

    
183
                    doc = Nokogiri::XML(body)
184
                    doc.xpath("/HISTORY/SEQ").first.content = index.to_s
185
                    new_body = doc.root.to_xml
186

    
187
                    update("history",
188
                           { seq: index, body: new_body },
189
                           "vid = #{vm.id} and seq = #{seq}", false)
190
                end
191
            end
192
        end
193
    end
194

    
195
    def purge_done_vm(options = {})
196
        vmpool = OpenNebula::VirtualMachinePool.new(client)
197
        vmpool.info(OpenNebula::Pool::INFO_ALL,
198
                    -1,
199
                    -1,
200
                    OpenNebula::VirtualMachine::VM_STATE.index('DONE'))
201

    
202
        ops = {
203
            start_time: 0,
204
            end_time: Time.now
205
        }.merge(options)
206

    
207
        start_time  = ops[:start_time].to_i
208
        end_time    = ops[:end_time].to_i
209

    
210
        last_id = vmpool["/VM_POOL/VM[last()]/ID"]
211

    
212
        vmpool.each do |vm|
213
            print percentage_line(vm.id, last_id, true)
214

    
215
            time = vm["ETIME"].to_i
216
            next unless time >= start_time && time < end_time
217

    
218
            delete("vm_pool", "oid = #{vm.id}", false)
219
            delete("history", "vid = #{vm.id}", false)
220
        end
221
    end
222

    
223
    def check_expr(object, expr)
224
        reg = /^(?<xpath>.+?)(?<operator>=|!=|>=|<=|>|<)(?<value>.*?)$/
225
        parsed = expr.match(reg)
226

    
227
        raise "Expression malformed: '#{expr}'" unless parsed
228

    
229
        val = object[parsed[:xpath]]
230
        return false if !val
231

    
232
        p_val = parsed[:value].strip
233
        val.strip!
234

    
235
        res = false
236

    
237
        res = case parsed[:operator]
238
        when '='
239
            val == p_val
240
        when '!='
241
            val != p_val
242
        when '<'
243
            val.to_i < p_val.to_i
244
        when '>'
245
            val.to_i > p_val.to_i
246
        when '<='
247
            val.to_i <= p_val.to_i
248
        when '>='
249
            val.to_i >= p_val.to_i
250
        end
251

    
252
        res
253
    end
254

    
255
    def change_body(object, xpath, value, options = {})
256
        case (object||'').downcase.strip.to_sym
257
        when :vm
258
            table = 'vm_pool'
259
            object = OpenNebula::VirtualMachinePool.new(client)
260
            federate = false
261

    
262
        when :host
263
            table = 'host_pool'
264
            object = OpenNebula::HostPool.new(client)
265
            federate = false
266

    
267
        when :vnet
268
            table = 'network_pool'
269
            object = OpenNebula::VirtualNetworkPool.new(client)
270
            federate = false
271

    
272
        when :image
273
            table = 'image_pool'
274
            object = OpenNebula::ImagePool.new(client)
275
            federate = false
276

    
277
        when :cluster
278
            table = 'cluster_pool'
279
            object = OpenNebula::ClusterPool.new(client)
280
            federate = false
281

    
282
        when :document
283
            table = 'document_pool'
284
            object = OpenNebula::DocumentPool.new(client)
285
            federate = false
286

    
287
        when :group
288
            table = 'group_pool'
289
            object = OpenNebula::GroupPool.new(client)
290
            federate = true
291

    
292
        when :marketplace
293
            table = 'marketplace_pool'
294
            object = OpenNebula::MarketPlacePool.new(client)
295
            federate = true
296

    
297
        when :marketplaceapp
298
            table = 'marketplaceapp_pool'
299
            object = OpenNebula::MarketPlaceAppPool.new(client)
300
            federate = true
301

    
302
        when :secgroup
303
            table = 'secgroup_pool'
304
            object = OpenNebula::SecurityGroupPool.new(client)
305
            federate = false
306

    
307
        when :template
308
            table = 'template_pool'
309
            object = OpenNebula::TemplatePool.new(client)
310
            federate = false
311

    
312
        when :vrouter
313
            table = 'vrouter_pool'
314
            object = OpenNebula::VirtualRouterPool.new(client)
315
            federate = false
316

    
317
        when :zone
318
            table = 'zone_pool'
319
            object = OpenNebula::ZonePool.new(client)
320
            federate = true
321

    
322
        else
323
            raise "Object type '#{object}' not supported"
324
        end
325

    
326
        if !value && !options[:delete]
327
            raise "A value or --delete should specified"
328
        end
329

    
330
        object.info
331

    
332
        object.each do |o|
333
            if options[:id]
334
                next unless o.id.to_s.strip == options[:id].to_s
335
            elsif options[:xpath]
336
                next unless o[options[:xpath]]
337
            elsif options[:expr]
338
                next unless check_expr(o, options[:expr])
339
            end
340

    
341
            o.info
342
            doc = Nokogiri::XML(o.to_xml, nil, NOKOGIRI_ENCODING) do |c|
343
                c.default_xml.noblanks
344
            end
345

    
346
            doc.xpath(xpath).each do |e|
347
                if options[:delete]
348
                    e.remove
349
                else
350
                    e.content = value
351
                end
352
            end
353

    
354
            xml = doc.root.to_xml
355

    
356
            if options[:dry]
357
                puts xml
358
            else
359
                update_body(table, xml, "oid = #{o.id}", federate)
360
            end
361
        end
362
    end
363
end
364