1 # -*- coding: utf-8 -*-
4 # Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
5 # This file is part of openvim
8 # Licensed under the Apache License, Version 2.0 (the "License"); you may
9 # not use this file except in compliance with the License. You may obtain
10 # a copy of the License at
12 # http://www.apache.org/licenses/LICENSE-2.0
14 # Unless required by applicable law or agreed to in writing, software
15 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17 # License for the specific language governing permissions and limitations
20 # For those usages not covered by the Apache License, Version 2.0 please
21 # contact with: nfvlabs@tid.es
25 This is thread that interact with the host and the libvirt to manage VM
26 One thread will be launched per host
28 __author__
= "Pablo Montes, Alfonso Tierno, Leonardo Mirabal"
29 __date__
= "$10-jul-2014 12:07:15$"
43 from jsonschema
import validate
as js_v
, exceptions
as js_e
44 from vim_schema
import localinfo_schema
, hostinfo_schema
47 class host_thread(threading
.Thread
):
50 def __init__(self
, name
, host
, user
, db
, db_lock
, test
, image_path
, host_id
, version
, develop_mode
,
51 develop_bridge_iface
, password
=None, keyfile
= None, logger_name
=None, debug
=None):
56 'host','user': host ip or name to manage and user
57 'db', 'db_lock': database class and lock to use it in exclusion
59 threading
.Thread
.__init
__(self
)
64 self
.db_lock
= db_lock
66 self
.password
= password
67 self
.keyfile
= keyfile
68 self
.localinfo_dirty
= False
70 if not test
and not host_thread
.lvirt_module
:
72 module_info
= imp
.find_module("libvirt")
73 host_thread
.lvirt_module
= imp
.load_module("libvirt", *module_info
)
74 except (IOError, ImportError) as e
:
75 raise ImportError("Cannot import python-libvirt. Openvim not properly installed" +str(e
))
77 self
.logger_name
= logger_name
79 self
.logger_name
= "openvim.host."+name
80 self
.logger
= logging
.getLogger(self
.logger_name
)
82 self
.logger
.setLevel(getattr(logging
, debug
))
85 self
.develop_mode
= develop_mode
86 self
.develop_bridge_iface
= develop_bridge_iface
87 self
.image_path
= image_path
88 self
.empty_image_path
= image_path
89 self
.host_id
= host_id
90 self
.version
= version
95 self
.server_status
= {} #dictionary with pairs server_uuid:server_status
96 self
.pending_terminate_server
=[] #list with pairs (time,server_uuid) time to send a terminate for a server being destroyed
97 self
.next_update_server_status
= 0 #time when must be check servers status
101 self
.queueLock
= threading
.Lock()
102 self
.taskQueue
= Queue
.Queue(2000)
104 self
.connectivity
= True
105 self
.lvirt_conn_uri
= "qemu+ssh://{user}@{host}/system?no_tty=1&no_verify=1".format(
106 user
=self
.user
, host
=self
.host
)
108 self
.lvirt_conn_uri
+= "&keyfile=" + keyfile
110 def ssh_connect(self
):
113 self
.ssh_conn
= paramiko
.SSHClient()
114 self
.ssh_conn
.set_missing_host_key_policy(paramiko
.AutoAddPolicy())
115 self
.ssh_conn
.load_system_host_keys()
116 self
.ssh_conn
.connect(self
.host
, username
=self
.user
, password
=self
.password
, key_filename
=self
.keyfile
,
118 except paramiko
.ssh_exception
.SSHException
as e
:
120 self
.logger
.error("ssh_connect ssh Exception: " + text
)
122 def check_connectivity(self
):
126 if not self
.ssh_conn
:
129 command
= 'sudo brctl show'
130 (_
, stdout
, stderr
) = self
.ssh_conn
.exec_command(command
, timeout
=10)
131 content
= stderr
.read()
133 self
.connectivity
= False
134 self
.logger
.error("ssh conection error")
135 except paramiko
.ssh_exception
.SSHException
as e
:
137 self
.connectivity
= False
138 self
.logger
.error("ssh_connect ssh Exception: " + text
)
139 raise paramiko
.ssh_exception
.SSHException("ssh error conection")
140 except Exception as e
:
141 self
.connectivity
= False
142 raise paramiko
.ssh_exception
.SSHException("ssh error conection")
144 def load_localinfo(self
):
150 command
= 'mkdir -p ' + self
.image_path
151 # print self.name, ': command:', command
152 (_
, stdout
, stderr
) = self
.ssh_conn
.exec_command(command
)
153 content
= stderr
.read()
155 self
.logger
.error("command: '%s' stderr: '%s'", command
, content
)
157 command
= 'cat ' + self
.image_path
+ '/.openvim.yaml'
158 # print self.name, ': command:', command
159 (_
, stdout
, stderr
) = self
.ssh_conn
.exec_command(command
)
160 content
= stdout
.read()
161 if len(content
) == 0:
162 self
.logger
.error("command: '%s' stderr='%s'", command
, stderr
.read())
163 raise paramiko
.ssh_exception
.SSHException("Error empty file, command: '{}'".format(command
))
164 self
.localinfo
= yaml
.load(content
)
165 js_v(self
.localinfo
, localinfo_schema
)
166 self
.localinfo_dirty
= False
167 if 'server_files' not in self
.localinfo
:
168 self
.localinfo
['server_files'] = {}
169 self
.logger
.debug("localinfo load from host")
172 except paramiko
.ssh_exception
.SSHException
as e
:
174 self
.logger
.error("load_localinfo ssh Exception: " + text
)
175 except host_thread
.lvirt_module
.libvirtError
as e
:
176 text
= e
.get_error_message()
177 self
.logger
.error("load_localinfo libvirt Exception: " + text
)
178 except yaml
.YAMLError
as exc
:
180 if hasattr(exc
, 'problem_mark'):
181 mark
= exc
.problem_mark
182 text
= " at position: (%s:%s)" % (mark
.line
+1, mark
.column
+1)
183 self
.logger
.error("load_localinfo yaml format Exception " + text
)
184 except js_e
.ValidationError
as e
:
186 if len(e
.path
)>0: text
=" at '" + ":".join(map(str, e
.path
))+"'"
187 self
.logger
.error("load_localinfo format Exception: %s %s", text
, str(e
))
188 except Exception as e
:
190 self
.logger
.error("load_localinfo Exception: " + text
)
192 #not loaded, insert a default data and force saving by activating dirty flag
193 self
.localinfo
= {'files':{}, 'server_files':{} }
194 #self.localinfo_dirty=True
195 self
.localinfo_dirty
=False
197 def load_hostinfo(self
):
205 command
= 'cat ' + self
.image_path
+ '/hostinfo.yaml'
206 #print self.name, ': command:', command
207 (_
, stdout
, stderr
) = self
.ssh_conn
.exec_command(command
)
208 content
= stdout
.read()
209 if len(content
) == 0:
210 self
.logger
.error("command: '%s' stderr: '%s'", command
, stderr
.read())
211 raise paramiko
.ssh_exception
.SSHException("Error empty file ")
212 self
.hostinfo
= yaml
.load(content
)
213 js_v(self
.hostinfo
, hostinfo_schema
)
214 self
.logger
.debug("hostlinfo load from host " + str(self
.hostinfo
))
217 except paramiko
.ssh_exception
.SSHException
as e
:
219 self
.logger
.error("load_hostinfo ssh Exception: " + text
)
220 except host_thread
.lvirt_module
.libvirtError
as e
:
221 text
= e
.get_error_message()
222 self
.logger
.error("load_hostinfo libvirt Exception: " + text
)
223 except yaml
.YAMLError
as exc
:
225 if hasattr(exc
, 'problem_mark'):
226 mark
= exc
.problem_mark
227 text
= " at position: (%s:%s)" % (mark
.line
+1, mark
.column
+1)
228 self
.logger
.error("load_hostinfo yaml format Exception " + text
)
229 except js_e
.ValidationError
as e
:
231 if len(e
.path
)>0: text
=" at '" + ":".join(map(str, e
.path
))+"'"
232 self
.logger
.error("load_hostinfo format Exception: %s %s", text
, e
.message
)
233 except Exception as e
:
235 self
.logger
.error("load_hostinfo Exception: " + text
)
237 #not loaded, insert a default data
240 def save_localinfo(self
, tries
=3):
242 self
.localinfo_dirty
= False
249 command
= 'cat > ' + self
.image_path
+ '/.openvim.yaml'
250 self
.logger
.debug("command:" + command
)
251 (stdin
, _
, _
) = self
.ssh_conn
.exec_command(command
)
252 yaml
.safe_dump(self
.localinfo
, stdin
, explicit_start
=True, indent
=4, default_flow_style
=False, tags
=False, encoding
='utf-8', allow_unicode
=True)
254 self
.localinfo_dirty
= False
257 except paramiko
.ssh_exception
.SSHException
as e
:
259 self
.logger
.error("save_localinfo ssh Exception: " + text
)
260 if "SSH session not active" in text
:
262 except host_thread
.lvirt_module
.libvirtError
as e
:
263 text
= e
.get_error_message()
264 self
.logger
.error("save_localinfo libvirt Exception: " + text
)
265 except yaml
.YAMLError
as exc
:
267 if hasattr(exc
, 'problem_mark'):
268 mark
= exc
.problem_mark
269 text
= " at position: (%s:%s)" % (mark
.line
+1, mark
.column
+1)
270 self
.logger
.error("save_localinfo yaml format Exception " + text
)
271 except Exception as e
:
273 self
.logger
.error("save_localinfo Exception: " + text
)
275 def load_servers_from_db(self
):
276 self
.db_lock
.acquire()
277 r
,c
= self
.db
.get_table(SELECT
=('uuid','status', 'image_id'), FROM
='instances', WHERE
={'host_id': self
.host_id
})
278 self
.db_lock
.release()
280 self
.server_status
= {}
282 self
.logger
.error("Error getting data from database: " + c
)
285 self
.server_status
[ server
['uuid'] ] = server
['status']
287 #convert from old version to new one
288 if 'inc_files' in self
.localinfo
and server
['uuid'] in self
.localinfo
['inc_files']:
289 server_files_dict
= {'source file': self
.localinfo
['inc_files'][ server
['uuid'] ] [0], 'file format':'raw' }
290 if server_files_dict
['source file'][-5:] == 'qcow2':
291 server_files_dict
['file format'] = 'qcow2'
293 self
.localinfo
['server_files'][ server
['uuid'] ] = { server
['image_id'] : server_files_dict
}
294 if 'inc_files' in self
.localinfo
:
295 del self
.localinfo
['inc_files']
296 self
.localinfo_dirty
= True
298 def delete_unused_files(self
):
299 '''Compares self.localinfo['server_files'] content with real servers running self.server_status obtained from database
300 Deletes unused entries at self.loacalinfo and the corresponding local files.
301 The only reason for this mismatch is the manual deletion of instances (VM) at database
305 for uuid
,images
in self
.localinfo
['server_files'].items():
306 if uuid
not in self
.server_status
:
307 for localfile
in images
.values():
309 self
.logger
.debug("deleting file '%s' of unused server '%s'", localfile
['source file'], uuid
)
310 self
.delete_file(localfile
['source file'])
311 except paramiko
.ssh_exception
.SSHException
as e
:
312 self
.logger
.error("Exception deleting file '%s': %s", localfile
['source file'], str(e
))
313 del self
.localinfo
['server_files'][uuid
]
314 self
.localinfo_dirty
= True
316 def insert_task(self
, task
, *aditional
):
318 self
.queueLock
.acquire()
319 task
= self
.taskQueue
.put( (task
,) + aditional
, timeout
=5)
320 self
.queueLock
.release()
323 return -1, "timeout inserting a task over host " + self
.name
327 self
.load_localinfo()
329 self
.load_servers_from_db()
330 self
.delete_unused_files()
333 self
.queueLock
.acquire()
334 if not self
.taskQueue
.empty():
335 task
= self
.taskQueue
.get()
338 self
.queueLock
.release()
342 if self
.localinfo_dirty
:
343 self
.save_localinfo()
344 elif self
.next_update_server_status
< now
:
345 self
.update_servers_status()
346 self
.next_update_server_status
= now
+ 5
347 elif len(self
.pending_terminate_server
)>0 and self
.pending_terminate_server
[0][0]<now
:
348 self
.server_forceoff()
353 if task
[0] == 'instance':
354 self
.logger
.debug("processing task instance " + str(task
[1]['action']))
358 r
= self
.action_on_server(task
[1], retry
==2)
361 elif task
[0] == 'image':
363 elif task
[0] == 'exit':
364 self
.logger
.debug("processing task exit")
367 elif task
[0] == 'reload':
368 self
.logger
.debug("processing task reload terminating and relaunching")
371 elif task
[0] == 'edit-iface':
372 self
.logger
.debug("processing task edit-iface port={}, old_net={}, new_net={}".format(
373 task
[1], task
[2], task
[3]))
374 self
.edit_iface(task
[1], task
[2], task
[3])
375 elif task
[0] == 'restore-iface':
376 self
.logger
.debug("processing task restore-iface={} mac={}".format(task
[1], task
[2]))
377 self
.restore_iface(task
[1], task
[2])
378 elif task
[0] == 'new-ovsbridge':
379 self
.logger
.debug("Creating compute OVS bridge")
380 self
.create_ovs_bridge()
381 elif task
[0] == 'new-vxlan':
382 self
.logger
.debug("Creating vxlan tunnel='{}', remote ip='{}'".format(task
[1], task
[2]))
383 self
.create_ovs_vxlan_tunnel(task
[1], task
[2])
384 elif task
[0] == 'del-ovsbridge':
385 self
.logger
.debug("Deleting OVS bridge")
386 self
.delete_ovs_bridge()
387 elif task
[0] == 'del-vxlan':
388 self
.logger
.debug("Deleting vxlan {} tunnel".format(task
[1]))
389 self
.delete_ovs_vxlan_tunnel(task
[1])
390 elif task
[0] == 'create-ovs-bridge-port':
391 self
.logger
.debug("Adding port ovim-{} to OVS bridge".format(task
[1]))
392 self
.create_ovs_bridge_port(task
[1])
393 elif task
[0] == 'del-ovs-port':
394 self
.logger
.debug("Delete bridge attached to ovs port vlan {} net {}".format(task
[1], task
[2]))
395 self
.delete_bridge_port_attached_to_ovs(task
[1], task
[2])
397 self
.logger
.debug("unknown task " + str(task
))
399 except Exception as e
:
400 self
.logger
.critical("Unexpected exception at run: " + str(e
), exc_info
=True)
402 def server_forceoff(self
, wait_until_finished
=False):
403 while len(self
.pending_terminate_server
)>0:
405 if self
.pending_terminate_server
[0][0]>now
:
406 if wait_until_finished
:
411 req
={'uuid':self
.pending_terminate_server
[0][1],
412 'action':{'terminate':'force'},
415 self
.action_on_server(req
)
416 self
.pending_terminate_server
.pop(0)
420 self
.server_forceoff(True)
421 if self
.localinfo_dirty
:
422 self
.save_localinfo()
424 self
.ssh_conn
.close()
425 except Exception as e
:
427 self
.logger
.error("terminate Exception: " + text
)
428 self
.logger
.debug("exit from host_thread")
430 def get_local_iface_name(self
, generic_name
):
431 if self
.hostinfo
!= None and "iface_names" in self
.hostinfo
and generic_name
in self
.hostinfo
["iface_names"]:
432 return self
.hostinfo
["iface_names"][generic_name
]
435 def create_xml_server(self
, server
, dev_list
, server_metadata
={}):
436 """Function that implements the generation of the VM XML definition.
437 Additional devices are in dev_list list
438 The main disk is upon dev_list[0]"""
440 #get if operating system is Windows
442 os_type
= server_metadata
.get('os_type', None)
443 if os_type
== None and 'metadata' in dev_list
[0]:
444 os_type
= dev_list
[0]['metadata'].get('os_type', None)
445 if os_type
!= None and os_type
.lower() == "windows":
447 #get type of hard disk bus
448 bus_ide
= True if windows_os
else False
449 bus
= server_metadata
.get('bus', None)
450 if bus
== None and 'metadata' in dev_list
[0]:
451 bus
= dev_list
[0]['metadata'].get('bus', None)
453 bus_ide
= True if bus
=='ide' else False
457 text
= "<domain type='kvm'>"
459 topo
= server_metadata
.get('topology', None)
460 if topo
== None and 'metadata' in dev_list
[0]:
461 topo
= dev_list
[0]['metadata'].get('topology', None)
463 name
= server
.get('name', '')[:28] + "_" + server
['uuid'][:28] #qemu impose a length limit of 59 chars or not start. Using 58
464 text
+= self
.inc_tab() + "<name>" + name
+ "</name>"
466 text
+= self
.tab() + "<uuid>" + server
['uuid'] + "</uuid>"
469 if 'extended' in server
and server
['extended']!=None and 'numas' in server
['extended']:
470 numa
= server
['extended']['numas'][0]
473 memory
= int(numa
.get('memory',0))*1024*1024 #in KiB
475 memory
= int(server
['ram'])*1024;
477 if not self
.develop_mode
:
480 return -1, 'No memory assigned to instance'
482 text
+= self
.tab() + "<memory unit='KiB'>" +memory
+"</memory>"
483 text
+= self
.tab() + "<currentMemory unit='KiB'>" +memory
+ "</currentMemory>"
485 text
+= self
.tab()+'<memoryBacking>'+ \
486 self
.inc_tab() + '<hugepages/>'+ \
487 self
.dec_tab()+ '</memoryBacking>'
490 use_cpu_pinning
=False
491 vcpus
= int(server
.get("vcpus",0))
493 if 'cores-source' in numa
:
495 for index
in range(0, len(numa
['cores-source'])):
496 cpu_pinning
.append( [ numa
['cores-id'][index
], numa
['cores-source'][index
] ] )
498 if 'threads-source' in numa
:
500 for index
in range(0, len(numa
['threads-source'])):
501 cpu_pinning
.append( [ numa
['threads-id'][index
], numa
['threads-source'][index
] ] )
503 if 'paired-threads-source' in numa
:
505 for index
in range(0, len(numa
['paired-threads-source'])):
506 cpu_pinning
.append( [numa
['paired-threads-id'][index
][0], numa
['paired-threads-source'][index
][0] ] )
507 cpu_pinning
.append( [numa
['paired-threads-id'][index
][1], numa
['paired-threads-source'][index
][1] ] )
510 if use_cpu_pinning
and not self
.develop_mode
:
511 text
+= self
.tab()+"<vcpu placement='static'>" +str(len(cpu_pinning
)) +"</vcpu>" + \
512 self
.tab()+'<cputune>'
514 for i
in range(0, len(cpu_pinning
)):
515 text
+= self
.tab() + "<vcpupin vcpu='" +str(cpu_pinning
[i
][0])+ "' cpuset='" +str(cpu_pinning
[i
][1]) +"'/>"
516 text
+= self
.dec_tab()+'</cputune>'+ \
517 self
.tab() + '<numatune>' +\
518 self
.inc_tab() + "<memory mode='strict' nodeset='" +str(numa
['source'])+ "'/>" +\
519 self
.dec_tab() + '</numatune>'
522 return -1, "Instance without number of cpus"
523 text
+= self
.tab()+"<vcpu>" + str(vcpus
) + "</vcpu>"
528 if dev
['type']=='cdrom' :
531 text
+= self
.tab()+ '<os>' + \
532 self
.inc_tab() + "<type arch='x86_64' machine='pc'>hvm</type>"
534 text
+= self
.tab() + "<boot dev='cdrom'/>"
535 text
+= self
.tab() + "<boot dev='hd'/>" + \
536 self
.dec_tab()+'</os>'
538 text
+= self
.tab()+'<features>'+\
539 self
.inc_tab()+'<acpi/>' +\
540 self
.tab()+'<apic/>' +\
541 self
.tab()+'<pae/>'+ \
542 self
.dec_tab() +'</features>'
543 if topo
== "oneSocket:hyperthreading":
545 return -1, 'Cannot expose hyperthreading with an odd number of vcpus'
546 text
+= self
.tab() + "<cpu mode='host-model'> <topology sockets='1' cores='%d' threads='2' /> </cpu>" % (vcpus
/2)
547 elif windows_os
or topo
== "oneSocket":
548 text
+= self
.tab() + "<cpu mode='host-model'> <topology sockets='1' cores='%d' threads='1' /> </cpu>" % vcpus
550 text
+= self
.tab() + "<cpu mode='host-model'></cpu>"
551 text
+= self
.tab() + "<clock offset='utc'/>" +\
552 self
.tab() + "<on_poweroff>preserve</on_poweroff>" + \
553 self
.tab() + "<on_reboot>restart</on_reboot>" + \
554 self
.tab() + "<on_crash>restart</on_crash>"
555 text
+= self
.tab() + "<devices>" + \
556 self
.inc_tab() + "<emulator>/usr/libexec/qemu-kvm</emulator>" + \
557 self
.tab() + "<serial type='pty'>" +\
558 self
.inc_tab() + "<target port='0'/>" + \
559 self
.dec_tab() + "</serial>" +\
560 self
.tab() + "<console type='pty'>" + \
561 self
.inc_tab()+ "<target type='serial' port='0'/>" + \
562 self
.dec_tab()+'</console>'
564 text
+= self
.tab() + "<controller type='usb' index='0'/>" + \
565 self
.tab() + "<controller type='ide' index='0'/>" + \
566 self
.tab() + "<input type='mouse' bus='ps2'/>" + \
567 self
.tab() + "<sound model='ich6'/>" + \
568 self
.tab() + "<video>" + \
569 self
.inc_tab() + "<model type='cirrus' vram='9216' heads='1'/>" + \
570 self
.dec_tab() + "</video>" + \
571 self
.tab() + "<memballoon model='virtio'/>" + \
572 self
.tab() + "<input type='tablet' bus='usb'/>" #TODO revisar
574 #> self.tab()+'<alias name=\'hostdev0\'/>\n' +\
575 #> self.dec_tab()+'</hostdev>\n' +\
576 #> self.tab()+'<input type=\'tablet\' bus=\'usb\'/>\n'
578 text
+= self
.tab() + "<graphics type='vnc' port='-1' autoport='yes'/>"
580 #If image contains 'GRAPH' include graphics
581 #if 'GRAPH' in image:
582 text
+= self
.tab() + "<graphics type='vnc' port='-1' autoport='yes' listen='0.0.0.0'>" +\
583 self
.inc_tab() + "<listen type='address' address='0.0.0.0'/>" +\
584 self
.dec_tab() + "</graphics>"
588 bus_ide_dev
= bus_ide
589 if dev
['type']=='cdrom' or dev
['type']=='disk':
590 if dev
['type']=='cdrom':
592 text
+= self
.tab() + "<disk type='file' device='"+dev
['type']+"'>"
593 if 'file format' in dev
:
594 text
+= self
.inc_tab() + "<driver name='qemu' type='" +dev
['file format']+ "' cache='writethrough'/>"
595 if 'source file' in dev
:
596 text
+= self
.tab() + "<source file='" +dev
['source file']+ "'/>"
597 #elif v['type'] == 'block':
598 # text += self.tab() + "<source dev='" + v['source'] + "'/>"
600 # return -1, 'Unknown disk type ' + v['type']
601 vpci
= dev
.get('vpci',None)
602 if vpci
== None and 'metadata' in dev
:
603 vpci
= dev
['metadata'].get('vpci',None)
604 text
+= self
.pci2xml(vpci
)
607 text
+= self
.tab() + "<target dev='hd" +vd_index
+ "' bus='ide'/>" #TODO allows several type of disks
609 text
+= self
.tab() + "<target dev='vd" +vd_index
+ "' bus='virtio'/>"
610 text
+= self
.dec_tab() + '</disk>'
611 vd_index
= chr(ord(vd_index
)+1)
612 elif dev
['type']=='xml':
613 dev_text
= dev
['xml']
615 dev_text
= dev_text
.replace('__vpci__', dev
['vpci'])
616 if 'source file' in dev
:
617 dev_text
= dev_text
.replace('__file__', dev
['source file'])
618 if 'file format' in dev
:
619 dev_text
= dev_text
.replace('__format__', dev
['source file'])
620 if '__dev__' in dev_text
:
621 dev_text
= dev_text
.replace('__dev__', vd_index
)
622 vd_index
= chr(ord(vd_index
)+1)
625 return -1, 'Unknown device type ' + dev
['type']
628 bridge_interfaces
= server
.get('networks', [])
629 for v
in bridge_interfaces
:
631 self
.db_lock
.acquire()
632 result
, content
= self
.db
.get_table(FROM
='nets', SELECT
=('provider',),WHERE
={'uuid':v
['net_id']} )
633 self
.db_lock
.release()
635 self
.logger
.error("create_xml_server ERROR %d getting nets %s", result
, content
)
637 #ALF: Allow by the moment the 'default' bridge net because is confortable for provide internet to VM
638 #I know it is not secure
639 #for v in sorted(desc['network interfaces'].itervalues()):
640 model
= v
.get("model", None)
641 if content
[0]['provider']=='default':
642 text
+= self
.tab() + "<interface type='network'>" + \
643 self
.inc_tab() + "<source network='" +content
[0]['provider']+ "'/>"
644 elif content
[0]['provider'][0:7]=='macvtap':
645 text
+= self
.tab()+"<interface type='direct'>" + \
646 self
.inc_tab() + "<source dev='" + self
.get_local_iface_name(content
[0]['provider'][8:]) + "' mode='bridge'/>" + \
647 self
.tab() + "<target dev='macvtap0'/>"
649 text
+= self
.tab() + "<alias name='net" + str(net_nb
) + "'/>"
652 elif content
[0]['provider'][0:6]=='bridge':
653 text
+= self
.tab() + "<interface type='bridge'>" + \
654 self
.inc_tab()+"<source bridge='" +self
.get_local_iface_name(content
[0]['provider'][7:])+ "'/>"
656 text
+= self
.tab() + "<target dev='vnet" + str(net_nb
)+ "'/>" +\
657 self
.tab() + "<alias name='net" + str(net_nb
)+ "'/>"
660 elif content
[0]['provider'][0:3] == "OVS":
661 vlan
= content
[0]['provider'].replace('OVS:', '')
662 text
+= self
.tab() + "<interface type='bridge'>" + \
663 self
.inc_tab() + "<source bridge='ovim-" + str(vlan
) + "'/>"
665 return -1, 'Unknown Bridge net provider ' + content
[0]['provider']
667 text
+= self
.tab() + "<model type='" +model
+ "'/>"
668 if v
.get('mac_address', None) != None:
669 text
+= self
.tab() +"<mac address='" +v
['mac_address']+ "'/>"
670 text
+= self
.pci2xml(v
.get('vpci',None))
671 text
+= self
.dec_tab()+'</interface>'
675 interfaces
= numa
.get('interfaces', [])
679 if self
.develop_mode
: #map these interfaces to bridges
680 text
+= self
.tab() + "<interface type='bridge'>" + \
681 self
.inc_tab()+"<source bridge='" +self
.develop_bridge_iface
+ "'/>"
683 text
+= self
.tab() + "<target dev='vnet" + str(net_nb
)+ "'/>" +\
684 self
.tab() + "<alias name='net" + str(net_nb
)+ "'/>"
686 text
+= self
.tab() + "<model type='e1000'/>" #e1000 is more probable to be supported than 'virtio'
687 if v
.get('mac_address', None) != None:
688 text
+= self
.tab() +"<mac address='" +v
['mac_address']+ "'/>"
689 text
+= self
.pci2xml(v
.get('vpci',None))
690 text
+= self
.dec_tab()+'</interface>'
693 if v
['dedicated'] == 'yes': #passthrought
694 text
+= self
.tab() + "<hostdev mode='subsystem' type='pci' managed='yes'>" + \
695 self
.inc_tab() + "<source>"
697 text
+= self
.pci2xml(v
['source'])
698 text
+= self
.dec_tab()+'</source>'
699 text
+= self
.pci2xml(v
.get('vpci',None))
701 text
+= self
.tab() + "<alias name='hostdev" + str(net_nb
) + "'/>"
702 text
+= self
.dec_tab()+'</hostdev>'
704 else: #sriov_interfaces
705 #skip not connected interfaces
706 if v
.get("net_id") == None:
708 text
+= self
.tab() + "<interface type='hostdev' managed='yes'>"
710 if v
.get('mac_address', None) != None:
711 text
+= self
.tab() + "<mac address='" +v
['mac_address']+ "'/>"
712 text
+= self
.tab()+'<source>'
714 text
+= self
.pci2xml(v
['source'])
715 text
+= self
.dec_tab()+'</source>'
716 if v
.get('vlan',None) != None:
717 text
+= self
.tab() + "<vlan> <tag id='" + str(v
['vlan']) + "'/> </vlan>"
718 text
+= self
.pci2xml(v
.get('vpci',None))
720 text
+= self
.tab() + "<alias name='hostdev" + str(net_nb
) + "'/>"
721 text
+= self
.dec_tab()+'</interface>'
724 text
+= self
.dec_tab()+'</devices>'+\
725 self
.dec_tab()+'</domain>'
728 def pci2xml(self
, pci
):
729 '''from a pci format text XXXX:XX:XX.X generates the xml content of <address>
730 alows an empty pci text'''
733 first_part
= pci
.split(':')
734 second_part
= first_part
[2].split('.')
735 return self
.tab() + "<address type='pci' domain='0x" + first_part
[0] + \
736 "' bus='0x" + first_part
[1] + "' slot='0x" + second_part
[0] + \
737 "' function='0x" + second_part
[1] + "'/>"
740 """Return indentation according to xml_level"""
741 return "\n" + (' '*self
.xml_level
)
744 """Increment and return indentation according to xml_level"""
749 """Decrement and return indentation according to xml_level"""
753 def create_ovs_bridge(self
):
755 Create a bridge in compute OVS to allocate VMs
756 :return: True if success
758 if self
.test
or not self
.connectivity
:
763 command
= 'sudo ovs-vsctl --may-exist add-br br-int -- set Bridge br-int stp_enable=true'
764 self
.logger
.debug("command: " + command
)
765 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
766 content
= stdout
.read()
767 if len(content
) == 0:
771 except paramiko
.ssh_exception
.SSHException
as e
:
772 self
.logger
.error("create_ovs_bridge ssh Exception: " + str(e
))
773 if "SSH session not active" in str(e
):
777 def delete_port_to_ovs_bridge(self
, vlan
, net_uuid
):
779 Delete linux bridge port attched to a OVS bridge, if port is not free the port is not removed
780 :param vlan: vlan port id
781 :param net_uuid: network id
785 if self
.test
or not self
.connectivity
:
788 port_name
= 'ovim-' + str(vlan
)
789 command
= 'sudo ovs-vsctl del-port br-int ' + port_name
790 self
.logger
.debug("command: " + command
)
791 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
792 content
= stdout
.read()
793 if len(content
) == 0:
797 except paramiko
.ssh_exception
.SSHException
as e
:
798 self
.logger
.error("delete_port_to_ovs_bridge ssh Exception: " + str(e
))
799 if "SSH session not active" in str(e
):
803 def delete_dhcp_server(self
, vlan
, net_uuid
, dhcp_path
):
805 Delete dhcp server process lining in namespace
806 :param vlan: segmentation id
807 :param net_uuid: network uuid
808 :param dhcp_path: conf fiel path that live in namespace side
811 if self
.test
or not self
.connectivity
:
813 if not self
.is_dhcp_port_free(vlan
, net_uuid
):
816 dhcp_namespace
= str(vlan
) + '-dnsmasq'
817 dhcp_path
= os
.path
.join(dhcp_path
, dhcp_namespace
)
818 pid_file
= os
.path
.join(dhcp_path
, 'dnsmasq.pid')
820 command
= 'sudo ip netns exec ' + dhcp_namespace
+ ' cat ' + pid_file
821 self
.logger
.debug("command: " + command
)
822 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
823 content
= stdout
.read()
825 command
= 'sudo ip netns exec ' + dhcp_namespace
+ ' kill -9 ' + content
826 self
.logger
.debug("command: " + command
)
827 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
828 content
= stdout
.read()
830 # if len(content) == 0:
834 except paramiko
.ssh_exception
.SSHException
as e
:
835 self
.logger
.error("delete_dhcp_server ssh Exception: " + str(e
))
836 if "SSH session not active" in str(e
):
840 def is_dhcp_port_free(self
, host_id
, net_uuid
):
842 Check if any port attached to the a net in a vxlan mesh across computes nodes
843 :param host_id: host id
844 :param net_uuid: network id
845 :return: True if is not free
847 self
.db_lock
.acquire()
848 result
, content
= self
.db
.get_table(
850 WHERE
={'type': 'instance:ovs', 'net_id': net_uuid
}
852 self
.db_lock
.release()
859 def is_port_free(self
, host_id
, net_uuid
):
861 Check if there not ovs ports of a network in a compute host.
862 :param host_id: host id
863 :param net_uuid: network id
864 :return: True if is not free
867 self
.db_lock
.acquire()
868 result
, content
= self
.db
.get_table(
869 FROM
='ports as p join instances as i on p.instance_id=i.uuid',
870 WHERE
={"i.host_id": self
.host_id
, 'p.type': 'instance:ovs', 'p.net_id': net_uuid
}
872 self
.db_lock
.release()
879 def add_port_to_ovs_bridge(self
, vlan
):
881 Add a bridge linux as a port to a OVS bridge and set a vlan for an specific linux bridge
882 :param vlan: vlan port id
883 :return: True if success
889 port_name
= 'ovim-' + str(vlan
)
890 command
= 'sudo ovs-vsctl add-port br-int ' + port_name
+ ' tag=' + str(vlan
)
891 self
.logger
.debug("command: " + command
)
892 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
893 content
= stdout
.read()
894 if len(content
) == 0:
898 except paramiko
.ssh_exception
.SSHException
as e
:
899 self
.logger
.error("add_port_to_ovs_bridge ssh Exception: " + str(e
))
900 if "SSH session not active" in str(e
):
904 def delete_dhcp_port(self
, vlan
, net_uuid
):
906 Delete from an existing OVS bridge a linux bridge port attached and the linux bridge itself.
907 :param vlan: segmentation id
908 :param net_uuid: network id
909 :return: True if success
915 if not self
.is_dhcp_port_free(vlan
, net_uuid
):
917 self
.delete_dhcp_interfaces(vlan
)
920 def delete_bridge_port_attached_to_ovs(self
, vlan
, net_uuid
):
922 Delete from an existing OVS bridge a linux bridge port attached and the linux bridge itself.
925 :return: True if success
930 if not self
.is_port_free(vlan
, net_uuid
):
932 self
.delete_port_to_ovs_bridge(vlan
, net_uuid
)
933 self
.delete_linux_bridge(vlan
)
936 def delete_linux_bridge(self
, vlan
):
938 Delete a linux bridge in a scpecific compute.
939 :param vlan: vlan port id
940 :return: True if success
946 port_name
= 'ovim-' + str(vlan
)
947 command
= 'sudo ip link set dev ovim-' + str(vlan
) + ' down'
948 self
.logger
.debug("command: " + command
)
949 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
950 # content = stdout.read()
952 # if len(content) != 0:
954 command
= 'sudo ifconfig ' + port_name
+ ' down && sudo brctl delbr ' + port_name
955 self
.logger
.debug("command: " + command
)
956 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
957 content
= stdout
.read()
958 if len(content
) == 0:
962 except paramiko
.ssh_exception
.SSHException
as e
:
963 self
.logger
.error("delete_linux_bridge ssh Exception: " + str(e
))
964 if "SSH session not active" in str(e
):
968 def remove_link_bridge_to_ovs(self
, vlan
, link
):
970 Delete a linux provider net connection to tenatn net
971 :param vlan: vlan port id
972 :param link: link name
973 :return: True if success
979 br_tap_name
= str(vlan
) + '-vethBO'
980 br_ovs_name
= str(vlan
) + '-vethOB'
982 # Delete ovs veth pair
983 command
= 'sudo ip link set dev {} down'.format(br_ovs_name
)
984 self
.logger
.debug("command: " + command
)
985 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
986 content
= stdout
.read()
988 command
= 'sudo ovs-vsctl del-port br-int {}'.format(br_ovs_name
)
989 self
.logger
.debug("command: " + command
)
990 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
991 content
= stdout
.read()
993 # Delete br veth pair
994 command
= 'sudo ip link set dev {} down'.format(br_tap_name
)
995 self
.logger
.debug("command: " + command
)
996 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
997 content
= stdout
.read()
999 # Delete br veth interface form bridge
1000 command
= 'sudo brctl delif {} {}'.format(link
, br_tap_name
)
1001 self
.logger
.debug("command: " + command
)
1002 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1003 content
= stdout
.read()
1005 # Delete br veth pair
1006 command
= 'sudo ip link set dev {} down'.format(link
)
1007 self
.logger
.debug("command: " + command
)
1008 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1009 content
= stdout
.read()
1011 if len(content
) == 0:
1015 except paramiko
.ssh_exception
.SSHException
as e
:
1016 self
.logger
.error("delete_linux_bridge ssh Exception: " + str(e
))
1017 if "SSH session not active" in str(e
):
1021 def create_ovs_bridge_port(self
, vlan
):
1023 Generate a linux bridge and attache the port to a OVS bridge
1024 :param vlan: vlan port id
1029 self
.create_linux_bridge(vlan
)
1030 self
.add_port_to_ovs_bridge(vlan
)
1032 def create_linux_bridge(self
, vlan
):
1034 Create a linux bridge with STP active
1035 :param vlan: netowrk vlan id
1042 port_name
= 'ovim-' + str(vlan
)
1043 command
= 'sudo brctl show | grep ' + port_name
1044 self
.logger
.debug("command: " + command
)
1045 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1046 content
= stdout
.read()
1048 # if exist nothing to create
1049 # if len(content) == 0:
1052 command
= 'sudo brctl addbr ' + port_name
1053 self
.logger
.debug("command: " + command
)
1054 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1055 content
= stdout
.read()
1057 # if len(content) == 0:
1062 command
= 'sudo brctl stp ' + port_name
+ ' on'
1063 self
.logger
.debug("command: " + command
)
1064 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1065 content
= stdout
.read()
1067 # if len(content) == 0:
1071 command
= 'sudo ip link set dev ' + port_name
+ ' up'
1072 self
.logger
.debug("command: " + command
)
1073 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1074 content
= stdout
.read()
1076 if len(content
) == 0:
1080 except paramiko
.ssh_exception
.SSHException
as e
:
1081 self
.logger
.error("create_linux_bridge ssh Exception: " + str(e
))
1082 if "SSH session not active" in str(e
):
1086 def set_mac_dhcp_server(self
, ip
, mac
, vlan
, netmask
, first_ip
, dhcp_path
):
1088 Write into dhcp conf file a rule to assigned a fixed ip given to an specific MAC address
1089 :param ip: IP address asigned to a VM
1090 :param mac: VM vnic mac to be macthed with the IP received
1091 :param vlan: Segmentation id
1092 :param netmask: netmask value
1093 :param path: dhcp conf file path that live in namespace side
1094 :return: True if success
1100 dhcp_namespace
= str(vlan
) + '-dnsmasq'
1101 dhcp_path
= os
.path
.join(dhcp_path
, dhcp_namespace
)
1102 dhcp_hostsdir
= os
.path
.join(dhcp_path
, dhcp_namespace
)
1108 ns_interface
= str(vlan
) + '-vethDO'
1109 command
= 'sudo ip netns exec ' + dhcp_namespace
+ ' cat /sys/class/net/{}/address'.format(ns_interface
)
1110 self
.logger
.debug("command: " + command
)
1111 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1112 iface_listen_mac
= stdout
.read()
1114 if iface_listen_mac
> 0:
1115 command
= 'sudo ip netns exec ' + dhcp_namespace
+ ' cat {} | grep {}'.format(dhcp_hostsdir
, dhcp_hostsdir
)
1116 self
.logger
.debug("command: " + command
)
1117 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1118 content
= stdout
.read()
1120 ip_data
= iface_listen_mac
.upper().replace('\n', '') + ',' + first_ip
1121 dhcp_hostsdir
= os
.path
.join(dhcp_path
, dhcp_namespace
)
1123 command
= 'sudo ip netns exec ' + dhcp_namespace
+ ' sudo bash -ec "echo ' + ip_data
+ ' >> ' + dhcp_hostsdir
+ '"'
1124 self
.logger
.debug("command: " + command
)
1125 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1126 content
= stdout
.read()
1129 ip_data
= mac
.upper() + ',' + ip
1131 command
= 'sudo ip netns exec ' + dhcp_namespace
+ ' touch ' + dhcp_hostsdir
1132 self
.logger
.debug("command: " + command
)
1133 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1134 content
= stdout
.read()
1136 command
= 'sudo ip netns exec ' + dhcp_namespace
+ ' sudo bash -ec "echo ' + ip_data
+ ' >> ' + dhcp_hostsdir
+ '"'
1138 self
.logger
.debug("command: " + command
)
1139 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1140 content
= stdout
.read()
1142 if len(content
) == 0:
1146 except paramiko
.ssh_exception
.SSHException
as e
:
1147 self
.logger
.error("set_mac_dhcp_server ssh Exception: " + str(e
))
1148 if "SSH session not active" in str(e
):
1152 def delete_mac_dhcp_server(self
, ip
, mac
, vlan
, dhcp_path
):
1154 Delete into dhcp conf file the ip assigned to a specific MAC address
1156 :param ip: IP address asigned to a VM
1157 :param mac: VM vnic mac to be macthed with the IP received
1158 :param vlan: Segmentation id
1159 :param dhcp_path: dhcp conf file path that live in namespace side
1166 dhcp_namespace
= str(vlan
) + '-dnsmasq'
1167 dhcp_path
= os
.path
.join(dhcp_path
, dhcp_namespace
)
1168 dhcp_hostsdir
= os
.path
.join(dhcp_path
, dhcp_namespace
)
1173 ip_data
= mac
.upper() + ',' + ip
1175 command
= 'sudo ip netns exec ' + dhcp_namespace
+ ' sudo sed -i \'/' + ip_data
+ '/d\' ' + dhcp_hostsdir
1176 self
.logger
.debug("command: " + command
)
1177 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1178 content
= stdout
.read()
1180 if len(content
) == 0:
1185 except paramiko
.ssh_exception
.SSHException
as e
:
1186 self
.logger
.error("set_mac_dhcp_server ssh Exception: " + str(e
))
1187 if "SSH session not active" in str(e
):
1191 def launch_dhcp_server(self
, vlan
, ip_range
, netmask
, dhcp_path
, gateway
, dns_list
=None, routes
=None):
1193 Generate a linux bridge and attache the port to a OVS bridge
1195 :param vlan: Segmentation id
1196 :param ip_range: IP dhcp range
1197 :param netmask: network netmask
1198 :param dhcp_path: dhcp conf file path that live in namespace side
1199 :param gateway: Gateway address for dhcp net
1200 :param dns_list: dns list for dhcp server
1201 :param routes: routes list for dhcp server
1202 :return: True if success
1208 ns_interface
= str(vlan
) + '-vethDO'
1209 dhcp_namespace
= str(vlan
) + '-dnsmasq'
1210 dhcp_path
= os
.path
.join(dhcp_path
, dhcp_namespace
, '')
1211 leases_path
= os
.path
.join(dhcp_path
, "dnsmasq.leases")
1212 pid_file
= os
.path
.join(dhcp_path
, 'dnsmasq.pid')
1215 dhcp_range
= ip_range
[0] + ',' + ip_range
[1] + ',' + netmask
1217 command
= 'sudo ip netns exec ' + dhcp_namespace
+ ' mkdir -p ' + dhcp_path
1218 self
.logger
.debug("command: " + command
)
1219 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1220 content
= stdout
.read()
1222 pid_path
= os
.path
.join(dhcp_path
, 'dnsmasq.pid')
1223 command
= 'sudo ip netns exec ' + dhcp_namespace
+ ' cat ' + pid_path
1224 self
.logger
.debug("command: " + command
)
1225 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1226 content
= stdout
.read()
1228 # check if pid is runing
1229 pid_status_path
= content
1231 command
= "ps aux | awk '{print $2 }' | grep " + pid_status_path
1232 self
.logger
.debug("command: " + command
)
1233 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1234 content
= stdout
.read()
1236 gateway_option
= ' --dhcp-option=3,' + gateway
1238 dhcp_route_option
= ''
1240 dhcp_route_option
= ' --dhcp-option=121'
1241 for key
, value
in routes
.iteritems():
1242 if 'default' == key
:
1243 gateway_option
= ' --dhcp-option=3,' + value
1245 dhcp_route_option
+= ',' + key
+ ',' + value
1248 dns_data
= ' --dhcp-option=6'
1249 for dns
in dns_list
:
1250 dns_data
+= ',' + dns
1253 command
= 'sudo ip netns exec ' + dhcp_namespace
+ ' /usr/sbin/dnsmasq --strict-order --except-interface=lo ' \
1254 '--interface=' + ns_interface
+ \
1255 ' --bind-interfaces --dhcp-hostsdir=' + dhcp_path
+ \
1256 ' --dhcp-range ' + dhcp_range
+ \
1257 ' --pid-file=' + pid_file
+ \
1258 ' --dhcp-leasefile=' + leases_path
+ \
1259 ' --listen-address ' + ip_range
[0] + \
1261 dhcp_route_option
+ \
1264 self
.logger
.debug("command: " + command
)
1265 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1266 content
= stdout
.readline()
1268 if len(content
) == 0:
1272 except paramiko
.ssh_exception
.SSHException
as e
:
1273 self
.logger
.error("launch_dhcp_server ssh Exception: " + str(e
))
1274 if "SSH session not active" in str(e
):
1278 def delete_dhcp_interfaces(self
, vlan
):
1280 Create a linux bridge with STP active
1281 :param vlan: netowrk vlan id
1288 br_veth_name
= str(vlan
) + '-vethDO'
1289 ovs_veth_name
= str(vlan
) + '-vethOD'
1290 dhcp_namespace
= str(vlan
) + '-dnsmasq'
1292 command
= 'sudo ovs-vsctl del-port br-int ' + ovs_veth_name
1293 self
.logger
.debug("command: " + command
)
1294 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1295 content
= stdout
.read()
1297 command
= 'sudo ip netns exec ' + dhcp_namespace
+ ' ip link set dev ' + br_veth_name
+ ' down'
1298 self
.logger
.debug("command: " + command
)
1299 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1300 content
= stdout
.read()
1302 command
= 'sudo ip link set dev ' + dhcp_namespace
+ ' down'
1303 self
.logger
.debug("command: " + command
)
1304 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1305 content
= stdout
.read()
1307 command
= 'sudo brctl delbr ' + dhcp_namespace
1308 self
.logger
.debug("command: " + command
)
1309 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1310 content
= stdout
.read()
1312 command
= 'sudo ip netns del ' + dhcp_namespace
1313 self
.logger
.debug("command: " + command
)
1314 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1315 content
= stdout
.read()
1317 except paramiko
.ssh_exception
.SSHException
as e
:
1318 self
.logger
.error("delete_dhcp_interfaces ssh Exception: " + str(e
))
1319 if "SSH session not active" in str(e
):
1323 def create_dhcp_interfaces(self
, vlan
, ip_listen_address
, netmask
):
1325 Create a linux bridge with STP active
1326 :param vlan: segmentation id
1327 :param ip_listen_address: Listen Ip address for the dhcp service, the tap interface living in namesapce side
1328 :param netmask: dhcp net CIDR
1329 :return: True if success
1335 ovs_veth_name
= str(vlan
) + '-vethOD'
1336 ns_veth
= str(vlan
) + '-vethDO'
1337 dhcp_namespace
= str(vlan
) + '-dnsmasq'
1339 command
= 'sudo ip netns add ' + dhcp_namespace
1340 self
.logger
.debug("command: " + command
)
1341 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1342 content
= stdout
.read()
1344 command
= 'sudo ip link add ' + ns_veth
+ ' type veth peer name ' + ovs_veth_name
1345 self
.logger
.debug("command: " + command
)
1346 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1347 content
= stdout
.read()
1349 command
= 'sudo ip link set ' + ns_veth
+ ' netns ' + dhcp_namespace
1350 self
.logger
.debug("command: " + command
)
1351 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1352 content
= stdout
.read()
1354 command
= 'sudo ip netns exec ' + dhcp_namespace
+ ' ip link set dev ' + ns_veth
+ ' up'
1355 self
.logger
.debug("command: " + command
)
1356 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1357 content
= stdout
.read()
1359 command
= 'sudo ovs-vsctl add-port br-int ' + ovs_veth_name
+ ' tag=' + str(vlan
)
1360 self
.logger
.debug("command: " + command
)
1361 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1362 content
= stdout
.read()
1364 command
= 'sudo ip link set dev ' + ovs_veth_name
+ ' up'
1365 self
.logger
.debug("command: " + command
)
1366 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1367 content
= stdout
.read()
1369 command
= 'sudo ip netns exec ' + dhcp_namespace
+ ' ip link set dev lo up'
1370 self
.logger
.debug("command: " + command
)
1371 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1372 content
= stdout
.read()
1374 command
= 'sudo ip netns exec ' + dhcp_namespace
+ ' ' + ' ifconfig ' + ns_veth \
1375 + ' ' + ip_listen_address
+ ' netmask ' + netmask
1376 self
.logger
.debug("command: " + command
)
1377 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1378 content
= stdout
.read()
1379 if len(content
) == 0:
1383 except paramiko
.ssh_exception
.SSHException
as e
:
1384 self
.logger
.error("create_dhcp_interfaces ssh Exception: " + str(e
))
1385 if "SSH session not active" in str(e
):
1389 def delete_qrouter_connection(self
, vlan
, link
):
1391 Delete qrouter Namesapce with all veth interfaces need it
1397 ns_qouter
= str(vlan
) + '-qrouter'
1398 qrouter_ovs_veth
= str(vlan
) + '-vethOQ'
1399 qrouter_ns_veth
= str(vlan
) + '-vethQO'
1401 qrouter_br_veth
= str(vlan
) + '-vethBQ'
1402 qrouter_ns_router_veth
= str(vlan
) + '-vethQB'
1404 # delete ovs veth to ovs br-int
1405 command
= 'sudo ovs-vsctl del-port br-int {}'.format(qrouter_ovs_veth
)
1406 self
.logger
.debug("command: " + command
)
1407 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1408 content
= stdout
.read()
1411 command
= 'sudo ip netns exec {} ip link set dev {} down'.format(ns_qouter
, qrouter_ns_veth
)
1412 self
.logger
.debug("command: " + command
)
1413 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1414 content
= stdout
.read()
1416 # down ovs veth interface
1417 command
= 'sudo ip link set dev {} down'.format(qrouter_br_veth
)
1418 self
.logger
.debug("command: " + command
)
1419 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1420 content
= stdout
.read()
1422 # down br veth interface
1423 command
= 'sudo ip link set dev {} down'.format(qrouter_ovs_veth
)
1424 self
.logger
.debug("command: " + command
)
1425 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1426 content
= stdout
.read()
1428 # down br veth interface
1429 command
= 'sudo ip link set dev {} down'.format(qrouter_ns_router_veth
)
1430 self
.logger
.debug("command: " + command
)
1431 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1432 content
= stdout
.read()
1434 # down br veth interface
1435 command
= 'sudo brctl delif {} {}'.format(link
, qrouter_br_veth
)
1436 self
.logger
.debug("command: " + command
)
1437 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1438 content
= stdout
.read()
1442 command
= 'sudo ip netns del ' + ns_qouter
1443 self
.logger
.debug("command: " + command
)
1444 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1445 content
= stdout
.read()
1447 def create_qrouter_ovs_connection(self
, vlan
, gateway
, dhcp_cidr
):
1449 Create qrouter Namesapce with all veth interfaces need it between NS and OVS
1455 ns_qouter
= str(vlan
) + '-qrouter'
1456 qrouter_ovs_veth
= str(vlan
) + '-vethOQ'
1457 qrouter_ns_veth
= str(vlan
) + '-vethQO'
1460 command
= 'sudo ip netns add ' + ns_qouter
1461 self
.logger
.debug("command: " + command
)
1462 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1463 content
= stdout
.read()
1466 command
= 'sudo ip link add {} type veth peer name {}'.format(qrouter_ns_veth
, qrouter_ovs_veth
)
1467 self
.logger
.debug("command: " + command
)
1468 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1469 content
= stdout
.read()
1471 # up ovs veth interface
1472 command
= 'sudo ip link set dev {} up'.format(qrouter_ovs_veth
)
1473 self
.logger
.debug("command: " + command
)
1474 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1475 content
= stdout
.read()
1477 # add ovs veth to ovs br-int
1478 command
= 'sudo ovs-vsctl add-port br-int {} tag={}'.format(qrouter_ovs_veth
, vlan
)
1479 self
.logger
.debug("command: " + command
)
1480 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1481 content
= stdout
.read()
1484 command
= 'sudo ip link set {} netns {}'.format(qrouter_ns_veth
, ns_qouter
)
1485 self
.logger
.debug("command: " + command
)
1486 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1487 content
= stdout
.read()
1490 command
= 'sudo ip netns exec {} ip link set dev lo up'.format(ns_qouter
)
1491 self
.logger
.debug("command: " + command
)
1492 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1493 content
= stdout
.read()
1496 command
= 'sudo ip netns exec {} ip link set dev {} up'.format(ns_qouter
, qrouter_ns_veth
)
1497 self
.logger
.debug("command: " + command
)
1498 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1499 content
= stdout
.read()
1501 from netaddr
import IPNetwork
1502 ip_tools
= IPNetwork(dhcp_cidr
)
1503 cidr_len
= ip_tools
.prefixlen
1506 command
= 'sudo ip netns exec {} ip address add {}/{} dev {}'.format(ns_qouter
, gateway
, cidr_len
, qrouter_ns_veth
)
1507 self
.logger
.debug("command: " + command
)
1508 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1509 content
= stdout
.read()
1511 def add_ns_routes(self
, vlan
, routes
):
1513 for key
, value
in routes
.iteritems():
1514 ns_qouter
= str(vlan
) + '-qrouter'
1515 qrouter_ns_router_veth
= str(vlan
) + '-vethQB'
1517 if key
== 'default':
1518 command
= 'sudo ip netns exec {} ip route add {} via {} '.format(ns_qouter
, key
, value
)
1520 command
= 'sudo ip netns exec {} ip route add {} via {} dev {}'.format(ns_qouter
, key
, value
,
1521 qrouter_ns_router_veth
)
1522 self
.logger
.debug("command: " + command
)
1523 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1524 content
= stdout
.read()
1526 def create_qrouter_br_connection(self
, vlan
, cidr
, link
):
1528 Create veth interfaces between user bridge (link) and OVS
1534 ns_qouter
= str(vlan
) + '-qrouter'
1535 qrouter_ns_router_veth
= str(vlan
) + '-vethQB'
1536 qrouter_br_veth
= str(vlan
) + '-vethBQ'
1539 command
= 'sudo ip link add {} type veth peer name {}'.format(qrouter_br_veth
, qrouter_ns_router_veth
)
1540 self
.logger
.debug("command: " + command
)
1541 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1542 content
= stdout
.read()
1544 # up ovs veth interface
1545 command
= 'sudo ip link set dev {} up'.format(qrouter_br_veth
)
1546 self
.logger
.debug("command: " + command
)
1547 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1548 content
= stdout
.read()
1551 command
= 'sudo ip link set {} netns {}'.format(qrouter_ns_router_veth
, ns_qouter
)
1552 self
.logger
.debug("command: " + command
)
1553 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1554 content
= stdout
.read()
1557 command
= 'sudo ip netns exec {} ip link set dev {} up'.format(ns_qouter
, qrouter_ns_router_veth
)
1558 self
.logger
.debug("command: " + command
)
1559 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1560 content
= stdout
.read()
1562 command
= 'sudo ip netns exec {} ip address add {} dev {}'.format(ns_qouter
, link
['nat'], qrouter_ns_router_veth
)
1563 self
.logger
.debug("command: " + command
)
1564 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1565 content
= stdout
.read()
1567 command
= 'sudo brctl show | grep {}'.format(link
['iface'])
1568 self
.logger
.debug("command: " + command
)
1569 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1570 content
= stdout
.read()
1574 command
= 'sudo brctl addif {} {}'.format(link
['iface'], qrouter_br_veth
)
1575 self
.logger
.debug("command: " + command
)
1576 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1577 content
= stdout
.read()
1580 command
= 'sudo ip netns exec {} iptables -t nat -A POSTROUTING -o {} -s {} -d {} -j MASQUERADE' \
1581 .format(ns_qouter
, qrouter_ns_router_veth
, link
['nat'], cidr
)
1582 self
.logger
.debug("command: " + command
)
1583 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1584 content
= stdout
.read()
1588 self
.logger
.error('Bridge {} given by user not exist'.format(qrouter_br_veth
))
1592 def create_link_bridge_to_ovs(self
, vlan
, link
):
1594 Create interfaces to connect a linux bridge with tenant net
1595 :param vlan: segmentation id
1596 :return: True if success
1602 br_tap_name
= str(vlan
) + '-vethBO'
1603 br_ovs_name
= str(vlan
) + '-vethOB'
1605 # is a bridge or a interface
1606 command
= 'sudo brctl show | grep {}'.format(link
)
1607 self
.logger
.debug("command: " + command
)
1608 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1609 content
= stdout
.read()
1612 command
= 'sudo ip link add {} type veth peer name {}'.format(br_tap_name
, br_ovs_name
)
1613 self
.logger
.debug("command: " + command
)
1614 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1615 content
= stdout
.read()
1617 command
= 'sudo ip link set dev {} up'.format(br_tap_name
)
1618 self
.logger
.debug("command: " + command
)
1619 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1620 content
= stdout
.read()
1622 command
= 'sudo ip link set dev {} up'.format(br_ovs_name
)
1623 self
.logger
.debug("command: " + command
)
1624 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1625 content
= stdout
.read()
1627 command
= 'sudo ovs-vsctl add-port br-int {} tag={}'.format(br_ovs_name
, str(vlan
))
1628 self
.logger
.debug("command: " + command
)
1629 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1630 content
= stdout
.read()
1632 command
= 'sudo brctl addif ' + link
+ ' {}'.format(br_tap_name
)
1633 self
.logger
.debug("command: " + command
)
1634 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1635 content
= stdout
.read()
1637 if len(content
) == 0:
1642 self
.logger
.error('Link is not present, please check {}'.format(link
))
1644 except paramiko
.ssh_exception
.SSHException
as e
:
1645 self
.logger
.error("create_dhcp_interfaces ssh Exception: " + str(e
))
1646 if "SSH session not active" in str(e
):
1650 def create_ovs_vxlan_tunnel(self
, vxlan_interface
, remote_ip
):
1652 Create a vlxn tunnel between to computes with an OVS installed. STP is also active at port level
1653 :param vxlan_interface: vlxan inteface name.
1654 :param remote_ip: tunnel endpoint remote compute ip.
1657 if self
.test
or not self
.connectivity
:
1660 command
= 'sudo ovs-vsctl add-port br-int ' + vxlan_interface
+ \
1661 ' -- set Interface ' + vxlan_interface
+ ' type=vxlan options:remote_ip=' + remote_ip
+ \
1662 ' -- set Port ' + vxlan_interface
+ ' other_config:stp-path-cost=10'
1663 self
.logger
.debug("command: " + command
)
1664 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1665 content
= stdout
.read()
1667 if len(content
) == 0:
1671 except paramiko
.ssh_exception
.SSHException
as e
:
1672 self
.logger
.error("create_ovs_vxlan_tunnel ssh Exception: " + str(e
))
1673 if "SSH session not active" in str(e
):
1677 def delete_ovs_vxlan_tunnel(self
, vxlan_interface
):
1679 Delete a vlxan tunnel port from a OVS brdige.
1680 :param vxlan_interface: vlxan name to be delete it.
1681 :return: True if success.
1683 if self
.test
or not self
.connectivity
:
1686 command
= 'sudo ovs-vsctl del-port br-int ' + vxlan_interface
1687 self
.logger
.debug("command: " + command
)
1688 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1689 content
= stdout
.read()
1691 if len(content
) == 0:
1695 except paramiko
.ssh_exception
.SSHException
as e
:
1696 self
.logger
.error("delete_ovs_vxlan_tunnel ssh Exception: " + str(e
))
1697 if "SSH session not active" in str(e
):
1701 def delete_ovs_bridge(self
):
1703 Delete a OVS bridge from a compute.
1704 :return: True if success
1706 if self
.test
or not self
.connectivity
:
1709 command
= 'sudo ovs-vsctl del-br br-int'
1710 self
.logger
.debug("command: " + command
)
1711 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1712 content
= stdout
.read()
1713 if len(content
) == 0:
1717 except paramiko
.ssh_exception
.SSHException
as e
:
1718 self
.logger
.error("delete_ovs_bridge ssh Exception: " + str(e
))
1719 if "SSH session not active" in str(e
):
1723 def get_file_info(self
, path
):
1724 command
= 'ls -lL --time-style=+%Y-%m-%dT%H:%M:%S ' + path
1725 self
.logger
.debug("command: " + command
)
1726 (_
, stdout
, _
) = self
.ssh_conn
.exec_command(command
)
1727 content
= stdout
.read()
1728 if len(content
) == 0:
1729 return None # file does not exist
1731 return content
.split(" ") # (permission, 1, owner, group, size, date, file)
1733 def qemu_get_info(self
, path
):
1734 command
= 'qemu-img info ' + path
1735 self
.logger
.debug("command: " + command
)
1736 (_
, stdout
, stderr
) = self
.ssh_conn
.exec_command(command
)
1737 content
= stdout
.read()
1738 if len(content
) == 0:
1739 error
= stderr
.read()
1740 self
.logger
.error("get_qemu_info error " + error
)
1741 raise paramiko
.ssh_exception
.SSHException("Error getting qemu_info: " + error
)
1744 return yaml
.load(content
)
1745 except yaml
.YAMLError
as exc
:
1747 if hasattr(exc
, 'problem_mark'):
1748 mark
= exc
.problem_mark
1749 text
= " at position: (%s:%s)" % (mark
.line
+1, mark
.column
+1)
1750 self
.logger
.error("get_qemu_info yaml format Exception " + text
)
1751 raise paramiko
.ssh_exception
.SSHException("Error getting qemu_info yaml format" + text
)
1753 def qemu_change_backing(self
, inc_file
, new_backing_file
):
1754 command
= 'qemu-img rebase -u -b ' + new_backing_file
+ ' ' + inc_file
1755 self
.logger
.debug("command: " + command
)
1756 (_
, _
, stderr
) = self
.ssh_conn
.exec_command(command
)
1757 content
= stderr
.read()
1758 if len(content
) == 0:
1761 self
.logger
.error("qemu_change_backing error: " + content
)
1764 def qemu_create_empty_disk(self
, dev
):
1766 if not dev
and 'source' not in dev
and 'file format' not in dev
and 'image_size' not in dev
:
1767 self
.logger
.error("qemu_create_empty_disk error: missing image parameter")
1770 empty_disk_path
= dev
['source file']
1772 command
= 'qemu-img create -f qcow2 ' + empty_disk_path
+ ' ' + str(dev
['image_size']) + 'G'
1773 self
.logger
.debug("command: " + command
)
1774 (_
, _
, stderr
) = self
.ssh_conn
.exec_command(command
)
1775 content
= stderr
.read()
1776 if len(content
) == 0:
1779 self
.logger
.error("qemu_create_empty_disk error: " + content
)
1782 def get_notused_filename(self
, proposed_name
, suffix
=''):
1783 '''Look for a non existing file_name in the host
1784 proposed_name: proposed file name, includes path
1785 suffix: suffix to be added to the name, before the extention
1787 extension
= proposed_name
.rfind(".")
1788 slash
= proposed_name
.rfind("/")
1789 if extension
< 0 or extension
< slash
: # no extension
1790 extension
= len(proposed_name
)
1791 target_name
= proposed_name
[:extension
] + suffix
+ proposed_name
[extension
:]
1792 info
= self
.get_file_info(target_name
)
1797 while info
is not None:
1798 target_name
= proposed_name
[:extension
] + suffix
+ "-" + str(index
) + proposed_name
[extension
:]
1800 info
= self
.get_file_info(target_name
)
1803 def get_notused_path(self
, proposed_path
, suffix
=''):
1804 '''Look for a non existing path at database for images
1805 proposed_path: proposed file name, includes path
1806 suffix: suffix to be added to the name, before the extention
1808 extension
= proposed_path
.rfind(".")
1810 extension
= len(proposed_path
)
1812 target_path
= proposed_path
[:extension
] + suffix
+ proposed_path
[extension
:]
1815 r
,_
=self
.db
.get_table(FROM
="images",WHERE
={"path":target_path
})
1818 target_path
= proposed_path
[:extension
] + suffix
+ "-" + str(index
) + proposed_path
[extension
:]
1822 def delete_file(self
, file_name
):
1823 command
= 'rm -f '+file_name
1824 self
.logger
.debug("command: " + command
)
1825 (_
, _
, stderr
) = self
.ssh_conn
.exec_command(command
)
1826 error_msg
= stderr
.read()
1827 if len(error_msg
) > 0:
1828 raise paramiko
.ssh_exception
.SSHException("Error deleting file: " + error_msg
)
1830 def copy_file(self
, source
, destination
, perserve_time
=True):
1831 if source
[0:4]=="http":
1832 command
= "wget --no-verbose -O '{dst}' '{src}' 2>'{dst_result}' || cat '{dst_result}' >&2 && rm '{dst_result}'".format(
1833 dst
=destination
, src
=source
, dst_result
=destination
+ ".result" )
1835 command
= 'cp --no-preserve=mode'
1837 command
+= ' --preserve=timestamps'
1838 command
+= " '{}' '{}'".format(source
, destination
)
1839 self
.logger
.debug("command: " + command
)
1840 (_
, _
, stderr
) = self
.ssh_conn
.exec_command(command
)
1841 error_msg
= stderr
.read()
1842 if len(error_msg
) > 0:
1843 raise paramiko
.ssh_exception
.SSHException("Error copying image to local host: " + error_msg
)
1845 def copy_remote_file(self
, remote_file
, use_incremental
):
1846 ''' Copy a file from the repository to local folder and recursively
1847 copy the backing files in case the remote file is incremental
1848 Read and/or modified self.localinfo['files'] that contain the
1849 unmodified copies of images in the local path
1851 remote_file: path of remote file
1852 use_incremental: None (leave the decision to this function), True, False
1854 local_file: name of local file
1855 qemu_info: dict with quemu information of local file
1856 use_incremental_out: True, False; same as use_incremental, but if None a decision is taken
1859 use_incremental_out
= use_incremental
1860 new_backing_file
= None
1862 file_from_local
= True
1864 #in case incremental use is not decided, take the decision depending on the image
1865 #avoid the use of incremental if this image is already incremental
1866 if remote_file
[0:4] == "http":
1867 file_from_local
= False
1869 qemu_remote_info
= self
.qemu_get_info(remote_file
)
1870 if use_incremental_out
==None:
1871 use_incremental_out
= not ( file_from_local
and 'backing file' in qemu_remote_info
)
1872 #copy recursivelly the backing files
1873 if file_from_local
and 'backing file' in qemu_remote_info
:
1874 new_backing_file
, _
, _
= self
.copy_remote_file(qemu_remote_info
['backing file'], True)
1876 #check if remote file is present locally
1877 if use_incremental_out
and remote_file
in self
.localinfo
['files']:
1878 local_file
= self
.localinfo
['files'][remote_file
]
1879 local_file_info
= self
.get_file_info(local_file
)
1881 remote_file_info
= self
.get_file_info(remote_file
)
1882 if local_file_info
== None:
1884 elif file_from_local
and (local_file_info
[4]!=remote_file_info
[4] or local_file_info
[5]!=remote_file_info
[5]):
1885 #local copy of file not valid because date or size are different.
1886 #TODO DELETE local file if this file is not used by any active virtual machine
1888 self
.delete_file(local_file
)
1889 del self
.localinfo
['files'][remote_file
]
1893 else: #check that the local file has the same backing file, or there are not backing at all
1894 qemu_info
= self
.qemu_get_info(local_file
)
1895 if new_backing_file
!= qemu_info
.get('backing file'):
1899 if local_file
== None: #copy the file
1900 img_name
= remote_file
.split('/') [-1]
1901 img_local
= self
.image_path
+ '/' + img_name
1902 local_file
= self
.get_notused_filename(img_local
)
1903 self
.copy_file(remote_file
, local_file
, use_incremental_out
)
1905 if use_incremental_out
:
1906 self
.localinfo
['files'][remote_file
] = local_file
1907 if new_backing_file
:
1908 self
.qemu_change_backing(local_file
, new_backing_file
)
1909 qemu_info
= self
.qemu_get_info(local_file
)
1911 return local_file
, qemu_info
, use_incremental_out
1913 def launch_server(self
, conn
, server
, rebuild
=False, domain
=None):
1915 time
.sleep(random
.randint(20,150)) #sleep random timeto be make it a bit more real
1918 server_id
= server
['uuid']
1919 paused
= server
.get('paused','no')
1921 if domain
!=None and rebuild
==False:
1923 #self.server_status[server_id] = 'ACTIVE'
1926 self
.db_lock
.acquire()
1927 result
, server_data
= self
.db
.get_instance(server_id
)
1928 self
.db_lock
.release()
1930 self
.logger
.error("launch_server ERROR getting server from DB %d %s", result
, server_data
)
1931 return result
, server_data
1933 #0: get image metadata
1934 server_metadata
= server
.get('metadata', {})
1935 use_incremental
= None
1937 if "use_incremental" in server_metadata
:
1938 use_incremental
= False if server_metadata
["use_incremental"] == "no" else True
1940 server_host_files
= self
.localinfo
['server_files'].get( server
['uuid'], {})
1942 #delete previous incremental files
1943 for file_
in server_host_files
.values():
1944 self
.delete_file(file_
['source file'] )
1945 server_host_files
={}
1947 #1: obtain aditional devices (disks)
1948 #Put as first device the main disk
1949 devices
= [ {"type":"disk", "image_id":server
['image_id'], "vpci":server_metadata
.get('vpci', None) } ]
1950 if 'extended' in server_data
and server_data
['extended']!=None and "devices" in server_data
['extended']:
1951 devices
+= server_data
['extended']['devices']
1954 image_id
= dev
.get('image_id')
1957 uuid_empty
= str(uuid
.uuid4())
1958 empty_path
= self
.empty_image_path
+ uuid_empty
+ '.qcow2' # local path for empty disk
1960 dev
['source file'] = empty_path
1961 dev
['file format'] = 'qcow2'
1962 self
.qemu_create_empty_disk(dev
)
1963 server_host_files
[uuid_empty
] = {'source file': empty_path
,
1964 'file format': dev
['file format']}
1968 self
.db_lock
.acquire()
1969 result
, content
= self
.db
.get_table(FROM
='images', SELECT
=('path', 'metadata'),
1970 WHERE
={'uuid': image_id
})
1971 self
.db_lock
.release()
1973 error_text
= "ERROR", result
, content
, "when getting image", dev
['image_id']
1974 self
.logger
.error("launch_server " + error_text
)
1975 return -1, error_text
1976 if content
[0]['metadata'] is not None:
1977 dev
['metadata'] = json
.loads(content
[0]['metadata'])
1979 dev
['metadata'] = {}
1981 if image_id
in server_host_files
:
1982 dev
['source file'] = server_host_files
[image_id
]['source file'] #local path
1983 dev
['file format'] = server_host_files
[image_id
]['file format'] # raw or qcow2
1986 #2: copy image to host
1988 remote_file
= content
[0]['path']
1990 remote_file
= empty_path
1991 use_incremental_image
= use_incremental
1992 if dev
['metadata'].get("use_incremental") == "no":
1993 use_incremental_image
= False
1994 local_file
, qemu_info
, use_incremental_image
= self
.copy_remote_file(remote_file
, use_incremental_image
)
1996 #create incremental image
1997 if use_incremental_image
:
1998 local_file_inc
= self
.get_notused_filename(local_file
, '.inc')
1999 command
= 'qemu-img create -f qcow2 '+local_file_inc
+ ' -o backing_file='+ local_file
2000 self
.logger
.debug("command: " + command
)
2001 (_
, _
, stderr
) = self
.ssh_conn
.exec_command(command
)
2002 error_msg
= stderr
.read()
2003 if len(error_msg
) > 0:
2004 raise paramiko
.ssh_exception
.SSHException("Error creating incremental file: " + error_msg
)
2005 local_file
= local_file_inc
2006 qemu_info
= {'file format':'qcow2'}
2008 server_host_files
[ dev
['image_id'] ] = {'source file': local_file
, 'file format': qemu_info
['file format']}
2010 dev
['source file'] = local_file
2011 dev
['file format'] = qemu_info
['file format']
2013 self
.localinfo
['server_files'][ server
['uuid'] ] = server_host_files
2014 self
.localinfo_dirty
= True
2017 result
, xml
= self
.create_xml_server(server_data
, devices
, server_metadata
) #local_file
2019 self
.logger
.error("create xml server error: " + xml
)
2021 self
.logger
.debug("create xml: " + xml
)
2022 atribute
= host_thread
.lvirt_module
.VIR_DOMAIN_START_PAUSED
if paused
== "yes" else 0
2024 if not rebuild
: #ensures that any pending destroying server is done
2025 self
.server_forceoff(True)
2026 #self.logger.debug("launching instance " + xml)
2027 conn
.createXML(xml
, atribute
)
2028 #self.server_status[server_id] = 'PAUSED' if paused == "yes" else 'ACTIVE'
2032 except paramiko
.ssh_exception
.SSHException
as e
:
2034 self
.logger
.error("launch_server id='%s' ssh Exception: %s", server_id
, text
)
2035 if "SSH session not active" in text
:
2037 except host_thread
.lvirt_module
.libvirtError
as e
:
2038 text
= e
.get_error_message()
2039 self
.logger
.error("launch_server id='%s' libvirt Exception: %s", server_id
, text
)
2040 except Exception as e
:
2042 self
.logger
.error("launch_server id='%s' Exception: %s", server_id
, text
)
2045 def update_servers_status(self
):
2047 # VIR_DOMAIN_NOSTATE = 0
2048 # VIR_DOMAIN_RUNNING = 1
2049 # VIR_DOMAIN_BLOCKED = 2
2050 # VIR_DOMAIN_PAUSED = 3
2051 # VIR_DOMAIN_SHUTDOWN = 4
2052 # VIR_DOMAIN_SHUTOFF = 5
2053 # VIR_DOMAIN_CRASHED = 6
2054 # VIR_DOMAIN_PMSUSPENDED = 7 #TODO suspended
2056 if self
.test
or len(self
.server_status
)==0:
2060 conn
= host_thread
.lvirt_module
.open(self
.lvirt_conn_uri
)
2061 domains
= conn
.listAllDomains()
2063 for domain
in domains
:
2064 uuid
= domain
.UUIDString() ;
2065 libvirt_status
= domain
.state()
2066 #print libvirt_status
2067 if libvirt_status
[0] == host_thread
.lvirt_module
.VIR_DOMAIN_RUNNING
or libvirt_status
[0] == host_thread
.lvirt_module
.VIR_DOMAIN_SHUTDOWN
:
2068 new_status
= "ACTIVE"
2069 elif libvirt_status
[0] == host_thread
.lvirt_module
.VIR_DOMAIN_PAUSED
:
2070 new_status
= "PAUSED"
2071 elif libvirt_status
[0] == host_thread
.lvirt_module
.VIR_DOMAIN_SHUTOFF
:
2072 new_status
= "INACTIVE"
2073 elif libvirt_status
[0] == host_thread
.lvirt_module
.VIR_DOMAIN_CRASHED
:
2074 new_status
= "ERROR"
2077 domain_dict
[uuid
] = new_status
2079 except host_thread
.lvirt_module
.libvirtError
as e
:
2080 self
.logger
.error("get_state() Exception " + e
.get_error_message())
2083 for server_id
, current_status
in self
.server_status
.iteritems():
2085 if server_id
in domain_dict
:
2086 new_status
= domain_dict
[server_id
]
2088 new_status
= "INACTIVE"
2090 if new_status
== None or new_status
== current_status
:
2092 if new_status
== 'INACTIVE' and current_status
== 'ERROR':
2093 continue #keep ERROR status, because obviously this machine is not running
2095 self
.logger
.debug("server id='%s' status change from '%s' to '%s'", server_id
, current_status
, new_status
)
2096 STATUS
={'progress':100, 'status':new_status
}
2097 if new_status
== 'ERROR':
2098 STATUS
['last_error'] = 'machine has crashed'
2099 self
.db_lock
.acquire()
2100 r
,_
= self
.db
.update_rows('instances', STATUS
, {'uuid':server_id
}, log
=False)
2101 self
.db_lock
.release()
2103 self
.server_status
[server_id
] = new_status
2105 def action_on_server(self
, req
, last_retry
=True):
2106 '''Perform an action on a req
2108 req: dictionary that contain:
2109 server properties: 'uuid','name','tenant_id','status'
2111 host properties: 'user', 'ip_name'
2112 return (error, text)
2113 0: No error. VM is updated to new state,
2114 -1: Invalid action, as trying to pause a PAUSED VM
2115 -2: Error accessing host
2117 -4: Error at DB access
2118 -5: Error while trying to perform action. VM is updated to ERROR
2120 server_id
= req
['uuid']
2123 old_status
= req
['status']
2127 if 'terminate' in req
['action']:
2128 new_status
= 'deleted'
2129 elif 'shutoff' in req
['action'] or 'shutdown' in req
['action'] or 'forceOff' in req
['action']:
2130 if req
['status']!='ERROR':
2132 new_status
= 'INACTIVE'
2133 elif 'start' in req
['action'] and req
['status']!='ERROR':
2134 new_status
= 'ACTIVE'
2135 elif 'resume' in req
['action'] and req
['status']!='ERROR' and req
['status']!='INACTIVE':
2136 new_status
= 'ACTIVE'
2137 elif 'pause' in req
['action'] and req
['status']!='ERROR':
2138 new_status
= 'PAUSED'
2139 elif 'reboot' in req
['action'] and req
['status']!='ERROR':
2140 new_status
= 'ACTIVE'
2141 elif 'rebuild' in req
['action']:
2142 time
.sleep(random
.randint(20,150))
2143 new_status
= 'ACTIVE'
2144 elif 'createImage' in req
['action']:
2146 self
.create_image(None, req
)
2149 conn
= host_thread
.lvirt_module
.open(self
.lvirt_conn_uri
)
2151 dom
= conn
.lookupByUUIDString(server_id
)
2152 except host_thread
.lvirt_module
.libvirtError
as e
:
2153 text
= e
.get_error_message()
2154 if 'LookupByUUIDString' in text
or 'Domain not found' in text
or 'No existe un dominio coincidente' in text
:
2157 self
.logger
.error("action_on_server id='%s' libvirt exception: %s", server_id
, text
)
2160 if 'forceOff' in req
['action']:
2162 self
.logger
.debug("action_on_server id='%s' domain not running", server_id
)
2165 self
.logger
.debug("sending DESTROY to server id='%s'", server_id
)
2167 except Exception as e
:
2168 if "domain is not running" not in e
.get_error_message():
2169 self
.logger
.error("action_on_server id='%s' Exception while sending force off: %s",
2170 server_id
, e
.get_error_message())
2171 last_error
= 'action_on_server Exception while destroy: ' + e
.get_error_message()
2172 new_status
= 'ERROR'
2174 elif 'terminate' in req
['action']:
2176 self
.logger
.debug("action_on_server id='%s' domain not running", server_id
)
2177 new_status
= 'deleted'
2180 if req
['action']['terminate'] == 'force':
2181 self
.logger
.debug("sending DESTROY to server id='%s'", server_id
)
2183 new_status
= 'deleted'
2185 self
.logger
.debug("sending SHUTDOWN to server id='%s'", server_id
)
2187 self
.pending_terminate_server
.append( (time
.time()+10,server_id
) )
2188 except Exception as e
:
2189 self
.logger
.error("action_on_server id='%s' Exception while destroy: %s",
2190 server_id
, e
.get_error_message())
2191 last_error
= 'action_on_server Exception while destroy: ' + e
.get_error_message()
2192 new_status
= 'ERROR'
2193 if "domain is not running" in e
.get_error_message():
2196 new_status
= 'deleted'
2198 self
.logger
.error("action_on_server id='%s' Exception while undefine: %s",
2199 server_id
, e
.get_error_message())
2200 last_error
= 'action_on_server Exception2 while undefine:', e
.get_error_message()
2201 #Exception: 'virDomainDetachDevice() failed'
2202 if new_status
=='deleted':
2203 if server_id
in self
.server_status
:
2204 del self
.server_status
[server_id
]
2205 if req
['uuid'] in self
.localinfo
['server_files']:
2206 for file_
in self
.localinfo
['server_files'][ req
['uuid'] ].values():
2208 self
.delete_file(file_
['source file'])
2211 del self
.localinfo
['server_files'][ req
['uuid'] ]
2212 self
.localinfo_dirty
= True
2214 elif 'shutoff' in req
['action'] or 'shutdown' in req
['action']:
2217 self
.logger
.debug("action_on_server id='%s' domain not running", server_id
)
2220 # new_status = 'INACTIVE'
2221 #TODO: check status for changing at database
2222 except Exception as e
:
2223 new_status
= 'ERROR'
2224 self
.logger
.error("action_on_server id='%s' Exception while shutdown: %s",
2225 server_id
, e
.get_error_message())
2226 last_error
= 'action_on_server Exception while shutdown: ' + e
.get_error_message()
2228 elif 'rebuild' in req
['action']:
2231 r
= self
.launch_server(conn
, req
, True, None)
2233 new_status
= 'ERROR'
2236 new_status
= 'ACTIVE'
2237 elif 'start' in req
['action']:
2238 # The instance is only create in DB but not yet at libvirt domain, needs to be create
2239 rebuild
= True if req
['action']['start'] == 'rebuild' else False
2240 r
= self
.launch_server(conn
, req
, rebuild
, dom
)
2242 new_status
= 'ERROR'
2245 new_status
= 'ACTIVE'
2247 elif 'resume' in req
['action']:
2253 # new_status = 'ACTIVE'
2254 except Exception as e
:
2255 self
.logger
.error("action_on_server id='%s' Exception while resume: %s",
2256 server_id
, e
.get_error_message())
2258 elif 'pause' in req
['action']:
2264 # new_status = 'PAUSED'
2265 except Exception as e
:
2266 self
.logger
.error("action_on_server id='%s' Exception while pause: %s",
2267 server_id
, e
.get_error_message())
2269 elif 'reboot' in req
['action']:
2275 self
.logger
.debug("action_on_server id='%s' reboot:", server_id
)
2276 #new_status = 'ACTIVE'
2277 except Exception as e
:
2278 self
.logger
.error("action_on_server id='%s' Exception while reboot: %s",
2279 server_id
, e
.get_error_message())
2280 elif 'createImage' in req
['action']:
2281 self
.create_image(dom
, req
)
2285 except host_thread
.lvirt_module
.libvirtError
as e
:
2286 if conn
is not None: conn
.close()
2287 text
= e
.get_error_message()
2288 new_status
= "ERROR"
2290 if 'LookupByUUIDString' in text
or 'Domain not found' in text
or 'No existe un dominio coincidente' in text
:
2291 self
.logger
.debug("action_on_server id='%s' Exception removed from host", server_id
)
2293 self
.logger
.error("action_on_server id='%s' Exception %s", server_id
, text
)
2294 #end of if self.test
2295 if new_status
== None:
2298 self
.logger
.debug("action_on_server id='%s' new status=%s %s",server_id
, new_status
, last_error
)
2299 UPDATE
= {'progress':100, 'status':new_status
}
2301 if new_status
=='ERROR':
2302 if not last_retry
: #if there will be another retry do not update database
2304 elif 'terminate' in req
['action']:
2305 #PUT a log in the database
2306 self
.logger
.error("PANIC deleting server id='%s' %s", server_id
, last_error
)
2307 self
.db_lock
.acquire()
2308 self
.db
.new_row('logs',
2309 {'uuid':server_id
, 'tenant_id':req
['tenant_id'], 'related':'instances','level':'panic',
2310 'description':'PANIC deleting server from host '+self
.name
+': '+last_error
}
2312 self
.db_lock
.release()
2313 if server_id
in self
.server_status
:
2314 del self
.server_status
[server_id
]
2317 UPDATE
['last_error'] = last_error
2318 if new_status
!= 'deleted' and (new_status
!= old_status
or new_status
== 'ERROR') :
2319 self
.db_lock
.acquire()
2320 self
.db
.update_rows('instances', UPDATE
, {'uuid':server_id
}, log
=True)
2321 self
.server_status
[server_id
] = new_status
2322 self
.db_lock
.release()
2323 if new_status
== 'ERROR':
2328 def restore_iface(self
, name
, mac
, lib_conn
=None):
2329 ''' make an ifdown, ifup to restore default parameter of na interface
2331 mac: mac address of the interface
2332 lib_conn: connection to the libvirt, if None a new connection is created
2333 Return 0,None if ok, -1,text if fails
2339 self
.logger
.debug("restore_iface '%s' %s", name
, mac
)
2343 conn
= host_thread
.lvirt_module
.open(self
.lvirt_conn_uri
)
2347 #wait to the pending VM deletion
2348 #TODO.Revise self.server_forceoff(True)
2350 iface
= conn
.interfaceLookupByMACString(mac
)
2351 if iface
.isActive():
2354 self
.logger
.debug("restore_iface '%s' %s", name
, mac
)
2355 except host_thread
.lvirt_module
.libvirtError
as e
:
2356 error_text
= e
.get_error_message()
2357 self
.logger
.error("restore_iface '%s' '%s' libvirt exception: %s", name
, mac
, error_text
)
2360 if lib_conn
is None and conn
is not None:
2362 return ret
, error_text
2365 def create_image(self
,dom
, req
):
2367 if 'path' in req
['action']['createImage']:
2368 file_dst
= req
['action']['createImage']['path']
2370 createImage
=req
['action']['createImage']
2371 img_name
= createImage
['source']['path']
2372 index
=img_name
.rfind('/')
2373 file_dst
= self
.get_notused_path(img_name
[:index
+1] + createImage
['name'] + '.qcow2')
2374 image_status
='ACTIVE'
2378 server_id
= req
['uuid']
2379 createImage
=req
['action']['createImage']
2380 file_orig
= self
.localinfo
['server_files'][server_id
] [ createImage
['source']['image_id'] ] ['source file']
2381 if 'path' in req
['action']['createImage']:
2382 file_dst
= req
['action']['createImage']['path']
2384 img_name
= createImage
['source']['path']
2385 index
=img_name
.rfind('/')
2386 file_dst
= self
.get_notused_filename(img_name
[:index
+1] + createImage
['name'] + '.qcow2')
2388 self
.copy_file(file_orig
, file_dst
)
2389 qemu_info
= self
.qemu_get_info(file_orig
)
2390 if 'backing file' in qemu_info
:
2391 for k
,v
in self
.localinfo
['files'].items():
2392 if v
==qemu_info
['backing file']:
2393 self
.qemu_change_backing(file_dst
, k
)
2395 image_status
='ACTIVE'
2397 except paramiko
.ssh_exception
.SSHException
as e
:
2398 image_status
='ERROR'
2399 error_text
= e
.args
[0]
2400 self
.logger
.error("create_image id='%s' ssh Exception: %s", server_id
, error_text
)
2401 if "SSH session not active" in error_text
and retry
==0:
2403 except Exception as e
:
2404 image_status
='ERROR'
2406 self
.logger
.error("create_image id='%s' Exception: %s", server_id
, error_text
)
2408 #TODO insert a last_error at database
2409 self
.db_lock
.acquire()
2410 self
.db
.update_rows('images', {'status':image_status
, 'progress': 100, 'path':file_dst
},
2411 {'uuid':req
['new_image']['uuid']}, log
=True)
2412 self
.db_lock
.release()
2414 def edit_iface(self
, port_id
, old_net
, new_net
):
2415 #This action imply remove and insert interface to put proper parameters
2420 self
.db_lock
.acquire()
2421 r
,c
= self
.db
.get_table(FROM
='ports as p join resources_port as rp on p.uuid=rp.port_id',
2422 WHERE
={'port_id': port_id
})
2423 self
.db_lock
.release()
2425 self
.logger
.error("edit_iface %s DDBB error: %s", port_id
, c
)
2428 self
.logger
.error("edit_iface %s port not found", port_id
)
2431 if port
["model"]!="VF":
2432 self
.logger
.error("edit_iface %s ERROR model must be VF", port_id
)
2434 #create xml detach file
2437 xml
.append("<interface type='hostdev' managed='yes'>")
2438 xml
.append(" <mac address='" +port
['mac']+ "'/>")
2439 xml
.append(" <source>"+ self
.pci2xml(port
['pci'])+"\n </source>")
2440 xml
.append('</interface>')
2445 conn
= host_thread
.lvirt_module
.open(self
.lvirt_conn_uri
)
2446 dom
= conn
.lookupByUUIDString(port
["instance_id"])
2449 self
.logger
.debug("edit_iface detaching SRIOV interface " + text
)
2450 dom
.detachDeviceFlags(text
, flags
=host_thread
.lvirt_module
.VIR_DOMAIN_AFFECT_LIVE
)
2452 xml
[-1] =" <vlan> <tag id='" + str(port
['vlan']) + "'/> </vlan>"
2454 xml
.append(self
.pci2xml(port
.get('vpci',None)) )
2455 xml
.append('</interface>')
2457 self
.logger
.debug("edit_iface attaching SRIOV interface " + text
)
2458 dom
.attachDeviceFlags(text
, flags
=host_thread
.lvirt_module
.VIR_DOMAIN_AFFECT_LIVE
)
2460 except host_thread
.lvirt_module
.libvirtError
as e
:
2461 text
= e
.get_error_message()
2462 self
.logger
.error("edit_iface %s libvirt exception: %s", port
["instance_id"], text
)
2465 if conn
is not None: conn
.close()
2468 def create_server(server
, db
, db_lock
, only_of_ports
):
2469 extended
= server
.get('extended', None)
2471 requirements
['numa']={'memory':0, 'proc_req_type': 'threads', 'proc_req_nb':0, 'port_list':[], 'sriov_list':[]}
2472 requirements
['ram'] = server
['flavor'].get('ram', 0)
2473 if requirements
['ram']== None:
2474 requirements
['ram'] = 0
2475 requirements
['vcpus'] = server
['flavor'].get('vcpus', 0)
2476 if requirements
['vcpus']== None:
2477 requirements
['vcpus'] = 0
2478 #If extended is not defined get requirements from flavor
2479 if extended
is None:
2480 #If extended is defined in flavor convert to dictionary and use it
2481 if 'extended' in server
['flavor'] and server
['flavor']['extended'] != None:
2482 json_acceptable_string
= server
['flavor']['extended'].replace("'", "\"")
2483 extended
= json
.loads(json_acceptable_string
)
2486 #print json.dumps(extended, indent=4)
2488 #For simplicity only one numa VM are supported in the initial implementation
2489 if extended
!= None:
2490 numas
= extended
.get('numas', [])
2492 return (-2, "Multi-NUMA VMs are not supported yet")
2494 # return (-1, "At least one numa must be specified")
2496 #a for loop is used in order to be ready to multi-NUMA VMs
2500 numa_req
['memory'] = numa
.get('memory', 0)
2502 numa_req
['proc_req_nb'] = numa
['cores'] #number of cores or threads to be reserved
2503 numa_req
['proc_req_type'] = 'cores' #indicates whether cores or threads must be reserved
2504 numa_req
['proc_req_list'] = numa
.get('cores-id', None) #list of ids to be assigned to the cores or threads
2505 elif 'paired-threads' in numa
:
2506 numa_req
['proc_req_nb'] = numa
['paired-threads']
2507 numa_req
['proc_req_type'] = 'paired-threads'
2508 numa_req
['proc_req_list'] = numa
.get('paired-threads-id', None)
2509 elif 'threads' in numa
:
2510 numa_req
['proc_req_nb'] = numa
['threads']
2511 numa_req
['proc_req_type'] = 'threads'
2512 numa_req
['proc_req_list'] = numa
.get('threads-id', None)
2514 numa_req
['proc_req_nb'] = 0 # by default
2515 numa_req
['proc_req_type'] = 'threads'
2519 #Generate a list of sriov and another for physical interfaces
2520 interfaces
= numa
.get('interfaces', [])
2523 for iface
in interfaces
:
2524 iface
['bandwidth'] = int(iface
['bandwidth'])
2525 if iface
['dedicated'][:3]=='yes':
2526 port_list
.append(iface
)
2528 sriov_list
.append(iface
)
2530 #Save lists ordered from more restrictive to less bw requirements
2531 numa_req
['sriov_list'] = sorted(sriov_list
, key
=lambda k
: k
['bandwidth'], reverse
=True)
2532 numa_req
['port_list'] = sorted(port_list
, key
=lambda k
: k
['bandwidth'], reverse
=True)
2535 request
.append(numa_req
)
2537 # print "----------\n"+json.dumps(request[0], indent=4)
2538 # print '----------\n\n'
2540 #Search in db for an appropriate numa for each requested numa
2541 #at the moment multi-NUMA VMs are not supported
2543 requirements
['numa'].update(request
[0])
2544 if requirements
['numa']['memory']>0:
2545 requirements
['ram']=0 #By the moment I make incompatible ask for both Huge and non huge pages memory
2546 elif requirements
['ram']==0:
2547 return (-1, "Memory information not set neither at extended field not at ram")
2548 if requirements
['numa']['proc_req_nb']>0:
2549 requirements
['vcpus']=0 #By the moment I make incompatible ask for both Isolated and non isolated cpus
2550 elif requirements
['vcpus']==0:
2551 return (-1, "Processor information not set neither at extended field not at vcpus")
2555 result
, content
= db
.get_numas(requirements
, server
.get('host_id', None), only_of_ports
)
2559 return (-1, content
)
2561 numa_id
= content
['numa_id']
2562 host_id
= content
['host_id']
2564 #obtain threads_id and calculate pinning
2567 if requirements
['numa']['proc_req_nb']>0:
2569 result
, content
= db
.get_table(FROM
='resources_core',
2570 SELECT
=('id','core_id','thread_id'),
2571 WHERE
={'numa_id':numa_id
,'instance_id': None, 'status':'ok'} )
2577 #convert rows to a dictionary indexed by core_id
2580 if not row
['core_id'] in cores_dict
:
2581 cores_dict
[row
['core_id']] = []
2582 cores_dict
[row
['core_id']].append([row
['thread_id'],row
['id']])
2584 #In case full cores are requested
2586 if requirements
['numa']['proc_req_type'] == 'cores':
2587 #Get/create the list of the vcpu_ids
2588 vcpu_id_list
= requirements
['numa']['proc_req_list']
2589 if vcpu_id_list
== None:
2590 vcpu_id_list
= range(0,int(requirements
['numa']['proc_req_nb']))
2592 for threads
in cores_dict
.itervalues():
2594 if len(threads
) != 2:
2597 #set pinning for the first thread
2598 cpu_pinning
.append( [ vcpu_id_list
.pop(0), threads
[0][0], threads
[0][1] ] )
2600 #reserve so it is not used the second thread
2601 reserved_threads
.append(threads
[1][1])
2603 if len(vcpu_id_list
) == 0:
2606 #In case paired threads are requested
2607 elif requirements
['numa']['proc_req_type'] == 'paired-threads':
2609 #Get/create the list of the vcpu_ids
2610 if requirements
['numa']['proc_req_list'] != None:
2612 for pair
in requirements
['numa']['proc_req_list']:
2614 return -1, "Field paired-threads-id not properly specified"
2616 vcpu_id_list
.append(pair
[0])
2617 vcpu_id_list
.append(pair
[1])
2619 vcpu_id_list
= range(0,2*int(requirements
['numa']['proc_req_nb']))
2621 for threads
in cores_dict
.itervalues():
2623 if len(threads
) != 2:
2625 #set pinning for the first thread
2626 cpu_pinning
.append([vcpu_id_list
.pop(0), threads
[0][0], threads
[0][1]])
2628 #set pinning for the second thread
2629 cpu_pinning
.append([vcpu_id_list
.pop(0), threads
[1][0], threads
[1][1]])
2631 if len(vcpu_id_list
) == 0:
2634 #In case normal threads are requested
2635 elif requirements
['numa']['proc_req_type'] == 'threads':
2636 #Get/create the list of the vcpu_ids
2637 vcpu_id_list
= requirements
['numa']['proc_req_list']
2638 if vcpu_id_list
== None:
2639 vcpu_id_list
= range(0,int(requirements
['numa']['proc_req_nb']))
2641 for threads_index
in sorted(cores_dict
, key
=lambda k
: len(cores_dict
[k
])):
2642 threads
= cores_dict
[threads_index
]
2643 #set pinning for the first thread
2644 cpu_pinning
.append([vcpu_id_list
.pop(0), threads
[0][0], threads
[0][1]])
2646 #if exists, set pinning for the second thread
2647 if len(threads
) == 2 and len(vcpu_id_list
) != 0:
2648 cpu_pinning
.append([vcpu_id_list
.pop(0), threads
[1][0], threads
[1][1]])
2650 if len(vcpu_id_list
) == 0:
2653 #Get the source pci addresses for the selected numa
2654 used_sriov_ports
= []
2655 for port
in requirements
['numa']['sriov_list']:
2657 result
, content
= db
.get_table(FROM
='resources_port', SELECT
=('id', 'pci', 'mac'),WHERE
={'numa_id':numa_id
,'root_id': port
['port_id'], 'port_id': None, 'Mbps_used': 0} )
2663 if row
['id'] in used_sriov_ports
or row
['id']==port
['port_id']:
2665 port
['pci'] = row
['pci']
2666 if 'mac_address' not in port
:
2667 port
['mac_address'] = row
['mac']
2669 port
['port_id']=row
['id']
2670 port
['Mbps_used'] = port
['bandwidth']
2671 used_sriov_ports
.append(row
['id'])
2674 for port
in requirements
['numa']['port_list']:
2675 port
['Mbps_used'] = None
2676 if port
['dedicated'] != "yes:sriov":
2677 port
['mac_address'] = port
['mac']
2681 result
, content
= db
.get_table(FROM
='resources_port', SELECT
=('id', 'pci', 'mac', 'Mbps'),WHERE
={'numa_id':numa_id
,'root_id': port
['port_id'], 'port_id': None, 'Mbps_used': 0} )
2686 port
['Mbps_used'] = content
[0]['Mbps']
2688 if row
['id'] in used_sriov_ports
or row
['id']==port
['port_id']:
2690 port
['pci'] = row
['pci']
2691 if 'mac_address' not in port
:
2692 port
['mac_address'] = row
['mac'] # mac cannot be set to passthrough ports
2694 port
['port_id']=row
['id']
2695 used_sriov_ports
.append(row
['id'])
2698 # print '2. Physical ports assignation:'+json.dumps(requirements['port_list'], indent=4)
2699 # print '2. SR-IOV assignation:'+json.dumps(requirements['sriov_list'], indent=4)
2701 server
['host_id'] = host_id
2703 #Generate dictionary for saving in db the instance resources
2705 resources
['bridged-ifaces'] = []
2708 numa_dict
['interfaces'] = []
2710 numa_dict
['interfaces'] += requirements
['numa']['port_list']
2711 numa_dict
['interfaces'] += requirements
['numa']['sriov_list']
2713 #Check bridge information
2714 unified_dataplane_iface
=[]
2715 unified_dataplane_iface
+= requirements
['numa']['port_list']
2716 unified_dataplane_iface
+= requirements
['numa']['sriov_list']
2718 for control_iface
in server
.get('networks', []):
2719 control_iface
['net_id']=control_iface
.pop('uuid')
2720 #Get the brifge name
2722 result
, content
= db
.get_table(FROM
='nets',
2723 SELECT
=('name', 'type', 'vlan', 'provider', 'enable_dhcp','dhcp_first_ip',
2724 'dhcp_last_ip', 'cidr', 'gateway_ip', 'dns', 'links', 'routes'),
2725 WHERE
={'uuid': control_iface
['net_id']})
2730 return -1, "Error at field netwoks: Not found any network wit uuid %s" % control_iface
['net_id']
2733 if control_iface
.get("type", 'virtual') == 'virtual':
2734 if network
['type']!='bridge_data' and network
['type']!='bridge_man':
2735 return -1, "Error at field netwoks: network uuid %s for control interface is not of type bridge_man or bridge_data" % control_iface
['net_id']
2736 resources
['bridged-ifaces'].append(control_iface
)
2737 if network
.get("provider") and network
["provider"][0:3] == "OVS":
2738 control_iface
["type"] = "instance:ovs"
2740 control_iface
["type"] = "instance:bridge"
2741 if network
.get("vlan"):
2742 control_iface
["vlan"] = network
["vlan"]
2744 if network
.get("enable_dhcp") == 'true':
2745 control_iface
["enable_dhcp"] = network
.get("enable_dhcp")
2746 control_iface
["dhcp_first_ip"] = network
["dhcp_first_ip"]
2747 control_iface
["dhcp_last_ip"] = network
["dhcp_last_ip"]
2748 control_iface
["cidr"] = network
["cidr"]
2750 if network
.get("dns"):
2751 control_iface
["dns"] = yaml
.safe_load(network
.get("dns"))
2752 if network
.get("links"):
2753 control_iface
["links"] = yaml
.safe_load(network
.get("links"))
2754 if network
.get("routes"):
2755 control_iface
["routes"] = yaml
.safe_load(network
.get("routes"))
2757 if network
['type']!='data' and network
['type']!='ptp':
2758 return -1, "Error at field netwoks: network uuid %s for dataplane interface is not of type data or ptp" % control_iface
['net_id']
2759 #dataplane interface, look for it in the numa tree and asign this network
2761 for dataplane_iface
in numa_dict
['interfaces']:
2762 if dataplane_iface
['name'] == control_iface
.get("name"):
2763 if (dataplane_iface
['dedicated'] == "yes" and control_iface
["type"] != "PF") or \
2764 (dataplane_iface
['dedicated'] == "no" and control_iface
["type"] != "VF") or \
2765 (dataplane_iface
['dedicated'] == "yes:sriov" and control_iface
["type"] != "VFnotShared") :
2766 return -1, "Error at field netwoks: mismatch at interface '%s' from flavor 'dedicated=%s' and networks 'type=%s'" % \
2767 (control_iface
.get("name"), dataplane_iface
['dedicated'], control_iface
["type"])
2768 dataplane_iface
['uuid'] = control_iface
['net_id']
2769 if dataplane_iface
['dedicated'] == "no":
2770 dataplane_iface
['vlan'] = network
['vlan']
2771 if dataplane_iface
['dedicated'] != "yes" and control_iface
.get("mac_address"):
2772 dataplane_iface
['mac_address'] = control_iface
.get("mac_address")
2773 if control_iface
.get("vpci"):
2774 dataplane_iface
['vpci'] = control_iface
.get("vpci")
2778 return -1, "Error at field netwoks: interface name %s from network not found at flavor" % control_iface
.get("name")
2780 resources
['host_id'] = host_id
2781 resources
['image_id'] = server
['image_id']
2782 resources
['flavor_id'] = server
['flavor_id']
2783 resources
['tenant_id'] = server
['tenant_id']
2784 resources
['ram'] = requirements
['ram']
2785 resources
['vcpus'] = requirements
['vcpus']
2786 resources
['status'] = 'CREATING'
2788 if 'description' in server
: resources
['description'] = server
['description']
2789 if 'name' in server
: resources
['name'] = server
['name']
2791 resources
['extended'] = {} #optional
2792 resources
['extended']['numas'] = []
2793 numa_dict
['numa_id'] = numa_id
2794 numa_dict
['memory'] = requirements
['numa']['memory']
2795 numa_dict
['cores'] = []
2797 for core
in cpu_pinning
:
2798 numa_dict
['cores'].append({'id': core
[2], 'vthread': core
[0], 'paired': paired
})
2799 for core
in reserved_threads
:
2800 numa_dict
['cores'].append({'id': core
})
2801 resources
['extended']['numas'].append(numa_dict
)
2802 if extended
!=None and 'devices' in extended
: #TODO allow extra devices without numa
2803 resources
['extended']['devices'] = extended
['devices']
2806 # '===================================={'
2807 #print json.dumps(resources, indent=4)
2808 #print '====================================}'