Class: CloudFlock::Target::Servers::Profile

Inherits:
Object
  • Object
show all
Defined in:
lib/cloudflock/target/servers/profile.rb,
lib/cloudflock/error.rb

Overview

Public: Provides methods to create a profile for a given host, mapping it to available public cloud offerings, and reference data gathered during this process.

Examples

# Generate a profile from a shell already logged in to a remote host
profile = Profile.new(ssh_object)
profile.build

# Only determine the memory and I/O statistics for a host
profile = Profile.new(ssh_object)
profile.determine_memory
profile.determine_io

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(shell) ⇒ Profile

Public: Initialize the Profile object.

shell - An SSH object open to the host to be profiled.

Raises ArgumentError if passed anything but an SSH object.

Raises:

  • (ArgumentError)


27
28
29
30
31
32
33
34
# File 'lib/cloudflock/target/servers/profile.rb', line 27

def initialize(shell)
  raise ArgumentError unless shell.kind_of? CloudFlock::Remote::SSH

  @shell = shell

  @warnings = []
  @info = {}
end

Instance Attribute Details

#warningsObject (readonly)

Public: Array containing warnings generated by the info gathering process.



20
21
22
# File 'lib/cloudflock/target/servers/profile.rb', line 20

def warnings
  @warnings
end

Instance Method Details

#[](key) ⇒ Object

Public: Simplify access to @info.

key - Key to check in @info.

Return value contained in @info by key.



343
344
345
# File 'lib/cloudflock/target/servers/profile.rb', line 343

def [](key)
  @info[key]
end

#buildObject

Public: Run all available determinations against the SSH object.

Returns nothing.



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/cloudflock/target/servers/profile.rb', line 39

def build
  determine_version
  determine_arch
  determine_hostname
  determine_memory
  determine_cpu
  determine_disk
  determine_ips
  determine_io
  determine_web
  determine_db
  determine_lib
  determine_rsync
  determine_processes
end

#determine_archObject

Public: Determine the architecture of the target host and assign it to @info.

Returns nothing.



104
105
106
107
108
109
110
111
112
113
114
# File 'lib/cloudflock/target/servers/profile.rb', line 104

def determine_arch
  uname = @shell.query("UNAME", "uname -m")
  case uname
  when /x86_64/
    @info[:arch] = 'x86_64'
  when /i\d86/
    @info[:arch] = 'i386'
  else
    @info[:arch] = "Unknown"
  end
end

#determine_cpuObject

Public: Determine the number of CPUs present on the target host and the speed of the processors. Assign these to @info[:count] and @info[:speed] respectively.

Returns nothing.



170
171
172
173
174
175
176
177
# File 'lib/cloudflock/target/servers/profile.rb', line 170

def determine_cpu
  count_command = 'cat /proc/cpuinfo|grep "^processor\\s*: [0-9]"|wc -l'
  speed_command = 'cat /proc/cpuinfo|grep "MHz"|head -1'
  cpus = {}
  cpus[:count] = @shell.query("CPU", count_command).to_i
  cpus[:speed] = @shell.query("MHZ", speed_command).gsub(/.*: /, '').to_i
  @info[:cpu] = cpus
end

#determine_dbObject

Public: Determine the amount of disk usage attributable to databases, as well as database count, and set this to @info. Currently supports MySQL.

Returns nothing.



263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/cloudflock/target/servers/profile.rb', line 263

