def delete(self, user): adm = libuser.admin() user_obj = adm.lookupUserByName(user) # Check if user exist if user_obj is None: kimchi_log.error('User "%s" does not exist', user) raise OperationFailed('GINUSER0011E', {'user': user}) group_obj = adm.lookupGroupById(int(user_obj.get('pw_gid')[0])) # Delete user with its home and mails too try: adm.deleteUser(user_obj, True, True) except Exception as e: kimchi_log.error('Could not delete user %s: %s', user, e) raise OperationFailed('GINUSER0010E', {'user': user}) # Handle user according to its profile self._delete_profile_settings(user) # Delete group if no users are assigned to it # It is not possible to delete user/group at same time if group_obj is None: msg = 'Group for user "%s" does not exist for removal' % user kimchi_log.warn(msg) raise OperationFailed('GINUSER0013E', {'user': user}) group = group_obj.get('gr_name')[0] if not adm.enumerateUsersByGroup(group): try: adm.deleteGroup(group_obj) except Exception as e: kimchi_log.error('Could not delete group "%s": %s', group, e) raise OperationFailed('GINUSER0012E', {'group': group})
def authenticate(username, password): ldap_server = config.get("authentication", "ldap_server").strip('"') ldap_search_base = config.get("authentication", "ldap_search_base").strip('"') ldap_search_filter = config.get("authentication", "ldap_search_filter", vars={ "username": username.encode("utf-8") }).strip('"') connect = ldap.open(ldap_server) try: result = connect.search_s(ldap_search_base, ldap.SCOPE_SUBTREE, ldap_search_filter) if len(result) == 0: entity = ldap_search_filter % {'username': username} raise ldap.LDAPError("Invalid ldap entity:%s" % entity) connect.bind_s(result[0][0], password) connect.unbind_s() return True except ldap.INVALID_CREDENTIALS: # invalid user password raise OperationFailed("KCHAUTH0002E") except ldap.NO_SUCH_OBJECT: # ldap search base specified wrongly. raise OperationFailed("KCHAUTH0005E", { "item": 'ldap_search_base', "value": ldap_search_base }) except ldap.LDAPError, e: arg = {"username": username, "code": e.message} raise OperationFailed("KCHAUTH0001E", arg)
def update(self, pool, name, params): chunk_data = params['chunk'].fullvalue() chunk_size = int(params['chunk_size']) if len(chunk_data) != chunk_size: raise OperationFailed("KCHVOL0026E") vol = StorageVolumeModel.get_storagevolume(pool, name, self.conn) vol_path = vol.path() vol_capacity = vol.info()[1] vol_data = upload_volumes.get(vol_path) if vol_data is None: raise OperationFailed("KCHVOL0027E", {"vol": vol_path}) cb = vol_data['cb'] lock = vol_data['lock'] with lock: offset = vol_data['offset'] if (offset + chunk_size) > vol_capacity: raise OperationFailed("KCHVOL0028E") cb('%s/%s' % (offset, vol_capacity)) self.doUpload(cb, vol, offset, chunk_data, chunk_size) cb('%s/%s' % (offset + chunk_size, vol_capacity)) vol_data['offset'] += chunk_size if vol_data['offset'] == vol_capacity: del upload_volumes[vol_path] cb('OK', True)
def create(self, vm_name, params): dom = VMModel.get_vm(vm_name, self.conn) if DOM_STATE_MAP[dom.info()[0]] != 'shutoff': raise InvalidOperation('KCHCDROM0011E') # Use device name passed or pick next dev_name = params.get('dev', None) if dev_name is None: params['dev'] = self._get_storage_device_name(vm_name) else: devices = self.get_list(vm_name) if dev_name in devices: raise OperationFailed('KCHCDROM0004E', { 'dev_name': dev_name, 'vm_name': vm_name }) # Path will never be blank due to API.json verification. # There is no need to cover this case here. path = params['path'] params['src_type'] = _check_cdrom_path(path) # Check if path is an url # Add device to VM dev_xml = _get_storage_xml(params) try: conn = self.conn.get() dom = conn.lookupByName(vm_name) dom.attachDeviceFlags(dev_xml, libvirt.VIR_DOMAIN_AFFECT_CURRENT) except Exception as e: raise OperationFailed("KCHCDROM0008E", {'error': e.message}) return params['dev']
def toggleRepo(self, repo_id, enable): repos = self._get_repos('KCHREPOS0011E') if repo_id not in repos.keys(): raise NotFoundError("KCHREPOS0012E", {'repo_id': repo_id}) entry = repos.get(repo_id) if enable and entry.enabled: raise InvalidOperation("KCHREPOS0015E", {'repo_id': repo_id}) if not enable and not entry.enabled: raise InvalidOperation("KCHREPOS0016E", {'repo_id': repo_id}) kimchiLock.acquire() try: if enable: entry.enable() else: entry.disable() write_repo_to_file(entry) except: if enable: raise OperationFailed("KCHREPOS0020E", {'repo_id': repo_id}) raise OperationFailed("KCHREPOS0021E", {'repo_id': repo_id}) finally: kimchiLock.release() return repo_id
def deactivate(self, name): if self._pool_used_by_template(name): raise InvalidOperation('KCHPOOL0034E', {'name': name}) pool = self.get_storagepool(name, self.conn) # FIXME: nfs workaround - do not try to deactivate a NFS pool # if the NFS server is not reachable. xml = pool.XMLDesc(0) pool_type = xpath_get_text(xml, "/pool/@type")[0] if pool_type == 'netfs' and not self._nfs_status_online(pool): # block the user from dactivating the pool. source = self._get_storage_source(pool_type, xml) raise OperationFailed("KCHPOOL0033E", { 'name': name, 'server': source['addr'] }) return try: persistent = pool.isPersistent() pool.destroy() except libvirt.libvirtError as e: raise OperationFailed("KCHPOOL0010E", { 'name': name, 'err': e.get_error_message() }) # If pool was not persistent, then it was erased by destroy() and # must return nothing here, to trigger _redirect() and avoid errors if not persistent: return ""
def _update_lvm_disks(self, pool_name, disks): # check if all the disks/partitions exists in the host for disk in disks: lsblk_cmd = ['lsblk', disk] output, error, returncode = run_command(lsblk_cmd) if returncode != 0: kimchi_log.error( '%s is not a valid disk/partition. Could not ' 'add it to the pool %s.', disk, pool_name) raise OperationFailed('KCHPOOL0027E', { 'disk': disk, 'pool': pool_name }) # add disks to the lvm pool using vgextend + virsh refresh vgextend_cmd = ["vgextend", pool_name] vgextend_cmd += disks output, error, returncode = run_command(vgextend_cmd) if returncode != 0: msg = "Could not add disks to pool %s, error: %s" kimchi_log.error(msg, pool_name, error) raise OperationFailed('KCHPOOL0028E', { 'pool': pool_name, 'err': error }) # refreshing pool state pool = self.get_storagepool(pool_name, self.conn) if pool.isActive(): pool.refresh(0)
def update(self, name, params): if detect_live_vm(): kimchi_log.error('Cannot update system fw while running VMs.') raise OperationFailed('GINFW0001E') fw_path = params['path'] pow_ok = params.get('overwrite-perm-ok', True) # First unpack the rpm to get the fw img file # FIXME: When there's a .deb package available, add support for that command = ['rpm', '-U', '--force', '--ignoreos', fw_path] output, error, rc = run_command(command) if rc: # rpm returns num failed pkgs on failure or neg for unknown raise OperationFailed('GINFW0002E', {'rc': rc, 'err': error}) # The image file should now be in /tmp/fwupdate/ # and match the rpm name. image_file, ext = os.path.splitext(os.path.basename(fw_path)) if image_file is None: kimchi_log.error('FW update failed: ' 'No image file found in the package file.') raise OperationFailed('GINFW0003E') command = [ 'update_flash', '-f', os.path.join('/tmp/fwupdate', '%s.img' % image_file) ] if not pow_ok: command.insert(1, '-n') kimchi_log.info('FW update: System will reboot to flash the firmware.') output, error, rc = run_command(command) if rc: raise OperationFailed('GINFW0004E', {'rc': rc})
def get_disk_ref_cnt(objstore, conn, path): try: with objstore as session: try: ref_cnt = session.get('storagevolume', path)['ref_cnt'] except NotFoundError: kimchi_log.info('Volume %s not found in obj store.' % path) ref_cnt = 0 # try to find this volume in existing vm vms_list = VMsModel.get_vms(conn) for vm in vms_list: dom = VMModel.get_vm(vm, conn) storages = get_vm_disks(dom) for disk in storages.keys(): d_info = get_vm_disk_info(dom, disk) if path == d_info['path']: ref_cnt = ref_cnt + 1 try: session.store('storagevolume', path, {'ref_cnt': ref_cnt}) except Exception as e: # Let the exception be raised. If we allow disks' # ref_cnts to be out of sync, data corruption could # occour if a disk is added to two guests # unknowingly. kimchi_log.error( 'Unable to store storage volume id in' ' objectstore due error: %s', e.message) raise OperationFailed('KCHVOL0017E', {'err': e.message}) except Exception as e: # This exception is going to catch errors returned by 'with', # specially ones generated by 'session.store'. It is outside # to avoid conflict with the __exit__ function of 'with' raise OperationFailed('KCHVOL0017E', {'err': e.message}) return ref_cnt
def delete_user(username): adm = libuser.admin() user_obj = adm.lookupUserByName(username) if user_obj is None: kimchi_log.error('User "%s" does not exist', username) raise OperationFailed('GINUSER0011E', {'user': username}) try: adm.deleteUser(user_obj, True, True) except Exception as e: kimchi_log.error('Could not delete user %s: %s', username, e) raise OperationFailed('GINUSER0010E', {'user': username})
def swupdate(self, *name): try: swupdate = SoftwareUpdate() except: raise OperationFailed('KCHPKGUPD0004E') pkgs = swupdate.getNumOfUpdates() if pkgs == 0: raise OperationFailed('KCHPKGUPD0001E') kimchi_log.debug('Host is going to be updated.') taskid = add_task('', swupdate.doUpdate, self.objstore, None) return self.task.lookup(taskid)
def _ensure_iface_up(self, iface): if netinfo.operstate(iface) != 'up': _, err, rc = run_command(['ip', 'link', 'set', 'dev', iface, 'up']) if rc != 0: raise OperationFailed("KCHNET0020E", {'iface': iface, 'err': err}) # Add a delay to wait for the link change takes into effect. for i in range(10): time.sleep(1) if netinfo.operstate(iface) == 'up': break else: raise OperationFailed("KCHNET0021E", {'iface': iface})
def parse_request(): if 'Content-Length' not in cherrypy.request.headers: return {} rawbody = cherrypy.request.body.read() if mime_in_header('Content-Type', 'application/json'): try: return json.loads(rawbody) except ValueError: e = OperationFailed('KCHAPI0006E') raise cherrypy.HTTPError(400, e.message) else: e = OperationFailed('KCHAPI0007E') raise cherrypy.HTTPError(415, e.message)
def connect(self, name): graphics = self._vm_get_graphics(name) graphics_type, graphics_listen, graphics_port = graphics if graphics_port is not None: vnc.add_proxy_token(name, graphics_port) else: raise OperationFailed("KCHVM0010E", {'name': name})
def delete(self, name): conn = self.conn.get() dom = self.get_vm(name, self.conn) self._vmscreenshot_delete(dom.UUIDString()) paths = self._vm_get_disk_paths(dom) info = self.lookup(name) if info['state'] == 'running': self.poweroff(name) try: dom.undefine() except libvirt.libvirtError as e: raise OperationFailed("KCHVM0021E", { 'name': name, 'err': e.get_error_message() }) for path in paths: vol = conn.storageVolLookupByPath(path) pool = vol.storagePoolLookupByVolume() xml = pool.XMLDesc(0) pool_type = xmlutils.xpath_get_text(xml, "/pool/@type")[0] if pool_type not in READONLY_POOL_TYPE: vol.delete(0) try: with self.objstore as session: session.delete('vm', dom.UUIDString(), ignore_missing=True) except Exception as e: # It is possible to delete vm without delete its database info kimchi_log.error( 'Error deleting vm information from database: ' '%s', e.message) vnc.remove_proxy_token(name)
def _static_vm_update(self, dom, params): state = DOM_STATE_MAP[dom.info()[0]] old_xml = new_xml = dom.XMLDesc(0) for key, val in params.items(): if key in VM_STATIC_UPDATE_PARAMS: if key == 'memory': # Libvirt saves memory in KiB. Retrieved xml has memory # in KiB too, so new valeu must be in KiB here val = val * 1024 if type(val) == int: val = str(val) xpath = VM_STATIC_UPDATE_PARAMS[key] new_xml = xmlutils.xml_item_update(new_xml, xpath, val) conn = self.conn.get() try: if 'name' in params: if state == 'running': msg_args = {'name': dom.name(), 'new_name': params['name']} raise InvalidParameter("KCHVM0003E", msg_args) # Undefine old vm, only if name is going to change dom.undefine() root = ET.fromstring(new_xml) root.remove(root.find('.currentMemory')) dom = conn.defineXML(ET.tostring(root, encoding="utf-8")) except libvirt.libvirtError as e: dom = conn.defineXML(old_xml) raise OperationFailed("KCHVM0008E", { 'name': dom.name(), 'err': e.get_error_message() }) return dom
def create(self, params): conn = self.conn.get() name = params['name'] if name in self.get_list(): raise InvalidOperation("KCHNET0001E", {'name': name}) connection = params["connection"] # set forward mode, isolated do not need forward if connection != 'isolated': params['forward'] = {'mode': connection} # set subnet, bridge network do not need subnet if connection in ["nat", 'isolated']: self._set_network_subnet(params) # only bridge network need bridge(linux bridge) or interface(macvtap) if connection == 'bridge': self._set_network_bridge(params) params['name'] = escape(params['name']) xml = to_network_xml(**params) try: network = conn.networkDefineXML(xml.encode("utf-8")) network.setAutostart(True) except libvirt.libvirtError as e: raise OperationFailed("KCHNET0008E", { 'name': name, 'err': e.get_error_message() }) return name
def _set_network_subnet(self, params): netaddr = params.get('subnet', '') # lookup a free network address for nat and isolated automatically if not netaddr: netaddr = self._get_available_address() if not netaddr: raise OperationFailed("KCHNET0009E", {'name': params['name']}) try: ip = ipaddr.IPNetwork(netaddr) except ValueError: raise InvalidParameter("KCHNET0003E", { 'subent': netaddr, 'network': params['name'] }) if ip.ip == ip.network: ip.ip = ip.ip + 1 dhcp_start = str(ip.ip + ip.numhosts / 2) dhcp_end = str(ip.ip + ip.numhosts - 2) params.update({ 'net': str(ip), 'dhcp': { 'range': { 'start': dhcp_start, 'end': dhcp_end } } })
def addRepo(self, params): """ Add a new APT repository based on <params> """ # To create a APT repository the dist is a required parameter # (in addition to baseurl, verified on controller through API.json) config = params.get('config', None) if config is None: raise MissingParameter("KCHREPOS0019E") if 'dist' not in config.keys(): raise MissingParameter("KCHREPOS0019E") uri = params['baseurl'] dist = config['dist'] comps = config.get('comps', []) validate_repo_url(uri) kimchiLock.acquire() try: repos = self._get_repos() source_entry = repos.add('deb', uri, dist, comps, file=self.filename) with self.pkg_lock(): repos.save() except Exception as e: kimchiLock.release() raise OperationFailed("KCHREPOS0026E", {'err': e.message}) kimchiLock.release() return self._get_repo_id(source_entry)
def _get_repos(self, errcode): try: kimchiLock.acquire() repos = get_yum_repositories() except Exception, e: kimchiLock.release() raise OperationFailed(errcode, {'err': str(e)})
def addRepo(self, params): """ Add a given repository to YumBase """ # At least one base url, or one mirror, must be given. baseurl = params.get('baseurl', '') config = params.get('config', {}) mirrorlist = config.get('mirrorlist', '') metalink = config.get('metalink', '') if not baseurl and not mirrorlist and not metalink: raise MissingParameter("KCHREPOS0013E") if baseurl: validate_repo_url(baseurl) if mirrorlist: validate_repo_url(mirrorlist) if metalink: validate_repo_url(metalink) if mirrorlist and metalink: raise InvalidOperation('KCHREPOS0030E') repo_id = params.get('repo_id', None) if repo_id is None: repo_id = "kimchi_repo_%s" % str(int(time.time() * 1000)) repos = self._get_repos('KCHREPOS0026E') if repo_id in repos.keys(): raise InvalidOperation("KCHREPOS0022E", {'repo_id': repo_id}) repo_name = config.get('repo_name', repo_id) repo = { 'baseurl': baseurl, 'mirrorlist': mirrorlist, 'name': repo_name, 'gpgcheck': 1, 'gpgkey': [], 'enabled': 1, 'metalink': metalink } # write a repo file in the system with repo{} information. parser = ConfigParser() parser.add_section(repo_id) for key, value in repo.iteritems(): if value: parser.set(repo_id, key, value) repofile = os.path.join(self._confdir, repo_id + '.repo') try: with open(repofile, 'w') as fd: parser.write(fd) except: raise OperationFailed("KCHREPOS0018E", {'repo_file': repofile}) return repo_id
def _set_default_gateway(self, gateway): old_route = self._get_default_route_entry() if old_route is None: old_gateway, old_iface = None, None else: old_gateway, old_iface = old_route.gateway, old_route.dev _, err, rc = run_command(['ip', 'route', 'del', 'default']) if rc and not ('No such process' in err): raise OperationFailed('GINNET0010E', {'reason': err}) _, err, rc = run_command( ['ip', 'route', 'add', 'default', 'via', gateway]) if rc: raise OperationFailed('GINNET0011E', {'reason': err}) self._save_gateway_changes(old_iface, old_gateway)
def _gen_debugreport_file(self, name): gen_cmd = self.get_system_report_tool() if gen_cmd is not None: return add_task('', gen_cmd, self.objstore, name) raise OperationFailed("KCHDR0002E")
def reset(self, name): dom = self.get_vm(name, self.conn) try: dom.reset(flags=0) except libvirt.libvirtError as e: raise OperationFailed("KCHVM0022E", {'name': name, 'err': e.get_error_message()})
def _auth(result): def _pam_conv(auth, query_list, userData=None): resp = [] for i in range(len(query_list)): query, qtype = query_list[i] if qtype == PAM.PAM_PROMPT_ECHO_ON: resp.append((username, 0)) elif qtype == PAM.PAM_PROMPT_ECHO_OFF: resp.append((password, 0)) elif qtype == PAM.PAM_PROMPT_ERROR_MSG: cherrypy.log.error_log.error( "PAM authenticate prompt error: %s" % query) resp.append(('', 0)) elif qtype == PAM.PAM_PROMPT_TEXT_INFO: resp.append(('', 0)) else: return None return resp result.value = False auth = PAM.pam() auth.start(service) auth.set_item(PAM.PAM_USER, username) auth.set_item(PAM.PAM_CONV, _pam_conv) try: auth.authenticate() except PAM.error, (resp, code): msg_args = {'username': username, 'code': code} raise OperationFailed("KCHAUTH0001E", msg_args)
def reject(self, name): command = ['update_flash', '-r'] output, error, rc = run_command(command) if rc: raise OperationFailed('GINFW0006E', {'rc': rc}) # update_flash returns a message on success, so log it. kimchi_log.info(output)
def connect(self, name): # (type, listen, port, passwd, passwdValidTo) graphics_port = self._vm_get_graphics(name)[2] if graphics_port is not None: vnc.add_proxy_token(name, graphics_port) else: raise OperationFailed("KCHVM0010E", {'name': name})
def lookup(self, name): try: swupdate = SoftwareUpdate() except Exception: raise OperationFailed('KCHPKGUPD0004E') return swupdate.getUpdate(name)
def __init__(self, args, scan=False): """ Construct a VM Template from a widely variable amount of information. The only required parameter is a name for the VMTemplate. If present, the os_distro and os_version fields are used to lookup recommended settings. Any parameters provided by the caller will override the defaults. If scan is True and a cdrom or a base img is present, the operating system will be detected by probing the installation media. """ self.info = {} self.fc_host_support = args.get('fc_host_support') # Fetch defaults based on the os distro and version try: distro, version = self._get_os_info(args, scan) except ImageFormatError as e: raise OperationFailed('KCHTMPL0020E', {'err': e.message}) os_distro = args.get('os_distro', distro) os_version = args.get('os_version', version) entry = osinfo.lookup(os_distro, os_version) self.info.update(entry) # Auto-generate a template name and no one is passed if 'name' not in args or args['name'] == '': args['name'] = self._gen_name(distro, version) self.name = args['name'] # Override with the passed in parameters graph_args = args.get('graphics') if graph_args: graphics = dict(self.info['graphics']) graphics.update(graph_args) args['graphics'] = graphics self.info.update(args)
def _set_nameservers(self, nameservers): try: with open(RESOLV_CONF, 'w') as f: f.write(''.join('nameserver %s\n' % server for server in nameservers)) except IOError as e: raise OperationFailed('GINNET0002E', {'reason': e.message})