knife-rackspace-cluster 0.0.2

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.
@@ -0,0 +1,152 @@
1
+ require 'chef/knife'
2
+ #require 'chef/knife/rackspace/rackspace_base'
3
+ #require 'chef/knife/rackspafce/rackspace_server_create'
4
+
5
+
6
+ # Make sure you subclass from Chef::Knife
7
+
8
+ class Chef
9
+ class Knife
10
+
11
+
12
+ module RaxClusterBase
13
+
14
+ def self.included(includer)
15
+ includer.class_eval do
16
+
17
+ deps do
18
+
19
+ require "net/https"
20
+ require 'net/http'
21
+ require "uri"
22
+ requie 'json'
23
+ #require 'chef/shef/ext'
24
+ #require 'rubygems'
25
+ #require 'chef/knife/rackspace_server_create'
26
+ #require 'json'
27
+ require 'fog'
28
+ #require "thread"
29
+ #require 'net/ssh/multi'
30
+ require 'readline'
31
+ require 'chef/knife/bootstrap'
32
+ require 'chef/json_compat'
33
+
34
+ Chef::Knife::Bootstrap.load_deps
35
+ #include Knife::RackspaceBase
36
+ end
37
+ #option :rax_cluster_auth,
38
+ # :short => "-auth auth_url_for_cluster",
39
+ # :long => "--rackspace-api-url url",
40
+ # :description => "Specify the URL to auth for creation of LB's, i.e. (https:////identity.api.rackspacecloud.com/v1.1)",
41
+ # :proc => Proc.new { |key| Chef::Config[:knife][:rax_cluster_auth] = key }
42
+
43
+ #option :rackspace_api_key,
44
+ # :short => "-K KEY",
45
+ # :long => "--rackspace-api-key KEY",
46
+ # :description => "Your rackspace API key",
47
+ # :proc => Proc.new { |key| Chef::Config[:knife][:rackspace_api_key] = key }
48
+ #
49
+ #option :rackspace_username,
50
+ # :short => "-A USERNAME",
51
+ # :long => "--rackspace-username USERNAME",
52
+ # :description => "Your rackspace API username",
53
+ # :proc => Proc.new { |username| Chef::Config[:knife][:rackspace_username] = username }
54
+ #
55
+ #option :rackspace_version,
56
+ # :long => '--rackspace-version VERSION',
57
+ # :description => 'Rackspace Cloud Servers API version',
58
+ # :default => "v1",
59
+ # :proc => Proc.new { |version| Chef::Config[:knife][:rackspace_version] = version }
60
+ #
61
+ #option :rackspace_api_auth_url,
62
+ # :long => "--rackspace-api-auth-url URL",
63
+ # :description => "Your rackspace API auth url",
64
+ # :default => "auth.api.rackspacecloud.com",
65
+ # :proc => Proc.new { |url| Chef::Config[:knife][:rackspace_api_auth_url] = url },
66
+ # :default => "https://identity.api.rackspacecloud.com/v1.1/auth"
67
+ #
68
+ #option :rackspace_endpoint,
69
+ # :long => "--rackspace-endpoint URL",
70
+ # :description => "Your rackspace API endpoint",
71
+ # :default => "https://dfw.servers.api.rackspacecloud.com/v2",
72
+ # :proc => Proc.new { |url| Chef::Config[:knife][:rackspace_endpoint] = url }
73
+ end
74
+ end
75
+ def populate_environment
76
+ self.setup_environment_vars{
77
+ rackspace_username = Chef::Config[:knife][:rackspace_username]
78
+ rackspace_password = Chef::Config[:knife][:rackspace_password]
79
+ rackspace_endpoint = Chef::Config[:knife][:rackspace_auth_url]
80
+ @headers = {"x-auth-user" => rackspace_username, "x-auth-key" => rackspace_password,
81
+ "auth_url" => rackspace_endpoint,
82
+ "content-type" => "application/json", "Accept" => "application/json"}
83
+ #@rax_endpoint = Chef::Config[:knife][:narciss_url] + "/" + Chef::Config[:knife][:narciss_version] + "/"
84
+ #if rackspace_username_set
85
+ # @rackspace_username = rackspace_username
86
+ #end
87
+ #if rackspace_password_set
88
+ # @rackspace_password = rackspace_password
89
+ #end
90
+ #if rackspace_tenant_set
91
+ # @rackspace_tenant = rackspace_tenant
92
+ #end
93
+ #if rackspace_endpoint_set
94
+ # @rackspace_endpoint = rackspace_endpoint
95
+ #end
96
+
97
+ }
98
+ end
99
+
100
+ def make_web_call(httpVerb,uri,headers=nil, request_content=nil)
101
+ verbs =
102
+ {"get" => "Net::HTTP::Get.new(uri.request_uri, headers)",
103
+ "head" => "Net::HTTP::Head.new(uri.request_uri, headers)",
104
+ "put" => "Net::HTTP::Put.new(uri.request_uri, headers)",
105
+ "delete" => "Net::HTTP::Delete.new(uri.request_uri, headers)",
106
+ "post" => "Net::HTTP::Post.new(uri.request_uri, headers)"
107
+ }
108
+ #Get to work boy! This is Ruby!
109
+ uri = URI.parse(uri)
110
+
111
+ http = Net::HTTP.new(uri.host, uri.port)
112
+ #if uri.host =~ /https/
113
+ http.use_ssl = true
114
+ #end
115
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
116
+ request = eval verbs[httpVerb]
117
+ if httpVerb == 'post' or httpVerb == 'put'
118
+ request.body = request_content
119
+ end
120
+ response = http.request(request)
121
+ if not ('200'..'204').include? response.code
122
+ puts "Error making web call"
123
+ puts "Response code : #{response.code}"
124
+ puts "Response body : #{response.body}"
125
+ #puts "Response Headers : #{response.headers}"
126
+ end
127
+ return response
128
+
129
+ end
130
+ #Just used for lbaas since fog doesn't allow meta data on LB's
131
+ def authenticate(auth_url='https://identity.api.rackspacecloud.com/v1.1/auth',username=Chef::Config[:knife][:rackspace_api_username] ,password=Chef::Config[:knife][:rackspace_api_key])
132
+ auth_json = {
133
+ "credentials" => {
134
+ "username" => username,
135
+ "key" => password
136
+ }
137
+ }
138
+ headers = {"Content-Type" => "application/json"}
139
+ auth_data = make_web_call('post', auth_url, headers, auth_json.to_json)
140
+ lb_data = JSON.parse(auth_data.body)
141
+ lb_returned = {'auth_token' => lb_data['auth']['token']['id'], 'lb_urls' => lb_data['auth']['serviceCatalog']['cloudLoadBalancers'] }
142
+ return lb_returned
143
+ end
144
+ def msg_pair(label, value, color=:cyan)
145
+ if value && !value.to_s.empty?
146
+ puts "#{ui.color(label, color)}: #{value}"
147
+ end
148
+ end
149
+
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,220 @@
1
+
2
+ require 'chef/knife/rackspace_server_create'
3
+ class Chef
4
+ class Knife
5
+ class RaxClusterBuild < RackspaceServerCreate
6
+
7
+ include Knife::RackspaceBase
8
+
9
+ deps do
10
+ require 'fog'
11
+ require 'readline'
12
+ require 'chef/json_compat'
13
+ require 'chef/knife/bootstrap'
14
+ Chef::Knife::Bootstrap.load_deps
15
+ end
16
+
17
+ banner "knife rackspace server create (options)"
18
+
19
+ option :flavor,
20
+ :short => "-f FLAVOR",
21
+ :long => "--flavor FLAVOR",
22
+ :description => "The flavor of server; default is 2 (512 MB)",
23
+ :proc => Proc.new { |f| Chef::Config[:knife][:flavor] = f.to_i },
24
+ :default => 2
25
+
26
+ option :image,
27
+ :short => "-I IMAGE",
28
+ :long => "--image IMAGE",
29
+ :description => "The image of the server",
30
+ :proc => Proc.new { |i| Chef::Config[:knife][:image] = i.to_s }
31
+
32
+ option :server_name,
33
+ :short => "-S NAME",
34
+ :long => "--server-name NAME",
35
+ :description => "The server name"
36
+
37
+ option :chef_node_name,
38
+ :short => "-N NAME",
39
+ :long => "--node-name NAME",
40
+ :description => "The Chef node name for your new node"
41
+
42
+ option :private_network,
43
+ :long => "--private-network",
44
+ :description => "Use the private IP for bootstrapping rather than the public IP",
45
+ :boolean => true,
46
+ :default => false
47
+
48
+ option :ssh_user,
49
+ :short => "-x USERNAME",
50
+ :long => "--ssh-user USERNAME",
51
+ :description => "The ssh username; default is 'root'",
52
+ :default => "root"
53
+
54
+ option :ssh_password,
55
+ :short => "-P PASSWORD",
56
+ :long => "--ssh-password PASSWORD",
57
+ :description => "The ssh password"
58
+
59
+ option :identity_file,
60
+ :short => "-i IDENTITY_FILE",
61
+ :long => "--identity-file IDENTITY_FILE",
62
+ :description => "The SSH identity file used for authentication"
63
+
64
+ option :prerelease,
65
+ :long => "--prerelease",
66
+ :description => "Install the pre-release chef gems"
67
+
68
+ option :bootstrap_version,
69
+ :long => "--bootstrap-version VERSION",
70
+ :description => "The version of Chef to install",
71
+ :proc => Proc.new { |v| Chef::Config[:knife][:bootstrap_version] = v }
72
+
73
+ option :distro,
74
+ :short => "-d DISTRO",
75
+ :long => "--distro DISTRO",
76
+ :description => "Bootstrap a distro using a template; default is 'chef-full'",
77
+ :proc => Proc.new { |d| Chef::Config[:knife][:distro] = d },
78
+ :default => "chef-full"
79
+
80
+ option :template_file,
81
+ :long => "--template-file TEMPLATE",
82
+ :description => "Full path to location of template to use",
83
+ :proc => Proc.new { |t| Chef::Config[:knife][:template_file] = t },
84
+ :default => false
85
+
86
+ option :run_list,
87
+ :short => "-r RUN_LIST",
88
+ :long => "--run-list RUN_LIST",
89
+ :description => "Comma separated list of roles/recipes to apply",
90
+ :proc => lambda { |o| o.split(/[\s,]+/) },
91
+ :default => []
92
+
93
+ option :first_boot_attributes,
94
+ :short => "-j JSON_ATTRIBS",
95
+ :long => "--json-attributes",
96
+ :description => "A JSON string to be added to the first run of chef-client",
97
+ :proc => lambda { |o| JSON.parse(o) },
98
+ :default => {}
99
+
100
+ option :rackspace_metadata,
101
+ :short => "-M JSON",
102
+ :long => "--rackspace-metadata JSON",
103
+ :description => "JSON string version of metadata hash to be supplied with the server create call",
104
+ :proc => Proc.new { |m| Chef::Config[:knife][:rackspace_metadata] = JSON.parse(m) },
105
+ :default => ""
106
+
107
+ option :host_key_verify,
108
+ :long => "--[no-]host-key-verify",
109
+ :description => "Verify host key, enabled by default",
110
+ :boolean => true,
111
+ :default => true
112
+
113
+ def tcp_test_ssh(hostname)
114
+ tcp_socket = TCPSocket.new(hostname, 22)
115
+ readable = IO.select([tcp_socket], nil, nil, 5)
116
+ if readable
117
+ Chef::Log.debug("sshd accepting connections on #{hostname}, banner is #{tcp_socket.gets}")
118
+ yield
119
+ true
120
+ else
121
+ false
122
+ end
123
+ rescue Errno::ETIMEDOUT
124
+ false
125
+ rescue Errno::EPERM
126
+ false
127
+ rescue Errno::ECONNREFUSED
128
+ sleep 2
129
+ false
130
+ rescue Errno::EHOSTUNREACH
131
+ sleep 2
132
+ false
133
+ ensure
134
+ tcp_socket && tcp_socket.close
135
+ end
136
+
137
+ def run
138
+ $stdout.sync = true
139
+
140
+ unless Chef::Config[:knife][:image]
141
+ ui.error("You have not provided a valid image value. Please note the short option for this value recently changed from '-i' to '-I'.")
142
+ exit 1
143
+ end
144
+
145
+ node_name = get_node_name(config[:chef_node_name] || config[:server_name])
146
+
147
+ server = connection.servers.create(
148
+ :name => node_name,
149
+ :image_id => Chef::Config[:knife][:image],
150
+ :flavor_id => locate_config_value(:flavor),
151
+ :metadata => Chef::Config[:knife][:rackspace_metadata]
152
+ )
153
+
154
+ msg_pair("Instance ID", server.id)
155
+ msg_pair("Host ID", server.host_id)
156
+ msg_pair("Name", server.name)
157
+ msg_pair("Flavor", server.flavor.name)
158
+ msg_pair("Image", server.image.name)
159
+ msg_pair("Metadata", server.metadata)
160
+
161
+ print "\n#{ui.color("Waiting server", :magenta)}"
162
+
163
+ # wait for it to be ready to do stuff
164
+ server.wait_for { print "."; ready? }
165
+
166
+ puts("\n")
167
+
168
+ msg_pair("Public DNS Name", public_dns_name(server))
169
+ msg_pair("Public IP Address", public_ip(server))
170
+ msg_pair("Private IP Address", private_ip(server))
171
+ msg_pair("Password", server.password)
172
+
173
+ print "\n#{ui.color("Waiting for sshd", :magenta)}"
174
+
175
+ #which IP address to bootstrap
176
+ bootstrap_ip_address = public_ip(server)
177
+ if config[:private_network]
178
+ bootstrap_ip_address = private_ip(server)
179
+ end
180
+ Chef::Log.debug("Bootstrap IP Address #{bootstrap_ip_address}")
181
+ if bootstrap_ip_address.nil?
182
+ ui.error("No IP address available for bootstrapping.")
183
+ exit 1
184
+ end
185
+
186
+ print(".") until tcp_test_ssh(bootstrap_ip_address) {
187
+ sleep @initial_sleep_delay ||= 10
188
+ puts("done")
189
+ }
190
+ bootstrap_for_node(server, bootstrap_ip_address).run
191
+
192
+ puts "\n"
193
+ msg_pair("Instance ID", server.id)
194
+ msg_pair("Host ID", server.host_id)
195
+ msg_pair("Name", server.name)
196
+ msg_pair("Flavor", server.flavor.name)
197
+ msg_pair("Image", server.image.name)
198
+ msg_pair("Metadata", server.metadata)
199
+ msg_pair("Public DNS Name", public_dns_name(server))
200
+ msg_pair("Public IP Address", public_ip(server))
201
+ msg_pair("Private IP Address", private_ip(server))
202
+ msg_pair("Password", server.password)
203
+ msg_pair("Environment", config[:environment] || '_default')
204
+ msg_pair("Run List", config[:run_list].join(', '))
205
+ ipaddress = ''
206
+ #if not server.private_ip_address
207
+ # ipaddress = server.public_ip_address
208
+ #end
209
+ #if not ipaddress
210
+ # ipaddress = "false"
211
+ #end
212
+ return_hash = {"public_ip" => public_ip(server), "private_ip" => private_ip(server), "server_name" => server.name, "server_id" => server.id}
213
+ return return_hash
214
+ end
215
+
216
+ end
217
+ end
218
+ end
219
+
220
+
@@ -0,0 +1,95 @@
1
+ class Chef
2
+ class Knife
3
+ class RaxClusterChange < Knife
4
+ attr_accessor :headers, :rax_endpoint, :lb_id
5
+ include Knife::RaxClusterBase
6
+
7
+ banner "knife rax cluster change LB_ID (chef_options)"
8
+ deps do
9
+ require 'fog'
10
+ require 'readline'
11
+ require 'chef/json_compat'
12
+ require 'chef/knife/bootstrap'
13
+ Chef::Knife::Bootstrap.load_deps
14
+ end
15
+
16
+ option :lb_region,
17
+ :short => "-r lb_region",
18
+ :long => "--load-balancer-region lb_region",
19
+ :description => "Load balancer region (only supports ORD || DFW)",
20
+ :proc => Proc.new { |lb_region| Chef::Config[:knife][:lb_region] = lb_region},
21
+ :default => "ORD"
22
+
23
+ option :run_list,
24
+ :long => "--run-list run_list",
25
+ :description => "Pass a comma delimted run list --run-list 'recipe[apt],role[base]'",
26
+ :proc => Proc.new { |run_list| Chef::Config[:knife][:run_list] = run_list}
27
+
28
+ option :chef_env,
29
+ :long => "--chef-env environment",
30
+ :description => "Pass a comma delimted run list --run-list 'recipe[apt],role[base]'",
31
+ :proc => Proc.new { |chef_env| Chef::Config[:knife][:chef_env] = chef_env}
32
+
33
+ def change_chef_vars(instances, &block)
34
+ instances.each { |inst|
35
+ query = "name:#{inst['server_name']}"
36
+ query_nodes = Chef::Search::Query.new
37
+ query_nodes.search('node', query) do |node_item|
38
+ yield node_item
39
+ end
40
+ }
41
+ end
42
+ def run
43
+ if !config[:run_list] and !config[:chef_env]
44
+ ui.fatal "Please specify either --run-list or --chef-env to change on your cluster"
45
+ exit(1)
46
+ end
47
+ if @name_args.empty?
48
+ ui.fatal "Please specify a load balancer ID to update"
49
+ exit(1)
50
+ end
51
+ lb_auth = authenticate()
52
+ headers = {"x-auth-token" => lb_auth['auth_token'], "content-type" => "application/json"}
53
+ lb_url = ""
54
+ lb_auth['lb_urls'].each {|lb|
55
+ if config[:lb_region].to_s.downcase == lb['region'].to_s.downcase
56
+ lb_url = lb['publicURL']
57
+ break
58
+ end
59
+ lb_url = lb['publicURL']
60
+ }
61
+ @name_args.each {|arg|
62
+ lb_url = lb_url + "/loadbalancers/#{arg}"
63
+ lb_data = make_web_call("get", lb_url, headers)
64
+ lb_data = JSON.parse(lb_data.body)
65
+ instances = []
66
+ lb_data['loadBalancer']['metadata'].each{|md|
67
+ instances << {"server_name" => md['key'], "uuid" => md['uuid']}
68
+ }
69
+
70
+ if config[:run_list]
71
+ config[:run_list] = config[:run_list].split(",")
72
+ change_chef_vars(instances) { |node_item|
73
+ ui.msg "Changing #{node_item.name} run list to #{config[:run_list]}"
74
+ node_item.run_list(config[:run_list])
75
+ node_item.save
76
+ }
77
+ end
78
+ if config[:chef_env]
79
+ change_chef_vars(instances){|node_item|
80
+ ui.msg "Changing #{node_item.name} chef environment to #{config[:chef_env]}"
81
+ node_item.chef_environment(config[:chef_env])
82
+ node_item.save
83
+
84
+ }
85
+ end
86
+
87
+ }
88
+
89
+ end
90
+
91
+
92
+
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,212 @@
1
+ require 'chef/knife/rax_cluster_base'
2
+ require 'chef/knife/rax_cluster_build'
3
+
4
+ class Chef
5
+ class Knife
6
+ class RaxClusterCreate < Knife
7
+ attr_accessor :headers, :rax_endpoint, :lb_name
8
+ include Knife::RaxClusterBase
9
+ banner "knife rax cluster create (cluster_name) [options]"
10
+ deps do
11
+ require 'fog'
12
+ require 'readline'
13
+ require 'chef/json_compat'
14
+ require 'chef/knife/bootstrap'
15
+ Chef::Knife::Bootstrap.load_deps
16
+ end
17
+
18
+ option :algorithm,
19
+ :short => "-a Load_balacner_algorithm",
20
+ :long => "--algorithm algorithm",
21
+ :description => "Load balancer algorithm",
22
+ :proc => Proc.new { |algorithm| Chef::Config[:knife][:algorithm] = algorithm },
23
+ :default => "ROUND_ROBIN"
24
+
25
+ option :blue_print,
26
+ :short => "-B Blue_print_file",
27
+ :long => "--map blue_print_file",
28
+ :description => "Path to blue Print json file",
29
+ :proc => Proc.new { |i| Chef::Config[:knife][:blue_print] = i.to_s }
30
+
31
+ option :port,
32
+ :short => "-lb_port port",
33
+ :long => "--load-balancer-port port",
34
+ :description => "Load balancer port",
35
+ :proc => Proc.new { |port| Chef::Config[:knife][:port] = port},
36
+ :default => "80"
37
+
38
+ option :timeout,
39
+ :short => "-t timeout",
40
+ :long => "--load-balancer-timeout timeout",
41
+ :description => "Load balancer timeout",
42
+ :proc => Proc.new { |timeout| Chef::Config[:knife][:timeout] = timeout},
43
+ :default => "30"
44
+
45
+ option :lb_region,
46
+ :short => "-r lb_region",
47
+ :long => "--load-balancer-region lb_region",
48
+ :description => "Load balancer region (only supports ORD || DFW)",
49
+ :proc => Proc.new { |lb_region| Chef::Config[:knife][:lb_region] = lb_region},
50
+ :default => "ORD"
51
+
52
+ option :protocol,
53
+ :short => "-p protocol",
54
+ :long => "--load-balancer-protocol protocol",
55
+ :description => "Load balancer protocol",
56
+ :proc => Proc.new { |protocol| Chef::Config[:knife][:protocol] = protocol},
57
+ :default => 'HTTP'
58
+
59
+ option :generate_map_template,
60
+ :short => "-G",
61
+ :long => "--generate_map_template",
62
+ :description => "Generate server map Template in current dir named map_template.json"
63
+
64
+ #option :session_persistence,
65
+ #:short => "-S on_or_off",
66
+ #:long => "--session-persistence session_persistence_on_or_off",
67
+ #:description => "Load balancer session persistence on or off",
68
+ #:proc => Proc.new { |session_persistence| Chef::Config[:knife][:session_persistence] = session_persistence}
69
+
70
+ def generate_map_template
71
+ file_name = "./map_template.json"
72
+ template = %q(
73
+ {
74
+ "blue_print" :
75
+ {
76
+ "name_convention" : "web",
77
+ "run_list" : [
78
+ "recipe[apt]"
79
+ ],
80
+ "quantity" : 1,
81
+ "chef_env" : "dev",
82
+ "image_ref" : "a9753ff4-f46c-427d-9498-1358564f622f",
83
+ "flavor" : 2
84
+ }
85
+
86
+
87
+ }
88
+ )
89
+
90
+ File.open(file_name, 'w') { |file| file.write(template)}
91
+ end
92
+ def create_lb(instances)
93
+ lb_request = {
94
+ "loadBalancer" => {
95
+ "name" => @lb_name.to_s + "_cluster",
96
+ "port" => config[:port] || '80',
97
+ "protocol" => config[:protocol] || 'HTTP',
98
+ "algorithm" => config[:algorithm] || 'ROUND_ROBIN',
99
+ "virtualIps" => [
100
+ {
101
+ "type" => "PUBLIC"
102
+ }
103
+ ],
104
+ "nodes" => [],
105
+ "metadata" => []
106
+ }
107
+ }
108
+
109
+ instances.each {|inst|
110
+ lb_request['loadBalancer']['nodes'] << {"address" => inst['ip_address'], 'port' =>Chef::Config[:knife][:port] || '80', "condition" => "ENABLED" }
111
+ lb_request['loadBalancer']['metadata'] << {"key" => inst['server_name'], "value" => inst['uuid']}
112
+ }
113
+ lb_authenticate = authenticate()
114
+ lb_url = ""
115
+ lb_authenticate['lb_urls'].each {|lb|
116
+ if config[:lb_region].to_s.downcase == lb['region'].to_s.downcase
117
+ lb_url = lb['publicURL']
118
+ break
119
+ end
120
+ lb_url = lb['publicURL']
121
+ }
122
+ lb_url = lb_url + "/loadbalancers"
123
+
124
+ headers = {'Content-type' => 'application/json', 'x-auth-token' => lb_authenticate['auth_token']}
125
+ create_lb_call = make_web_call("post",lb_url, headers, lb_request.to_json )
126
+ lb_details = JSON.parse(create_lb_call.body)
127
+ ui.msg "Load Balancer Cluster Sucesfully Created"
128
+ ui.msg "Load Balancer ID: #{lb_details['loadBalancer']['id']}"
129
+ ui.msg "Load Balancer Name: #{lb_details['loadBalancer']['name']}"
130
+ lb_ip = ""
131
+ lb_details['loadBalancer']['virtualIps'].each {|lb| (lb['ipVersion'] == "IPV4") ? lb_ip = lb['address'] : "not_found"}
132
+ ui.msg "Load Balancer IP Address: #{lb_ip}"
133
+ end
134
+
135
+
136
+ def deploy(blue_print,update_cluster=nil)
137
+ (File.exist?(blue_print)) ? map_contents = JSON.parse(File.read(blue_print)) : map_contents = JSON.parse(blue_print)
138
+ sleep_interval = 1
139
+ instances = []
140
+ if map_contents.has_key?("blue_print")
141
+ bp_values = map_contents['blue_print']
142
+ bootstrap_nodes = []
143
+ quantity = bp_values['quantity'].to_i
144
+ quantity.times do |node_name|
145
+ node_name = rand(900000000)
146
+ create_server = Chef::Knife::RaxClusterBuild.new
147
+ #create_server.config[:identity_file] = config[:identity_file]
148
+ Chef::Config[:knife][:image] = bp_values['image_ref']
149
+ create_server.config[:chef_node_name] = bp_values['name_convention'] + node_name.to_s
150
+ create_server.config[:environment] = bp_values['chef_env']
151
+ Chef::Config[:environment] = bp_values['chef_env']
152
+ create_server.config[:run_list] = bp_values['run_list']
153
+ Chef::Config[:knife][:flavor] = bp_values['flavor']
154
+ begin
155
+ bootstrap_nodes << Thread.new { Thread.current['server_return'] = create_server.run }
156
+ rescue
157
+ ui.msg "Bootstrapping failed"
158
+ end
159
+
160
+ end
161
+ quantity.times do |times|
162
+ if quantity > 20
163
+ sleep_interval = 3
164
+ end
165
+ sleep(sleep_interval)
166
+ bootstrap_nodes[times].join
167
+ instances << {"server_name" => bootstrap_nodes[times]['server_return']['server_name'],
168
+ "ip_address" => bootstrap_nodes[times]['server_return']['private_ip'],
169
+ "uuid" => bootstrap_nodes[times]['server_return']['server_id'],
170
+ "name_convention" => bp_values['name_convention'],
171
+ "chef_env" => bp_values['chef_env'],
172
+ "run_list" => bp_values['run_list']
173
+ }
174
+ end
175
+ end
176
+ if update_cluster
177
+ return instances
178
+ else
179
+ create_lb(instances)
180
+ end
181
+
182
+
183
+
184
+ end
185
+
186
+ def run
187
+ #Generate template config
188
+ if config[:generate_map_template]
189
+ generate_map_template()
190
+ ui.msg "Map template saved as ./map_template.json"
191
+ exit()
192
+ end
193
+
194
+
195
+ if @name_args.empty? or @name_args.size > 1
196
+ ui.fatal "Please specify a single name for your cluster"
197
+ exit(1)
198
+ end
199
+ #Set load balancer name
200
+ @lb_name = @name_args[0]
201
+
202
+ if config[:blue_print]
203
+ deploy(config[:blue_print])
204
+
205
+ end
206
+
207
+ end
208
+
209
+
210
+ end
211
+ end
212
+ end
@@ -0,0 +1,76 @@
1
+ require 'chef/knife/rax_cluster_base'
2
+ require 'chef/knife/rackspace_server_delete'
3
+
4
+ class Chef
5
+ class Knife
6
+ class RaxClusterDelete < Knife
7
+ attr_accessor :headers, :rax_endpoint, :lb_name
8
+ include Knife::RaxClusterBase
9
+ banner "knife rax cluster delete (load_balancer_id) [options]"
10
+ deps do
11
+ require 'fog'
12
+ require 'readline'
13
+ require 'chef/json_compat'
14
+ require 'chef/knife/bootstrap'
15
+ Chef::Knife::Bootstrap.load_deps
16
+ end
17
+
18
+ option :lb_region,
19
+ :short => "-r lb_region",
20
+ :long => "--load-balancer-region lb_region",
21
+ :description => "Load balancer region (only supports ORD || DFW)",
22
+ :proc => Proc.new { |lb_region| Chef::Config[:knife][:lb_region] = lb_region},
23
+ :default => "ORD"
24
+
25
+ def delete_cluster
26
+ lb_authenticate = authenticate()
27
+ lb_url = ""
28
+ puts config[:lb_region]
29
+ headers = {"x-auth-token" => lb_authenticate['auth_token'], "content-type" => "application/json"}
30
+ lb_authenticate['lb_urls'].each {|lb|
31
+ if config[:lb_region].to_s.downcase == lb['region'].to_s.downcase
32
+ lb_url = lb['publicURL']
33
+ break
34
+ end
35
+ lb_url = lb['publicURL']
36
+ }
37
+ @name_args.each {|arg|
38
+ server_uuids = []
39
+ lb_url = lb_url + "/loadbalancers/#{arg}"
40
+ get_uuids = make_web_call("get", lb_url, headers )
41
+ if get_uuids.code == '404'
42
+ ui.msg "Make sure you specify the -r flag to specify what region the LB is located"
43
+ exit(1)
44
+ end
45
+ lb_data = JSON.parse(get_uuids.body)
46
+ lb_data['loadBalancer']['metadata'].each{|meta|
47
+ server_uuids << {'uuid' => meta['value'], 'server_name' => meta['key'] }
48
+ }
49
+ server_uuids.each { |uuid|
50
+ rs_delete = RackspaceServerDelete.new
51
+ rs_delete.config[:yes] = 'yes'
52
+ rs_delete.name_args = [ uuid['uuid'] ]
53
+ rs_delete.config[:purge] = true
54
+ rs_delete.config[:chef_node_name] = uuid['server_name']
55
+ rs_delete.run
56
+ }
57
+ delete_lb_call = make_web_call("delete", lb_url, headers)
58
+ puts "Deleted loadbalancer id #{arg}"
59
+
60
+
61
+ }
62
+ end
63
+
64
+ def run
65
+ if @name_args.empty?
66
+ ui.fatal "Please specify a Load balancer ID to delete"
67
+ end
68
+ ui.confirm("Are you sure you want to delete this Load balancer and ALL nodes associated with it?")
69
+ delete_cluster
70
+ end
71
+
72
+
73
+
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,104 @@
1
+ require 'chef/knife/rax_cluster_base'
2
+ require 'chef/knife/rackspace_server_delete'
3
+
4
+ class Chef
5
+ class Knife
6
+ class RaxClusterExpand < Knife
7
+ attr_accessor :headers, :rax_endpoint, :lb_id
8
+ include Knife::RaxClusterBase
9
+ banner "knife rax cluster expand (load_balancer_id) -B template_file.json"
10
+ deps do
11
+ require 'fog'
12
+ require 'readline'
13
+ require 'chef/json_compat'
14
+ require 'chef/knife/bootstrap'
15
+ Chef::Knife::Bootstrap.load_deps
16
+ end
17
+
18
+ option :blue_print,
19
+ :short => "-B Blue_print_file",
20
+ :long => "--map blue_print_file",
21
+ :description => "Path to blue Print json file",
22
+ :proc => Proc.new { |i| Chef::Config[:knife][:blue_print] = i.to_s }
23
+
24
+ option :port,
25
+ :short => "-lb_port port",
26
+ :long => "--load-balancer-port port",
27
+ :description => "Load balancer port",
28
+ :proc => Proc.new { |port| Chef::Config[:knife][:port] = port},
29
+ :default => "80"
30
+
31
+ option :lb_region,
32
+ :short => "-r lb_region",
33
+ :long => "--load-balancer-region lb_region",
34
+ :description => "Load balancer region (only supports ORD || DFW)",
35
+ :proc => Proc.new { |lb_region| Chef::Config[:knife][:lb_region] = lb_region},
36
+ :default => "ORD"
37
+
38
+ def expand_cluster
39
+ rs_cluster = RaxClusterCreate.new
40
+ rs_cluster.config[:blue_print] = config[:blue_print]
41
+ rs_cluster.lb_name = @name_args[0]
42
+ instance_return = rs_cluster.deploy(config[:blue_print],'update_cluster')
43
+ lb_auth = authenticate()
44
+ puts lb_auth['auth_token']
45
+ headers = {"x-auth-token" => lb_auth['auth_token'], "content-type" => "application/json"}
46
+ lb_url = ""
47
+ lb_auth['lb_urls'].each {|lb|
48
+ if config[:lb_region].to_s.downcase == lb['region'].to_s.downcase
49
+ lb_url = lb['publicURL']
50
+ break
51
+ end
52
+ lb_url = lb['publicURL']
53
+ }
54
+ meta_data_request = {
55
+ "metadata" => []
56
+ }
57
+ node_data_request = {
58
+ "nodes" => []
59
+ }
60
+ meta_url = lb_url + "/loadbalancers/#{@lb_id}/metadata"
61
+ node_url = lb_url + "/loadbalancers/#{@lb_id}/nodes"
62
+
63
+ instance_return.each {|inst|
64
+ node_data_request['nodes'] << {"address" => inst['ip_address'], 'port' =>Chef::Config[:knife][:port] || '80', "condition" => "ENABLED" }
65
+ meta_data_request['metadata'] << {"key" => inst['server_name'], "value" => inst['uuid']}
66
+ }
67
+ meta_request = make_web_call("post", meta_url, headers, meta_data_request.to_json)
68
+ lb_status = lb_url + "/loadbalancers/#{@lb_id}"
69
+ lb_stats = make_web_call("get", lb_status, headers)
70
+ lb_stats = JSON.parse(lb_stats.body)
71
+
72
+ while lb_stats['loadBalancer']['status'].to_s.downcase != 'active'
73
+ sleep(5)
74
+ lb_stats = make_web_call("get", lb_status, headers)
75
+ lb_stats = JSON.parse(lb_stats.body)
76
+ end
77
+ node_request = make_web_call("post", node_url, headers, node_data_request.to_json)
78
+ ui.msg "Load balancer id #{@lb_id} has been updated"
79
+
80
+ end
81
+
82
+ def run
83
+ if @name_args.empty?
84
+ ui.fatal "Please specify Load balancer ID to add nodes too"
85
+ exit(1)
86
+ end
87
+ if !config[:blue_print]
88
+ ui.fatal "Please specify a blue print file to parse with -B"
89
+ exit(1)
90
+ end
91
+
92
+ if config[:blue_print]
93
+ @lb_id = @name_args[0]
94
+ expand_cluster
95
+ end
96
+
97
+ end
98
+
99
+
100
+
101
+
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,56 @@
1
+ class Chef
2
+ class Knife
3
+ class RaxClusterList < Knife
4
+ attr_accessor :headers, :rax_endpoint, :lb_id
5
+ include Knife::RaxClusterBase
6
+
7
+ banner "knife rax cluster list -r lb_region"
8
+ deps do
9
+ require 'fog'
10
+ require 'readline'
11
+ require 'chef/json_compat'
12
+ require 'chef/knife/bootstrap'
13
+ Chef::Knife::Bootstrap.load_deps
14
+ end
15
+
16
+ option :lb_region,
17
+ :short => "-r lb_region",
18
+ :long => "--load-balancer-region lb_region",
19
+ :description => "Load balancer region (only supports ORD || DFW)",
20
+ :proc => Proc.new { |lb_region| Chef::Config[:knife][:lb_region] = lb_region},
21
+ :default => "ORD"
22
+
23
+
24
+
25
+ def run
26
+ lb_auth = authenticate
27
+ headers = {"x-auth-token" => lb_auth['auth_token'], "content-type" => "application/json"}
28
+ lb_url = ""
29
+ lb_auth['lb_urls'].each {|lb|
30
+ if config[:lb_region].to_s.downcase == lb['region'].to_s.downcase
31
+ lb_url = lb['publicURL']
32
+ break
33
+ end
34
+ lb_url = lb['publicURL']
35
+ }
36
+ lb_url = lb_url + "/loadbalancers"
37
+ lb_list = make_web_call("get", lb_url, headers)
38
+ lb_list = JSON.parse(lb_list.body)
39
+ lb_list['loadBalancers'].each {|lb|
40
+ if (lb['name'] =~ /_cluster/i)
41
+ msg_pair("LB Details for #{lb['name']}", " ")
42
+ msg_pair("\s\s\s\sLB ID", "#{lb['id']}")
43
+ msg_pair("\s\s\s\sLB Port", "#{lb['port']}")
44
+ msg_pair("\s\s\s\sLB Algorithm", "#{lb['algorithm']}")
45
+ msg_pair("\s\s\s\sLB Protocol", "#{lb['protocol']}")
46
+ msg_pair("\s\s\s\sLB Node Count", "#{lb['nodeCount']}")
47
+ ui.msg "\n\n"
48
+ end
49
+ }
50
+
51
+ end
52
+
53
+
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,126 @@
1
+ module MyKnifePlugins
2
+ # Make sure you subclass from Chef::Knife
3
+ class OverRide < Chef::Knife
4
+
5
+
6
+ banner "Cloud Builder"
7
+ deps do
8
+
9
+ require 'chef/knife/rackspace/rackspace_server_create'
10
+ require 'json'
11
+ require 'fog'
12
+ require "thread"
13
+ require 'chef/knife/rackspace/rackspace_base'
14
+ require 'net/ssh/multi'
15
+ require 'readline'
16
+ require 'chef/knife/bootstrap'
17
+ require 'chef/json_compat'
18
+ Chef::Knife::Bootstrap.load_deps
19
+ #include Knife::RackspaceBase
20
+ end
21
+ option :server_map,
22
+ :short => "-M MAP_File",
23
+ :long => "--map Map_File",
24
+ :description => "Path to Server Map json file",
25
+ :proc => Proc.new { |i| Chef::Config[:knife][:server_map] = i.to_s }
26
+
27
+ option :provider,
28
+ :short => "-P Provider",
29
+ :long => "--provider provider",
30
+ :description => "Specify RAX, open_stack",
31
+ :default => 'RAX'
32
+ #:proc => Proc.new { |i| Chef::Config[:knife][:server_map] = i.to_s }
33
+
34
+ option :generate_map_template,
35
+ :short => "-G",
36
+ :long => "--generate_map_template",
37
+ :description => "Generate server map Template in current dir named map_template.json"
38
+
39
+ #option :image,
40
+ #:short => "-I IMAGE",
41
+ #:long => "--image IMAGE",
42
+ #:description => "The image of the server",
43
+ #:proc => Proc.new { |i| Chef::Config[:knife][:image] = i.to_s }
44
+
45
+
46
+
47
+
48
+ # This method will be executed when you run this knife command.
49
+ def generate_map_template
50
+ file_name = "./map_template.json"
51
+ template = %q(
52
+ {
53
+ "servers" : [
54
+ {
55
+ "name_convention" : "web",
56
+ "run_list" : [
57
+ "role[base]",
58
+ "role[narciss]"
59
+ ],
60
+ "quantity" : 2,
61
+ "chef_env" : "dev",
62
+ "image_ref" : "c195ef3b-9195-4474-b6f7-16e5bd86acd0",
63
+ "flavor" : 2
64
+
65
+ }
66
+ ]
67
+ })
68
+ File.open(file_name, 'w') { |file| file.write(template)}
69
+ end
70
+ #Populates server_calls with map data
71
+ def parse_server_map(map_file)
72
+ map_contents = JSON.parse(File.read(map_file))
73
+ server_calls = {}
74
+ if map_contents.has_key?("servers")
75
+ for i in map_contents['servers']
76
+ server_calls[i['name_convention']] = {
77
+ "run_list" => i['run_list'] ,
78
+ "quantity" => i['quantity'], "chef_env" => i['chef_env'],
79
+ "image_ref" => i['image_ref']
80
+ }
81
+ #i['quantity'].times do
82
+ run_list = i['run_list'].join(', ')
83
+ #Thread.new {
84
+ create_server = Chef::Knife::RackspaceServerCreate.new
85
+ #create_server.config[:image] = i['image_ref']
86
+ Chef::Config[:knife][:image] = i['image_ref']
87
+ create_server.config[:server_name] = i['name_convention']
88
+ create_server.config[:environment] = i['chef_env']
89
+ create_server.config[:run_list] = i['run_list']
90
+ #create_server.config[:flavor] = i['flavor']
91
+ Chef::Config[:knife][:flavor] = i['flavor']
92
+ create_server.run
93
+ ##}
94
+ #end
95
+
96
+ end
97
+
98
+ else
99
+ ui.fatal "JSON file incorrect format"
100
+ end
101
+ end
102
+
103
+
104
+ def launch_build
105
+ yield
106
+
107
+ end
108
+
109
+ def run
110
+ #Generate template config
111
+ if config[:generate_map_template]
112
+ generate_map_template()
113
+ end
114
+ #Parses Map and takes action
115
+ if config[:server_map]
116
+ parse_server_map(config[:server_map])
117
+
118
+
119
+ end
120
+
121
+
122
+
123
+ end
124
+
125
+ end
126
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: knife-rackspace-cluster
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.2
6
+ platform: ruby
7
+ authors:
8
+ - zack feldstein
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2013-02-19 00:00:00 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: knife-rackspace
17
+ prerelease: false
18
+ requirement: &id001 !ruby/object:Gem::Requirement
19
+ none: false
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ type: :runtime
25
+ version_requirements: *id001
26
+ - !ruby/object:Gem::Dependency
27
+ name: fog
28
+ prerelease: false
29
+ requirement: &id002 !ruby/object:Gem::Requirement
30
+ none: false
31
+ requirements:
32
+ - - "="
33
+ - !ruby/object:Gem::Version
34
+ version: 1.8.0
35
+ type: :runtime
36
+ version_requirements: *id002
37
+ description: Creates rax clusters
38
+ email: zack.feldstein@rackspace.com
39
+ executables: []
40
+
41
+ extensions: []
42
+
43
+ extra_rdoc_files: []
44
+
45
+ files:
46
+ - lib/chef/knife/rax_cluster_base.rb
47
+ - lib/chef/knife/rax_cluster_build.rb
48
+ - lib/chef/knife/rax_cluster_change.rb
49
+ - lib/chef/knife/rax_cluster_create.rb
50
+ - lib/chef/knife/rax_cluster_delete.rb
51
+ - lib/chef/knife/rax_cluster_expand.rb
52
+ - lib/chef/knife/rax_cluster_list.rb
53
+ - lib/chef/knife/super_rax.rb
54
+ homepage: http://github.com/jrcloud/knife_rax_cluster
55
+ licenses: []
56
+
57
+ post_install_message:
58
+ rdoc_options: []
59
+
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: "0"
74
+ requirements: []
75
+
76
+ rubyforge_project:
77
+ rubygems_version: 1.8.23
78
+ signing_key:
79
+ specification_version: 3
80
+ summary: Knife rax cluster
81
+ test_files: []
82
+