kytoon 1.2.5 → 1.3.0

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