1 # -*- coding: utf-8 -*-
3 # This file is part of Karesansui.
5 # Copyright (C) 2009-2010 HDE, Inc.
7 # This program is free software; you can redistribute it and/or
8 # modify it under the terms of the GNU General Public License
9 # as published by the Free Software Foundation; either version 2
10 # of the License, or (at your option) any later version.
22 from karesansui.lib.rest import Rest, auth
23 from karesansui.lib.const import ICON_DIR_TPL, \
24 VIRT_COMMAND_CREATE_GUEST, VIRT_COMMAND_REPLICATE_GUEST, \
25 VIRT_COMMAND_DELETE_GUEST, \
26 VNC_PORT_MIN_NUMBER, PORT_MAX_NUMBER, \
27 MEMORY_MIN_SIZE, DISK_MIN_SIZE, \
28 VIRT_DOMAINS_DIR, DEFAULT_KEYMAP
30 from karesansui.lib.const import XEN_KEYMAP_DIR, KVM_KEYMAP_DIR
32 from karesansui.lib.utils import comma_split, \
33 generate_mac_address, is_param, \
34 next_number, generate_uuid, string_from_uuid, uniq_sort, \
35 uni_force, get_partition_info, chk_create_disk, json_dumps, \
36 get_ifconfig_info, get_keymaps, available_virt_mechs, \
37 available_virt_uris, is_iso9660_filesystem_format, \
38 get_dom_list, get_dom_type, base64_encode
40 from karesansui.lib.utils import get_xml_parse as XMLParse
41 from karesansui.lib.utils import get_xml_xpath as XMLXpath
42 from karesansui.lib.utils import get_nums_xml_xpath as XMLXpathNum
43 from karesansui.lib.file.configfile import ConfigFile
45 from karesansui.lib.checker import Checker, \
46 CHECK_EMPTY, CHECK_VALID, CHECK_LENGTH, CHECK_ONLYSPACE, \
47 CHECK_MIN, CHECK_MAX, CHECK_EXIST
49 from karesansui.lib.const import \
50 NOTE_TITLE_MIN_LENGTH, NOTE_TITLE_MAX_LENGTH, \
51 MACHINE_NAME_MIN_LENGTH, MACHINE_NAME_MAX_LENGTH, \
52 TAG_MIN_LENGTH, TAG_MAX_LENGTH, \
53 VNC_PORT_MIN_NUMBER, VNC_PORT_MAX_NUMBER, \
54 HYPERVISOR_MIN_SIZE, HYPERVISOR_MAX_SIZE, \
55 MEMORY_MIN_SIZE, DISK_MIN_SIZE, \
56 DOMAIN_NAME_MIN_LENGTH, DOMAIN_NAME_MAX_LENGTH, \
57 MACHINE_HYPERVISOR, MACHINE_ATTRIBUTE, \
58 DISK_QEMU_FORMAT, DISK_NON_QEMU_FORMAT, \
59 VIRT_COMMAND_CREATE_STORAGE_VOLUME, \
60 VIRT_COMMAND_DELETE_STORAGE_VOLUME, STORAGE_VOLUME_PWD, \
63 from karesansui.lib.virt.virt import KaresansuiVirtConnection
64 from karesansui.lib.virt.config_export import ExportConfigParam
66 from karesansui.lib.merge import MergeGuest
68 from karesansui.db.access.machine import \
69 findbyhost1guestall, findbyhost1, \
71 new as m_new, save as m_save, delete as m_delete
73 from karesansui.db.access.machine2jobgroup import new as m2j_new, save as m2j_save
74 from karesansui.db.access.notebook import new as n_new
75 #from karesansui.db.access.tag import new as t_new, samecount as t_count, findby1name as t_name
76 from karesansui.db.access.tag import new as t_new, samecount as t_count, findby1name as t_name
77 from karesansui.db.access._2pysilhouette import jg_save, jg_delete
78 from karesansui.db.model._2pysilhouette import Job, JobGroup
80 from pysilhouette.command import dict2command
82 def validates_guest_add(obj):
89 if not is_param(obj.input, 'm_name'):
91 checker.add_error(_('Parameter m_name does not exist.'))
93 check = checker.check_string(
96 CHECK_EMPTY | CHECK_LENGTH | CHECK_ONLYSPACE,
98 min = MACHINE_NAME_MIN_LENGTH,
99 max = MACHINE_NAME_MAX_LENGTH,
102 if is_param(obj.input, 'note_title'):
103 check = checker.check_string(
105 obj.input.note_title,
106 CHECK_LENGTH | CHECK_ONLYSPACE,
108 min = NOTE_TITLE_MIN_LENGTH,
109 max = NOTE_TITLE_MAX_LENGTH,
112 if is_param(obj.input, 'note_value'):
113 check = checker.check_string(
115 obj.input.note_value,
122 if is_param(obj.input, 'tags'):
123 for tag in comma_split(obj.input.tags):
124 check = checker.check_string(
127 CHECK_LENGTH | CHECK_ONLYSPACE,
129 min = TAG_MIN_LENGTH,
130 max = TAG_MAX_LENGTH,
134 if not is_param(obj.input, 'm_hypervisor'):
136 checker.add_error(_('Parameter m_hypervisor does not exist.'))
138 check = checker.check_hypervisor(
140 obj.input.m_hypervisor,
141 CHECK_EMPTY | CHECK_VALID | CHECK_MIN | CHECK_MAX,
146 if not is_param(obj.input, 'domain_name'):
148 checker.add_error(_('Parameter domain_name does not exist.'))
150 check = checker.check_string(
152 obj.input.domain_name,
153 CHECK_EMPTY | CHECK_VALID | CHECK_LENGTH | CHECK_ONLYSPACE,
155 DOMAIN_NAME_MIN_LENGTH,
156 DOMAIN_NAME_MAX_LENGTH,
159 if obj.input.domain_name in get_dom_list():
160 dom_type = get_dom_type(obj.input.domain_name)
161 checker.add_error(_("The same domain name already exists for hypervisor '%s'.") % dom_type.upper())
164 if is_param(obj.input, 'vm_mem_size'):
165 check = checker.check_number(
166 _('Memory Size (MB)'),
167 obj.input.vm_mem_size,
168 CHECK_VALID | CHECK_MIN | CHECK_EMPTY,
173 if is_param(obj.input, 'vm_disk_size'):
174 check = checker.check_number(
176 obj.input.vm_disk_size,
177 CHECK_VALID | CHECK_MIN | CHECK_EMPTY,
182 if not is_param(obj.input, 'boot_image'):
184 checker.add_error(_('Parameter boot_image does not exist.'))
186 if obj.input.boot_image == "kernel":
187 if not is_param(obj.input, 'vm_kernel'):
189 checker.add_error(_('Parameter vm_kernel does not exist.'))
191 check = checker.check_startfile(
194 CHECK_EMPTY | CHECK_VALID | CHECK_EXIST,
197 if not is_param(obj.input, 'vm_initrd'):
199 checker.add_error(_('Parameter vm_initrd does not exist.'))
201 check = checker.check_startfile(
204 CHECK_EMPTY | CHECK_VALID | CHECK_EXIST,
207 if obj.input.boot_image == "iso":
208 if not is_param(obj.input, 'vm_iso'):
210 checker.add_error(_('Parameter vm_iso does not exist.'))
212 check = checker.check_startfile(
215 CHECK_EMPTY | CHECK_VALID | CHECK_EXIST,
218 check = is_iso9660_filesystem_format(obj.input.vm_iso)
219 checker.add_error(_('"%s" is not valid ISO 9660 CD-ROM filesystem data.') % obj.input.vm_iso)
221 if not is_param(obj.input, 'keymap'):
223 checker.add_error(_('"%s" is required.') % _('VNC Keymap'))
226 if int(obj.input.m_hypervisor) == MACHINE_HYPERVISOR['XEN']:
228 elif int(obj.input.m_hypervisor) == MACHINE_HYPERVISOR['KVM']:
230 check = checker.check_keymap(
233 CHECK_EMPTY | CHECK_EXIST,
237 if not is_param(obj.input, 'vm_vncport'):
239 checker.add_error(_('Parameter vm_vncport does not exist.'))
241 check = checker.check_number(
242 _('VNC Port Number'),
243 obj.input.vm_vncport,
244 CHECK_EMPTY | CHECK_VALID | CHECK_MIN | CHECK_MAX,
249 if not is_param(obj.input, 'vm_mac'):
251 checker.add_error(_('Parameter vm_mac does not exist.'))
253 check = checker.check_macaddr(
256 CHECK_EMPTY | CHECK_VALID,
259 obj.view.alert = checker.errors
264 def make_storage_volume_job(uuid, name, pool_name, format,
265 capacity, allocation, unit, order):
266 cmdname = u"Create Storage Volume"
267 cmd = VIRT_COMMAND_CREATE_STORAGE_VOLUME
270 options['volume_name'] = uuid
271 options['name'] = name
272 options['pool_name'] = pool_name
273 options['format'] = format
274 options['capacity'] = capacity
275 options['allocation'] = allocation
276 options['unit'] = unit
277 options['permission_owner'] = pwd.getpwnam(STORAGE_VOLUME_PWD["OWNER"])[2]
278 options['permission_group'] = pwd.getpwnam(STORAGE_VOLUME_PWD["GROUP"])[2]
279 options['permission_mode'] = STORAGE_VOLUME_PWD["MODE"]
280 options['use'] = DISK_USES["IMAGES"]
283 "%s/%s" % (karesansui.config['application.bin.dir'], cmd), options)
285 rollback_options = {}
286 #rollback_options["name"] = name
287 rollback_options["name"] = uuid
288 rollback_options["pool_name"] = pool_name
289 rollback_options["use"] = DISK_USES["IMAGES"]
291 rollback_cmd = dict2command(
292 "%s/%s" % (karesansui.config['application.bin.dir'], VIRT_COMMAND_DELETE_STORAGE_VOLUME),
295 _job = Job('%s command' % cmdname, order, _cmd)
296 # delete_guestコマンドが削除まで担当してくれるので、ここではロールバックコマンドを設定しない。
297 #_job.rollback_command = rollback_cmd
300 def regist_guest(obj, _guest, icon_filename,
301 cmd, options, cmdname, rollback_options, is_create=False):
304 _guest.icon = icon_filename
306 if (karesansui.sheconf.has_key('env.uniqkey') is False) \
307 or (karesansui.sheconf['env.uniqkey'].strip('') == ''):
310 action_cmd = dict2command(
311 "%s/%s" % (karesansui.config['application.bin.dir'], cmd),
314 rollback_cmd = dict2command(
315 "%s/%s" % (karesansui.config['application.bin.dir'], VIRT_COMMAND_DELETE_GUEST),
318 _jobgroup = JobGroup(cmdname[0], karesansui.sheconf['env.uniqkey'])
322 if is_create is True:
323 _volume_job = make_storage_volume_job(options["uuid"],
324 options["storage-volume"],
325 options["storage-pool"],
326 options["disk-format"],
327 options["disk-size"],
328 options["disk-size"],
334 _jobgroup.jobs.append(_volume_job)
337 _job = Job('%s command' % cmdname[1], order, action_cmd)
338 _job.rollback_command = rollback_cmd
339 _jobgroup.jobs.append(_job)
343 m_save(obj.orm, _guest)
346 obj.logger.error('Failed to register the Guest OS. #1 - guest name=%s' \
352 jg_save(obj.pysilhouette.orm, _jobgroup)
353 obj.pysilhouette.orm.commit()
356 obj.logger.error('Failed to register the JobGroup. #2 - jobgroup name=%s' \
360 m_delete(obj.orm, _guest)
362 obj.logger.error('#3 Rollback successful. - guest id=%d' % _guest.id)
364 obj.logger.critical('#4 Rollback failed. - guest id=%d' % _guest.id)
369 # Machine2JobGroup INSERT
371 _m2j = m2j_new(machine=_guest,
372 jobgroup_id=_jobgroup.id,
373 uniq_key=karesansui.sheconf['env.uniqkey'],
375 modified_user=obj.me,
377 m2j_save(obj.orm, _m2j)
380 # rollback(machine, jobgroup)
382 m_delete(obj.orm, _guest)
386 obj.logger.critical('Failed to register the Machine. #5 - guest id=%d' \
389 jg_delete(obj.pysilhouette.orm, _jobgroup)
390 obj.pysilhouette.orm.commit()
393 obj.logger.critical('Failed to register the JobGroup. #6 - jobgroup id=%d' \
402 ret = Rest._post(self, f)
403 if hasattr(self, "kvc") is True:
408 def _GET(self, *param, **params):
409 host_id = self.chk_hostby1(param)
410 if host_id is None: return web.notfound()
412 model = findbyhost1(self.orm, host_id)
413 uris = available_virt_uris()
415 self.kvc = KaresansuiVirtConnection()
416 try: # libvirt connection scope -->
418 #inactive_pool = self.kvc.list_inactive_storage_pool()
420 active_pool = self.kvc.list_active_storage_pool()
421 pools = inactive_pool + active_pool
425 return web.badrequest('One can not start a storage pool.')
427 self.view.pools = pools
432 pool_obj = self.kvc.search_kvn_storage_pools(pool)[0]
433 if pool_obj.is_active() is True:
434 vols_obj = pool_obj.search_kvn_storage_volumes(self.kvc)
436 for vol_obj in vols_obj:
437 vol_name = vol_obj.get_storage_volume_name()
438 vols_info[vol_name] = vol_obj.get_info()
440 pools_vols_info[pool] = vols_info
442 pools_info[pool] = pool_obj.get_info()
444 self.view.pools_info = pools_info
445 self.view.pools_vols_info = pools_vols_info
448 if self.is_mode_input() is True:
453 self.view.host_id = host_id
454 self.view.DEFAULT_KEYMAP = DEFAULT_KEYMAP
455 self.view.DISK_NON_QEMU_FORMAT = DISK_NON_QEMU_FORMAT
456 self.view.DISK_QEMU_FORMAT = DISK_QEMU_FORMAT
458 self.view.hypervisors = {}
459 self.view.mac_address = {}
460 self.view.keymaps = {}
461 self.view.phydev = {}
462 self.view.virnet = {}
466 for k,v in MACHINE_HYPERVISOR.iteritems():
467 if k in available_virt_mechs():
468 self.view.hypervisors[k] = v
470 mem_info = self.kvc.get_mem_info()
471 active_networks = self.kvc.list_active_network()
472 used_vnc_ports = self.kvc.list_used_vnc_port()
473 bus_types = self.kvc.bus_types
474 self.view.bus_types = bus_types
475 self.view.min_mem = MEMORY_MIN_SIZE
476 self.view.max_mem = mem_info['host_max_mem']
478 self.view.mac_address[k] = generate_mac_address(k)
479 self.view.keymaps[k] = eval("get_keymaps(%s_KEYMAP_DIR)" % k)
483 phydev_regex = re.compile(r"%s" % bridge_prefix[k])
484 for dev,dev_info in get_ifconfig_info().iteritems():
486 if phydev_regex.match(dev):
491 phydev.append("%s0" % bridge_prefix[k])
493 self.view.phydev[k] = phydev # Physical device
496 self.view.virnet[k] = sorted(active_networks)
497 used_ports[k] = used_vnc_ports
501 for k, _used_port in used_ports.iteritems():
502 exclude_ports = exclude_ports + _used_port
503 exclude_ports = sorted(exclude_ports)
504 exclude_ports = [p for p, q in zip(exclude_ports, exclude_ports[1:] + [None]) if p != q]
505 self.view.vnc_port = next_number(VNC_PORT_MIN_NUMBER,
509 # TODO VIRT_DOMAINS_DIR
510 # TODO max_disk, mix_diskはいらないので削除
511 partition_info = get_partition_info(VIRT_DOMAINS_DIR)
512 disk_info = {"host_max_disk":int(partition_info[1][0]),
513 "guest_alloc_disk":int(partition_info[2][0]),
514 "host_free_disk":int(partition_info[3][0]),
517 self.view.min_disk = DISK_MIN_SIZE
518 self.view.max_disk = disk_info["host_max_disk"]
521 models = findbyhost1guestall(self.orm, host_id)
524 # Physical Guest Info
525 self.view.hypervisors = {}
527 for k,v in MACHINE_HYPERVISOR.iteritems():
528 if k in available_virt_mechs():
529 self.view.hypervisors[k] = v
531 if hasattr(self, "kvc") is not True:
532 self.kvc = KaresansuiVirtConnection(uri)
533 domname = self.kvc.uuid_to_domname(model.uniq_key)
534 #if not domname: return web.conflict(web.ctx.path)
535 _virt = self.kvc.search_kvg_guests(domname)
537 guests.append(MergeGuest(model, _virt[0]))
539 guests.append(MergeGuest(model, None))
541 # Exported Guest Info
543 for pool_name in pools:
546 pool = self.kvc.search_kvn_storage_pools(pool_name)
547 path = pool[0].get_info()["target"]["path"]
549 if os.path.exists(path):
550 for _afile in glob.glob("%s/*/info.dat" % (path,)):
551 param = ExportConfigParam()
552 param.load_xml_config(_afile)
554 _dir = os.path.dirname(_afile)
556 uuid = param.get_uuid()
557 name = param.get_domain()
558 created = param.get_created()
559 title = param.get_title()
561 title = re.sub("[\r\n]","",title)
563 title = _('untitled')
566 created_str = time.strftime("%Y/%m/%d %H:%M:%S", \
567 time.localtime(float(created)))
569 created_str = _("N/A")
571 files.append({"dir": _dir,
573 #"b64dir" : base64_encode(_dir),
576 "created" : int(created),
577 "created_str" : created_str,
579 "icon" : param.get_database()["icon"],
582 exports[pool_name] = files
585 if self.is_json() is True:
588 guests_json.append(x.get_json(self.me.languages))
590 self.view.guests = json_dumps(guests_json)
592 self.view.exports = exports
593 self.view.guests = guests
598 pass # libvirt connection scope --> Guest#_post()
601 def _POST(self, *param, **params):
602 host_id = self.chk_hostby1(param)
603 if host_id is None: return web.notfound()
605 model = findbyhost1(self.orm, host_id)
607 uris = available_virt_uris()
608 if model.attribute == 0 and model.hypervisor == 1:
610 elif model.attribute == 0 and model.hypervisor == 2:
615 if not validates_guest_add(self):
616 return web.badrequest(self.view.alert)
620 self.kvc = KaresansuiVirtConnection(uri)
621 active_guests = self.kvc.list_active_guest()
622 inactive_guests = self.kvc.list_inactive_guest()
623 used_vnc_ports = self.kvc.list_used_vnc_port()
624 used_mac_addrs = self.kvc.list_used_mac_addr()
625 mem_info = self.kvc.get_mem_info()
627 if is_param(self.input, "disk_layout") and \
628 self.input.disk_layout == "create" and \
629 is_param(self.input, "pool_dir"):
630 target_path = self.kvc.get_storage_pool_targetpath(self.input.pool_dir)
631 if target_path: # disk
632 if not chk_create_disk(target_path, self.input.vm_disk_size):
633 partition = get_partition_info(target_path, header=False)
634 return web.badrequest(_("No space available to create disk image in '%s' partition.") % partition[5][0])
640 # Check on whether value has already been used
642 if (self.input.domain_name in active_guests) \
643 or (self.input.domain_name in inactive_guests):
644 return web.conflict(web.ctx.path, "Guest OS is already there.")
646 if(int(self.input.vm_vncport) in used_vnc_ports):
647 return web.conflict(web.ctx.path, "VNC Port is already there.")
649 if(self.input.vm_mac in used_mac_addrs):
650 return web.conflict(web.ctx.path, "MAC Address is already there.")
652 uuid = string_from_uuid(generate_uuid())
655 options['uuid'] = uuid
657 if is_param(self.input, "domain_name"):
658 options['name'] = self.input.domain_name
659 if is_param(self.input, "vm_mem_size"):
660 options['mem-size'] = self.input.vm_mem_size
661 if is_param(self.input, "vm_kernel"):
662 options['kernel'] = self.input.vm_kernel
663 if is_param(self.input, "vm_initrd"):
664 options['initrd'] = self.input.vm_initrd
665 if is_param(self.input, "vm_iso"):
666 options['iso'] = self.input.vm_iso
667 if is_param(self.input, "keymap"):
668 options['keymap'] = self.input.keymap
671 if is_param(self.input, "disk_layout"):
672 if is_param(self.input, "bus_type"):
673 options['bus'] = self.input.bus_type
675 if self.input.disk_layout == "create": # create volume
677 options['disk-format'] = self.input.disk_format
678 options["storage-pool"] = self.input.pool_dir
679 options["storage-volume"] = options['name'] # default domain name
680 options['disk-size'] = self.input.vm_disk_size
682 elif self.input.disk_layout == "iscsi": # iscsi volume TODO
683 if is_param(self.input, "iscsi_pool") and is_param(self.input, "iscsi_volume"):
684 options["storage-pool"] = self.input.iscsi_pool
685 options["storage-volume"] = self.input.iscsi_volume
687 return web.badrequest()
689 return web.badrequest()
691 return web.badrequest()
693 if is_param(self.input, "vm_vncport"):
694 options['vnc-port'] = self.input.vm_vncport
695 if is_param(self.input, "vm_mac"):
696 options['mac'] = self.input.vm_mac
697 if is_param(self.input, "vm_extra"):
698 options['extra'] = self.input.vm_extra
699 if is_param(self.input, "nic_type"):
700 if self.input.nic_type == "phydev":
701 options['interface-format'] = "b:" + self.input.phydev
702 elif self.input.nic_type == "virnet":
703 options['interface-format'] = "n:" + self.input.virnet
705 if int(self.input.m_hypervisor) == MACHINE_HYPERVISOR['XEN']:
706 i_hypervisor = MACHINE_HYPERVISOR['XEN']
707 options['type'] = u"XEN"
708 elif int(self.input.m_hypervisor) == MACHINE_HYPERVISOR['KVM']:
709 i_hypervisor = MACHINE_HYPERVISOR['KVM']
710 options['type'] = u"KVM"
712 return web.badrequest("This is not the hypervisor.")
714 host = findbyhost1(self.orm, host_id)
718 if is_param(self.input, "note_title"):
719 note_title = self.input.note_title
722 if is_param(self.input, "note_value"):
723 note_value = self.input.note_value
725 _notebook = n_new(note_title, note_value)
729 if is_param(self.input, "tags"):
731 tag_array = comma_split(self.input.tags)
732 tag_array = uniq_sort(tag_array)
734 if t_count(self.orm, x) == 0:
735 _tags.append(t_new(x))
737 _tags.append(t_name(self.orm, x))
741 if is_param(self.input, "icon_filename", empty=True):
742 icon_filename = self.input.icon_filename
744 _guest = m_new(created_user=self.me,
745 modified_user=self.me,
746 uniq_key=uni_force(uuid),
747 name=self.input.m_name,
748 attribute=MACHINE_ATTRIBUTE['GUEST'],
749 hypervisor=i_hypervisor,
757 ret = regist_guest(self,
760 VIRT_COMMAND_CREATE_GUEST,
762 ('Create Guest', 'Create Guest'),
763 {"name": options['name'],
764 "pool" : options["storage-pool"],
765 "volume" : options["uuid"],
770 return web.accepted()
775 '/host/(\d+)/guest/?(\.part|\.json)$', Guest,