gaddygaddy 0.1.78

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,396 @@
1
+ #
2
+ # Name:
3
+ # gaddygaddy-client.rb
4
+ #
5
+ # Created by: GaddyGaddy
6
+ #
7
+ # Description:
8
+ #
9
+ #
10
+ #
11
+ # Copyright (c) 2013 GaddyGaddy
12
+ #
13
+ # All rights reserved.
14
+ #
15
+
16
+ APPLICATION_NAME = "gaddygaddy-client"
17
+ CHEF_CMD = '/usr/local/bin/chef-solo'
18
+
19
+ require 'gg_config/gg_config'
20
+ require 'device_info/device_info'
21
+ require 'fileutils'
22
+ require 'utils/hash_monkeypatch'
23
+ require 'json'
24
+ require 'subcommand'
25
+ require 'gaddygaddy-client/chef_files'
26
+ require 'gaddygaddy-client/notification'
27
+ require 'gaddygaddy-client/log_data'
28
+ require 'logging/logging'
29
+ require 'restclient'
30
+ require 'systemu'
31
+ require 'utils/request'
32
+ require 'utils/retriable'
33
+ require 'utils/run'
34
+ require 'error_constants'
35
+ require_relative 'gaddygaddy-client/espeak'
36
+
37
+ REVISION = "$Revision: 1 $"[10..-3].chomp
38
+
39
+ DEFAULT_CHEF_DIR = "/var/chef"
40
+ DEFAULT_CONF_DIR = "/conf"
41
+ DEFAULT_CONF_FILE = "gaddy_*.gcf"
42
+ CONFIG_FILE_NAME = "node.json"
43
+
44
+ class TokenJException < JException;end
45
+ class CreateDeviceJException < JException;end
46
+
47
+ class GaddyGaddy_Client
48
+
49
+ include Logging
50
+ include Subcommands
51
+ include Retriable
52
+ extend Retriable
53
+
54
+ def initialize
55
+ logger.outputters = Log4r::Outputter.stdout
56
+ end
57
+
58
+ def usage
59
+ puts "gaddygaddy-client [OPTIONS]"
60
+ puts "The client to run for the GaddyGaddy service"
61
+ puts "For more information about the commands run gaddygaddy-client --help"
62
+ puts "Revision :#{REVISION}"
63
+ puts
64
+ end
65
+
66
+ def read_options
67
+ # This hash will hold all of the options
68
+ # parsed from the command-line by
69
+ # OptionParser.
70
+ @options = {}
71
+
72
+
73
+ global_options do |opts|
74
+ opts.banner = "Usage: gaddygaddy-client [options] [subcommand [options]]"
75
+ opts.description = "GaddyGaddy client for the GaddyGaddy service"
76
+ opts.separator ""
77
+ opts.separator "Global options are:"
78
+
79
+ @options[:conf_dir] = DEFAULT_CONF_DIR
80
+ opts.on( '--config_dir CONF-DIR', "Directory containing the client configuration files, default is #{DEFAULT_CONF_DIR}" ) do |conf_dir|
81
+ @options[:conf_dir] = conf_dir
82
+ end
83
+
84
+ @options[:conf_file] = DEFAULT_CONF_FILE
85
+ opts.on( '--config_file CONF-FILE', "File name (without) path for the configuration file, default is #{DEFAULT_CONF_FILE}" ) do |conf_file|
86
+ @options[:conf_file] = conf_file
87
+ end
88
+
89
+ @options[:log_file] = nil
90
+ opts.on( '-L', '--logfile FILE', 'Write log to FILE, defaults to STDOUT' ) do|file|
91
+ @options[:log_file] = file
92
+ end
93
+
94
+ @options[:file_host] = "config2.gaddygaddy.com"
95
+ opts.on( '--file_host FILE_HOST', 'The file host to get files like cookbooks from' ) do|file_host|
96
+ @options[:file_host] = file_host
97
+ end
98
+
99
+ @options[:host] = "https://www.gaddygaddy.com:8012"
100
+ opts.on( '-H', '--host HOST', 'The host to connect to' ) do|host|
101
+ @options[:host] = host
102
+ end
103
+
104
+ @options[:log_level] = 'info'
105
+ opts.on( '-l', '--log_level level', 'Set the log level (debug, info, warn, error, fatal)' ) do|level|
106
+ @options[:log_level] = level
107
+ end
108
+
109
+ @options[:test_mode] = false
110
+ opts.on( '--test_mode', 'Used for testing, will only retry once for example' ) do
111
+ @options[:test_mode] = true
112
+ end
113
+
114
+ @options[:token] = nil
115
+ opts.on( '-t', '--token TOKEN', 'The token to be used to access' ) do|token|
116
+ @options[:token] = token
117
+ end
118
+
119
+
120
+
121
+ end
122
+
123
+ add_help_option
124
+
125
+ command :get_cookbooks do |opts|
126
+ opts.banner = "Usage: get_cookbooks [options]"
127
+ opts.description = "Will download the cookbooks from the GaddyGaddy service"
128
+ @options[:chef_dir] = DEFAULT_CHEF_DIR
129
+ opts.on( '--chef_dir CHEF-DIR', "The chef dir to place the files into, default is #{DEFAULT_CHEF_DIR}" ) do |chef_dir|
130
+ @options[:chef_dir] = chef_dir
131
+ end
132
+ @options[:cookbooks_version] = nil
133
+ opts.on( '--cookbooks_version COOKBOOKS_VERSION', "Override the setting of which cookbook version to download" ) do |cookbooks_version|
134
+ @options[:cookbooks_version] = cookbooks_version
135
+ end
136
+ end
137
+
138
+ command :chef_config do |opts|
139
+ opts.banner = "Usage: chef_config [options]"
140
+ opts.description = "Will download the config and place it in a directory"
141
+ end
142
+
143
+ command :send_device_data do |opts|
144
+ opts.banner = "Usage: send_device_data [options]"
145
+ opts.description = "Will send information about the gaddy to gaddygaddy.com"
146
+ end
147
+
148
+ command :upload_log_file do |opts|
149
+ opts.banner = "Usage: verify_installation[options]"
150
+ opts.description = "Will verify the installation and check for gaddy file and network configuration"
151
+ @options[:lines] = 100
152
+ opts.on("-i", "--lines lines", "Number of lines to upload, count from end of file") do |lines|
153
+ @options[:lines] = lines
154
+ end
155
+ opts.on("-f", "--id function_id", "Function id") do |function_id|
156
+ @options[:function_id] = function_id
157
+ end
158
+ @options[:upload_log_file] = nil
159
+ opts.on("-u", "--upload_log_file upload_log_file", "Name of log file to upload, mandatory") do |upload_log_file|
160
+ @options[:upload_log_file] = upload_log_file
161
+ end
162
+ end
163
+
164
+ command :verify_installation do |opts|
165
+ opts.banner = "Usage: verify_installation[options]"
166
+ opts.description = "Will verify the installation and check for gaddy file and network configuration"
167
+ end
168
+
169
+ command :notify do |opts|
170
+ opts.banner = "Usage: verify_installation[options]"
171
+ opts.description = "Will verify the installation and check for gaddy file and network configuration"
172
+ @options[:event] = nil
173
+ opts.on("-e", "--event event", "Event to notify, should be json format") do |event|
174
+ @options[:event] = event
175
+ end
176
+ end
177
+
178
+ cmd = nil
179
+ begin
180
+ cmd = opt_parse
181
+ mandatory = []
182
+ missing = mandatory.select{ |param| @options[param].nil? }
183
+ unless missing.empty?
184
+ puts "Missing options: #{missing.join(', ')}" #
185
+ puts opt_parse #
186
+ exit 1
187
+ end
188
+ unless cmd
189
+ puts "No command is specified\n\n"
190
+ usage
191
+ puts print_actions
192
+ exit 1
193
+ end
194
+ rescue OptionParser::InvalidOption, OptionParser::MissingArgument #
195
+ puts $!.to_s # Friendly output when parsing fails
196
+ puts opt_parse #
197
+ exit 1 #
198
+ end
199
+ cmd
200
+ end
201
+
202
+ def gg_config
203
+ @gg_config ||= GGConfig.new(:conf_dir => @options[:conf_dir], :conf_file => @options[:conf_file])
204
+ end
205
+
206
+ # Get the config directory
207
+ def conf_dir
208
+ @options[:conf_dir]
209
+ end
210
+
211
+ # Should try to get the host from options or config
212
+
213
+ def get_host
214
+ host = @options[:host]
215
+ # This is a temporary fix to handle the move to Amazon
216
+ if host == 'ap.gaddygaddy.com:86'
217
+ host = 'ap-pre.gaddygaddy.com:86'
218
+ end
219
+ host
220
+ end
221
+
222
+
223
+ def get_chef_config_file
224
+ response = Request.client_service_get get_host, "/chef/node_json/1/#{gg_config.user_id_salt}/#{gg_config.device_id}/#{gg_config.token}"
225
+ raise "Could not get config file for device #{gg_config.device_id}" unless response["run_list"].length > 0
226
+ logger.debug "Got config file: #{response}"
227
+ response
228
+ end
229
+
230
+ def get_chef_config
231
+ begin
232
+ FileUtils.mkdir_p conf_dir
233
+ rescue Exception => e
234
+ raise e.inspect
235
+ end
236
+ conf_content = get_chef_config_file
237
+ chef_config = conf_content['variables']
238
+ chef_config['run_list'] = conf_content['run_list']
239
+
240
+ conf_file = File.open File.join(conf_dir, CONFIG_FILE_NAME), "w"
241
+ conf_file.write chef_config.to_json
242
+ conf_file.close
243
+ end
244
+
245
+ # Check that the config file exist and has valid content, this will also be validated toward the server
246
+ def config_valid
247
+ config_ok = false
248
+ config_file_name = Dir.glob(File.join(conf_dir, @options[:conf_file]))[0]
249
+ logger.debug "Will validate config for #{config_file_name}"
250
+ if File.exists?(config_file_name)
251
+ begin
252
+ conf_file = File.open(config_file_name)
253
+ config = JSON.parse(conf_file.read)
254
+ rescue Exception => e
255
+ raise "Could not read from file #{config_file_name}"
256
+ end
257
+ logger.debug "Have read from file #{config_file_name}"
258
+ config_ok = config["device_id"]
259
+ end
260
+ # TODO validate the client against the client service
261
+ config_ok
262
+ end
263
+
264
+ # Get a device id, to start with only implemented for Raspberry
265
+ def hardware_id
266
+ hardware_id = `cat /proc/cpuinfo|grep Serial`.strip[10..-1].to_s.strip
267
+ logger.debug "The device id is #{hardware_id}"
268
+ if hardware_id == ""
269
+ hardware_id = 'test_device'
270
+ end
271
+ raise JNoDeviceIDFound.new({:message => "Could not found a device id for this computer, a device id is needed, see further help"}) unless hardware_id
272
+ hardware_id
273
+ end
274
+
275
+ # Will send ohai device data to gaddygaddy
276
+ def send_device_data
277
+ @device_info.post
278
+ end
279
+
280
+ def upload_log_file
281
+ raise "Missing option upload_log_file" unless @options[:upload_log_file]
282
+ log_file_name = @options[:upload_log_file].split('/').last
283
+ log_file_name = log_file_name[0..-5] if log_file_name[-4..-1] == '.log'
284
+ logger.debug "Log file name is #{log_file_name}"
285
+ full_log_file_name = @options[:upload_log_file]
286
+ url = Request.get_base_url(get_host) + "/device/upload_log_file/1/#{gg_config.user_id_salt}/#{gg_config.device_id}/#{gg_config.token}/#{log_file_name}"
287
+ log_data = LogData.get_log_data(@options[:upload_log_file], @options[:lines])
288
+ log_time_stamp = LogData.get_log_file_time(@options[:upload_log_file])
289
+ params = {:log_data => log_data,
290
+ :log_time_stamp => log_time_stamp,
291
+ :full_log_file_name => full_log_file_name,
292
+ }
293
+ params[:function_id] = @options[:function_id] if @options[:function_id]
294
+ response = Request.client_service_post url, params
295
+ logger.debug "The response for the request is #{response} #{response.class}"
296
+ raise JCouldNotPostClientDataException.new({:message=> "Could not post data to the gaddygaddy service, error code is #{response.body}"}) unless response[:status].to_i == 0
297
+ end
298
+
299
+
300
+ def self.alert(type)
301
+ alert_timing = case type
302
+ when :no_config_file
303
+ {:on => 0.1, :off => 0.1,:alert_text => 'Could not found any gaddy config file in /conf/gaddy_XXXX.gcf'}
304
+ when :no_network
305
+ {:on => 1,:off => 1, :alert_text => 'Could not connect to internet, network configuration seems broken, restart when fixed'}
306
+ end
307
+ begin
308
+ `echo none >/sys/class/leds/led0/trigger`
309
+ count = 0
310
+ send_info_count = 20 / (alert_timing[:on] + alert_timing[:off])
311
+ do_alert = true
312
+ while do_alert do
313
+ if (count % send_info_count) == 0
314
+ puts alert_timing[:alert_text]
315
+ ESpeak.speak alert_timing[:alert_text]
316
+ end
317
+ `echo 1 >/sys/class/leds/led0/brightness`
318
+ sleep alert_timing[:on]
319
+ `echo 0 >/sys/class/leds/led0/brightness`
320
+ sleep alert_timing[:off]
321
+ count += 1
322
+ end
323
+ ensure
324
+ `echo mmc0 >/sys/class/leds/led0/trigger`
325
+ end
326
+ end
327
+
328
+ def self.ip_to_verify
329
+ '8.8.8.8'
330
+ end
331
+
332
+ def self.network_failed?
333
+ ping_result = `ping -w 10 #{ip_to_verify} -c 1`
334
+ ping_result.index("100% packet loss") || ($?.exitstatus !=0)
335
+ end
336
+
337
+ # Will run chef for network config to try to install network
338
+ def install_network
339
+ # chef_cmd = CHEF_CMD + '
340
+ # run_cmd()
341
+ end
342
+
343
+ # Verify that we have a conf file and verify network connection
344
+ def self.verify_installation
345
+ conf_file = Dir.glob(File.join('/','conf', "gaddy*.gcf"))
346
+ alert(:no_config_file) if conf_file.empty?
347
+ if network_failed?
348
+ # install_network
349
+ alert(:no_network)
350
+ end
351
+ end
352
+
353
+ def read_settings
354
+ environment = ENV['RACK_ENV'] || ENV['ENV'] || 'gaddy'
355
+ YAML.load(File.read(File.join(File.dirname(__FILE__), '..','conf', "#{environment}.yml")))
356
+ end
357
+
358
+ def run
359
+ cmd = read_options
360
+ settings = read_settings
361
+ begin
362
+ Retriable.set_test_mode if @options[:test_mode]
363
+ @device_info = DeviceInfo.new(get_host, gg_config)
364
+ set_log_level @options[:log_level]
365
+ set_log_file @options[:log_file] if @options[:log_file]
366
+ logger.info "Will start #{APPLICATION_NAME} with command #{cmd}"
367
+ case cmd
368
+ when "get_cookbooks"
369
+ chef_files = ChefFiles.new(gg_config, get_host, @options[:chef_dir])
370
+ chef_files.get_cookbooks @options[:file_host], @options[:cookbooks_version]
371
+ when "chef_config"
372
+ get_chef_config
373
+ when 'send_device_data'
374
+ send_device_data
375
+ when "verify_installation"
376
+ self.class.verify_installation
377
+ when 'notify'
378
+ Notification::FileNotification.file_dir = settings['file_dir']
379
+ notification = Notification::Send.new(:speech_enabled => settings['speech_enabled'])
380
+ notification.event = @options[:event]
381
+ notification.notify gg_config.device_id
382
+ when 'upload_log_file'
383
+ upload_log_file
384
+ else
385
+ usage
386
+ raise "No valid command entered, the command is #{cmd}"
387
+ end
388
+
389
+ rescue Exception => e
390
+ logger.error e.message
391
+ logger.error "Enable full stack trace with -l DEBUG" unless logger.debug?
392
+ logger.error "Backtrace:\n\t#{e.backtrace.join("\n\t")}" if logger.debug?
393
+ exit -1
394
+ end
395
+ end
396
+ end