foreman_cpp_cloudstack 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,48 @@
1
+ # Foreman Cloudstack Plugin
2
+
3
+ This plugin enables provisioning and managing a Cloudstack Server in Foreman.
4
+
5
+ ## Installation
6
+
7
+ The only way I can get this to work today is unzipping the source code here on top of foreman or by using the included Vagrantfile. The typical gem installation is what I want to support but does not work yet.
8
+
9
+ Please see the Foreman manual for appropriate instructions:
10
+
11
+ * [Foreman: How to Install a Plugin](http://theforeman.org/manuals/latest/index.html#6.1InstallaPlugin)
12
+
13
+ The gem name is "foreman_cpp_cloudstack".
14
+
15
+ ## Compatibility
16
+
17
+ | Foreman Version | Plugin Version |
18
+ | ---------------:| --------------:|
19
+ | >= 1.7 | 0.1.4 |
20
+
21
+ ## Latest code
22
+
23
+ You can get the develop branch of the plugin by specifying your Gemfile in this way:
24
+
25
+ gem 'foreman_cpp_cloudstack', :git => "https://github.com/bytemine/foreman-cloudstack.git"
26
+
27
+ ## Limitations
28
+
29
+ Only advanced networking is supported
30
+
31
+ All user data is gzipped
32
+
33
+ # Copyright
34
+
35
+ Copyright (c) 2014 Citrix
36
+
37
+ This program is free software: you can redistribute it and/or modify
38
+ it under the terms of the GNU General Public License as published by
39
+ the Free Software Foundation, either version 3 of the License, or
40
+ (at your option) any later version.
41
+
42
+ This program is distributed in the hope that it will be useful,
43
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
44
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
45
+ GNU General Public License for more details.
46
+
47
+ You should have received a copy of the GNU General Public License
48
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ begin
4
+ require 'bundler/setup'
5
+ rescue LoadError
6
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
+ end
@@ -0,0 +1,15 @@
1
+ module FogExtensions
2
+ module Cloudstack
3
+ module Flavor
4
+ extend ActiveSupport::Concern
5
+
6
+ def to_label
7
+ "#{id} - #{name}"
8
+ end
9
+
10
+ def to_s
11
+ name
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,88 @@
1
+ module FogExtensions
2
+ module Cloudstack
3
+ module Server
4
+ extend ActiveSupport::Concern
5
+
6
+ #included do
7
+ # alias_method_chain :security_groups, :no_id
8
+ # attr_writer :security_group, :network # floating IP
9
+ #end
10
+
11
+ def to_s
12
+ name
13
+ end
14
+
15
+ def ip_address
16
+ logger.info "BG inspect nics:"
17
+ logger.info nics.inspect
18
+ return nics[0]["ipaddress"]
19
+ end
20
+
21
+ def test_method
22
+ nics[0]["ipaddress"]
23
+ end
24
+
25
+ def ip_addresses
26
+ logger.info "BG inspect nics:"
27
+ logger.info nics.inspect
28
+ nics.map { |n| n.ipaddress }
29
+ end
30
+
31
+ def start
32
+ if state.downcase == 'paused'
33
+ service.unpause_server(id)
34
+ else
35
+ service.resume_server(id)
36
+ end
37
+ end
38
+
39
+ def stop
40
+ service.suspend_server(id)
41
+ end
42
+
43
+ def pause
44
+ service.pause_server(id)
45
+ end
46
+
47
+ def tenant
48
+ service.tenants.detect{|t| t.id == tenant_id }
49
+ end
50
+
51
+ def flavor_with_object
52
+ service.flavors.get attributes[:flavor]['id']
53
+ end
54
+
55
+ def created_at
56
+ Time.parse attributes['created']
57
+ end
58
+
59
+ # the original method requires a server ID, however we want to be able to call this method on new instances too
60
+ def security_groups_with_no_id
61
+ return [] if id.nil?
62
+
63
+ security_groups_without_no_id
64
+ end
65
+
66
+ def network
67
+ return @network if @network # in case we didnt submitting the form again after an error.
68
+ return networks.try(:first).try(:name) if persisted?
69
+ nil
70
+ end
71
+
72
+ def security_group
73
+ return @security_group if @security_group # in case we didnt submitting the form again after an error.
74
+ return security_groups.try(:first).try(:name) if persisted?
75
+ nil
76
+ end
77
+
78
+ def reset
79
+ reboot('HARD')
80
+ end
81
+
82
+ def vm_description
83
+ ""
84
+ end
85
+
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,28 @@
1
+ require 'socket'
2
+ require 'timeout'
3
+ require 'zlib'
4
+
5
+ module ForemanCPPCloudstack::Compute
6
+ extend Orchestration::Compute
7
+
8
+ def setUserData
9
+ logger.info "Rendering UserData template for #{name}"
10
+ template = configTemplate(:kind => "user_data")
11
+ @host = self
12
+ # For some reason this renders as 'built' in spoof view but 'provision' when
13
+ # actually used. For now, use foreman_url('built') in the template
14
+ if self.provider.downcase == "cloudstack"
15
+ logger.info "computing cloudstack userdata"
16
+ wio = StringIO.new("w")
17
+ w_gz = Zlib::GzipWriter.new(wio)
18
+ w_gz.write(unattended_render(template.template))
19
+ w_gz.close
20
+ self.compute_attributes[:user_data] = Base64.strict_encode64(wio.string)
21
+ else
22
+ self.compute_attributes[:user_data] = unattended_render(template.template)
23
+ end
24
+ self.handle_ca
25
+ return false if errors.any?
26
+ logger.info "Revoked old certificates and enabled autosign for UserData"
27
+ end
28
+ end
@@ -0,0 +1,238 @@
1
+ require 'uri'
2
+
3
+ module ForemanCPPCloudstack
4
+ class Cloudstack < ComputeResource
5
+ has_one :key_pair, :foreign_key => :compute_resource_id, :dependent => :destroy
6
+ after_create :setup_key_pair
7
+ after_destroy :destroy_key_pair
8
+ delegate :flavors, :to => :client
9
+ delegate :disk_offerings, :to => :client
10
+ attr_accessor :zone, :hypervisor
11
+ #alias_attribute :subnet_id, :network_ids
12
+
13
+ validates :url, :user, :password, :presence => true
14
+
15
+ # add additional params to the 'new' method
16
+ def initialize(params)
17
+ super
18
+ if params
19
+ attrs[:zone_id] = params[:zone]
20
+ attrs[:hypervisor] = params[:hypervisor]
21
+ end
22
+ end
23
+
24
+ def domains
25
+ return [] if url.blank? or user.blank? or password.blank?
26
+ domainsobj = client.list_domains
27
+
28
+ domains_array = []
29
+ zonesobj["listdomainsresponse"]["domain"].each do |domain|
30
+ z = domain["name"]
31
+ domains_array.push(z)
32
+ end
33
+ logger.info(domainsobj)
34
+ logger.info(domains_array)
35
+ return domains_array
36
+ end
37
+
38
+ def zones
39
+ return [] if url.blank? or user.blank? or password.blank?
40
+ zonesobj = client.list_zones
41
+
42
+ zones_array = []
43
+ zonesobj["listzonesresponse"]["zone"].each do |zone|
44
+ z = {:name => zone["name"], :id => zone["id"]}
45
+ zones_array.push(z)
46
+ end
47
+ return zones_array
48
+ end
49
+
50
+ # save the zone
51
+ def set_zone=(zone_id)
52
+ self.attrs[:zone_id] = zone_id
53
+ end
54
+
55
+ def get_zone
56
+ self.attrs[:zone_id]
57
+ end
58
+
59
+ def hypervisors
60
+ return [] if url.blank? or user.blank? or password.blank?
61
+ hypervisorsobj = client.list_hypervisors
62
+
63
+ hypervisors_array = []
64
+ hypervisorsobj["listhypervisorsresponse"]["hypervisor"].each do |hypervisor|
65
+ hypervisors_array.push(hypervisor["name"])
66
+ end
67
+ return hypervisors_array
68
+ end
69
+
70
+ def domain
71
+ attrs[:domain]
72
+ end
73
+
74
+ def zone
75
+ attrs[:zone]
76
+ end
77
+
78
+ def zone_id
79
+ return client.list_zones["listzonesresponse"]["zone"][0]["id"]
80
+ end
81
+
82
+ def hypervisor
83
+ attrs[:hypervisor]
84
+ end
85
+
86
+ def set_hypervisor=(hypervisor)
87
+ self.attrs[:hypervisor] = hypervisor
88
+ end
89
+
90
+ def get_hypervisor
91
+ self.attrs[:hypervisor]
92
+ end
93
+
94
+ def provided_attributes
95
+ super.merge({ :ip => :test_method })
96
+ end
97
+
98
+ def self.model_name
99
+ ComputeResource.model_name
100
+ end
101
+
102
+ def image_param_name
103
+ :image_ref
104
+ end
105
+
106
+ def capabilities
107
+ [:image]
108
+ end
109
+
110
+ def networks
111
+ fog_ntwrks = []
112
+ networks_array = client.list_networks["listnetworksresponse"]["network"]
113
+ networks_array.each do |network|
114
+ ntwrk = Fog::Compute::Cloudstack::Address.new
115
+ ntwrk.id = network["id"]
116
+ ntwrk.network_id = network["name"]
117
+ fog_ntwrks.push(ntwrk)
118
+ end
119
+ return fog_ntwrks
120
+ end
121
+
122
+ def test_connection options = {}
123
+ client
124
+ errors[:url].empty? and errors[:user].empty? and errors[:password].empty? and zones
125
+ rescue Fog::Compute::Cloudstack::Error => e
126
+ errors[:base] << e.message
127
+ end
128
+
129
+ def available_images
130
+ client.images
131
+ end
132
+
133
+ def create_vm(args = {})
134
+ args[:security_group_ids] = nil
135
+ args[:network_ids] = [args[:network_ids]] if args[:network_ids]
136
+ args[:network_ids] = [args[:subnet_id]] if args[:subnet_id]
137
+ args[:network_ids] = nil
138
+
139
+ args[:zone_id] = get_zone
140
+
141
+ args[:display_name] = args[:name]
142
+ # name has to be hostname without domain: no dots allowed
143
+ name = args[:name].split(/\.(?=[\w])/).first || args[:name]
144
+ args[:name] = name
145
+
146
+ options = vm_instance_defaults.merge(args.to_hash.symbolize_keys)
147
+ vm = client.servers.create options
148
+ vm.wait_for { nics.present? }
149
+ logger.warn "captured ipaddress"
150
+ logger.warn vm.nics[0]["ipaddress"]
151
+ logger.warn vm.inspect
152
+ vm
153
+ rescue => e
154
+ message = JSON.parse(e.response.body)['badRequest']['message'] rescue (e.to_s)
155
+ logger.warn "failed to create vm: #{message}"
156
+ destroy_vm vm.id if vm
157
+ raise message
158
+ end
159
+
160
+ def destroy_vm uuid
161
+ find_vm_by_uuid(uuid).destroy
162
+ rescue ActiveRecord::RecordNotFound
163
+ # if the VM does not exists, we don't really care.
164
+ true
165
+ end
166
+
167
+ def console(uuid)
168
+ vm = find_vm_by_uuid(uuid)
169
+ vm.console.body.merge({'timestamp' => Time.now.utc})
170
+ end
171
+
172
+ def associated_host(vm)
173
+ Host.authorized(:view_hosts, Host).where(:ip => [vm.nics[0]["ipaddress"], vm.floating_ip_address, vm.private_ip_address]).first
174
+ end
175
+
176
+ def ip_address uuid
177
+ vm = find_vm_by_uuid(uuid)
178
+ vm.nics[0]["ipaddress"]
179
+ end
180
+
181
+ def flavor_name(flavor_ref)
182
+ client.flavors.get(flavor_ref).try(:name)
183
+ end
184
+
185
+ def provider_friendly_name
186
+ "Cloudstack"
187
+ end
188
+
189
+ private
190
+
191
+ def client
192
+ results = /^(https|http):\/\/(\S+):(\d+)(\/\S+)/.match(url)
193
+ scheme = results[1]
194
+ path = results[4]
195
+ host = results[2]
196
+ port = results[3]
197
+
198
+ @client = Fog::Compute.new(
199
+ :provider => 'cloudstack',
200
+ :cloudstack_api_key => user,
201
+ :cloudstack_host => host,
202
+ :cloudstack_port => port,
203
+ :cloudstack_path => path,
204
+ :cloudstack_scheme => scheme,
205
+ :cloudstack_secret_access_key => password
206
+ )
207
+ end
208
+
209
+ def setup_key_pair
210
+ result = client.create_ssh_key_pair("foreman-#{id}#{Foreman.uuid}")
211
+ private_key = result["createsshkeypairresponse"]["keypair"]["privatekey"]
212
+ name = result["createsshkeypairresponse"]["keypair"]["name"]
213
+ KeyPair.create! :name => name, :compute_resource_id => self.id, :secret => private_key
214
+ rescue => e
215
+ logger.warn "failed to generate key pair"
216
+ destroy_key_pair
217
+ raise
218
+ end
219
+
220
+ def destroy_key_pair
221
+ return unless key_pair
222
+ logger.info "removing CloudStack key #{key_pair.name}"
223
+ result = client.delete_ssh_key_pair(key_pair.name)
224
+ key.destroy if key
225
+ key_pair.destroy
226
+ true
227
+ rescue => e
228
+ logger.warn "failed to delete key pair from CloudStack, you might need to cleanup manually : #{e}"
229
+ end
230
+
231
+ def vm_instance_defaults
232
+ super.merge(
233
+ :key_name => key_pair.name
234
+ )
235
+ end
236
+
237
+ end
238
+ end
@@ -0,0 +1 @@
1
+ attributes :user, :tenant
@@ -0,0 +1 @@
1
+ attributes :user, :tenant
@@ -0,0 +1,15 @@
1
+ <%= text_f f, :url, :size => "col-md-8", :help_block => _("e.g. http://managementserver.example.com:8080/client/api") %>
2
+ <%= text_f f, :user, :label => _("API Key") %>
3
+ <%= password_f f, :password, :label => _("Secret Key") %>
4
+
5
+ <% zones = f.object.zones rescue [] %>
6
+ <%= selectable_f(f, :zone, zones.map { |z| [z[:name], z[:id]] }, {}, {:label => _('Zone'), :disabled => zones.empty?,
7
+ :help_inline => link_to_function(zones.empty? ? _("Load Zones") : _("Test Connection"), "testConnection(this)",
8
+ :class => "btn + #{zones.empty? ? "btn-default" : "btn-success"}",
9
+ :'data-url' => test_connection_compute_resources_path) + image_tag('/assets/spinner.gif', :id => 'test_connection_indicator', :class => 'hide').html_safe }) %>
10
+
11
+ <% hypervisors = f.object.hypervisors rescue [] %>
12
+ <%= selectable_f(f, :hypervisor, hypervisors.map { |h| [h, h] }, {}, {:label => _('Hypervisor'), :disabled => hypervisors.empty?,
13
+ :help_inline => link_to_function(hypervisors.empty? ? _("Load Hypervisors") : _("Test Connection"), "testConnection(this)",
14
+ :class => "btn + #{hypervisors.empty? ? "btn-default" : "btn-success"}",
15
+ :'data-url' => test_connection_compute_resources_path) + image_tag('/assets/spinner.gif', :id => 'test_connection_indicator', :class => 'hide').html_safe }) %>