nixadm 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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