
­­­­­­­­­­­­­­­­­­
<!DOCTYPE html>
<html>
3
;j _              .   @   s   d Z ddlZddlZddlZddlZddlZddlmZmZm	Z	m
Z
mZmZmZmZmZ ddlmZ ddlmZmZmZ dZdZdZd	Zd
ZdZdZdZdZdZdZ dZ!G dd de	j"Z#G dd de$Z%dd Z&dd Z'd d! Z(d"d# Z)G d$d% d%e$Z*d&d' Z+d(d) Z,dd+d,Z-d-d. Z.ej/d/d0 Z0d1d2 Z1d3d4 Z2d5d6 Z3d7d8 Z4d9d: Z5d;d< Z6d=d> Z7d?d@ Z8dAdB Z9dCdD Z:dEdF Z;dGdH Z<dIdJ Z=dKdL Z>e*dMe.e*dNe-dOgdPdQe*dRe0e*dSe1e*dTe2e*dUe-dUge*dVe3e*dWe+dXe*dYe+dZe*d[e+d\e*d]e+d^e*d_e+d`e*dae+dbe*dce+dde*dee+dfe*dge+dhe*die,djdke*dle4e*dme,dndoe*dpe5e*dqe6gZ?e&drdsj@ee&dtdte&dudvj@ee&dwdxe'dydze&d{d|e(d}d~e(dde(dde(dde(dde(dde(dde(dde(dde'dde(dde'dee(dde&dde&dde&dde&dde)de7e&dde'dde)de9e&dde&dde)de:e'dde&dde)de;e&ddj@ee'dee'dde)de<e)de=e&dde&dde&dde)de>e&dde&ddg,ZAejBejCddd ZDdddZEdS )aO  Doctor v2 vitals collector (KPT-5984).

Python port of the kcdoctor.sh (1.0-8) vitals collection: gathers the
same data set into a DataPackage-based archive processed by the patch
server generic uploads pipeline.

Scalar and short values are consolidated into a single ``doctor.json``
(the eportal doctor precedent), one field each, instead of a tiny
archive entry per value; bulk data (logs, command dumps, package lists,
sysfs tunables, crash artifacts) stays as separate archive entries. The
1:1 inventory of collected items lives in
docs/features/KPT-5984-doctor-v2/parity-inventory.md.
    N)	authconfigdelivery_kit
http_utilskcare	log_utilsplatform_utilsserveridutils)run_command)	HTTPErrorURLErrorhttplibz2.0-1z/boot/grub2/grub.cfgz/etc/kdump.confz/var/log/dpkg.logz/etc/apt/sources.list*z/etc/yum.repos.d/*z/var/log/libcare/*.logi'  z/sysenable*nr_*max_**cnt* /sys/kernel/debug/tracing/events/sys/kernel/tracing/events/sys/devices
[0-9]*.logkmsg*logc               @   s    e Zd ZdZdZedd ZdS )KcarectlDoctorPackagezkcarectl-doctorz/upload/kcarectl-doctor/c             C   s   t jS )N)r   DOCTOR_REPORT_MAX_SIZE_BYTES)self r   ,/usr/libexec/kcare/python/kcarectl/doctor.pymax_size;   s    zKcarectlDoctorPackage.max_sizeN)__name__
__module____qualname__	data_type
upload_uripropertyr   r   r   r   r   r   7   s   r   c               @   s    e Zd ZdZdd Zdd ZdS )CollectionSpeczA single bulk vitals item ported from kcdoctor.sh.

    Collection failures are recorded in errors.log and never abort the
    report: an unhandled exception inside the package context manager
    would remove the whole archive.
    c             C   s   || _ || _d S )N)arcname_collect)r   r%   collectr   r   r   __init__L   s    zCollectionSpec.__init__c             C   sN   y| j || j W n6 tk
rH } z|jdj| j| W Y d d }~X nX d S )Nzfailed to collect {0}: {1})r&   r%   	Exception	log_errorformat)r   data_packageer   r   r   applyQ   s    zCollectionSpec.applyN)r   r   r    __doc__r(   r.   r   r   r   r   r$   D   s   r$   c                s    fdd}t | |S )z4`run "<cmd>"` kcdoctor.sh helper (no shell features)c                s   | j |  d S )N)
add_stdout)r,   r%   )cmdr   r   r'   ]   s    zrun_spec.<locals>.collect)r$   )r%   r1   r'   r   )r1   r   run_specY   s    r2   c                s    fdd}t | |S )z4`dump "<path>"` kcdoctor.sh helper for regular filesc                s   | j | d d S )N)src_path)add_file)r,   r%   )pathr   r   r'   h   s    zdump_spec.<locals>.collect)r$   )r%   r5   r'   r   )r5   r   	dump_specd   s    r6   c                s    fdd}t | |S )z`dump "<path>"` for /proc and /sys files.

    tar reports them as 0-size so they are first read into memory
    (see the same approach in anomaly.prepare_kernel_anomaly_report).
    c                sL   t jj s | jdj  d S t d}| j||j d W d Q R X d S )Nzfile not found: {0}rb)
data_bytes)osr5   existsr*   r+   openr4   read)r,   r%   f)r5   r   r   r'   w   s
    zproc_dump_spec.<locals>.collect)r$   )r%   r5   r'   r   )r5   r   proc_dump_speco   s    	r>   c             C   s
   t | |S )N)r$   )r%   fnr   r   r   callable_spec   s    r@   c               @   s    e Zd ZdZdd Zdd ZdS )	FieldSpeczA single doctor.json field ported from kcdoctor.sh.

    Collection failures are recorded in errors.log and never abort the
    report; a collector returning None omits the field (e.g. a
    distro-specific source file absent on this system).
    c             C   s   || _ || _d S )N)keyr&   )r   rB   r'   r   r   r   r(      s    zFieldSpec.__init__c             C   sX   y| j  }W n4 tk
r@ } z|jdj| j| d S d }~X nX |d k	rT||| j< d S )Nzfailed to collect {0}: {1})r&   r)   r*   r+   rB   )r   r,   reportvaluer-   r   r   r   r.      s    zFieldSpec.applyN)r   r   r    r/   r(   r.   r   r   r   r   rA      s   rA   c                s    fdd}|S )z`dump "<path>"` as a doctor.json string field.

    Read in binary (non-utf8 safe) and decoded leniently; an absent
    distro-specific source file simply omits the field (None).
    c           
      s:   t jj sd S t d} | j jddj S Q R X d S )Nr7   zutf-8replace)r9   r5   r:   r;   r<   decodestrip)r=   )r5   r   r   r'      s    zread_field.<locals>.collectr   )r5   r'   r   )r5   r   
read_field   s    rH   c                s"   t jtj| fdd}|S )z`grep <pattern> <path>` as a doctor.json list-of-lines field.

    Match on bytes (like collect_apt_sources) so a non-utf8 byte cannot
    lose a line; the matched lines are decoded leniently for the JSON.
    c           
      sD   t jj sd S t d} | j j }W d Q R X fdd|D S )Nr7   c                s"   g | ]} j |r|jd dqS )zutf-8rE   )searchrF   ).0line)regexr   r   
<listcomp>   s    z/grep_field.<locals>.collect.<locals>.<listcomp>)r9   r5   r:   r;   r<   
splitlines)r=   lines)r5   rL   r   r   r'      s
    zgrep_field.<locals>.collect)recompiler
   bstr)r5   patternr'   r   )r5   rL   r   
grep_field   s    
rT   Fc                s    fdd}|S )z@`run "<cmd>"` captured as a doctor.json field instead of a file.c                 s0   t  ddd\} }} |pdj }r,|j S |S )NT)catch_stdoutcatch_stderr )r   rG   rN   )_stdouttext)argvas_linesr   r   r'      s    zcmd_field.<locals>.collectr   )r[   r\   r'   r   )r[   r\   r   	cmd_field   s    r]   c               C   s   t S )N)DOCTOR_VERSIONr   r   r   r   collect_doctor_version   s    r_   c              C   s<   y"t jtjd} tj| j j S  tk
r6   dS X dS )zthe public IP of the machine as seen by the patch server.

    Cached: the main_ip and server_id fields share one HTTP roundtrip
    per run.
    ZmyipZNAN)r   urlopenr
   get_patch_server_urlnstrr<   rG   r)   )responser   r   r   get_main_ip   s
    rd   c              C   s$   t j } | st jdd} tj| S )N.rX   )r	   get_serveridrd   rE   r
   rb   )	server_idr   r   r   collect_server_id   s    rh   c               C   s   t j S )N)r   get_kernel_hashr   r   r   r   collect_kernel_id   s    rj   c              C   sB   i } x8dD ]0}t dd| gddd	\}}}|p0d
j | |< q
W | S )Narmpouname-T)rU   rV   rW   )rk   rl   rm   rn   ro   )r   rG   )resultflagrX   rY   r   r   r   collect_uname   s
    
rt   c           
   C   sH   t jjtsdS ttd} | j jdd}W dQ R X dd |j D S )z7`grep vmlinuz /boot/grub2/grub.cfg | sed 's/root=.*//'`Nr7   zutf-8rE   c             S   s"   g | ]}d |krt jdd|qS )Zvmlinuzzroot=.*rW   )rP   sub)rJ   rK   r   r   r   rM     s    z)collect_grub2_entries.<locals>.<listcomp>)r9   r5   r:   	GRUB2_CFGr;   r<   rF   rN   )r=   rZ   r   r   r   collect_grub2_entries	  s
    rw   c               C   s   t tjtS )N)sortedglobYUM_REPOS_GLOBr   r   r   r   collect_yum_repos  s    r{   c              C   s   dd t jD } | t j dS )z8presence-only port of the kcdoctor.sh detect_cp() probesc             S   s   g | ]\}}| r|qS r   r   )rJ   nameprober   r   r   rM     s    z)collect_control_panel.<locals>.<listcomp>)cpZsoftaculous)r   CONTROL_PANEL_PROBEShas_softaculous)Zpanelsr   r   r   collect_control_panel  s    r   c             C   s@   t ddgddd\}}}| j|tj|p(djdddd	d
 dS )z`ipcs -m | sed -e s/-/=/g`ipcsz-mT)rU   rV   rW   rq   =zutf-8)encoding)r8   N)r   r4   r
   rR   rE   )r,   r%   rX   rY   r   r   r   collect_ipcs%  s    r   c             C   sh   t jjts | jdjt dS ttd}dd |j j D }W dQ R X | j	|dj
|d d dS )z$`grep ' install ' /var/log/dpkg.log`zfile not found: {0}Nr7   c             S   s   g | ]}d |kr|qS )s	    install r   )rJ   rK   r   r   r   rM   4  s    z,collect_dpkg_install_log.<locals>.<listcomp>   
)r8   )r9   r5   r:   DPKG_LOGr*   r+   r;   r<   rN   r4   join)r,   r%   r=   rO   r   r   r   collect_dpkg_install_log,  s     r   c             C   s   t jjdst jjdrd}nXt jjdrrd}yt| d W qv tk
rn } z| jdj| W Y d d }~X qvX nd}| j|| d S )	Nz/usr/bin/rpmz/bin/rpmzFrpm -q -a --queryformat="%{N}|%{V}-%{R}|%{arch}|%{INSTALLTIME:date}\n"z/usr/bin/dpkgzJ/usr/bin/dpkg-query -W -f "${binary:Package}|${Version}|${Architecture}\n"zdpkg.logzfailed to collect dpkg.log: {0}zecho "unknown package manager")r9   r5   r:   r   r)   r*   r+   r0   )r,   r%   packages_cmdr-   r   r   r   collect_packages9  s    $r   c       
         sF  t jdg }xdttjtD ]R}tjj|rfx@tj|D ]&\}}|j	fddt|D  q:W q|j
| qW |s| jdjt dS g }x|D ]}y"t|d}|j }W dQ R X W n6 tk
r }	 z| jdj||	 wW Y dd}	~	X nX tjdj|d	d
 |j	 fdd|j D  qW | j|dj|d d dS )z/`grep -rE '^(deb|URIs)' /etc/apt/sources.list*`s   ^(deb|URIs)c             3   s   | ]}t jj |V  qd S )N)r9   r5   r   )rJ   r|   )rootr   r   	<genexpr>U  s    z&collect_apt_sources.<locals>.<genexpr>zfile not found: {0}Nr7   zfailed to read {0}: {1}z{0}:zutf-8)r   c             3   s    | ]}j |r | V  qd S )N)rI   )rJ   rK   )prefixrL   r   r   r   j  s    r   )r8   )rP   rQ   rx   ry   APT_SOURCES_GLOBr9   r5   isdirwalkextendappendr*   r+   r;   r<   r)   r
   rR   rN   r4   r   )
r,   r%   filesr5   rX   namesrO   r=   contentr-   r   )r   rL   r   r   collect_apt_sourcesL  s*    
"
"r   c       	      C   s   t tjt}|s&| jdjt dS g }x|D ]}y0t|d}|j j t d }W dQ R X W n6 t	k
r } z| jdj|| w0W Y dd}~X nX t
jdj|dd}|j|dj|  q0W | j|dj|d d	 dS )
z%`tail -n10000 /var/log/libcare/*.log`zfile not found: {0}Nr7   zfailed to read {0}: {1}z==> {0} <==
zutf-8)r   r   )r8   )rx   ry   LIBCARE_LOGS_GLOBr*   r+   r;   r<   rN   LOG_TAIL_LINESr)   r
   rR   r   r   r4   )	r,   r%   pathschunksr5   r=   rO   r-   headerr   r   r   collect_libcare_logso  s    
$r   c                s   t jjtjsdS | j|djtj xtt jtjD ]d\}}}xXt|D ]L t	 fddt
D rHt jj| }t jj|tj}| jdj||d qHW q4W dS )z3crashreporter artifacts from /var/cache/kcare/dumpsNz
ls -lR {0}c             3   s   | ]}t j  |V  qd S )N)fnmatch)rJ   rS   )r|   r   r   r     s    z&collect_crash_dumps.<locals>.<genexpr>zcrashreporter/{0})r3   )r9   r5   r   r   
KDUMPS_DIRr0   r+   r   rx   anyCRASH_DUMP_PATTERNSr   relpathr4   )r,   r%   r   rX   r   r5   Zrel_pathr   )r|   r   collect_crash_dumps  s    r   c             C   s   t jjtsd S tj }t jj|s(d S | j|dj| xTt	t
j
t jj|dD ]8}t jj|sfqT| jdjt jj|t jj|dd qTW d S )Nz
ls -lR {0}*zkdump/{0}/vmcore-dmesg.txtzvmcore-dmesg.txt)r3   )r9   r5   r:   
KDUMP_CONFr   get_kdump_rootr   r0   r+   rx   ry   r   r4   basename)r,   r%   
kdump_rootZevtr   r   r   collect_kdump  s    r   c       	         s   g }xt jtD ]\}}}|jtr2g |dd< qxt|D ] t fddtD r<t jj	| }y"t
|d}|j }W dQ R X W n tk
r   d}Y nX |jtjdj|| d  q<W qW | j|dj	|d dS )	zGtunables gathered by the kcdoctor.sh get_sysfs_info() `find /sys` callsNc             3   s   | ]}t j  |V  qd S )N)r   )rJ   rS   )r|   r   r   r     s    z'collect_sysfs_config.<locals>.<genexpr>r7       zcat {0}
r   )r8   )r9   r   
SYSFS_ROOT
startswithSYSFS_EXCLUDE_PREFIXESrx   r   SYSFS_NAME_PATTERNSr5   r   r;   r<   r)   r   r
   rR   r+   r4   )	r,   r%   r   r   dirsr   r5   r=   r   r   )r|   r   collect_sysfs_config  s    

&r   Zdoctor_versionZ	virt_whatz/usr/libexec/kcare/virt-whatT)r\   Zmain_iprg   Z	kernel_iddaterp   Zredhat_releasez/etc/redhat-releaseZdebian_versionz/etc/debian_versionZ
os_releasez/etc/os-releaseZissuez
/etc/issueZsysconfig_kernelz/etc/sysconfig/kernelZproc_uptimez/proc/uptimeZproc_loadavgz/proc/loadavgZproc_cmdlinez/proc/cmdlineproc_versionz/proc/versionZdefault_grubz/etc/default/grubDEFAULTZgrub2_entriesZ	sshd_portz/etc/ssh/sshd_configZPortZyum_repos_dZcontrol_panelsyslogztail -n{0} /var/log/syslogdmesgmessagesztail -n{0} /var/log/messagesls_var_cache_kcarezls -lR /var/cache/kcare/z
kcare.confz/etc/sysconfig/kcare/kcare.confcpuinfozcat /proc/cpuinfoZproc_vmstatz/proc/vmstatZproc_devicesz/proc/devicesZproc_diskstatsz/proc/diskstatsZproc_mdstatz/proc/mdstatZproc_meminfoz/proc/meminfoZ
proc_swapsz/proc/swapsZproc_filesystemsz/proc/filesystemsZproc_mountsz/proc/mountsZproc_interruptsz/proc/interruptsz	grub.confz/boot/grub/grub.confproc_modulesz/proc/modulesz	grub2.cfgZproc_zoneinfoz/proc/zoneinfoZls_boot_configsz:ls /etc/grub.conf /boot/grub/grub.conf /boot/grub/menu.lstZls_bootzls -l /bootZprintenvZ	dmidecoder   sysctlz	sysctl -azsysctl.confz/etc/sysctl.confzpackages.listZlspciz	lspci -vvZdpkg_lzdpkg -lZapt_sourceszyum.confz/etc/yum.confZyum_repolistzyum repolistZlibcare_logszkcarectl.logz tail -n{0} /var/log/kcarectl.logz
kdump.confz
kcare-cronz/etc/cron.d/kcare-cronzcrashreporter/lszkdump/lsZ	aa_statusz	aa-statusZsestatusZkprobes_listz"cat /sys/kernel/debug/kprobes/listZsysfs_configZlsblkzlsblk -fZdfzdf -h)loggerc              C   s\   t  } | H i }xtD ]}|j| | qW | jd| xtD ]}|j|  q<W W d Q R X | S )Nzdoctor.json)r   FIELD_SPECSr.   add_json
FILE_SPECS)r,   rC   fieldspecr   r   r   prepare_doctor_report  s    

