azure 0.6.1 → 0.6.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 16b612bbee697e6b52ae74452eb8c74474582c95
4
- data.tar.gz: c54be6fa7ef63f2867b680ad272ae5745e62b51b
3
+ metadata.gz: 2218a9a37d85a587e477daa5f91d8c06d0eb3f59
4
+ data.tar.gz: 39efa8b58227090e87058d9aa4765251d423f4ce
5
5
  SHA512:
6
- metadata.gz: ac0c2777f58a96237eb85db41f6990932b352d1873aa8c7bd256b9422ed4ab760ce590618006f517b2d56baf4cb9376b6288929a9093f01a7ebe464d0edaa9d2
7
- data.tar.gz: 7fe4e38feb67a395d7a7e986d3415cb434f13857298ccb27b09356485d46a447165bf6c006c00e5b600e41c2a96ada8877b07aea16c0d14fdc4a1832045f7ec2
6
+ metadata.gz: a47b93b9e6e87ea04bc0458569e16d3b75046f8a3ce178c4d0914078f94d4b78d4ffd3ac124c8870033f09cf2d6932fec147ed739d43f1fb71e8cb72620a32ca
7
+ data.tar.gz: f15f40f69a33d35902b0f585c1de2a405a7468b7c21befe2b0902d0d61da233923c8bb417b4d0212e8ddab87937a9607c32d13220e2316e9d492df2d2a314c40
@@ -1,5 +1,11 @@
1
+ 2014.03.15 - version 0.6.2
2
+ * Restart Virtual Machine
3
+ * Add disk to Virtual Machine
4
+ * Add/Update Virtual Machine endpoints
5
+ * Delete Virtual Machine endpoint
6
+
1
7
  2014.02.18 - version 0.6.1
2
- * Fixed http redirection error.
8
+ * Fixed http redirection error
3
9
  * Add a new role to existing deployment
4
10
  * Add support for including VMs in availability sets
5
11
 
