kytoon 1.2.5 → 1.3.0

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.
data/CHANGELOG CHANGED
@@ -1,3 +1,14 @@
1
+ * Mon Dec 13 2012 Dan Prince <dprince@redhat.com> - 1.3.0
2
+ -OpenStack: Switch to Fog.
3
+ -OpenStack: Add support for automatic floating IP configuration via
4
+ 'assign_floating_ip' in server group json files.
5
+ -OpenStack: Add support for security group names.
6
+ -OpenStack: Add endpoint configs for region, service_type, service_name.
7
+ -OpenStack: Configure ssh access within the server group.
8
+ -Libvirt: grep correct dnsmasq leases file (use network name)
9
+ -Libvirt: fix libvirt_use_sudo option (string conversion)
10
+ -Libvirt: Configure ssh access within the server group.
11
+
1
12
  * Mon Nov 26 2012 Dan Prince <dprince@redhat.com> - 1.2.5
2
13
  - Libvirt: ping test instances to ensure they have IPs before
3
14
  trying to configure hosts, etc.
data/README.md CHANGED
@@ -25,10 +25,13 @@ Inspired by and based on the Chef VPC Toolkit.
25
25
 
26
26
  ## Installation
27
27
 
28
- Gem install kytoon.
28
+ Quick install on Fedora:
29
29
 
30
+ yum install -y rubygems ruby-devel gcc gcc-c++ libxslt-devel
30
31
  gem install kytoon
31
32
 
33
+ *NOTE: Kytoon has been tested with Fog 1.8.0+ only (1.9.0 will be required to work with Rackspace's OpenStack Cloud)
34
+
32
35
  Create a .kytoon.conf file in your $HOME directory.
33
36
 
34
37
  # The default group type.
@@ -41,9 +44,13 @@ Create a .kytoon.conf file in your $HOME directory.
41
44
  openstack_password: <password>
42
45
  openstack_network_name: public # Optional: defaults to public
43
46
  openstack_keypair_name: < keyname > # Optional: file injection via personalities is the default
47
+ openstack_security_groups: ['', ''] # Optional: Array of security group names
44
48
  openstack_ip_type: 4 # IP type (4 or 6): defaults to 4
45
49
  openstack_build_timeout: 480 # Server build timeout. Defaults to: 480
46
50
  openstack_ping_timeout: 60 # Server build timeout. Defaults to: 60
51
+ openstack_service_name: < name > # Optional: default is None... some clouds have multiple 'compute' services so this may be required
52
+ openstack_service_type: compute # Optional: default is 'compute'
53
+ openstack_region: < region name > # Optional
47
54
 
48
55
  # Libvirt settings
49
56
  # Whether commands to create local group should use sudo
data/Rakefile CHANGED
@@ -28,7 +28,7 @@ begin
28
28
  gem.add_dependency 'rake'
29
29
  gem.add_dependency 'builder'
30
30
  gem.add_dependency 'json'
31
- gem.add_dependency 'openstack-compute'
31
+ gem.add_dependency 'fog'
32
32
  gem.add_dependency 'thor'
33
33
  gem.add_dependency 'uuidtools'
34
34
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.2.5
1
+ 1.3.0
@@ -4,9 +4,16 @@
4
4
  {
5
5
  "hostname": "nova1",
6
6
  "memory": "1",
7
+ "original_xml": "/home/dprince/f17.xml",
8
+ "create_cow": "true"
9
+ },
10
+ {
11
+ "hostname": "login",
12
+ "memory": "1",
7
13
  "gateway": "true",
8
14
  "original_xml": "/home/dprince/f17.xml",
9
15
  "create_cow": "true"
10
16
  }
17
+
11
18
  ]
12
19
  }
@@ -61,7 +61,7 @@ class ServerGroup
61
61
  json_hash=JSON.parse(json)
62
62
 
63
63
  configs = Util.load_configs
64
- use_sudo = ENV['LIBVIRT_USE_SUDO'] || configs['libvirt_use_sudo']
64
+ use_sudo = ENV['LIBVIRT_USE_SUDO'] || configs['libvirt_use_sudo'].to_s
65
65
 