r   c             C   s  |s
t jr4|rdnd}tjdj|dd |   dS tj dkrXtjddd |   dS t }|dkrztjd |   dS zzy0|j	 }tjd	j|dd t
jd
j| W nD tttjtjfk
r } ztjdj| |   W Y dd}~X nX W d|j  X dS )a  Collect and upload the doctor report via the generic uploads proxy.

    PUTs the archive to ``/upload/kcarectl-doctor/`` -- the same modern
    pipeline as ``kernel-anomaly`` -- which ePortal proxies transparently
    to the patch server, so no ePortal change is required. Any 2xx reply
    is success, including the ``REDUCED_REPORT`` ``204`` where the proxy
    intentionally drops the report (``send()`` returns without raising, so
    we do not fall back).

    Falls back to the legacy ``kcdoctor.sh`` flow (``fallback``) when:

    * ``force_fallback`` (the ``--doctor --fallback`` CLI flag) or
      ``config.FORCE_DOCTOR_FALLBACK`` is set -- the rollout kill-switch.
      ``--fallback`` is the one-off, per-invocation form; the config knob
      is settable per host in kcare.conf or pushed fleet-wide via the
      ``KC-Flag-Force-Doctor-Fallback`` feature flag. Either forces the
      legacy flow on every host, registered or not, before the new path
      is even considered;
    * the host is not registered, i.e. there is no auth string (OQ-4: the
      proxy upload requires auth, so the new path is not even attempted);
    * report collection failed (``prepare_doctor_report`` returned None);
    * the new route is unavailable on an older patch server / ePortal --
      a 404 / 405 / 403, surfaced as ``HTTPError``, or a connection-level
      failure. ``send()`` drives ``http_utils.upload_file`` (raw
      ``httplib``, not ``urllib``), so transport failures surface as an
      ``OSError`` subclass -- ``socket.timeout``, ``ConnectionRefused``,
      ``socket.gaierror`` (DNS), ``ssl.SSLError`` -- or an
      ``httplib.HTTPException``, *not* ``URLError``; all of these mean the
      new path is unusable. ``upload_file`` raises only on status >= 400,
      so a redirect is not treated as a failure; the proxy and patch
      server never redirect uploads.

    :param fallback: legacy ``kcdoctor.sh`` runner, injected by the caller
        (KPT-6065) to keep this module free of an ``__init__`` import cycle.
    :param force_fallback: when True (``--doctor --fallback``), skip the v2
        path and run ``fallback`` directly; the per-invocation form of
        ``config.FORCE_DOCTOR_FALLBACK``.
    z--fallback requestedzFORCE_DOCTOR_FALLBACK is setz.doctor: {0}; using the legacy kcdoctor.sh flowF)	print_msgNzAdoctor: host is not registered; using the legacy kcdoctor.sh flowzCdoctor: report collection failed; using the legacy kcdoctor.sh flowzdoctor: report uploaded as {0}z,Please provide this filename to support: {0}zJdoctor: report upload unavailable ({0}); using the legacy kcdoctor.sh flow)r   FORCE_DOCTOR_FALLBACKr   loginfor+   r   get_http_auth_stringr   logwarnsendr
   print_wrapperr   r   r   HTTPExceptionsocketerrorremove_archive)fallbackforce_fallbacksrcr,   upload_nameerrr   r   r   send_doctor_report-  s4    )

r   )r   r   r   r   )r   r   r   )r   r   )F)F)Fr/   r   ry   r9   rP   r   kcarectlr   r   r   r   r   r   r   r	   r
   Zkcarectl.process_utilsr   Zkcarectl.py23r   r   r   r^   rv   r   r   r   rz   r   r   r   r   r   r   DataPackager   objectr$   r2   r6   r>   r@   rA   rH   rT   r]   r_   cachedrd   rh   rj   rt   rw   r{   r   r   r   r   r   r   r   r   r   r   r+   r   catch_errorsr   r   r   r   r   r   r   <module>   s   ,  



#