diff --git a/ansible/ansible.cfg b/ansible/ansible.cfg index 9fa5adccc09..cf0e5cdb3b8 100644 --- a/ansible/ansible.cfg +++ b/ansible/ansible.cfg @@ -170,7 +170,7 @@ fact_caching_timeout = 86400 [privilege_escalation] #become=True -become_method='sudo' +become_method=sudo #become_user='root' #become_ask_pass=False diff --git a/ansible/devutil/conn_graph_helper.py b/ansible/devutil/conn_graph_helper.py index f18d7aac3e2..265a4d15f4b 100644 --- a/ansible/devutil/conn_graph_helper.py +++ b/ansible/devutil/conn_graph_helper.py @@ -1,11 +1,32 @@ import os import inspect import sys -import imp +try: + import importlib.util + import importlib.machinery + use_importlib = True +except ImportError: + import imp + use_importlib = False CONN_GRAPH_LOG = "/tmp/conn_graph_debug.txt" +def load_source(modname, filename): + if use_importlib: + loader = importlib.machinery.SourceFileLoader(modname, filename) + spec = importlib.util.spec_from_file_location(modname, filename, loader=loader) + module = importlib.util.module_from_spec(spec) + # The module is always executed and not cached in sys.modules. + # Uncomment the following line to cache the module. + # sys.modules[module.__name__] = module + loader.exec_module(module) + else: + # For Python 2.x compatibility + module = imp.load_source(modname, filename) + return module + + def get_conn_graph_facts(hostnames): """ @summary: Load conn_graph_facts from conn_graph_facts.xml @@ -18,7 +39,7 @@ def get_conn_graph_facts(hostnames): if ansible_path not in sys.path: sys.path.append(ansible_path) - utils = imp.load_source('conn_graph_utils', os.path.join( + utils = load_source('conn_graph_utils', os.path.join( ansible_path, 'library/conn_graph_facts.py')) utils.LAB_GRAPHFILE_PATH = os.path.join( ansible_path, utils.LAB_GRAPHFILE_PATH) diff --git a/ansible/health_checker.py b/ansible/health_checker.py index d8f4d739b4b..a5f892bd1e2 100755 --- a/ansible/health_checker.py +++ b/ansible/health_checker.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 -import imp import os import logging import sys @@ -13,6 +12,7 @@ except ImportError: # ToDo: Support running without Ansible has_ansible = False +from devutil.conn_graph_helper import load_source ANSIBLE_DIR = os.path.abspath(os.path.dirname(__file__)) SONIC_MGMT_DIR = os.path.dirname(ANSIBLE_DIR) @@ -35,7 +35,7 @@ def get_testbeds_dict(): """Return a dictionary containing mapping from dut hostname to testbed name.""" - testbed = imp.load_source('testbed', os.path.join( + testbed = load_source('testbed', os.path.join( SONIC_MGMT_DIR, 'tests/common/testbed.py')) testbeds_dict = testbed.TestbedInfo(TESTBED_FILE).testbed_topo return testbeds_dict diff --git a/ansible/plugins/connection/multi_passwd_ssh.py b/ansible/plugins/connection/multi_passwd_ssh.py index 9834c0afe1f..73997fd7631 100644 --- a/ansible/plugins/connection/multi_passwd_ssh.py +++ b/ansible/plugins/connection/multi_passwd_ssh.py @@ -1,5 +1,11 @@ import hashlib -import imp +try: + import importlib.util + import importlib.machinery + use_importlib = True +except ImportError: + import imp + use_importlib = False import logging import os @@ -10,9 +16,24 @@ logger = logging.getLogger(__name__) + +def load_source(modname, filename): + loader = importlib.machinery.SourceFileLoader(modname, filename) + spec = importlib.util.spec_from_file_location(modname, filename, loader=loader) + module = importlib.util.module_from_spec(spec) + # The module is always executed and not cached in sys.modules. + # Uncomment the following line to cache the module. + # sys.modules[module.__name__] = module + loader.exec_module(module) + return module + + # HACK: workaround to import the SSH connection plugin _ssh_mod = os.path.join(os.path.dirname(connection.__file__), "ssh.py") -_ssh = imp.load_source("_ssh", _ssh_mod) +if use_importlib: + _ssh = load_source("_ssh", _ssh_mod) +else: + _ssh = imp.load_source("_ssh", _ssh_mod) # Use same options as the builtin Ansible SSH plugin DOCUMENTATION = _ssh.DOCUMENTATION diff --git a/ansible/recover_server.py b/ansible/recover_server.py index 636d8dd88f6..278cb2652b7 100755 --- a/ansible/recover_server.py +++ b/ansible/recover_server.py @@ -10,7 +10,6 @@ import argparse import collections import datetime -import imp import logging import os import subprocess @@ -18,7 +17,7 @@ import tempfile import threading import time - +from devutil.conn_graph_helper import load_source from tabulate import tabulate # Add tests path to syspath sys.path.append('../') @@ -40,7 +39,7 @@ def parse_testbed(testbedfile, testbed_servers): """Return a dictionary containing mapping from server name to testbeds.""" - testbed = imp.load_source('testbed', os.path.join( + testbed = load_source('testbed', os.path.join( SONIC_MGMT_DIR, 'tests/common/testbed.py')) testbeds = {server_name: list() for server_name in testbed_servers} for tbname, tb in testbed.TestbedInfo(testbedfile).testbed_topo.items(): @@ -258,7 +257,7 @@ def _join_all(threads): break time.sleep(5) - utilities = imp.load_source('utilities', os.path.join( + utilities = load_source('utilities', os.path.join( SONIC_MGMT_DIR, 'tests/common/utilities.py')) curr_date = datetime.datetime.today().strftime('%Y-%m-%d_%H-%M-%S') diff --git a/ansible/restart_nightly_ptf.py b/ansible/restart_nightly_ptf.py index c0ca92006a7..909db8090cb 100755 --- a/ansible/restart_nightly_ptf.py +++ b/ansible/restart_nightly_ptf.py @@ -2,7 +2,6 @@ import argparse import logging -import imp import os import recover_server import sys @@ -10,7 +9,7 @@ import datetime import time import tempfile - +from devutil.conn_graph_helper import load_source # Add tests path to syspath sys.path.append('../') @@ -69,7 +68,7 @@ def __call__(self): def parse_testbed(testbedfile, servers): """Return a dictionary containing mapping from server name to nightly testbeds that need restart-ptf.""" - testbed = imp.load_source('testbed', os.path.join( + testbed = load_source('testbed', os.path.join( SONIC_MGMT_DIR, 'tests/common/testbed.py')) all_testbeds = testbed.TestbedInfo(testbedfile).testbed_topo nightly_dir = os.path.join(SONIC_MGMT_DIR, ".azure-pipelines", "nightly") @@ -117,7 +116,7 @@ def _join_all(threads): break time.sleep(5) - utilities = imp.load_source('utilities', os.path.join( + utilities = load_source('utilities', os.path.join( SONIC_MGMT_DIR, 'tests/common/utilities.py')) curr_date = datetime.datetime.today().strftime('%Y-%m-%d_%H-%M-%S') diff --git a/ansible/roles/vm_set/tasks/control_mux_simulator.yml b/ansible/roles/vm_set/tasks/control_mux_simulator.yml index 4e837c6895b..9b034e6c2a5 100644 --- a/ansible/roles/vm_set/tasks/control_mux_simulator.yml +++ b/ansible/roles/vm_set/tasks/control_mux_simulator.yml @@ -17,27 +17,43 @@ werkzeug_version: "1.0.1" python_command: "python" - - name: Use newer Flask version for pip3 + - name: Use newer Flask and Werkzeug version for pip3 set_fact: flask_version: "2.3.3" - python_command: "python3" - when: pip_executable == "pip3" - - - name: Use newer Werkzeug version for pip3 - set_fact: werkzeug_version: "2.3.7" python_command: "python3" when: pip_executable == "pip3" - - name: Install flask - pip: name=flask version={{ flask_version }} state=forcereinstall executable={{ pip_executable }} - become: yes - environment: "{{ proxy_env | default({}) }}" - - - name: Install werkzeug - pip: name=werkzeug version={{ werkzeug_version }} state=forcereinstall executable={{ pip_executable }} - become: yes - environment: "{{ proxy_env | default({}) }}" + - name: Run python3 in a virtualenv + set_fact: + python_command: "/tmp/sonic-mgmt-virtualenv/bin/python3" + when: host_distribution_version.stdout >= "24.04" + + - name: Install Flask and Werkzeug + block: + - name: Install flask + pip: name=flask version={{ flask_version }} state=forcereinstall executable={{ pip_executable }} + become: yes + environment: "{{ proxy_env | default({}) }}" + + - name: Install werkzeug + pip: name=werkzeug version={{ werkzeug_version }} state=forcereinstall executable={{ pip_executable }} + become: yes + environment: "{{ proxy_env | default({}) }}" + when: host_distribution_version.stdout < "24.04" + + - name: Install Flask and Werkzeug + block: + - name: Install flask + pip: name=flask version={{ flask_version }} state=forcereinstall virtualenv=/tmp/sonic-mgmt-virtualenv virtualenv_site_packages=true + become: yes + environment: "{{ proxy_env | default({}) }}" + + - name: Install werkzeug + pip: name=werkzeug version={{ werkzeug_version }} state=forcereinstall virtualenv=/tmp/sonic-mgmt-virtualenv virtualenv_site_packages=true + become: yes + environment: "{{ proxy_env | default({}) }}" + when: host_distribution_version.stdout >= "24.04" - name: Copy the mux simulator to test server copy: diff --git a/ansible/roles/vm_set/tasks/docker.yml b/ansible/roles/vm_set/tasks/docker.yml index 33b91a808ff..f8e0b9c72ae 100644 --- a/ansible/roles/vm_set/tasks/docker.yml +++ b/ansible/roles/vm_set/tasks/docker.yml @@ -1,8 +1,3 @@ -- name: Add docker official GPG key - apt_key: url=https://download.docker.com/linux/ubuntu/gpg state=present - become: yes - environment: "{{ proxy_env | default({}) }}" - - name: Check docker repository find: paths: /etc/apt/sources.list.d/ @@ -54,6 +49,13 @@ become: yes when: host_distribution_version.stdout == "22.04" and docker_repo.matched == 0 +- name: Add docker repository for 24.04 + apt_repository: + repo: deb [arch=amd64] https://download.docker.com/linux/ubuntu noble stable + state: present + become: yes + when: host_distribution_version.stdout == "24.04" and docker_repo.matched == 0 + # In ansible 2.8, there isn't update_cache_retries option in apt module, we can manually run update as a seperate and retryable step - name: Run the "apt-get update" as a separate and retryable step apt: @@ -94,4 +96,23 @@ pip: name=docker version=7.1.0 state=forcereinstall executable={{ pip_executable }} become: yes environment: "{{ proxy_env | default({}) }}" - when: pip_executable=="pip3" + when: pip_executable=="pip3" and host_distribution_version.stdout < "24.04" + +- name: Update python3 packages + block: + - name: Ensure python3-venv is installed + apt: + name: python3-venv + state: present + update_cache: yes + become: yes + - name: Install python packages + pip: name=docker version=7.1.0 state=forcereinstall virtualenv=/tmp/sonic-mgmt-virtualenv virtualenv_site_packages=true virtualenv_command="python3 -m venv" + become: yes + environment: "{{ proxy_env | default({}) }}" + when: host_distribution_version.stdout >= "24.04" + +- name: Use virtualenv for all Python scripts on Ubuntu 24.04 and newer + set_fact: + ansible_python_interpreter: "/tmp/sonic-mgmt-virtualenv/bin/python" + when: host_distribution_version.stdout >= "24.04" diff --git a/ansible/roles/vm_set/tasks/main.yml b/ansible/roles/vm_set/tasks/main.yml index 26ef76b1ffe..8bebc93957f 100644 --- a/ansible/roles/vm_set/tasks/main.yml +++ b/ansible/roles/vm_set/tasks/main.yml @@ -69,27 +69,27 @@ - block: - name: Install necessary packages - apt: pkg={{ item }} update_cache=yes cache_valid_time=86400 - become: yes - with_items: - - ifupdown - - qemu - - openvswitch-switch - - net-tools - - bridge-utils - - util-linux - - - name: Install necessary packages - apt: pkg={{ item }} update_cache=yes cache_valid_time=86400 + apt: + update_cache: yes + cache_valid_time: 86400 + pkg: + - ifupdown + - openvswitch-switch + - net-tools + - bridge-utils + - util-linux + - iproute2 + - vlan + - apt-transport-https + - ca-certificates + - curl + - software-properties-common + - libvirt-clients + register: apt_res + retries: 2 + delay: 30 + until: apt_res is success become: yes - with_items: - - iproute2 - - vlan - - apt-transport-https - - ca-certificates - - curl - - software-properties-common - - libvirt-clients - name: Install necessary packages apt: @@ -109,23 +109,18 @@ - libvirt-daemon-system - qemu-system-x86 become: yes - when: host_distribution_version.stdout == "20.04" or host_distribution_version.stdout == "22.04" + when: host_distribution_version.stdout >= "20.04" when: package_installation|bool - name: Get default pip_executable set_fact: pip_executable: pip - when: pip_executable is not defined and host_distribution_version.stdout != "20.04" and host_distribution_version.stdout != "22.04" + when: pip_executable is not defined and host_distribution_version.stdout < "20.04" - name: Get default pip_executable set_fact: pip_executable: pip3 - when: pip_executable is not defined and (host_distribution_version.stdout == "20.04" or host_distribution_version.stdout == "22.04") - -- name: remove old python packages - pip: name=docker-py state=absent executable={{ pip_executable }} - environment: "{{ proxy_env | default({}) }}" - ignore_errors: yes + when: pip_executable is not defined and (host_distribution_version.stdout >= "20.04") - include_tasks: docker.yml when: package_installation|bool @@ -134,7 +129,13 @@ pip: name=requests version=2.32.3 state=present executable={{ pip_executable }} become: yes environment: "{{ proxy_env | default({}) }}" - when: pip_executable=="pip3" + when: pip_executable=="pip3" and host_distribution_version.stdout < "24.04" + +- name: Install requests package + pip: name=requests version=2.32.3 state=present virtualenv=/tmp/sonic-mgmt-virtualenv virtualenv_site_packages=true virtualenv_command="python3 -m venv" + become: yes + environment: "{{ proxy_env | default({}) }}" + when: host_distribution_version.stdout >= "24.04" - name: Ensure {{ ansible_user }} in docker,sudo group user: @@ -149,7 +150,32 @@ append: yes groups: libvirt become: yes - when: host_distribution_version.stdout == "20.04" or host_distribution_version.stdout == "22.04" + when: host_distribution_version.stdout >= "20.04" + +- name: Update libvirt qemu configuration + block: + - name: Set user to root in qemu.conf + lineinfile: + path: /etc/libvirt/qemu.conf + regexp: '^#?user\s*=.*' + line: 'user = "root"' + state: present + backrefs: yes + become: yes + - name: Set group to root in qemu.conf + lineinfile: + path: /etc/libvirt/qemu.conf + regexp: '^#?group\s*=.*' + line: 'group = "root"' + state: present + backrefs: yes + become: yes + - name: Restart libvirtd to apply qemu.conf changes + service: + name: libvirtd + state: restarted + become: yes + when: host_distribution_version.stdout >= "24.04" - name: Install br_netfilter kernel module become: yes @@ -207,7 +233,7 @@ # root_path is supposed to be absolute path. - set_fact: root_path: "{{ home_path + '/' + root_path }}" - when: "not '{{ root_path }}'.startswith('/')" + when: "not root_path.startswith('/')" - debug: msg="home_path = {{ home_path }} root_path = {{ root_path }}" @@ -366,3 +392,8 @@ loop_control: loop_var: dut_name when: duts_name is defined + +- name: Use virtualenv for all Python scripts on Ubuntu 24.04 and newer + set_fact: + ansible_python_interpreter: "/tmp/sonic-mgmt-virtualenv/bin/python" + when: host_distribution_version.stdout >= "24.04" diff --git a/tests/arp/test_unknown_mac.py b/tests/arp/test_unknown_mac.py index 99d53249265..6ba9c9e8be6 100644 --- a/tests/arp/test_unknown_mac.py +++ b/tests/arp/test_unknown_mac.py @@ -30,7 +30,8 @@ def initClassVars(func): """ Automatically assign instance variables. currently handles only arg list """ - names, varargs, keywords, defaults = inspect.getargspec(func) + signature = inspect.signature(func) + names = list(signature.parameters.keys()) @functools.wraps(func) def wrapper(self, *args): diff --git a/tests/common/cache/facts_cache.py b/tests/common/cache/facts_cache.py index e7008a529d0..9cc602de649 100644 --- a/tests/common/cache/facts_cache.py +++ b/tests/common/cache/facts_cache.py @@ -209,10 +209,8 @@ def _get_default_zone(function, func_args, func_kargs): raise ValueError("Failed to get attribute 'hostname' of type string from instance of type %s." % type(func_args[0])) zone = hostname - if sys.version_info.major > 2: - arg_names = inspect.getfullargspec(function)[0] - else: - arg_names = inspect.getargspec(function)[0] + signature = inspect.signature(function) + arg_names = list(signature.parameters.keys()) if 'namespace' in arg_names: try: index = arg_names.index('namespace') diff --git a/tests/common/connections/base_console_conn.py b/tests/common/connections/base_console_conn.py index c82297262c3..1b14e99d01f 100644 --- a/tests/common/connections/base_console_conn.py +++ b/tests/common/connections/base_console_conn.py @@ -5,7 +5,10 @@ import logging from netmiko.cisco_base_connection import CiscoBaseConnection -from netmiko.ssh_exception import NetMikoAuthenticationException +try: + from netmiko.ssh_exception import NetMikoAuthenticationException +except ImportError: + from netmiko.exceptions import NetMikoAuthenticationException # For interactive shell import sys diff --git a/tests/common/connections/ssh_console_conn.py b/tests/common/connections/ssh_console_conn.py index be53b050d97..760f6a1449f 100644 --- a/tests/common/connections/ssh_console_conn.py +++ b/tests/common/connections/ssh_console_conn.py @@ -1,7 +1,10 @@ import time import re from .base_console_conn import CONSOLE_SSH_DIGI_CONFIG, BaseConsoleConn, CONSOLE_SSH -from netmiko.ssh_exception import NetMikoAuthenticationException +try: + from netmiko.ssh_exception import NetMikoAuthenticationException +except ImportError: + from netmiko.exceptions import NetMikoAuthenticationException from paramiko.ssh_exception import SSHException diff --git a/tests/common/connections/telnet_console_conn.py b/tests/common/connections/telnet_console_conn.py index e522c221bb9..0efbaa545ff 100644 --- a/tests/common/connections/telnet_console_conn.py +++ b/tests/common/connections/telnet_console_conn.py @@ -1,7 +1,10 @@ import time import re from .base_console_conn import BaseConsoleConn -from netmiko.ssh_exception import NetMikoAuthenticationException +try: + from netmiko.ssh_exception import NetMikoAuthenticationException +except ImportError: + from netmiko.exceptions import NetMikoAuthenticationException class TelnetConsoleConn(BaseConsoleConn): diff --git a/tests/common/devices/duthosts.py b/tests/common/devices/duthosts.py index 88db8181c06..0a2ac72e315 100644 --- a/tests/common/devices/duthosts.py +++ b/tests/common/devices/duthosts.py @@ -174,7 +174,7 @@ def __getitem__(self, index): unicode_type = str if sys.version_info.major >= 3 else unicode # noqa F821 if type(index) == int: return self.nodes[index] - elif type(index) in [str, unicode_type]: + elif type(index) in [str, unicode_type] or isinstance(index, str): for node in self.nodes: if node.hostname == index: return node diff --git a/tests/common/helpers/platform_api/scripts/platform_api_server.py b/tests/common/helpers/platform_api/scripts/platform_api_server.py index e9b56c1654b..592848a7a59 100644 --- a/tests/common/helpers/platform_api/scripts/platform_api_server.py +++ b/tests/common/helpers/platform_api/scripts/platform_api_server.py @@ -73,11 +73,8 @@ def do_platform_api(self): while len(path) != 1: _dir = path.pop() - # TODO: Clean this up once we no longer need to support Python 2 - if sys.version_info.major == 3: - args = inspect.getfullargspec(getattr(obj, 'get_' + _dir)).args - else: - args = inspect.getargspec(getattr(obj, 'get_' + _dir)).args + signature = inspect.signature(getattr(obj, 'get_' + _dir)) + args = list(signature.parameters.keys()) if 'index' in args: _idx = int(path.pop()) diff --git a/tests/ssh/conftest.py b/tests/ssh/conftest.py index 8360f149e67..f3d17101543 100644 --- a/tests/ssh/conftest.py +++ b/tests/ssh/conftest.py @@ -1,4 +1,10 @@ -import imp +try: + import importlib.util + import importlib.machinery + use_importlib = True +except ImportError: + import imp + use_importlib = False import subprocess import pytest import logging @@ -25,6 +31,17 @@ ] +def load_source(modname, filename): + loader = importlib.machinery.SourceFileLoader(modname, filename) + spec = importlib.util.spec_from_file_location(modname, filename, loader=loader) + module = importlib.util.module_from_spec(spec) + # The module is always executed and not cached in sys.modules. + # Uncomment the following line to cache the module. + # sys.modules[module.__name__] = module + loader.exec_module(module) + return module + + def generate_ssh_ciphers(request, typename): if typename == "enc": remote_cmd = "ssh -Q cipher" @@ -43,7 +60,10 @@ def generate_ssh_ciphers(request, typename): testbed_name = request.config.option.testbed testbed_file = request.config.option.testbed_file - testbed_module = imp.load_source('testbed', 'common/testbed.py') + if use_importlib: + testbed_module = load_source('testbed', 'common/testbed.py') + else: + testbed_module = imp.load_source('testbed', 'common/testbed.py') tbinfo = testbed_module.TestbedInfo( testbed_file).testbed_topo.get(testbed_name, None)