ironfan 5.0.11 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. data/.gitignore +4 -0
  2. data/.gitmodules +3 -0
  3. data/Gemfile +8 -26
  4. data/Gemfile.lock +38 -41
  5. data/NOTES-REALM.md +172 -0
  6. data/Rakefile +19 -77
  7. data/config/ubuntu12.04-ironfan.erb +7 -0
  8. data/ironfan.gemspec +28 -225
  9. data/lib/chef/cluster_knife.rb +26 -0
  10. data/lib/chef/knife/bootstrap/ubuntu12.04-ironfan.erb +7 -0
  11. data/lib/chef/knife/cluster_bootstrap.rb +1 -3
  12. data/lib/chef/knife/cluster_diff.rb +2 -8
  13. data/lib/chef/knife/cluster_kick.rb +1 -3
  14. data/lib/chef/knife/cluster_kill.rb +1 -2
  15. data/lib/chef/knife/cluster_launch.rb +17 -34
  16. data/lib/chef/knife/cluster_list.rb +6 -5
  17. data/lib/chef/knife/cluster_proxy.rb +1 -3
  18. data/lib/chef/knife/cluster_pry.rb +1 -2
  19. data/lib/chef/knife/cluster_show.rb +6 -7
  20. data/lib/chef/knife/cluster_ssh.rb +10 -8
  21. data/lib/chef/knife/cluster_start.rb +1 -2
  22. data/lib/chef/knife/cluster_stop.rb +1 -2
  23. data/lib/chef/knife/cluster_sync.rb +2 -3
  24. data/lib/chef/knife/ironfan_knife_common.rb +58 -18
  25. data/lib/chef/knife/ironfan_script.rb +0 -3
  26. data/lib/ironfan/broker/computer.rb +14 -11
  27. data/lib/ironfan/broker.rb +17 -12
  28. data/lib/ironfan/cookbook_requirements.rb +155 -0
  29. data/lib/ironfan/dsl/cloud.rb +2 -0
  30. data/lib/ironfan/dsl/cluster.rb +25 -15
  31. data/lib/ironfan/dsl/component.rb +12 -15
  32. data/lib/ironfan/dsl/compute.rb +10 -8
  33. data/lib/ironfan/dsl/ec2.rb +2 -26
  34. data/lib/ironfan/dsl/facet.rb +16 -14
  35. data/lib/ironfan/dsl/openstack.rb +147 -0
  36. data/lib/ironfan/dsl/realm.rb +23 -16
  37. data/lib/ironfan/dsl/security_group.rb +29 -0
  38. data/lib/ironfan/dsl/server.rb +14 -5
  39. data/lib/ironfan/dsl/static.rb +63 -0
  40. data/lib/ironfan/dsl/vsphere.rb +1 -0
  41. data/lib/ironfan/dsl.rb +1 -134
  42. data/lib/ironfan/headers.rb +19 -0
  43. data/lib/ironfan/provider/chef/node.rb +3 -2
  44. data/lib/ironfan/provider/ec2/machine.rb +10 -14
  45. data/lib/ironfan/provider/ec2/security_group.rb +58 -43
  46. data/lib/ironfan/provider/openstack/elastic_ip.rb +96 -0
  47. data/lib/ironfan/provider/openstack/keypair.rb +78 -0
  48. data/lib/ironfan/provider/openstack/machine.rb +371 -0
  49. data/lib/ironfan/provider/openstack/security_group.rb +224 -0
  50. data/lib/ironfan/provider/openstack.rb +69 -0
  51. data/lib/ironfan/provider/static/machine.rb +192 -0
  52. data/lib/ironfan/provider/static.rb +23 -0
  53. data/lib/ironfan/provider.rb +58 -1
  54. data/lib/ironfan/requirements.rb +17 -1
  55. data/lib/ironfan/version.rb +3 -0
  56. data/lib/ironfan.rb +107 -172
  57. data/spec/chef/cluster_bootstrap_spec.rb +2 -7
  58. data/spec/chef/cluster_launch_spec.rb +1 -2
  59. data/spec/fixtures/realms/samurai.rb +26 -0
  60. data/spec/integration/minimal-chef-repo/clusters/.gitkeep +0 -0
  61. data/spec/integration/minimal-chef-repo/config/.gitkeep +0 -0
  62. data/spec/integration/minimal-chef-repo/knife/credentials/.gitignore +1 -0
  63. data/spec/integration/minimal-chef-repo/knife/credentials/certificates/.gitkeep +0 -0
  64. data/spec/integration/minimal-chef-repo/knife/credentials/client_keys/.gitkeep +0 -0
  65. data/spec/integration/minimal-chef-repo/knife/credentials/data_bag_keys/.gitkeep +0 -0
  66. data/spec/integration/minimal-chef-repo/knife/credentials/ec2_certs/.gitkeep +0 -0
  67. data/spec/integration/minimal-chef-repo/knife/credentials/ec2_keys/.gitkeep +0 -0
  68. data/spec/integration/minimal-chef-repo/knife/credentials/ironfantest-validator.pem +27 -0
  69. data/spec/integration/minimal-chef-repo/knife/credentials/ironfantester.pem +27 -0
  70. data/spec/integration/minimal-chef-repo/tasks/.gitkeep +0 -0
  71. data/spec/ironfan/cluster_spec.rb +1 -2
  72. data/spec/ironfan/diff_spec.rb +0 -2
  73. data/spec/ironfan/dsl_spec.rb +6 -3
  74. data/spec/ironfan/ec2/cloud_provider_spec.rb +17 -18
  75. data/spec/ironfan/ec2/elb_spec.rb +44 -41
  76. data/spec/ironfan/ec2/security_group_spec.rb +45 -47
  77. data/spec/ironfan/manifest_spec.rb +0 -1
  78. data/spec/ironfan/plugin_spec.rb +55 -40
  79. data/spec/ironfan/realm_spec.rb +42 -30
  80. data/spec/spec_helper.rb +17 -31
  81. data/spec/{spec_helper → support}/dummy_chef.rb +0 -0
  82. data/spec/{spec_helper → support}/dummy_diff_drawer.rb +0 -0
  83. metadata +78 -155
  84. data/.rspec +0 -2
  85. data/.yardopts +0 -19
  86. data/VERSION +0 -2
  87. data/chefignore +0 -41
  88. data/notes/Future-development-proposals.md +0 -266
  89. data/notes/Home.md +0 -55
  90. data/notes/INSTALL-cloud_setup.md +0 -103
  91. data/notes/INSTALL.md +0 -134
  92. data/notes/Ironfan-Roadmap.md +0 -70
  93. data/notes/Upgrading-to-v4.md +0 -66
  94. data/notes/advanced-superpowers.md +0 -16
  95. data/notes/aws_servers.jpg +0 -0
  96. data/notes/aws_user_key.png +0 -0
  97. data/notes/cookbook-versioning.md +0 -11
  98. data/notes/core_concepts.md +0 -200
  99. data/notes/declaring_volumes.md +0 -3
  100. data/notes/design_notes-aspect_oriented_devops.md +0 -36
  101. data/notes/design_notes-ci_testing.md +0 -169
  102. data/notes/design_notes-cookbook_event_ordering.md +0 -249
  103. data/notes/design_notes-meta_discovery.md +0 -59
  104. data/notes/ec2-pricing_and_capacity.md +0 -75
  105. data/notes/ec2-pricing_and_capacity.numbers +0 -0
  106. data/notes/homebase-layout.txt +0 -102
  107. data/notes/knife-cluster-commands.md +0 -21
  108. data/notes/named-cloud-objects.md +0 -11
  109. data/notes/opscode_org_key.png +0 -0
  110. data/notes/opscode_user_key.png +0 -0
  111. data/notes/philosophy.md +0 -13
  112. data/notes/rake_tasks.md +0 -24
  113. data/notes/renamed-recipes.txt +0 -142
  114. data/notes/silverware.md +0 -85
  115. data/notes/style_guide.md +0 -300
  116. data/notes/tips_and_troubleshooting.md +0 -92
  117. data/notes/walkthrough-hadoop.md +0 -168
  118. data/notes/walkthrough-web.md +0 -166
  119. data/spec/fixtures/gunbai.rb +0 -24
  120. data/spec/test_config.rb +0 -20
  121. data/tasks/chef_config.rake +0 -38
