cloudconfig 0.1.1 → 0.4.0

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