Skip to content
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

Add 'alias mode' support for show commands #298

Merged
merged 13 commits into from
Sep 27, 2018

Conversation

paavaanan
Copy link
Contributor

- What I did

Added portalias support for following commands. To enable alias output in CLI execute "sudo config interface_naming_mode alias" command and use the below commands.

  • show ip route
  • show ipv6 route
  • show interface transceiver eeprom
  • show arp
  • show lldp neighbors
  • show vlan brief
  • show vlan config

- How I did it

Implemented a seperate run_command_in_alias_mode function to change the results of linux commands from default interface name to vendor specific name. This function read each line of the output and replace the equivalent vendor names. To speed up the result for mutli-line output ( show ip route/ipv6 route/arp) we cache the alias_names from port_config.ini at init. This results in quick substitution.

- How to verify it
Execute the commands as described above

show/main.py Outdated
@@ -28,6 +29,12 @@ class Config(object):
def __init__(self):
self.path = os.getcwd()
self.aliases = {}
global port_dict
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer not to introduce new global variables if possible. Since you're initializing this in the Config class, why not make it a member of the class? Or better yet, create a new class for name-to-alias translation, and make this a member and interface_name_to_alias() a method of that class.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in the new commit

show/main.py Outdated Show resolved Hide resolved
show/main.py Outdated
for port_name in port_dict.keys():
if interface_name == port_name:
return port_dict[port_name]['alias']
print "Invalid interface {}".format(interface_name)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use click.echo() instead of print for consistency.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in the new commit

show/main.py Outdated
if interface_name != "":
output = output.replace(interface_name,
interface_name_to_alias(interface_name))
print output.rstrip('\n')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use click.echo() instead of print for consistency.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in the new commit

show/main.py Outdated


def run_command_in_alias_mode(command, linux_command=False):
"""Replace command output results from default
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clarify docstring. Suggest: "Run command and replace all instances of SONiC interface names in output with vendor-specific interface aliases."

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in the new commit

show/main.py Outdated
break
if output:
if linux_command:
result = re.findall('Ethernet\w+', output)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assumes all SONiC interfaces names begin with "Ethernet". This is not necessarily true. It is also possible that vendor aliases begin with "Ethernet", so you will try to convert those, as well. This should instead try to find instances of all SONiC interface names configured for the current device.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in the new commit

show/main.py Outdated Show resolved Hide resolved
show/main.py Outdated
@@ -955,7 +1034,11 @@ def tablelize(keys, data):
r = []
r.append(k)
r.append(data[k]['vlanid'])
r.append(m)
if get_interface_mode() == "alias":
alias = interface_name_to_alias(m)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delete extra space after =

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in the new commit

@paavaanan
Copy link
Contributor Author

In this new pull request implemented portalias feature for sonic-specific commands mentioned below:

  • show pfc counters
  • show queue counters
  • show interface status
  • show interface description
  • show interface transceiver lpmode
  • show interface transceiver presence
  • show interface transceiver eeprom
  • show lldp table
  • show mac

As discussed implemented an Alias class for most of the portalias related functions. Most the above commands are in tabulated form. In function run_command_in_alias_mode else part handles above commands. Above command reusults consist of 3 parts. Header, Underline, and tabulate data. These are splitted into different categories and handled accordingly.

Header------------> Adjusted spacing with the help of vendor_name_length variable
Underline---------> Introduced index variable to pick the column to be adjusted.  
Data--------------> String replace default name to alias name.

show/main.py Outdated
@@ -38,6 +40,45 @@ def read_config(self, filename):
pass


class Alias(object):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please give class a more descriptive name, something along the lines of "InterfaceAliasConverter"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in the new commit...

show/main.py Outdated
@@ -38,6 +40,45 @@ def read_config(self, filename):
pass


class Alias(object):
"""Object to Alias conversion"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is "Object"? Description should be more along the lines of "Class which handles conversion between interface name and alias"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in the new commit

show/main.py Outdated
"""Object to Alias conversion"""

def __init__(self):
self.vendor_name_length = 0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename: vendor_name_max_length

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in the new commit

show/main.py Outdated
if interface_name == port_name:
return self.port_dict[port_name]['alias']
click.echo("Invalid interface {}".format(interface_name))

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove blank line 67. Consider adding blank line between 65 and 66.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

show/main.py Outdated
if interface_alias == self.port_dict[port_name]['alias']:
return port_name
click.echo("Invalid interface {}".format(interface_alias))

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove blank line 78. Consider adding blank line between 76 and 77.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

