cloudconfig 0.1.1 → 0.4.0

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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f9fbb1c3690722f460de0b9f05d5b25089e72217
4
+ data.tar.gz: 76904f5715a14580d938762f661082089b7ed6ce
5
+ SHA512:
6
+ metadata.gz: 2866cd11bbe9b214c0968ac885d460fc64bfae8da8635eb7e33ba281f8dce4c423e33601886bfa1b8b6c08d5a1dfd44a790b1cf6a08f752c48ef720ab600bf2f
7
+ data.tar.gz: da098dbdbf9f71bee3de73262cc8f99fdbf9b12b9d4587226e13fd247235b168da1e4b8de1dd5ab90fc4588670dd6786bc541383797543550843aff22132040a
data/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # Cloudconfig
2
2
 
3
3
  [![Build Status](https://travis-ci.org/klarna/cloudconfig.png?branch=master)](https://travis-ci.org/klarna/cloudconfig)
4
+ [![Gem Version](https://badge.fury.io/rb/cloudconfig.png)](http://badge.fury.io/rb/cloudconfig)
4
5
 
5
6
  Cloudconfig is an application that manages configurations for resources in Cloudstack.
6
7
 
@@ -41,7 +42,8 @@ Cloudconfig will configure resources in Cloudstack according to resources in yam
41
42
  Resources that currently are handled by cloudconfig:
42
43
 
43
44
  - Compute offerings (create, update, delete)
44
- - Disk offerings (update)
45
+ - Disk offerings (create, update, delete)
46
+ - System offerings (create, update, delete)
45
47
  - Host tags (create, delete)
46
48
  - Storage tags (create, delete)
47
49
 
@@ -51,18 +53,26 @@ Resource files are expected to be structured in following manner in configured r
51
53
  ├── diskofferings.yaml
52
54
  ├── hosts.yaml
53
55
  ├── serviceofferings.yaml
54
- └── storages.yaml
56
+ ├── storages.yaml
57
+ └── systemofferings.yaml
55
58
 
56
59
  diskofferings.yaml:
57
60
 
58
61
  DiskOfferings:
59
- 20gb: { tags: "SSD",
60
- displaytext: "20GB SSD Drive",
61
- disksize: 200 }
62
+ 20gb: { tags: "SSD",
63
+ displaytext: "20GB SSD Drive",
64
+ iscustomized: false,
65
+ disksize: 200 }
62
66
 
63
- 50GB: { tags: "SSD",
64
- displaytext: "50GB SSD Drive",
65
- disksize: 50 }
67
+ 50GB: { tags: "SSD",
68
+ displaytext: "50GB SSD Drive",
69
+ iscustomized: false,
70
+ disksize: 50 }
71
+
72
+ Custom: { tags: "",
73
+ displaytext: "Customized disk offering size",
74
+ iscustomized: true,
75
+ disksize: 0 }
66
76
 
67
77
  hosts.yaml:
68
78
 
@@ -93,12 +103,30 @@ storage.yaml:
93
103
  ssd: { tags: "ssd",
94
104
  zonename: "example" }
95
105
 
106
+ systemofferings.yaml:
107
+
108
+ SystemOfferings:
109
+ console-proxy: { displaytext: "1vCPU, 500MHz, 1GB RAM",
110
+ cpunumber: 1,
111
+ cpuspeed: 500,
112
+ memory: 1024,
113
+ storagetype: "shared",
114
+ issystem: true,
115
+ systemvmtype: "consoleproxy" }
116
+ domain-router: { displaytext: "1vCPU, 500MHz, 256MB RAM",
117
+ cpunumber: 1,
118
+ cpuspeed: 500,
119
+ memory: 256,
120
+ storagetype: "shared",
121
+ issystem: true,
122
+ systemvmtype: "domainrouter" }
123
+
96
124
  ### Testing
97
125
 
98
126
  Currently only unit testing:
99
127
 
100
128
  bundle install
101
- bundle exec ruby test/test.rb
129
+ rake
102
130
 
103
131
  ## Contributing
104
132
 
data/Rakefile CHANGED
@@ -1 +1,7 @@
1
1
  require "bundler/gem_tasks"
2
+
3
+ task :unit_tests do
4
+ load "test/test.rb"
5
+ end
6
+
7
+ task :default => :unit_tests
@@ -29,12 +29,14 @@ module Cloudconfig
29
29
  end
30
30
  desc "update RESOURCE", "Updates RESOURCE from configuration yaml file."
31
31
  long_desc <<-LONGDESC
32
- `update serviceofferings` will configure service offerings.
33
- \x5`update hosts` will configure hosts.
34
- \x5`update storages` will configure storage pools.
35
- \x5`update diskofferings` will configure disk offerings.
32
+ `update serviceofferings` will configure service offerings. (X)
33
+ \x5`update hosts` will configure hosts tags.
34
+ \x5`update storages` will configure storage pools tags.
35
+ \x5`update diskofferings` will configure disk offerings. (X)
36
+ \x5`update systemofferings` will configure system offerings. (X)
37
+ \x5`Marked with X` - The resource will be have a new ID number when updated, if there are more than 'displaytext', 'displayoffering' and 'sortkey' that have been given new values.
36
38
  LONGDESC
37
- option :delete, :desc => "delete option for service offerings, resources existing in CloudPlatform but not in configuation yaml file are deleted in CloudPlatform."
39
+ option :delete, :desc => "delete option for service offerings, disk offerings and system offerings - resources existing in CloudPlatform but not in configuation yaml file are deleted in CloudPlatform."
38
40
  option :dryrun, :desc => "dry run option, changes that should be made are listed, but not performed."
39
41
  def update(resource)
40
42
  r = Resources.new(resource)
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
10
10
  spec.email = ["katrin.nilsson@klarna.com", "carl.loa.odin@klarna.com", "olle.lundberg@klarna.com"]
11
11
  spec.description = %q{Cloudconfig is an application that manages configurations for resources in Cloudstack.}
12
12
  spec.summary = %q{Resource configuration manager for Cloudstack.}
13
- spec.homepage = ""
13
+ spec.homepage = "https://rubygems.org/gems/cloudconfig"
14
14
  spec.license = "Apache License, Version 2.0"
15
15
 
16
16
  spec.files = `git ls-files`.split($/)
@@ -19,9 +19,9 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ["lib"]
20
20
 
21
21
  spec.add_development_dependency "bundler", "~> 1.3"
22
- spec.add_development_dependency "rake", "~> 10.3.2"
22
+ spec.add_development_dependency "rake", "~> 10.3", ">= 10.3.2"
23
23
 
24
- spec.add_runtime_dependency "thor", "~> 0.19.1"
25
- spec.add_runtime_dependency "user_config", "~> 0.0.4"
26
- spec.add_runtime_dependency "cloudstack_ruby_client", "~> 1.0.1"
24
+ spec.add_runtime_dependency "thor", "~> 0.19", ">= 0.19.1"
25
+ spec.add_runtime_dependency "user_config", "~> 0.0", ">= 0.0.4"
26
+ spec.add_runtime_dependency "cloudstack_ruby_client", "~> 1.0", ">= 1.0.1"
27
27
  end
@@ -5,170 +5,322 @@ require 'cloudstack_ruby_client'
5
5
  require 'user_config'
6
6
 
7
7
  module Cloudconfig
8
- class Resources
9
-
10
-
11
- attr_accessor :config, :delete, :dryrun, :resource, :client, :config_file
12
-
13
-
14
- def initialize(resource)
15
- @delete = false
16
- @dryrun = false
17
- @resource = resource
18
- uconfig = UserConfig.new('.cloudconfig')
19
- @config_file = uconfig['config.yaml']
20
- end
21
-
22
-
23
- def update()
24
- @client = create_cloudstack_client()
25
- resource_file, resource_cloud = define_yamlfile_and_cloudresource()
26
- r_updated, r_created, r_deleted = check_resource(resource_file, resource_cloud)
27
- feedback = ""
28
- if @dryrun
29
- feedback = "The following actions would be performed with this command:\n"
30
- else
31
- for r in r_updated
32
- r_diff = Hash[(r[0].to_a) - (r[1].to_a)]
33
- r_union = Hash[r[0].to_a | r_diff.to_a]
34
- update_resource(r_union)
35
- end
36
- r_created.each{ |r| create_resource(r) }
37
- r_deleted.each{ |r| delete_resource(r) }
38
- end
39
- for r in r_updated
40
- # r_diff represents the parameters that should be changed in resource
41
- r_diff = Hash[(r[0].to_a) - (r[1].to_a)]
42
- feedback += "Some values has been changed in #{@resource} named #{r[0]["name"]}.\nOld values were:\n#{JSON.pretty_generate(r[1])}\nNew values are:\n#{JSON.pretty_generate(r_diff)}\n"
43
- end
44
- r_created.each{ |r| feedback += "#{@resource} named #{r["name"]} has been created\n" }
45
- r_deleted.each{ |r| feedback += "#{@resource} named #{r["name"]} has been deleted\n" }
46
- puts feedback
47
- end
48
-
49
-
50
- def create_cloudstack_client()
51
- client = CloudstackRubyClient::Client.new(@config_file["url"], @config_file["api_key"], @config_file["secret_key"], true)
52
- return client
53
- end
54
-
55
-
56
- # Save the current list of resources in resource_cloud, and the ones in the yaml file in resource_file
57
- def define_yamlfile_and_cloudresource()
58
- if @resource == "serviceofferings"
59
- resource_title = "ServiceOfferings"
60
- resource_cloud = @client.list_service_offerings()["serviceoffering"]
61
- elsif @resource == "hosts"
62
- resource_title = "Hosts"
63
- resource_cloud = @client.list_hosts()["host"]
64
- elsif @resource == "storages"
65
- resource_title = "Storages"
66
- resource_cloud = @client.list_storage_pools()["storagepool"]
67
- elsif @resource == "diskofferings"
68
- resource_title = "DiskOfferings"
69
- resource_cloud = @client.list_disk_offerings()["diskoffering"]
70
- end
71
- resource_file = YAML.load_file("#{@config_file["resource_directory"]}/#{@resource}.yaml")["#{resource_title}"]
72
- return resource_file, resource_cloud
73
- end
74
-
75
-
76
- # Compare resources in cloud and yaml file
77
- def check_resource(resource_file, resource_cloud)
78
- updated = Array.new
79
- created = Array.new
80
- deleted = Array.new
81
- for r in resource_file
82
- r_total = r[1].merge({"name" => "#{r[0]}"})
83
- found = false
84
- i = 0
85
- # If the resource has not yet been found, compare names and update resource if names match but other parameters don't
86
- while !found && i < resource_cloud.length
87
- if resource_cloud[i]["name"] == r[0]
88
- new_resource = resource_cloud[i].merge(r[1])
89
- if resource_cloud[i] != new_resource
90
- r_total = r_total.merge({"id" => "#{resource_cloud[i]["id"]}"})
91
- # Update resource with new values, in first parameter, and send old values in second parameter
92
- updated.push([r_total, resource_cloud[i]])
93
- end
94
- resource_cloud.delete_at(i)
95
- found = true
96
- else
97
- i += 1
98
- end
99
- end
100
- if (!found) && (@resource == "serviceofferings")
101
- # Create resources. (Only works for service offerings at the moment)
102
- created.push(r_total)
103
- end
104
- end
105
- if delete && (resource_cloud.length > 0) && (@resource == "serviceofferings")
106
- # Remove all resources that are not included in yaml file. (Only works for service offerings at the moment)
107
- for r in resource_cloud
108
- deleted.push(r)
109
- end
110
- end
111
- # Return resources that should be updated, created and deleted
112
- return updated, created, deleted
113
- end
114
-
115
-
116
- def update_resource(res)
117
- if @resource == "serviceofferings"
118
- @client.delete_service_offering({"id" => "#{res["id"]}"})
119
- @client.create_service_offering(res)
120
- elsif @resource == "hosts"
121
- @client.update_host(res)
122
- elsif @resource == "storages"
123
- @client.update_storage_pool(res)
124
- elsif @resource == "diskofferings"
125
- # Parameter iscustomized has different name (customized) when creating resource, and parameter disksize create error if iscustomized is true.
126
- if res["iscustomized"] == true
127
- res.delete("disksize")
128
- end
129
- res = res.merge({"customized" => res["iscustomized"]})
130
- res.delete("iscustomized")
131
- @client.delete_disk_offering({"id" => "#{res["id"]}"})
132
- @client.create_disk_offering(res)
133
- end
134
- end
135
-
136
-
137
- def create_resource(res)
138
- if @resource == "serviceofferings"
139
- @client.create_service_offering(res)
140
- end
141
- end
142
-
143
-
144
- def delete_resource(res)
145
- if @resource == "serviceofferings"
146
- @client.delete_service_offering({"id" => "#{res["id"]}"})
147
- end
148
- end
149
-
150
- def list_resources()
151
- @client = create_cloudstack_client()
152
- resource_file, resource_cloud = define_yamlfile_and_cloudresource()
153
- puts JSON.pretty_generate(resource_cloud)
154
- end
155
-
156
- def compare_resources()
157
- @delete = true
158
- @client = create_cloudstack_client()
159
- resources = ["serviceofferings", "hosts", "storages", "diskofferings"]
160
- for r in resources
161
- @res = r
162
- resource_file, resource_cloud = define_yamlfile_and_cloudresource()
163
- r_updated, r_created, r_deleted = check_resource(resource_file, resource_cloud)
164
- puts "The following #{r} will be updated:"
165
- r_updated.each{ |re| puts "\n#{re[0]["name"]}" }
166
- puts "The following #{r} will be created:"
167
- r_created.each{ |re| puts "\n#{re["name"]}" }
168
- puts "The following #{r} will be deleted:"
169
- r_deleted.each{ |re| puts "\n#{re["name"]}" }
170
- end
171
- end
172
-
173
- end
8
+ class Resources
9
+
10
+
11
+ attr_accessor :config, :delete, :dryrun, :resource, :client, :config_file
12
+
13
+
14
+ def initialize(resource)
15
+ @delete = false
16
+ @dryrun = false
17
+ @resource = resource
18
+ uconfig = UserConfig.new('.cloudconfig')
19
+ @config_file = uconfig['config.yaml']
20
+ end
21
+
22
+
23
+ def update()
24
+ @client = create_cloudstack_client()
25
+ begin
26
+ resource_file, resource_cloud = define_yamlfile_and_cloudresource()
27
+ rescue Exception => error_msg
28
+ raise error_msg, "Resource could not be loaded from configuration file and/or the cloud."
29
+ end
30
+ r_updated, r_created, r_deleted = check_resource(resource_file, resource_cloud)
31
+ if @dryrun
32
+ puts "The following actions would be performed with this command:"
33
+ end
34
+ for updated in r_updated
35
+ begin
36
+ updated_resource, only_updated = update_resource(updated)
37
+ if !updated_resource.empty?
38
+ if only_updated
39
+ puts "Some values have been changed in the #{@resource} named #{updated_resource[0]}.\nOld values were:\n#{JSON.pretty_generate(updated_resource[1])}\nNew values are:\n#{JSON.pretty_generate(updated_resource[2])}"
40
+ else
41
+ puts "The #{@resource} named #{updated_resource[0]} has been recreated.\nOld values were:\n#{JSON.pretty_generate(updated_resource[1])}\nNew values are:\n#{JSON.pretty_generate(updated_resource[2])}"
42
+ end
43
+ end
44
+ rescue CreationError => error_msg
45
+ puts "#{updated[0]["name"]} could not be updated since: #{error_msg}"
46
+ end
47
+ end
48
+ for created in r_created
49
+ begin
50
+ created_resource = create_resource(created)
51
+ if !created_resource.empty?
52
+ puts "The #{@resource} named #{created_resource[0]} has been created"
53
+ end
54
+ rescue CreationError => error_msg
55
+ puts "#{created["name"]} could not be created since: #{error_msg}"
56
+ end
57
+ end
58
+ for deleted in r_deleted
59
+ deleted_resource = delete_resource(deleted)
60
+ if !deleted_resource.empty?
61
+ puts "The #{@resource} named #{deleted_resource[0]} has been deleted"
62
+ end
63
+ end
64
+ end
65
+
66
+
67
+ def create_cloudstack_client()
68
+ client = CloudstackRubyClient::Client.new(@config_file["url"], @config_file["api_key"], @config_file["secret_key"], true)
69
+ return client
70
+ end
71
+
72
+
73
+ # Save the current list of resources in resource_cloud, and the ones in the yaml file in resource_file
74
+ def define_yamlfile_and_cloudresource()
75
+ if @resource == "serviceofferings"
76
+ resource_title = "ServiceOfferings"
77
+ resource_cloud = @client.list_service_offerings()["serviceoffering"]
78
+ elsif @resource == "hosts"
79
+ resource_title = "Hosts"
80
+ resource_cloud = @client.list_hosts()["host"]
81
+ elsif @resource == "storages"
82
+ resource_title = "Storages"
83
+ resource_cloud = @client.list_storage_pools()["storagepool"]
84
+ elsif @resource == "diskofferings"
85
+ resource_title = "DiskOfferings"
86
+ resource_cloud = @client.list_disk_offerings()["diskoffering"]
87
+ elsif @resource == "systemofferings"
88
+ resource_title = "SystemOfferings"
89
+ resource_cloud = @client.list_service_offerings({"issystem" => true})["serviceoffering"]
90
+ end
91
+ resource_file = YAML.load_file("#{@config_file["resource_directory"]}/#{@resource}.yaml")["#{resource_title}"]
92
+ return resource_file, resource_cloud
93
+ end
94
+
95
+
96
+ # Compare resources in cloud and yaml file
97
+ def check_resource(resource_file, resource_cloud)
98
+ updated = Array.new
99
+ created = Array.new
100
+ deleted = Array.new
101
+ for r in resource_file
102
+ r_total = r[1].merge({"name" => "#{r[0]}"})
103
+ found = false
104
+ i = 0
105
+ # If the resource has not yet been found, compare names and update resource if names match but other parameters don't
106
+ while !found && i < resource_cloud.length
107
+ if resource_cloud[i]["name"] == r[0]
108
+ new_resource = resource_cloud[i].merge(r[1])
109
+ if resource_cloud[i] != new_resource
110
+ r_total = r_total.merge({"id" => "#{resource_cloud[i]["id"]}"})
111
+ # Update resource with new values, in first parameter, and send old values in second parameter
112
+ updated.push([r_total, resource_cloud[i]])
113
+ end
114
+ resource_cloud.delete_at(i)
115
+ found = true
116
+ else
117
+ i += 1
118
+ end
119
+ end
120
+ if (!found) && ((@resource == "serviceofferings") || (@resource == "diskofferings") || (@resource == "systemofferings"))
121
+ # Create resources
122
+ created.push(r_total)
123
+ end
124
+ end
125
+ if delete && (resource_cloud.length > 0) && ((@resource == "serviceofferings") || (@resource == "diskofferings") || (@resource == "systemofferings"))
126
+ # Remove all resources that are not included in yaml file. (Only works for service offerings at the moment)
127
+ for r in resource_cloud
128
+ deleted.push(r)
129
+ end
130
+ end
131
+ # Return resources that should be updated, created and deleted
132
+ return updated, created, deleted
133
+ end
134
+
135
+
136
+ def update_resource(res)
137
+ updated = true
138
+ updated_resource = Array.new
139
+ # 'res_union' contains the the parameters that the new, updated or recreated, resource should contain
140
+ res_union = Hash[res[1].to_a | res[0].to_a]
141
+ # 'res_diff' contains the parameters that differs from the existing resource, and represents what will be added/changed
142
+ res_diff, needs_recreation = required_changes(res)
143
+ if !res_diff.empty?
144
+ # All these resources could need recreation and are controlled, to see if all nedded requirements are met.
145
+ if (@resource == "serviceofferings") || (@resource == "systemofferings") || (@resource == "diskofferings")
146
+ if needs_recreation
147
+ updated = false
148
+ if has_recreated_resource?(res_union)
149
+ updated_resource.push(res[0]["name"], res[1], res_diff)
150
+ end
151
+ else
152
+ # Only an update is nedded
153
+ if (@resource == "serviceofferings") || (@resource == "systemofferings")
154
+ if !@dryrun
155
+ @client.update_service_offering(res_union)
156
+ end
157
+ elsif @resource == "diskofferings"
158
+ if !@dryrun
159
+ @client.update_disk_offering(res_union)
160
+ end
161
+ else
162
+ updated = false
163
+ end
164
+ end
165
+ # All these resources can only be updated.
166
+ elsif (@resource == "hosts") || (@resource == "storages")
167
+ if @resource == "hosts"
168
+ if !@dryrun
169
+ @client.update_host(res_union)
170
+ end
171
+ elsif @resource == "storages"
172
+ if !@dryrun
173
+ @client.update_storage_pool(res_union)
174
+ end
175
+ else
176
+ updated = false
177
+ end
178
+ end
179
+ else
180
+ updated = false
181
+ end
182
+ if updated
183
+ updated_resource.push(res[0]["name"], res[1], res_diff)
184
+ end
185
+ return updated_resource, updated
186
+ end
187
+
188
+
189
+ def required_changes(res)
190
+ # Find the parameters that differs
191
+ res_diff = Hash[(res[0].to_a) - (res[1].to_a)]
192
+ res_diff.delete_if { |param| param == "id" }
193
+ changes = res_diff.clone
194
+ # If the parameters that differs only contain the following keys, then the resource only needs an update. Otherwise, a recreation is required.
195
+ only_update_parameters = ["displaytext", "sortkey", "displayoffering"]
196
+ only_update_parameters.each { |searched_param| changes.delete_if { |actual_param| actual_param == searched_param } }
197
+ # Remove all changes that aren't actually changes, to avoid unnecessary updates
198
+ for param, value in changes
199
+ if (!res[1].has_key?(param) || (res[1][param] == "")) && (value == "")
200
+ changes.delete(param)
201
+ res_diff.delete(param)
202
+ end
203
+ end
204
+ needs_recreation = false
205
+ if !changes.empty?
206
+ needs_recreation = true
207
+ end
208
+ return res_diff, needs_recreation
209
+ end
210
+
211
+
212
+ def has_recreated_resource?(res)
213
+ # The resource should only be deleted if it can be recreated, so a dryrun check needs to be made to assure this
214
+ actual_dryrun = @dryrun
215
+ @dryrun = true
216
+ created = create_resource(res)
217
+ if !created.empty?
218
+ # No errors occured, and an actual run can be made
219
+ @dryrun = actual_dryrun
220
+ deleted = delete_resource(res)
221
+ created = create_resource(res)
222
+ return true
223
+ end
224
+ @dryrun = actual_dryrun
225
+ return false
226
+ end
227
+
228
+
229
+ def create_resource(res)
230
+ check_for_creation_errors(res)
231
+ created = Array.new
232
+ created.push(res["name"])
233
+ if (@resource == "serviceofferings") || (@resource == "systemofferings")
234
+ if @resource == "systemofferings"
235
+ # All system offerings needs to include this
236
+ res["issystem"] = true
237
+ end
238
+ if !@dryrun
239
+ @client.create_service_offering(res)
240
+ end
241
+ elsif (@resource == "diskofferings")
242
+ # Parameter 'iscustomized' has different name ('customized') when creating resource, and parameter disksize create error if iscustomized is true
243
+ if res.has_key?("iscustomized") && res["iscustomized"] == true
244
+ res.delete("disksize")
245
+ end
246
+ res = res.merge({"customized" => res["iscustomized"]})
247
+ res.delete("iscustomized")
248
+ if !@dryrun
249
+ @client.create_disk_offering(res)
250
+ end
251
+ end
252
+ created
253
+ end
254
+
255
+
256
+ def delete_resource(res)
257
+ deleted = Array.new
258
+ if (@resource == "serviceofferings") || (@resource == "systemofferings")
259
+ deleted.push(res["name"])
260
+ if !@dryrun
261
+ @client.delete_service_offering({"id" => "#{res["id"]}"})
262
+ end
263
+ elsif (@resource == "diskofferings")
264
+ deleted.push(res["name"])
265
+ if !@dryrun
266
+ @client.delete_disk_offering({"id" => "#{res["id"]}"})
267
+ end
268
+ end
269
+ deleted
270
+ end
271
+
272
+
273
+ def check_for_creation_errors(res)
274
+ errors = Array.new
275
+ if !res.has_key?("displaytext")
276
+ errors.push("'displaytext' has not been specified in configuration file.")
277
+ end
278
+ if @resource == "diskofferings"
279
+ if (!res.has_key?("iscustomized") || (res["iscustomized"] == false)) && (!res.has_key?("disksize") || (res["disksize"] == 0))
280
+ errors.push("'iscustomized' is unspecified or set to false and 'disksize' has not been specified, or has been specified to a value of 0 or below.")
281
+ end
282
+ elsif (@resource == "serviceofferings") || (@resource == "systemofferings")
283
+ if !res.has_key?("cpunumber") || !res.has_key?("cpuspeed") || !res.has_key?("memory") || (res["cpunumber"] <= 0) || (res["cpuspeed"] <= 0) || (res["memory"] <= 0)
284
+ errors.push("'cpunumber', 'cpuspeed' and/or 'memory' has not been defined, or is defined as 0 or below.")
285
+ end
286
+ if @resource == "systemofferings"
287
+ approved_systemvmtype = ["domainrouter", "consoleproxy", "secondarystoragevm"]
288
+ if !res.has_key?("systemvmtype") || !approved_systemvmtype.include?(res["systemvmtype"])
289
+ errors.push("'systemvmtype' is unspecified or set to a value not valid in Cloudconfig.")
290
+ end
291
+ end
292
+ end
293
+ if !errors.empty?
294
+ raise CreationError, errors
295
+ end
296
+ end
297
+
298
+
299
+ def list_resources()
300
+ @client = create_cloudstack_client()
301
+ resource_file, resource_cloud = define_yamlfile_and_cloudresource()
302
+ puts JSON.pretty_generate(resource_cloud)
303
+ end
304
+
305
+ def compare_resources()
306
+ @delete = true
307
+ @client = create_cloudstack_client()
308
+ resources = ["serviceofferings", "hosts", "storages", "diskofferings", "systemofferings"]
309
+ for r in resources
310
+ @res = r
311
+ resource_file, resource_cloud = define_yamlfile_and_cloudresource()
312
+ r_updated, r_created, r_deleted = check_resource(resource_file, resource_cloud)
313
+ puts "The following #{r} will be updated:"
314
+ r_updated.each{ |re| puts "\n#{re[0]["name"]}" }
315
+ puts "The following #{r} will be created:"
316
+ r_created.each{ |re| puts "\n#{re["name"]}" }
317
+ puts "The following #{r} will be deleted:"
318
+ r_deleted.each{ |re| puts "\n#{re["name"]}" }
319
+ end
320
+ end
321
+
322
+ end
323
+
324
+ class CreationError < Exception
325
+ end
174
326
  end
@@ -1,3 +1,3 @@
1
1
  module Cloudconfig
2
- VERSION = "0.1.1"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -4,15 +4,30 @@ class TestHelper
4
4
  resource_cloud = [
5
5
  { "name" => "Resource-01",
6
6
  "id" => 111,
7
- "tag" => "RES"
7
+ "displaytext" => "Description A",
8
+ "tag" => "RES",
9
+ "cpunumber" => 1,
10
+ "cpuspeed" => 1,
11
+ "memory" => 1,
12
+ "systemvmtype" => "domainrouter",
13
+ "iscustomized" => false,
14
+ "disksize" => 1
8
15
  },
9
16
  { "name" => "Resource-02",
10
17
  "id" => 222,
11
- "tag" => "RES"
18
+ "displaytext" => "Description B",
19
+ "tag" => "RES",
20
+ "cpunumber" => 1,
21
+ "cpuspeed" => 1,
22
+ "memory" => 1,
23
+ "systemvmtype" => "domainrouter",
24
+ "iscustomized" => false,
25
+ "disksize" => 1
12
26
  },
13
27
  { "name" => "Resource-03",
14
28
  "id" => 333,
15
- "tag" => "RAN"
29
+ "displaytext" => "Description C",
30
+ "tag" => "RAN",
16
31
  }
17
32
  ]
18
33
  return resource_cloud.to_a
@@ -20,17 +35,78 @@ class TestHelper
20
35
 
21
36
  def get_resource_file()
22
37
  resource_file = {
38
+ # Displaytext has changed in Resource-01
23
39
  "Resource-01" => {
24
- "id" => 111,
25
- "tag" => "RES"
40
+ "displaytext" => "A different description",
41
+ "tag" => "RES",
42
+ "cpunumber" => 1,
43
+ "cpuspeed" => 1,
44
+ "memory" => 1,
45
+ "systemvmtype" => "domainrouter",
46
+ "iscustomized" => false,
47
+ "disksize" => 1
26
48
  },
49
+ # Tags has changed in Resource-02
27
50
  "Resource-02" => {
28
- "id" => 222,
29
- "tag" => "RAN"
51
+ "displaytext" => "Description B",
52
+ "tag" => "RAN",
53
+ "cpunumber" => 1,
54
+ "cpuspeed" => 1,
55
+ "memory" => 1,
56
+ "systemvmtype" => "domainrouter",
57
+ "iscustomized" => false,
58
+ "disksize" => 1
30
59
  },
60
+ # Resource-03 has been deleted
61
+ # The resources below should be up for creation
62
+ # serviceofferings, systemofferings and diskofferings: Resource-04 should be created
31
63
  "Resource-04" => {
32
- "id" => 444,
33
- "tag" => "RAN"
64
+ "displaytext" => "Description D",
65
+ "cpunumber" => 1,
66
+ "cpuspeed" => 1,
67
+ "memory" => 1,
68
+ "systemvmtype" => "domainrouter",
69
+ "iscustomized" => false,
70
+ "disksize" => 1
71
+ },
72
+ # serviceofferings, systemofferings and diskofferings: Resouce-05 does not have 'displaytext' and should not be created
73
+ "Resource-05" => {
74
+ "cpunumber" => 1,
75
+ "cpuspeed" => 1,
76
+ "memory" => 1,
77
+ "systemvmtype" => "consoleproxy",
78
+ "iscustomized" => true
79
+ },
80
+ # serviceofferings and systemofferings: Resource-06 doas not have 'cpunumber' and should not be created
81
+ # diskofferings: Resource-06 should be created
82
+ "Resource-06" => {
83
+ "displaytext" => "Description F",
84
+ "cpuspeed" => 1,
85
+ "memory" => 1,
86
+ "systemvmtype" => "secondarystoragevm",
87
+ "iscustomized" => true
88
+ },
89
+ # serviceofferings and systemofferings: Resource-07 does not have valid 'cpuspeed' value and can not be created
90
+ # diskofferings: Resource-07 has 'iscustomized' = false but does not have 'disksize' and should not be created
91
+ "Resource-07" => {
92
+ "displaytext" => "Description G",
93
+ "cpunumber" => 1,
94
+ "cpuspeed" => 0,
95
+ "memory" => 1,
96
+ "systemvmtype" => "domainrouter",
97
+ "iscustomized" => false
98
+ },
99
+ # serviceofferings: Resource-08 should be created
100
+ # systemofferings: Resource-08 does not have a valid 'systemvmtype' and can not be created
101
+ # diskofferings: Resource-08 does not have a valid 'disksize'i and should not be created
102
+ "Resource-08" => {
103
+ "displaytext" => "Description H",
104
+ "cpunumber" => 1,
105
+ "cpuspeed" => 1,
106
+ "memory" => 1,
107
+ "systemvmtype" => "invalidsystemvmtype",
108
+ "iscustomized" => false,
109
+ "disksize" => 0
34
110
  }
35
111
  }
36
112
  return resource_file
@@ -11,7 +11,8 @@ class TestResources < Test::Unit::TestCase
11
11
  @res = [Cloudconfig::Resources.new("serviceofferings"),
12
12
  Cloudconfig::Resources.new("hosts"),
13
13
  Cloudconfig::Resources.new("storages"),
14
- Cloudconfig::Resources.new("diskofferings")]
14
+ Cloudconfig::Resources.new("diskofferings"),
15
+ Cloudconfig::Resources.new("systemofferings")]
15
16
 
16
17
  for r in @res
17
18
  r.delete = false
@@ -24,81 +25,152 @@ class TestResources < Test::Unit::TestCase
24
25
 
25
26
  def test_update_serviceofferings_delete_is_false
26
27
  # Test array and hash in test_helper.rb, with delete option set to false in setup
27
- upd, cre, del = @res[0].check_resource(@r_test_file, @r_test_cloud)
28
- assert_equal(1, upd.length)
29
- assert_equal("Resource-02", "#{upd[0][0]["name"]}")
30
- assert_equal(1, cre.length)
31
- assert_equal("Resource-04", "#{cre[0]["name"]}")
32
- assert_equal(0, del.length)
28
+ resource = @res[0]
29
+ upd, cre, del = resource.check_resource(@r_test_file, @r_test_cloud)
30
+ control_updates(upd, resource)
31
+ control_creates(cre, resource)
32
+ control_deletes(del, resource)
33
33
  end
34
34
 
35
35
  # Same test, with delete set to true
36
36
  def test_update_serviceofferings_delete_is_true
37
- @res[0].delete = true
38
- upd, cre, del = @res[0].check_resource(@r_test_file, @r_test_cloud)
39
- assert_equal(1, upd.length)
40
- assert_equal("Resource-02", "#{upd[0][0]["name"]}")
41
- assert_equal(1, cre.length)
42
- assert_equal("Resource-04", "#{cre[0]["name"]}")
43
- assert_equal(1, del.length)
44
- assert_equal("Resource-03", "#{del[0]["name"]}")
37
+ resource = @res[0]
38
+ resource.delete = true
39
+ upd, cre, del = resource.check_resource(@r_test_file, @r_test_cloud)
40
+ control_updates(upd, resource)
41
+ control_creates(cre, resource)
42
+ control_deletes(del, resource)
43
+ end
44
+
45
+ def test_update_systemofferings_delete_is_false
46
+ resource = @res[4]
47
+ upd, cre, del = resource.check_resource(@r_test_file, @r_test_cloud)
48
+ control_updates(upd, resource)
49
+ control_creates(cre, resource)
50
+ control_deletes(del, resource)
51
+ end
52
+
53
+ def test_update_systemofferings_delete_is_true
54
+ resource = @res[4]
55
+ resource.delete = true
56
+ upd, cre, del = resource.check_resource(@r_test_file, @r_test_cloud)
57
+ control_updates(upd, resource)
58
+ control_creates(cre, resource)
59
+ control_deletes(del, resource)
45
60
  end
46
61
 
47
62
  def test_update_hosts_delete_is_false
48
- # Test array and hash in test_helper.rb, with delete option set to false in setup
49
- upd, cre, del = @res[1].check_resource(@r_test_file, @r_test_cloud)
50
- assert_equal(1, upd.length)
51
- assert_equal("Resource-02", "#{upd[0][0]["name"]}")
52
- assert_equal(0, cre.length)
53
- assert_equal(0, del.length)
63
+ resource = @res[1]
64
+ upd, cre, del = resource.check_resource(@r_test_file, @r_test_cloud)
65
+ control_updates(upd, resource)
66
+ control_creates(cre, resource)
67
+ control_deletes(del, resource)
54
68
  end
55
69
 
56
- # Same test, with delete set to true
57
70
  def test_update_hosts_delete_is_true
58
- @res[1].delete = true
59
- upd, cre, del = @res[1].check_resource(@r_test_file, @r_test_cloud)
60
- assert_equal(1, upd.length)
61
- assert_equal("Resource-02", "#{upd[0][0]["name"]}")
62
- assert_equal(0, cre.length)
63
- assert_equal(0, del.length)
71
+ resource = @res[1]
72
+ resource.delete = true
73
+ upd, cre, del = resource.check_resource(@r_test_file, @r_test_cloud)
74
+ control_updates(upd, resource)
75
+ control_creates(cre, resource)
76
+ control_deletes(del, resource)
64
77
  end
65
78
 
66
79
  def test_update_storages_delete_is_false
67
- # Test array and hash in test_helper.rb, with delete option set to false in setup
68
- upd, cre, del = @res[2].check_resource(@r_test_file, @r_test_cloud)
69
- assert_equal(1, upd.length)
70
- assert_equal("Resource-02", "#{upd[0][0]["name"]}")
71
- assert_equal(0, cre.length)
72
- assert_equal(0, del.length)
80
+ resource = @res[2]
81
+ upd, cre, del = resource.check_resource(@r_test_file, @r_test_cloud)
82
+ control_updates(upd, resource)
83
+ control_creates(cre, resource)
84
+ control_deletes(del, resource)
73
85
  end
74
86
 
75
- # Same test, with delete set to true
76
87
  def test_update_storages_delete_is_true
77
- @res[2].delete = true
78
- upd, cre, del = @res[2].check_resource(@r_test_file, @r_test_cloud)
79
- assert_equal(1, upd.length)
80
- assert_equal("Resource-02", "#{upd[0][0]["name"]}")
81
- assert_equal(0, cre.length)
82
- assert_equal(0, del.length)
88
+ resource = @res[2]
89
+ resource.delete = true
90
+ upd, cre, del = resource.check_resource(@r_test_file, @r_test_cloud)
91
+ control_updates(upd, resource)
92
+ control_creates(cre, resource)
93
+ control_deletes(del, resource)
83
94
  end
84
95
 
85
96
  def test_update_diskofferings_delete_is_false
86
- # Test array and hash in test_helper.rb, with delete option set to false in setup
87
- upd, cre, del = @res[3].check_resource(@r_test_file, @r_test_cloud)
88
- assert_equal(1, upd.length)
89
- assert_equal("Resource-02", "#{upd[0][0]["name"]}")
90
- assert_equal(0, cre.length)
91
- assert_equal(0, del.length)
97
+ resource = @res[3]
98
+ upd, cre, del = resource.check_resource(@r_test_file, @r_test_cloud)
99
+ control_updates(upd, resource)
100
+ control_creates(cre, resource)
101
+ control_deletes(del, resource)
92
102
  end
93
103
 
94
- # Same test, with delete set to true
95
104
  def test_update_diskofferings_delete_is_true
96
- @res[3].delete = true
97
- upd, cre, del = @res[3].check_resource(@r_test_file, @r_test_cloud)
98
- assert_equal(1, upd.length)
99
- assert_equal("Resource-02", "#{upd[0][0]["name"]}")
100
- assert_equal(0, cre.length)
101
- assert_equal(0, del.length)
105
+ resource = @res[3]
106
+ resource.delete = true
107
+ upd, cre, del = resource.check_resource(@r_test_file, @r_test_cloud)
108
+ control_updates(upd, resource)
109
+ control_creates(cre, resource)
110
+ control_deletes(del, resource)
111
+ end
112
+
113
+ def control_updates(updated_resources, resource)
114
+ assert_equal(2, updated_resources.length, "Two resources should be checked for updation")
115
+ updated_resources.each { |r| assert(["Resource-01", "Resource-02"].include?(r[0]["name"])) }
116
+ recreatable = false
117
+ # if resource is serviceofferings, diskofferings or systemofferings
118
+ if (resource == @res[0]) || (resource == @res[3]) || (resource == @res[4])
119
+ recreatable = true
120
+ end
121
+ for updated in updated_resources
122
+ r, only_updated = resource.update_resource(updated)
123
+ assert(!r.empty?, "A call to the update_resource method did not return an updated #{resource}")
124
+ if recreatable
125
+ if r[0] == "Resource-01"
126
+ assert(only_updated, "#{r[0]} should only be updated")
127
+ elsif r[0] == "Resource-02"
128
+ assert(!only_updated, "#{r[0]} should be recreated")
129
+ else
130
+ assert(false, "#{r[0]} should not be updated")
131
+ end
132
+ else
133
+ assert(only_updated, "#{resource} should only be updated")
134
+ end
135
+ end
136
+ end
137
+
138
+ def control_creates(created_resources, resource)
139
+ if (resource == @res[0]) || (resource == @res[3]) || (resource == @res[4])
140
+ assert_equal(5, created_resources.length, "Five resources should be checked for creation")
141
+ created_resources.each { |r| assert(["Resource-04", "Resource-05", "Resource-06", "Resource-07", "Resource-08"].include?(r["name"])) }
142
+ should_be_created = Array.new
143
+ # if resource is serviceofferings, systemofferings or diskofferings
144
+ should_be_created.push("Resource-04")
145
+ # if resource is serviceofferings
146
+ if resource == @res[0]
147
+ should_be_created.push("Resource-08")
148
+ # if resource is diskofferings
149
+ elsif resource == @res[3]
150
+ should_be_created.push("Resource-06")
151
+ end
152
+ for created in created_resources
153
+ if should_be_created.include?(created["name"])
154
+ r = resource.create_resource(created)
155
+ assert_equal(1, r.length, "#{r[0]} should be created without any problems")
156
+ else
157
+ assert_raise Cloudconfig::CreationError do
158
+ r = resource.create_resource(created)
159
+ end
160
+ end
161
+ end
162
+ else
163
+ assert(created_resources.empty?, "No #{resource} should be created, since the resource is not creatable")
164
+ end
165
+ end
166
+
167
+ def control_deletes(deleted_resources, resource)
168
+ if resource.delete && ((resource == @res[0]) || (resource == @res[3]) || (resource == @res[4]))
169
+ assert_equal(1, deleted_resources.length, "One #{resource} should be deleted, since the resource is deletable and 'delete' is #{resource.delete}")
170
+ assert_equal("Resource-03", "#{deleted_resources[0]["name"]}")
171
+ else
172
+ assert(deleted_resources.empty?, "No #{resource} should be deleted, since the resource is not deletable")
173
+ end
102
174
  end
103
175
 
104
176
  end
metadata CHANGED
@@ -1,8 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cloudconfig
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
5
- prerelease:
4
+ version: 0.4.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - Katrin Nilsson
@@ -11,63 +10,102 @@ authors:
11
10
  autorequire:
12
11
  bindir: bin
13
12
  cert_chain: []
14
- date: 2014-06-25 00:00:00.000000000 Z
13
+ date: 2014-08-15 00:00:00.000000000 Z
15
14
  dependencies:
16
15
  - !ruby/object:Gem::Dependency
17
16
  name: bundler
18
- requirement: &17262780 !ruby/object:Gem::Requirement
19
- none: false
17
+ requirement: !ruby/object:Gem::Requirement
20
18
  requirements:
21
19
  - - ~>
22
20
  - !ruby/object:Gem::Version
23
21
  version: '1.3'
24
22
  type: :development
25
23
  prerelease: false
26
- version_requirements: *17262780
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ version: '1.3'
27
29
  - !ruby/object:Gem::Dependency
28
30
  name: rake
29
- requirement: &17261260 !ruby/object:Gem::Requirement
30
- none: false
31
+ requirement: !ruby/object:Gem::Requirement
31
32
  requirements:
32
33
  - - ~>
34
+ - !ruby/object:Gem::Version
35
+ version: '10.3'
36
+ - - ! '>='
33
37
  - !ruby/object:Gem::Version
34
38
  version: 10.3.2
35
39
  type: :development
36
40
  prerelease: false
37
- version_requirements: *17261260
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '10.3'
46
+ - - ! '>='
47
+ - !ruby/object:Gem::Version
48
+ version: 10.3.2
38
49
  - !ruby/object:Gem::Dependency
39
50
  name: thor
40
- requirement: &17260780 !ruby/object:Gem::Requirement
41
- none: false
51
+ requirement: !ruby/object:Gem::Requirement
42
52
  requirements:
43
53
  - - ~>
54
+ - !ruby/object:Gem::Version
55
+ version: '0.19'
56
+ - - ! '>='
44
57
  - !ruby/object:Gem::Version
45
58
  version: 0.19.1
46
59
  type: :runtime
47
60
  prerelease: false
48
- version_requirements: *17260780
61
+ version_requirements: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ~>
64
+ - !ruby/object:Gem::Version
65
+ version: '0.19'
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: 0.19.1
49
69
  - !ruby/object:Gem::Dependency
50
70
  name: user_config
51
- requirement: &17260320 !ruby/object:Gem::Requirement
52
- none: false
71
+ requirement: !ruby/object:Gem::Requirement
53
72
  requirements:
54
73
  - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: '0.0'
76
+ - - ! '>='
55
77
  - !ruby/object:Gem::Version
56
78
  version: 0.0.4
57
79
  type: :runtime
58
80
  prerelease: false
59
- version_requirements: *17260320
81
+ version_requirements: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: '0.0'
86
+ - - ! '>='
87
+ - !ruby/object:Gem::Version
88
+ version: 0.0.4
60
89
  - !ruby/object:Gem::Dependency
61
90
  name: cloudstack_ruby_client
62
- requirement: &17259860 !ruby/object:Gem::Requirement
63
- none: false
91
+ requirement: !ruby/object:Gem::Requirement
64
92
  requirements:
65
93
  - - ~>
94
+ - !ruby/object:Gem::Version
95
+ version: '1.0'
96
+ - - ! '>='
66
97
  - !ruby/object:Gem::Version
67
98
  version: 1.0.1
68
99
  type: :runtime
69
100
  prerelease: false
70
- version_requirements: *17259860
101
+ version_requirements: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ~>
104
+ - !ruby/object:Gem::Version
105
+ version: '1.0'
106
+ - - ! '>='
107
+ - !ruby/object:Gem::Version
108
+ version: 1.0.1
71
109
  description: Cloudconfig is an application that manages configurations for resources
72
110
  in Cloudstack.
73
111
  email:
@@ -91,30 +129,29 @@ files:
91
129
  - lib/cloudconfig/version.rb
92
130
  - test/helper/test_helper.rb
93
131
  - test/test.rb
94
- homepage: ''
132
+ homepage: https://rubygems.org/gems/cloudconfig
95
133
  licenses:
96
134
  - Apache License, Version 2.0
135
+ metadata: {}
97
136
  post_install_message:
98
137
  rdoc_options: []
99
138
  require_paths:
100
139
  - lib
101
140
  required_ruby_version: !ruby/object:Gem::Requirement
102
- none: false
103
141
  requirements:
104
142
  - - ! '>='
105
143
  - !ruby/object:Gem::Version
106
144
  version: '0'
107
145
  required_rubygems_version: !ruby/object:Gem::Requirement
108
- none: false
109
146
  requirements:
110
147
  - - ! '>='
111
148
  - !ruby/object:Gem::Version
112
149
  version: '0'
113
150
  requirements: []
114
151
  rubyforge_project:
115
- rubygems_version: 1.8.11
152
+ rubygems_version: 2.3.0
116
153
  signing_key:
117
- specification_version: 3
154
+ specification_version: 4
118
155
  summary: Resource configuration manager for Cloudstack.
119
156
  test_files:
120
157
  - test/helper/test_helper.rb