@@ -21,7 +21,16 @@ module Ironfan
21
21
  def self.expected_ids(computer)
22
22
  return unless computer.server
23
23
  ec2 = computer.server.cloud(:ec2)
24
- ec2.security_groups.keys.map { |name| group_name_with_vpc(name,ec2.vpc) }.uniq
24
+
25
+ server_groups = computer.server.security_groups
26
+ cloud_groups = ec2.security_groups
27
+
28
+ result = []
29
+ [server_groups, cloud_groups].each do |container|
30
+ container.keys.each { |name| result.push( group_name_with_vpc(name,ec2.vpc) )}
31
+ end
32
+ return result.uniq
33
+
25
34
  end
26
35
 
27
36
  def name()
@@ -83,44 +92,46 @@ module Ironfan
83
92
 
84
93
  # Iterate over all of the security group information, keeping track of
85
94
  # any groups that must exist and any authorizations that must be ensured
86
- cloud.security_groups.values.each do |dsl_group|
87
-
88
- groups_to_create << dsl_group.name
89
-
90
- groups_to_create << dsl_group.group_authorized.map do |other_group|
91
- most_appropriate_group_name(other_group, cloud.vpc)
92
- end
93
-
94
- groups_to_create << dsl_group.group_authorized_by.map do |other_group|
95
- most_appropriate_group_name(other_group, cloud.vpc)
96
- end
97
-
98
- authorizations_to_ensure << dsl_group.group_authorized.map do |other_group|
99
- {
100
- :grantor => most_appropriate_group_name(dsl_group.name, cloud.vpc),
101
- :grantee => most_appropriate_group_name(other_group, cloud.vpc),
102
- :grantee_type => :group,
103
- :range => WIDE_OPEN,
104
- }
105
- end
106
-
107
- authorizations_to_ensure << dsl_group.group_authorized_by.map do |other_group|
108
- {
109
- :grantor => most_appropriate_group_name(other_group, cloud.vpc),
110
- :grantee => most_appropriate_group_name(dsl_group.name, cloud.vpc),
111
- :grantee_type => :group,
112
- :range => WIDE_OPEN,
113
- }
114
- end
115
-
116
- authorizations_to_ensure << dsl_group.range_authorizations.map do |range_auth|
117
- range, cidr, protocol = range_auth
118
- {
119
- :grantor => group_name_with_vpc(dsl_group.name, cloud.vpc),
120
- :grantee => { :cidr_ip => cidr, :ip_protocol => protocol },
121
- :grantee_type => :cidr,
122
- :range => range,
123
- }
95
+ [computer.server.security_groups, cloud.security_groups].each do |container|
96
+ container.values.each do |dsl_group|
97
+
98
+ groups_to_create << dsl_group.name
99
+
100
+ groups_to_create << dsl_group.group_authorized.map do |other_group|
101
+ most_appropriate_group_name(other_group, cloud.vpc)
102
+ end
103
+
104
+ groups_to_create << dsl_group.group_authorized_by.map do |other_group|
105
+ most_appropriate_group_name(other_group, cloud.vpc)
106
+ end
107
+
108
+ authorizations_to_ensure << dsl_group.group_authorized.map do |other_group|
109
+ {
110
+ :grantor => most_appropriate_group_name(dsl_group.name, cloud.vpc),
111
+ :grantee => most_appropriate_group_name(other_group, cloud.vpc),
112
+ :grantee_type => :group,
113
+ :range => WIDE_OPEN,
114
+ }
115
+ end
116
+
117
+ authorizations_to_ensure << dsl_group.group_authorized_by.map do |other_group|
118
+ {
119
+ :grantor => most_appropriate_group_name(other_group, cloud.vpc),
120
+ :grantee => most_appropriate_group_name(dsl_group.name, cloud.vpc),
121
+ :grantee_type => :group,
122
+ :range => WIDE_OPEN,
123
+ }
124
+ end
125
+
126
+ authorizations_to_ensure << dsl_group.range_authorizations.map do |range_auth|
127
+ range, cidr, protocol = range_auth
128
+ {
129
+ :grantor => group_name_with_vpc(dsl_group.name, cloud.vpc),
130
+ :grantee => { :cidr_ip => cidr, :ip_protocol => protocol },
131
+ :grantee_type => :cidr,
132
+ :range => range,
133
+ }
134
+ end
124
135
  end
