gogetit 0.1.16

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,44 @@
1
+ require 'thor'
2
+ require 'gogetit'
3
+
4
+ module Gogetit
5
+ class CLI < Thor
6
+ package_name 'Gogetit'
7
+
8
+ desc 'list', 'List containers and instances, running currently.'
9
+ def list
10
+ puts "Listing LXD containers on #{Gogetit.config[:lxd][:url]}.."
11
+ system("lxc list #{Gogetit.config[:lxd][:name]}:")
12
+ puts ''
13
+ puts "Listing KVM domains on #{Gogetit.config[:libvirt][:url]}.."
14
+ system("virsh -c #{Gogetit.config[:libvirt][:url]} list --all")
15
+ end
16
+
17
+ desc 'create (TYPE) NAME', 'Create either a container or KVM domain.'
18
+ def create(type=nil, name)
19
+ case type
20
+ when 'lxd', nil
21
+ Gogetit.lxd.create(name)
22
+ when 'libvirt'
23
+ Gogetit.libvirt.create(name)
24
+ else
25
+ puts 'Invalid argument entered'
26
+ end
27
+ Gogetit.config[:default][:user] ||= ENV['USER']
28
+ puts "ssh #{Gogetit.config[:default][:user]}@#{name}"
29
+ end
30
+
31
+ desc 'destroy NAME', 'Destroy either a container or KVM domain.'
32
+ def destroy(name)
33
+ type = Gogetit.get_provider_of(name)
34
+ if type
35
+ case type
36
+ when 'lxd', nil
37
+ Gogetit.lxd.destroy(name)
38
+ when 'libvirt'
39
+ Gogetit.libvirt.destroy(name)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,3 @@
1
+ module Gogetit
2
+ VERSION = "0.1.16"
3
+ end
@@ -0,0 +1,119 @@
1
+ require 'maas/client'
2
+
3
+ module Gogetit
4
+ class GogetMAAS
5
+ attr_reader :config, :conn, :domain, :logger
6
+
7
+ def initialize(conf, logger)
8
+ @config = conf
9
+ @conn = Maas::Client::MaasClient.new(
10
+ config[:maas][:key],
11
+ config[:maas][:url]
12
+ )
13
+ @logger = logger
14
+ end
15
+
16
+ def get_domain
17
+ return @domain if @domain
18
+ logger.info("Calling <#{__method__.to_s}>")
19
+ @domain = conn.request(:get, ['domains'])[0]['name']
20
+ end
21
+
22
+ def machine_exists?(name)
23
+ logger.info("Calling <#{__method__.to_s}>")
24
+ conn.request(:get, ['machines']).each do |m|
25
+ return true if m['hostname'] == name
26
+ end
27
+ false
28
+ end
29
+
30
+ def dnsresource_exists?(name)
31
+ logger.info("Calling <#{__method__.to_s}>")
32
+ conn.request(:get, ['dnsresources']).each do |item|
33
+ return true if item['fqdn'] == name + '.' + get_domain
34
+ end
35
+ false
36
+ end
37
+
38
+ def domain_name_exists?(name)
39
+ return true if dnsresource_exists?(name) or machine_exists?(name)
40
+ end
41
+
42
+ def ipaddresses(op = nil, params = nil)
43
+ case op
44
+ when nil
45
+ conn.request(:get, ['ipaddresses'])
46
+ when 'reserve'
47
+ # sample = {
48
+ # 'subnet' => '10.1.2.0/24',
49
+ # 'ip' => '10.1.2.8',
50
+ # 'hostname' => 'hostname',
51
+ # 'mac' => 'blahblah'
52
+ # }
53
+ default_param = { 'op' => op }
54
+ conn.request(:post, ['ipaddresses'], default_param.merge!(params))
55
+ when 'release'
56
+ # Gogetit.maas.ipaddresses('release', {'ip' => '10.1.2.8'})
57
+ # sample = {
58
+ # 'ip' => '10.1.2.8',
59
+ # 'hostname' => 'hostname',
60
+ # 'mac' => 'blahblah'
61
+ # }
62
+ default_param = { 'op' => op }
63
+ conn.request(:post, ['ipaddresses'], default_param.merge!(params))
64
+ end
65
+ end
66
+
67
+ def delete_dns_record(name)
68
+ logger.info("Calling <#{__method__.to_s}>")
69
+ id = nil
70
+ conn.request(:get, ['dnsresources']).each do |item|
71
+ if item['fqdn'] == name + '.' + get_domain
72
+ id = item['id']
73
+ end
74
+ end
75
+
76
+ if ! id.nil?
77
+ conn.request(:delete, ['dnsresources', id.to_s])
78
+ else
79
+ logger.warn('No such record found.')
80
+ end
81
+ end
82
+
83
+ def refresh_pods
84
+ logger.info("Calling <#{__method__.to_s}>")
85
+ pod_id = conn.request(:get, ['pods'])
86
+ pod_id.each do |pod|
87
+ conn.request(:post, ['pods', pod['id']], { 'op' => 'refresh' } )
88
+ end
89
+ end
90
+
91
+ def get_system_id(name)
92
+ logger.info("Calling <#{__method__.to_s}>")
93
+ conn.request(:get, ['machines']).each do |m|
94
+ return m['system_id'] if m['hostname'] == name
95
+ end
96
+ nil
97
+ end
98
+
99
+ def wait_until_state(system_id, state)
100
+ logger.info("Calling <#{__method__.to_s}> for being #{state}")
101
+ until conn.request(:get, ['machines', system_id])['status_name'] == state
102
+ sleep 3
103
+ end
104
+ end
105
+
106
+ def get_machine_state(system_id)
107
+ logger.info("Calling <#{__method__.to_s}>")
108
+ conn.request(:get, ['machines']).each do |m|
109
+ return m['status_name'] if m['system_id'] == system_id
110
+ end
111
+ false
112
+ end
113
+
114
+ def change_hostname(system_id, hostname)
115
+ logger.info("Calling <#{__method__.to_s}>")
116
+ conn.request(:put, ['machines', system_id], { 'hostname' => hostname })
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,56 @@
1
+ require 'logger'
2
+
3
+ module Gogetit
4
+ # It was just taken from below source. Thanks to clowder!
5
+ # https://gist.github.com/clowder/3639600
6
+ class MultiLogger
7
+ attr_reader :level
8
+
9
+ def initialize(args={})
10
+ @level = args[:level] || Logger::Severity::INFO
11
+ @loggers = []
12
+
13
+ Array(args[:loggers]).each { |logger| add_logger(logger) }
14
+ end
15
+
16
+ def add_logger(logger)
17
+ logger.level = level
18
+ @loggers << logger
19
+ end
20
+
21
+ def level=(level)
22
+ @level = level
23
+ @loggers.each { |logger| logger.level = level }
24
+ end
25
+
26
+ def datetime_format=(format)
27
+ @loggers.each { |logger| logger.datetime_format = format }
28
+ end
29
+
30
+ def formatter=(format)
31
+ @loggers.each { |logger| logger.formatter = format }
32
+ end
33
+
34
+ def progname=(name)
35
+ @loggers.each { |logger| logger.progname = name }
36
+ end
37
+
38
+ def close
39
+ @loggers.map(&:close)
40
+ end
41
+
42
+ def add(level, *args)
43
+ @loggers.each { |logger| logger.add(level, args) }
44
+ end
45
+
46
+ Logger::Severity.constants.each do |level|
47
+ define_method(level.downcase) do |*args|
48
+ @loggers.each { |logger| logger.send(level.downcase, args) }
49
+ end
50
+
51
+ define_method("#{ level.downcase }?".to_sym) do
52
+ @level <= Logger::Severity.const_get(level)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,260 @@
1
+ require 'libvirt'
2
+ require 'securerandom'
3
+ require 'oga'
4
+ require 'rexml/document'
5
+ require 'util'
6
+
7
+ module Gogetit
8
+ class GogetLibvirt
9
+ include Gogetit::Util
10
+
11
+ attr_reader :config, :conn, :maas, :logger
12
+
13
+ def initialize(conf, maas, logger)
14
+ @config = conf
15
+ @conn = Libvirt::open(config[:libvirt][:url])
16
+ @maas = maas
17
+ @logger = logger
18
+ end
19
+
20
+ def get_domain_list
21
+ logger.info("Calling <#{__method__.to_s}>")
22
+ domains = []
23
+ conn.list_all_domains.each do |d|
24
+ domains << d.name
25
+ end
26
+ domains
27
+ end
28
+
29
+ def domain_exists?(name)
30
+ logger.info("Calling <#{__method__.to_s}>")
31
+ get_domain_list.each do |d|
32
+ return true if d == name
33
+ end
34
+ false
35
+ end
36
+
37
+ def get_mac_addr(domain_name)
38
+ logger.info("Calling <#{__method__.to_s}>")
39
+ Oga.parse_xml(conn.lookup_domain_by_name(domain_name).xml_desc)
40
+ .at_xpath('domain/devices/interface[1]/mac')
41
+ .attribute('address')
42
+ .value
43
+ end
44
+
45
+ # subject.create(name: 'test01')
46
+ def create(name, conf_file = nil)
47
+ logger.info("Calling <#{__method__.to_s}>")
48
+ if maas.domain_name_exists?(name) or domain_exists?(name)
49
+ puts "Domain #{name} already exists! Please check both on MAAS and libvirt."
50
+ return false
51
+ end
52
+
53
+ conf_file ||= config[:default_provider_conf_file]
54
+ domain = symbolize_keys(YAML.load_file(conf_file))
55
+ domain[:name] = name
56
+ domain[:uuid] = SecureRandom.uuid
57
+
58
+ dom = conn.define_domain_xml(define_domain(domain))
59
+ maas.refresh_pods
60
+
61
+ system_id = maas.get_system_id(domain[:name])
62
+ maas.wait_until_state(system_id, 'Ready')
63
+ logger.info("Calling to deploy...")
64
+ maas.conn.request(:post, ['machines', system_id], {'op' => 'deploy'})
65
+ maas.wait_until_state(system_id, 'Deployed')
66
+ logger.info("#{domain[:name]} has been created.")
67
+ true
68
+ end
69
+
70
+ def destroy(name)
71
+ logger.info("Calling <#{__method__.to_s}>")
72
+ system_id = maas.get_system_id(name)
73
+ if maas.machine_exists?(name)
74
+ if maas.get_machine_state(system_id) == 'Deployed'
75
+ logger.info("Calling to release...")
76
+ maas.conn.request(:post, ['machines', system_id], {'op' => 'release'})
77
+ maas.wait_until_state(system_id, 'Ready')
78
+ end
79
+ maas.conn.request(:delete, ['machines', system_id])
80
+ end
81
+
82
+ pools = []
83
+ conn.list_storage_pools.each do |name|
84
+ pools << self.conn.lookup_storage_pool_by_name(name)
85
+ end
86
+
87
+ dom = conn.lookup_domain_by_name(name)
88
+ dom.destroy if dom.active?
89
+ Oga.parse_xml(dom.xml_desc).xpath('domain/devices/disk/source').each do |d|
90
+ pool_path = d.attribute('file').value.split('/')[0..2].join('/')
91
+ pools.each do |p|
92
+ if Oga.parse_xml(p.xml_desc).at_xpath('pool/target/path').inner_text == pool_path
93
+ logger.info("Deleting volume in #{p.name} pool.")
94
+ p.lookup_volume_by_name(d.attribute('file').value.split('/')[3]).delete
95
+ end
96
+ end
97
+ end
98
+ dom.undefine
99
+
100
+ maas.refresh_pods
101
+ logger.info("#{name} has been destroyed.")
102
+ true
103
+ end
104
+
105
+ def define_domain(domain)
106
+ logger.info("Calling <#{__method__.to_s}>")
107
+ template = File.read(config[:lib_dir] + '/template/domain.xml')
108
+ doc = Oga.parse_xml(template)
109
+
110
+ name = domain[:name]
111
+ doc.at_xpath('domain/name').inner_text = name
112
+ uuid = domain[:uuid]
113
+ doc.at_xpath('domain/uuid').inner_text = uuid
114
+ vcpu = domain[:vcpu].to_s
115
+ doc.at_xpath('domain/vcpu').inner_text = vcpu
116
+ memory = domain[:memory].to_s
117
+ doc.at_xpath('domain/memory').inner_text = memory
118
+ doc.at_xpath('domain/currentMemory').inner_text = memory
119
+
120
+ doc = define_volumes(doc, domain)
121
+ doc = add_nic(doc, domain[:nic])
122
+
123
+ #print_xml(doc)
124
+ #volumes.each do |v|
125
+ # print_xml(v)
126
+ #end
127
+
128
+ return Oga::XML::Generator.new(doc).to_xml
129
+ end
130
+
131
+ def print_xml(doc)
132
+ logger.info("Calling <#{__method__.to_s}>")
133
+ output = REXML::Document.new(Oga::XML::Generator.new(doc).to_xml)
134
+ formatter = REXML::Formatters::Pretty.new
135
+ formatter.compact = true
136
+ formatter.write(output, $stdout)
137
+ end
138
+
139
+ def get_pool_path(pool)
140
+ logger.info("Calling <#{__method__.to_s}>")
141
+ path = nil
142
+ conn.list_all_storage_pools.each do |p|
143
+ if p.name == pool
144
+ pool_doc = Oga.parse_xml(p.xml_desc)
145
+ path = pool_doc.at_xpath('pool/target/path').inner_text
146
+ end
147
+ end
148
+
149
+ if path
150
+ return path
151
+ else
152
+ raise 'No such pool found.'
153
+ end
154
+ end
155
+
156
+ def define_volumes(document, domain)
157
+ logger.info("Calling <#{__method__.to_s}>")
158
+ disk_template = File.read(config[:lib_dir] + '/template/disk.xml')
159
+ disk_doc = Oga.parse_xml(disk_template)
160
+ volume_template = File.read(config[:lib_dir] + '/template/volume.xml')
161
+ volume_doc = Oga.parse_xml(volume_template)
162
+
163
+ defined_volumes = []
164
+
165
+ # For root volume
166
+ pool_path = get_pool_path(domain[:disk][:root][:pool])
167
+ volume_name = "#{domain[:name]}_root_sda.qcow2"
168
+ volume_file = pool_path + "/" + volume_name
169
+ disk_doc.at_xpath('disk/source').attribute('file').value = volume_file
170
+ document.at_xpath('domain/devices').children << disk_doc.at_xpath('disk')
171
+
172
+ volume_doc.at_xpath('volume/name').inner_text = volume_name
173
+ volume_doc.at_xpath('volume/target/path').inner_text = volume_file
174
+ volume_doc.at_xpath('volume/capacity').inner_text = domain[:disk][:root][:capacity].to_s
175
+
176
+ create_volume(domain[:disk][:root][:pool], Oga::XML::Generator.new(volume_doc).to_xml)
177
+ defined_volumes << volume_doc
178
+
179
+ # For data(secondary) volumes
180
+ if domain[:disk][:data] != [] and domain[:disk][:data] != nil
181
+ disk_index = 98
182
+ domain[:disk][:data].each do |v|
183
+ pool_path = get_pool_path(v[:pool])
184
+ volume_index = "sd" + disk_index.chr
185
+ volume_name = "#{domain[:name]}_data_#{volume_index}.qcow2"
186
+ volume_file = pool_path + "/" + volume_name
187
+ disk_doc = Oga.parse_xml(disk_template)
188
+ disk_doc.at_xpath('disk/source').attribute('file').value = volume_file
189
+ disk_doc.at_xpath('disk/target').attribute('dev').value = volume_index
190
+ document.at_xpath('domain/devices').children << disk_doc.at_xpath('disk')
191
+
192
+ volume_doc = Oga.parse_xml(volume_template)
193
+ volume_doc.at_xpath('volume/name').inner_text = volume_name
194
+ volume_doc.at_xpath('volume/target/path').inner_text = volume_file
195
+ volume_doc.at_xpath('volume/capacity').inner_text = v[:capacity].to_s
196
+ create_volume(v[:pool], Oga::XML::Generator.new(volume_doc).to_xml)
197
+ defined_volumes << volume_doc
198
+ disk_index += 1
199
+ end
200
+ end
201
+
202
+ return document
203
+ end
204
+
205
+ def create_volume(pool_name, volume_doc)
206
+ logger.info("Calling <#{__method__.to_s}> to create volume in #{pool_name} pool.")
207
+ pool = conn.lookup_storage_pool_by_name(pool_name)
208
+ pool.create_volume_xml(volume_doc)
209
+ pool.refresh
210
+ end
211
+
212
+ def add_nic(document, nic_conf)
213
+ logger.info("Calling <#{__method__.to_s}>")
214
+ template = File.read(config[:lib_dir] + "/template/nic.xml")
215
+ doc = Oga.parse_xml(template)
216
+
217
+ nic_conf.each do |nic|
218
+ doc = Oga.parse_xml(template)
219
+ doc.at_xpath('interface/source').attribute('network').value = nic[:network]
220
+ doc.at_xpath('interface/source').attribute('portgroup').value = nic[:portgroup]
221
+ document.at_xpath('domain/devices').children << doc.at_xpath('interface')
222
+ end
223
+
224
+ document
225
+ end
226
+
227
+ #def generate_xml
228
+ # domain = Oga::XML::Element.new(name: 'domain')
229
+ # domain.add_attribute(Oga::XML::Attribute.new(name: "type", value: "kvm"))
230
+ # name = Oga::XML::Element.new(name: 'name')
231
+ # name.inner_text = 'ceph'
232
+ # domain.children << name
233
+ # doc = REXML::Document.new(Oga::XML::Generator.new(domain).to_xml)
234
+ # formatter = REXML::Formatters::Pretty.new
235
+ # formatter.compact = true
236
+ # formatter.write(doc, $stdout)
237
+
238
+ # disk = create_element(name: 'domain')
239
+ # disk.add_attribute(Oga::XML::Attribute.new(name: "type", value: "file"))
240
+ # disk.add_attribute(Oga::XML::Attribute.new(name: "device", value: "disk"))
241
+ # driver = create_element(name: 'domain')
242
+ # driver.add_attribute(Oga::XML::Attribute.new(name: "name", value: "qemu"))
243
+ # driver.add_attribute(Oga::XML::Attribute.new(name: "type", value: "qcow2"))
244
+ # source = create_element(name: 'source')
245
+ # source.add_attribute(Oga::XML::Attribute.new(name: "file", value: file))
246
+ # target = create_element(name: 'target')
247
+ # target.add_attribute(Oga::XML::Attribute.new(name: "dev", value: seq_name))
248
+ # target.add_attribute(Oga::XML::Attribute.new(name: "bus", value: "scsi"))
249
+
250
+ #end
251
+
252
+ #def create_element(name)
253
+ # Oga::XML::Element.new(name: name)
254
+ #end
255
+
256
+ #def add_attribute(element, hash)
257
+ # element.add_attribute(Oga::XML::Attribute.new(hash))
258
+ #end
259
+ end
260
+ end