ansible-collections / ansible.windows

Windows core collection for Ansible
https://galaxy.ansible.com/ansible/windows
GNU General Public License v3.0
249 stars 169 forks source link

win_powershell: assigning result from Get-Content to $Ansible.Result results in memory leak #360

Closed Yannik closed 2 years ago

Yannik commented 2 years ago
SUMMARY-

Assigning the result from Get-Content to $Ansible.Result in win_powershell results in memory leak on the target host. The task never ends, and even if cancelled on the controller node, the powershell process runs forever on the target node, resulting in total memory exhaustion.

ISSUE TYPE
COMPONENT NAME

win_powershell

ANSIBLE VERSION
ansible [core 2.11.6] 
  config file = /home/yannik/projects/luetjenburg/ansible/ansible.cfg
  configured module search path = ['/home/yannik/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/yannik/.local/lib/python3.9/site-packages/ansible
  ansible collection location = /home/yannik/projects/luet/ansible/vendor_collections
  executable location = /usr/local/bin/ansible
  python version = 3.9.12 (main, Mar 25 2022, 00:00:00) [GCC 11.2.1 20220127 (Red Hat 11.2.1-9)]
  jinja version = 3.0.3
  libyaml = True
COLLECTION VERSION
# /home/yannik/projects/luetjenburg/ansible/vendor_collections/ansible_collections
Collection        Version
----------------- -------
ansible.netcommon 2.3.0  
ansible.utils     2.3.1  
community.network 3.0.0  
juniper.device    1.0.1  

# /usr/local/lib/python3.9/site-packages/ansible_collections
Collection                    Version
----------------------------- -------
amazon.aws                    2.1.0  
ansible.netcommon             2.5.0  
ansible.posix                 1.3.0  
ansible.utils                 2.4.3  
ansible.windows               1.9.0  
arista.eos                    3.1.0  
awx.awx                       19.4.0 
azure.azcollection            1.11.0 
check_point.mgmt              2.2.2  
chocolatey.chocolatey         1.1.0  
cisco.aci                     2.1.0  
cisco.asa                     2.1.0  
cisco.intersight              1.0.18 
cisco.ios                     2.6.0  
cisco.iosxr                   2.6.0  
cisco.ise                     1.2.1  
cisco.meraki                  2.6.0  
cisco.mso                     1.3.0  
cisco.nso                     1.0.3  
cisco.nxos                    2.8.2  
cisco.ucs                     1.6.0  
cloud.common                  2.1.0  
cloudscale_ch.cloud           2.2.0  
community.aws                 2.2.0  
community.azure               1.1.0  
community.ciscosmb            1.0.4  
community.crypto              2.2.0  
community.digitalocean        1.15.0 
community.dns                 2.0.6  
community.docker              2.1.1  
community.fortios             1.0.0  
community.general             4.4.0  
community.google              1.0.0  
community.grafana             1.3.0  
community.hashi_vault         2.2.0  
community.hrobot              1.2.2  
community.kubernetes          2.0.1  
community.kubevirt            1.0.0  
community.libvirt             1.0.2  
community.mongodb             1.3.2  
community.mysql               2.3.3  
community.network             3.0.0  
community.okd                 2.1.0  
community.postgresql          1.6.1  
community.proxysql            1.3.1  
community.rabbitmq            1.1.0  
community.routeros            2.0.0  
community.skydive             1.0.0  
community.sops                1.2.0  
community.vmware              1.17.1 
community.windows             1.9.0  
community.zabbix              1.5.1  
containers.podman             1.9.1  
cyberark.conjur               1.1.0  
cyberark.pas                  1.0.13 
dellemc.enterprise_sonic      1.1.0  
dellemc.openmanage            4.4.0  
dellemc.os10                  1.1.1  
dellemc.os6                   1.0.7  
dellemc.os9                   1.0.4  
f5networks.f5_modules         1.14.0 
fortinet.fortimanager         2.1.4  
fortinet.fortios              2.1.3  
frr.frr                       1.0.3  
gluster.gluster               1.0.2  
google.cloud                  1.0.2  
hetzner.hcloud                1.6.0  
hpe.nimble                    1.1.4  
ibm.qradar                    1.0.3  
infinidat.infinibox           1.3.3  
infoblox.nios_modules         1.2.1  
inspur.sm                     1.3.0  
junipernetworks.junos         2.8.0  
kubernetes.core               2.2.3  
mellanox.onyx                 1.0.0  
netapp.aws                    21.7.0 
netapp.azure                  21.10.0
netapp.cloudmanager           21.13.0
netapp.elementsw              21.7.0 
netapp.ontap                  21.15.1
netapp.storagegrid            21.9.0 
netapp.um_info                21.8.0 
netapp_eseries.santricity     1.2.13 
netbox.netbox                 3.5.1  
ngine_io.cloudstack           2.2.2  
ngine_io.exoscale             1.0.0  
ngine_io.vultr                1.1.0  
openstack.cloud               1.6.0  
openvswitch.openvswitch       2.1.0  
ovirt.ovirt                   1.6.6  
purestorage.flasharray        1.12.1 
purestorage.flashblade        1.9.0  
sensu.sensu_go                1.13.0 
servicenow.servicenow         1.0.6  
splunk.es                     1.0.2  
t_systems_mms.icinga_director 1.27.0 
theforeman.foreman            2.2.0  
vyos.vyos                     2.6.0  
wti.remote                    1.0.3  
CONFIGURATION
ANSIBLE_NOCOWS(/home/yannik/projects/luet/ansible/ansible.cfg) = True
COLLECTIONS_PATHS(/home/yannik/projects/luet/ansible/ansible.cfg) = ['/home/yannik/projects/luet/ansible/vendor_collections']
DEFAULT_HOST_LIST(/home/yannik/projects/luet/ansible/ansible.cfg) = ['/home/yannik/projects/luet/ansible/hosts']
DEFAULT_LOAD_CALLBACK_PLUGINS(/home/yannik/projects/luet/ansible/ansible.cfg) = True
DEFAULT_ROLES_PATH(/home/yannik/projects/luet/ansible/ansible.cfg) = ['/home/yannik/projects/luet/ansible/vendor_roles']
DEFAULT_STDOUT_CALLBACK(/home/yannik/projects/luet/ansible/ansible.cfg) = yaml
INTERPRETER_PYTHON(/home/yannik/projects/luet/ansible/ansible.cfg) = auto_legacy_silent
OS / ENVIRONMENT

Host os: fedora 35 Target os: Windows 11 Enterprise 21H2, build 22000.675

STEPS TO REPRODUCE
- ansible.windows.win_powershell:
    script: |
      if (Test-Path $env:temp\test.log) {
        Remove-Item $env:temp\test.log
      }
      Add-Content -Path $env:temp\test.log "Test"
      $Ansible.Result = (Get-Content -Path $env:temp\test.log)
    error_action: stop
EXPECTED RESULTS

Task should terminate, no memory leak should occur, and the result is correctly returned to the ansible controller

ACTUAL RESULTS
TASK [mgmt-vm : ansible.windows.win_powershell] *****************************************************************************************************
task path: /home/yannik/projects/luetjenburg/ansible/roles/mgmt-vm/tasks/mgmt-vm.yml:176
Using module file /usr/local/lib/python3.9/site-packages/ansible_collections/ansible/windows/plugins/modules/win_powershell.ps1
Pipelining is enabled.
<mgmt-vm> ESTABLISH WINRM CONNECTION FOR USER: administrator on PORT 5985 TO mgmt-vm
EXEC (via pipeline wrapper)

The task never terminates, and memory usage of the powershell.exe process on the target node is rising very quickly. Terminating the ansible process on the controller node does not result in the process being ended on the target node. Memory usage will rise until exhaustion: Screenshot from 2022-05-23 11-10-48

This is after about 3-5 minutes. Running the task again results in an additional powershell.exe process also quickly ramping up to memory exhaustion.

jborean93 commented 2 years ago

Thanks for the bug report, the trouble here is that Get-Content returns a string for every line but each of those strings contain extended properties. The code currently doesn't inspect those so it's applying the depth restrictions it should be. When Ansible goes to finally serialize that object it comes across the PSProvider property which has recursive nesting causing PowerShell to just eventually run out of memory as it's constantly trying to serialize the data.

I see 2 options available here:

The first will be the simplest but people could be expecting the ETS members to be present whereas the second option acts more like ConvertTo-Json does in PowerShell today. I'll have to think this through and figure out what the best step would be to continue here.

In the meantime as a workaround I recommend you change the Get-Content to [System.IO.File]::ReadAllLines("$env:temp\test.log").

jborean93 commented 2 years ago

I ended up going for option 1 and stripping out the ETS properties for these primitive types. The PR for this is https://github.com/ansible-collections/ansible.windows/pull/373.