def determine_db
  db = {}
  mysql_count = %w{find /var/lib/mysql/* -maxdepth 0 -type d 2>/dev/null |
                     wc -l}.join(' ')
  db[:count] = @shell.query("DB_MYSQL_COUNT", mysql_count)

  mysql_size = "du -s /var/lib/mysql 2>/dev/null | awk '{print $1}'"
  db[:size] = @shell.query("DB_MYSQL_SIZE", mysql_size)

  db[:count] = db[:count].to_i
  db[:size] = db[:size].to_i

  @info[:db] = db
end

#determine_diskObject

Public: Determine the amount of disk space in use on the target host and set that to @info.

Returns nothing.



183
184
185
186
187
188
189
190
# File 'lib/cloudflock/target/servers/profile.rb', line 183

def determine_disk
  # Use a less accurate (tends to inflate) method if du takes too long
  df_command = "df 2>/dev/null |awk '$1 ~ /\\// {I = I + $3} END {print I}'"
  disk = @shell.query("DISK_USED_DF", df_command) 

  # Result is returned as KiB used. We need GB used.
  @info[:disk] = disk.to_f / 1000 ** 2
end

#determine_hostnameObject

Public: Determine the hostname of the target host and assign it to @info

Returns nothing.



120
121
122
# File 'lib/cloudflock/target/servers/profile.rb', line 120

def determine_hostname
  @info[:hostname] = @shell.query("HOST", "hostname")
end

#determine_ioObject

Public: Determine amount of historical I/O usage via sysstat and set it to @info.

Returns nothing.



215
216
217
218
219
220
221
222
223
224
225
# File 'lib/cloudflock/target/servers/profile.rb', line 215

def determine_io
  io = {}

  iostat = @shell.query("IOSTAT", "iostat -c | sed -n 4p | awk '{print $4}'")
  io[:wait] = iostat.to_f

  up = @shell.query("UPTIME", "uptime | sed -e 's/.*up\\([^,]*\\),.*/\\1/'")
  io[:uptime] = up.chomp

  @info[:io] = io
end

#determine_ipsObject

Public: Determine the number of public and private IP addressess in use on the target host and assign that to @info.

Returns nothing.



196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/cloudflock/target/servers/profile.rb', line 196

def determine_ips
  ips = {:private => [], :public => []}

  ip_command = %w{/sbin/ifconfig | grep 'inet addr' | egrep -v ':127' | sed
                  -e 's/.*addr:\([0-9.]*\) .*/\1/'}.join(' ')
  ifconfig = @shell.query("IP_CONFIG", ip_command)

  ifconfig.each_line do |ip|
    ip.strip!
    ips[rfc1918?(ip)] << ip
  end

  @info[:ip] = ips
end

#determine_libObject

Public: Gather information about the currently installed libc, ruby, perl, python and php versions on the host, then set these values to @info.

Returns nothing.



282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
# File 'lib/cloudflock/target/servers/profile.rb', line 282

def determine_lib
  lib = {}

  libc_command = %w{ls -al `find /lib /usr/lib -name 'libc.so*' | head -1` |
                   sed 's/.*-> //'}.join(' ')
  lib[:libc] = @shell.query("LIBC", libc_command)
  lib[:libc].gsub!(/^.*-|\.so$/, '')

  lib[:perl] = @shell.query("PERL", "perl -e 'print $^V;'")
  lib[:perl] = lib[:perl].gsub(/^v([0-9.]*).*/, '\1')

  python_command = "python -c 'import sys; print sys.version' 2>/dev/null"
  lib[:python] = @shell.query("PYTHON", python_command)
  lib[:python] = lib[:python].gsub(/^([0-9.]*).*/m, '\1')

  ruby_command = "ruby -e 'print RUBY_VERSION' 2>/dev/null"
  lib[:ruby] = @shell.query("RUBY", ruby_command)

  lib[:php] = @shell.query("PHP_VER", "php -v 2>/dev/null | head -1")
  lib[:php] = lib[:php].gsub(/^PHP ([0-9.]*).*/, '\1')

  @info[:lib] = lib
end

#determine_memoryObject

Public: Determine the available and total memory on the target host as well as historical usage via sar(1), if possible. Assign these to @info and @info, respectively.

Returns nothing.



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
# File 'lib/cloudflock/target/servers/profile.rb', line 129

def determine_memory
  result = {}

  free = %w{free -m |awk '$1 ~ /Mem/ {print $2, $2-$6-$7}; $1 ~ /Swap/
            {print $3}'|xargs}.join(' ')
  mem = @shell.query("MEMORY", free)
  total, used, swap = mem.split(/\s+/)

  result[:total] = total.to_i
  result[:mem_used] = used.to_i
  result[:swap_used] = swap.to_i
  result[:swapping?] = swap.to_i > 0
  @info[:memory] = result

  # Determine average mem and swap usage
  result = {}
  sar_location = @shell.query("SAR", "which sar 2>/dev/null")
  sar_command = %w{for l in $(find /var/log/ -name 'sa??'); do sar -r -f $l |
                   grep Average; done | awk '{I+=1; TOT=$2+$3; CACHE+=$5+$6;
                   FREE+=$2; SWAP+=$9;} END {CACHE=CACHE/I; FREE=FREE/I;
                   SWAP=SWAP/I; print (TOT-(CACHE+FREE))/TOT*100,
                   SWAP;}'}.join(' ')

  if sar_location =~ /bin\//
    sar_usage = @shell.query("HIST_MEM", sar_command)

    if sar_usage =~ /\d \d/
      hist_mem, hist_swap = sar_usage.split(/ /)
      result[:mem_used] = hist_mem.to_i
      result[:swap_used] = hist_swap.to_i
    end
  end

  @info[:memory_hist] = result
end

#determine_processesObject

Public: Check the process listing and store it in

Returns nothing.



324
325
326
327
328
329
330
331
332
333
334
335
336
# File 'lib/cloudflock/target/servers/profile.rb', line 324

def determine_processes
  ps_command = 'ps aux'

  processes = @shell.query("PS_LIST", ps_command)
  @info[:processes] = processes.gsub(/\r/, '').split(/\n/)

  unless @info[:processes].grep(/psa/i).empty?
    @warnings << "Server likely to be running Plesk"
  end
  unless @info[:processes].grep(/cpanellogd/i).empty?
    @warnings << "Server likely to be running cPanel"
  end
end

#determine_rsyncObject

Public: Check for the existence of rsync(1) on the host. Set @info accordingly.

Returns nothing.



310
311
312
313
314
315
316
317
318
319
# File 'lib/cloudflock/target/servers/profile.rb', line 310

def determine_rsync
  rsync_command = %w{which rsync 2>/dev/null || ([ -f
                     /root/.rackspace/rsync ] && printf
                     '/root/.rackspace/rsync') || printf
                     'NONE'}.join(' ')

  rsync = @shell.query("RSYNC", rsync_command)

  @info[:rsync] = (rsync =~ /NONE/).nil? ? rsync : false
end

#determine_versionObject

Public: Determine vendor and version of the OS running on the target host, and create an appropriate CPE object for it, assigning it to @info.

Returns nothing.



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
# File 'lib/cloudflock/target/servers/profile.rb', line 59

def determine_version
  release = @shell.query("CPE", "cat /etc/system-release-cpe")

  begin
    cpe = CPE.parse(release)
    cpe.version.gsub!(/[^0-9.]/, '')
    @info[:cpe] = cpe
    return
  rescue ArgumentError
    cpe = CPE.new(part: CPE::OS, product: "linux")
  end

  issue = @shell.query("ISSUE", "cat /etc/issue")

  case issue
  when /Arch/
    cpe.vendor = "Arch"
  when /CentOS/
    cpe.vendor = "CentOS"
    issue.gsub!(/\.\d.*$/, '')
  when /Debian/
    cpe.vendor = "Debian"
    issue.gsub!(/\.\d.*$/, '')
  when /This is/
    cpe.vendor = "Gentoo"
  when /SUSE/
    cpe.vendor = "openSUSE"
  when /Ubuntu/
    cpe.vendor = "Ubuntu"
  when /Red/
    cpe.vendor = "Redhat"
    issue.gsub!(/\.\d.*$/, '')
  else
    cpe.vendor = "Unknown"
  end

  cpe.version = version_number(issue)

  @info[:cpe] = cpe
end

#determine_webObject

Public: Determine the web server used on the host, and attempt to enumerate domain names configured on the server for both non-SSL and SSL; set this information to @info. Presently only Apache is supported.

Returns nothing.



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
# File 'lib/cloudflock/target/servers/profile.rb', line 232

def determine_web
  web = {}

  netstat_command = %w{netstat -ntlp | awk '$4 ~ /:80$/ || $4 ~ /:443$/
               {sub(/^[^\/]*\//, ""); print $NF}' | head -1}.join(' ')
  web[:binary] = @shell.query("WEB_NETSTAT", netstat_command)

  unless web[:binary].empty?
    version_command = "`which #{web[:binary]}` -v | grep version"
    web[:version] = @shell.query("WEB_VERSION", version_command)
    web[:version].gsub!(/.*version: /i, '')
    binary = web[:binary] == "httpd" ? "apachectl" : "apache2ctl"
    binary << " -S 2>&1"

    web_command = "#{binary} | grep ')' | grep -vi 'default' | wc -l"
    hosts = @shell.query("WEB_HTTP", web_command)
    web[:hosts_http] = hosts.to_i

    ssl_command = "#{binary} | grep ':443'|grep -vi 'default' | wc -l"
    ssl_hosts = @shell.query("WEB_HTTPS", ssl_command)
    web[:hosts_https] = ssl_hosts.to_i
  end

  @info[:web] = web
end

#keysObject

Public: Allow access to the list of keys extant in @info.

Return an Array of keys present in @info.



350
351
352
# File 'lib/cloudflock/target/servers/profile.rb', line 350

def keys
  @info.keys
end

#rfc1918?(ip) ⇒ Boolean

Internal: Determine whether a given IP resides in a block designated as private by RFC1918.

ip - A String containing an IP address.

Returns :private or :public Symbol based on whether the IP is private.

Returns:

  • (Boolean)


384
385
386
387
388
389
390
391
392
393
# File 'lib/cloudflock/target/servers/profile.rb', line 384

def rfc1918?(ip)
  octets = ip.split /\./
  if octets[0] == "10" || (octets[0] == "192" && octets[1] == "168")
    return :private
  elsif octets[0] == "172" && octets[1].to_i >=16 && octets[1].to_i <= 31
    return :private
  end

  :public
end

#to_hashObject

Public: Return server information and warnings as a Hash. Useful for calling Hash#merge.

Return info Hash.



358
359
360
# File 'lib/cloudflock/target/servers/profile.rb', line 358

def to_hash
  @info.merge({warnings: @warnings})
end

#version_number(version) ⇒ Object

Internal: Deconstruct the version String provided in order to strip out extraneous data before and after the version number. Version number strings are defined as beginning with a digit, ending with a digit, and containing nothing but digits and at most one decimal point.

version - A String containing version number of the OS on the target host.

Returns a String containing the parsed version string.



370
371
372
373
374
375
376
# File 'lib/cloudflock/target/servers/profile.rb', line 370

def version_number(version)
  if version =~ /\d/
    version.gsub(/^[^\d]*/, '').gsub(/[^\d]*$/, '').gsub(/(\d*\.\d*).*/, '\1')
  else
    "-"
  end
end