continuent-tools-core 0.9.0 → 0.10.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e7f7d7c19ce143f710bec3d7b7c0548c76e2cc4b
4
- data.tar.gz: c8fc19d558b5e084195527b21737623a8d56dc3d
3
+ metadata.gz: 98ee92f1a8d4f2e609211c326b0fe054e9a048ad
4
+ data.tar.gz: 1a99ad83f75681fca485e740677e876d56fc519f
5
5
  SHA512:
6
- metadata.gz: 28857957400e927922005705c025814245cf72d3a8118ba0afedd4ab136c150701baba607aea3b3e245876dd82ffc68b6823f0be43b359d685d80e86c3ed7e71
7
- data.tar.gz: daa48a69f3a8c21c627add56e9ecd9a187386c45031bef2d8582b9e855fc6b0b0620f94ac678df4f2dbb467f9c0bd4f01c2282fda386559f4e6d740d1e8830bd
6
+ metadata.gz: e97a7d38b5089aa673929764ccac1fa0331921359143e241148d3e693288163c01051a5f13150e80a81996a592ffc167b662fa46d48b870fbe13c4488374295e
7
+ data.tar.gz: 9da41564c3b4534072dac80f4680fa207b95d6841e7921c5006d034103282d2c859c641b612ae995b7ccd33d0465aec668879dc73a1f065ab20a9834aad0e6ff
data/README.md CHANGED
@@ -1,9 +1,30 @@
1
1
  continuent-tools-core
2
2
  =====================
3
3
 
