gogetit 0.2.1 → 0.3.0

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