125
136
  end
126
137
  end
@@ -180,15 +191,19 @@ module Ironfan
180
191
  #
181
192
  # Utility
182
193
  #
183
- def self.ensure_groups(computer)
194
+ def self.ensure_groups computer
184
195
  return unless Ec2.applicable computer
185
196
  # Ensure the security_groups include those for cluster & facet
186
197
  # FIXME: This violates the DSL's immutability; it should be
187
198
  # something calculated from within the DSL construction
188
199
  Ironfan.todo("CODE SMELL: violation of DSL immutability: #{caller}")
189
- cloud = computer.server.cloud(:ec2)
190
- c_group = cloud.security_group(computer.server.cluster_name)
191
- c_group.authorized_by_group(c_group.name)
200
+ server = computer.server
201
+ cluster_name = "#{computer.server.realm_name}-#{computer.server.cluster_name}"
202
+ server.security_group computer.server.realm_name
203
+ realm_group = server.security_group cluster_name
204
+ realm_group.authorized_by_group realm_group.name
205
+ facet_name = "#{computer.server.realm_name}-#{computer.server.cluster_name}-#{computer.server.facet_name}"
206
+ server.security_group facet_name
192
207
  end
193
208
 
194
209
  # Try an authorization, ignoring duplicates (this is easier than correlating).