4
- The continuent-tools-core is a package of libraries that are found in Tungsten Replicator. When the gem is built, these files are automatically exported from [https://code.google.com/p/tungsten-replicator/](https://code.google.com/p/tungsten-replicator/).
4
+ The continuent-tools-core is a package of libraries and scripts to use with most Continuent Tungsten deployments. It is the basis for all other RubyGems distributed by Continuent.
5
5
 
6
- gem install continuent-tools-core
6
+ The code is focused around the 'TungstenScript' Ruby class that is found in Tungsten Replicator. When the gem is built, these files are automatically exported from [https://code.google.com/p/tungsten-replicator/](https://code.google.com/p/tungsten-replicator/).
7
+
8
+ Installation may be done using the `gem` utility. This will ensure that all prerequisites are installed.
9
+
10
+ $> gem install continuent-tools-core
11
+
12
+ Using the tungsten\_create\_load script
13
+ ===
14
+
15
+ The tungsten\_create\_load script may be used to apply load directly to a MySQL server or through a Tungsten Connector. By default, the script will read your configuration and use the Tungsten Connector if it is available. This may be disabled by adding '--use-connector=false'.
16
+
17
+ Automated Continuent Tungsten management scripts
18
+ ===
19
+
20
+ The automated Continuent Tungsten management scripts are designed to be used as a way to manage Continuent Tungsten host configurations. They use provided or discovered directory information to determine the configuration of the local host and install or update the necessary components.
21
+
22
+ These tools can be called using CRON, Puppet, Chef or other devops tools with little customization to the specific platform.
23
+
24
+ See [MANAGE\_CONFIGURATION.md](MANAGE\_CONFIGURATION.md) for more details.
25
+
26
+ Using the TungstenScript class
27
+ ===
7
28
 
8
29
  Script Structure
9
30
  ---
@@ -50,15 +50,28 @@ class TungstenParseTHLIndex
50
50
  end
51
51
 
52
52
  def find_thl_record
53
- each_thl_index_line() {
54
- |line|
55
- match = parse_thl_index_line(line)
56
- if match != nil
57
- if match[:start] <= opt(:seqno) && match[:end] >= opt(:seqno)
58
- TU.output(match[:file])
53
+ filename = nil
54
+
55
+ begin
56
+ each_thl_index_line() {
57
+ |line|
58
+ match = parse_thl_index_line(line)
59
+ if match != nil
60
+ if match[:start] <= opt(:seqno) && match[:end] >= opt(:seqno)
61
+ filename = match[:file]
62
+ raise IgnoreError.new()
63
+ end
59
64
  end
60
- end
61
- }
65
+ }
66
+ rescue IgnoreError
67
+ # Do Nothing
68
+ end
69
+
70
+ if filename != nil
71
+ TU.output(filename)
72
+ else
73
+ TU.error("Unable to find THL event ##{opt(:seqno)}")
74
+ end
62
75
  end
63
76
 
64
77
  def parse_thl_index_line(line)
@@ -76,14 +89,14 @@ class TungstenParseTHLIndex
76
89
  end
77
90
 
78
91
  def each_thl_index_line(&block)
79
- TU.cmd_stdout("cat /Users/jmace/Downloads/thl.txt") {
92
+ TU.cmd_stdout("#{TI.thl(opt(:service))} index") {
80
93
  |line|
81
94
  block.call(line.strip())
82
95
  }
83
96
  end
84
97
 
85
98
  def get_thl_index
86
- return TU.cmd_result("cat /Users/jmace/Downloads/thl.txt").split("\n")
99
+ return TU.cmd_result("#{TI.thl(opt(:service))} index").split("\n")
87
100
  end
88
101
 
89
102
  def configure
@@ -54,7 +54,16 @@ class ContinuentCreateLoad
54
54
  # generation thread.
55
55
  load_threads.peach{
56
56
  |load_thread|
57
- TU.cmd(get_mysql_command() + " -h#{load_thread[:host]}", true, method(:create_schema_load), nil, method(:forward_mysql_errors))
57
+ while (ContinuentCreateLoad.interrupted?() == false)
58
+ begin
59
+ TU.cmd(get_mysql_command() + " -h#{load_thread[:host]}", true, method(:create_schema_load), nil, method(:forward_mysql_errors))
60
+ rescue => e
61
+ TU.debug(e)
62
+ end
63
+ if ContinuentCreateLoad.interrupted?() == false
64
+ TU.notice("Reconnecting to the closed MySQL connection.")
65
+ end
66
+ end
58
67
  }
59
68
 
60
69
  puts("\n")
@@ -65,7 +74,12 @@ class ContinuentCreateLoad
65
74
  [
66
75
  "DROP SCHEMA IF EXISTS tungsten_create_load;",
67
76
  "CREATE SCHEMA tungsten_create_load;",
68
- "CREATE TABLE tungsten_create_load.values (id int NOT NULL auto_increment primary key, val int NOT NULL, origin varchar(32) NULL);"
77
+ "CREATE TABLE tungsten_create_load.#{opt(:table_name)} (
78
+ id int NOT NULL auto_increment primary key,
79
+ val int NOT NULL,
80
+ origin varchar(32) NULL,
81
+ filler varchar(1024) NULL
82
+ );"
69
83
  ].each{|sql|
70
84
  stdin.puts(sql)
71
85
  putc '.'
@@ -74,13 +88,15 @@ class ContinuentCreateLoad
74
88
  end
75
89
 
76
90
  def create_schema_load(stdin)
77
- sql = "INSERT INTO tungsten_create_load.values (val, origin) VALUES (5, @@hostname);"
91
+ value = ""; 1024.times{value << ((rand(2)==1?65:97) + rand(25)).chr}
92
+ sql = "INSERT INTO tungsten_create_load.#{opt(:table_name)} (val, origin, filler) VALUES (5, @@hostname, '#{value}');"*[opt(:chunk_size),1].max()
78
93
  while (ContinuentCreateLoad.interrupted?() == false)
79
94
  stdin.puts(sql)
80
95
  putc '.'
81
96
  $stdout.flush()
82
97
  sleep opt(:sleep).to_i()
83
98
  end
99
+ stdin.puts("exit")
84
100
  end
85
101
 
86
102
  def forward_mysql_errors(msg)
@@ -102,6 +118,13 @@ class ContinuentCreateLoad
102
118
  :default => 1,
103
119
  })
104
120
 
121
+ add_option(:chunk_size, {
122
+ :on => "--chunk-size String",
123
+ :parse => method(:parse_integer_option),
124
+ :help => "How many rows should be entered per iteration",
125
+ :default => 10,
126
+ })
127
+
105
128
  add_option(:sleep, {
106
129
  :on => "--sleep String",
107
130
  :parse => method(:parse_integer_option),
@@ -114,6 +137,12 @@ class ContinuentCreateLoad
114
137
  :parse => method(:parse_boolean_option),
115
138
  :help => "Enable/Disable use of the Tungsten Connector for adding load to the system.",
116
139
  })
140
+
141
+ add_option(:table_name, {
142
+ :on => "--table-name String",
143
+ :help => "The MySQL table name to fill with this script",
144
+ :default => "values"
145
+ })
117
146
  end
118
147
 
119
148
  def validate
@@ -123,7 +152,7 @@ class ContinuentCreateLoad
123
152
  return TU.is_valid?()
124
153
  end
125
154
 
126
- if opt(:use_connector) == nil
155
+ if opt(:use_connector) == nil || opt(:use_connector) == true
127
156
  if TI.is_connector?()
128
157
  opt(:use_connector, true)
129
158
 
@@ -139,14 +168,17 @@ class ContinuentCreateLoad
139
168
  defaults_file.puts("password=#{TI.setting(TI.setting_key(CONNECTORS, "connector_password"))}")
140
169
  defaults_file.flush()
141
170
 
142
- opt(:mysqlport, TI.setting(TI.setting_key(CONNECTORS, "connector_listen_port")))
143
171
  opt(:mysqlhost, TI.hostname())
172
+ opt(:mysqlport, TI.setting(TI.setting_key(CONNECTORS, "connector_listen_port")))
144
173
  elsif TI.is_replicator?()
145
174
  opt(:use_connector, false)
146
175
 
147
176
  if TI.setting(TI.setting_key(REPL_SERVICES, @options[:service], "repl_datasource_type")) != "mysql"
148
177
  TU.error("Unable to create load on this system because it is not configured for MySQL")
149
178
  end
179
+
180
+ opt(:mysqlhost, TI.setting(TI.setting_key(REPL_SERVICES, opt(:service), "repl_direct_datasource_host")))
181
+ opt(:mysqlport, TI.setting(TI.setting_key(REPL_SERVICES, opt(:service), "repl_direct_datasource_port")))
150
182
  else
151
183
  TU.error("Unable to create load on this system because it is not configured as a Tungsten Connector or Tungsten Replicator")
152
184
  end
@@ -157,10 +189,9 @@ class ContinuentCreateLoad
157
189
  |h|
158
190
  (h == TI.hostname())
159
191
  }
160
- hosts << TI.hostname()
161
192
  opt(:hosts, hosts)
162
193
  else
163
- opt(:hosts, [TI.hostname()])
194
+ opt(:hosts, [opt(:mysqlhost)])
164
195
  end
165
196
  end
166
197
 
@@ -0,0 +1,379 @@
1
+ #!/usr/bin/env ruby
2
+ # Copyright (C) 2014 Continuent, Inc.
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, WITHOUT
12
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
+ # License for the specific language governing permissions and limitations
14
+ # under the License.
15
+ #
16
+ # Initial developer(s): Jeff Mace
17
+ # Contributor(s):
18
+
19
+ # TODO : Add commands to output host definitions in Chef form
20
+
21
+ begin
22
+ require 'rubygems'
23
+ gem 'continuent-tools-core'
24
+ rescue LoadError
25
+ end
26
+
27
+ require 'continuent-tools-core'
28
+
29
+ class TungstenDirectoryProvider
30
+ def initialize(key)
31
+ @key = key
32
+ end
33
+
34
+ def self.inherited(subclass)
35
+ @subclasses ||= []
36
+ @subclasses << subclass
37
+ end
38
+
39
+ def self.subclasses
40
+ @subclasses
41
+ end
42
+
43
+ def self.get_provider(key)
44
+ @subclasses.each{
45
+ |klass|
46
+
47
+ regex = Regexp.new(klass.get_regex())
48
+ if key =~ regex
49
+ return klass.new(key)
50
+ end
51
+ }
52
+
53
+ nil
54
+ end
55
+ end
56
+
57
+ Dir.glob(File.dirname(__FILE__) + '/../providers/*.rb').each do |file|
58
+ begin
59
+ require file
60
+ rescue IgnoreError
61
+ end
62
+ end
63
+
64
+ class TungstenDirectory
65
+ include TungstenScript
66
+ private
67
+
68
+ def main
69
+ directory_entries = collect_directory_entries()
70
+
71
+ unless TU.is_valid?
72
+ return
73
+ end
74
+
75
+ case command()
76
+ when "list"
77
+ TU.output(JSON.pretty_generate(directory_entries))
78
+ when "hosts"
79
+ if directory_entries.has_key?(opt(:hostname))
80
+ location = directory_entries[opt(:hostname)]["location"]
81
+ else
82
+ location = nil
83
+ end
84
+ hostsmap = generate_hosts_map(directory_entries, location)
85
+ hostsmap.keys().sort().each{
86
+ |h|
87
+ TU.output("#{hostsmap[h]}\t#{h}")
88
+ }
89
+ when "hosts_puppet_manifest"
90
+ if directory_entries.has_key?(opt(:hostname))
91
+ location = directory_entries[opt(:hostname)]["location"]
92
+ else
93
+ location = nil
94
+ end
95
+ hostsmap = generate_hosts_map(directory_entries, location)
96
+ hostsmap.keys().sort().each{
97
+ |h|
98
+ TU.output("host { '#{h}' : ip => '#{hostsmap[h]}', comment => 'Created by #{script_name()}'}")
99
+ }
100
+ end
101
+ end
102
+
103
+ def collect_directory_entries
104
+ found_files = false
105
+ directory_entries = {}
106
+
107
+ Dir.glob(opt(:config)).each{
108
+ |f|
109
+ found_files = true
110
+
111
+ # Parse the configuration file and remove any sections
112
+ # that may hold command line options
113
+ contents = TU.parse_ini_file(f)
114
+ contents.delete("tungsten_directory")
115
+ contents.delete("directory")
116
+
117
+ # Iterate through each section and merge directory entries
118
+ contents.each{
119
+ |k,v|
120
+
121
+ matches = k.match("autodetect.(.*)")
122
+ if matches != nil and matches.size() > 0
123
+ # Search for a matching autodetect provider
124
+ provider = TungstenDirectoryProvider.get_provider(matches[1])
125
+ unless provider == nil
126
+ directory_entries.merge!(provider.get_entries(v))
127
+ else
128
+ TU.error("Unable to autodetect #{matches[1]} entries")
129
+ end
130
+ else
131
+ entry = parse_directory_entry(v)
132
+ hostname = k
133
+ if entry.has_key?("hostname")
134
+ hostname = entry["hostname"]
135
+ else
136
+ entry["hostname"] = hostname
137
+ end
138
+
139
+ # Build a manual entry and add it to the list
140
+ directory_entries[k] = entry
141
+ end
142
+ }
143
+ }
144
+
145
+ if found_files == false
146
+ raise "Unable to find any files at #{opt(:config)}"
147
+ end
148
+
149
+ # Modify each directory entry and prepare it for output
150
+ directory_entries.each{
151
+ |id,entry|
152
+ unless entry["tags"].is_a?(Hash)
153
+ next
154
+ end
155
+
156
+ unless entry["hostname"] != ""
157
+ TU.error("Entry '#{entry["id"]}' does not include a hostname")
158
+ end
159
+
160
+ unless entry["location"] != ""
161
+ TU.error("Entry '#{entry["id"]}' does not include a location")
162
+ end
163
+
164
+ entry["id"] = id
165
+ entry["tags"].each{
166
+ |k,v|
167
+ v = v.split(",")
168
+ if v.size() > 1
169
+ entry["tags"][k] = v
170
+ end
171
+ }
172
+ }
173
+
174
+ if has_filters?()
175
+ directory_entries = apply_filters(directory_entries, opt(:filters))
176
+ end
177
+
178
+ return directory_entries
179
+ end
180
+
181
+ def parse_directory_entry(e)
182
+ entry = e.dup()
183
+ entry["tags"] = {}
184
+ entry["provider"] = "ini"
185
+
186
+ e.keys().each{
187
+ |k|
188
+ parts = k.split(".")
189
+ if parts.size() == 1
190
+ next
191
+ end
192
+
193
+ type = parts.shift()
194
+ remainder = parts.join(".")
195
+
196
+ case type
197
+ when "tags"
198
+ entry["tags"][remainder] = entry[k]
199
+ entry.delete(k)
200
+ end
201
+ }
202
+
203
+ return entry
204
+ end
205
+
206
+ # For each entry include the private address if the host location
207
+ # matches the given location. Use the public address if there is no private
208
+ # address or the locations do not match.
209
+ def generate_hosts_map(directory_entries, location = nil)
210
+ hosts_map = {}
211
+
212
+ directory_entries.each{
213
+ |hostname,entry|
214
+ if location != nil && entry["location"] == location
215
+ if entry.has_key?("private-address")
216
+ hosts_map[hostname] = entry["private-address"]
217
+ elsif entry.has_key?("public-address")
218
+ hosts_map[hostname] = entry["public-address"]
219
+ else
220
+ TU.error("Unable to find a private or public address for #{hostname}")
221
+ end
222
+ else
223
+ if entry.has_key?("public-address")
224
+ hosts_map[hostname] = entry["public-address"]
225
+ else
226
+ TU.error("Unable to find a public address for #{hostname}")
227
+ end
228
+ end
229
+ }
230
+
231
+ return hosts_map
232
+ end
233
+
234
+ def has_filters?
235
+ filters = opt(:filters)
236
+
237
+ if filters == nil
238
+ return false
239
+ end
240
+
241
+ if filters.is_a?(Array) && filters.count() > 0
242
+ return true
243
+ else
244
+ return false
245
+ end
246
+ end
247
+
248
+ # Remove any entry that does not match the provided filters
249
+ def apply_filters(entries, filters)
250
+ filters.each{
251
+ |filter|
252
+ parts = filter.split("=")
253
+
254
+ # Determine if the filter is joined with a '=' or '!='
255
+ last_char=parts[0][-1,1]
256
+ if last_char == "!"
257
+ test_equals = false
258
+ key = parts[0][0, parts[0].length()-1]
259
+ match = parts[1]
260
+ else
261
+ test_equals = true
262
+ key = parts[0]
263
+ match = parts[1]
264
+ end
265
+
266
+ entries.each{
267
+ |e, entry|
268
+ # Find the key value in entry, or nil if it doesn't exist
269
+ value = find_entry_setting(entry, key)
270
+
271
+ # Calculate if the returned value matches the filter value
272
+ if value.is_a?(Array)
273
+ is_equal = false
274
+ value.each{
275
+ |v|
276
+ if is_value_match?(v, match)
277
+ is_equal = true
278
+ end
279
+ }
280
+ else
281
+ is_equal = is_value_match?(value, match)
282
+ end
283
+
284
+ # Delete the entry if the result of the match check isn't what
285
+ # the filter is looking for
286
+ if is_equal != test_equals
287
+ entries.delete(e)
288
+ end
289
+ }
290
+ }
291
+
292
+ return entries
293
+ end
294
+
295
+ def is_value_match?(value, match)
296
+ match = match.sub("%", ".*")
297
+ matches = value.to_s().match("^#{match}$")
298
+ if matches == nil
299
+ return false
300
+ else
301
+ return true
302
+ end
303
+ end
304
+
305
+ def find_entry_setting(entry, key)
306
+ attrs = key.split(".")
307
+ attr_count = attrs.size
308
+ current_val = entry
309
+ for i in 0..(attr_count-1)
310
+ attr_name = attrs[i]
311
+ return current_val[attr_name] if i == (attr_count-1)
312
+ return nil if current_val[attr_name].nil?
313
+ current_val = current_val[attr_name]
314
+ end
315
+
316
+ return nil
317
+ end
318
+
319
+ def configure
320
+ super()
321
+
322
+ require_installed_directory?(false)
323
+
324
+ add_option(:config, {
325
+ :on => "--config String",
326
+ :help => "Path to INI file that holds directory information",
327
+ :default => "/etc/tungsten/directory.ini"
328
+ })
329
+
330
+ add_option(:hostname, {
331
+ :on => "--hostname String",
332
+ :help => "Use this hostname for calculating hosts entries",
333
+ :default => TU.hostname()
334
+ })
335
+
336
+ add_option(:filters, {
337
+ :on => "--filter String"
338
+ }) {|val|
339
+ unless @options.has_key?(:filters)
340
+ @options[:filters] = []
341
+ end
342
+
343
+ parts = val.split("=")
344
+ if parts.count() != 2
345
+ TU.error("Unable to parse --filter=#{val}. The filter should be in the form of 'key=value' or 'key!=value'.")
346
+ end
347
+
348
+ @options[:filters] << val
349
+ nil
350
+ }
351
+
352
+ add_command(:list, {
353
+ :help => "Output the directory information as JSON",
354
+ :default => true
355
+ })
356
+
357
+ add_command(:hosts, {
358
+ :help => "Output /etc/hosts entries for the directory hosts"
359
+ })
360
+
361
+ add_command(:hosts_puppet_manifest, {
362
+ :help => "Output a Puppet manifest for the directory hosts"
363
+ })
364
+ end
365
+
366
+ def validate
367
+ super()
368
+
369
+ unless TU.is_valid?()
370
+ return TU.is_valid?()
371
+ end
372
+ end
373
+
374
+ def script_name
375
+ "tungsten_directory"
376
+ end
377
+
378
+ self.new().run()
379
+ end