show/main.py Outdated
output = output.lstrip()
index = 0

if command_type == 1:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

command_type values as simple integers are not clearly defined (what does "1" mean? etc.). This makes them confusing. Also, if we have one "command_type" for each command, this will become unwieldy. It would be better if you could key off the command string itself, and not rely on an integer which represents the command.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is good one. Initially I thougt of using enums, but due to lack of package I ruled out the option. As suggested I used the command strings directly in the function and included in the latest commit.

show/main.py Outdated


# Global Alias object
_alias = InterfaceAliasConverter()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change variable name to be more descriptive and reflect new class name.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in the new commit..

show/main.py Outdated
self.vendor_name_max_length = len(
self.port_dict[port_name]['alias'])

def interface_name_to_alias(self, interface_name):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that the class name has changed to "InterfaceAliasConverter", I think you can remove "interface" from the method name (e.g., name_to_alias(self, interface_name):)

show/main.py Outdated
click.echo("Invalid interface {}".format(interface_name))
raise click.Abort()

def interface_alias_to_name(self, interface_alias):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that the class name has changed to "InterfaceAliasConverter", I think you can remove "interface" from the method name (e.g., alias_to_name(self, interface_alias):)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in the new commit..

show/main.py Outdated
elif output.startswith("Port Rx"):
output = output.replace("Port Rx", "Port Rx".rjust(
_alias.vendor_name_max_length))
elif (command == "sudo sfputil show eeprom") or(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you simplify this to elif command.startswith("sudo sfputil show"):?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in the new commit..

show/main.py Outdated
if output == '' and process.poll() is not None:
break
if output:
if linux_command:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to make Linux commands the default (i.e., "else") case here, such that if the command doesn't match any of the if/elif cases, it is treated as a Linux command? This would eliminate the need for the linux_command parameter.

Copy link
Contributor Author

@paavaanan paavaanan Aug 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea. But, without the linux_command it triggers lot of confusion in replacing interfaces from default to alias. Currently we are using different substitution methods for linux commands and sonic utilities. Without this differentiation we are forced to club the logic. This is an overhead to take care all possibilities. Would you like to do this approach?

Copy link
Contributor

@jleveque jleveque Sep 4, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain what you mean by "we are forced to club the logic"? My thought is that if the command doesn't match any of the specific if command == ... clauses, it would fall through into a "default" case, and this case would handle all Linux commands.

It would allow us to remove a lot of duplicate code such as this:

if get_interface_mode() == "alias":
        run_command_in_alias_mode(cmd, linux_command=True)
    else:
        run_command(cmd, display_cmd=verbose)

And instead, be able to check the alias mode inside run_command(), and if alias mode is enabled, then run_command() would call run_command_in_alias_mode().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Joe, the linux/sonic commands use 2 different logics in replacing the interface names. I tested the else style of approach what you suggested. I moved the sonic_utitilies replacement logic inside a generic function "print_output_in_alias_mode" and included in most of the "if" statements.

if command == "portstat":
               """Show interface counters"""
               if output.startswith("IFACE"):
                   output = output.replace("IFACE", "IFACE".rjust(
                                           _interface.vendor_name_max_length))
               print_output_in_alias_mode(output, index)<----

            elif command == "pfcstat":
               """Show pfc counters"""
               if output.startswith("Port Tx"):
                   output = output.replace("Port Tx", "Port Tx".rjust(
                                           _interface.vendor_name_max_length))

               elif output.startswith("Port Rx"):
                   output = output.replace("Port Rx", "Port Rx".rjust(
                                           _interface.vendor_name_max_length))
               print_output_in_alias_mode(output, index)<----

Since, sonic_utitilies replacement logic is safely binded inside the if statements now it is free to introduce else for linux_utitlites commands. This approach works fine.

I agree with checking alias mode inside the run_command. Will raise pull request post testing.

show/main.py Outdated
@@ -116,6 +156,10 @@ def run_command(command, display_cmd=False):
if display_cmd:
click.echo(click.style("Command: ", fg='cyan') + click.style(command, fg='green'))

if get_interface_mode() == "alias" and not command.startswith("intfutil"):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest to add a comment explaining that we don't translate the output of intfutil because it already displays both the name and alias for each interface.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed as above

show/main.py Outdated Show resolved Hide resolved
show/main.py Outdated
word[index] = underline
output = ' ' .join(word)

# Replace default interface name to vendor-name
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

# Replace SONiC interface name with vendor alias

show/main.py Outdated
"""Class which handles conversion between interface name and alias"""

def __init__(self):
self.vendor_name_max_length = 0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please rename to "alias_max_length"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in new commit!

show/main.py Outdated
self.port_dict[port_name]['alias'])