data/README.md CHANGED
@@ -369,6 +369,38 @@ virtual_machine_service.shutdown_virtual_machine('vm_name', 'cloud_service_name'
369
369
  #API to start Virtual Machine
370
370
  virtual_machine_service.start_virtual_machine('vm_name', 'cloud_service_name')
371
371
 
372
+ #API to restart Virtual Machine
373
+ virtual_machine_service.restart_virtual_machine('vm_name', 'cloud_service_name')
374
+
375
+ #API for add disk to Virtual Machine
376
+ lun = 1 #Valid LUN values are 0 through 15.
377
+ options = {
378
+ :disk_label => 'disk-label',
379
+ :disk_size => 100, #In GB
380
+ :import => false
381
+ }
382
+ virtual_machine_service.add_data_disk('vm_name', 'cloud_service_name', lun, options)
383
+
384
+ #API to add/update Virtual Machine endpoints
385
+ endpoint1 = {
386
+ :name => 'ep-1',
387
+ :public_port => 996,
388
+ :local_port => 998,
389
+ :protocol => 'TCP',
390
+ }
391
+ endpoint2 = {
392
+ :name => 'ep-2',
393
+ :public_port => 997,
394
+ :local_port => 997,
395
+ :protocol => 'TCP',
396
+ :load_balancer_name => ‘lb-ep2’,
397
+ :load_balancer => {:protocol => 'http', :path => 'hello'}
398
+ }
399
+ virtual_machine_service.update_endpoints('vm_name', 'cloud_service_name', endpoint1, endpoint2)
400
+
401
+ #API to delete Virtual Machine endpoint
402
+ virtual_machine_service.delete_endpoint('vm_name', 'cloud_service_name', 'endpoint_name')
403
+
372
404
  #API to delete Virtual Machine
373
405
  virtual_machine_service.delete_virtual_machine('vm_name', 'cloud_service_name')
374
406
 
@@ -17,7 +17,7 @@ module Azure
17
17
  class Version
18
18
  MAJOR = 0 unless defined? MAJOR
19
19
  MINOR = 6 unless defined? MINOR
20
- UPDATE = 1 unless defined? UPDATE
20
+ UPDATE = 2 unless defined? UPDATE
21
21
  PRE = nil unless defined? PRE
22
22
 
23
23
  class << self
@@ -39,7 +39,10 @@ module Azure
39
39
  disks.each do |disk_node|
40
40
  disk = VirtualMachineDisk.new
41
41
  disk.name = xml_content(disk_node, 'Name')
42
+ disk.os_type = xml_content(disk_node, 'OS')
42
43
  disk.attached = !xml_content(disk_node,'AttachedTo').empty?
44
+ disk.image = xml_content(disk_node, 'SourceImageName')
45
+ disk.size = xml_content(disk_node, 'LogicalDiskSizeInGB')
43
46
  os_disks << disk
44
47
  end
45
48
  os_disks
@@ -20,7 +20,7 @@ module Azure
20
20
  yield self if block_given?
21
21
  end
22
22
 
23
- attr_accessor :name, :attached
23
+ attr_accessor :name, :attached, :os_type, :image, :size
24
24
 
25
25
  end
26
26
  end
@@ -50,6 +50,11 @@ module Azure
50
50
  Serialization.disks_from_xml(response)
51
51
  end
52
52
 
53
+ def get_virtual_machine_disk(disk_name)
54
+ disk = list_virtual_machine_disks.select {|x| x.name == disk_name}
55
+ disk.first
56
+ end
57
+
53
58
  # Public: Deletes the specified data or operating system disk from the image repository.
54
59
  #
55
60
  # Returns None
@@ -43,6 +43,18 @@ module Azure
43
43
  builder.doc.to_xml
44
44
  end
45
45
 
46
+ def self.restart_virtual_machine_to_xml
47
+ builder = Nokogiri::XML::Builder.new do |xml|
48
+ xml.RestartRoleOperation(
49
+ 'xmlns' => 'http://schemas.microsoft.com/windowsazure',
50
+ 'xmlns:i' => 'http://www.w3.org/2001/XMLSchema-instance'
51
+ ) do
52
+ xml.OperationType 'RestartRoleOperation'
53
+ end
54
+ end
55
+ builder.doc.to_xml
56
+ end
57
+
46
58
  def self.deployment_to_xml(params, options)
47
59
  options[:deployment_name] ||= options[:cloud_service_name]
48
60
  builder = Nokogiri::XML::Builder.new do |xml|
@@ -101,6 +113,7 @@ module Azure
101
113
  end
102
114
 
103
115
  def self.provisioning_configuration_to_xml(xml, params, options)
116
+ fingerprint = params[:certificate][:fingerprint]
104
117
  if options[:os_type] == 'Linux'
105
118
  xml.ConfigurationSet('i:type' => 'LinuxProvisioningConfigurationSet') do
106
119
  xml.ConfigurationSetType 'LinuxProvisioningConfiguration'
@@ -110,11 +123,11 @@ module Azure
110
123
  xml.UserPassword params[:password]
111
124
  xml.DisableSshPasswordAuthentication 'false'
112
125
  end
113
- if params[:certificate][:fingerprint]
126
+ if fingerprint
114
127
  xml.SSH do
115
128
  xml.PublicKeys do
116
129
  xml.PublicKey do
117
- xml.Fingerprint params[:certificate][:fingerprint]
130
+ xml.Fingerprint fingerprint
118
131
  xml.Path "/home/#{params[:vm_user]}/.ssh/authorized_keys"
119
132
  end
120
133
  end
@@ -139,7 +152,7 @@ module Azure
139
152
  if options[:winrm_transport].include?('https')
140
153
  xml.Listener do
141
154
  xml.Protocol 'Https'
142
- xml.CertificateThumbprint params[:certificate][:fingerprint] if params[:certificate][:fingerprint]
155
+ xml.CertificateThumbprint fingerprint if fingerprint
143
156
  end
144
157
  end
145
158
  end
@@ -152,73 +165,82 @@ module Azure
152
165
 
153
166
  def self.default_endpoints_to_xml(xml, options)
154
167
  os_type = options[:os_type]
168
+ endpoints = []
155
169
  if os_type == 'Linux'
156
- xml.InputEndpoint do
157
- xml.LocalPort '22'
158
- xml.Name 'SSH'
159
- xml.Port options[:ssh_port] || '22'
160
- xml.Protocol 'TCP'
170
+ endpoints << {
171
+ name: 'SSH',
172
+ public_port: options[:ssh_port] || '22',
173
+ protocol: 'TCP',
174
+ local_port: '22'
175
+ }
176
+ elsif os_type == 'Windows' && options[:winrm_transport]
177
+ if options[:winrm_transport].include?('http')
178
+ endpoints << {
179
+ name: 'WinRm-Http',
180
+ public_port: '5985',
181
+ protocol: 'TCP',
182
+ local_port: '5985'
183
+ }
161
184
  end
162
- elsif os_type == 'Windows'
163
- if options[:winrm_transport] && options[:winrm_transport].include?('http')
164
- xml.InputEndpoint do
165
- xml.LocalPort '5985'
166
- xml.Name 'WinRm-Http'
167
- xml.Port '5985'
168
- xml.Protocol 'TCP'
169
- end
170
- end
171
- if options[:winrm_transport] && options[:winrm_transport].include?('https')
172
- xml.InputEndpoint do
173
- xml.LocalPort '5986'
174
- xml.Name 'WinRm-Https'
175
- xml.Port '5986'
176
- xml.Protocol 'TCP'
177
- end
185
+ if options[:winrm_transport].include?('https')
186
+ endpoints << {
187
+ name: 'PowerShell',
188
+ public_port: '5986',
189
+ protocol: 'TCP',
190
+ local_port: '5986'
191
+ }
178
192
  end
179
193
  end
194
+ endpoints_to_xml(xml, endpoints)
180
195
  end
181
196
 
182
197
  def self.tcp_endpoints_to_xml(xml, tcp_endpoints)
183
- if tcp_endpoints
184
- tcp_endpoints.split(',').each do |endpoint|
185
- ports = endpoint.split(':')
186
- xml.InputEndpoint do
187
- xml.LocalPort ports[0]
188
- if ports.length > 1
189
- xml.Name 'TCP-PORT-' + ports[1]
190
- xml.Port ports[1]
191
- else
192
- xml.Name 'TCP-PORT-' + ports[0]
193
- xml.Port ports[0]
194
- end
195
- xml.Protocol 'TCP'
196
- end
198
+ endpoints = []
199
+ tcp_endpoints.split(',').each do |endpoint|
200
+ ports = endpoint.split(':')
201
+ tcp_ep = {}
202
+ if ports.length > 1
203
+ tcp_ep[:name] = 'TCP-PORT-' + ports[1]
204
+ tcp_ep[:public_port] = ports[1]
205
+ else
206
+ tcp_ep[:name] = 'TCP-PORT-' + ports[0]
207
+ tcp_ep[:public_port] = ports[0]
197
208
  end
209
+ tcp_ep[:local_port] = ports[0]
210
+ tcp_ep[:protocol] = 'TCP'
211
+ endpoints << tcp_ep
198
212
  end
213
+ endpoints_to_xml(xml, endpoints)
199
214
  end
200
215
 
201
216
  def self.virtual_machines_from_xml(deployXML, cloud_service_name)
202
- unless (deployXML.nil? or deployXML.at_css('Deployment Name').nil?)
203
- rolesXML = deployXML.css('Deployment RoleInstanceList RoleInstance')
217
+ unless deployXML.nil? or deployXML.at_css('Deployment Name').nil?
218
+ instances = deployXML.css('Deployment RoleInstanceList RoleInstance')
219
+ roles = deployXML.css('Deployment RoleList Role')
220
+ ip = deployXML.css('Deployment VirtualIPs VirtualIP')
204
221
  vms = []
205
- rolesXML.each do |instance|
222
+ instances.each do |instance|
206
223
  vm = VirtualMachine.new
224
+ role_name = xml_content(instance, 'RoleName')
207
225
  vm.status = xml_content(instance, 'InstanceStatus')
208
- vm.vm_name = xml_content(instance, 'RoleName').downcase
226
+ vm.vm_name = role_name.downcase
227
+ vm.ipaddress = xml_content(ip, 'Address')
209
228
  vm.role_size = xml_content(instance, 'InstanceSize')
210
229
  vm.hostname = xml_content(instance, 'HostName')
211
230
  vm.cloud_service_name = cloud_service_name.downcase
212
231
  vm.deployment_name = xml_content(deployXML, 'Deployment Name')
213
232
  vm.deployment_status = xml_content(deployXML, 'Deployment Status')
214
- tcp_endpoints_from_xml(instance, vm)
215
- vm.ipaddress = xml_content(instance, 'IpAddress') unless vm.ipaddress
216
- vm.virtual_network_name = xml_content(deployXML.css('Deployment'), 'VirtualNetworkName')
217
- deployXML.css('Deployment RoleList Role').each do |role|
218
- if xml_content(role, 'RoleName') == xml_content(instance, 'RoleName')
233
+ vm.virtual_network_name = xml_content(
234
+ deployXML.css('Deployment'),
235
+ 'VirtualNetworkName'
236
+ )
237
+ roles.each do |role|
238
+ if xml_content(role, 'RoleName') == role_name
219
239
  vm.availability_set_name = xml_content(role, 'AvailabilitySetName')
240
+ endpoints_from_xml(role, vm)
220
241
  vm.os_type = xml_content(role, 'OSVirtualHardDisk OS')
221
242
  vm.disk_name = xml_content(role, 'OSVirtualHardDisk DiskName')
243
+ vm.media_link = xml_content(role, 'OSVirtualHardDisk MediaLink')
222
244
  break
223
245
  end
224
246
  end
@@ -228,30 +250,124 @@ module Azure
228
250
  end
229
251
  end
230
252
 
231
- def self.tcp_endpoints_from_xml(rolesXML, vm)
253
+ def self.endpoints_from_xml(rolesXML, vm)
232
254
  vm.tcp_endpoints = []
233
255
  vm.udp_endpoints = []
234
- endpoints = rolesXML.css('InstanceEndpoint')
256
+ endpoints = rolesXML.css('ConfigurationSets ConfigurationSet InputEndpoints InputEndpoint')
235
257
  endpoints.each do |endpoint|
236
- if vm.ipaddress.nil?
237
- if xml_content(endpoint, 'Name').downcase == 'ssh'
238
- vm.ipaddress = xml_content(endpoint, 'Vip')
239
- elsif !(xml_content(endpoint, 'Name').downcase =~ /winrm/).nil?
240
- vm.ipaddress = xml_content(endpoint, 'Vip')
241
- end
258
+ lb_name = xml_content(endpoint, 'LoadBalancedEndpointSetName')
259
+ ep = {}
260
+ ep[:name] = xml_content(endpoint, 'Name')
261
+ ep[:vip] = xml_content(endpoint, 'Vip')
262
+ ep[:public_port] = xml_content(endpoint, 'Port')
263
+ ep[:local_port] = xml_content(endpoint, 'LocalPort')
264
+ ep[:protocol] = xml_content(endpoint, 'Protocol')
265
+ server_return = xml_content(endpoint, 'EnableDirectServerReturn')
266
+ ep[:direct_server_return] = server_return if !server_return.empty?
267
+ unless lb_name.empty?
268
+ ep[:protocol] = endpoint.css('Protocol').last.text
269
+ ep[:load_balancer_name] = lb_name
270
+ lb_port = xml_content(endpoint, 'LoadBalancerProbe Port')
271
+ lb_protocol = xml_content(endpoint, 'LoadBalancerProbe Protocol')
272
+ lb_path = xml_content(endpoint, 'LoadBalancerProbe Path')
273
+ lb_interval = xml_content(
274
+ endpoint,
275
+ 'LoadBalancerProbe IntervalInSeconds'
276
+ )
277
+ lb_timeout = xml_content(
278
+ endpoint,
279
+ 'LoadBalancerProbe TimeoutInSeconds'
280
+ )
281
+ ep[:load_balancer] = {
282
+ port: lb_port,
283
+ path: lb_path,
284
+ protocol: lb_protocol,
285
+ interval: lb_interval,
286
+ timeout: lb_timeout
287
+ }
242
288
  end
243
- hash = Hash.new
244
- hash['Name'] = xml_content(endpoint, 'Name')
245
- hash['Vip'] = xml_content(endpoint, 'Vip')
246
- hash['PublicPort'] = xml_content(endpoint, 'PublicPort')
247
- hash['LocalPort'] = xml_content(endpoint, 'LocalPort')
248
- if xml_content(endpoint, 'Protocol') == 'tcp'
249
- vm.tcp_endpoints << hash
289
+ if ep[:protocol].downcase == 'tcp'
290
+ vm.tcp_endpoints << ep
250
291
  else
251
- vm.udp_endpoints << hash
292
+ vm.udp_endpoints << ep
252
293
  end
253
294
  end
254
295
  end
296
+
297
+ def self.update_role_to_xml(endpoints, vm)
298
+ builder = Nokogiri::XML::Builder.new do |xml|
299
+ xml.PersistentVMRole(
300
+ 'xmlns' => 'http://schemas.microsoft.com/windowsazure',
301
+ 'xmlns:i' => 'http://www.w3.org/2001/XMLSchema-instance'
302
+ ) do
303
+ xml.ConfigurationSets do
304
+ xml.ConfigurationSet do
305
+ xml.ConfigurationSetType 'NetworkConfiguration'
306
+ xml.InputEndpoints do
307
+ endpoints_to_xml(xml, endpoints)
308
+ end
309
+ end
310
+ end
311
+ xml.OSVirtualHardDisk do
312
+ end
313
+ end
314
+ end
315
+ builder.doc.to_xml
316
+ end
317
+
318
+ def self.endpoints_to_xml(xml, endpoints)
319
+ endpoints.each do |endpoint|
320
+ endpoint[:load_balancer] ||= {}
321
+ protocol = endpoint[:protocol]
322
+ port = endpoint[:public_port]
323
+ interval = endpoint[:load_balancer][:interval]
324
+ timeout = endpoint[:load_balancer][:timeout]
325
+ path = endpoint[:load_balancer][:path]
326
+ balancer_name = endpoint[:load_balancer_name]
327
+ xml.InputEndpoint do
328
+ xml.LoadBalancedEndpointSetName balancer_name if balancer_name
329
+ xml.LocalPort endpoint[:local_port]
330
+ xml.Name endpoint[:name]
331
+ xml.Port endpoint[:public_port]
332
+ if balancer_name
333
+ xml.LoadBalancerProbe do
334
+ xml.Path path if path
335
+ xml.Port endpoint[:load_balancer][:port] || port
336
+ xml.Protocol endpoint[:load_balancer][:protocol] || 'TCP'
337
+ xml.IntervalInSeconds interval if interval
338
+ xml.TimeoutInSeconds timeout if timeout
339
+ end
340
+ end
341
+ xml.Protocol protocol
342
+ xml.EnableDirectServerReturn endpoint[:direct_server_return] unless endpoint[:direct_server_return].nil?
343
+ end
344
+ end
345
+ end
346
+
347
+ def self.add_data_disk_to_xml(lun, media_link, options)
348
+ if options[:import] && options[:disk_name].nil?
349
+ Loggerx.error_with_exit "The data disk name is not valid."
350
+ end
351
+ builder = Nokogiri::XML::Builder.new do |xml|
352
+ xml.DataVirtualHardDisk(
353
+ 'xmlns' => 'http://schemas.microsoft.com/windowsazure',
354
+ 'xmlns:i' => 'http://www.w3.org/2001/XMLSchema-instance'
355
+ ) do
356
+ xml.HostCaching options[:host_caching] || 'ReadOnly'
357
+ xml.DiskLabel options[:disk_label]
358
+ xml.DiskName options[:disk_name] if options[:import]
359
+ xml.Lun lun
360
+ xml.LogicalDiskSizeInGB options[:disk_size] || 1
361
+ unless options[:import]
362
+ disk_name = media_link[/([^\/]+)$/]
363
+ media_link = media_link.gsub(/#{disk_name}/, (Time.now.strftime('disk_%Y_%m_%d_%H_%M')) + '.vhd')
364
+ xml.MediaLink media_link
365
+ end
366
+ end
367
+ end
368
+ builder.doc.to_xml
369
+ end
370
+
255
371
  end
256
372
  end
257
373
  end
@@ -36,6 +36,7 @@ module Azure
36
36
  attr_accessor :disk_name
37
37
  attr_accessor :virtual_network_name
38
38
  attr_accessor :availability_set_name
39
+ attr_accessor :media_link
39
40
  end
40
41
  end
41
42
  end
@@ -18,7 +18,6 @@ include Azure::VirtualMachineImageManagement
18
18
  module Azure
19
19
  module VirtualMachineManagement
20
20
  class VirtualMachineManagementService < BaseManagementService
21
-
22
21
  def initialize
23
22
  super()
24
23
  end
@@ -26,16 +25,19 @@ module Azure
26
25
  # Public: Get a lists of virtual machines available under the current subscription.
27
26
  #
28
27
  # Returns an list of Azure::VirtualMachineManagement::VirtualMachine instances.
29
- def list_virtual_machines
28
+ def list_virtual_machines(*cloud_service_names)
30
29
  roles = []
31
- cloud_service = Azure::CloudServiceManagementService.new
32
- cloud_services = cloud_service.list_cloud_services
33
- cloud_services.each do |cloud_service|
34
- request_path = "/services/hostedservices/#{cloud_service.name}/deploymentslots/production"
30
+ cloud_service_names.flatten!
31
+ if cloud_service_names.empty?
32
+ cloud_service = Azure::CloudServiceManagementService.new
33
+ cloud_service_names = cloud_service.list_cloud_services.map(&:name)
34
+ end
35
+ cloud_service_names.each do |cloud_service_name|
36
+ request_path = "/services/hostedservices/#{cloud_service_name}/deploymentslots/production"
35
37
  request = ManagementHttpRequest.new(:get, request_path)
36
38
  request.warn = true
37
39
  response = request.call
38
- roles << Serialization.virtual_machines_from_xml(response,cloud_service.name)
40
+ roles << Serialization.virtual_machines_from_xml(response, cloud_service_name)
39
41
  end
40
42
  roles.flatten.compact
41
43
  end
@@ -49,7 +51,7 @@ module Azure
49
51
  #
50
52
  # Returns an Azure::VirtualMachineManagement::VirtualMachine instance.
51
53
  def get_virtual_machine(name, cloud_service_name)
52
- server = list_virtual_machines.select {|x| x.vm_name == name && x.cloud_service_name == cloud_service_name}
54
+ server = list_virtual_machines(cloud_service_name).select { |x| x.vm_name == name && x.cloud_service_name == cloud_service_name }
53
55
  server.first
54
56
  end
55
57
 
@@ -83,7 +85,7 @@ module Azure
83
85
  # * +:ssh_private_key_file+ - String. Path of private key file.
84
86
  # * +:ssh_certificate_file+ - String. Path of certificate file.
85
87
  # * +:ssh_port+ - Integer. Specifies the SSH port number.
86
- # * +:vm_size+ - String. Specifies the size of the virtual machine instance.
88
+ # * +:vm_size+ - String. Specifies the size of the virtual machine instance.
87
89
  # * +:winrm_transport+ - Array. Specifies WINRM transport protocol.
88
90
  # * +:availability_set_name+ - String. Specifies the availability set name.
89
91
  #
@@ -103,15 +105,15 @@ module Azure
103
105
  options[:os_type] = get_os_type(params[:image])
104
106
  validate_deployment_params(params, options)
105
107
  options[:deployment_name] ||= options[:cloud_service_name]
106
-
108
+
107
109
  unless add_role
108
- Loggerx.info "Creating deploymnent..."
110
+ Loggerx.info 'Creating deploymnent...'
109
111
  options[:cloud_service_name] ||= generate_cloud_service_name(params[:vm_name])
110
112
  options[:storage_account_name] ||= generate_storage_account_name(params[:vm_name])
111
113
  optionals = {}
112
114
  if options[:virtual_network_name]
113
115
  virtual_network_service = Azure::VirtualNetworkManagementService.new
114
- virtual_networks = virtual_network_service.list_virtual_networks.select{|x| x.name == options[:virtual_network_name]}
116
+ virtual_networks = virtual_network_service.list_virtual_networks.select { |x| x.name == options[:virtual_network_name] }
115
117
  if virtual_networks.empty?
116
118
  Loggerx.error_with_exit "Virtual network #{options[:virtual_network_name]} doesn't exists"
117
119
  else
@@ -124,19 +126,19 @@ module Azure
124
126
  end
125
127
  cloud_service = Azure::CloudServiceManagementService.new
126
128
  cloud_service.create_cloud_service(options[:cloud_service_name], optionals)
127
- cloud_service.upload_certificate(options[:cloud_service_name],params[:certificate]) unless params[:certificate].empty?
129
+ cloud_service.upload_certificate(options[:cloud_service_name], params[:certificate]) unless params[:certificate].empty?
128
130
  Azure::StorageManagementService.new.create_storage_account(options[:storage_account_name], optionals)
129
- body = Serialization.deployment_to_xml(params,options)
131
+ body = Serialization.deployment_to_xml(params, options)
130
132
  path = "/services/hostedservices/#{options[:cloud_service_name]}/deployments"
131
133
  else
132
134
 
133
- Loggerx.info "Deployment exists, adding role..."
135
+ Loggerx.info 'Deployment exists, adding role...'
134
136
  body = Serialization.role_to_xml(params, options).to_xml
135
137
  path = "/services/hostedservices/#{options[:cloud_service_name]}/deployments/#{options[:deployment_name]}/roles"
136
138
  end
137
139
  Loggerx.error 'Cloud service name is required for adding role.' unless options[:cloud_service_name]
138
140
  Loggerx.error 'Storage account name is required for adding role.' unless options[:storage_account_name]
139
- Loggerx.info "Deployment in progress..."
141
+ Loggerx.info 'Deployment in progress...'
140
142
  request = ManagementHttpRequest.new(:post, path, body)
141
143
  request.call
142
144
  get_virtual_machine(params[:vm_name], options[:cloud_service_name])
@@ -156,19 +158,34 @@ module Azure
156
158
  #
157
159
  # Returns NONE
158
160
  def delete_virtual_machine(vm_name, cloud_service_name)
159
- vm = get_virtual_machine(vm_name,cloud_service_name)
161
+ vm = get_virtual_machine(vm_name, cloud_service_name)
160
162
  if vm
161
163
  cloud_service = Azure::CloudServiceManagementService.new
162
164
  cloud_service.delete_cloud_service_deployment(cloud_service_name)
163
165
  cloud_service.delete_cloud_service(cloud_service_name)
164
166
  Loggerx.info "Waiting for disk to be released.\n"
165
- sleep 60
167
+ disk_name = vm.disk_name
166
168
  disk_management_service = VirtualMachineDiskManagementService.new
167
- disk_management_service.delete_virtual_machine_disk(vm.disk_name)
169
+ # Wait for 180s for disk to be released.
170
+ disk = nil
171
+ 18.times do
172
+ print '# '
173
+ disk = disk_management_service.get_virtual_machine_disk(disk_name)
174
+ unless disk.attached
175
+ print "Disk released.\n"
176
+ break
177
+ end
178
+ sleep 10
179
+ end
180
+ if disk.attached
181
+ Loggerx.error "\nCannot delete disk #{disk_name}."
182
+ else
183
+ disk_management_service.delete_virtual_machine_disk(disk_name)
184
+ end
168
185
  else
169
186
  Loggerx.error "Cannot find virtual machine #{vm_name} under cloud service #{cloud_service_name}"
170
187
  end
171
- rescue
188
+ rescue
172
189
  end
173
190
 
174
191
  # Public: Shuts down the specified virtual machine.
@@ -184,8 +201,8 @@ module Azure
184
201
  def shutdown_virtual_machine(vm_name, cloud_service_name)
185
202
  vm = get_virtual_machine(vm_name, cloud_service_name)
186
203
  if vm
187
- if ['StoppedVM','StoppedDeallocated'].include?(vm.status)
188
- Loggerx.error "Cannot perform the shutdown operation on a stopped virtual machine."
204
+ if ['StoppedVM', 'StoppedDeallocated'].include?(vm.status)
205
+ Loggerx.error 'Cannot perform the shutdown operation on a stopped virtual machine.'
189
206
  elsif vm.deployment_status == 'Running'
190
207
  path = "/services/hostedservices/#{vm.cloud_service_name}/deployments/#{vm.deployment_name}/roleinstances/#{vm.vm_name}/Operations"
191
208
  body = Serialization.shutdown_virtual_machine_to_xml
@@ -193,7 +210,7 @@ module Azure
193
210
  request = ManagementHttpRequest.new(:post, path, body)
194
211
  request.call
195
212
  else
196
- Loggerx.error "Cannot perform the shutdown operation on a stopped deployment."
213
+ Loggerx.error 'Cannot perform the shutdown operation on a stopped deployment.'
197
214
  end
198
215
  else
199
216
  Loggerx.error "Cannot find virtual machine \"#{vm_name}\" under cloud service \"#{cloud_service_name}\". "
@@ -214,7 +231,7 @@ module Azure
214
231
  vm = get_virtual_machine(vm_name, cloud_service_name)
215
232
  if vm
216
233
  if vm.status == 'ReadyRole'
217
- Loggerx.error "Cannot perform the start operation on started virtual machine."
234
+ Loggerx.error 'Cannot perform the start operation on started virtual machine.'
218
235
  else
219
236
  path = "/services/hostedservices/#{vm.cloud_service_name}/deployments/#{vm.deployment_name}/roleinstances/#{vm.vm_name}/Operations"
220
237
  body = Serialization.start_virtual_machine_to_xml
@@ -227,6 +244,156 @@ module Azure
227
244
  end
228
245
  end
229
246
 
247
+ # Public: Restarts the specified virtual machine.
248
+ #
249
+ # ==== Attributes
250
+ #
251
+ # * +name+ - String. Virtual machine name.
252
+ # * +cloud_service_name+ - String. Cloud service name.
253
+ #
254
+ # See http://msdn.microsoft.com/en-us/library/windowsazure/jj157197.aspx
255
+ #
256
+ # Returns NONE
257
+ def restart_virtual_machine(vm_name, cloud_service_name)
258
+ vm = get_virtual_machine(vm_name, cloud_service_name)
259
+ if vm
260
+ path = "/services/hostedservices/#{vm.cloud_service_name}/deployments/#{vm.deployment_name}/roleinstances/#{vm.vm_name}/Operations"
261
+ body = Serialization.restart_virtual_machine_to_xml
262
+ Loggerx.info "Restarting virtual machine \"#{vm.vm_name}\" ..."
263
+ request = ManagementHttpRequest.new(:post, path, body)
264
+ request.call
265
+ else
266
+ Loggerx.error "Cannot find virtual machine \"#{vm_name}\" under cloud service \"#{cloud_service_name}\"."
267
+ end
268
+ end
269
+
270
+ # Public: Add/Update endpoints of virtual machine.
271
+ #
272
+ # ==== Attributes
273
+ #
274
+ # * +name+ - String. Virtual machine name.
275
+ # * +cloud_service_name+ - String. Cloud service name.
276
+ # * +input_endpoints+ - Hash. A hash of the name/value pairs for the endpoint.
277
+ #
278
+ # ==== Endpoint
279
+ #
280
+ # Accepted key/value pairs are:
281
+ # * +:local_port+ - String. Specifies the internal port on which the
282
+ # Virtual Machine is listening.
283
+ # * +:public_port+ - String. Specifies the external port to use for
284
+ # the endpoint.
285
+ # * +:name+ - String. Specifies the name of the external endpoint.
286
+ # * +load_balancer_name+ - String. Specifies a name for a set of
287
+ # load-balanced endpoints.
288
+ # * +:protocol+ - String. Specifies the transport protocol
289
+ # for the endpoint. Possible values are: TCP, UDP
290
+ # * +:direct_server_return+ - String. Specifies whether the endpoint
291
+ # uses Direct Server Return. (optional)
292
+ # * +:load_balancer - Hash. Contains properties that define the
293
+ # endpoint settings that the load balancer uses to monitor the
294
+ # availability of the Virtual Machine (optional)
295
+ #
296
+ # === Load balancer
297
+ #
298
+ # Accepted key/value pairs are:
299
+ # * +:port+ - String. Specifies the internal port on which the
300
+ # Virtual Machine is listening.
301
+ # * +:protocol+ - String. Specifies the protocol to use to inspect the
302
+ # availability status of the virtual machine.
303
+ # * +:interval+ - String. Specifies the interval for the load balancer
304
+ # probe in seconds. (optional)
305
+ # * +:timeout+ - String. Specifies the timeout for the load balancer
306
+ # probe in seconds. (optional)
307
+ # * +:path+ - String. Specifies the relative path to inspect to
308
+ # determine the availability status of the Virtual Machine. (optional)
309
+ #
310
+ # See http://msdn.microsoft.com/en-us/library/windowsazure/jj157187.aspx
311
+ #
312
+ # Returns NONE
313
+ def update_endpoints(vm_name, cloud_service_name, *input_endpoints)
314
+ input_endpoints.flatten!
315
+ vm = get_virtual_machine(vm_name, cloud_service_name)
316
+ if vm
317
+ path = "/services/hostedservices/#{vm.cloud_service_name}/deployments/#{vm.deployment_name}/roles/#{vm_name}"
318
+ endpoints = vm.tcp_endpoints + vm.udp_endpoints
319
+ input_endpoints.each do |iep|
320
+ endpoints.delete_if { |ep| iep[:name].downcase == ep[:name].downcase && iep[:protocol].downcase == ep[:protocol] }
321
+ end
322
+ endpoints += input_endpoints
323
+ body = Serialization.update_role_to_xml(endpoints, vm)
324
+ request = ManagementHttpRequest.new(:put, path, body)
325
+ Loggerx.info "Updating endpoints of virtual machine #{vm.vm_name} ..."
326
+ request.call
327
+ else
328
+ Loggerx.error "Cannot find virtual machine \"#{vm_name}\" under cloud service \"#{cloud_service_name}\"."
329
+ end
330
+ end
331
+
332
+ # Public: Delete endpoint of virtual machine.
333
+ #
334
+ # ==== Attributes
335
+ #
336
+ # * +name+ - String. Virtual machine name.
337
+ # * +cloud_service_name+ - String. Cloud service name.
338
+ # * +endpoint_name+ - String. Name of endpoint.
339
+ #
340
+ # See http://msdn.microsoft.com/en-us/library/windowsazure/jj157187.aspx
341
+ #
342
+ # Returns NONE
343
+ def delete_endpoint(vm_name, cloud_service_name, endpoint_name)
344
+ vm = get_virtual_machine(vm_name, cloud_service_name)
345
+ if vm
346
+ path = "/services/hostedservices/#{vm.cloud_service_name}/deployments/#{vm.deployment_name}/roles/#{vm_name}"
347
+ endpoints = vm.tcp_endpoints + vm.udp_endpoints
348
+ endpoints.delete_if { |ep| endpoint_name.downcase == ep[:name].downcase }
349
+ body = Serialization.update_role_to_xml(endpoints, vm)
350
+ request = ManagementHttpRequest.new(:put, path, body)
351
+ Loggerx.info "Deleting virtual machine endpoint #{endpoint_name} ..."
352
+ request.call
353
+ else
354
+ Loggerx.error "Cannot find virtual machine \"#{vm_name}\" under cloud service \"#{cloud_service_name}\"."
355
+ end
356
+ end
357
+
358
+ # Public: adds a data disk to a virtual machine.
359
+ #
360
+ # ==== Attributes
361
+ #
362
+ # * +cloud_service_name+ - String. Cloud service name.
363
+ # * +vm_name+ - String. Virtual machine name.
364
+ # * +lun+ - String. Specifies the Logical Unit Number
365
+ # (LUN) for the disk. Valid LUN values are 0 through 15.
366
+ # * +options+ - Hash. Optional parameters.
367
+ #
368
+ # ==== Options
369
+ #
370
+ # Accepted key/value pairs in options parameter are:
371
+ # * +:import+ - Boolean. if true, then allows to use an existing
372
+ # disk by disk name. if false, then create and attach new data disk.
373
+ # * +:disk_name+ - String. Specifies the name of the disk.
374
+ # Reqruied if using existing disk.
375
+ # * +:host_caching+ - String. Specifies the caching behavior of data disk
376
+ # The default is ReadOnly. Possible values are: None, ReadOnly, ReadWrite
377
+ # * +:disk_label+ - String. Specifies the description of the data disk.
378
+ # * +:disk_size+ - String. Specifies the size of disk in GB
379
+ #
380
+ # See http://msdn.microsoft.com/en-us/library/windowsazure/jj157199.aspx
381
+ #
382
+ # Returns None
383
+ def add_data_disk(vm_name, cloud_service_name, lun, options = {})
384
+ options[:import] ||= false
385
+ vm = get_virtual_machine(vm_name, cloud_service_name)
386
+ if vm
387
+ path = "/services/hostedservices/#{cloud_service_name}/deployments/#{vm.deployment_name}/roles/#{vm_name}/DataDisks"
388
+ body = Serialization.add_data_disk_to_xml(lun, vm.media_link, options)
389
+ Loggerx.info "Adding data disk to virtual machine #{vm_name} ..."
390
+ request = ManagementHttpRequest.new(:post, path, body)
391
+ request.call
392
+ else
393
+ Loggerx.error "Cannot find virtual machine \"#{vm_name}\" under cloud service \"#{cloud_service_name}\"."
394
+ end
395
+ end
396
+
230
397
  private
231
398
 
232
399
  # Private: Gets the operating system type of an image.
@@ -234,45 +401,45 @@ module Azure
234
401
  # Returns Linux or Windows
235
402
  def get_os_type(image_name)
236
403
  image_service = Azure::VirtualMachineImageManagementService.new
237
- image = image_service.list_virtual_machine_images.select{|x| x.name == image_name}.first
238
- Loggerx.error_with_exit "The virtual machine image source is not valid." unless image
404
+ image = image_service.list_virtual_machine_images.select { |x| x.name == image_name }.first
405
+ Loggerx.error_with_exit 'The virtual machine image source is not valid.' unless image
239
406
  image.os_type
240
407
  end
241
408
 
242
409
  def generate_cloud_service_name(vm_name)
243
- random_string(vm_name+'-service-')
410
+ random_string(vm_name + '-service-')
244
411
  end
245
412
 
246
413
  def generate_storage_account_name(vm_name)
247
- random_string(vm_name+'storage').gsub(/[^0-9a-z ]/i, '').downcase[0..23]
414
+ random_string(vm_name + 'storage').gsub(/[^0-9a-z ]/i, '').downcase[0..23]
248
415
  end
249
416
 
250
417
  def validate_deployment_params(params, options)
251
418
  errors = []
252
- params_keys = ["vm_name", "image", "location", "vm_user"]
253
- if options[:os_type] == "Windows"
254
- params_keys += ["password"]
419
+ params_keys = ['vm_name', 'image', 'location', 'vm_user']
420
+ if options[:os_type] == 'Windows'
421
+ params_keys += ['password']
255
422
  end
256
423
  options_keys = []
257
- options_keys = ['private_key_file','certificate_file'] if certificate_required?(params, options)
424
+ options_keys = ['private_key_file', 'certificate_file'] if certificate_required?(params, options)
258
425
 
259
426
  params_keys.each do |key|
260
427
  errors << key if params[key.to_sym].nil?
261
428
  end
262
-
429
+
263
430
  options_keys.each do |key|
264
431
  errors << key if options[key.to_sym].nil?
265
432
  end
266
433
  validate_role_size(options[:vm_size])
267
- validate_location(params[:location]) unless errors.include?("location")
434
+ validate_location(params[:location]) unless errors.include?('location')
268
435
  if errors.empty?
269
- params[:certificate]={}
436
+ params[:certificate] = {}
270
437
  if certificate_required?(params, options)
271
438
  begin
272
439
  params[:certificate][:key] = OpenSSL::PKey.read File.read(options[:private_key_file])
273
440
  params[:certificate][:cert] = OpenSSL::X509::Certificate.new File.read(options[:certificate_file])
274
441
  params[:certificate][:fingerprint] = export_fingerprint(params[:certificate][:cert])
275
- rescue Exception =>e
442
+ rescue Exception => e
276
443
  Loggerx.error_with_exit e.message
277
444
  end
278
445
  end
@@ -304,14 +471,13 @@ module Azure
304
471
 
305
472
  def validate_location(location_name)
306
473
  locations = Azure::BaseManagementService.new.list_locations
307
- location = locations.select{|loc| loc.name.downcase == location_name.downcase}.first
474
+ location = locations.select { |loc| loc.name.downcase == location_name.downcase }.first
308
475
  if location.nil?
309
- Loggerx.error_with_exit "Value '#{location_name}' specified for parameter 'location' is invalid. Allowed values are #{locations.collect(&:name).join(',')}"
310
- elsif !location.available_services.include?("PersistentVMRole")
476
+ Loggerx.error_with_exit "Value '#{location_name}' specified for parameter 'location' is invalid. Allowed values are #{locations.map(&:name).join(',')}"
477
+ elsif !location.available_services.include?('PersistentVMRole')
311
478
  Loggerx.error_with_exit "Persistentvmrole not enabled for \"#{location.name}\". Try different location"
312
479
  end
313
480
  end
314
-
315
481
  end
316
482
  end
317
483
  end