-
Notifications
You must be signed in to change notification settings - Fork 1k
[multi_asic]sanity check changes for multi asic #2622
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,6 +31,14 @@ | |
| # Get show interface counter | ||
| - show_interface: comamnd='counter' interface='Ethernet4' | ||
|
|
||
| # Get show interface status for all internal and external interfaces | ||
| - show_interface command='status' include_internal_intfs=True | ||
|
|
||
| # Get show interface status external interfaces for namespace | ||
| - show_interface command='status' namespace='asic0' | ||
|
|
||
| # Get show interface status for external and interfaces for namespace | ||
| - show_interface command='status' namespace='asic0' include_internal_intfs=True | ||
| ''' | ||
|
|
||
| RETURN = ''' | ||
|
|
@@ -65,9 +73,11 @@ class ShowInterfaceModule(object): | |
| def __init__(self): | ||
| self.module = AnsibleModule( | ||
| argument_spec=dict( | ||
| command=dict(required=True, type='str'), | ||
| interfaces=dict(required=False, type='list', default=None), | ||
| up_ports=dict(type='raw', default={}), | ||
| command=dict(required=True, type='str'), | ||
| namespace=dict(required=False, type='str', default=None), | ||
| interfaces=dict(required=False, type='list', default=None), | ||
| up_ports=dict(type='raw', default={}), | ||
| include_internal_intfs=dict(required=False, type=bool, default=False), | ||
| ), | ||
| supports_check_mode=False) | ||
| self.m_args = self.module.params | ||
|
|
@@ -79,13 +89,18 @@ def run(self): | |
| """ | ||
| Main method of the class | ||
| """ | ||
| if self.m_args['command'] == 'status': self.collect_interface_status() | ||
| if self.m_args['command'] == 'counter': self.collect_interface_counter() | ||
| namespace = self.m_args["namespace"] | ||
| include_internal_intfs = self.m_args['include_internal_intfs'] | ||
| if self.m_args['command'] == 'status': | ||
| self.collect_interface_status(namespace, include_internal_intfs) | ||
| if self.m_args['command'] == 'counter': | ||
| self.collect_interface_counter() | ||
| self.module.exit_json(ansible_facts=self.facts) | ||
|
|
||
| def collect_interface_status(self): | ||
| def collect_interface_status(self, namespace=None, include_internal_intfs=False): | ||
| regex_int_fec = re.compile(r'(\S+)\s+[\d,N\/A]+\s+(\w+)\s+(\d+)\s+(rs|N\/A|none)\s+([\w\/]+)\s+(\w+)\s+(\w+)\s+(\w+)') | ||
| regex_int = re.compile(r'(\S+)\s+[\d,N\/A]+\s+(\w+)\s+(\d+)\s+([\w\/]+)\s+(\w+)\s+(\w+)\s+(\w+)') | ||
| regex_int_internal = re.compile(r'(\S+)\s+[\d,N\/A]+\s+(\w+)\s+(\d+)\s+(rs|N\/A)\s+([\w\-]+)\s+(\w+)\s+(\w+)\s+(\w+)') | ||
| self.int_status = {} | ||
| if self.m_args['interfaces'] is not None: | ||
| for interface in self.m_args['interfaces']: | ||
|
|
@@ -120,11 +135,16 @@ def collect_interface_status(self): | |
| self.module.fail_json(msg="Command failed rc=%d, out=%s, err=%s" % (rc, self.out, err)) | ||
| else: | ||
| try: | ||
| rc, self.out, err = self.module.run_command('show interface status', executable='/bin/bash', use_unsafe_shell=True) | ||
| cli_options = " -n {}".format(namespace) if namespace is not None else "" | ||
| if include_internal_intfs: | ||
| cli_options += " -d all" | ||
| intf_status_cmd = "show interface status{}".format(cli_options) | ||
| rc, self.out, err = self.module.run_command(intf_status_cmd, executable='/bin/bash', use_unsafe_shell=True) | ||
| for line in self.out.split("\n"): | ||
| line = line.strip() | ||
| fec = regex_int_fec.match(line) | ||
| old = regex_int.match(line) | ||
| internal = regex_int_internal.match(line) | ||
| if fec: | ||
| interface = fec.group(1) | ||
| self.int_status[interface] = {} | ||
|
|
@@ -145,6 +165,16 @@ def collect_interface_status(self): | |
| self.int_status[interface]['vlan'] = old.group(5) | ||
| self.int_status[interface]['oper_state'] = old.group(6) | ||
| self.int_status[interface]['admin_state'] = old.group(7) | ||
| elif internal and include_internal_intfs: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a need to check for "include_internal_intfs"? Code is fine either way
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. include_internal_intfs is required to check if the internal ports are up |
||
| interface = internal.group(1) | ||
| self.int_status[interface] = {} | ||
| self.int_status[interface]['name'] = interface | ||
| self.int_status[interface]['speed'] = internal.group(2) | ||
| self.int_status[interface]['fec'] = internal.group(4) | ||
| self.int_status[interface]['alias'] = internal.group(5) | ||
| self.int_status[interface]['vlan'] = internal.group(6) | ||
| self.int_status[interface]['oper_state'] = internal.group(7) | ||
| self.int_status[interface]['admin_state'] = internal.group(8) | ||
| self.facts['int_status'] = self.int_status | ||
| except Exception as e: | ||
| self.module.fail_json(msg=str(e)) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| #!/usr/bin/python | ||
|
|
||
| from ansible.module_utils.basic import * | ||
| import re | ||
|
|
||
| DOCUMENTATION = ''' | ||
| module: show_ip_interface.py | ||
| Short_description: Retrieve show ip interface | ||
| Description: | ||
| - Retrieve IPv4 address of interface and IPv4 address of its neighbor | ||
|
|
||
| options: | ||
| - namespace:: | ||
| Description: In multi ASIC env, namespace to run the command | ||
| Required: False | ||
|
|
||
| ''' | ||
|
|
||
| EXAMPLES = ''' | ||
| # Get show ip interface | ||
| - show_ip_interface: | ||
|
|
||
| # Get show ip interface in namespace asic0 | ||
| - show_ip_interface: namespace='asic0' | ||
|
|
||
| ''' | ||
|
|
||
|
|
||
| class ShowIpInterfaceModule(object): | ||
| def __init__(self): | ||
| self.module = AnsibleModule( | ||
| argument_spec=dict( | ||
| namespace=dict(required=False, type='str', default=None), | ||
| ), | ||
| supports_check_mode=False | ||
| ) | ||
| self.m_args = self.module.params | ||
| self.out = None | ||
| self.facts = {} | ||
| self.ns = "" | ||
| ns = self.m_args["namespace"] | ||
| if ns is not None: | ||
| self.ns = "sudo ip netns exec {} ".format(ns) | ||
|
|
||
| def run(self): | ||
| """ | ||
| Main method of the class | ||
| """ | ||
| regex_int = re.compile( | ||
| "\s*(\S+)\s+" # interface name | ||
| "(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\/(\d{1,2})\s*" # IPv4 | ||
| "(up|down)\/(up|down)\s*" # oper/admin state | ||
| "(\S+)\s*" # neighbor name | ||
| "(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|N\/A)\s*" # peer IPv4 | ||
| ) | ||
|
|
||
| self.ip_int = {} | ||
| try: | ||
| rc, self.out, err = self.module.run_command( | ||
| "{}show ip interfaces".format(self.ns), | ||
| executable='/bin/bash', | ||
| use_unsafe_shell=True | ||
| ) | ||
| for line in self.out.split("\n"): | ||
| line = line.strip() | ||
| m = re.match(regex_int, line) | ||
| if m: | ||
| self.ip_int[m.group(1)] = {} | ||
| self.ip_int[m.group(1)]["ipv4"] = m.group(2) | ||
| self.ip_int[m.group(1)]["prefix_len"] = m.group(3) | ||
| self.ip_int[m.group(1)]["admin"] = m.group(4) | ||
| self.ip_int[m.group(1)]["oper_state"] = m.group(5) | ||
| self.ip_int[m.group(1)]["bgp_neighbor"] = m.group(6) | ||
| self.ip_int[m.group(1)]["peer_ipv4"] = m.group(7) | ||
| self.facts['ip_interfaces'] = self.ip_int | ||
| except Exception as e: | ||
| self.module.fail_json(msg=str(e)) | ||
| if rc != 0: | ||
| self.module.fail_json(msg="Command failed rc = %d, out = %s, err = %s" % (rc, self.out, err)) | ||
|
|
||
| self.module.exit_json(ansible_facts=self.facts) | ||
|
|
||
| def main(): | ||
| ShowIpInt = ShowIpInterfaceModule() | ||
| ShowIpInt.run() | ||
| return | ||
|
|
||
| if __name__ == "__main__": | ||
| main() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -127,7 +127,6 @@ class SonicHost(AnsibleHostBase): | |
| and also provides the ability to run Ansible modules on the SONiC device. | ||
| """ | ||
|
|
||
| _DEFAULT_CRITICAL_SERVICES = ["swss", "syncd", "database", "teamd", "bgp", "pmon", "lldp", "snmp"] | ||
|
|
||
| def __init__(self, ansible_adhoc, hostname, | ||
| shell_user=None, shell_passwd=None): | ||
|
|
@@ -158,8 +157,8 @@ def __init__(self, ansible_adhoc, hostname, | |
|
|
||
| self._facts = self._gather_facts() | ||
| self._os_version = self._get_os_version() | ||
| self.is_multi_asic = True if self.facts["num_asic"] > 1 else False | ||
|
|
||
| self.reset_critical_services_tracking_list() | ||
|
|
||
| @property | ||
| def facts(self): | ||
|
|
@@ -217,20 +216,16 @@ def critical_services(self, var): | |
| This list is used for tracking purposes ONLY. Updating the list does | ||
| not actually modify any services running on the device. | ||
| """ | ||
|
|
||
| if self.facts["num_asic"] > 1: | ||
| self._critical_services = self._generate_critical_services_for_multi_asic(var) | ||
| else: | ||
| self._critical_services = var | ||
| self._critical_services = var | ||
|
|
||
| logging.debug(self._critical_services) | ||
|
|
||
| def reset_critical_services_tracking_list(self): | ||
| def reset_critical_services_tracking_list(self, service_list): | ||
| """ | ||
| Resets the list of critical services to the default. | ||
| Resets the list of critical services. | ||
| """ | ||
|
|
||
| self.critical_services = self._DEFAULT_CRITICAL_SERVICES | ||
| self.critical_services = service_list | ||
|
|
||
| def _gather_facts(self): | ||
| """ | ||
|
|
@@ -270,21 +265,6 @@ def _get_asic_count(self, platform): | |
| def _get_router_mac(self): | ||
| return self.command("sonic-cfggen -d -v 'DEVICE_METADATA.localhost.mac'")["stdout_lines"][0].decode("utf-8") | ||
|
|
||
| def _generate_critical_services_for_multi_asic(self, services): | ||
| """ | ||
| Generates a fully-qualified list of critical services for multi-asic platforms, based on a | ||
| base list of services. | ||
|
|
||
| Example: | ||
| ["swss", "syncd"] -> ["swss0", "swss1", "swss2", "syncd0", "syncd1", "syncd2"] | ||
| """ | ||
|
|
||
| m_service = [] | ||
| for service in services: | ||
| for asic in range(self.facts["num_asic"]): | ||
| asic_service = service + str(asic) | ||
| m_service.insert(asic, asic_service) | ||
| return m_service | ||
|
|
||
| def _get_platform_info(self): | ||
| """ | ||
|
|
@@ -1121,6 +1101,9 @@ def get_route(self, prefix): | |
| cmd = 'show bgp ipv4' if ipaddress.ip_network(unicode(prefix)).version == 4 else 'show bgp ipv6' | ||
| return json.loads(self.shell('vtysh -c "{} {} json"'.format(cmd, prefix))['stdout']) | ||
|
|
||
| def run_redis_cli_cmd(self, redis_cmd): | ||
| cmd = "/usr/bin/redis-cli {}".format(redis_cmd) | ||
| return self.command(cmd) | ||
|
|
||
| class K8sMasterHost(AnsibleHostBase): | ||
| """ | ||
|
|
@@ -1545,6 +1528,8 @@ class SonicAsic(object): | |
| The purpose is to hide the complexity of handling ASIC/namespace specific details. | ||
| For example, passing asic_id, namespace, instance_id etc. to ansible module to deal with namespaces. | ||
| """ | ||
|
|
||
| _DEFAULT_ASIC_SERVICES = ["bgp", "database", "lldp", "swss", "syncd", "teamd"] | ||
| def __init__(self, sonichost, asic_index): | ||
| """ Initializing a ASIC on a SONiC host. | ||
|
|
||
|
|
@@ -1554,7 +1539,26 @@ def __init__(self, sonichost, asic_index): | |
| """ | ||
| self.sonichost = sonichost | ||
| self.asic_index = asic_index | ||
| if self.sonichost.is_multi_asic: | ||
| self.namespace = "{}{}".format(NAMESPACE_PREFIX, self.asic_index) | ||
| else: | ||
| # set the namespace to DEFAULT_NAMESPACE(None) for single asic | ||
| self.namespace = DEFAULT_NAMESPACE | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would making it "None" work better since ansible modules test for "None" namespace.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. DEFAULT_NAMESPACE is None |
||
|
|
||
| def get_critical_services(self): | ||
| """This function returns the list of the critical services | ||
| for the namespace(asic) | ||
|
|
||
| If the dut is multi asic, then the asic_id is appended t0 the | ||
| _DEFAULT_ASIC_SERVICES list | ||
| Returns: | ||
| [list]: list of the services running the namespace/asic | ||
| """ | ||
| a_service = [] | ||
| for service in self._DEFAULT_ASIC_SERVICES: | ||
| a_service.append("{}{}".format( | ||
| service, self.asic_index if self.sonichost.is_multi_asic else "")) | ||
| return a_service | ||
|
|
||
| def bgp_facts(self, *module_args, **complex_args): | ||
| """ Wrapper method for bgp_facts ansible module. | ||
|
|
@@ -1585,10 +1589,43 @@ def config_facts(self, *module_args, **complex_args): | |
| """ | ||
| if 'host' not in complex_args: | ||
| complex_args['host'] = self.sonichost.hostname | ||
| if self.sonichost.facts['num_asic'] != 1: | ||
| complex_args['namespace'] = 'asic{}'.format(self.asic_index) | ||
| if self.sonichost.is_multi_asic: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this check required. Wont "complex_args['namespace'] = DEFAULT_NAMESPACE" work for config_facts? May be config_facts should be updated to handle DEFAULT_NAMESPACE |
||
| complex_args['namespace'] = self.namespace | ||
| return self.sonichost.config_facts(*module_args, **complex_args) | ||
|
|
||
| def show_interface(self, *module_args, **complex_args): | ||
| """Wrapper for the ansible module 'show_interface' | ||
|
|
||
| Args: | ||
| module_args: other ansible module args passed from the caller | ||
| complex_args: other ansible keyword args | ||
|
|
||
| Returns: | ||
| [dict]: [the output of show interface status command] | ||
| """ | ||
| complex_args['namespace'] = self.namespace | ||
| return self.sonichost.show_interface(*module_args, **complex_args) | ||
|
|
||
| def show_ip_interface(self, *module_args, **complex_args): | ||
| """Wrapper for the ansible module 'show_ip_interface' | ||
|
|
||
| Args: | ||
| module_args: other ansible module args passed from the caller | ||
| complex_args: other ansible keyword args | ||
|
|
||
| Returns: | ||
| [dict]: [the output of show interface status command] | ||
| """ | ||
| complex_args['namespace'] = self.namespace | ||
| return self.sonichost.show_ip_interface(*module_args, **complex_args) | ||
|
|
||
| def run_redis_cli_cmd(self, redis_cmd): | ||
| if self.namespace: | ||
|
||
| redis_cli = "/usr/bin/redis-cli" | ||
| cmd = "sudo ip netns exec {} {} {}".format(self.namespace, redis_cli,redis_cmd) | ||
| return self.sonichost.command(cmd) | ||
arlakshm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| # for single asic platforms there are not Namespaces, so the redis-cli command is same the DUT host | ||
| return self.sonichost.run_redis_cli_cmd(redis_cmd) | ||
|
|
||
| class MultiAsicSonicHost(object): | ||
| """ This class represents a Multi-asic SonicHost It has two attributes: | ||
|
|
@@ -1599,6 +1636,8 @@ class MultiAsicSonicHost(object): | |
| So, even a single asic pizza box is represented as a MultiAsicSonicHost with 1 SonicAsic. | ||
| """ | ||
|
|
||
| _DEFAULT_SERVICES = ["pmon", "snmp", "lldp", "database"] | ||
|
|
||
| def __init__(self, ansible_adhoc, hostname): | ||
| """ Initializing a MultiAsicSonicHost. | ||
|
|
||
|
|
@@ -1608,6 +1647,21 @@ def __init__(self, ansible_adhoc, hostname): | |
| """ | ||
| self.sonichost = SonicHost(ansible_adhoc, hostname) | ||
| self.asics = [SonicAsic(self.sonichost, asic_index) for asic_index in range(self.sonichost.facts["num_asic"])] | ||
| self.critical_services_tracking_list() | ||
|
|
||
| def critical_services_tracking_list(self): | ||
| """Get the list of services running on the DUT | ||
| The services on the sonic devices are: | ||
| - services running on the host | ||
| - services which are replicated per asic | ||
| Returns: | ||
| [list]: list of the services running the device | ||
| """ | ||
| service_list = [] | ||
| service_list+= self._DEFAULT_SERVICES | ||
| for asic in self.asics: | ||
| service_list += asic.get_critical_services() | ||
| self.sonichost.reset_critical_services_tracking_list(service_list) | ||
arlakshm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| def _run_on_asics(self, *module_args, **complex_args): | ||
| """ Run an asible module on asics based on 'asic_index' keyword in complex_args | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.