continuent-tools-core 0.9.0 → 0.10.6

Sign up to get free protection for your applications and to get access to all the features.
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