@@ -0,0 +1,96 @@
1
+ module Ironfan
2
+ class Provider
3
+ class OpenStack
4
+ class ElasticIp < Ironfan::Provider::Resource
5
+ delegate :addresses, :associate_address,
6
+ :allocate_address, :auto_elastic_ip, :destroy,
7
+ :domain, :domain=, :describe_addresses, :disassociate_address,
8
+ :domain, :id, :network_interface_id, :network_interface_id=,
9
+ :save, :server=,
10
+ :server, :server_id, :server_id=,
11
+ :to => :adaptee
12
+
13
+ def self.shared?() true; end
14
+ def self.multiple?() false; end
15
+ def self.resource_type() :elastic_ip; end
16
+ def self.expected_ids(computer) [ computer.server.openstack.elastic_ip ]; end
17
+
18
+ def public_ip() adaptee.ip ; end
19
+ def name() adaptee.ip ; end
20
+
21
+ #
22
+ # Discovery
23
+ #
24
+
25
+ def self.load!(cluster=nil)
26
+ OpenStack.connection.addresses.each do |eip|
27
+ register eip
28
+
29
+ # The rest of this definition shows relevant information when -VV
30
+ # is passed to knife and aids in troubleshooting any refusal to
31
+ # attach Elastic IPs
32
+ Chef::Log.debug( "OpenStack Pool: #{eip.pool}" )
33
+ if eip.ip.nil?
34
+ Chef::Log.debug( "no Elastic IPs currently allocated" )
35
+ else
36
+ Chef::Log.debug( "available ip match: #{eip.ip}" )
37
+ Chef::Log.debug( "available allocation_id match: #{eip.id}" )
38
+ end
39
+ Chef::Log.debug( "----------------------" )
40
+ end
41
+
42
+ cluster.servers.each do |s|
43
+ next if s.openstack.elastic_ip.nil?
44
+ if recall? s.openstack.elastic_ip
45
+ Chef::Log.debug( "Cluster elastic_ip matches #{s.openstack.elastic_ip}" )
46
+ else
47
+ Chef::Log.debug( "No matching Elastic IP for #{s.openstack.elastic_ip}" )
48
+ end
49
+ end
50
+
51
+ end
52
+
53
+ #
54
+ # Manipulation
55
+ #
56
+
57
+ def self.save!(computer)
58
+ return unless computer.created?
59
+ return unless elastic_ip = computer.server.openstack.elastic_ip
60
+ return unless recall? elastic_ip
61
+ # also, in the case of VPC Elastic IPs, can discover and use allocation_id to attach a VPC Elastic IP.
62
+ return unless computer.server.openstack.methods.include?(:elastic_ip)
63
+ if ( computer.server.openstack.elastic_ip.nil?)
64
+ if computer.server.addresses.nil?
65
+ OpenStack.connection.allocate_address
66
+ load!
67
+ elastic_ip = computer.server.addresses.first.public_ip
68
+ Chef::Log.debug( "allocating new Elastic IP address" )
69
+ else
70
+ # Second, :elastic_ip is set, has an address available to use but has no set value available in facet definition.
71
+ elastic_ip = computer.server.addresses.first.public_ip
72
+ Chef::Log.debug( "using first available Elastic IP address" )
73
+ end
74
+ elsif ( !computer.server.openstack.elastic_ip.nil? or cloud.vpc.nil? )
75
+ # Third, :elastic_ip is set, has an address available to use, has a set value in facet definition and is not VPC.
76
+ elastic_ip = computer.server.openstack.elastic_ip
77
+ Chef::Log.debug( "using requested Elastic IP address" )
78
+ elsif ( computer.server.opentsack.elastic_ip.nil? )
79
+ # Fourth, is exactly like Third but on a VPC domain. (this is functionaility for attaching VPC Elastic IPS)
80
+ allocation_id = computer.server.openstack.allocation_id
81
+ Chef::Log.debug( "using Elastic IP address matched to given Allocation ID" )
82
+ else
83
+ ui.fatal("You have set both :elastic_ip and :auto_elastic_ip in your facet definition; which are mutually exclusive.")
84
+ end
85
+ Ironfan.step(computer.name, "associating Elastic IP #{elastic_ip}", :blue)
86
+ Ironfan.unless_dry_run do
87
+ Ironfan.safely do
88
+ allocation_id = recall(elastic_ip).id
89
+ OpenSTack.connection.associate_address( computer.machine.id, elastic_ip, nil, allocation_id )
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,78 @@
1
+ module Ironfan
2
+ class Provider
3
+ class OpenStack
4
+
5
+ class Keypair < Ironfan::Provider::Resource
6
+ delegate :_dump, :collection, :collection=, :connection,
7
+ :connection=, :destroy, :fingerprint, :fingerprint=, :identity,
8
+ :identity=, :name, :name=, :new_record?, :public_key,
9
+ :public_key=, :reload, :requires, :requires_one, :save,
10
+ :symbolize_keys, :wait_for, :writable?, :write,
11
+ :to => :adaptee
12
+
13
+ field :key_filename, String, :default => ->{ "#{Keypair.key_dir}/#{name}.pem" }
14
+
15
+ def self.shared? ; true ; end
16
+ def self.multiple? ; false ; end
17
+ def self.resource_type ; :keypair ; end
18
+ def self.expected_ids(computer)
19
+ [computer.server.cluster_name]
20
+ end
21
+
22
+ def private_key
23
+ File.open(key_filename, "rb").read
24
+ end
25
+
26
+ def private_key=(body=nil)
27
+ File.open(key_filename, "w", 0600){|f| f.print( body ) }
28
+ end
29
+
30
+ def to_s
31
+ "<%-15s %-12s>" % [self.class.handle, name]
32
+ end
33
+
34
+ #
35
+ # Discovery
36
+ #
37
+ def self.load!(cluster=nil)
38
+ OpenStack.connection.key_pairs.each do |keypair|
39
+ register keypair unless keypair.blank?
40
+ end
41
+ end
42
+
43
+ def receive_adaptee(obj)
44
+ obj = Openstack.connection.key_pairs.new(obj) if obj.is_a?(Hash)
45
+ super
46
+ end
47
+
48
+ #
49
+ # Manipulation
50
+ #
51
+
52
+ def self.prepare!(computers)
53
+ return if computers.empty?
54
+ name = computers.values[0].server.cluster_name
55
+ return if recall? name
56
+ Ironfan.step(name, "creating key pair for #{name}", :blue)
57
+ result = OpenStack.connection.create_key_pair(name)
58
+ private_key = result.body["keypair"]["private_key"]
59
+ load! # Reload to get the native object
60
+ recall(name).private_key = private_key
61
+ end
62
+
63
+ #
64
+ # Utility
65
+ #
66
+
67
+ def self.key_dir
68
+ return Chef::Config.openstack_key_dir if Chef::Config.openstack_key_dir
69
+ dir = "#{ENV['HOME']}/.chef/credentials/openstack_keys"
70
+ warn "Please set 'openstack_key_dir' in your knife.rb. Will use #{dir} as a default"
71
+ dir
72
+ end
73
+
74
+ end
75
+
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,371 @@
1
+ module Ironfan
2
+ class Provider
3
+ class OpenStack
4
+
5
+ class Machine < Ironfan::IaasProvider::Machine
6
+ delegate :_dump, :addresses, :ami_launch_index, :ami_launch_index=,
7
+ :architecture, :architecture=, :availability_zone,
8
+ :availability_zone=, :block_device_mapping, :block_device_mapping=,
9
+ :client_token, :client_token=, :collection, :collection=,
10
+ :connection, :connection=, :console_output,
11
+ :destroy,
12
+ :ebs_optimized, :flavor, :flavor=,
13
+ :iam_instance_profile,
14
+ :iam_instance_profile=, :iam_instance_profile_arn=,
15
+ :iam_instance_profile_name=, :id, :id=, :identity, :identity=,
16
+ :image, :image=, :instance_initiated_shutdown_behavior,
17
+ :instance_initiated_shutdown_behavior=, :ip_address, :kernel_id,
18
+ :kernel_id=, :key_name, :key_name=, :key_pair, :key_pair=,
19
+ :monitor=, :monitoring, :monitoring=, :network_interfaces,
20
+ :network_interfaces=, :new_record?, :password, :password=,
21
+ :placement_group, :placement_group=, :platform, :platform=,
22
+ :private_key, :private_key=,
23
+ :private_key_path, :private_key_path=, :product_codes,
24
+ :product_codes=,
25
+ :public_key, :public_key=, :public_key_path, :public_key_path=,
26
+ :ramdisk_id, :ramdisk_id=, :ready?, :reason, :reason=, :reboot,
27
+ :reload, :requires, :requires_one, :root_device_name,
28
+ :root_device_name=, :root_device_type, :root_device_type=, :save,
29
+ :scp, :scp_download, :scp_upload, :security_group_ids,
30
+ :security_group_ids=, :setup, :ssh, :ssh_port, :sshable?, :start,
31
+ :state, :state=, :state_reason, :state_reason=, :stop, :subnet_id,
32
+ :subnet_id=, :symbolize_keys, :tenancy, :tenancy=,
33
+ :user_data, :user_data=, :username, :username=, :volumes,
34
+ :wait_for, :name, :name=,
35
+ :metadata, :metadata=,
36
+ :to => :adaptee
37
+
38
+ def self.shared?() false; end
39
+ def self.multiple?() false; end
40
+ # def self.resource_type() Ironfan::IaasProvider::Machine; end
41
+ def self.resource_type() :machine; end
42
+ def self.expected_ids(computer) [computer.server.full_name]; end
43
+
44
+ def tags
45
+ t = metadata.to_hash.update({"Name" => @adaptee.name})
46
+ return t.keys.inject({}) {|h,k| h[k]=t[k]; h[k.to_sym]=t[k]; h}
47
+ end
48
+
49
+ def vpc_id
50
+ return nil
51
+ end
52
+
53
+ def created_at
54
+ return @adaptee.created
55
+ end
56
+
57
+ def flavor_id
58
+ # sometimes flavor comes back empty - especially right after the machine has been launched
59
+ return flavor && flavor["id"]
60
+ end
61
+
62
+ def flavor_name
63
+ fl = OpenStack.flavor_id_hash[ flavor_id ]
64
+ fl && fl.name
65
+ end
66
+
67
+ def image_id
68
+ return image[:id]
69
+ end
70
+
71
+ def groups ; Array(@adaptee.security_groups) ; end
72
+
73
+ def public_hostname ; private_ip_address ; end
74
+ def dns_name ; public_ip_address ; end
75
+
76
+ def keypair ; key_pair ; end
77
+
78
+ def created?
79
+ not ['HARD_DELETED', 'SOFT_DELETED', ].include? state
80
+ end
81
+ def pending?
82
+ state == "BUILD"
83
+ end
84
+ def running?
85
+ state == "ACTIVE"
86
+ end
87
+ def stopping?
88
+ state == "STOPPING"
89
+ end
90
+
91
+ def stopped?
92
+ state == "STOPPED"
93
+ end
94
+
95
+ def error?
96
+ state == "ERROR"
97
+ end
98
+
99
+ def start
100
+ machine = self
101
+ adaptee.start
102
+ adaptee.wait_for{ machine.pending? or machine.running? or machine.error? }
103
+ end
104
+
105
+ def stop
106
+ machine = self
107
+ adaptee.stop
108
+ adaptee.wait_for{ machine.stopping? or machine.stopped? }
109
+ end
110
+
111
+ def perform_after_launch_tasks?
112
+ true
113
+ end
114
+
115
+ def to_display(style,values={})
116
+ # style == :minimal
117
+ values["State"] = (state || "unknown").to_sym
118
+ values["MachineID"] = id
119
+ values["Public IP"] = public_ip_address
120
+ values["Private IP"] = private_ip_address
121
+ values["Created On"] = created_at.to_date
122
+ return values if style == :minimal
123
+
124
+ # style == :default
125
+ values["Flavor"] = flavor_name
126
+ values["AZ"] = availability_zone
127
+ return values if style == :default
128
+
129
+ # style == :expanded
130
+ values["Image"] = image_id
131
+ #values["Volumes"] = volumes.map(&:id).join(', ')
132
+ values["SSH Key"] = key_name
133
+ values
134
+ end
135
+
136
+ def ssh_key
137
+ keypair = cloud.keypair || computer.server.cluster_name
138
+ end
139
+
140
+ def private_ip_address
141
+ adaptee.private_ip_address rescue nil
142
+ end
143
+
144
+ def public_ip_address
145
+ adaptee.floating_ip_address rescue nil
146
+ end
147
+
148
+ def to_s
149
+ "<%-15s %-12s %-25s %-25s %-15s %-15s %-12s %-12s %s:%s>" % [
150
+ self.class.handle, id, created_at, name, private_ip_address, public_ip_address, flavor_name, availability_zone, key_name, groups.join(',') ]
151
+ end
152
+
153
+ #
154
+ # Discovery
155
+ #
156
+ def self.load!(cluster=nil)
157
+ OpenStack.connection.servers.each do |fs|
158
+ machine = new(:adaptee => fs)
159
+ if (not machine.created?)
160
+ next unless Ironfan.chef_config[:include_terminated]
161
+ remember machine, :append_id => "terminated:#{machine.id}"
162
+ elsif recall? machine.name
163
+ machine.bogus << :duplicate_machines
164
+ recall(machine.name).bogus << :duplicate_machines
165
+ remember machine, :append_id => "duplicate:#{machine.id}"
166
+ else # never seen it
167
+ remember machine
168
+ end
169
+ end
170
+ end
171
+
172
+ def receive_adaptee(obj)
173
+ obj = OpenStack.connection.servers.new(obj) if obj.is_a?(Hash)
174
+ super
175
+ end
176
+
177
+ # Find active machines that haven't matched, but should have,
178
+ # make sure all bogus machines have a computer to attach to
179
+ # for display purposes
180
+ def self.validate_resources!(computers)
181
+ recall.each_value do |machine|
182
+ next unless machine.users.empty? and machine.name
183
+ if machine.name.match("^#{computers.cluster.name}-")
184
+ machine.bogus << :unexpected_machine
185
+ end
186
+ next unless machine.bogus?
187
+ fake = Ironfan::Broker::Computer.new
188
+ fake[:machine] = machine
189
+ fake.name = machine.name
190
+ machine.users << fake
191
+ computers << fake
192
+ end
193
+ end
194
+
195
+ #
196
+ # Manipulation
197
+ #
198
+ def self.create!(computer)
199
+ Ironfan.todo("CODE SMELL: overly large method: #{caller}")
200
+ return if computer.machine? and computer.machine.created?
201
+ Ironfan.step(computer.name,"creating cloud machine", :green)
202
+ #
203
+ errors = lint(computer)
204
+ if errors.present? then raise ArgumentError, "Failed validation: #{errors.inspect}" ; end
205
+ #
206
+ launch_desc = launch_description(computer)
207
+ Chef::Log.debug(JSON.pretty_generate(launch_desc))
208
+
209
+ # tag the computer correctly
210
+ tags = {
211
+ 'cluster' => computer.server.cluster_name,
212
+ 'facet' => computer.server.facet_name,
213
+ 'index' => computer.server.index.to_s,
214
+ 'name' => computer.name,
215
+ 'creator' => Chef::Config.username
216
+ }
217
+
218
+ Ironfan.safely do
219
+ fog_server = OpenStack.connection.servers.create(launch_desc)
220
+ machine = Machine.new(:adaptee => fog_server)
221
+ computer.machine = machine
222
+ remember machine, :id => computer.name
223
+
224
+ Ironfan.step(fog_server.id,"waiting for machine to be ready", :gray)
225
+ Ironfan.tell_you_thrice :name => fog_server.id,
226
+ :problem => "server unavailable",
227
+ :error_class => Fog::Errors::Error do
228
+ fog_server.wait_for { state == "ACTIVE" }
229
+ end
230
+ end
231
+
232
+
233
+ computer.machine.metadata.set(tags)
234
+
235
+ #OpenStack.ensure_tags(tags, computer.machine)
236
+
237
+ # no volumes at the momnt
238
+
239
+ # register the new volumes for later save!, and tag appropriately
240
+ #computer.machine.volumes.each do |v|
241
+ # Ironfan.todo "CODE SMELL: Machine is too familiar with EbsVolume problems"
242
+ # ebs_vol = OpenStack::EbsVolume.register v
243
+ # drive = computer.drives.values.select do |drive|
244
+ # drive.volume.device == ebs_vol.device
245
+ # end.first
246
+ # drive.disk = ebs_vol
247
+ #
248
+ # vol_name = "#{computer.name}-#{drive.volume.name}"
249
+ # tags['server'] = computer.name
250
+ # tags['name'] = vol_name
251
+ # tags['Name'] = vol_name
252
+ # tags['mount_point'] = drive.volume.mount_point
253
+ # tags['device'] = drive.volume.device
254
+ # OpenStack.ensure_tags(tags,ebs_vol)
255
+ #end
256
+ end
257
+
258
+ # @returns [Hash{String, Array}] of 'what you did wrong' => [relevant, info]
259
+ def self.lint(computer)
260
+ cloud = computer.server.cloud(:openstack)
261
+ info = [computer.name, cloud.inspect]
262
+ errors = {}
263
+ server_errors = computer.server.lint
264
+ errors["Unhappy Server"] = server_errors if server_errors.present?
265
+ errors["No AMI found"] = info if cloud.image_id.blank?
266
+ errors['Missing client'] = info unless computer.client?
267
+ errors['Missing private_key'] = computer.client unless computer.private_key
268
+ #
269
+ #all_asserted_regions = [OpenStack.connection.region, cloud.region, Chef::Config[:knife][:region], Ironfan.chef_config[:region]].compact.uniq
270
+ #errors["mismatched region"] = all_asserted_regions unless all_asserted_regions.count == 1
271
+ #
272
+ errors
273
+ end
274
+
275
+ def self.launch_description(computer)
276
+ cloud = computer.server.cloud(:openstack)
277
+ user_data_hsh = {
278
+ :chef_server => Chef::Config[:chef_server_url],
279
+ :node_name => computer.name,
280
+ :organization => Chef::Config[:organization],
281
+ :cluster_name => computer.server.cluster_name,
282
+ :facet_name => computer.server.facet_name,
283
+ :facet_index => computer.server.index,
284
+ :client_key => computer.private_key
285
+ }
286
+
287
+ # main machine info
288
+ # note that Fog does not actually create tags when it creates a
289
+ # server; they and permanence are applied during sync
290
+ description = {
291
+ :image_ref => cloud.image_id,
292
+ :flavor_ref => OpenStack.flavor_hash[cloud.flavor].id,
293
+ #:vpc_id => cloud.vpc,
294
+ #:subnet_id => cloud.subnet,
295
+ :key_name => cloud.ssh_key_name(computer),
296
+ :user_data => JSON.pretty_generate(user_data_hsh),
297
+ #:block_device_mapping => block_device_mapping(computer),
298
+ :availability_zone => cloud.default_availability_zone,
299
+ #:monitoring => cloud.monitoring,
300
+ :name => computer.name,
301
+ }
302
+
303
+ description[:security_groups] = (computer.server.security_groups.keys + cloud.security_groups.keys).uniq
304
+
305
+ #description[:iam_server_certificates] = cloud.iam_server_certificates.values.map do |cert|
306
+ # IamServerCertificate.recall(IamServerCertificate.full_name(computer, cert))
307
+ #end.compact.map(&:name)
308
+
309
+ #description[:elastic_load_balancers] = cloud.elastic_load_balancers.values.map do |elb|
310
+ # ElasticLoadBalancer.recall(ElasticLoadBalancer.full_name(computer, elb))
311
+ #end.compact.map(&:name)
312
+
313
+ #if cloud.flavor_info[:placement_groupable]
314
+ # ui.warn "1.3.1 and earlier versions of Fog don't correctly support placement groups, so your nodes will land willy-nilly. We're working on a fix"
315
+ # description[:placement] = { 'groupName' => cloud.placement_group.to_s }
316
+ #end
317
+ #if cloud.flavor_info[:ebs_optimizable]
318
+ # description[:ebs_optimized] = cloud.ebs_optimized
319
+ #end
320
+ description
321
+ end
322
+
323
+ # An array of hashes with dorky-looking keys, just like Fog wants it.
324
+ def self.block_device_mapping(computer)
325
+ Ironfan.todo "CODE SMELL: Machine is too familiar with EbsVolume problems"
326
+ computer.drives.values.map do |drive|
327
+ next if drive.disk # Don't create any disc already satisfied
328
+ volume = drive.volume or next
329
+ hsh = { 'DeviceName' => volume.device }
330
+ if volume.attachable == 'ephemeral'
331
+ hsh['VirtualName'] = drive.name
332
+ # if set for creation at launch (and not already created)
333
+ elsif drive.node[:volume_id].blank? && volume.create_at_launch
334
+ if volume.snapshot_id.blank? && volume.size.blank?
335
+ raise "Must specify a size or a snapshot ID for #{volume}"
336
+ end
337
+ hsh['Ebs.SnapshotId'] = volume.snapshot_id if volume.snapshot_id.present?
338
+ hsh['Ebs.VolumeSize'] = volume.size.to_s if volume.size.present?
339
+ hsh['Ebs.DeleteOnTermination'] = (not volume.keep).to_s
340
+ else next
341
+ end
342
+ hsh
343
+ end.compact
344
+ end
345
+
346
+ def self.destroy!(computer)
347
+ return unless computer.machine?
348
+ forget computer.machine.name
349
+ computer.machine.destroy
350
+ computer.machine.reload # show the node as shutting down
351
+ end
352
+
353
+ def self.save!(computer)
354
+ return unless computer.machine?
355
+ # the EC2 API does not surface disable_api_termination as a value, so we
356
+ # have to set it every time.
357
+ permanent = computer.server.cloud(:openstack).permanent
358
+ return unless computer.created?
359
+ #Ironfan.step(computer.name, "setting termination flag #{permanent}", :blue)
360
+ #Ironfan.unless_dry_run do
361
+ # Ironfan.safely do
362
+ # OpenStack.connection.modify_instance_attribute( computer.machine.id,
363
+ # {'DisableApiTermination.Value' => computer.permanent?, })
364
+ # end
365
+ #end
366
+ end
367
+ end
368
+
369
+ end
370
+ end
371
+ end