Statistics
| Branch: | Tag: | Revision:

one / src / onedb / onedb_backend.rb @ 3a89a0d1

History | View | Annotate | Download (10.3 KB)

1
# -------------------------------------------------------------------------- #
2
# Copyright 2002-2015, OpenNebula Project (OpenNebula.org), C12G Labs        #
3
#                                                                            #
4
# Licensed under the Apache License, Version 2.0 (the "License"); you may    #
5
# not use this file except in compliance with the License. You may obtain    #
6
# a copy of the License at                                                   #
7
#                                                                            #
8
# http://www.apache.org/licenses/LICENSE-2.0                                 #
9
#                                                                            #
10
# Unless required by applicable law or agreed to in writing, software        #
11
# distributed under the License is distributed on an "AS IS" BASIS,          #
12
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #
13
# See the License for the specific language governing permissions and        #
14
# limitations under the License.                                             #
15
#--------------------------------------------------------------------------- #
16

    
17
require 'time'
18
require 'rubygems'
19
require 'cgi'
20

    
21
begin
22
    require 'sequel'
23
rescue LoadError
24
    STDERR.puts "Ruby gem sequel is needed for this operation:"
25
    STDERR.puts "  $ sudo gem install sequel"
26
    exit -1
27
end
28

    
29
class OneDBBacKEnd
30
    def read_db_version
31
        connect_db
32

    
33
        ret = {}
34

    
35
        begin
36
            ret[:version]   = "2.0"
37
            ret[:timestamp] = 0
38
            ret[:comment]   = ""
39

    
40
            @db.fetch("SELECT version, timestamp, comment FROM db_versioning " +
41
                      "WHERE oid=(SELECT MAX(oid) FROM db_versioning)") do |row|
42
                ret[:version]   = row[:version]
43
                ret[:timestamp] = row[:timestamp]
44
                ret[:comment]   = row[:comment]
45
            end
46

    
47
            ret[:local_version]   = ret[:version]
48
            ret[:local_timestamp] = ret[:timestamp]
49
            ret[:local_comment]   = ret[:comment]
50
            ret[:is_slave]        = false
51

    
52
            begin
53
               @db.fetch("SELECT version, timestamp, comment, is_slave FROM "+
54
                        "local_db_versioning WHERE oid=(SELECT MAX(oid) "+
55
                        "FROM local_db_versioning)") do |row|
56
                    ret[:local_version]   = row[:version]
57
                    ret[:local_timestamp] = row[:timestamp]
58
                    ret[:local_comment]   = row[:comment]
59
                    ret[:is_slave]        = row[:is_slave]
60
               end
61
            rescue Exception => e
62
                if e.class == Sequel::DatabaseConnectionError
63
                    raise e
64
                end
65
            end
66

    
67
            return ret
68

    
69
        rescue Exception => e
70
            if e.class == Sequel::DatabaseConnectionError
71
                raise e
72
            elsif !db_exists?
73
                # If the DB doesn't have db_version table, it means it is empty or a 2.x
74
                raise "Database schema does not look to be created by " <<
75
                      "OpenNebula: table user_pool is missing or empty."
76
            end
77

    
78
            begin
79
                # Table image_pool is present only in 2.X DBs
80
                @db.fetch("SELECT * FROM image_pool") { |row| }
81
            rescue
82
                raise "Database schema looks to be created by OpenNebula 1.X." <<
83
                      "This tool only works with databases created by 2.X versions."
84
            end
85

    
86
            comment = "Could not read any previous db_versioning data, " <<
87
                      "assuming it is an OpenNebula 2.0 or 2.2 DB."
88

    
89
            return ret
90
        end
91
    end
92

    
93
    def history
94
        connect_db
95

    
96
        begin
97
            query = "SELECT version, timestamp, comment FROM db_versioning"
98
            @db.fetch(query) do |row|
99
                puts "Version:   #{row[:version]}"
100

    
101
                time = Time.at(row[:timestamp])
102
                puts "Timestamp: #{time.strftime("%m/%d %H:%M:%S")}"
103

    
104
                puts "Comment:   #{row[:comment]}"
105

    
106
                puts ""
107
            end
108
        rescue Exception => e
109
            raise "No version records found. Error message: " + e.message
110
        end
111
    end
112

    
113
    def update_db_version(version)
114
        comment = "Database migrated from #{version} to #{db_version}"+
115
                  " (#{one_version}) by onedb command."
116

    
117
        max_oid = nil
118
        @db.fetch("SELECT MAX(oid) FROM db_versioning") do |row|
119
            max_oid = row[:"MAX(oid)"].to_i
120
        end
121

    
122
        max_oid = 0 if max_oid.nil?
123

    
124
        query =
125
        @db.run(
126
            "INSERT INTO db_versioning (oid, version, timestamp, comment) "<<
127
            "VALUES ("                                                     <<
128
                "#{max_oid+1}, "                                           <<
129
                "'#{db_version}', "                                        <<
130
                "#{Time.new.to_i}, "                                       <<
131
                "'#{comment}')"
132
        )
133

    
134
        puts comment
135
    end
136

    
137
    def update_local_db_version(version)
138
        comment = "Database migrated from #{version} to #{db_version}"+
139
                  " (#{one_version}) by onedb command."
140

    
141
        max_oid = nil
142
        @db.fetch("SELECT MAX(oid) FROM local_db_versioning") do |row|
143
            max_oid = row[:"MAX(oid)"].to_i
144
        end
145

    
146
        max_oid = 0 if max_oid.nil?
147

    
148
        is_slave = 0
149

    
150
        @db.fetch("SELECT is_slave FROM local_db_versioning "<<
151
                  "WHERE oid=#{max_oid}") do |row|
152
            is_slave = row[:is_slave] ? 1 : 0
153
        end
154

    
155
        @db.run(
156
            "INSERT INTO local_db_versioning (oid, version, timestamp, comment, is_slave) "<<
157
            "VALUES ("                                                     <<
158
                "#{max_oid+1}, "                                           <<
159
                "'#{db_version}', "                                        <<
160
                "#{Time.new.to_i}, "                                       <<
161
                "'#{comment}',"                                            <<
162
                "#{is_slave})"
163
        )
164

    
165
        puts comment
166
    end
167

    
168
    def db()
169
        return @db
170
    end
171

    
172
    private
173

    
174
    def db_exists?
175
        begin
176
            found = false
177

    
178
            # User with ID 0 (oneadmin) always exists
179
            @db.fetch("SELECT * FROM user_pool WHERE oid=0") { |row|
180
                found = true
181
            }
182
        rescue
183
        end
184

    
185
        return found
186
    end
187

    
188
    def init_log_time()
189
        @block_n = 0
190
        @time0 = Time.now
191
    end
192

    
193
    def log_time()
194
        if LOG_TIME
195
            @time1 = Time.now
196
            puts "    > #{db_version} Time for block #{@block_n}: #{"%0.02f" % (@time1 - @time0).to_s}s"
197
            @time0 = Time.now
198
            @block_n += 1
199
        end
200
    end
201
end
202

    
203
class BackEndMySQL < OneDBBacKEnd
204
    def initialize(opts={})
205
        @server  = opts[:server]
206
        @port    = opts[:port]
207
        @user    = opts[:user]
208
        @passwd  = opts[:passwd]
209
        @db_name = opts[:db_name]
210

    
211
        # Check for errors:
212
        error   = false
213

    
214
        (error = true; missing = "USER"  )  if @user    == nil
215
        (error = true; missing = "DBNAME")  if @db_name == nil
216

    
217
        if error
218
            raise "MySQL option #{missing} is needed"
219
        end
220

    
221
        # Check for defaults:
222
        @server = "localhost"   if @server.nil?
223
        @port   = 0             if @port.nil?
224

    
225
        # Clean leading and trailing quotes, if any
226
        @server  = @server [1..-2] if @server [0] == ?"
227
        @port    = @port   [1..-2] if @port   [0] == ?"
228
        @user    = @user   [1..-2] if @user   [0] == ?"
229
        @passwd  = @passwd [1..-2] if @passwd [0] == ?"
230
        @db_name = @db_name[1..-2] if @db_name[0] == ?"
231
    end
232

    
233
    def bck_file
234
        t = Time.now
235
        "#{VAR_LOCATION}/mysql_#{@server}_#{@db_name}_"<<
236
        "#{t.year}-#{t.month}-#{t.day}_#{t.hour}:#{t.min}:#{t.sec}.sql"
237
    end
238

    
239
    def backup(bck_file)
240
        cmd = "mysqldump -u #{@user} -p'#{@passwd}' -h #{@server} " +
241
              "-P #{@port} --add-drop-table #{@db_name} > #{bck_file}"
242

    
243
        rc = system(cmd)
244
        if !rc
245
            raise "Unknown error running '#{cmd}'"
246
        end
247

    
248
        puts "MySQL dump stored in #{bck_file}"
249
        puts "Use 'onedb restore' or restore the DB using the mysql command:"
250
        puts "mysql -u user -h server -P port db_name < backup_file"
251
        puts
252
    end
253

    
254
    def restore(bck_file, force=nil)
255
        connect_db
256

    
257
        if !force && db_exists?
258
            raise "MySQL database #{@db_name} at #{@server} exists," <<
259
                  " use -f to overwrite."
260
        end
261

    
262
        rc = system("mysql -u #{@user} -p'#{@passwd}' -h #{@server} -P #{@port} #{@db_name} < #{bck_file}")
263
        if !rc
264
            raise "Error while restoring MySQL DB #{@db_name} at #{@server}."
265
        end
266

    
267
        puts "MySQL DB #{@db_name} at #{@server} restored."
268
    end
269

    
270
    private
271

    
272
    def connect_db
273
        passwd = CGI.escape(@passwd)
274

    
275
        endpoint = "mysql://#{@user}:#{passwd}@#{@server}:#{@port}/#{@db_name}"
276

    
277
        begin
278
            @db = Sequel.connect(endpoint)
279
        rescue Exception => e
280
            raise "Error connecting to DB: " + e.message
281
        end
282
    end
283
end
284

    
285
class BackEndSQLite < OneDBBacKEnd
286
    require 'fileutils'
287

    
288
    def initialize(file)
289
        @sqlite_file = file
290
    end
291

    
292
    def bck_file
293
        t = Time.now
294
        "#{VAR_LOCATION}/one.db_"<<
295
        "#{t.year}-#{t.month}-#{t.day}_#{t.hour}:#{t.min}:#{t.sec}.bck"
296
    end
297

    
298
    def backup(bck_file)
299
        FileUtils.cp(@sqlite_file, "#{bck_file}")
300
        puts "Sqlite database backup stored in #{bck_file}"
301
        puts "Use 'onedb restore' or copy the file back to restore the DB."
302
        puts
303
    end
304

    
305
    def restore(bck_file, force=nil)
306
        if File.exists?(@sqlite_file) && !force
307
            raise "File #{@sqlite_file} exists, use -f to overwrite."
308
        end
309

    
310
        FileUtils.cp(bck_file, @sqlite_file)
311
        puts "Sqlite database backup restored in #{@sqlite_file}"
312
    end
313

    
314
    private
315

    
316
    def connect_db
317
        if !File.exists?(@sqlite_file)
318
            raise "File #{@sqlite_file} doesn't exist"
319
        end
320

    
321
        begin
322
            @db = Sequel.sqlite(@sqlite_file)
323
            @db.integer_booleans = true
324
        rescue Exception => e
325
            raise "Error connecting to DB: " + e.message
326
        end
327
    end
328
end