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
46 class RunCommandException(Exception):
49 class host_thread(threading
.Thread
):
52 def __init__(self
, name
, host
, user
, db
, db_lock
, test
, image_path
, host_id
, version
, develop_mode
,
53 develop_bridge_iface
, password
=None, keyfile
= None, logger_name
=None, debug
=None, hypervisors
=None):
54 """Init a thread to communicate with compute node or ovs_controller.
55 :param host_id: host identity
56 :param name: name of the thread
57 :param host: host ip or name to manage and user
58 :param user, password, keyfile: user and credentials to connect to host
59 :param db, db_lock': database class and lock to use it in exclusion
61 threading
.Thread
.__init
__(self
)
66 self
.db_lock
= db_lock
68 self
.password
= password
69 self
.keyfile
= keyfile
70 self
.localinfo_dirty
= False
71 self
.connectivity
= True
73 if not test
and not host_thread
.lvirt_module
:
75 module_info
= imp
.find_module("libvirt")
76 host_thread
.lvirt_module
= imp
.load_module("libvirt", *module_info
)
77 except (IOError, ImportError) as e
:
78 raise ImportError("Cannot import python-libvirt. Openvim not properly installed" +str(e
))
80 self
.logger_name
= logger_name
82 self
.logger_name
= "openvim.host."+name
83 self
.logger
= logging
.getLogger(self
.logger_name
)
85 self
.logger
.setLevel(getattr(logging
, debug
))
88 self
.develop_mode
= develop_mode
89 self
.develop_bridge_iface
= develop_bridge_iface
90 self
.image_path
= image_path
91 self
.empty_image_path
= image_path
92 self
.host_id
= host_id
93 self
.version
= version
98 self
.server_status
= {} #dictionary with pairs server_uuid:server_status
99 self
.pending_terminate_server
=[] #list with pairs (time,server_uuid) time to send a terminate for a server being destroyed
100 self
.next_update_server_status
= 0 #time when must be check servers status
102 ####### self.hypervisor = "kvm" #hypervisor flag (default: kvm)
104 self
.hypervisors
= hypervisors
106 self
.hypervisors
= "kvm"
108 self
.xen_hyp
= True if "xen" in self
.hypervisors
else False
112 self
.queueLock
= threading
.Lock()
113 self
.taskQueue
= Queue
.Queue(2000)
115 self
.run_command_session
= None
117 self
.localhost
= True if host
== 'localhost' else False
120 self
.lvirt_conn_uri
= "xen+ssh://{user}@{host}/?no_tty=1&no_verify=1".format(
121 user
=self
.user
, host
=self
.host
)
123 self
.lvirt_conn_uri
= "qemu+ssh://{user}@{host}/system?no_tty=1&no_verify=1".format(
124 user
=self
.user
, host
=self
.host
)
126 self
.lvirt_conn_uri
+= "&keyfile=" + keyfile
128 self
.remote_ip
= None
131 def run_command(self
, command
, keep_session
=False, ignore_exit_status
=False):
132 """Run a command passed as a str on a localhost or at remote machine.
133 :param command: text with the command to execute.
134 :param keep_session: if True it returns a <stdin> for sending input with '<stdin>.write("text\n")'.
135 A command with keep_session=True MUST be followed by a command with keep_session=False in order to
136 close the session and get the output
137 :param ignore_exit_status: Return stdout and not raise an exepction in case of error.
138 :return: the output of the command if 'keep_session=False' or the <stdin> object if 'keep_session=True'
139 :raises: RunCommandException if command fails
141 if self
.run_command_session
and keep_session
:
142 raise RunCommandException("Internal error. A command with keep_session=True must be followed by another "
143 "command with keep_session=False to close session")
146 if self
.run_command_session
:
147 p
= self
.run_command_session
148 self
.run_command_session
= None
149 (output
, outerror
) = p
.communicate()
150 returncode
= p
.returncode
153 p
= subprocess
.Popen(('bash', "-c", command
), stdin
=subprocess
.PIPE
, stdout
=subprocess
.PIPE
,
154 stderr
=subprocess
.PIPE
)
155 self
.run_command_session
= p
158 if not ignore_exit_status
:
159 output
= subprocess
.check_output(('bash', "-c", command
))
163 p
= subprocess
.Popen(('bash', "-c", command
), stdout
=subprocess
.PIPE
)
164 out
, err
= p
.communicate()
167 if self
.run_command_session
:
168 (i
, o
, e
) = self
.run_command_session
169 self
.run_command_session
= None
170 i
.channel
.shutdown_write()
172 if not self
.ssh_conn
:
174 (i
, o
, e
) = self
.ssh_conn
.exec_command(command
, timeout
=10)
176 self
.run_command_session
= (i
, o
, e
)
178 returncode
= o
.channel
.recv_exit_status()
181 if returncode
!= 0 and not ignore_exit_status
:
182 text
= "run_command='{}' Error='{}'".format(command
, outerror
)
183 self
.logger
.error(text
)
184 raise RunCommandException(text
)
186 self
.logger
.debug("run_command='{}' result='{}'".format(command
, output
))
189 except RunCommandException
:
191 except subprocess
.CalledProcessError
as e
:
192 text
= "run_command Exception '{}' '{}'".format(str(e
), e
.output
)
193 except (paramiko
.ssh_exception
.SSHException
, Exception) as e
:
194 text
= "run_command='{}' Exception='{}'".format(command
, str(e
))
196 self
.run_command_session
= None
197 raise RunCommandException(text
)
199 def ssh_connect(self
):
202 self
.ssh_conn
= paramiko
.SSHClient()
203 self
.ssh_conn
.set_missing_host_key_policy(paramiko
.AutoAddPolicy())
204 self
.ssh_conn
.load_system_host_keys()
205 self
.ssh_conn
.connect(self
.host
, username
=self
.user
, password
=self
.password
, key_filename
=self
.keyfile
,
206 timeout
=10) # auth_timeout=10)
207 self
.remote_ip
= self
.ssh_conn
.get_transport().sock
.getpeername()[0]
208 self
.local_ip
= self
.ssh_conn
.get_transport().sock
.getsockname()[0]
209 except (paramiko
.ssh_exception
.SSHException
, Exception) as e
:
210 text
= 'ssh connect Exception: {}'.format(e
)
215 def check_connectivity(self
):
218 command
= 'sudo brctl show'
219 self
.run_command(command
)
220 except RunCommandException
as e
:
221 self
.connectivity
= False
222 self
.logger
.error("check_connectivity Exception: " + str(e
))
224 def load_localinfo(self
):
227 self
.run_command('sudo mkdir -p ' + self
.image_path
)
228 result
= self
.run_command('cat {}/.openvim.yaml'.format(self
.image_path
))
229 self
.localinfo
= yaml
.load(result
)
230 js_v(self
.localinfo
, localinfo_schema
)
231 self
.localinfo_dirty
= False
232 if 'server_files' not in self
.localinfo
:
233 self
.localinfo
['server_files'] = {}
234 self
.logger
.debug("localinfo loaded from host")
236 except RunCommandException
as e
:
237 self
.logger
.error("load_localinfo Exception: " + str(e
))
238 except host_thread
.lvirt_module
.libvirtError
as e
:
239 text
= e
.get_error_message()
240 self
.logger
.error("load_localinfo libvirt Exception: " + text
)
241 except yaml
.YAMLError
as exc
:
243 if hasattr(exc
, 'problem_mark'):
244 mark
= exc
.problem_mark
245 text
= " at position: (%s:%s)" % (mark
.line
+1, mark
.column
+1)
246 self
.logger
.error("load_localinfo yaml format Exception " + text
)
247 except js_e
.ValidationError
as e
:
249 if len(e
.path
)>0: text
=" at '" + ":".join(map(str, e
.path
))+"'"
250 self
.logger
.error("load_localinfo format Exception: %s %s", text
, str(e
))
251 except Exception as e
:
253 self
.logger
.error("load_localinfo Exception: " + text
)
255 # not loaded, insert a default data and force saving by activating dirty flag
256 self
.localinfo
= {'files':{}, 'server_files':{} }
257 # self.localinfo_dirty=True
258 self
.localinfo_dirty
=False
260 def load_hostinfo(self
):
264 result
= self
.run_command('cat {}/hostinfo.yaml'.format(self
.image_path
))
265 self
.hostinfo
= yaml
.load(result
)
266 js_v(self
.hostinfo
, hostinfo_schema
)
267 self
.logger
.debug("hostinfo load from host " + str(self
.hostinfo
))
269 except RunCommandException
as e
:
270 self
.logger
.error("load_hostinfo ssh Exception: " + str(e
))
271 except host_thread
.lvirt_module
.libvirtError
as e
:
272 text
= e
.get_error_message()
273 self
.logger
.error("load_hostinfo libvirt Exception: " + text
)
274 except yaml
.YAMLError
as exc
:
276 if hasattr(exc
, 'problem_mark'):
277 mark
= exc
.problem_mark
278 text
= " at position: (%s:%s)" % (mark
.line
+1, mark
.column
+1)
279 self
.logger
.error("load_hostinfo yaml format Exception " + text
)
280 except js_e
.ValidationError
as e
:
282 if len(e
.path
)>0: text
=" at '" + ":".join(map(str, e
.path
))+"'"
283 self
.logger
.error("load_hostinfo format Exception: %s %s", text
, str(e
))
284 except Exception as e
:
286 self
.logger
.error("load_hostinfo Exception: " + text
)
288 #not loaded, insert a default data
291 def save_localinfo(self
, tries
=3):
293 self
.localinfo_dirty
= False
300 command
= 'cat > {}/.openvim.yaml'.format(self
.image_path
)
301 in_stream
= self
.run_command(command
, keep_session
=True)
302 yaml
.safe_dump(self
.localinfo
, in_stream
, explicit_start
=True, indent
=4, default_flow_style
=False,
303 tags
=False, encoding
='utf-8', allow_unicode
=True)
304 result
= self
.run_command(command
, keep_session
=False) # to end session
306 self
.localinfo_dirty
= False
309 except RunCommandException
as e
:
310 self
.logger
.error("save_localinfo ssh Exception: " + str(e
))
311 except host_thread
.lvirt_module
.libvirtError
as e
:
312 text
= e
.get_error_message()
313 self
.logger
.error("save_localinfo libvirt Exception: " + text
)
314 except yaml
.YAMLError
as exc
:
316 if hasattr(exc
, 'problem_mark'):
317 mark
= exc
.problem_mark
318 text
= " at position: (%s:%s)" % (mark
.line
+1, mark
.column
+1)
319 self
.logger
.error("save_localinfo yaml format Exception " + text
)
320 except Exception as e
:
322 self
.logger
.error("save_localinfo Exception: " + text
)
324 def load_servers_from_db(self
):
325 self
.db_lock
.acquire()
326 r
,c
= self
.db
.get_table(SELECT
=('uuid','status', 'image_id'), FROM
='instances', WHERE
={'host_id': self
.host_id
})
327 self
.db_lock
.release()
329 self
.server_status
= {}
331 self
.logger
.error("Error getting data from database: " + c
)
334 self
.server_status
[ server
['uuid'] ] = server
['status']
336 #convert from old version to new one
337 if 'inc_files' in self
.localinfo
and server
['uuid'] in self
.localinfo
['inc_files']:
338 server_files_dict
= {'source file': self
.localinfo
['inc_files'][ server
['uuid'] ] [0], 'file format':'raw' }
339 if server_files_dict
['source file'][-5:] == 'qcow2':
340 server_files_dict
['file format'] = 'qcow2'
342 self
.localinfo
['server_files'][ server
['uuid'] ] = { server
['image_id'] : server_files_dict
}
343 if 'inc_files' in self
.localinfo
:
344 del self
.localinfo
['inc_files']
345 self
.localinfo_dirty
= True
347 def delete_unused_files(self
):
348 '''Compares self.localinfo['server_files'] content with real servers running self.server_status obtained from database
349 Deletes unused entries at self.loacalinfo and the corresponding local files.
350 The only reason for this mismatch is the manual deletion of instances (VM) at database
354 for uuid
,images
in self
.localinfo
['server_files'].items():
355 if uuid
not in self
.server_status
:
356 for localfile
in images
.values():
358 self
.logger
.debug("deleting file '%s' of unused server '%s'", localfile
['source file'], uuid
)
359 self
.delete_file(localfile
['source file'])
360 except RunCommandException
as e
:
361 self
.logger
.error("Exception deleting file '%s': %s", localfile
['source file'], str(e
))
362 del self
.localinfo
['server_files'][uuid
]
363 self
.localinfo_dirty
= True
365 def insert_task(self
, task
, *aditional
):
367 self
.queueLock
.acquire()
368 task
= self
.taskQueue
.put( (task
,) + aditional
, timeout
=5)
369 self
.queueLock
.release()
372 return -1, "timeout inserting a task over host " + self
.name
376 self
.load_localinfo()
378 self
.load_servers_from_db()
379 self
.delete_unused_files()
382 self
.queueLock
.acquire()
383 if not self
.taskQueue
.empty():
384 task
= self
.taskQueue
.get()
387 self
.queueLock
.release()
391 if self
.localinfo_dirty
:
392 self
.save_localinfo()
393 elif self
.next_update_server_status
< now
:
394 self
.update_servers_status()
395 self
.next_update_server_status
= now
+ 5
396 elif len(self
.pending_terminate_server
)>0 and self
.pending_terminate_server
[0][0]<now
:
397 self
.server_forceoff()
402 if task
[0] == 'instance':
403 self
.logger
.debug("processing task instance " + str(task
[1]['action']))
407 r
= self
.action_on_server(task
[1], retry
==2)
410 elif task
[0] == 'image':
412 elif task
[0] == 'exit':
413 self
.logger
.debug("processing task exit")
416 elif task
[0] == 'reload':
417 self
.logger
.debug("processing task reload terminating and relaunching")
420 elif task
[0] == 'edit-iface':
421 self
.logger
.debug("processing task edit-iface port={}, old_net={}, new_net={}".format(
422 task
[1], task
[2], task
[3]))
423 self
.edit_iface(task
[1], task
[2], task
[3])
424 elif task
[0] == 'restore-iface':
425 self
.logger
.debug("processing task restore-iface={} mac={}".format(task
[1], task
[2]))
426 self
.restore_iface(task
[1], task
[2])
427 elif task
[0] == 'new-ovsbridge':
428 self
.logger
.debug("Creating compute OVS bridge")
429 self
.create_ovs_bridge()
430 elif task
[0] == 'new-vxlan':
431 self
.logger
.debug("Creating vxlan tunnel='{}', remote ip='{}'".format(task
[1], task
[2]))
432 self
.create_ovs_vxlan_tunnel(task
[1], task
[2])
433 elif task
[0] == 'del-ovsbridge':
434 self
.logger
.debug("Deleting OVS bridge")
435 self
.delete_ovs_bridge()
436 elif task
[0] == 'del-vxlan':
437 self
.logger
.debug("Deleting vxlan {} tunnel".format(task
[1]))
438 self
.delete_ovs_vxlan_tunnel(task
[1])
439 elif task
[0] == 'create-ovs-bridge-port':
440 self
.logger
.debug("Adding port ovim-{} to OVS bridge".format(task
[1]))
441 self
.create_ovs_bridge_port(task
[1])
442 elif task
[0] == 'del-ovs-port':
443 self
.logger
.debug("Delete bridge attached to ovs port vlan {} net {}".format(task
[1], task
[2]))
444 self
.delete_bridge_port_attached_to_ovs(task
[1], task
[2])
446 self
.logger
.debug("unknown task " + str(task
))
448 except Exception as e
:
449 self
.logger
.critical("Unexpected exception at run: " + str(e
), exc_info
=True)
451 def server_forceoff(self
, wait_until_finished
=False):
452 while len(self
.pending_terminate_server
)>0:
454 if self
.pending_terminate_server
[0][0]>now
:
455 if wait_until_finished
:
460 req
={'uuid':self
.pending_terminate_server
[0][1],
461 'action':{'terminate':'force'},
464 self
.action_on_server(req
)
465 self
.pending_terminate_server
.pop(0)
469 self
.server_forceoff(True)
470 if self
.localinfo_dirty
:
471 self
.save_localinfo()
473 self
.ssh_conn
.close()
474 except Exception as e
:
476 self
.logger
.error("terminate Exception: " + text
)
477 self
.logger
.debug("exit from host_thread")
479 def get_local_iface_name(self
, generic_name
):
480 if self
.hostinfo
!= None and "iface_names" in self
.hostinfo
and generic_name
in self
.hostinfo
["iface_names"]:
481 return self
.hostinfo
["iface_names"][generic_name
]
484 def create_xml_server(self
, server
, dev_list
, server_metadata
={}):
485 """Function that implements the generation of the VM XML definition.
486 Additional devices are in dev_list list
487 The main disk is upon dev_list[0]"""
489 #get if operating system is Windows
491 os_type
= server_metadata
.get('os_type', None)
492 if os_type
== None and 'metadata' in dev_list
[0]:
493 os_type
= dev_list
[0]['metadata'].get('os_type', None)
494 if os_type
!= None and os_type
.lower() == "windows":
496 #get type of hard disk bus
497 bus_ide
= True if windows_os
else False
498 bus
= server_metadata
.get('bus', None)
499 if bus
== None and 'metadata' in dev_list
[0]:
500 bus
= dev_list
[0]['metadata'].get('bus', None)
502 bus_ide
= True if bus
=='ide' else False
505 hypervisor
= server
.get('hypervisor', 'kvm')
506 os_type_img
= server
.get('os_image_type', 'other')
508 if hypervisor
[:3] == 'xen':
509 text
= "<domain type='xen'>"
511 text
= "<domain type='kvm'>"
513 topo
= server_metadata
.get('topology', None)
514 if topo
== None and 'metadata' in dev_list
[0]:
515 topo
= dev_list
[0]['metadata'].get('topology', None)
517 name
= server
.get('name', '')[:28] + "_" + server
['uuid'][:28] #qemu impose a length limit of 59 chars or not start. Using 58
518 text
+= self
.inc_tab() + "<name>" + name
+ "</name>"
520 text
+= self
.tab() + "<uuid>" + server
['uuid'] + "</uuid>"
523 if 'extended' in server
and server
['extended']!=None and 'numas' in server
['extended']:
524 numa
= server
['extended']['numas'][0]
527 memory
= int(numa
.get('memory',0))*1024*1024 #in KiB
529 memory
= int(server
['ram'])*1024;
531 if not self
.develop_mode
:
534 return -1, 'No memory assigned to instance'
536 text
+= self
.tab() + "<memory unit='KiB'>" +memory
+"</memory>"
537 text
+= self
.tab() + "<currentMemory unit='KiB'>" +memory
+ "</currentMemory>"
539 text
+= self
.tab()+'<memoryBacking>'+ \
540 self
.inc_tab() + '<hugepages/>'+ \
541 self
.dec_tab()+ '</memoryBacking>'
544 use_cpu_pinning
=False
545 vcpus
= int(server
.get("vcpus",0))
547 if 'cores-source' in numa
:
549 for index
in range(0, len(numa
['cores-source'])):
550 cpu_pinning
.append( [ numa
['cores-id'][index
], numa
['cores-source'][index
] ] )
552 if 'threads-source' in numa
:
554 for index
in range(0, len(numa
['threads-source'])):
555 cpu_pinning
.append( [ numa
['threads-id'][index
], numa
['threads-source'][index
] ] )
557 if 'paired-threads-source' in numa
:
559 for index
in range(0, len(numa
['paired-threads-source'])):
560 cpu_pinning
.append( [numa
['paired-threads-id'][index
][0], numa
['paired-threads-source'][index
][0] ] )
561 cpu_pinning
.append( [numa
['paired-threads-id'][index
][1], numa
['paired-threads-source'][index
][1] ] )
564 if use_cpu_pinning
and not self
.develop_mode
:
565 text
+= self
.tab()+"<vcpu placement='static'>" +str(len(cpu_pinning
)) +"</vcpu>" + \
566 self
.tab()+'<cputune>'
568 for i
in range(0, len(cpu_pinning
)):
569 text
+= self
.tab() + "<vcpupin vcpu='" +str(cpu_pinning
[i
][0])+ "' cpuset='" +str(cpu_pinning
[i
][1]) +"'/>"
570 text
+= self
.dec_tab()+'</cputune>'+ \
571 self
.tab() + '<numatune>' +\
572 self
.inc_tab() + "<memory mode='strict' nodeset='" +str(numa
['source'])+ "'/>" +\
573 self
.dec_tab() + '</numatune>'
576 return -1, "Instance without number of cpus"
577 text
+= self
.tab()+"<vcpu>" + str(vcpus
) + "</vcpu>"
582 if dev
['type']=='cdrom' :
585 if hypervisor
== 'xenhvm':
586 text
+= self
.tab()+ '<os>' + \
587 self
.inc_tab() + "<type arch='x86_64' machine='xenfv'>hvm</type>"
588 text
+= self
.tab() + "<loader type='rom'>/usr/lib/xen/boot/hvmloader</loader>"
590 text
+= self
.tab() + "<boot dev='cdrom'/>"
591 text
+= self
.tab() + "<boot dev='hd'/>" + \
592 self
.dec_tab()+'</os>'
593 elif hypervisor
== 'xen-unik':
594 text
+= self
.tab()+ '<os>' + \
595 self
.inc_tab() + "<type arch='x86_64' machine='xenpv'>xen</type>"
596 text
+= self
.tab() + "<kernel>" + str(dev_list
[0]['source file']) + "</kernel>" + \
597 self
.dec_tab()+'</os>'
599 text
+= self
.tab()+ '<os>' + \
600 self
.inc_tab() + "<type arch='x86_64' machine='pc'>hvm</type>"
602 text
+= self
.tab() + "<boot dev='cdrom'/>"
603 text
+= self
.tab() + "<boot dev='hd'/>" + \
604 self
.dec_tab()+'</os>'
606 text
+= self
.tab()+'<features>'+\
607 self
.inc_tab()+'<acpi/>' +\
608 self
.tab()+'<apic/>' +\
609 self
.tab()+'<pae/>'+ \
610 self
.dec_tab() +'</features>'
611 if topo
== "oneSocket:hyperthreading":
613 return -1, 'Cannot expose hyperthreading with an odd number of vcpus'
614 text
+= self
.tab() + "<cpu mode='host-model'> <topology sockets='1' cores='%d' threads='2' /> </cpu>" % (vcpus
/2)
615 elif windows_os
or topo
== "oneSocket":
616 text
+= self
.tab() + "<cpu mode='host-model'> <topology sockets='1' cores='%d' threads='1' /> </cpu>" % vcpus
618 text
+= self
.tab() + "<cpu mode='host-model'></cpu>"
619 text
+= self
.tab() + "<clock offset='utc'/>" +\
620 self
.tab() + "<on_poweroff>preserve</on_poweroff>" + \
621 self
.tab() + "<on_reboot>restart</on_reboot>" + \
622 self
.tab() + "<on_crash>restart</on_crash>"
623 if hypervisor
== 'xenhvm':
624 text
+= self
.tab() + "<devices>" + \
625 self
.inc_tab() + "<emulator>/usr/bin/qemu-system-i386</emulator>" + \
626 self
.tab() + "<serial type='pty'>" +\
627 self
.inc_tab() + "<target port='0'/>" + \
628 self
.dec_tab() + "</serial>" +\
629 self
.tab() + "<console type='pty'>" + \
630 self
.inc_tab()+ "<target type='serial' port='0'/>" + \
631 self
.dec_tab()+'</console>' #In some libvirt version may be: <emulator>/usr/lib64/xen/bin/qemu-dm</emulator> (depends on distro)
632 elif hypervisor
== 'xen-unik':
633 text
+= self
.tab() + "<devices>" + \
634 self
.tab() + "<console type='pty'>" + \
635 self
.inc_tab()+ "<target type='xen' port='0'/>" + \
636 self
.dec_tab()+'</console>'
638 text
+= self
.tab() + "<devices>" + \
639 self
.inc_tab() + "<emulator>/usr/libexec/qemu-kvm</emulator>" + \
640 self
.tab() + "<serial type='pty'>" +\
641 self
.inc_tab() + "<target port='0'/>" + \
642 self
.dec_tab() + "</serial>" +\
643 self
.tab() + "<console type='pty'>" + \
644 self
.inc_tab()+ "<target type='serial' port='0'/>" + \
645 self
.dec_tab()+'</console>'
647 text
+= self
.tab() + "<controller type='usb' index='0'/>" + \
648 self
.tab() + "<controller type='ide' index='0'/>" + \
649 self
.tab() + "<input type='mouse' bus='ps2'/>" + \
650 self
.tab() + "<sound model='ich6'/>" + \
651 self
.tab() + "<video>" + \
652 self
.inc_tab() + "<model type='cirrus' vram='9216' heads='1'/>" + \
653 self
.dec_tab() + "</video>" + \
654 self
.tab() + "<memballoon model='virtio'/>" + \
655 self
.tab() + "<input type='tablet' bus='usb'/>" #TODO revisar
656 elif hypervisor
== 'xen-unik':
659 text
+= self
.tab() + "<controller type='ide' index='0'/>" + \
660 self
.tab() + "<input type='mouse' bus='ps2'/>" + \
661 self
.tab() + "<input type='keyboard' bus='ps2'/>" + \
662 self
.tab() + "<video>" + \
663 self
.inc_tab() + "<model type='cirrus' vram='9216' heads='1'/>" + \
664 self
.dec_tab() + "</video>"
666 #> self.tab()+'<alias name=\'hostdev0\'/>\n' +\
667 #> self.dec_tab()+'</hostdev>\n' +\
668 #> self.tab()+'<input type=\'tablet\' bus=\'usb\'/>\n'
670 text
+= self
.tab() + "<graphics type='vnc' port='-1' autoport='yes'/>"
672 #If image contains 'GRAPH' include graphics
673 #if 'GRAPH' in image:
674 text
+= self
.tab() + "<graphics type='vnc' port='-1' autoport='yes' listen='0.0.0.0'>" +\
675 self
.inc_tab() + "<listen type='address' address='0.0.0.0'/>" +\
676 self
.dec_tab() + "</graphics>"
680 bus_ide_dev
= bus_ide
681 if (dev
['type']=='cdrom' or dev
['type']=='disk') and hypervisor
!= 'xen-unik':
682 if dev
['type']=='cdrom':
684 text
+= self
.tab() + "<disk type='file' device='"+dev
['type']+"'>"
685 if 'file format' in dev
:
686 text
+= self
.inc_tab() + "<driver name='qemu' type='" +dev
['file format']+ "' cache='writethrough'/>"
687 if 'source file' in dev
:
688 text
+= self
.tab() + "<source file='" +dev
['source file']+ "'/>"
689 #elif v['type'] == 'block':
690 # text += self.tab() + "<source dev='" + v['source'] + "'/>"
692 # return -1, 'Unknown disk type ' + v['type']
693 vpci
= dev
.get('vpci',None)
694 if vpci
== None and 'metadata' in dev
:
695 vpci
= dev
['metadata'].get('vpci',None)
696 text
+= self
.pci2xml(vpci
)
699 text
+= self
.tab() + "<target dev='hd" +vd_index
+ "' bus='ide'/>" #TODO allows several type of disks
701 text
+= self
.tab() + "<target dev='vd" +vd_index
+ "' bus='virtio'/>"
702 text
+= self
.dec_tab() + '</disk>'
703 vd_index
= chr(ord(vd_index
)+1)
704 elif dev
['type']=='xml':
705 dev_text
= dev
['xml']
707 dev_text
= dev_text
.replace('__vpci__', dev
['vpci'])
708 if 'source file' in dev
:
709 dev_text
= dev_text
.replace('__file__', dev
['source file'])
710 if 'file format' in dev
:
711 dev_text
= dev_text
.replace('__format__', dev
['source file'])
712 if '__dev__' in dev_text
:
713 dev_text
= dev_text
.replace('__dev__', vd_index
)
714 vd_index
= chr(ord(vd_index
)+1)
716 elif hypervisor
== 'xen-unik':
719 return -1, 'Unknown device type ' + dev
['type']
722 bridge_interfaces
= server
.get('networks', [])
723 for v
in bridge_interfaces
:
725 self
.db_lock
.acquire()
726 result
, content
= self
.db
.get_table(FROM
='nets', SELECT
=('provider',),WHERE
={'uuid':v
['net_id']} )
727 self
.db_lock
.release()
729 self
.logger
.error("create_xml_server ERROR %d getting nets %s", result
, content
)
731 #ALF: Allow by the moment the 'default' bridge net because is confortable for provide internet to VM
732 #I know it is not secure
733 #for v in sorted(desc['network interfaces'].itervalues()):
734 model
= v
.get("model", None)
735 if content
[0]['provider']=='default':
736 text
+= self
.tab() + "<interface type='network'>" + \
737 self
.inc_tab() + "<source network='" +content
[0]['provider']+ "'/>"
738 elif content
[0]['provider'][0:7]=='macvtap':
739 text
+= self
.tab()+"<interface type='direct'>" + \
740 self
.inc_tab() + "<source dev='" + self
.get_local_iface_name(content
[0]['provider'][8:]) + "' mode='bridge'/>" + \
741 self
.tab() + "<target dev='macvtap0'/>"
743 text
+= self
.tab() + "<alias name='net" + str(net_nb
) + "'/>"
746 elif content
[0]['provider'][0:6]=='bridge':
747 text
+= self
.tab() + "<interface type='bridge'>" + \
748 self
.inc_tab()+"<source bridge='" +self
.get_local_iface_name(content
[0]['provider'][7:])+ "'/>"
750 text
+= self
.tab() + "<target dev='vnet" + str(net_nb
)+ "'/>" +\
751 self
.tab() + "<alias name='net" + str(net_nb
)+ "'/>"
754 elif content
[0]['provider'][0:3] == "OVS":
755 vlan
= content
[0]['provider'].replace('OVS:', '')
756 text
+= self
.tab() + "<interface type='bridge'>" + \
757 self
.inc_tab() + "<source bridge='ovim-" + str(vlan
) + "'/>"
758 if hypervisor
== 'xenhvm' or hypervisor
== 'xen-unik':
759 text
+= self
.tab() + "<script path='vif-openvswitch'/>"
761 return -1, 'Unknown Bridge net provider ' + content
[0]['provider']
763 text
+= self
.tab() + "<model type='" +model
+ "'/>"
764 if v
.get('mac_address', None) != None:
765 text
+= self
.tab() +"<mac address='" +v
['mac_address']+ "'/>"
766 text
+= self
.pci2xml(v
.get('vpci',None))
767 text
+= self
.dec_tab()+'</interface>'
771 interfaces
= numa
.get('interfaces', [])
775 if self
.develop_mode
: #map these interfaces to bridges
776 text
+= self
.tab() + "<interface type='bridge'>" + \
777 self
.inc_tab()+"<source bridge='" +self
.develop_bridge_iface
+ "'/>"
779 text
+= self
.tab() + "<target dev='vnet" + str(net_nb
)+ "'/>" +\
780 self
.tab() + "<alias name='net" + str(net_nb
)+ "'/>"
782 text
+= self
.tab() + "<model type='e1000'/>" #e1000 is more probable to be supported than 'virtio'
783 if v
.get('mac_address', None) != None:
784 text
+= self
.tab() +"<mac address='" +v
['mac_address']+ "'/>"
785 text
+= self
.pci2xml(v
.get('vpci',None))
786 text
+= self
.dec_tab()+'</interface>'
789 if v
['dedicated'] == 'yes': #passthrought
790 text
+= self
.tab() + "<hostdev mode='subsystem' type='pci' managed='yes'>" + \
791 self
.inc_tab() + "<source>"
793 text
+= self
.pci2xml(v
['source'])
794 text
+= self
.dec_tab()+'</source>'
795 text
+= self
.pci2xml(v
.get('vpci',None))
797 text
+= self
.tab() + "<alias name='hostdev" + str(net_nb
) + "'/>"
798 text
+= self
.dec_tab()+'</hostdev>'
800 else: #sriov_interfaces
801 #skip not connected interfaces
802 if v
.get("net_id") == None:
804 text
+= self
.tab() + "<interface type='hostdev' managed='yes'>"
806 if v
.get('mac_address', None) != None:
807 text
+= self
.tab() + "<mac address='" +v
['mac_address']+ "'/>"
808 text
+= self
.tab()+'<source>'
810 text
+= self
.pci2xml(v
['source'])
811 text
+= self
.dec_tab()+'</source>'
812 if v
.get('vlan',None) != None:
813 text
+= self
.tab() + "<vlan> <tag id='" + str(v
['vlan']) + "'/> </vlan>"
814 text
+= self
.pci2xml(v
.get('vpci',None))
816 text
+= self
.tab() + "<alias name='hostdev" + str(net_nb
) + "'/>"
817 text
+= self
.dec_tab()+'</interface>'
820 text
+= self
.dec_tab()+'</devices>'+\
821 self
.dec_tab()+'</domain>'
824 def pci2xml(self
, pci
):
825 '''from a pci format text XXXX:XX:XX.X generates the xml content of <address>
826 alows an empty pci text'''
829 first_part
= pci
.split(':')
830 second_part
= first_part
[2].split('.')
831 return self
.tab() + "<address type='pci' domain='0x" + first_part
[0] + \
832 "' bus='0x" + first_part
[1] + "' slot='0x" + second_part
[0] + \
833 "' function='0x" + second_part
[1] + "'/>"
836 """Return indentation according to xml_level"""
837 return "\n" + (' '*self
.xml_level
)
840 """Increment and return indentation according to xml_level"""
845 """Decrement and return indentation according to xml_level"""
849 def create_ovs_bridge(self
):
851 Create a bridge in compute OVS to allocate VMs
852 :return: True if success
854 if self
.test
or not self
.connectivity
:
858 self
.run_command('sudo ovs-vsctl --may-exist add-br br-int -- set Bridge br-int stp_enable=true')
861 except RunCommandException
as e
:
862 self
.logger
.error("create_ovs_bridge ssh Exception: " + str(e
))
863 if "SSH session not active" in str(e
):
867 def delete_port_to_ovs_bridge(self
, vlan
, net_uuid
):
869 Delete linux bridge port attched to a OVS bridge, if port is not free the port is not removed
870 :param vlan: vlan port id
871 :param net_uuid: network id
875 if self
.test
or not self
.connectivity
:
878 port_name
= 'ovim-{}'.format(str(vlan
))
879 command
= 'sudo ovs-vsctl del-port br-int {}'.format(port_name
)
880 self
.run_command(command
)
882 except RunCommandException
as e
:
883 self
.logger
.error("delete_port_to_ovs_bridge ssh Exception: {}".format(str(e
)))
886 def delete_dhcp_server(self
, vlan
, net_uuid
, dhcp_path
):
888 Delete dhcp server process lining in namespace
889 :param vlan: segmentation id
890 :param net_uuid: network uuid
891 :param dhcp_path: conf fiel path that live in namespace side
894 if self
.test
or not self
.connectivity
:
896 if not self
.is_dhcp_port_free(vlan
, net_uuid
):
899 dhcp_namespace
= '{}-dnsmasq'.format(str(vlan
))
900 dhcp_path
= os
.path
.join(dhcp_path
, dhcp_namespace
)
901 pid_file
= os
.path
.join(dhcp_path
, 'dnsmasq.pid')
903 command
= 'sudo ip netns exec {} cat {}'.format(dhcp_namespace
, pid_file
)
904 content
= self
.run_command(command
, ignore_exit_status
=True)
905 dns_pid
= content
.replace('\n', '')
906 command
= 'sudo ip netns exec {} kill -9 {}'.format(dhcp_namespace
, dns_pid
)
907 self
.run_command(command
, ignore_exit_status
=True)
909 except RunCommandException
as e
:
910 self
.logger
.error("delete_dhcp_server ssh Exception: " + str(e
))
913 def is_dhcp_port_free(self
, host_id
, net_uuid
):
915 Check if any port attached to the a net in a vxlan mesh across computes nodes
916 :param host_id: host id
917 :param net_uuid: network id
918 :return: True if is not free
920 self
.db_lock
.acquire()
921 result
, content
= self
.db
.get_table(
923 WHERE
={'type': 'instance:ovs', 'net_id': net_uuid
}
925 self
.db_lock
.release()
932 def is_port_free(self
, host_id
, net_uuid
):
934 Check if there not ovs ports of a network in a compute host.
935 :param host_id: host id
936 :param net_uuid: network id
937 :return: True if is not free
940 self
.db_lock
.acquire()
941 result
, content
= self
.db
.get_table(
942 FROM
='ports as p join instances as i on p.instance_id=i.uuid',
943 WHERE
={"i.host_id": self
.host_id
, 'p.type': 'instance:ovs', 'p.net_id': net_uuid
}
945 self
.db_lock
.release()
952 def add_port_to_ovs_bridge(self
, vlan
):
954 Add a bridge linux as a port to a OVS bridge and set a vlan for an specific linux bridge
955 :param vlan: vlan port id
956 :return: True if success
962 port_name
= 'ovim-{}'.format(str(vlan
))
963 command
= 'sudo ovs-vsctl add-port br-int {} tag={}'.format(port_name
, str(vlan
))
964 self
.run_command(command
)
966 except RunCommandException
as e
:
967 self
.logger
.error("add_port_to_ovs_bridge Exception: " + str(e
))
970 def delete_dhcp_port(self
, vlan
, net_uuid
, dhcp_path
):
972 Delete from an existing OVS bridge a linux bridge port attached and the linux bridge itself.
973 :param vlan: segmentation id
974 :param net_uuid: network id
975 :return: True if success
981 if not self
.is_dhcp_port_free(vlan
, net_uuid
):
983 self
.delete_dhcp_interfaces(vlan
, dhcp_path
)
986 def delete_bridge_port_attached_to_ovs(self
, vlan
, net_uuid
):
988 Delete from an existing OVS bridge a linux bridge port attached and the linux bridge itself.
991 :return: True if success
996 if not self
.is_port_free(vlan
, net_uuid
):
998 self
.delete_port_to_ovs_bridge(vlan
, net_uuid
)
999 self
.delete_linux_bridge(vlan
)
1002 def delete_linux_bridge(self
, vlan
):
1004 Delete a linux bridge in a scpecific compute.
1005 :param vlan: vlan port id
1006 :return: True if success
1012 port_name
= 'ovim-{}'.format(str(vlan
))
1013 command
= 'sudo ip link set dev ovim-{} down'.format(str(vlan
))
1014 self
.run_command(command
)
1016 command
= 'sudo ip link delete {} && sudo brctl delbr {}'.format(port_name
, port_name
)
1017 self
.run_command(command
)
1019 except RunCommandException
as e
:
1020 self
.logger
.error("delete_linux_bridge Exception: {}".format(str(e
)))
1023 def remove_link_bridge_to_ovs(self
, vlan
, link
):
1025 Delete a linux provider net connection to tenatn net
1026 :param vlan: vlan port id
1027 :param link: link name
1028 :return: True if success
1034 br_tap_name
= '{}-vethBO'.format(str(vlan
))
1035 br_ovs_name
= '{}-vethOB'.format(str(vlan
))
1037 # Delete ovs veth pair
1038 command
= 'sudo ip link set dev {} down'.format(br_ovs_name
)
1039 self
.run_command(command
, ignore_exit_status
=True)
1041 command
= 'sudo ovs-vsctl del-port br-int {}'.format(br_ovs_name
)
1042 self
.run_command(command
)
1044 # Delete br veth pair
1045 command
= 'sudo ip link set dev {} down'.format(br_tap_name
)
1046 self
.run_command(command
, ignore_exit_status
=True)
1048 # Delete br veth interface form bridge
1049 command
= 'sudo brctl delif {} {}'.format(link
, br_tap_name
)
1050 self
.run_command(command
)
1052 # Delete br veth pair
1053 command
= 'sudo ip link set dev {} down'.format(link
)
1054 self
.run_command(command
, ignore_exit_status
=True)
1057 except RunCommandException
as e
:
1058 self
.logger
.error("remove_link_bridge_to_ovs Exception: {}".format(str(e
)))
1061 def create_ovs_bridge_port(self
, vlan
):
1063 Generate a linux bridge and attache the port to a OVS bridge
1064 :param vlan: vlan port id
1069 self
.create_linux_bridge(vlan
)
1070 self
.add_port_to_ovs_bridge(vlan
)
1072 def create_linux_bridge(self
, vlan
):
1074 Create a linux bridge with STP active
1075 :param vlan: netowrk vlan id
1082 port_name
= 'ovim-{}'.format(str(vlan
))
1083 command
= 'sudo brctl show | grep {}'.format(port_name
)
1084 result
= self
.run_command(command
, ignore_exit_status
=True)
1086 command
= 'sudo brctl addbr {}'.format(port_name
)
1087 self
.run_command(command
)
1089 command
= 'sudo brctl stp {} on'.format(port_name
)
1090 self
.run_command(command
)
1092 command
= 'sudo ip link set dev {} up'.format(port_name
)
1093 self
.run_command(command
)
1095 except RunCommandException
as e
:
1096 self
.logger
.error("create_linux_bridge ssh Exception: {}".format(str(e
)))
1099 def set_mac_dhcp_server(self
, ip
, mac
, vlan
, netmask
, first_ip
, dhcp_path
):
1101 Write into dhcp conf file a rule to assigned a fixed ip given to an specific MAC address
1102 :param ip: IP address asigned to a VM
1103 :param mac: VM vnic mac to be macthed with the IP received
1104 :param vlan: Segmentation id
1105 :param netmask: netmask value
1106 :param path: dhcp conf file path that live in namespace side
1107 :return: True if success
1113 dhcp_namespace
= '{}-dnsmasq'.format(str(vlan
))
1114 dhcp_path
= os
.path
.join(dhcp_path
, dhcp_namespace
)
1115 dhcp_hostsdir
= os
.path
.join(dhcp_path
, dhcp_namespace
)
1116 ns_interface
= '{}-vethDO'.format(str(vlan
))
1121 command
= 'sudo ip netns exec {} cat /sys/class/net/{}/address'.format(dhcp_namespace
, ns_interface
)
1122 iface_listen_mac
= self
.run_command(command
, ignore_exit_status
=True)
1124 if iface_listen_mac
> 0:
1125 command
= 'sudo ip netns exec {} cat {} | grep -i {}'.format(dhcp_namespace
,
1128 content
= self
.run_command(command
, ignore_exit_status
=True)
1130 ip_data
= iface_listen_mac
.upper().replace('\n', '') + ',' + first_ip
1131 dhcp_hostsdir
= os
.path
.join(dhcp_path
, dhcp_namespace
)
1133 command
= 'sudo ip netns exec {} sudo bash -ec "echo {} >> {}"'.format(dhcp_namespace
,
1136 self
.run_command(command
)
1138 ip_data
= mac
.upper() + ',' + ip
1139 command
= 'sudo ip netns exec {} sudo bash -ec "echo {} >> {}"'.format(dhcp_namespace
,
1142 self
.run_command(command
, ignore_exit_status
=False)
1147 except RunCommandException
as e
:
1148 self
.logger
.error("set_mac_dhcp_server ssh Exception: " + str(e
))
1151 def delete_mac_dhcp_server(self
, ip
, mac
, vlan
, dhcp_path
):
1153 Delete into dhcp conf file the ip assigned to a specific MAC address
1155 :param ip: IP address asigned to a VM
1156 :param mac: VM vnic mac to be macthed with the IP received
1157 :param vlan: Segmentation id
1158 :param dhcp_path: dhcp conf file path that live in namespace side
1165 dhcp_namespace
= str(vlan
) + '-dnsmasq'
1166 dhcp_path
= os
.path
.join(dhcp_path
, dhcp_namespace
)
1167 dhcp_hostsdir
= os
.path
.join(dhcp_path
, dhcp_namespace
)
1172 ip_data
= mac
.upper() + ',' + ip
1174 command
= 'sudo ip netns exec ' + dhcp_namespace
+ ' sudo sed -i \'/' + ip_data
+ '/d\' ' + dhcp_hostsdir
1175 self
.run_command(command
)
1178 except RunCommandException
as e
:
1179 self
.logger
.error("delete_mac_dhcp_server Exception: " + str(e
))
1182 def launch_dhcp_server(self
, vlan
, ip_range
, netmask
, dhcp_path
, gateway
, dns_list
=None, routes
=None):
1184 Generate a linux bridge and attache the port to a OVS bridge
1186 :param vlan: Segmentation id
1187 :param ip_range: IP dhcp range
1188 :param netmask: network netmask
1189 :param dhcp_path: dhcp conf file path that live in namespace side
1190 :param gateway: Gateway address for dhcp net
1191 :param dns_list: dns list for dhcp server
1192 :param routes: routes list for dhcp server
1193 :return: True if success
1199 ns_interface
= str(vlan
) + '-vethDO'
1200 dhcp_namespace
= str(vlan
) + '-dnsmasq'
1201 dhcp_path
= os
.path
.join(dhcp_path
, dhcp_namespace
, '')
1202 leases_path
= os
.path
.join(dhcp_path
, "dnsmasq.leases")
1203 pid_file
= os
.path
.join(dhcp_path
, 'dnsmasq.pid')
1205 dhcp_range
= ip_range
[0] + ',' + ip_range
[1] + ',' + netmask
1207 command
= 'sudo ip netns exec {} mkdir -p {}'.format(dhcp_namespace
, dhcp_path
)
1208 self
.run_command(command
)
1210 # check if dnsmasq process is running
1211 dnsmasq_is_runing
= False
1212 pid_path
= os
.path
.join(dhcp_path
, 'dnsmasq.pid')
1213 command
= 'sudo ip netns exec ' + dhcp_namespace
+ ' ls ' + pid_path
1214 content
= self
.run_command(command
, ignore_exit_status
=True)
1216 # check if pid is runing
1218 pid_path
= content
.replace('\n', '')
1219 command
= "ps aux | awk '{print $2 }' | grep {}" + pid_path
1220 dnsmasq_is_runing
= self
.run_command(command
, ignore_exit_status
=True)
1222 gateway_option
= ' --dhcp-option=3,' + gateway
1224 dhcp_route_option
= ''
1226 dhcp_route_option
= ' --dhcp-option=121'
1227 for key
, value
in routes
.iteritems():
1228 if 'default' == key
:
1229 gateway_option
= ' --dhcp-option=3,' + value
1231 dhcp_route_option
+= ',' + key
+ ',' + value
1234 dns_data
= ' --dhcp-option=6'
1235 for dns
in dns_list
:
1236 dns_data
+= ',' + dns
1238 if not dnsmasq_is_runing
:
1239 command
= 'sudo ip netns exec ' + dhcp_namespace
+ ' /usr/sbin/dnsmasq --strict-order --except-interface=lo ' \
1240 '--interface=' + ns_interface
+ \
1241 ' --bind-interfaces --dhcp-hostsdir=' + dhcp_path
+ \
1242 ' --dhcp-range ' + dhcp_range
+ \
1243 ' --pid-file=' + pid_file
+ \
1244 ' --dhcp-leasefile=' + leases_path
+ \
1245 ' --listen-address ' + ip_range
[0] + \
1247 dhcp_route_option
+ \
1250 self
.run_command(command
)
1252 except RunCommandException
as e
:
1253 self
.logger
.error("launch_dhcp_server ssh Exception: " + str(e
))
1256 def delete_dhcp_interfaces(self
, vlan
, dhcp_path
):
1258 Delete a linux dnsmasq bridge and namespace
1259 :param vlan: netowrk vlan id
1266 br_veth_name
='{}-vethDO'.format(str(vlan
))
1267 ovs_veth_name
= '{}-vethOD'.format(str(vlan
))
1268 dhcp_namespace
= '{}-dnsmasq'.format(str(vlan
))
1270 dhcp_path
= os
.path
.join(dhcp_path
, dhcp_namespace
)
1271 command
= 'sudo ovs-vsctl del-port br-int {}'.format(ovs_veth_name
)
1272 self
.run_command(command
, ignore_exit_status
=True) # to end session
1274 command
= 'sudo ip link set dev {} down'.format(ovs_veth_name
)
1275 self
.run_command(command
, ignore_exit_status
=True) # to end session
1277 command
= 'sudo ip link delete {} '.format(ovs_veth_name
)
1278 self
.run_command(command
, ignore_exit_status
=True)
1280 command
= 'sudo ip netns exec {} ip link set dev {} down'.format(dhcp_namespace
, br_veth_name
)
1281 self
.run_command(command
, ignore_exit_status
=True)
1283 command
= 'sudo rm -rf {}'.format(dhcp_path
)
1284 self
.run_command(command
)
1286 command
= 'sudo ip netns del {}'.format(dhcp_namespace
)
1287 self
.run_command(command
)
1290 except RunCommandException
as e
:
1291 self
.logger
.error("delete_dhcp_interfaces ssh Exception: {}".format(str(e
)))
1294 def create_dhcp_interfaces(self
, vlan
, ip_listen_address
, netmask
):
1296 Create a linux bridge with STP active
1297 :param vlan: segmentation id
1298 :param ip_listen_address: Listen Ip address for the dhcp service, the tap interface living in namesapce side
1299 :param netmask: dhcp net CIDR
1300 :return: True if success
1305 ovs_veth_name
= '{}-vethOD'.format(str(vlan
))
1306 ns_veth
= '{}-vethDO'.format(str(vlan
))
1307 dhcp_namespace
= '{}-dnsmasq'.format(str(vlan
))
1309 command
= 'sudo ip netns add {}'.format(dhcp_namespace
)
1310 self
.run_command(command
)
1312 command
= 'sudo ip link add {} type veth peer name {}'.format(ns_veth
, ovs_veth_name
)
1313 self
.run_command(command
)
1315 command
= 'sudo ip link set {} netns {}'.format(ns_veth
, dhcp_namespace
)
1316 self
.run_command(command
)
1318 command
= 'sudo ip netns exec {} ip link set dev {} up'.format(dhcp_namespace
, ns_veth
)
1319 self
.run_command(command
)
1321 command
= 'sudo ovs-vsctl add-port br-int {} tag={}'.format(ovs_veth_name
, str(vlan
))
1322 self
.run_command(command
, ignore_exit_status
=True)
1324 command
= 'sudo ip link set dev {} up'.format(ovs_veth_name
)
1325 self
.run_command(command
)
1327 command
= 'sudo ip netns exec {} ip link set dev lo up'.format(dhcp_namespace
)
1328 self
.run_command(command
)
1330 command
= 'sudo ip netns exec {} ifconfig {} {} netmask {}'.format(dhcp_namespace
,
1334 self
.run_command(command
)
1336 except RunCommandException
as e
:
1337 self
.logger
.error("create_dhcp_interfaces ssh Exception: {}".format(str(e
)))
1340 def delete_qrouter_connection(self
, vlan
, link
):
1342 Delete qrouter Namesapce with all veth interfaces need it
1351 ns_qouter
= '{}-qrouter'.format(str(vlan
))
1352 qrouter_ovs_veth
= '{}-vethOQ'.format(str(vlan
))
1353 qrouter_ns_veth
= '{}-vethQO'.format(str(vlan
))
1354 qrouter_br_veth
= '{}-vethBQ'.format(str(vlan
))
1355 qrouter_ns_router_veth
= '{}-vethQB'.format(str(vlan
))
1357 command
= 'sudo ovs-vsctl del-port br-int {}'.format(qrouter_ovs_veth
)
1358 self
.run_command(command
, ignore_exit_status
=True)
1361 command
= 'sudo ip netns exec {} ip link set dev {} down'.format(ns_qouter
, qrouter_ns_veth
)
1362 self
.run_command(command
, ignore_exit_status
=True)
1364 command
= 'sudo ip netns exec {} ip link delete {} '.format(ns_qouter
, qrouter_ns_veth
)
1365 self
.run_command(command
, ignore_exit_status
=True)
1367 command
= 'sudo ip netns del ' + ns_qouter
1368 self
.run_command(command
)
1370 # down ovs veth interface
1371 command
= 'sudo ip link set dev {} down'.format(qrouter_br_veth
)
1372 self
.run_command(command
, ignore_exit_status
=True)
1374 # down br veth interface
1375 command
= 'sudo ip link set dev {} down'.format(qrouter_ovs_veth
)
1376 self
.run_command(command
, ignore_exit_status
=True)
1378 # delete veth interface
1379 command
= 'sudo ip link delete {} '.format(link
, qrouter_ovs_veth
)
1380 self
.run_command(command
, ignore_exit_status
=True)
1382 # down br veth interface
1383 command
= 'sudo ip link set dev {} down'.format(qrouter_ns_router_veth
)
1384 self
.run_command(command
, ignore_exit_status
=True)
1386 # delete veth interface
1387 command
= 'sudo ip link delete {} '.format(link
, qrouter_ns_router_veth
)
1388 self
.run_command(command
, ignore_exit_status
=True)
1390 # down br veth interface
1391 command
= 'sudo brctl delif {} {}'.format(link
, qrouter_br_veth
)
1392 self
.run_command(command
)
1396 except RunCommandException
as e
:
1397 self
.logger
.error("delete_qrouter_connection ssh Exception: {}".format(str(e
)))
1400 def create_qrouter_ovs_connection(self
, vlan
, gateway
, dhcp_cidr
):
1402 Create qrouter Namesapce with all veth interfaces need it between NS and OVS
1412 ns_qouter
= '{}-qrouter'.format(str(vlan
))
1413 qrouter_ovs_veth
='{}-vethOQ'.format(str(vlan
))
1414 qrouter_ns_veth
= '{}-vethQO'.format(str(vlan
))
1417 command
= 'sudo ip netns add {}'.format(ns_qouter
)
1418 self
.run_command(command
)
1421 command
= 'sudo ip link add {} type veth peer name {}'.format(qrouter_ns_veth
, qrouter_ovs_veth
)
1422 self
.run_command(command
, ignore_exit_status
=True)
1424 # up ovs veth interface
1425 command
= 'sudo ip link set dev {} up'.format(qrouter_ovs_veth
)
1426 self
.run_command(command
)
1428 # add ovs veth to ovs br-int
1429 command
= 'sudo ovs-vsctl add-port br-int {} tag={}'.format(qrouter_ovs_veth
, vlan
)
1430 self
.run_command(command
)
1433 command
= 'sudo ip link set {} netns {}'.format(qrouter_ns_veth
, ns_qouter
)
1434 self
.run_command(command
)
1437 command
= 'sudo ip netns exec {} ip link set dev lo up'.format(ns_qouter
)
1438 self
.run_command(command
)
1441 command
= 'sudo ip netns exec {} ip link set dev {} up'.format(ns_qouter
, qrouter_ns_veth
)
1442 self
.run_command(command
)
1444 from netaddr
import IPNetwork
1445 ip_tools
= IPNetwork(dhcp_cidr
)
1446 cidr_len
= ip_tools
.prefixlen
1449 command
= 'sudo ip netns exec {} ip address add {}/{} dev {}'.format(ns_qouter
, gateway
, cidr_len
, qrouter_ns_veth
)
1450 self
.run_command(command
)
1454 except RunCommandException
as e
:
1455 self
.logger
.error("Create_dhcp_interfaces ssh Exception: {}".format(str(e
)))
1458 def add_ns_routes(self
, vlan
, routes
):
1470 ns_qouter
= '{}-qrouter'.format(str(vlan
))
1471 qrouter_ns_router_veth
= '{}-vethQB'.format(str(vlan
))
1473 for key
, value
in routes
.iteritems():
1475 if key
== 'default':
1476 command
= 'sudo ip netns exec {} ip route add {} via {} '.format(ns_qouter
, key
, value
)
1478 command
= 'sudo ip netns exec {} ip route add {} via {} dev {}'.format(ns_qouter
, key
, value
,
1479 qrouter_ns_router_veth
)
1481 self
.run_command(command
)
1485 except RunCommandException
as e
:
1486 self
.logger
.error("add_ns_routes, error adding routes to namesapce, {}".format(str(e
)))
1489 def create_qrouter_br_connection(self
, vlan
, cidr
, link
):
1491 Create veth interfaces between user bridge (link) and OVS
1501 ns_qouter
= '{}-qrouter'.format(str(vlan
))
1502 qrouter_ns_router_veth
= '{}-vethQB'.format(str(vlan
))
1503 qrouter_br_veth
= '{}-vethBQ'.format(str(vlan
))
1505 command
= 'sudo brctl show | grep {}'.format(link
['iface'])
1506 content
= self
.run_command(command
, ignore_exit_status
=True)
1510 command
= 'sudo ip link add {} type veth peer name {}'.format(qrouter_br_veth
, qrouter_ns_router_veth
)
1511 self
.run_command(command
)
1513 # up ovs veth interface
1514 command
= 'sudo ip link set dev {} up'.format(qrouter_br_veth
)
1515 self
.run_command(command
)
1518 command
= 'sudo ip link set {} netns {}'.format(qrouter_ns_router_veth
, ns_qouter
)
1519 self
.run_command(command
)
1522 command
= 'sudo ip netns exec {} ip link set dev {} up'.format(ns_qouter
, qrouter_ns_router_veth
)
1523 self
.run_command(command
)
1525 command
= 'sudo ip netns exec {} ip address add {} dev {}'.format(ns_qouter
,
1527 qrouter_ns_router_veth
)
1528 self
.run_command(command
)
1531 command
= 'sudo brctl addif {} {}'.format(link
['iface'], qrouter_br_veth
)
1532 self
.run_command(command
)
1535 command
= 'sudo ip netns exec {} iptables -t nat -A POSTROUTING -o {} -s {} -d {} -j MASQUERADE' \
1536 .format(ns_qouter
, qrouter_ns_router_veth
, link
['nat'], cidr
)
1537 self
.run_command(command
)
1542 self
.logger
.error('create_qrouter_br_connection, Bridge {} given by user not exist'.format(qrouter_br_veth
))
1545 except RunCommandException
as e
:
1546 self
.logger
.error("Error creating qrouter, {}".format(str(e
)))
1549 def create_link_bridge_to_ovs(self
, vlan
, link
):
1551 Create interfaces to connect a linux bridge with tenant net
1552 :param vlan: segmentation id
1553 :return: True if success
1559 br_tap_name
= '{}-vethBO'.format(str(vlan
))
1560 br_ovs_name
= '{}-vethOB'.format(str(vlan
))
1562 # is a bridge or a interface
1563 command
= 'sudo brctl show | grep {}'.format(link
)
1564 content
= self
.run_command(command
, ignore_exit_status
=True)
1566 command
= 'sudo ip link add {} type veth peer name {}'.format(br_tap_name
, br_ovs_name
)
1567 self
.run_command(command
)
1569 command
= 'sudo ip link set dev {} up'.format(br_tap_name
)
1570 self
.run_command(command
)
1572 command
= 'sudo ip link set dev {} up'.format(br_ovs_name
)
1573 self
.run_command(command
)
1575 command
= 'sudo ovs-vsctl add-port br-int {} tag={}'.format(br_ovs_name
, str(vlan
))
1576 self
.run_command(command
)
1578 command
= 'sudo brctl addif ' + link
+ ' {}'.format(br_tap_name
)
1579 self
.run_command(command
)
1582 self
.logger
.error('Link is not present, please check {}'.format(link
))
1585 except RunCommandException
as e
:
1586 self
.logger
.error("create_link_bridge_to_ovs, Error creating link to ovs, {}".format(str(e
)))
1589 def create_ovs_vxlan_tunnel(self
, vxlan_interface
, remote_ip
):
1591 Create a vlxn tunnel between to computes with an OVS installed. STP is also active at port level
1592 :param vxlan_interface: vlxan inteface name.
1593 :param remote_ip: tunnel endpoint remote compute ip.
1596 if self
.test
or not self
.connectivity
:
1598 if remote_ip
== 'localhost':
1600 return True # TODO: Cannot create a vxlan between localhost and localhost
1601 remote_ip
= self
.local_ip
1604 command
= 'sudo ovs-vsctl add-port br-int {} -- set Interface {} type=vxlan options:remote_ip={} ' \
1605 '-- set Port {} other_config:stp-path-cost=10'.format(vxlan_interface
,
1609 self
.run_command(command
)
1611 except RunCommandException
as e
:
1612 self
.logger
.error("create_ovs_vxlan_tunnel, error creating vxlan tunnel, {}".format(str(e
)))
1615 def delete_ovs_vxlan_tunnel(self
, vxlan_interface
):
1617 Delete a vlxan tunnel port from a OVS brdige.
1618 :param vxlan_interface: vlxan name to be delete it.
1619 :return: True if success.
1621 if self
.test
or not self
.connectivity
:
1624 command
= 'sudo ovs-vsctl del-port br-int {}'.format(vxlan_interface
)
1625 self
.run_command(command
)
1627 except RunCommandException
as e
:
1628 self
.logger
.error("delete_ovs_vxlan_tunnel, error deleting vxlan tunenl, {}".format(str(e
)))
1631 def delete_ovs_bridge(self
):
1633 Delete a OVS bridge from a compute.
1634 :return: True if success
1636 if self
.test
or not self
.connectivity
:
1639 command
= 'sudo ovs-vsctl del-br br-int'
1640 self
.run_command(command
)
1642 except RunCommandException
as e
:
1643 self
.logger
.error("delete_ovs_bridge ssh Exception: {}".format(str(e
)))
1646 def get_file_info(self
, path
):
1647 command
= 'ls -lL --time-style=+%Y-%m-%dT%H:%M:%S ' + path
1649 content
= self
.run_command(command
)
1650 return content
.split(" ") # (permission, 1, owner, group, size, date, file)
1651 except RunCommandException
as e
:
1652 return None # file does not exist
1654 def qemu_get_info(self
, path
):
1655 command
= 'qemu-img info ' + path
1656 content
= self
.run_command(command
)
1658 return yaml
.load(content
)
1659 except yaml
.YAMLError
as exc
:
1661 if hasattr(exc
, 'problem_mark'):
1662 mark
= exc
.problem_mark
1663 text
= " at position: (%s:%s)" % (mark
.line
+1, mark
.column
+1)
1664 self
.logger
.error("get_qemu_info yaml format Exception " + text
)
1665 raise RunCommandException("Error getting qemu_info yaml format" + text
)
1667 def qemu_change_backing(self
, inc_file
, new_backing_file
):
1668 command
= 'qemu-img rebase -u -b {} {}'.format(new_backing_file
, inc_file
)
1670 self
.run_command(command
)
1672 except RunCommandException
as e
:
1673 self
.logger
.error("qemu_change_backing error: " + str(e
))
1676 def qemu_create_empty_disk(self
, dev
):
1678 if not dev
and 'source' not in dev
and 'file format' not in dev
and 'image_size' not in dev
:
1679 self
.logger
.error("qemu_create_empty_disk error: missing image parameter")
1682 empty_disk_path
= dev
['source file']
1684 command
= 'qemu-img create -f qcow2 {} {}G'.format(empty_disk_path
, dev
['image_size'])
1686 self
.run_command(command
)
1688 except RunCommandException
as e
:
1689 self
.logger
.error("qemu_create_empty_disk error: " + str(e
))
1692 def get_notused_filename(self
, proposed_name
, suffix
=''):
1693 '''Look for a non existing file_name in the host
1694 proposed_name: proposed file name, includes path
1695 suffix: suffix to be added to the name, before the extention
1697 extension
= proposed_name
.rfind(".")
1698 slash
= proposed_name
.rfind("/")
1699 if extension
< 0 or extension
< slash
: # no extension
1700 extension
= len(proposed_name
)
1701 target_name
= proposed_name
[:extension
] + suffix
+ proposed_name
[extension
:]
1702 info
= self
.get_file_info(target_name
)
1707 while info
is not None:
1708 target_name
= proposed_name
[:extension
] + suffix
+ "-" + str(index
) + proposed_name
[extension
:]
1710 info
= self
.get_file_info(target_name
)
1713 def get_notused_path(self
, proposed_path
, suffix
=''):
1714 '''Look for a non existing path at database for images
1715 proposed_path: proposed file name, includes path
1716 suffix: suffix to be added to the name, before the extention
1718 extension
= proposed_path
.rfind(".")
1720 extension
= len(proposed_path
)
1722 target_path
= proposed_path
[:extension
] + suffix
+ proposed_path
[extension
:]
1725 r
,_
=self
.db
.get_table(FROM
="images",WHERE
={"path":target_path
})
1728 target_path
= proposed_path
[:extension
] + suffix
+ "-" + str(index
) + proposed_path
[extension
:]
1732 def delete_file(self
, file_name
):
1733 command
= 'rm -f ' + file_name
1734 self
.run_command(command
)
1736 def copy_file(self
, source
, destination
, perserve_time
=True):
1737 if source
[0:4]=="http":
1738 command
= "wget --no-verbose -O '{dst}' '{src}' 2>'{dst_result}' || cat '{dst_result}' >&2 && rm '{dst_result}'".format(
1739 dst
=destination
, src
=source
, dst_result
=destination
+ ".result" )
1741 command
= 'cp --no-preserve=mode'
1743 command
+= ' --preserve=timestamps'
1744 command
+= " '{}' '{}'".format(source
, destination
)
1745 self
.run_command(command
)
1747 def copy_remote_file(self
, remote_file
, use_incremental
):
1748 ''' Copy a file from the repository to local folder and recursively
1749 copy the backing files in case the remote file is incremental
1750 Read and/or modified self.localinfo['files'] that contain the
1751 unmodified copies of images in the local path
1753 remote_file: path of remote file
1754 use_incremental: None (leave the decision to this function), True, False
1756 local_file: name of local file
1757 qemu_info: dict with quemu information of local file
1758 use_incremental_out: True, False; same as use_incremental, but if None a decision is taken
1761 use_incremental_out
= use_incremental
1762 new_backing_file
= None
1764 file_from_local
= True
1766 #in case incremental use is not decided, take the decision depending on the image
1767 #avoid the use of incremental if this image is already incremental
1768 if remote_file
[0:4] == "http":
1769 file_from_local
= False
1771 qemu_remote_info
= self
.qemu_get_info(remote_file
)
1772 if use_incremental_out
==None:
1773 use_incremental_out
= not ( file_from_local
and 'backing file' in qemu_remote_info
)
1774 #copy recursivelly the backing files
1775 if file_from_local
and 'backing file' in qemu_remote_info
:
1776 new_backing_file
, _
, _
= self
.copy_remote_file(qemu_remote_info
['backing file'], True)
1778 #check if remote file is present locally
1779 if use_incremental_out
and remote_file
in self
.localinfo
['files']:
1780 local_file
= self
.localinfo
['files'][remote_file
]
1781 local_file_info
= self
.get_file_info(local_file
)
1783 remote_file_info
= self
.get_file_info(remote_file
)
1784 if local_file_info
== None:
1786 elif file_from_local
and (local_file_info
[4]!=remote_file_info
[4] or local_file_info
[5]!=remote_file_info
[5]):
1787 #local copy of file not valid because date or size are different.
1788 #TODO DELETE local file if this file is not used by any active virtual machine
1790 self
.delete_file(local_file
)
1791 del self
.localinfo
['files'][remote_file
]
1795 else: #check that the local file has the same backing file, or there are not backing at all
1796 qemu_info
= self
.qemu_get_info(local_file
)
1797 if new_backing_file
!= qemu_info
.get('backing file'):
1801 if local_file
== None: #copy the file
1802 img_name
= remote_file
.split('/') [-1]
1803 img_local
= self
.image_path
+ '/' + img_name
1804 local_file
= self
.get_notused_filename(img_local
)
1805 self
.copy_file(remote_file
, local_file
, use_incremental_out
)
1807 if use_incremental_out
:
1808 self
.localinfo
['files'][remote_file
] = local_file
1809 if new_backing_file
:
1810 self
.qemu_change_backing(local_file
, new_backing_file
)
1811 qemu_info
= self
.qemu_get_info(local_file
)
1813 return local_file
, qemu_info
, use_incremental_out
1815 def launch_server(self
, conn
, server
, rebuild
=False, domain
=None):
1817 time
.sleep(random
.randint(20,150)) #sleep random timeto be make it a bit more real
1820 server_id
= server
['uuid']
1821 paused
= server
.get('paused','no')
1823 if domain
!=None and rebuild
==False:
1825 #self.server_status[server_id] = 'ACTIVE'
1828 self
.db_lock
.acquire()
1829 result
, server_data
= self
.db
.get_instance(server_id
)
1830 self
.db_lock
.release()
1832 self
.logger
.error("launch_server ERROR getting server from DB %d %s", result
, server_data
)
1833 return result
, server_data
1835 self
.hypervisor
= str(server_data
['hypervisor'])
1837 #0: get image metadata
1838 server_metadata
= server
.get('metadata', {})
1839 use_incremental
= None
1841 if "use_incremental" in server_metadata
:
1842 use_incremental
= False if server_metadata
["use_incremental"] == "no" else True
1843 if self
.xen_hyp
== True:
1844 use_incremental
= False
1846 server_host_files
= self
.localinfo
['server_files'].get( server
['uuid'], {})
1848 #delete previous incremental files
1849 for file_
in server_host_files
.values():
1850 self
.delete_file(file_
['source file'] )
1851 server_host_files
={}
1853 #1: obtain aditional devices (disks)
1854 #Put as first device the main disk
1855 devices
= [ {"type":"disk", "image_id":server
['image_id'], "vpci":server_metadata
.get('vpci', None) } ]
1856 if 'extended' in server_data
and server_data
['extended']!=None and "devices" in server_data
['extended']:
1857 devices
+= server_data
['extended']['devices']
1860 image_id
= dev
.get('image_id')
1863 uuid_empty
= str(uuid
.uuid4())
1864 empty_path
= self
.empty_image_path
+ uuid_empty
+ '.qcow2' # local path for empty disk
1866 dev
['source file'] = empty_path
1867 dev
['file format'] = 'qcow2'
1868 self
.qemu_create_empty_disk(dev
)
1869 server_host_files
[uuid_empty
] = {'source file': empty_path
,
1870 'file format': dev
['file format']}
1874 self
.db_lock
.acquire()
1875 result
, content
= self
.db
.get_table(FROM
='images', SELECT
=('path', 'metadata'),
1876 WHERE
={'uuid': image_id
})
1877 self
.db_lock
.release()
1879 error_text
= "ERROR", result
, content
, "when getting image", dev
['image_id']
1880 self
.logger
.error("launch_server " + error_text
)
1881 return -1, error_text
1882 if content
[0]['metadata'] is not None:
1883 dev
['metadata'] = json
.loads(content
[0]['metadata'])
1885 dev
['metadata'] = {}
1887 if image_id
in server_host_files
:
1888 dev
['source file'] = server_host_files
[image_id
]['source file'] #local path
1889 dev
['file format'] = server_host_files
[image_id
]['file format'] # raw or qcow2
1892 #2: copy image to host
1894 remote_file
= content
[0]['path']
1896 remote_file
= empty_path
1897 use_incremental_image
= use_incremental
1898 if dev
['metadata'].get("use_incremental") == "no":
1899 use_incremental_image
= False
1900 local_file
, qemu_info
, use_incremental_image
= self
.copy_remote_file(remote_file
, use_incremental_image
)
1902 #create incremental image
1903 if use_incremental_image
:
1904 local_file_inc
= self
.get_notused_filename(local_file
, '.inc')
1905 command
= 'qemu-img create -f qcow2 {} -o backing_file={}'.format(local_file_inc
, local_file
)
1906 self
.run_command(command
)
1907 local_file
= local_file_inc
1908 qemu_info
= {'file format': 'qcow2'}
1910 server_host_files
[ dev
['image_id'] ] = {'source file': local_file
, 'file format': qemu_info
['file format']}
1912 dev
['source file'] = local_file
1913 dev
['file format'] = qemu_info
['file format']
1915 self
.localinfo
['server_files'][ server
['uuid'] ] = server_host_files
1916 self
.localinfo_dirty
= True
1919 result
, xml
= self
.create_xml_server(server_data
, devices
, server_metadata
) #local_file
1921 self
.logger
.error("create xml server error: " + xml
)
1923 self
.logger
.debug("create xml: " + xml
)
1924 atribute
= host_thread
.lvirt_module
.VIR_DOMAIN_START_PAUSED
if paused
== "yes" else 0
1926 if not rebuild
: #ensures that any pending destroying server is done
1927 self
.server_forceoff(True)
1928 #self.logger.debug("launching instance " + xml)
1929 conn
.createXML(xml
, atribute
)
1930 #self.server_status[server_id] = 'PAUSED' if paused == "yes" else 'ACTIVE'
1934 except paramiko
.ssh_exception
.SSHException
as e
:
1936 self
.logger
.error("launch_server id='%s' ssh Exception: %s", server_id
, text
)
1937 if "SSH session not active" in text
:
1939 except host_thread
.lvirt_module
.libvirtError
as e
:
1940 text
= e
.get_error_message()
1941 self
.logger
.error("launch_server id='%s' libvirt Exception: %s", server_id
, text
)
1942 except Exception as e
:
1944 self
.logger
.error("launch_server id='%s' Exception: %s", server_id
, text
)
1947 def update_servers_status(self
):
1949 # VIR_DOMAIN_NOSTATE = 0
1950 # VIR_DOMAIN_RUNNING = 1
1951 # VIR_DOMAIN_BLOCKED = 2
1952 # VIR_DOMAIN_PAUSED = 3
1953 # VIR_DOMAIN_SHUTDOWN = 4
1954 # VIR_DOMAIN_SHUTOFF = 5
1955 # VIR_DOMAIN_CRASHED = 6
1956 # VIR_DOMAIN_PMSUSPENDED = 7 #TODO suspended
1958 if self
.test
or len(self
.server_status
)==0:
1962 conn
= host_thread
.lvirt_module
.open(self
.lvirt_conn_uri
)
1963 domains
= conn
.listAllDomains()
1965 for domain
in domains
:
1966 uuid
= domain
.UUIDString() ;
1967 libvirt_status
= domain
.state()
1968 #print libvirt_status
1969 if libvirt_status
[0] == host_thread
.lvirt_module
.VIR_DOMAIN_RUNNING
or libvirt_status
[0] == host_thread
.lvirt_module
.VIR_DOMAIN_SHUTDOWN
:
1970 new_status
= "ACTIVE"
1971 elif libvirt_status
[0] == host_thread
.lvirt_module
.VIR_DOMAIN_PAUSED
:
1972 new_status
= "PAUSED"
1973 elif libvirt_status
[0] == host_thread
.lvirt_module
.VIR_DOMAIN_SHUTOFF
:
1974 new_status
= "INACTIVE"
1975 elif libvirt_status
[0] == host_thread
.lvirt_module
.VIR_DOMAIN_CRASHED
:
1976 new_status
= "ERROR"
1979 domain_dict
[uuid
] = new_status
1981 except host_thread
.lvirt_module
.libvirtError
as e
:
1982 self
.logger
.error("get_state() Exception " + e
.get_error_message())
1985 for server_id
, current_status
in self
.server_status
.iteritems():
1987 if server_id
in domain_dict
:
1988 new_status
= domain_dict
[server_id
]
1990 new_status
= "INACTIVE"
1992 if new_status
== None or new_status
== current_status
:
1994 if new_status
== 'INACTIVE' and current_status
== 'ERROR':
1995 continue #keep ERROR status, because obviously this machine is not running
1997 self
.logger
.debug("server id='%s' status change from '%s' to '%s'", server_id
, current_status
, new_status
)
1998 STATUS
={'progress':100, 'status':new_status
}
1999 if new_status
== 'ERROR':
2000 STATUS
['last_error'] = 'machine has crashed'
2001 self
.db_lock
.acquire()
2002 r
,_
= self
.db
.update_rows('instances', STATUS
, {'uuid':server_id
}, log
=False)
2003 self
.db_lock
.release()
2005 self
.server_status
[server_id
] = new_status
2007 def action_on_server(self
, req
, last_retry
=True):
2008 '''Perform an action on a req
2010 req: dictionary that contain:
2011 server properties: 'uuid','name','tenant_id','status'
2013 host properties: 'user', 'ip_name'
2014 return (error, text)
2015 0: No error. VM is updated to new state,
2016 -1: Invalid action, as trying to pause a PAUSED VM
2017 -2: Error accessing host
2019 -4: Error at DB access
2020 -5: Error while trying to perform action. VM is updated to ERROR
2022 server_id
= req
['uuid']
2025 old_status
= req
['status']
2029 if 'terminate' in req
['action']:
2030 new_status
= 'deleted'
2031 elif 'shutoff' in req
['action'] or 'shutdown' in req
['action'] or 'forceOff' in req
['action']:
2032 if req
['status']!='ERROR':
2034 new_status
= 'INACTIVE'
2035 elif 'start' in req
['action'] and req
['status']!='ERROR':
2036 new_status
= 'ACTIVE'
2037 elif 'resume' in req
['action'] and req
['status']!='ERROR' and req
['status']!='INACTIVE':
2038 new_status
= 'ACTIVE'
2039 elif 'pause' in req
['action'] and req
['status']!='ERROR':
2040 new_status
= 'PAUSED'
2041 elif 'reboot' in req
['action'] and req
['status']!='ERROR':
2042 new_status
= 'ACTIVE'
2043 elif 'rebuild' in req
['action']:
2044 time
.sleep(random
.randint(20,150))
2045 new_status
= 'ACTIVE'
2046 elif 'createImage' in req
['action']:
2048 self
.create_image(None, req
)
2051 conn
= host_thread
.lvirt_module
.open(self
.lvirt_conn_uri
)
2053 dom
= conn
.lookupByUUIDString(server_id
)
2054 except host_thread
.lvirt_module
.libvirtError
as e
:
2055 text
= e
.get_error_message()
2056 if 'LookupByUUIDString' in text
or 'Domain not found' in text
or 'No existe un dominio coincidente' in text
:
2059 self
.logger
.error("action_on_server id='%s' libvirt exception: %s", server_id
, text
)
2062 if 'forceOff' in req
['action']:
2064 self
.logger
.debug("action_on_server id='%s' domain not running", server_id
)
2067 self
.logger
.debug("sending DESTROY to server id='%s'", server_id
)
2069 except Exception as e
:
2070 if "domain is not running" not in e
.get_error_message():
2071 self
.logger
.error("action_on_server id='%s' Exception while sending force off: %s",
2072 server_id
, e
.get_error_message())
2073 last_error
= 'action_on_server Exception while destroy: ' + e
.get_error_message()
2074 new_status
= 'ERROR'
2076 elif 'terminate' in req
['action']:
2078 self
.logger
.debug("action_on_server id='%s' domain not running", server_id
)
2079 new_status
= 'deleted'
2082 if req
['action']['terminate'] == 'force':
2083 self
.logger
.debug("sending DESTROY to server id='%s'", server_id
)
2085 new_status
= 'deleted'
2087 self
.logger
.debug("sending SHUTDOWN to server id='%s'", server_id
)
2089 self
.pending_terminate_server
.append( (time
.time()+10,server_id
) )
2090 except Exception as e
:
2091 self
.logger
.error("action_on_server id='%s' Exception while destroy: %s",
2092 server_id
, e
.get_error_message())
2093 last_error
= 'action_on_server Exception while destroy: ' + e
.get_error_message()
2094 new_status
= 'ERROR'
2095 if "domain is not running" in e
.get_error_message():
2098 new_status
= 'deleted'
2100 self
.logger
.error("action_on_server id='%s' Exception while undefine: %s",
2101 server_id
, e
.get_error_message())
2102 last_error
= 'action_on_server Exception2 while undefine:', e
.get_error_message()
2103 #Exception: 'virDomainDetachDevice() failed'
2104 if new_status
=='deleted':
2105 if server_id
in self
.server_status
:
2106 del self
.server_status
[server_id
]
2107 if req
['uuid'] in self
.localinfo
['server_files']:
2108 for file_
in self
.localinfo
['server_files'][ req
['uuid'] ].values():
2110 self
.delete_file(file_
['source file'])
2113 del self
.localinfo
['server_files'][ req
['uuid'] ]
2114 self
.localinfo_dirty
= True
2116 elif 'shutoff' in req
['action'] or 'shutdown' in req
['action']:
2119 self
.logger
.debug("action_on_server id='%s' domain not running", server_id
)
2122 # new_status = 'INACTIVE'
2123 #TODO: check status for changing at database
2124 except Exception as e
:
2125 new_status
= 'ERROR'
2126 self
.logger
.error("action_on_server id='%s' Exception while shutdown: %s",
2127 server_id
, e
.get_error_message())
2128 last_error
= 'action_on_server Exception while shutdown: ' + e
.get_error_message()
2130 elif 'rebuild' in req
['action']:
2133 r
= self
.launch_server(conn
, req
, True, None)
2135 new_status
= 'ERROR'
2138 new_status
= 'ACTIVE'
2139 elif 'start' in req
['action']:
2140 # The instance is only create in DB but not yet at libvirt domain, needs to be create
2141 rebuild
= True if req
['action']['start'] == 'rebuild' else False
2142 r
= self
.launch_server(conn
, req
, rebuild
, dom
)
2144 new_status
= 'ERROR'
2147 new_status
= 'ACTIVE'
2149 elif 'resume' in req
['action']:
2155 # new_status = 'ACTIVE'
2156 except Exception as e
:
2157 self
.logger
.error("action_on_server id='%s' Exception while resume: %s",
2158 server_id
, e
.get_error_message())
2160 elif 'pause' in req
['action']:
2166 # new_status = 'PAUSED'
2167 except Exception as e
:
2168 self
.logger
.error("action_on_server id='%s' Exception while pause: %s",
2169 server_id
, e
.get_error_message())
2171 elif 'reboot' in req
['action']:
2177 self
.logger
.debug("action_on_server id='%s' reboot:", server_id
)
2178 #new_status = 'ACTIVE'
2179 except Exception as e
:
2180 self
.logger
.error("action_on_server id='%s' Exception while reboot: %s",
2181 server_id
, e
.get_error_message())
2182 elif 'createImage' in req
['action']:
2183 self
.create_image(dom
, req
)
2187 except host_thread
.lvirt_module
.libvirtError
as e
:
2188 if conn
is not None: conn
.close()
2189 text
= e
.get_error_message()
2190 new_status
= "ERROR"
2192 if 'LookupByUUIDString' in text
or 'Domain not found' in text
or 'No existe un dominio coincidente' in text
:
2193 self
.logger
.debug("action_on_server id='%s' Exception removed from host", server_id
)
2195 self
.logger
.error("action_on_server id='%s' Exception %s", server_id
, text
)
2196 #end of if self.test
2197 if new_status
== None:
2200 self
.logger
.debug("action_on_server id='%s' new status=%s %s",server_id
, new_status
, last_error
)
2201 UPDATE
= {'progress':100, 'status':new_status
}
2203 if new_status
=='ERROR':
2204 if not last_retry
: #if there will be another retry do not update database
2206 elif 'terminate' in req
['action']:
2207 #PUT a log in the database
2208 self
.logger
.error("PANIC deleting server id='%s' %s", server_id
, last_error
)
2209 self
.db_lock
.acquire()
2210 self
.db
.new_row('logs',
2211 {'uuid':server_id
, 'tenant_id':req
['tenant_id'], 'related':'instances','level':'panic',
2212 'description':'PANIC deleting server from host '+self
.name
+': '+last_error
}
2214 self
.db_lock
.release()
2215 if server_id
in self
.server_status
:
2216 del self
.server_status
[server_id
]
2219 UPDATE
['last_error'] = last_error
2220 if new_status
!= 'deleted' and (new_status
!= old_status
or new_status
== 'ERROR') :
2221 self
.db_lock
.acquire()
2222 self
.db
.update_rows('instances', UPDATE
, {'uuid':server_id
}, log
=True)
2223 self
.server_status
[server_id
] = new_status
2224 self
.db_lock
.release()
2225 if new_status
== 'ERROR':
2230 def restore_iface(self
, name
, mac
, lib_conn
=None):
2231 ''' make an ifdown, ifup to restore default parameter of na interface
2233 mac: mac address of the interface
2234 lib_conn: connection to the libvirt, if None a new connection is created
2235 Return 0,None if ok, -1,text if fails
2241 self
.logger
.debug("restore_iface '%s' %s", name
, mac
)
2245 conn
= host_thread
.lvirt_module
.open(self
.lvirt_conn_uri
)
2249 #wait to the pending VM deletion
2250 #TODO.Revise self.server_forceoff(True)
2252 iface
= conn
.interfaceLookupByMACString(mac
)
2253 if iface
.isActive():
2256 self
.logger
.debug("restore_iface '%s' %s", name
, mac
)
2257 except host_thread
.lvirt_module
.libvirtError
as e
:
2258 error_text
= e
.get_error_message()
2259 self
.logger
.error("restore_iface '%s' '%s' libvirt exception: %s", name
, mac
, error_text
)
2262 if lib_conn
is None and conn
is not None:
2264 return ret
, error_text
2267 def create_image(self
,dom
, req
):
2269 if 'path' in req
['action']['createImage']:
2270 file_dst
= req
['action']['createImage']['path']
2272 createImage
=req
['action']['createImage']
2273 img_name
= createImage
['source']['path']
2274 index
=img_name
.rfind('/')
2275 file_dst
= self
.get_notused_path(img_name
[:index
+1] + createImage
['name'] + '.qcow2')
2276 image_status
='ACTIVE'
2280 server_id
= req
['uuid']
2281 createImage
=req
['action']['createImage']
2282 file_orig
= self
.localinfo
['server_files'][server_id
] [ createImage
['source']['image_id'] ] ['source file']
2283 if 'path' in req
['action']['createImage']:
2284 file_dst
= req
['action']['createImage']['path']
2286 img_name
= createImage
['source']['path']
2287 index
=img_name
.rfind('/')
2288 file_dst
= self
.get_notused_filename(img_name
[:index
+1] + createImage
['name'] + '.qcow2')
2290 self
.copy_file(file_orig
, file_dst
)
2291 qemu_info
= self
.qemu_get_info(file_orig
)
2292 if 'backing file' in qemu_info
:
2293 for k
,v
in self
.localinfo
['files'].items():
2294 if v
==qemu_info
['backing file']:
2295 self
.qemu_change_backing(file_dst
, k
)
2297 image_status
='ACTIVE'
2299 except paramiko
.ssh_exception
.SSHException
as e
:
2300 image_status
='ERROR'
2301 error_text
= e
.args
[0]
2302 self
.logger
.error("create_image id='%s' ssh Exception: %s", server_id
, error_text
)
2303 if "SSH session not active" in error_text
and retry
==0:
2305 except Exception as e
:
2306 image_status
='ERROR'
2308 self
.logger
.error("create_image id='%s' Exception: %s", server_id
, error_text
)
2310 #TODO insert a last_error at database
2311 self
.db_lock
.acquire()
2312 self
.db
.update_rows('images', {'status':image_status
, 'progress': 100, 'path':file_dst
},
2313 {'uuid':req
['new_image']['uuid']}, log
=True)
2314 self
.db_lock
.release()
2316 def edit_iface(self
, port_id
, old_net
, new_net
):
2317 #This action imply remove and insert interface to put proper parameters
2322 self
.db_lock
.acquire()
2323 r
,c
= self
.db
.get_table(FROM
='ports as p join resources_port as rp on p.uuid=rp.port_id',
2324 WHERE
={'port_id': port_id
})
2325 self
.db_lock
.release()
2327 self
.logger
.error("edit_iface %s DDBB error: %s", port_id
, c
)
2330 self
.logger
.error("edit_iface %s port not found", port_id
)
2333 if port
["model"]!="VF":
2334 self
.logger
.error("edit_iface %s ERROR model must be VF", port_id
)
2336 #create xml detach file
2339 xml
.append("<interface type='hostdev' managed='yes'>")
2340 xml
.append(" <mac address='" +port
['mac']+ "'/>")
2341 xml
.append(" <source>"+ self
.pci2xml(port
['pci'])+"\n </source>")
2342 xml
.append('</interface>')
2347 conn
= host_thread
.lvirt_module
.open(self
.lvirt_conn_uri
)
2348 dom
= conn
.lookupByUUIDString(port
["instance_id"])
2351 self
.logger
.debug("edit_iface detaching SRIOV interface " + text
)
2352 dom
.detachDeviceFlags(text
, flags
=host_thread
.lvirt_module
.VIR_DOMAIN_AFFECT_LIVE
)
2354 xml
[-1] =" <vlan> <tag id='" + str(port
['vlan']) + "'/> </vlan>"
2356 xml
.append(self
.pci2xml(port
.get('vpci',None)) )
2357 xml
.append('</interface>')
2359 self
.logger
.debug("edit_iface attaching SRIOV interface " + text
)
2360 dom
.attachDeviceFlags(text
, flags
=host_thread
.lvirt_module
.VIR_DOMAIN_AFFECT_LIVE
)
2362 except host_thread
.lvirt_module
.libvirtError
as e
:
2363 text
= e
.get_error_message()
2364 self
.logger
.error("edit_iface %s libvirt exception: %s", port
["instance_id"], text
)
2367 if conn
is not None: conn
.close()
2370 def create_server(server
, db
, db_lock
, only_of_ports
):
2371 extended
= server
.get('extended', None)
2373 requirements
['numa']={'memory':0, 'proc_req_type': 'threads', 'proc_req_nb':0, 'port_list':[], 'sriov_list':[]}
2374 requirements
['ram'] = server
['flavor'].get('ram', 0)
2375 if requirements
['ram']== None:
2376 requirements
['ram'] = 0
2377 requirements
['vcpus'] = server
['flavor'].get('vcpus', 0)
2378 if requirements
['vcpus']== None:
2379 requirements
['vcpus'] = 0
2380 #If extended is not defined get requirements from flavor
2381 if extended
is None:
2382 #If extended is defined in flavor convert to dictionary and use it
2383 if 'extended' in server
['flavor'] and server
['flavor']['extended'] != None:
2384 json_acceptable_string
= server
['flavor']['extended'].replace("'", "\"")
2385 extended
= json
.loads(json_acceptable_string
)
2388 #print json.dumps(extended, indent=4)
2390 #For simplicity only one numa VM are supported in the initial implementation
2391 if extended
!= None:
2392 numas
= extended
.get('numas', [])
2394 return (-2, "Multi-NUMA VMs are not supported yet")
2396 # return (-1, "At least one numa must be specified")
2398 #a for loop is used in order to be ready to multi-NUMA VMs
2402 numa_req
['memory'] = numa
.get('memory', 0)
2404 numa_req
['proc_req_nb'] = numa
['cores'] #number of cores or threads to be reserved
2405 numa_req
['proc_req_type'] = 'cores' #indicates whether cores or threads must be reserved
2406 numa_req
['proc_req_list'] = numa
.get('cores-id', None) #list of ids to be assigned to the cores or threads
2407 elif 'paired-threads' in numa
:
2408 numa_req
['proc_req_nb'] = numa
['paired-threads']
2409 numa_req
['proc_req_type'] = 'paired-threads'
2410 numa_req
['proc_req_list'] = numa
.get('paired-threads-id', None)
2411 elif 'threads' in numa
:
2412 numa_req
['proc_req_nb'] = numa
['threads']
2413 numa_req
['proc_req_type'] = 'threads'
2414 numa_req
['proc_req_list'] = numa
.get('threads-id', None)
2416 numa_req
['proc_req_nb'] = 0 # by default
2417 numa_req
['proc_req_type'] = 'threads'
2421 #Generate a list of sriov and another for physical interfaces
2422 interfaces
= numa
.get('interfaces', [])
2425 for iface
in interfaces
:
2426 iface
['bandwidth'] = int(iface
['bandwidth'])
2427 if iface
['dedicated'][:3]=='yes':
2428 port_list
.append(iface
)
2430 sriov_list
.append(iface
)
2432 #Save lists ordered from more restrictive to less bw requirements
2433 numa_req
['sriov_list'] = sorted(sriov_list
, key
=lambda k
: k
['bandwidth'], reverse
=True)
2434 numa_req
['port_list'] = sorted(port_list
, key
=lambda k
: k
['bandwidth'], reverse
=True)
2437 request
.append(numa_req
)
2439 # print "----------\n"+json.dumps(request[0], indent=4)
2440 # print '----------\n\n'
2442 #Search in db for an appropriate numa for each requested numa
2443 #at the moment multi-NUMA VMs are not supported
2445 requirements
['numa'].update(request
[0])
2446 if requirements
['numa']['memory']>0:
2447 requirements
['ram']=0 #By the moment I make incompatible ask for both Huge and non huge pages memory
2448 elif requirements
['ram']==0:
2449 return (-1, "Memory information not set neither at extended field not at ram")
2450 if requirements
['numa']['proc_req_nb']>0:
2451 requirements
['vcpus']=0 #By the moment I make incompatible ask for both Isolated and non isolated cpus
2452 elif requirements
['vcpus']==0:
2453 return (-1, "Processor information not set neither at extended field not at vcpus")
2455 if 'hypervisor' in server
: requirements
['hypervisor'] = server
['hypervisor'] #Unikernels extension
2458 result
, content
= db
.get_numas(requirements
, server
.get('host_id', None), only_of_ports
)
2462 return (-1, content
)
2464 numa_id
= content
['numa_id']
2465 host_id
= content
['host_id']
2467 #obtain threads_id and calculate pinning
2470 if requirements
['numa']['proc_req_nb']>0:
2472 result
, content
= db
.get_table(FROM
='resources_core',
2473 SELECT
=('id','core_id','thread_id'),
2474 WHERE
={'numa_id':numa_id
,'instance_id': None, 'status':'ok'} )
2480 #convert rows to a dictionary indexed by core_id
2483 if not row
['core_id'] in cores_dict
:
2484 cores_dict
[row
['core_id']] = []
2485 cores_dict
[row
['core_id']].append([row
['thread_id'],row
['id']])
2487 #In case full cores are requested
2489 if requirements
['numa']['proc_req_type'] == 'cores':
2490 #Get/create the list of the vcpu_ids
2491 vcpu_id_list
= requirements
['numa']['proc_req_list']
2492 if vcpu_id_list
== None:
2493 vcpu_id_list
= range(0,int(requirements
['numa']['proc_req_nb']))
2495 for threads
in cores_dict
.itervalues():
2497 if len(threads
) != 2:
2500 #set pinning for the first thread
2501 cpu_pinning
.append( [ vcpu_id_list
.pop(0), threads
[0][0], threads
[0][1] ] )
2503 #reserve so it is not used the second thread
2504 reserved_threads
.append(threads
[1][1])
2506 if len(vcpu_id_list
) == 0:
2509 #In case paired threads are requested
2510 elif requirements
['numa']['proc_req_type'] == 'paired-threads':
2512 #Get/create the list of the vcpu_ids
2513 if requirements
['numa']['proc_req_list'] != None:
2515 for pair
in requirements
['numa']['proc_req_list']:
2517 return -1, "Field paired-threads-id not properly specified"
2519 vcpu_id_list
.append(pair
[0])
2520 vcpu_id_list
.append(pair
[1])
2522 vcpu_id_list
= range(0,2*int(requirements
['numa']['proc_req_nb']))
2524 for threads
in cores_dict
.itervalues():
2526 if len(threads
) != 2:
2528 #set pinning for the first thread
2529 cpu_pinning
.append([vcpu_id_list
.pop(0), threads
[0][0], threads
[0][1]])
2531 #set pinning for the second thread
2532 cpu_pinning
.append([vcpu_id_list
.pop(0), threads
[1][0], threads
[1][1]])
2534 if len(vcpu_id_list
) == 0:
2537 #In case normal threads are requested
2538 elif requirements
['numa']['proc_req_type'] == 'threads':
2539 #Get/create the list of the vcpu_ids
2540 vcpu_id_list
= requirements
['numa']['proc_req_list']
2541 if vcpu_id_list
== None:
2542 vcpu_id_list
= range(0,int(requirements
['numa']['proc_req_nb']))
2544 for threads_index
in sorted(cores_dict
, key
=lambda k
: len(cores_dict
[k
])):
2545 threads
= cores_dict
[threads_index
]
2546 #set pinning for the first thread
2547 cpu_pinning
.append([vcpu_id_list
.pop(0), threads
[0][0], threads
[0][1]])
2549 #if exists, set pinning for the second thread
2550 if len(threads
) == 2 and len(vcpu_id_list
) != 0:
2551 cpu_pinning
.append([vcpu_id_list
.pop(0), threads
[1][0], threads
[1][1]])
2553 if len(vcpu_id_list
) == 0:
2556 #Get the source pci addresses for the selected numa
2557 used_sriov_ports
= []
2558 for port
in requirements
['numa']['sriov_list']:
2560 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} )
2566 if row
['id'] in used_sriov_ports
or row
['id']==port
['port_id']:
2568 port
['pci'] = row
['pci']
2569 if 'mac_address' not in port
:
2570 port
['mac_address'] = row
['mac']
2572 port
['port_id']=row
['id']
2573 port
['Mbps_used'] = port
['bandwidth']
2574 used_sriov_ports
.append(row
['id'])
2577 for port
in requirements
['numa']['port_list']:
2578 port
['Mbps_used'] = None
2579 if port
['dedicated'] != "yes:sriov":
2580 port
['mac_address'] = port
['mac']
2584 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} )
2589 port
['Mbps_used'] = content
[0]['Mbps']
2591 if row
['id'] in used_sriov_ports
or row
['id']==port
['port_id']:
2593 port
['pci'] = row
['pci']
2594 if 'mac_address' not in port
:
2595 port
['mac_address'] = row
['mac'] # mac cannot be set to passthrough ports
2597 port
['port_id']=row
['id']
2598 used_sriov_ports
.append(row
['id'])
2601 # print '2. Physical ports assignation:'+json.dumps(requirements['port_list'], indent=4)
2602 # print '2. SR-IOV assignation:'+json.dumps(requirements['sriov_list'], indent=4)
2604 server
['host_id'] = host_id
2606 #Generate dictionary for saving in db the instance resources
2608 resources
['bridged-ifaces'] = []
2611 numa_dict
['interfaces'] = []
2613 numa_dict
['interfaces'] += requirements
['numa']['port_list']
2614 numa_dict
['interfaces'] += requirements
['numa']['sriov_list']
2616 #Check bridge information
2617 unified_dataplane_iface
=[]
2618 unified_dataplane_iface
+= requirements
['numa']['port_list']
2619 unified_dataplane_iface
+= requirements
['numa']['sriov_list']
2621 for control_iface
in server
.get('networks', []):
2622 control_iface
['net_id']=control_iface
.pop('uuid')
2623 #Get the brifge name
2625 result
, content
= db
.get_table(FROM
='nets',
2626 SELECT
=('name', 'type', 'vlan', 'provider', 'enable_dhcp','dhcp_first_ip',
2627 'dhcp_last_ip', 'cidr', 'gateway_ip', 'dns', 'links', 'routes'),
2628 WHERE
={'uuid': control_iface
['net_id']})
2633 return -1, "Error at field netwoks: Not found any network wit uuid %s" % control_iface
['net_id']
2636 if control_iface
.get("type", 'virtual') == 'virtual':
2637 if network
['type']!='bridge_data' and network
['type']!='bridge_man':
2638 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']
2639 resources
['bridged-ifaces'].append(control_iface
)
2640 if network
.get("provider") and network
["provider"][0:3] == "OVS":
2641 control_iface
["type"] = "instance:ovs"
2643 control_iface
["type"] = "instance:bridge"
2644 if network
.get("vlan"):
2645 control_iface
["vlan"] = network
["vlan"]
2647 if network
.get("enable_dhcp") == 'true':
2648 control_iface
["enable_dhcp"] = network
.get("enable_dhcp")
2649 control_iface
["dhcp_first_ip"] = network
["dhcp_first_ip"]
2650 control_iface
["dhcp_last_ip"] = network
["dhcp_last_ip"]
2651 control_iface
["cidr"] = network
["cidr"]
2653 if network
.get("dns"):
2654 control_iface
["dns"] = yaml
.safe_load(network
.get("dns"))
2655 if network
.get("links"):
2656 control_iface
["links"] = yaml
.safe_load(network
.get("links"))
2657 if network
.get("routes"):
2658 control_iface
["routes"] = yaml
.safe_load(network
.get("routes"))
2660 if network
['type']!='data' and network
['type']!='ptp':
2661 return -1, "Error at field netwoks: network uuid %s for dataplane interface is not of type data or ptp" % control_iface
['net_id']
2662 #dataplane interface, look for it in the numa tree and asign this network
2664 for dataplane_iface
in numa_dict
['interfaces']:
2665 if dataplane_iface
['name'] == control_iface
.get("name"):
2666 if (dataplane_iface
['dedicated'] == "yes" and control_iface
["type"] != "PF") or \
2667 (dataplane_iface
['dedicated'] == "no" and control_iface
["type"] != "VF") or \
2668 (dataplane_iface
['dedicated'] == "yes:sriov" and control_iface
["type"] != "VFnotShared") :
2669 return -1, "Error at field netwoks: mismatch at interface '%s' from flavor 'dedicated=%s' and networks 'type=%s'" % \
2670 (control_iface
.get("name"), dataplane_iface
['dedicated'], control_iface
["type"])
2671 dataplane_iface
['uuid'] = control_iface
['net_id']
2672 if dataplane_iface
['dedicated'] == "no":
2673 dataplane_iface
['vlan'] = network
['vlan']
2674 if dataplane_iface
['dedicated'] != "yes" and control_iface
.get("mac_address"):
2675 dataplane_iface
['mac_address'] = control_iface
.get("mac_address")
2676 if control_iface
.get("vpci"):
2677 dataplane_iface
['vpci'] = control_iface
.get("vpci")
2681 return -1, "Error at field netwoks: interface name %s from network not found at flavor" % control_iface
.get("name")
2683 resources
['host_id'] = host_id
2684 resources
['image_id'] = server
['image_id']
2685 resources
['flavor_id'] = server
['flavor_id']
2686 resources
['tenant_id'] = server
['tenant_id']
2687 resources
['ram'] = requirements
['ram']
2688 resources
['vcpus'] = requirements
['vcpus']
2689 resources
['status'] = 'CREATING'
2691 if 'description' in server
: resources
['description'] = server
['description']
2692 if 'name' in server
: resources
['name'] = server
['name']
2693 if 'hypervisor' in server
: resources
['hypervisor'] = server
['hypervisor']
2694 if 'os_image_type' in server
: resources
['os_image_type'] = server
['os_image_type']
2696 resources
['extended'] = {} #optional
2697 resources
['extended']['numas'] = []
2698 numa_dict
['numa_id'] = numa_id
2699 numa_dict
['memory'] = requirements
['numa']['memory']
2700 numa_dict
['cores'] = []
2702 for core
in cpu_pinning
:
2703 numa_dict
['cores'].append({'id': core
[2], 'vthread': core
[0], 'paired': paired
})
2704 for core
in reserved_threads
:
2705 numa_dict
['cores'].append({'id': core
})
2706 resources
['extended']['numas'].append(numa_dict
)
2707 if extended
!=None and 'devices' in extended
: #TODO allow extra devices without numa
2708 resources
['extended']['devices'] = extended
['devices']
2711 # '===================================={'
2712 #print json.dumps(resources, indent=4)
2713 #print '====================================}'