def name_to_alias(self, interface_name):
"""Return alias interface name if default
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clarify comment: "Return vendor interface alias if SONiC interface name is given as argument"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

show/main.py Outdated
raise click.Abort()

def alias_to_name(self, interface_alias):
"""Return default interface name if alias name is given as argument
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clarify comment: "Return SONiC interface name if vendor port alias given as argument"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

show/main.py Outdated
@@ -207,6 +402,10 @@ def alias(interfacename):
body = []

if interfacename is not None:

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove blank line

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

show/main.py Show resolved Hide resolved
@jleveque
Copy link
Contributor

jleveque commented Sep 14, 2018

Is there a reason you didn't add alias-to-name conversion for the "show interfaces transceiver ..." commands? If not, please add it.

Also, please fix the merge conflict (it's only whitespace-related).

Could you also please add a new command, show interfaces naming-mode which displays either "default" or "alias" depending on what is currently set?

@paavaanan
Copy link
Contributor Author

paavaanan commented Sep 15, 2018

transciever support is not added due to the lack of xvcrd implemetation in Dell boxes. Since it is added recently, I can able to test with the new change. Issue addresed in new commit.

Addressed space conflicts and added "show interface naming_mode" command support in new commit..

show/main.py Outdated
iface_alias_converter.alias_max_length))
print_output_in_alias_mode(output, index)

elif command == "sudo sfputil show eeprom":
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be command.startswith("sudo sfputil show eeprom") because the command can take flags and an optional interface argument.

You also need to add code to translate the optional interface name from vendor alias to SONiC name in the "show interfaces transceiver ..." commands on lines 506-548 like you did in the other commands (see lines 560-562 for reference).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed and updated in new commit.. 👍

@jleveque jleveque changed the title show commands with alias support for linux utilities Add 'alias mode' support for show commands Sep 27, 2018
@jleveque jleveque merged commit b8a62d7 into sonic-net:master Sep 27, 2018
@qiluo-msft
Copy link
Contributor

This will break 'show interface status' in vs, please check.

root@346360b6760c:/# show interface status
Traceback (most recent call last):
  File "/usr/local/bin/sonic-cfggen", line 256, in <module>
    main()
  File "/usr/local/bin/sonic-cfggen", line 240, in main
    print(json.dumps(FormatConverter.to_serialized(data[args.var_json]), indent=4, cls=minigraph_encoder))
KeyError: 'PORT'
Traceback (most recent call last):
  File "/usr/bin/show", line 9, in <module>
    load_entry_point('sonic-utilities==1.2', 'console_scripts', 'show')()
  File "/usr/local/lib/python2.7/dist-packages/pkg_resources/__init__.py", line 484, in load_entry_point
    return get_distribution(dist).load_entry_point(group, name)
  File "/usr/local/lib/python2.7/dist-packages/pkg_resources/__init__.py", line 2714, in load_entry_point
    return ep.load()
  File "/usr/local/lib/python2.7/dist-packages/pkg_resources/__init__.py", line 2332, in load
    return self.resolve()
  File "/usr/local/lib/python2.7/dist-packages/pkg_resources/__init__.py", line 2338, in resolve
    module = __import__(self.module_name, fromlist=['__name__'], level=0)
  File "/usr/lib/python2.7/dist-packages/show/main.py", line 193, in <module>
    iface_alias_converter = InterfaceAliasConverter()
  File "/usr/lib/python2.7/dist-packages/show/main.py", line 54, in __init__
    self.port_dict = json.loads(p.stdout.read())
  File "/usr/lib/python2.7/json/__init__.py", line 338, in loads
    return _default_decoder.decode(s)
  File "/usr/lib/python2.7/json/decoder.py", line 366, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/usr/lib/python2.7/json/decoder.py", line 384, in raw_decode
    raise ValueError("No JSON object could be decoded")
ValueError: No JSON object could be decoded

@jleveque
Copy link
Contributor

@qiluo-msft: The root cause was due to the lack of a "PORT" table in ConfigDB on the virtual switch. The "PORT" table will be populated by this PR: sonic-net/sonic-buildimage#2169

@nikos-github
Copy link
Contributor

@paavaanan @jleveque Your commit is breaking 'show interface status', 'show platform summary' and other cmds. Alias is an optional field. See #403

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants