nixadm 1.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c7d91b74d9982ad275f6432d2ed60e6835a3e2bc05e057da200c927200c00998
4
+ data.tar.gz: 2873bd8d7aed1583f8bb7464a8114911f4886af8b79024336b7c99d978177ebb
5
+ SHA512:
6
+ metadata.gz: f2055495232a0a6d87f9fa7174daead46b7148606d633677e06b046c20237f2f43951510b085169b9e5cb668a6c8fef0ed850cbafccc3775a481ec4df5346d3a
7
+ data.tar.gz: ac9bef4a7749c7f812b0fbea27965bd1a0e5209120d56b6ca1ea9629585e84cbaf84b28eeffbbcc8f75dee47472be18c85943f40eec0703a37c7456679e9d18a
@@ -0,0 +1,26 @@
1
+ # NixAdm
2
+
3
+ ## About
4
+
5
+ This is a general library designed to aid with system administration tasks,
6
+ specifically to replace all shell scripting with Ruby code.
7
+
8
+ ## Installation
9
+
10
+ You can install directly from the command line via Ruby gems as follows:
11
+
12
+ gem install nixadm
13
+
14
+ ## Building from Source
15
+
16
+ This project uses CMake. To build the gem:
17
+
18
+ cmake .
19
+ make gem
20
+
21
+ The resultant gem will be generated in the <tt>pkg</tt> directory.
22
+
23
+ ## License
24
+
25
+ Copyright information is located in the COPYING file. The software license is
26
+ located in the LICENSE file.
@@ -0,0 +1,267 @@
1
+ require 'nixadm/util'
2
+ require 'nixadm/zfs'
3
+
4
+ module NixAdm
5
+
6
+ # Base backup class. Provides centralized logging facility and job start/stop
7
+ # templates.
8
+ class Backup < NixAdm::Command
9
+
10
+ attr_reader :host
11
+
12
+ def initialize(host, options = {})
13
+ super(host, options)
14
+
15
+ if not $backup_dir.nil?
16
+ @logfile = File.open("#{$backup_dir}/backup.log", 'w')
17
+ end
18
+ end
19
+
20
+ def run()
21
+ startBackup()
22
+ yield
23
+ endBackup()
24
+ end
25
+
26
+ def startBackup()
27
+
28
+ end
29
+
30
+ def endBackup()
31
+
32
+ end
33
+
34
+ #------------------------------------------------------------------------------
35
+ # Utility functions
36
+ #------------------------------------------------------------------------------
37
+
38
+ protected
39
+
40
+ def cd(path)
41
+ Dir.chdir(path)
42
+ end
43
+
44
+ def replicationStatus(mfs, sfs)
45
+ h1 = mfs.pool.host.name
46
+ p1 = mfs.pool.name
47
+ f1 = mfs.name
48
+ h2 = sfs.pool.host.name
49
+ p2 = sfs.pool.name
50
+ f2 = sfs.name
51
+
52
+ return "replicating #{h1}:#{p1}/#{f1} -> #{h2}:#{p2}/#{f2}"
53
+ end
54
+
55
+ # Run general shell command and log output
56
+ def run_command(command)
57
+ sys = NixAdm::Pipeline.new()
58
+
59
+ sys.classic(command) do |stdin, stdout|
60
+ stdin.close()
61
+
62
+ while true
63
+ line = stdout.gets
64
+ break if line.nil?
65
+ @logfile.write(line)
66
+ end
67
+ end
68
+ end
69
+
70
+ # Executes a command, captures output into file and zips the file
71
+ # @param command A command or array containing a pipeline of commands
72
+ # @param filename The name (path) of the file to dump database to
73
+ def zipStore(host, command, filename)
74
+ store host, command, filename, zip: true
75
+ end
76
+
77
+ # Executes a command, captures output into file
78
+ # @param command A command or array containing a pipeline of commands
79
+ # @param filename The name (path) of the file to dump database to
80
+ # @param zip If true, gzip the file.
81
+ def store(host, command, filename, zip: false)
82
+ sys = NixAdm::Pipeline.new()
83
+ pipeline = nil
84
+
85
+ sys.debug = debug()
86
+
87
+ # If the host we are performing this command on is the same host, don't
88
+ # resolve with host as secong argument, as this just makes that host ssh
89
+ # back into itself to run the command.
90
+ if @host == host
91
+ pipeline = resolveCommand(command)
92
+ else
93
+ pipeline = resolveCommand(command, host)
94
+ end
95
+
96
+ sys.run([pipeline]) do |stdin, stdout|
97
+ stdin.close()
98
+
99
+ dumpfile = File.open(filename, 'wb')
100
+
101
+ while true
102
+ data = stdout.read(1024)
103
+ break if data == nil
104
+ dumpfile.write(data)
105
+ end
106
+
107
+ dumpfile.close()
108
+ end
109
+
110
+ if zip == true
111
+ # Compress the file
112
+ sys.run "gzip -f #{filename}"
113
+ end
114
+ end
115
+
116
+ def backup_dir(dir)
117
+ return "#{$backup_dir}/#{dir}"
118
+ end
119
+
120
+ # Replicates ZFS filesystem(s) on from host to backup hosts
121
+ #
122
+ # @param host The main host we are backing up
123
+ # @param filesystems An array of ZFS filesystems to replicate
124
+ #
125
+ # @param targets An array or hashes defining target hosts to replicate
126
+ # to. It has the following form:
127
+ #
128
+ # targets = [ { host: 'root@midway', root: 'pool' },
129
+ # { host: 'root@backup.c21bowman.com', root: 'pool' },
130
+ # { host: 'root@arc2', root: 'pool' } ]
131
+ #
132
+ def replicate(host, objects, targets)
133
+ admin = NixAdm::ZFS::Admin.new(host)
134
+
135
+ objects.each do |object|
136
+
137
+ # First look for matching filesystem
138
+ master_object = admin.object(object)
139
+
140
+ if master_object.nil?
141
+ raise "Error: #{object} does not exist"
142
+ end
143
+
144
+ startSourceZfsReplication master_object
145
+
146
+ failed = false
147
+ targets.each do | target |
148
+
149
+ if target[:host].nil? or target[:pool].nil?
150
+ raise "Invalid arguments: missing host or pool"
151
+ end
152
+
153
+ slave = NixAdm::ZFS::Host.new(target[:host])
154
+ pool = slave.pool(target[:pool])
155
+
156
+ attempts = 0
157
+ while attempts < 2
158
+ slave_object = pool.object(master_object.name)
159
+
160
+ break if not slave_object.nil?
161
+
162
+ # If we get here, it means the remote object does not exists. If this
163
+ # is a filesystem, we must first create it. If it's a zvol, we do
164
+ # nothing.
165
+
166
+ $stderr.puts "Dest ZFS entity '#{pool.name}/#{master_object.name}' " +
167
+ "does not exist ... creating"
168
+
169
+ if master_object.is_a?(NixAdm::ZFS::Filesystem)
170
+ if pool.createFilesystem(master_object.name) == false
171
+ msg = "Error: ZFS entity '#{pool.name}/#{master_object.name}' " + \
172
+ "does not exist and could not be created"
173
+ raise msg
174
+ end
175
+ elsif master_object.is_a?(NixAdm::ZFS::Volume)
176
+ if pool.createVolume(master_object.name) == false
177
+ msg = "Error: ZFS entity '#{pool.name}/#{master_object.name}' " + \
178
+ "does not exist and could not be created"
179
+ raise msg
180
+ end
181
+ end
182
+
183
+ attempts += 1
184
+ end
185
+
186
+ if block_given?
187
+ yield master_object, slave_object
188
+ end
189
+
190
+ if admin.replicate(master_object, slave_object) == true
191
+ endDestZfsReplication slave_object
192
+ else
193
+ failed = true
194
+ msg = "FAILED: #{job} - #{admin.status.msg}"
195
+ log msg, admin.status.val
196
+ end
197
+ end
198
+
199
+ if failed == false
200
+ endSourceZfsReplication master_object
201
+ end
202
+ end
203
+ end
204
+
205
+ def startSourceZfsReplication(master_object)
206
+ return success()
207
+ end
208
+
209
+ def endDestZfsReplication(slave_object)
210
+ return success()
211
+ end
212
+
213
+ def endSourceZfsReplication(master_object)
214
+ return success()
215
+ end
216
+
217
+ # Trim snaphots on given filesystem
218
+ #
219
+ # @param host The host name
220
+ # @param filesystems A list of filesystems to trim snapshots
221
+ def trim(host, objects)
222
+ admin = NixAdm::ZFS::Admin.new(host)
223
+
224
+ objects.each do |object|
225
+ master_object = admin.object(object)
226
+
227
+ if not master_object.nil?
228
+ master_object.trimSnapshots()
229
+ end
230
+ end
231
+ end
232
+
233
+ end
234
+
235
+ class PrimaryBackup < Backup
236
+
237
+ def initialize(host)
238
+ super
239
+ end
240
+
241
+ def startSourceZfsReplication(master_object)
242
+ return master_object.createSnapshot()
243
+ end
244
+
245
+ def endSourceZfsReplication(master_object)
246
+ return master_object.trimSnapshots()
247
+ end
248
+
249
+ end # module PrimaryBackup
250
+
251
+ class SecondaryBackup < Backup
252
+
253
+ def initialize(host)
254
+ super
255
+ end
256
+
257
+ def endDestZfsReplication(slave_object)
258
+ return slave_object.trimSnapshots()
259
+ end
260
+
261
+ def endSourceZfsReplication(master_object)
262
+ return master_object.trimSnapshots()
263
+ end
264
+
265
+ end # module SecondaryBackup
266
+
267
+ end # module NixAdm
@@ -0,0 +1,9 @@
1
+ require 'nixadm/util'
2
+ require 'nixadm/backup'
3
+
4
+ module NixAdm
5
+ module PostreSQL
6
+
7
+
8
+ end # module PostreSQL
9
+ end # module NixAdm
@@ -0,0 +1,217 @@
1
+ require 'nixadm/util'
2
+ require 'nixadm/backup'
3
+
4
+ module NixAdm
5
+ module PostgreSQL
6
+
7
+ module Replication
8
+
9
+ class Node
10
+
11
+ include NixAdm::Util
12
+
13
+ def initialize(host, config_file=nil)
14
+ @host = host
15
+ @db = connectDb(host, config_file)
16
+
17
+ if @db.nil?
18
+ raise "Connection failed to #{host}"
19
+ end
20
+
21
+ logSystemInit(config_file)
22
+ @logfields[:system] = 'psql'
23
+ end
24
+
25
+ def status()
26
+ return @db.exec('select * from pg_stat_replication')
27
+ end
28
+
29
+ end # class Node
30
+
31
+ # Primary database instance
32
+ class Primary < Node
33
+
34
+ include NixAdm::Util
35
+
36
+ attr_reader :db
37
+
38
+ def initialize(host, config_file=nil)
39
+ super
40
+ end
41
+
42
+ def currentLsm()
43
+ return @db.exec('select pg_current_wal_lsn()')[0]
44
+ end
45
+
46
+ end # class Master
47
+
48
+ # Hot standby instance
49
+ class Secondary < Node
50
+
51
+ include NixAdm::Util
52
+
53
+ attr_reader :primary
54
+
55
+ def initialize(primary, host, config_file=nil)
56
+ @primary = primary
57
+
58
+ super(host, config_file)
59
+ end
60
+
61
+ def currentLsm()
62
+ return @db.exec('select pg_last_wal_receive_lsn()')[0]
63
+ end
64
+
65
+ end # class Replica
66
+
67
+ end # module Replication
68
+
69
+ # This class exports an entire PostgreSQL database cluster to $backup_dir/db,
70
+ # including the relevant global settings.
71
+ #
72
+ # Example:
73
+ #
74
+ # export = Export.new('db', { :debug => true })
75
+ # export.run()
76
+
77
+ class Export < NixAdm::Backup
78
+
79
+ attr_accessor :filesystems, :targets
80
+
81
+ def initialize(host, options = {})
82
+ super host, options: options
83
+
84
+ @ssh_opts = '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
85
+ end
86
+
87
+ def log(msg, code=0)
88
+ $stderr.puts msg
89
+ end
90
+
91
+ #------------------------------------------------------------------------------
92
+ # Main backup function
93
+ #------------------------------------------------------------------------------
94
+
95
+ def run()
96
+ begin
97
+ backup_databases()
98
+ rescue Exception => ex
99
+ log "Backup failed: #{ex.to_s}\n #{ex.backtrace}"
100
+ end
101
+ end
102
+
103
+ #------------------------------------------------------------------------------
104
+ # Individual backup procedures
105
+ #------------------------------------------------------------------------------
106
+
107
+ # This does a pg_dump of all databases in plaintext format. It gzips them all.
108
+ def backup_databases()
109
+ cd backup_dir('db')
110
+
111
+ # List of databases on server
112
+ databases = listDatabases()
113
+
114
+ # List of databases to exclude
115
+ exclude =
116
+ {
117
+ 'template0' => true,
118
+ 'template1' => true
119
+ }
120
+
121
+ # Backup individual databases
122
+ databases.each do |db|
123
+ next if exclude.has_key?(db)
124
+
125
+ begin
126
+ log "Dumping: #{db}"
127
+ store @host, "pg_dump -p 5433 #{db}", "#{db}.db"
128
+ rescue
129
+ puts $!
130
+ log "Failed #{@host} #{db}: #{$?.to_s}", 1
131
+ next
132
+ end
133
+ end
134
+
135
+ # Dump the globals
136
+ globals_file = 'globals.sql'
137
+ log "Dumping globals to #{globals_file}"
138
+ store @host, 'pg_dumpall -g -p 5433', globals_file
139
+ end
140
+
141
+ protected
142
+
143
+ def listDatabases()
144
+ command = %Q{ psql -At -p 5433 postgres -c 'select datname from pg_database' }
145
+ pipeline = resolveCommand(command)
146
+
147
+ dbs = []
148
+ exec([pipeline]) do |line|
149
+ dbs << line.strip
150
+ end
151
+
152
+ return dbs
153
+ end
154
+
155
+ end # class Export
156
+
157
+ # This class imports everything exported from the Export class. Before
158
+ # importing, you need to make sure you initialize your database with proper
159
+ # encoding and collation. To do so, switch user to postgres. Then run:
160
+ #
161
+ # /usr/local/bin/initdb --pgdata=/var/lib/pgsql9/data/ -E 'UTF-8' \
162
+ # --lc-collate='en_US.UTF-8' --lc-ctype='en_US.UTF-8';
163
+ #
164
+ # /usr/local/bin/createuser -sl root
165
+ #
166
+ # Then start Postgres. Now you can import databases
167
+
168
+ class Import < NixAdm::Backup
169
+
170
+ def initialize(host, options = {})
171
+ super host, options: options
172
+
173
+ @ssh_opts = '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
174
+ end
175
+
176
+ #------------------------------------------------------------------------------
177
+ # Main backup function
178
+ #------------------------------------------------------------------------------
179
+
180
+ def run()
181
+ begin
182
+ import_databases()
183
+ rescue Exception => ex
184
+ log "Import failed: #{ex.to_s}\n #{ex.backtrace}"
185
+ end
186
+ end
187
+
188
+ def import_databases()
189
+ dbs = Dir.entries($backup_dir)
190
+
191
+ # Restore globals first
192
+ system "psql -p 5433 -d postgres -f #{$backup_dir}/globals.sql"
193
+
194
+ dbs.each do |file|
195
+ next if File.extname(file) != '.db'
196
+
197
+ db = file.split('.')[0]
198
+ puts "Loading #{db}"
199
+
200
+ sql = %q{ select count(*) from pg_database where datname='dba' }
201
+ count = %x{ psql -p 5433 postgres -At -c "#{sql}" }
202
+ count.strip!
203
+
204
+ if count == '1'
205
+ %x{ psql -p 5433 postgres -c 'drop database #{db}' }
206
+ end
207
+
208
+ %x{ psql -p 5433 postgres -c 'create database #{db}' }
209
+ %x{ psql -p 5433 -d #{db} -f #{$backup_dir}/#{file} }
210
+ %x{ psql -p 5433 #{db} -c 'analyze' }
211
+ end
212
+ end
213
+
214
+ end # class Import
215
+
216
+ end # module PostgreSQL
217
+ end # module NixAdm