gogetit 0.2.1 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a8894161933329fe5c330fdfe273572eaca29169
4
- data.tar.gz: b1ece96587307c03ed4c858b5c4568bdffc789b3
3
+ metadata.gz: 9d5861321df7c324e2ed483329663114406a308b
4
+ data.tar.gz: 996bd6ae6cf0f34a38fb36f91abff1d94d06b34b
5
5
  SHA512:
6
- metadata.gz: 579d18217f3eba138849cff0c7e3e06430843ff629c3e7a3a0e65f7d9655e0a066adbbea8ba012bd1467311a33a28a849a4caf93360bf83c42f4531967a6708a
7
- data.tar.gz: 15b7091187727b452fa188a3d6cb7d70fa52270947c1f1a59b6a6c81bbc1dc11244c0d25703ffba319d01894c797c198518ebcdbf987e823393eee947f4fcbe8
6
+ metadata.gz: 61507178c9a1ad0aedb2d969a5714f8d9c3197e75fd016aaa974a51f6d860e9e2d65bbdd45680ce72e48600db02dde295a47d626dafc902d482cda99b4795aeb
7
+ data.tar.gz: e517a9a7000d81f192a7e456e572933408d3a843536e3e7e7d8fb0f98c25323a517080632d71306be4b6ed6650c5f6aa1639b0e0091f66cdd7fa83df16c7f3cf
data/README.md CHANGED
@@ -9,6 +9,12 @@ By using this, you will get to use them all together in automated and efficient
9
9
  - Aware of Chef knife and its sub commands such as Vault to automate routine tasks.
10
10
  - Being used by [kitchen-gogetkitchen](https://github.com/itisnotdone/kitchen-gogetkitchen) as a driver for Chef Test Kitchen.
11
11
 
12
+ ## Limitations
13
+ - Network resource awareness is only provided by MAAS.
14
+ - Only LXD and Libvirt(KVM) are available as provider.
15
+ - Only IPv4 is available for IP assignment.
16
+ - It is tested only on Ubuntu 16.04 with OVS as virtual switch.
17
+
12
18
  ## Installation
13
19
 
14
20
  ### dependent packages
@@ -31,17 +37,41 @@ $ gem install gogetit --no-ri --no-rdoc
31
37
  ## Usage
32
38
  ```bash
33
39
  gogetit list
34
- gogetit create lxd lxd01
35
- gogetit create libvirt kvm01
40
+ gogetit create lxd01
41
+ gogetit create lxd01 --provider lxd
42
+
43
+ # For advanced network configuration
44
+ gogetit create lxd01 -p lxd -i 192.168.0.10
45
+
46
+ # When specifying multiple IPs, the first one will be chosen as gateway.
47
+ # And the IP which belongs to the gateway interface will be the IP of the FQDN
48
+ # of the container which is set by MAAS.
49
+ # The IPs should belong to networks defined and recognized by MAAS.
50
+ gogetit create lxd01 -p lxd -i 192.168.10.10 10.0.0.2
51
+
52
+ # When specifying multiple VLANs, the first one will be chosen as gateway.
53
+ # gogetit create lxd01 -p lxd -v 0 10 12
54
+ # gogetit create lxd01 -p lxd -v 10 11
55
+
56
+ gogetit create kvm01 -p libvirt
57
+ gogetit create kvm01 -p libvirt -i 192.168.10.10 10.0.0.2
58
+
59
+ # to provision with a bare metal machine
60
+ # gogetit create kvm01 -p bare
36
61
 
37
62
  gogetit destroy lxd01
38
63
  gogetit rebuild kvm01
39
64
 
40
65
  # to create a container bootstrapping as a chef node
41
- gogetit create --chef chef01
66
+ gogetit create chef01 --chef
67
+
68
+ or
69
+
70
+ CHEF=true gogetit create chef01
71
+
42
72
 
43
73
  # to destroy a container deleting corresponding chef node and client
44
- gogetit destroy --chef chef01
74
+ gogetit destroy chef01 --chef
45
75
  ```
46
76
 
47
77
  ```ruby
@@ -49,8 +79,9 @@ require 'gogetit'
49
79
  ```
50
80
 
51
81
  ## TODO
52
- - Network subnets and space aware via MAAS
53
- - Static IP allocation
82
+ - Provisioning LXD with useful cloud-init user-data.
83
+ - Deploying bare metal machine from machine pool.
84
+ - Static IP auto-assignment with VLAN info.
54
85
 
55
86
  ## Development and Contributing
56
87
  Clone and then execute followings:
@@ -17,16 +17,28 @@ module Gogetit
17
17
  end
18
18
 
19
19
  desc 'create (TYPE) NAME', 'Create either a container or KVM domain.'
20
- method_option :chef, :type => :boolean, :desc => "Enable chef awareness."
21
- def create(type='lxd', name)
22
- case type
20
+ method_option :provider, :aliases => '-p', :type => :string, \
21
+ :default => 'lxd', :desc => 'A provider such as lxd and libvirt'
22
+ method_option :chef, :aliases => '-c', :type => :boolean, \
23
+ :default => false, :desc => 'Chef awareness'
24
+
25
+ method_option :vlans, :aliases => '-v', :type => :array, \
26
+ :desc => 'A list of VLAN IDs to connect to'
27
+ method_option :ipaddresses, :aliases => '-i', :type => :array, \
28
+ :desc => 'A list of static IPs to assign'
29
+ def create(name)
30
+ abort("vlans and ipaddresses can not be used at the same time.") \
31
+ if options['vlans'] and options['ipaddresses']
32
+
33
+ case options[:provider]
23
34
  when 'lxd'
24
- Gogetit.lxd.create(name)
35
+ Gogetit.lxd.create(name, options.to_hash)
25
36
  when 'libvirt'
26
- Gogetit.libvirt.create(name)
37
+ Gogetit.libvirt.create(name, options.to_hash)
27
38
  else
28
39
  abort('Invalid argument entered.')
29
40
  end
41
+
30
42
  # post-tasks
31
43
  if options[:chef]
32
44
  knife_bootstrap(name, type, Gogetit.config)
@@ -34,6 +46,7 @@ module Gogetit
34
46
  end
35
47
  Gogetit.config[:default][:user] ||= ENV['USER']
36
48
  puts "ssh #{Gogetit.config[:default][:user]}@#{name}"
49
+ print "ssh #{Gogetit.config[:default][:user]}@#{name}"
37
50
  end
38
51
 
39
52
  desc 'destroy NAME', 'Destroy either a container or KVM domain.'
@@ -61,23 +61,5 @@ module Gogetit
61
61
  abort('Please define default configuration for GoGetIt at ~/.gogetit/gogetit.yml.')
62
62
  end
63
63
  config.merge!(symbolize_keys(YAML.load_file(conf_file)))
64
-
65
- logger.debug('Define provider configuration directory..')
66
- provider_conf_dir = user_gogetit_home + '/conf'
67
- config[:provider_conf_dir] = provider_conf_dir
68
- default_provider_conf_file = provider_conf_dir + '/default.yml'
69
- config[:default_provider_conf_file] = default_provider_conf_file
70
- if not File.exists?(default_provider_conf_file)
71
- if not File.directory?(provider_conf_dir)
72
- logger.debug('Creating provider configuration directory..')
73
- FileUtils.mkdir(provider_conf_dir)
74
- end
75
- src = File.new(lib_dir + '/sample_conf/default.yml')
76
- dst = Dir.new(provider_conf_dir)
77
- logger.debug('Copying provider configuration file..')
78
- FileUtils.cp(src, dst)
79
- abort('Please define default configuration for providers at ~/.gogetit/conf/default.yml.')
80
- end
81
-
82
64
  end
83
65
  end
@@ -1,4 +1,5 @@
1
1
  require 'maas/client'
2
+ require 'ipaddr'
2
3
 
3
4
  module Gogetit
4
5
  class GogetMAAS
@@ -36,48 +37,142 @@ module Gogetit
36
37
  end
37
38
 
38
39
  def domain_name_exists?(name)
40
+ logger.info("Calling <#{__method__.to_s}>")
39
41
  return true if dnsresource_exists?(name) or machine_exists?(name)
40
42
  end
41
43
 
44
+ def ip_reserved?(addresses)
45
+ logger.info("Calling <#{__method__.to_s}>")
46
+ ips = Set.new
47
+ addresses.each do |ip|
48
+ ips.add(IPAddr.new(ip))
49
+ end
50
+
51
+ reserved_ips = Set.new
52
+ conn.request(:get, ['ipaddresses']).each do |ip|
53
+ reserved_ips.add(IPAddr.new(ip['ip']))
54
+ end
55
+
56
+ rackcontroller_ips = Set.new
57
+ conn.request(:get, ['rackcontrollers']).each do |rctrl|
58
+ rctrl['ip_addresses'].each do |ip|
59
+ rackcontroller_ips.add(IPAddr.new(ip))
60
+ end
61
+ end
62
+
63
+ # reserved_ips | rackcontroller_ips
64
+ # Returns a new array by joining ary with other_ary,
65
+ # excluding any duplicates and preserving the order from the original array.
66
+ if ips.disjoint? reserved_ips | rackcontroller_ips
67
+ subnets = conn.request(:get, ['subnets'])
68
+ ipranges = conn.request(:get, ['ipranges'])
69
+
70
+ ifaces = []
71
+
72
+ ips.each do |ip|
73
+ available = false
74
+ subnets.each do |subnet|
75
+ if IPAddr.new(subnet['cidr']).include?(ip)
76
+ ipranges.each do |range|
77
+ if range['subnet']['id'] == subnet['id']
78
+ first = IPAddr.new(range['start_ip']).to_i
79
+ last = IPAddr.new(range['end_ip']).to_i
80
+ if (first..last) === ip.to_i
81
+ logger.info("#{ip} is available.")
82
+ available = true
83
+ subnets.delete(subnet)
84
+ subnet['ip'] = ip.to_s
85
+ ifaces << subnet
86
+ break
87
+ end
88
+ end
89
+ end
90
+ end
91
+ break if available
92
+ end
93
+
94
+ if not available
95
+ logger.info("#{ip.to_s} does not belong to any subnet pre-defined.")
96
+ return false
97
+ end
98
+ end
99
+
100
+ else
101
+ logger.info("#{(ips & (reserved_ips | rackcontroller_ips)).to_a.join(', ')}\
102
+ is already reserved.")
103
+ return false
104
+ end
105
+
106
+ return ifaces
107
+ end
108
+
109
+ def ipaddresses_reserved?(ip)
110
+ logger.info("Calling <#{__method__.to_s}>")
111
+ conn.request(:get, ['ipaddresses']).each do |address|
112
+ if address['ip'] == ip
113
+ logger.info("#{ip} is reserved.")
114
+ return true
115
+ end
116
+ end
117
+ return false
118
+ end
119
+
42
120
  def ipaddresses(op = nil, params = nil)
121
+ logger.info("Calling <#{__method__.to_s}>")
43
122
  case op
44
123
  when nil
45
124
  conn.request(:get, ['ipaddresses'])
46
125
  when 'reserve'
47
- # sample = {
126
+ # params = {
48
127
  # 'subnet' => '10.1.2.0/24',
49
128
  # 'ip' => '10.1.2.8',
50
129
  # 'hostname' => 'hostname',
51
130
  # 'mac' => 'blahblah'
52
131
  # }
53
132
  default_param = { 'op' => op }
133
+ logger.info("#{params['ip']} is being reserved..")
54
134
  conn.request(:post, ['ipaddresses'], default_param.merge!(params))
55
135
  when 'release'
56
136
  # Gogetit.maas.ipaddresses('release', {'ip' => '10.1.2.8'})
57
- # sample = {
137
+ # params = {
58
138
  # 'ip' => '10.1.2.8',
59
- # 'hostname' => 'hostname',
60
- # 'mac' => 'blahblah'
61
139
  # }
62
140
  default_param = { 'op' => op }
141
+ logger.info("#{params['ip']} is being released..")
63
142
  conn.request(:post, ['ipaddresses'], default_param.merge!(params))
64
143
  end
65
144
  end
66
145
 
146
+ def interfaces(url = [], params = {})
147
+ logger.info("Calling <#{__method__.to_s}>")
148
+
149
+ url.insert(0, 'nodes')
150
+ url.insert(2, 'interfaces')
151
+ # ['nodes', system_id, 'interfaces']
152
+
153
+ case params['op']
154
+ when nil
155
+ conn.request(:get, url)
156
+ when 'create_vlan'
157
+ logger.info("Creating a vlan interface for id: #{params['vlan']}..")
158
+ conn.request(:post, url, params)
159
+ when 'link_subnet'
160
+ logger.info("Linking a subnet for id: #{url[3]}..")
161
+ conn.request(:post, url, params)
162
+ when 'unlink_subnet'
163
+ logger.info("Linking a subnet for id: #{url[3]}..")
164
+ conn.request(:post, url, params)
165
+ end
166
+ end
167
+
67
168
  def delete_dns_record(name)
68
169
  logger.info("Calling <#{__method__.to_s}>")
69
- id = nil
70
170
  conn.request(:get, ['dnsresources']).each do |item|
71
171
  if item['fqdn'] == name + '.' + get_domain
72
- id = item['id']
172
+ logger.info("#{item['fqdn']} is being deleted..")
173
+ conn.request(:delete, ['dnsresources', item['id']])
73
174
  end
74
175
  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
176
  end
82
177
 
83
178
  def refresh_pods
@@ -91,7 +91,7 @@ module Gogetit
91
91
  end
92
92
 
93
93
  def wait_until_available(fqdn, logger)
94
- until ping_available?(fqdn)
94
+ until ping_available?(fqdn, logger)
95
95
  logger.info("Calling <#{__method__.to_s}> for ping to be ready..")
96
96
  sleep 3
97
97
  end
@@ -104,8 +104,10 @@ module Gogetit
104
104
  logger.info("#{fqdn} is now available to ssh..")
105
105
  end
106
106
 
107
- def ping_available?(fqdn)
108
- `ping -c 1 -W 1 #{fqdn}`
107
+ def ping_available?(host, logger)
108
+ # host can be both IP and FQDN.
109
+ logger.info("Calling <#{__method__.to_s}> for #{host}")
110
+ `ping -c 1 -W 1 #{host}`
109
111
  $?.exitstatus == 0
110
112
  end
111
113
 
@@ -116,5 +118,19 @@ module Gogetit
116
118
  puts e
117
119
  end
118
120
  end
121
+
122
+ def check_ip_available(addresses, maas, logger)
123
+ logger.info("Calling <#{__method__.to_s}>")
124
+ # to do a ping test
125
+ addresses.each do |ip|
126
+ abort("#{ip} is already being used.") if ping_available?(ip, logger)
127
+ end
128
+ # to check with MAAS
129
+ ifaces = maas.ip_reserved?(addresses)
130
+ abort("one of #{addresses.join(', ')} is already being used.") \
131
+ unless ifaces
132
+ return ifaces
133
+ end
134
+
119
135
  end
120
136
  end
@@ -1,3 +1,3 @@
1
1
  module Gogetit
2
- VERSION = "0.2.1"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -42,16 +42,70 @@ module Gogetit
42
42
  .value
43
43
  end
44
44
 
45
+ def generate_nics(ifaces, domain)
46
+ abort("There is no dns server specified for the gateway network.") \
47
+ unless ifaces[0]['dns_servers'][0]
48
+ abort("There is no gateway specified for the gateway network.") \
49
+ unless ifaces[0]['gateway_ip']
50
+
51
+ # It seems the first IP has to belong to the untagged VLAN in the Fabric.
52
+ abort("The first IP you entered does not belong to the untagged VLAN in the Fabric.") \
53
+ unless ifaces[0]['vlan']['name'] == 'untagged'
54
+
55
+ domain[:ifaces] = ifaces
56
+ domain[:nic] = []
57
+
58
+ ifaces.each_with_index do |iface,index|
59
+ if index == 0
60
+ if iface['vlan']['name'] == 'untagged'
61
+ nic = {
62
+ network: config[:default][:native_bridge],
63
+ portgroup: config[:default][:native_bridge]
64
+ }
65
+ elsif iface['vlan']['name'] != 'untagged'
66
+ nic = {
67
+ network: config[:default][:native_bridge],
68
+ portgroup: config[:default][:native_bridge] + '-' + iface['vlan']['vid'].to_s
69
+ }
70
+ end
71
+ domain[:nic].push(nic)
72
+ elsif index > 0
73
+ # Only if the fisrt interface has untagged VLAN,
74
+ # it will be configured with VLANs.
75
+ # This will not be hit as of now and might be deprecated.
76
+ if ifaces[0]['vlan']['name'] != 'untagged'
77
+ nic = {
78
+ network: config[:default][:native_bridge],
79
+ portgroup: config[:default][:native_bridge] + '-' + iface['vlan']['vid'].to_s
80
+ }
81
+ domain[:nic].push(nic)
82
+ end
83
+ end
84
+ end
85
+ return domain
86
+ end
87
+
45
88
  # subject.create(name: 'test01')
46
- def create(name, conf_file = nil)
89
+ def create(name, options = nil)
47
90
  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
91
+ abort("Domain #{name} already exists! Please check both on MAAS and libvirt.") \
92
+ if maas.domain_name_exists?(name) or domain_exists?(name)
93
+
94
+ domain = config[:libvirt][:specs][:default]
95
+ if options['ipaddresses']
96
+ ifaces = check_ip_available(options['ipaddresses'], maas, logger)
97
+ domain = generate_nics(ifaces, domain)
98
+ elsif options[:vlans]
99
+ #check_vlan_available(options[:vlans])
100
+ else
101
+ domain[:nic] = [
102
+ {
103
+ network: config[:default][:native_bridge],
104
+ portgroup: config[:default][:native_bridge]
105
+ }
106
+ ]
51
107
  end
52
108
 
53
- conf_file ||= config[:default_provider_conf_file]
54
- domain = symbolize_keys(YAML.load_file(conf_file))
55
109
  domain[:name] = name
56
110
  domain[:uuid] = SecureRandom.uuid
57
111
 
@@ -60,6 +114,65 @@ module Gogetit
60
114
 
61
115
  system_id = maas.get_system_id(domain[:name])
62
116
  maas.wait_until_state(system_id, 'Ready')
117
+
118
+ # To configure interfaces
119
+ if options['ipaddresses']
120
+
121
+ # It assumes you only have a physical interfaces.
122
+ interfaces = maas.interfaces([system_id])
123
+ maas.interfaces(
124
+ [system_id, interfaces[0]['id']],
125
+ {
126
+ 'op' => 'unlink_subnet',
127
+ 'id' => interfaces[0]['links'][0]['id']
128
+ }
129
+ )
130
+
131
+ maas.interfaces(
132
+ [system_id, interfaces[0]['id']],
133
+ {
134
+ 'op' => 'link_subnet',
135
+ 'mode' => 'STATIC',
136
+ 'subnet' => ifaces[0]['id'],
137
+ 'ip_address' => ifaces[0]['ip'],
138
+ 'default_gateway' => 'True',
139
+ 'force' => 'False'
140
+ }
141
+ )
142
+
143
+ if domain[:ifaces].length > 1
144
+ ifaces.shift
145
+
146
+ # VLAN configuration
147
+ ifaces.each_with_index do |iface,index|
148
+ params = {
149
+ 'op' => 'create_vlan',
150
+ 'vlan' => iface['vlan']['id'],
151
+ 'parent' => interfaces[0]['id']
152
+ }
153
+ maas.interfaces([system_id], params)
154
+
155
+ interfaces = maas.interfaces([system_id])
156
+ interfaces.shift
157
+
158
+ maas.interfaces([system_id, interfaces[index]['id']],
159
+ {
160
+ 'op' => 'link_subnet',
161
+ 'mode' => 'STATIC',
162
+ 'subnet' => ifaces[index]['id'],
163
+ 'ip_address' => ifaces[index]['ip'],
164
+ 'default_gateway' => 'False',
165
+ 'force' => 'False'
166
+ }
167
+ )
168
+ end
169
+ end
170
+
171
+ elsif options[:vlans]
172
+ #check_vlan_available(options[:vlans])
173
+ else
174
+ end
175
+
63
176
  logger.info("Calling to deploy...")
64
177
  maas.conn.request(:post, ['machines', system_id], {'op' => 'deploy'})
65
178
  maas.wait_until_state(system_id, 'Deployed')
@@ -123,10 +236,10 @@ module Gogetit
123
236
  doc = define_volumes(doc, domain)
124
237
  doc = add_nic(doc, domain[:nic])
125
238
 
126
- #print_xml(doc)
127
- #volumes.each do |v|
128
- # print_xml(v)
129
- #end
239
+ # print_xml(doc)
240
+ # volumes.each do |v|
241
+ # print_xml(v)
242
+ # end
130
243
 
131
244
  return Oga::XML::Generator.new(doc).to_xml
132
245
  end
@@ -1,5 +1,6 @@
1
1
  require 'hyperkit'
2
2
  require 'gogetit/util'
3
+ require 'yaml'
3
4
 
4
5
  module Gogetit
5
6
  class GogetLXD
@@ -41,17 +42,179 @@ module Gogetit
41
42
  end
42
43
  end
43
44
 
44
- def create(name, args = {})
45
+ def generate_args(options)
46
+ args = {}
47
+ args[:devices] = {}
48
+
49
+ ifaces = check_ip_available(options['ipaddresses'], maas, logger)
50
+ abort("There is no dns server specified for the gateway network.") \
51
+ unless ifaces[0]['dns_servers'][0]
52
+ abort("There is no gateway specified for the gateway network.") \
53
+ unless ifaces[0]['gateway_ip']
54
+ args[:ifaces] = ifaces
55
+ args[:config] = {
56
+ 'user.network-config': {
57
+ 'version' => 1,
58
+ 'config' => [
59
+ {
60
+ 'type' => 'nameserver',
61
+ 'address' => ifaces[0]['dns_servers'][0]
62
+ }
63
+ ]
64
+ }
65
+ }
66
+
67
+ ifaces.each_with_index do |iface,index|
68
+ if index == 0
69
+ iface_conf = {
70
+ 'type' => 'physical',
71
+ 'name' => "eth#{index}",
72
+ 'subnets' => [
73
+ {
74
+ 'type' => 'static',
75
+ 'ipv4' => true,
76
+ 'address' => iface['ip'] + '/' + iface['cidr'].split('/')[1],
77
+ 'gateway' => iface['gateway_ip'],
78
+ 'mtu' => iface['vlan']['mtu'],
79
+ 'control' => 'auto'
80
+ }
81
+ ]
82
+ }
83
+ elsif index > 0
84
+ if ifaces[0]['vlan']['name'] != 'untagged'
85
+ iface_conf = {
86
+ 'type' => 'physical',
87
+ 'name' => "eth#{index}",
88
+ 'subnets' => [
89
+ {
90
+ 'type' => 'static',
91
+ 'ipv4' => true,
92
+ 'address' => iface['ip'] + '/' + iface['cidr'].split('/')[1],
93
+ 'mtu' => iface['vlan']['mtu'],
94
+ 'control' => 'auto'
95
+ }
96
+ ]
97
+ }
98
+ elsif ifaces[0]['vlan']['name'] == 'untagged'
99
+ iface_conf = {
100
+ 'type' => 'vlan',
101
+ 'name' => "eth0.#{iface['vlan']['vid'].to_s}",
102
+ 'vlan_id' => iface['vlan']['vid'].to_s,
103
+ 'vlan_link' => 'eth0',
104
+ 'subnets' => [
105
+ {
106
+ 'type' => 'static',
107
+ 'ipv4' => true,
108
+ 'address' => iface['ip'] + '/' + iface['cidr'].split('/')[1],
109
+ 'mtu' => iface['vlan']['mtu'],
110
+ 'control' => 'auto'
111
+ }
112
+ ]
113
+ }
114
+ end
115
+ end
116
+
117
+ args[:config][:'user.network-config']['config'].push(iface_conf)
118
+ end
119
+
120
+ args[:config][:"user.network-config"] = \
121
+ YAML.dump(args[:config][:"user.network-config"])[4..-1]
122
+
123
+ # To configure devices
124
+ ifaces.each_with_index do |iface,index|
125
+ if index == 0
126
+ if iface['vlan']['name'] == 'untagged' # or vid == 0
127
+ args[:devices][:"eth#{index}"] = {
128
+ mtu: iface['vlan']['mtu'].to_s, #This must be string
129
+ name: "eth#{index}",
130
+ nictype: 'bridged',
131
+ parent: config[:default][:native_bridge],
132
+ type: 'nic'
133
+ }
134
+ elsif iface['vlan']['name'] != 'untagged' # or vid != 0
135
+ args[:devices][:"eth#{index}"] = {
136
+ mtu: iface['vlan']['mtu'].to_s, #This must be string
137
+ name: "eth#{index}",
138
+ nictype: 'bridged',
139
+ parent: config[:default][:native_bridge] + "-" + iface['vlan']['vid'].to_s,
140
+ type: 'nic'
141
+ }
142
+ end
143
+ # When ifaces[0]['vlan']['name'] == 'untagged' and index > 0,
144
+ # it does not need to generate more devices
145
+ # since it will configure the IPs with tagged VLANs.
146
+ elsif ifaces[0]['vlan']['name'] != 'untagged'
147
+ args[:devices][:"eth#{index}"] = {
148
+ mtu: iface['vlan']['mtu'].to_s, #This must be string
149
+ name: "eth#{index}",
150
+ nictype: 'bridged',
151
+ parent: config[:default][:native_bridge] + "-" + iface['vlan']['vid'].to_s,
152
+ type: 'nic'
153
+ }
154
+ end
155
+ end
156
+
157
+ return args
158
+ end
159
+
160
+ def create(name, options = {})
45
161
  logger.info("Calling <#{__method__.to_s}>")
46
- if container_exists?(name) or maas.domain_name_exists?(name)
47
- puts "Container #{name} already exists!"
48
- return false
162
+ abort("Container or Hostname #{name} already exists!") \
163
+ if container_exists?(name) or maas.domain_name_exists?(name)
164
+
165
+ args = {}
166
+ if options['ipaddresses']
167
+ args = generate_args(options)
168
+ elsif options[:vlans]
169
+ #check_vlan_available(options[:vlans])
170
+ else
171
+ args[:profiles] ||= config[:lxd][:profiles]
49
172
  end
50
173
 
51
174
  args[:alias] ||= config[:lxd][:default_alias]
52
- args[:profiles] ||= config[:lxd][:profiles]
53
175
  args[:sync] ||= true
176
+
54
177
  conn.create_container(name, args)
178
+ container = conn.container(name)
179
+
180
+ if options['vlans'] or options['ipaddresses']
181
+ container.devices = args[:devices].merge!(container.devices.to_hash)
182
+ conn.update_container(name, container)
183
+ # Fetch container object again
184
+ container = conn.container(name)
185
+
186
+ # Generate params to reserve IPs
187
+ args[:ifaces].each_with_index do |iface,index|
188
+ if index == 0
189
+ params = {
190
+ 'subnet' => iface['cidr'],
191
+ 'ip' => iface['ip'],
192
+ 'hostname' => name,
193
+ 'mac' => container[:expanded_config][:"volatile.eth#{index}.hwaddr"]
194
+ }
195
+ elsif index > 0
196
+ # if dot, '.', is used as a conjunction instead of '-', it fails ocuring '404 not found'.
197
+ # if under score, '_', is used as a conjunction instead of '-', it breaks MAAS DNS somehow..
198
+ if args[:ifaces][0]['vlan']['name'] == 'untagged'
199
+ params = {
200
+ 'subnet' => iface['cidr'],
201
+ 'ip' => iface['ip'],
202
+ 'hostname' => 'eth0' + '-' + iface['vlan']['vid'].to_s + '-' + name,
203
+ 'mac' => container[:expanded_config][:"volatile.eth0.hwaddr"]
204
+ }
205
+ elsif args[:ifaces][0]['vlan']['name'] != 'untagged'
206
+ params = {
207
+ 'subnet' => iface['cidr'],
208
+ 'ip' => iface['ip'],
209
+ 'hostname' => "eth#{index}" + '-' + name,
210
+ 'mac' => container[:expanded_config][:"volatile.eth#{index}.hwaddr"]
211
+ }
212
+ end
213
+ end
214
+ maas.ipaddresses('reserve', params)
215
+ end
216
+ end
217
+
55
218
  conn.start_container(name, :sync=>"true")
56
219
 
57
220
  fqdn = name + '.' + maas.get_domain
@@ -62,12 +225,39 @@ module Gogetit
62
225
 
63
226
  def destroy(name, args = {})
64
227
  logger.info("Calling <#{__method__.to_s}>")
228
+
229
+ container = conn.container(name)
65
230
  args[:sync] ||= true
231
+
66
232
  if get_state(name) == 'Running'
67
233
  conn.stop_container(name, args)
68
234
  end
235
+
69
236
  wait_until_state(name, 'Stopped')
70
237
  conn.delete_container(name, args)
238
+
239
+ if container[:config][:"user.network-config"]
240
+ net_conf = YAML.load(
241
+ container[:config][:"user.network-config"]
242
+ )['config']
243
+ # To remove DNS configuration
244
+ net_conf.shift
245
+
246
+ net_conf.each do |nic|
247
+ if nic['subnets'][0]['type'] == 'static'
248
+ # It assumes we only assign a single subnet on a VLAN.
249
+ # Subnets in a VLAN, VLANs in a Fabric
250
+ ip = nic['subnets'][0]['address'].split('/')[0]
251
+
252
+ if maas.ipaddresses_reserved?(ip)
253
+ maas.ipaddresses('release', { 'ip' => ip })
254
+ end
255
+ end
256
+ end
257
+ end
258
+
259
+ # When multiple static IPs were reserved, it will not delete anything
260
+ # since they are deleted when releasing the IPs above.
71
261
  maas.delete_dns_record(name)
72
262
  logger.info("#{name} has been destroyed.")
73
263
  true
@@ -1,18 +1,45 @@
1
1
  default:
2
2
  user: ubuntu
3
- etcd:
4
- url: http://etcd.example.com:2379
3
+ native_bridge: my_favorite_bridge
4
+ # etcd:
5
+ # url: http://etcd.example.com:2379
6
+ # about MAAS
5
7
  maas:
6
8
  key: K:E:Y
7
9
  url: http://maas.example.com/MAAS/api/2.0
10
+ # about LXD
8
11
  lxd:
9
12
  url: https://lxd.example.com:8443
10
13
  name: REMOTE_NAME
11
14
  default_alias: ubuntu-16.04
12
15
  profiles:
13
16
  - my_favorite_bridge
17
+ # about Libvirt
14
18
  libvirt:
15
19
  url: qemu+ssh://WHOAMI@kvm.example.com/system
20
+ specs:
21
+ default:
22
+ vcpu: 1
23
+ memory: 1
24
+ disk:
25
+ root:
26
+ pool: ssd
27
+ capacity: 4
28
+ ceph:
29
+ vcpu: 4
30
+ memory: 8
31
+ disk:
32
+ root:
33
+ pool: ssd
34
+ capacity: 4
35
+ data:
36
+ -
37
+ pool: hdd
38
+ capacity: 8
39
+ -
40
+ pool: hdd
41
+ capacity: 8
42
+ # about Chef
16
43
  chef:
17
44
  chef_repo_root: /SOMEWHERE/CHEF_REPO
18
45
  bootstrap:
@@ -0,0 +1,17 @@
1
+ config:
2
+ # raw.lxc: lxc.aa_profile=unconfined
3
+ # security.privileged: "true"
4
+ user.user-data: |
5
+ #cloud-config
6
+ ssh_authorized_keys:
7
+ - ssh-rsa blahblah someone@somewhere
8
+ apt_mirror: http://repo.example.com/ubuntu/
9
+ description: ""
10
+ devices:
11
+ eth0:
12
+ mtu: "8954"
13
+ name: eth0
14
+ nictype: bridged
15
+ parent: somebr
16
+ type: nic
17
+ name: somebr
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gogetit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Don Draper
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-08-07 00:00:00.000000000 Z
11
+ date: 2017-09-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -215,9 +215,8 @@ files:
215
215
  - lib/gogetit/version.rb
216
216
  - lib/providers/libvirt.rb
217
217
  - lib/providers/lxd.rb
218
- - lib/sample_conf/ceph.yml
219
- - lib/sample_conf/default.yml
220
218
  - lib/sample_conf/gogetit.yml
219
+ - lib/sample_conf/lxd_profile.yaml
221
220
  - lib/template/disk.xml
222
221
  - lib/template/domain.xml
223
222
  - lib/template/nic.xml
@@ -1,17 +0,0 @@
1
- vcpu: 4
2
- memory: 8
3
- disk:
4
- root:
5
- pool: ssd
6
- capacity: 4
7
- data:
8
- -
9
- pool: hdd
10
- capacity: 8
11
- -
12
- pool: hdd
13
- capacity: 8
14
- nic:
15
- -
16
- network: br-one
17
- portgroup: all
@@ -1,10 +0,0 @@
1
- vcpu: 1
2
- memory: 1
3
- disk:
4
- root:
5
- pool: ssd
6
- capacity: 4
7
- nic:
8
- -
9
- network: br-one
10
- portgroup: all