Juniper / ansible-junos-stdlib

Junos modules for Ansible
Apache License 2.0
304 stars 158 forks source link

[Question] Using a console server (conserve) for day 0 configuration #586

Closed ahussey-redhat closed 9 months ago

ahussey-redhat commented 2 years ago

Issue Type

Module Name

juniper.device.pyez

juniper.device collection and Python libraries version

# ansible --version
ansible [core 2.12.6]
  config file = /home/HUSSDOGG.com/alex/Documents/code/ansible/playbooks/network_bootstrap/ansible.cfg
  configured module search path = ['/home/HUSSDOGG.com/alex/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3.10/site-packages/ansible
  ansible collection location = /home/HUSSDOGG.com/alex/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/bin/ansible
  python version = 3.10.4 (main, Mar 25 2022, 00:00:00) [GCC 12.0.1 20220308 (Red Hat 12.0.1-0)]
  jinja version = 3.0.3
  libyaml = True

# ansible-galaxy collection list
Collection              Version
----------------------- -------
ansible.netcommon       2.6.1  
ansible.posix           1.3.0  
ansible.utils           2.6.0  
community.general       4.0.0  
freeipa.ansible_freeipa 0.4.2  
juniper.device          1.0.1  

# /usr/lib/python3.10/site-packages/ansible_collections
Collection                    Version
----------------------------- -------
ansible.netcommon       2.6.1  
ansible.posix           1.3.0  
ansible.utils           2.6.0  
community.general       5.1.0  
freeipa.ansible_freeipa 0.4.2  
juniper.device          1.0.1  

# pip freeze
ansible==5.8.0
ansible-compat==2.0.3
ansible-core==2.12.6
ansible-lint==6.1.0
argcomplete==2.0.0
asgiref==3.4.1
astroid==2.11.2
attrs==21.4.0
Babel==2.9.1
bcrypt==3.2.2
Beaker==1.10.0
beautifulsoup4==4.11.0
blivet==3.4.4
blivet-gui==2.3.0
borgbackup==1.2.0
bracex==2.2.1
Brlapi==0.8.3
cffi==1.15.0
chardet==4.0.0
charset-normalizer==2.0.11
chrome-gnome-shell==0.0.0
click==8.0.4
commonmark==0.9.1
construct==2.10.68
cryptography==36.0.0
cupshelpers==1.0
dasbus==1.6
dbus-python==1.2.18
decorator==5.1.1
Deprecated==1.2.13
dill==0.3.4
distro==1.6.0
Django==3.2.9
djangorestframework==3.12.4
dnspython==2.2.0
enrich==1.2.7
fedora-third-party==0.10
fros==1.1
gpg==1.15.1
gssapi==1.7.2
humanize==3.13.1
idna==3.3
importlib-metadata==4.10.1
iniconfig==1.1.1
ipaclient==4.9.9
ipalib==4.9.9
ipaplatform==4.9.9
ipapython==4.9.9
isort==5.10.1
jeepney==0.7.1
Jinja2==3.0.3
jmespath==1.0.0
jsonschema==4.5.1
junos-eznc==2.6.3
jwcrypto==0.9.1
jxmlease==1.0.3
keyring==23.5.1
koji==1.29.0
langtable==0.0.58
lazy-object-proxy==1.7.1
libcomps==0.1.18
libvirt-python==8.0.0
llfuse==1.4.1
lorax===devel
lxml==4.7.1
Mako==1.1.4
MarkupSafe==2.0.0
mccabe==0.7.0
msgpack==1.0.3
ncclient==0.6.9
netaddr==0.8.0
netifaces==0.11.0
nftables==0.1
ntlm-auth==1.5.0
olefile==0.46
packaging==21.3
paramiko==2.11.0
Paste==3.5.0
pathspec==0.9.0
pexpect==4.8.0
pid==2.2.3
Pillow==9.1.0
platformdirs==2.5.1
pluggy==1.0.0
ply==3.11
productmd==1.33
progressbar2==3.53.2
psutil==5.8.0
ptyprocess==0.6.0
pwquality==1.4.4
py==1.11.0
pyasn1==0.4.8
pyasn1-modules==0.2.8
pybeam==0.7
pycairo==1.21.0
pycdio==2.1.0
pycdlib==1.12.0
pycparser==2.20
pycrypto==2.6.1
pycups==2.0.1
pycurl==7.45.1
pyenchant==3.2.2
pygit2==1.7.1
Pygments==2.12.0
PyGObject==3.42.1
pykickstart==3.36
pylint==2.13.4
PyNaCl==1.4.0
pyOpenSSL==21.0.0
pyparsing==2.4.7
pyparted==3.12.0
pyrsistent==0.18.1
pyserial==3.5
PySocks==1.7.1
pytest==7.1.2
python-augeas==1.1.0
python-dateutil==2.8.1
python-ldap==3.4.0
python-meh==0.50
python-utils==2.5.6
python-yubico==1.3.3
pytz==2022.1
pyudev==0.22.0
pyusb==1.2.1
pywinrm==0.4.1
pyxdg==0.27
PyYAML==6.0
qrcode==7.3.1
regex==2022.4.24
requests==2.27.1
requests-file==1.5.1
requests-ftp==0.3.1
requests-gssapi==1.2.3
requests-ntlm==1.1.0
resolvelib==0.5.5
rich==12.4.1
rpm==4.17.0
rpmautospec==0.2.6
rpmlint==2.2.0
ruamel.yaml==0.17.21
ruamel.yaml.clib==0.2.6
scp==0.14.4
SecretStorage==3.3.1
selinux==3.3
sepolicy==3.3
setools==4.4.0
setroubleshoot==1.1
simpleaudio==1.0.4
simpleline==1.9.0
six==1.16.0
sos==4.3
soupsieve==2.3.1
sqlparse==0.4.2
SSSDConfig==2.7.0
subprocess-tee==0.3.5
systemd-python==234
Tempita==0.5.2
toml==0.10.2
tomli==2.0.1
transitions==0.8.11
urlgrabber==4.1.0
urllib3==1.26.8
wcmatch==8.3
wrapt==1.14.0
xmltodict==0.12.0
yamllint==1.26.3
yamlordereddictloader==0.4.0
zipp==3.7.0
zstd==1.4.5.1

OS / Environment

Fedora release 36 (Thirty Six) 5.17.13-300.fc36.x86_64

Summary

I am trying to run my playbooks (which definitely work with direct serial access) via a console server. For the purposes of this testing, I'm just using conserver, and have deployed a single console which uses device /dev/ttyUSB0. I can connect perfectly fine using console <console-name>. I'm just a bit confused as to how I specify which console I want to connect to. Do I specify port: "{{ inventory_hostname }}" where inventory_hostname is the same name as the console I want to connect to?

Steps to reproduce

sudo dnf install -y conserver conserver-client
sudo echo < EOF >> /etc/conserver.cf
default full {
    host defaultmaster;
}

config * {
    sslrequired no;
}
console <inventory-hostname-of-device> {
    type device;
    device /dev/ttyUSB0;
    parity none;
    baud 9600;
    idlestring "#";
    idletimeout 5m;     # send a '#' every 5 minutes of idle
    timestamp "";       # no timestamps on this console
        rw *;
        master defaultmaster;
}

access * {
    trusted 127.0.0.1;
}

EOF

sudo echo < EOF >> /etc/conserver.passwd
*any*:*password*
EOF

sudo echo < EOF >> /etc/console.cf
config * {
    master 127.0.0.1;
    sslrequired no; 
}
EOF

sudo echo < EOF >> /etc/systemd/system/conserver.service
[Unit]
Description=Conserver Serial-Port Console Daemon
After=network.target

[Service]
Type=forking
ExecStart=/usr/sbin/conserver -d -i -u -M 127.0.0.1

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now conserver.service

# confirm you have access, and the consoles show up
console -x 

console <console-name>
---
- name: Bootstrap a JUNOS network device
  hosts: qfx
  become: false
  gather_facts: false
  connection: juniper.device.pyez
  vars:
    host: <console-server>
    user: root
    passwd: <junos-root-password>
    mode: serial

  tasks:
    - name: Retrieve current configuration to allow rollback
      juniper.device.config:
        retrieve: committed
        dest_dir: configs/backups
        format: set
     vars:
       port: "{{ inventory_hostname }}"

Expected results

The play runs successfully and I have a dump of the config in the `configs/backups` directory

Actual results

[alex@HUSSDOGG.com@hussws03 network_bootstrap]$ ansible-playbook playbook_configure_qfx.yml -vvv --vault-id @prompt
ansible-playbook [core 2.12.6]
  config file = /home/HUSSDOGG.com/alex/Documents/code/ansible/playbooks/network_bootstrap/ansible.cfg
  configured module search path = ['/home/HUSSDOGG.com/alex/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3.10/site-packages/ansible
  ansible collection location = /home/HUSSDOGG.com/alex/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/bin/ansible-playbook
  python version = 3.10.4 (main, Mar 25 2022, 00:00:00) [GCC 12.0.1 20220308 (Red Hat 12.0.1-0)]
  jinja version = 3.0.3
  libyaml = True
Using /home/HUSSDOGG.com/alex/Documents/code/ansible/playbooks/network_bootstrap/ansible.cfg as config file
Vault password (default): 
host_list declined parsing /home/HUSSDOGG.com/alex/Documents/code/ansible/playbooks/network_bootstrap/inventory.yml as it did not pass its verify_file() method
script declined parsing /home/HUSSDOGG.com/alex/Documents/code/ansible/playbooks/network_bootstrap/inventory.yml as it did not pass its verify_file() method
Parsed /home/HUSSDOGG.com/alex/Documents/code/ansible/playbooks/network_bootstrap/inventory.yml inventory source with yaml plugin
redirecting (type: callback) ansible.builtin.yaml to community.general.yaml
redirecting (type: callback) ansible.builtin.yaml to community.general.yaml
Skipping callback 'default', as we already have a stdout callback.
Skipping callback 'minimal', as we already have a stdout callback.
Skipping callback 'oneline', as we already have a stdout callback.

PLAYBOOK: playbook_configure_qfx.yml *********************************************************************************************************************************************************************************************************
1 plays in playbook_configure_qfx.yml

PLAY [Bootstrap a JUNOS network device] ******************************************************************************************************************************************************************************************************
META: ran handlers

TASK [Retrieve current configuration to allow rollback] **************************************************************************************************************************************************************************************
task path: /home/HUSSDOGG.com/alex/Documents/code/ansible/playbooks/network_bootstrap/playbook_configure_qfx.yml:7
<husssw01.hussdogg.com> ESTABLISH LOCAL CONNECTION FOR USER: alex@HUSSDOGG.com
<husssw01.hussdogg.com> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo /home/HUSSDOGG.com/alex/.ansible/tmp/ansible-local-14825k3p00or2 `"&& mkdir "` echo /home/HUSSDOGG.com/alex/.ansible/tmp/ansible-local-14825k3p00or2/ansible-tmp-1654908710.7810507-14831-143902925021343 `" && echo ansible-tmp-1654908710.7810507-14831-143902925021343="` echo /home/HUSSDOGG.com/alex/.ansible/tmp/ansible-local-14825k3p00or2/ansible-tmp-1654908710.7810507-14831-143902925021343 `" ) && sleep 0'
Using module file /home/HUSSDOGG.com/alex/.ansible/collections/ansible_collections/juniper/device/plugins/modules/config.py
<husssw01.hussdogg.com> PUT /home/HUSSDOGG.com/alex/.ansible/tmp/ansible-local-14825k3p00or2/tmpuxluykmc TO /home/HUSSDOGG.com/alex/.ansible/tmp/ansible-local-14825k3p00or2/ansible-tmp-1654908710.7810507-14831-143902925021343/AnsiballZ_config.py
<husssw01.hussdogg.com> EXEC /bin/sh -c 'chmod u+x /home/HUSSDOGG.com/alex/.ansible/tmp/ansible-local-14825k3p00or2/ansible-tmp-1654908710.7810507-14831-143902925021343/ /home/HUSSDOGG.com/alex/.ansible/tmp/ansible-local-14825k3p00or2/ansible-tmp-1654908710.7810507-14831-143902925021343/AnsiballZ_config.py && sleep 0'
<husssw01.hussdogg.com> EXEC /bin/sh -c '/usr/bin/python3 /home/HUSSDOGG.com/alex/.ansible/tmp/ansible-local-14825k3p00or2/ansible-tmp-1654908710.7810507-14831-143902925021343/AnsiballZ_config.py && sleep 0'
<husssw01.hussdogg.com> EXEC /bin/sh -c 'rm -f -r /home/HUSSDOGG.com/alex/.ansible/tmp/ansible-local-14825k3p00or2/ansible-tmp-1654908710.7810507-14831-143902925021343/ > /dev/null 2>&1 && sleep 0'
The full traceback is:
Traceback (most recent call last):
  File "/home/HUSSDOGG.com/alex/.ansible/tmp/ansible-local-14825k3p00or2/ansible-tmp-1654908710.7810507-14831-143902925021343/AnsiballZ_config.py", line 107, in <module>
    _ansiballz_main()
  File "/home/HUSSDOGG.com/alex/.ansible/tmp/ansible-local-14825k3p00or2/ansible-tmp-1654908710.7810507-14831-143902925021343/AnsiballZ_config.py", line 99, in _ansiballz_main
    invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)
  File "/home/HUSSDOGG.com/alex/.ansible/tmp/ansible-local-14825k3p00or2/ansible-tmp-1654908710.7810507-14831-143902925021343/AnsiballZ_config.py", line 47, in invoke_module
    runpy.run_module(mod_name='ansible_collections.juniper.device.plugins.modules.config', init_globals=dict(_module_fqn='ansible_collections.juniper.device.plugins.modules.config', _modlib_path=modlib_path),
  File "/usr/lib64/python3.10/runpy.py", line 209, in run_module
    return _run_module_code(code, init_globals, run_name, mod_spec)
  File "/usr/lib64/python3.10/runpy.py", line 96, in _run_module_code
    _run_code(code, mod_globals, init_globals,
  File "/usr/lib64/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "/tmp/ansible_juniper.device.config_payload_uv6ossa2/ansible_juniper.device.config_payload.zip/ansible_collections/juniper/device/plugins/modules/config.py", line 1164, in <module>
  File "/tmp/ansible_juniper.device.config_payload_uv6ossa2/ansible_juniper.device.config_payload.zip/ansible_collections/juniper/device/plugins/modules/config.py", line 777, in main
  File "/tmp/ansible_juniper.device.config_payload_uv6ossa2/ansible_juniper.device.config_payload.zip/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py", line 620, in __init__
  File "/tmp/ansible_juniper.device.config_payload_uv6ossa2/ansible_juniper.device.config_payload.zip/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py", line 681, in get_connection
  File "/tmp/ansible_juniper.device.config_payload_uv6ossa2/ansible_juniper.device.config_payload.zip/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py", line 697, in get_capabilities
  File "/tmp/ansible_juniper.device.config_payload_uv6ossa2/ansible_juniper.device.config_payload.zip/ansible/module_utils/connection.py", line 200, in __rpc__
ansible.module_utils.connection.ConnectionError: Unable to make a PyEZ connection: ConnectUnknownHostError(hussws03.idm.hussdogg.com)
fatal: [husssw01.hussdogg.com]: FAILED! => changed=false 
  module_stderr: |-
    Traceback (most recent call last):
      File "/home/HUSSDOGG.com/alex/.ansible/tmp/ansible-local-14825k3p00or2/ansible-tmp-1654908710.7810507-14831-143902925021343/AnsiballZ_config.py", line 107, in <module>
        _ansiballz_main()
      File "/home/HUSSDOGG.com/alex/.ansible/tmp/ansible-local-14825k3p00or2/ansible-tmp-1654908710.7810507-14831-143902925021343/AnsiballZ_config.py", line 99, in _ansiballz_main
        invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)
      File "/home/HUSSDOGG.com/alex/.ansible/tmp/ansible-local-14825k3p00or2/ansible-tmp-1654908710.7810507-14831-143902925021343/AnsiballZ_config.py", line 47, in invoke_module
        runpy.run_module(mod_name='ansible_collections.juniper.device.plugins.modules.config', init_globals=dict(_module_fqn='ansible_collections.juniper.device.plugins.modules.config', _modlib_path=modlib_path),
      File "/usr/lib64/python3.10/runpy.py", line 209, in run_module
        return _run_module_code(code, init_globals, run_name, mod_spec)
      File "/usr/lib64/python3.10/runpy.py", line 96, in _run_module_code
        _run_code(code, mod_globals, init_globals,
      File "/usr/lib64/python3.10/runpy.py", line 86, in _run_code
        exec(code, run_globals)
      File "/tmp/ansible_juniper.device.config_payload_uv6ossa2/ansible_juniper.device.config_payload.zip/ansible_collections/juniper/device/plugins/modules/config.py", line 1164, in <module>
      File "/tmp/ansible_juniper.device.config_payload_uv6ossa2/ansible_juniper.device.config_payload.zip/ansible_collections/juniper/device/plugins/modules/config.py", line 777, in main
      File "/tmp/ansible_juniper.device.config_payload_uv6ossa2/ansible_juniper.device.config_payload.zip/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py", line 620, in __init__
      File "/tmp/ansible_juniper.device.config_payload_uv6ossa2/ansible_juniper.device.config_payload.zip/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py", line 681, in get_connection
      File "/tmp/ansible_juniper.device.config_payload_uv6ossa2/ansible_juniper.device.config_payload.zip/ansible_collections/juniper/device/plugins/module_utils/juniper_junos_common.py", line 697, in get_capabilities
      File "/tmp/ansible_juniper.device.config_payload_uv6ossa2/ansible_juniper.device.config_payload.zip/ansible/module_utils/connection.py", line 200, in __rpc__
    ansible.module_utils.connection.ConnectionError: Unable to make a PyEZ connection: ConnectUnknownHostError(hussws03.idm.hussdogg.com)
  module_stdout: ''
  msg: |-
    MODULE FAILURE
    See stdout/stderr for the exact error
  rc: 1

PLAY RECAP ***********************************************************************************************************************************************************************************************************************************
husssw01.hussdogg.com      : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0  
apurvaraghu commented 11 months ago

Hello,

Please provide further information/references on how to install and use conserver in ansible along with the type of device used in the inventory file.

ahussey-redhat commented 11 months ago

Hello @apurvaraghu, Would you like me to provide an Ansible playbook which installs and configures conserver? With regards to the device used in the inventory file, I'm using SRX320 and QFX3500 via console over USB

conserver is just providing a centralised way to manage the USB/serial access

moophat commented 11 months ago

"I can connect perfectly fine using console ." sound like a really naive way to put it.

From the conserver doc & manpage: " adds logging, and multi-user access for remote administration of serial ports, using locally-installed multi-port serial interfaces, and/or "reverse-telnet" to console servers

The console(1) client program communicates with the master console server process to find the port (and host, in a multi-server configuration) on which the appropriate child is listening.

From the Pyez baseline func tty_serial.py self._ser = serial.Serial()

Sound like when you type console console-name the damn thing use a custom-made tcp-based protocol to for client-server access (while the actual serial device is used by the server). Meanwhile PyEZ will attempt to use the actual serial console device itself, with all the baud rate parameter and other sh!t. Asking PyEZ to connect to this server sound like trying to plug oil pipe into water pipe to me.

ahussey-redhat commented 11 months ago

That makes sense. My objective was to be able to connect to conserver remotely (probably via SSH), to be able to run my Ansible config on my devices from a different host

This could still be achieved without using conserver, and just using delegate_to for the host which has the console/serial connections

ahussey-redhat commented 11 months ago

I was also looking at how this could be used with something like Raritan's serial console (https://www.raritan.com/products/kvm-serial/serial-console-servers) to enable out-of-band management of network devices

I was using conserver as way to see if I could have similar functionality.

Admittedly this issue is fairly old, I actually had to install conserver to refresh myself on how I was using it

apurvaraghu commented 9 months ago

Hello @apurvaraghu, Would you like me to provide an Ansible playbook which installs and configures conserver? With regards to the device used in the inventory file, I'm using SRX320 and QFX3500 via console over USB

conserver is just providing a centralised way to manage the USB/serial access

That would be great. Thanks!

ahussey-redhat commented 9 months ago

Note

This playbook is designed to be run on Fedora/RHEL

playbook.yaml

---
- name: Install and configure conserver
  hosts: localhost
  connection: local
  become: true
  gather_facts: false
  tasks:
   - name: Install the conserver application
     ansible.builtin.dnf:
       name:
        - conserver
        - conserver-client
       state: latest
   - name: Configure conserver application
     ansible.builtin.template:
       src: templates/conserver.cf
       dest: /etc/conserver.cf
   - name: Configure conserver connections
     ansible.builtin.copy:
       src: files/conserver.passwd
       dest: /etc/conserver.passwd
   - name: Configure conserver client console
     ansible.builtin.copy:
       src: files/console.cf
       dest: /etc/console.cf
   - name: Configure conserver systemd service
     ansible.builtin.copy:
       src: files/conserver.service
       dest: /etc/systemd/system/conserver.service
   - name: Ensure conserver service is enable and running
     ansible.builtin.systemd_service:
       name: conserver.service
       enabled: true
       state: started
       daemon_reload: true

templates/conserver.cf

default full {
    host defaultmaster;
}

config * {
    sslrequired no;
}
{% for host in groups.network_devices %}
console {{ hostvars[host]['inventory_hostname'] }} {
    type device;
    device {{ hostvars[host]['port'] }};
    parity none;
    baud 9600;
    idlestring "#";
    idletimeout 5m;     # send a '#' every 5 minutes of idle
    timestamp "";       # no timestamps on this console
        rw *;
        master defaultmaster;
}
{% endfor %}
access * {
    trusted 127.0.0.1;
}

files/conserver.passwd

*any*:*password*

files/conserver.service

[Unit]
Description=Conserver Serial-Port Console Daemon
After=network.target

[Service]
Type=forking
ExecStart=/usr/sbin/conserver -d -i -u -M 127.0.0.1

[Install]
WantedBy=multi-user.target

files/console.cf

config * {
    master 127.0.0.1;
    sslrequired no; 
}

inventory.yaml

---
all:
  children:
    srx:
      hosts:
        fw01_0:
        fw01_1:
    qfx:
      hosts:
        sw01_0:
        sw01_1:
    network_devices:
      children:
        qfx:
        srx:

host_vars

# fw01_0
---
port: /dev/ttyUSB0
# fw01_1
---
port: /dev/ttyUSB1
# sw01_0
---
port: /dev/ttyUSB2
# sw01_1
---
port: /dev/ttyUSB3