knife-rackspace-cluster 0.0.2

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