2020
2121require 'client'
2222require 'opennebula_vm'
23+ require 'tempfile'
2324
2425# LXC Container abstraction. Handles container native and added
2526# operations. Allows to gather container config and status data
@@ -39,6 +40,9 @@ def initialize(one, client)
3940 @client = client
4041
4142 @name = @one . vm_name
43+
44+ @driver_config = @one . lxcrc
45+ @driver_config . merge! ( :id_map => 0 ) if @one . privileged?
4246 end
4347
4448 class << self
@@ -96,11 +100,8 @@ def create(options = {})
96100 error = false
97101 mounted = [ ]
98102
99- lxcrc = @one . lxcrc
100- lxcrc . merge! ( :id_map => 0 ) if @one . privileged?
101-
102103 @one . disks . each do |disk |
103- if disk . mount ( lxcrc )
104+ if disk . mount ( @driver_config )
104105 mounted << disk
105106 else
106107 error = true
@@ -141,7 +142,7 @@ def start
141142 return false
142143 end
143144
144- return true if wait_deploy ( 5 )
145+ return true if wait_deploy ( 5 ) && configure_pci_nics
145146
146147 clean ( true )
147148 return false
@@ -159,23 +160,48 @@ def shutdown
159160 def reboot
160161 rc = @client . stop ( @name )
161162
162- # Remove nic from ovs-switch if needed
163- @one . get_nics . each do |nic |
164- del_bridge_port ( nic ) # network driver matching implemented here
165- end
163+ # # avoid bind mounts on new container restart
164+ # @one.disks.each {|d| return false unless d.clean_live }
165+
166+ # lxc-stop doesn't remove ovs host side nic
167+ @one . nics . each { |n | @one . del_bridge_port ( n ) }
166168
167169 return false unless rc
168170
169- @client . start ( @name )
171+ # Make sure container starts with an updated configuration file
172+ file = Tempfile . new
173+ file . write ( @one . to_lxc )
174+ file . flush
175+ file . rewind
176+
177+ # starting without ovs host side nic removal results in error
178+ @client . start ( @name , { :rcfile => file . path } )
179+ wait_deploy ( 5 )
170180 end
171181
182+ #---------------------------------------------------------------------------
183+ # Storage
184+ #---------------------------------------------------------------------------
185+
172186 def clean ( ignore_err = false )
187+ disks = @one . disks
188+
189+ # sort disks so rootfs is last during cleanup operations
190+ disks . each do |disk |
191+ next unless disk . rootfs?
192+
193+ disks << disks . delete ( disk )
194+ break
195+ end
196+
197+ # rubocop:disable Style/CombinableLoops
173198 # Unmap storage
174- @one . disks . each do |disk |
199+ disks . each do |disk |
175200 rc = disk . umount ( { :ignore_err => ignore_err } )
176201
177202 return false if ignore_err != true && !rc
178203 end
204+ # rubocop:enable Style/CombinableLoops
179205
180206 # Clean bindpoint
181207 FileUtils . rm_rf ( @one . bind_folder ) if Dir . exist? ( @one . bind_folder )
@@ -184,6 +210,92 @@ def clean(ignore_err = false)
184210 @client . destroy ( @name ) if @client . list . include? ( @name )
185211 end
186212
213+ def attach_disk ( id = nil , guest_path = nil )
214+ id ||= @one . hotplug_disk_id
215+ guest_path ||= "/#{ @driver_config [ :mountopts ] [ :mountpoint ] . gsub ( '$id' , id . to_s ) } "
216+
217+ disk = @one . disk ( id )
218+ container_path = @one . bind_mount_path ( guest_path )
219+
220+ disk . mount ( @driver_config )
221+ disk . pass_mount ( container_path )
222+ end
223+
224+ def detach_disk ( id = nil )
225+ id ||= @one . hotplug_disk_id
226+ disk = @one . disk ( id )
227+
228+ # umount the entry inside the container
229+ cmd = "umount #{ disk . mountpoint } "
230+ return false unless execute ( cmd )
231+
232+ disk . umount
233+ end
234+
235+ def attach_context
236+ return true unless @one . has_context?
237+
238+ id = @one . context_id
239+ guest_path = '/context'
240+
241+ attach_disk ( id , guest_path )
242+ end
243+
244+ def detach_context
245+ return true unless @one . has_context?
246+
247+ detach_disk ( @one . context_id )
248+ end
249+
250+ #---------------------------------------------------------------------------
251+ # Network
252+ #---------------------------------------------------------------------------
253+
254+ def attach_nic ( mac )
255+ if @one . pci_attach?
256+ nic = @one . hotplug_pci
257+ pci_nic = @one . nic_name_by_address ( nic [ 'ADDRESS' ] )
258+
259+ @client . attach_device ( @name , pci_nic )
260+ else
261+ nic = @one . nic_by_mac ( mac )
262+
263+ veth_peer = @one . create_veth_pair ( nic )
264+ return false unless veth_peer
265+
266+ if !@one . add_bridge_port ( nic )
267+ @one . delete_nic ( nic [ 'TARGET' ] )
268+ return false
269+ end
270+
271+ return true if @client . attach_device ( @name , LXCVM . veth_peer ( nic ) ,
272+ LXCVM . nic_guest ( nic ) )
273+
274+ @one . del_bridge_port ( nic )
275+ @one . delete_nic ( nic [ 'TARGET' ] )
276+ false
277+ end
278+ end
279+
280+ def detach_nic ( mac )
281+ if @one . pci_attach?
282+ nic = @one . hotplug_pci
283+ pci_nic = nic_name_by_short_address ( nic [ 'SHORT_ADDRESS' ] )
284+
285+ @client . detach_device ( @name , pci_nic )
286+ else
287+ nic = @one . nic_by_mac ( mac )
288+
289+ return false unless @client . detach_device ( @name , LXCVM . nic_guest ( nic ) ,
290+ LXCVM . veth_peer ( nic ) )
291+
292+ return true if !@one . del_bridge_port ( nic )
293+
294+ @one . delete_nic ( nic [ 'TARGET' ] )
295+ true
296+ end
297+ end
298+
187299 #---------------------------------------------------------------------------
188300 # VNC
189301 #---------------------------------------------------------------------------
@@ -192,8 +304,64 @@ def vnc(signal)
192304 @one . vnc ( signal , @one . lxcrc [ :vnc ] [ :command ] , @one . lxcrc [ :vnc ] )
193305 end
194306
307+ #---------------------------------------------------------------------------
308+ # Command Injection
309+ #---------------------------------------------------------------------------
310+
311+ def restart_context
312+ cmd = 'service one-context-reconfigure restart'
313+ execute ( cmd , true )
314+
315+ configure_pci_nics
316+ end
317+
318+ # Trigger context pci configuration script without translated PCI device address
319+ # one-context will fail to setup device due to VM_ADDRESS
320+ def configure_pci_nics
321+ pci_nics = @one . pci_nics
322+
323+ return true if pci_nics . empty?
324+
325+ cmd = 'set -a; source /context/context.sh;'
326+ pci_nics . each do |nic |
327+ # override VM_ADDRESS
328+ id = nic [ 'NIC_ID' ]
329+ address = nic [ 'SHORT_ADDRESS' ]
330+
331+ cmd << " export PCI#{ id } _ADDRESS=#{ address } ;"
332+ end
333+ # find -lname fails on alpine. -lname not an option
334+ cmd << ' /etc/one-context.d/loc-10-network-pci local'
335+
336+ bash ( cmd )
337+ true
338+ end
339+
340+ def execute ( cmd , detach = false )
341+ @client . attach ( @name , cmd , { } , detach )
342+ end
343+
344+ def bash ( cmd )
345+ @client . bash ( @name , cmd , { } )
346+ end
347+
195348 private
196349
350+ def get_pci_nic_name_by_short_address ( address )
351+ # find -lname fails on alpine. -lname not an option
352+ cmd = "find /sys/class/net/*/device -lname *#{ address } " # same as loc-10-network-pci lookup
353+
354+ rc , o , _e = bash ( cmd )
355+
356+ if rc != 0
357+ msg = "#{ __method__ } PCI Device #{ address } not found inside container"
358+ OpenNebula ::DriverLogger msg
359+ return false
360+ end
361+
362+ o . split ( '/' ) [ 4 ] # /sys/class/net/dev159/device
363+ end
364+
197365 # Waits for the container to be RUNNING
198366 # @param timeout[Integer] seconds to wait for the conatiner to start
199367 def wait_deploy ( timeout )
@@ -207,18 +375,4 @@ def wait_deploy(timeout)
207375 running?
208376 end
209377
210- def del_bridge_port ( nic )
211- return true unless /ovswitch/ =~ nic [ 'VN_MAD' ]
212-
213- cmd = 'sudo -n ovs-vsctl --if-exists del-port ' \
214- "#{ nic [ 'BRIDGE' ] } #{ nic [ 'TARGET' ] } "
215-
216- rc , _o , e = Command . execute ( cmd , false , 1 )
217-
218- return true if rc . zero?
219-
220- OpenNebula ::DriverLogger . log_error "#{ __method__ } : #{ e } "
221- false
222- end
223-
224378end
0 commit comments