66
66
  sg=ServerGroup.new(
67
67
  :id => json_hash["id"],
@@ -138,11 +138,25 @@ class ServerGroup
138
138
  end
139
139
  out_file=File.join(@@data_dir, "#{@id}.json")
140
140
  File.delete(out_file) if File.exists?(out_file)
141
+
142
+ #cleanup ssh keys
143
+ private_ssh_key = File.join(@@data_dir, "#{@id}_id_rsa")
144
+ public_ssh_key = File.join(@@data_dir, "#{@id}_id_rsa.pub")
145
+ [private_ssh_key, public_ssh_key].each do |file|
146
+ File.delete(file) if File.exists?(file)
147
+ end
148
+
141
149
  end
142
150
 
143
151
  def self.create(sg)
144
152
 
145
153
  ssh_public_key = Kytoon::Util.load_public_key
154
+
155
+ base_key_name=File.join(@@data_dir, "#{sg.id}_id_rsa")
156
+ Kytoon::Util.generate_ssh_keypair(base_key_name)
157
+ private_ssh_key=IO.read(base_key_name)
158
+ public_ssh_key=IO.read(base_key_name + ".pub")
159
+
146
160
  sudo = sg.use_sudo =~ /(true|t|yes|y|1)$/i ? "sudo" : ""
147
161
  hosts_file_data = "127.0.0.1\tlocalhost localhost.localdomain\n"
148
162
  sg.servers.each do |server|
@@ -158,6 +172,31 @@ class ServerGroup
158
172
  end
159
173
 
160
174
  puts "Copying hosts files..."
175
+
176
+ gateway_ssh_config = %{
177
+ [ -d .ssh ] || mkdir .ssh
178
+ cat > .ssh/id_rsa <<-EOF_CAT
179
+ #{private_ssh_key}
180
+ EOF_CAT
181
+ chmod 600 .ssh/id_rsa
182
+ cat > .ssh/id_rsa.pub <<-EOF_CAT
183
+ #{public_ssh_key}
184
+ EOF_CAT
185
+ chmod 600 .ssh/id_rsa.pub
186
+ cat > .ssh/config <<-EOF_CAT
187
+ StrictHostKeyChecking no
188
+ EOF_CAT
189
+ chmod 600 .ssh/config
190
+ }
191
+
192
+ node_ssh_config= %{
193
+ [ -d .ssh ] || mkdir .ssh
194
+ cat > .ssh/authorized_keys <<-EOF_CAT
195
+ #{public_ssh_key}
196
+ EOF_CAT
197
+ chmod 600 .ssh/authorized_keys
198
+ }
199
+
161
200
  #now that we have IP info copy hosts files into the servers
162
201
  sg.servers.each do |server|
163
202
  ping_test(server['ip_address'])
@@ -169,6 +208,7 @@ hostname "#{server['hostname']}"
169
208
  if [ -f /etc/sysconfig/network ]; then
170
209
  sed -e "s|^HOSTNAME.*|HOSTNAME=#{server['hostname']}|" -i /etc/sysconfig/network
171
210
  fi
211
+ #{server['gateway'] == 'true' ? gateway_ssh_config : node_ssh_config}
172
212
  }, server['ip_address']) do |ok, out|
173
213
  if not ok
174
214
  puts out
@@ -304,7 +344,8 @@ if [ -n "$LV_ROOT" ]; then
304
344
  mount $LV_ROOT / : \
305
345
  sh "/bin/mkdir -p /root/.ssh" : \
306
346
  write-append /root/.ssh/authorized_keys "#{ssh_public_key}" : \
307
- sh "/bin/chmod -R 700 /root/.ssh"
347
+ sh "/bin/chmod 700 /root/.ssh" : \
348
+ sh "/bin/chmod 600 /root/.ssh/authorized_keys"
308
349
  fi
309
350
 
310
351
  #{sudo} virsh setmaxmem #{domain_name} #{instance_memory}
@@ -320,17 +361,22 @@ fi
320
361
 
321
362
  # lookup server IP here...
322
363
  mac_addr = nil
364
+ network_name = nil
323
365
  dom_xml = %x{#{sudo} virsh --connect=qemu:///system dumpxml #{domain_name}}
324
366
  dom = REXML::Document.new(dom_xml)
325
367
  REXML::XPath.each(dom, "//interface/mac") do |interface_xml|
326
368
  mac_addr = interface_xml.attributes['address']
327
369
  end
328
370
  raise KytoonException, "Failed to lookup mac address for #{inst_name}" if mac_addr.nil?
371
+ REXML::XPath.each(dom, "//interface/source") do |interface_xml|
372
+ network_name = interface_xml.attributes['network']
373
+ end
374
+ raise KytoonException, "Failed to lookup network name for #{inst_name}" if network_name.nil?
329
375
 
330
- instance_ip = %x{grep -i #{mac_addr} /var/lib/libvirt/dnsmasq/default.leases | cut -d " " -f 3}.chomp
376
+ instance_ip = %x{grep -i #{mac_addr} /var/lib/libvirt/dnsmasq/#{network_name}.leases | cut -d " " -f 3}.chomp
331
377
  count = 0
332
378
  until not instance_ip.empty? do
333
- instance_ip = %x{grep -i #{mac_addr} /var/lib/libvirt/dnsmasq/default.leases | cut -d " " -f 3}.chomp
379
+ instance_ip = %x{grep -i #{mac_addr} /var/lib/libvirt/dnsmasq/#{network_name}.leases | cut -d " " -f 3}.chomp
334
380
  sleep 1
335
381
  count += 1
336
382
  if count >= 60 then
@@ -1,6 +1,6 @@
1
1
  require 'json'
2
2
  require 'kytoon/util'
3
- require 'openstack/compute'
3
+ require 'fog'
4
4
 
5
5
  module Kytoon
6
6
 
@@ -26,10 +26,12 @@ class ServerGroup
26
26
 
27
27
  attr_accessor :id
28
28
  attr_accessor :name
29
+ attr_accessor :use_security_groups
29
30
 
30
31
  def initialize(options={})
31
32
  @id = options[:id] || Time.now.to_f
32
33
  @name = options[:name]
34
+ @use_security_groups = options[:use_security_groups]
33
35
  @servers=[]
34
36
  end
35
37
 
@@ -52,7 +54,8 @@ class ServerGroup
52
54
 
53
55
  sg=ServerGroup.new(
54
56
  :id => json_hash["id"],
55
- :name => json_hash["name"]
57
+ :name => json_hash["name"],
58
+ :use_security_groups => json_hash["use_security_groups"]
56
59
  )
57
60
  json_hash["servers"].each do |server_hash|
58
61
 
@@ -62,7 +65,11 @@ class ServerGroup
62
65
  'image_ref' => server_hash['image_ref'],
63
66
  'flavor_ref' => server_hash['flavor_ref'],
64
67
  'keypair_name' => server_hash['keypair_name'],
68
+ 'floating_ip' => server_hash['floating_ip'],
65
69
  'gateway' => server_hash['gateway'] || "false",
70
+ 'assign_floating_ip' => server_hash['assign_floating_ip'] || "false",
71
+ 'floating_ip' => server_hash['floating_ip'] || nil,
72
+ 'floating_ip_id' => server_hash['floating_ip_id'] || nil,
66
73
  'ip_address' => server_hash['ip_address']
67
74
  }
68
75
  end
@@ -84,7 +91,7 @@ class ServerGroup
84
91
 
85
92
  def server_names
86
93
 
87
- names=[]
94
+ names=[]
88
95
 
89
96
  servers.each do |server|
90
97
  if block_given? then
@@ -103,10 +110,11 @@ class ServerGroup
103
110
  sg_hash = {
104
111
  'id' => @id,
105
112
  'name' => @name,
113
+ 'use_security_groups' => @use_security_groups,
106
114
  'servers' => []
107
115
  }
108
116
  @servers.each do |server|
109
- sg_hash['servers'] << {'id' => server['id'], 'hostname' => server['hostname'], 'image_ref' => server['image_ref'], 'gateway' => server['gateway'], 'flavor_ref' => server['flavor_ref'], 'ip_address' => server['ip_address']}
117
+ sg_hash['servers'] << {'id' => server['id'], 'hostname' => server['hostname'], 'image_ref' => server['image_ref'], 'gateway' => server['gateway'], 'flavor_ref' => server['flavor_ref'], 'ip_address' => server['ip_address'], 'floating_ip' => server['floating_ip'], 'floating_ip_id' => server['floating_ip_id'], 'assign_floating_ip' => server['assign_floating_ip']}
110
118
  end
111
119
 
112
120
  FileUtils.mkdir_p(@@data_dir)
@@ -118,23 +126,44 @@ class ServerGroup
118
126
 
119
127
  def delete
120
128
  servers.each do |server|
129
+ if server['assign_floating_ip'] == 'true' then
130
+ ServerGroup.release_floating_ip(server)
131
+ end
121
132
  ServerGroup.destroy_instance(server['id'])
122
133
  end
134
+
135
+ #cleanup ssh keys
136
+ private_ssh_key = File.join(@@data_dir, "#{@id}_id_rsa")
137
+ public_ssh_key = File.join(@@data_dir, "#{@id}_id_rsa.pub")
138
+ [private_ssh_key, public_ssh_key].each do |file|
139
+ File.delete(file) if File.exists?(file)
140
+ end
141
+
123
142
  out_file=File.join(@@data_dir, "#{@id}.json")
124
143
  File.delete(out_file) if File.exists?(out_file)
125
144
  end
126
145
 
127
-
128
146
  def self.create(sg)
129
147
 
130
148
  hosts_file_data = "127.0.0.1\tlocalhost localhost.localdomain\n"
131
149
 
132
150
  build_timeout = (Util.load_configs['openstack_build_timeout'] || 60).to_i
133
151
 
152
+ base_key_name=File.join(@@data_dir, "#{sg.id}_id_rsa")
153
+ Kytoon::Util.generate_ssh_keypair(base_key_name)
154
+ private_ssh_key=IO.read(base_key_name)
155
+ public_ssh_key=IO.read(base_key_name + ".pub")
156
+
134
157
  sg.servers.each do |server|
135
158
  server_id = create_instance(sg.id, server['hostname'], server['image_ref'], server['flavor_ref'], server['keypair_name']).id
136
-
137
159
  server['id'] = server_id
160
+
161
+ if server['assign_floating_ip'] == 'true' then
162
+ floating_data = assign_floating_ip(server_id)
163
+ server['floating_ip_id'] = floating_data[0]
164
+ server['floating_ip'] = floating_data[1]
165
+ end
166
+
138
167
  sg.cache_to_disk
139
168
  end
140
169
 
@@ -143,7 +172,11 @@ class ServerGroup
143
172
  ips = get_server_ips
144
173
  sg.servers.each do |server|
145
174
  server_ip = ips[server['id']]
146
- server['ip_address'] = server_ip
175
+ if server['assign_floating_ip'] == 'true' then
176
+ server['ip_address'] = server['floating_ip']
177
+ else
178
+ server['ip_address'] = server_ip
179
+ end
147
180
  sg.cache_to_disk
148
181
  hosts_file_data += "#{server_ip}\t#{server['hostname']}\n"
149
182
  end
@@ -152,9 +185,33 @@ class ServerGroup
152
185
  raise KytoonException, "Timeout building server group."
153
186
  end
154
187
 
155
-
156
188
  puts "Copying hosts files..."
157
- #now that we have IP info copy hosts files into the servers
189
+
190
+ gateway_ssh_config = %{
191
+ [ -d .ssh ] || mkdir .ssh
192
+ cat > .ssh/id_rsa <<-EOF_CAT
193
+ #{private_ssh_key}
194
+ EOF_CAT
195
+ chmod 600 .ssh/id_rsa
196
+ cat > .ssh/id_rsa.pub <<-EOF_CAT
197
+ #{public_ssh_key}
198
+ EOF_CAT
199
+ chmod 600 .ssh/id_rsa.pub
200
+ cat > .ssh/config <<-EOF_CAT
201
+ StrictHostKeyChecking no
202
+ EOF_CAT
203
+ chmod 600 .ssh/config
204
+ }
205
+
206
+ node_ssh_config= %{
207
+ [ -d .ssh ] || mkdir .ssh
208
+ cat > .ssh/authorized_keys <<-EOF_CAT
209
+ #{public_ssh_key}
210
+ EOF_CAT
211
+ chmod 600 .ssh/authorized_keys
212
+ }
213
+
214
+ # now that we have IP info copy hosts files into the servers
158
215
  sg.servers.each do |server|
159
216
  ping_test(server['ip_address'])
160
217
  Kytoon::Util.remote_exec(%{
@@ -165,6 +222,7 @@ hostname "#{server['hostname']}"
165
222
  if [ -f /etc/sysconfig/network ]; then
166
223
  sed -e "s|^HOSTNAME.*|HOSTNAME=#{server['hostname']}|" -i /etc/sysconfig/network
167
224
  fi
225
+ #{server['gateway'] == 'true' ? gateway_ssh_config : node_ssh_config}
168
226
  }, server['ip_address']) do |ok, out|
169
227
  if not ok
170
228
  puts out
@@ -215,11 +273,15 @@ fi
215
273
  def self.init_connection
216
274
  configs = Util.load_configs
217
275
  if @@connection.nil? then
218
- @@connection = OpenStack::Compute::Connection.new(
219
- :username => configs['openstack_username'].to_s,
220
- :api_key => configs['openstack_password'].to_s,
221
- :auth_url => configs['openstack_url'],
222
- :retry_auth => false)
276
+ @connection = Fog::Compute.new(
277
+ :provider => :openstack,
278
+ :openstack_auth_url => configs['openstack_url'],
279
+ :openstack_username => configs['openstack_username'],
280
+ :openstack_api_key => configs['openstack_password'],
281
+ :openstack_service_name => configs['openstack_service_name'],
282
+ :openstack_service_type => configs['openstack_service_type'],
283
+ :openstack_region => configs['openstack_region']
284
+ )
223
285
  else
224
286
  @@connection
225
287
  end
@@ -227,24 +289,76 @@ fi
227
289
 
228
290
  def self.create_instance(group_id, hostname, image_ref, flavor_ref, keypair_name)
229
291
 
230
- ssh_public_key = Util.public_key_path
231
292
  configs = Util.load_configs
232
-
233
293
  conn = self.init_connection
234
294
 
235
295
  options = {
236
296
  :name => "#{group_id}_#{hostname}",
237
- :imageRef => image_ref,
238
- :flavorRef => flavor_ref,
239
- :personality => {ssh_public_key => "/root/.ssh/authorized_keys"},
240
- :is_debug => true}
297
+ :image_ref => image_ref,
298
+ :flavor_ref => flavor_ref}
241
299
 
242
300
  keypair_name = configs['openstack_keypair_name'] if keypair_name.nil?
301
+ security_groups = configs['openstack_security_groups']
302
+ if not security_groups.nil? and not security_groups.empty? then
303
+ options.store(:security_groups, security_groups)
304
+ end
243
305
  if not keypair_name.nil? and not keypair_name.empty? then
244
306
  options.store(:key_name, keypair_name)
307
+ else
308
+ options.store(:personality, [
309
+ {'path' => "/root/.ssh/authorized_keys",
310
+ 'contents' => IO.read(Util.public_key_path)}])
311
+ end
312
+
313
+ server = conn.servers.create(options)
314
+ server
315
+
316
+ end
317
+
318
+ def self.assign_floating_ip(server_id)
319
+
320
+ conn = self.init_connection
321
+
322
+ data = conn.allocate_address.body
323
+ address_id = data['floating_ip']['id']
324
+ address_ip = data['floating_ip']['ip']
325
+
326
+ configs = Util.load_configs
327
+ network_name = configs['openstack_network_name'] || 'public'
328
+
329
+ # wait for instance to obtain fixed ip
330
+ 1.upto(60) do
331
+ server = conn.servers.get(server_id)
332
+ if server.addresses and server.addresses[network_name] and server.addresses[network_name].detect {|a| a['version'] == self.default_ip_type} then
333
+ break
334
+ end
335
+ end
336
+
337
+ conn.associate_address(server_id, address_ip).body
338
+ [address_id, address_ip]
339
+
340
+ end
341
+
342
+ def self.release_floating_ip(server)
343
+
344
+ conn = self.init_connection
345
+
346
+ address_ip = server['floating_ip']
347
+ address_id = server['floating_ip_id']
348
+
349
+ conn.disassociate_address(server['id'], address_ip)
350
+
351
+ # wait for address to disassociate (instance_id should be nil)
352
+ 1.upto(30) do
353
+ floating_ips = conn.list_all_addresses.body['floating_ips']
354
+ break if floating_ips.detect {|f| f['id'] == address_id and f['instance_id' == nil]}
245
355
  end
246
356
 
247
- conn.create_server(options)
357
+ begin
358
+ conn.release_address(address_id)
359
+ rescue Fog::Compute::OpenStack::NotFound
360
+ puts "Unable to release IP address #{address_ip}: Not Found."
361
+ end
248
362
 
249
363
  end
250
364
 
@@ -265,12 +379,14 @@ fi
265
379
  until all_active do
266
380
  all_active = true
267
381
  conn.servers.each do |server|
268
- server = conn.server(server[:id])
269
- if server.status == 'ACTIVE' and ips[server.id].nil? then
270
- addresses = server.addresses[network_name.to_sym].select {|a| a.version == self.default_ip_type}
271
- ips[server.id] = addresses[0].address
272
- else
273
- all_active = false
382
+ if ips[server.id].nil? then
383
+ server = conn.servers.get(server.id)
384
+ if server.state == 'ACTIVE' then
385
+ addresses = server.addresses[network_name].select {|a| a['version'] == self.default_ip_type}
386
+ ips[server.id] = addresses[0]['addr']
387
+ else
388
+ all_active = false
389
+ end
274
390
  end
275
391
  end
276
392
  end
@@ -302,7 +418,12 @@ fi
302
418
  def self.destroy_instance(uuid)
303
419
  begin
304
420
  conn = self.init_connection
305
- conn.server(uuid).delete!
421
+ server = conn.servers.get(uuid)
422
+ if server then
423
+ server.destroy
424
+ else
425
+ puts "Server #{uuid} no longer exists."
426
+ end
306
427
  rescue Exception => e
307
428
  puts "Error deleting server: #{e.message}"
308
429
  end
@@ -116,6 +116,12 @@ REMOTE_EXEC_EOF
116
116
 
117
117
  end
118
118
 
119
+ # Generate an ssh keypair using the specified base path
120
+ def self.generate_ssh_keypair(ssh_key_basepath)
121
+ FileUtils.mkdir_p(File.dirname(ssh_key_basepath))
122
+ %x{ssh-keygen -N '' -f #{ssh_key_basepath} -t rsa -q}
123
+ end
124
+
119
125
  end
120
126
 
121
127
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kytoon
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.5
4
+ version: 1.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-11-26 00:00:00.000000000 Z
12
+ date: 2012-12-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rdoc
@@ -172,7 +172,7 @@ dependencies:
172
172
  - !ruby/object:Gem::Version
173
173
  version: '0'
174
174
  - !ruby/object:Gem::Dependency
175
- name: openstack-compute
175
+ name: fog
176
176
  requirement: !ruby/object:Gem::Requirement
177
177
  none: false
178
178
  requirements:
@@ -289,7 +289,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
289
289
  version: '0'
290
290
  segments:
291
291
  - 0
292
- hash: 1238263257557128483
292
+ hash: 3174001929267576694
293
293
  required_rubygems_version: !ruby/object:Gem::Requirement
294
294
  none: false
295
295
  requirements: