[config] Add support for accepting port alias in lieu of port name#260
[config] Add support for accepting port alias in lieu of port name#260jleveque merged 10 commits intosonic-net:masterfrom
Conversation
config/main.py
Outdated
|
|
||
| def set_interface_mode(mode): | ||
| tmp = "/tmp/" | ||
| output = subprocess.check_output(['tty']).strip('\n') |
There was a problem hiding this comment.
After our discussion earlier, I understand that the interface_status.sh script in PR sonic-net/sonic-buildimage#1751 will remove stale entries upon a new tty connection. However, I am still concerned that this approach is unnecessarily complicated.
Why not simply set an environment variable per user (e.g., export SONIC_CLI_ALIAS_MODE="true")? Using this approach, we could also cause the setting to persist between sessions by writing this to the user's .profile file. If the variable is either "false" or not defined, we assume input will be SONiC port names (the default behavior). If the variable is "true" we assume the input will be port aliases.
There was a problem hiding this comment.
Agreed. Initially we approched the issue in the above context. Since python is the child process of parent bash (as like for any sessios) we can inherit the status of enviroment variable say SONIC_CLI_ALIAS_MODE to python but we can't revert back the state to TRUE/FALSE back to bash environment variable. . The change do exists until the script terminates. SONIC_CLI_ALIAS_MODE value cannot be exported to parent bash.
There was a problem hiding this comment.
Ah, yes ... you can't modify the environment of the parent shell. That's a problem. If we wanted to proceed with this approach, I guess we would need to prompt the user to log out and log back in again for the change to take effect; I think that's quite an inconvenience.
@lguohan: Do you have any alternative suggestions? Otherwise, I believe the tty-based approach may be our best bet.
config/main.py
Outdated
| if proc.returncode != 0 and not ignore_error: | ||
| sys.exit(proc.returncode) | ||
|
|
||
| def convert_interface_name(interface_name): |
There was a problem hiding this comment.
This function will blindly convert a port name to a port alias and vice-versa. This could pose issues if a device shares the same string for both a port name and a port alias, as it will always find a match on line 49 and return that port's alias.
I think it is better to have two functions with distinct functionality: interface_name_to_alias(interface_name) and interface_alias_to_name(interface_alias).
There was a problem hiding this comment.
Introduced interface_alias_to_name() to convert the alias to default mode. In the current code change, there is no any need for interface_name_to_alias() conversion. While implementing the feature for other subset of commands if needed will implement on need basis.
config/main.py
Outdated
| if proc.returncode != 0 and not ignore_error: | ||
| sys.exit(proc.returncode) | ||
|
|
||
| def interface_alias_to_name(interface_name): |
There was a problem hiding this comment.
The parameter should be called interface_alias. Otherwise it's very confusing.
|
• Modified the design to user based approach. Harnessed the power of /etc/sudoers and /etc/environment file to retain the environment variable value even in sudo sessions. So, when the user request the change from default mode to alias mode, the action is completed in two phases. |
|
config/main.py
Outdated
| f = open(bashrc,'w') | ||
| f.write(newdata) | ||
| f.close() | ||
| print "Please logout and login again!" |
There was a problem hiding this comment.
Suggest change to "Please log out and log back in for changes take effect."
config/main.py
Outdated
| if interface_name == port_dict[port_name]['alias']: | ||
| return port_name | ||
| print "Invalid interface {}".format(interface_name) | ||
| sys.exit(0) |
There was a problem hiding this comment.
As this is a utility function, I'm not sure it should cause the program to exit. Maybe return None and have the caller exit if it does?
There was a problem hiding this comment.
comment addressed below
config/main.py
Outdated
| if interface_name == port_name: | ||
| return port_dict[port_name]['alias'] | ||
| print "Invalid interface {}".format(interface_name) | ||
| sys.exit(0) |
There was a problem hiding this comment.
As this is a utility function, I'm not sure it should cause the program to exit. Maybe return None and have the caller exit if it does?
There was a problem hiding this comment.
shutdown command as an example:
def shutdown(interface_name, verbose):
"""Shut down interface"""
print "lias"
if get_interface_mode() == "alias":
interface_name = interface_alias(interface_name)
- If we fail to return sys.exit(0) in function interface_alias() it propogates to next line and execute "ip link set <interface_nam> down. Where interface_name is invalid.
- This is unnecessary and tends to double opertaion. To avoid this sys.exit(0) introduced. and also it alerts the user about invalid interface.
- If you are skeptical to the exit in utiltiy function we can shift the exit in parent shutdown().
command = "ip link set {} down".format(interface_name)
run_command(command, display_cmd=verbose)
There was a problem hiding this comment.
Yes. I think the caller functions should check for None, print an error and exit.
There was a problem hiding this comment.
Will addres the changes
config/main.py
Outdated
| if proc.returncode != 0 and not ignore_error: | ||
| sys.exit(proc.returncode) | ||
|
|
||
| def interface_alias(interface_name): |
There was a problem hiding this comment.
This was not the change I requested. I requested you change the parameter name to interface_alias, not the function name. It should be def interface_alias_to_name(interface_alias):
There was a problem hiding this comment.
Thanks for the clarification
|
|
||
| port_dict = json.loads(p.stdout.read()) | ||
|
|
||
| if interface_name is not None: |
There was a problem hiding this comment.
What happens in the case where interface_name is None?
There was a problem hiding this comment.
- The interface_name validation is done by pthon click package at the initial phase of the function it throws missing argument error.
admin@sonic:~$ sudo config interface shutdown None
Invalid interface None
admin@sonic:~$ sudo config interface shutdown
Usage: config interface shutdown [OPTIONS] <interface_name>
Error: Missing argument "interface_name".
There was a problem hiding this comment.
I wasn't asking what happens if a user provides the string "None" as the interface name.
My question is regarding the fact that you have an if statement (if interface_name is not None:) which returns a value but no corresponding else statement. Thus, if interface_name is of type None (not the string "None"), this function will not return anything. I think this was overlooked, so I was asking for an explanation to see if this is the behavior you expect.
There was a problem hiding this comment.
I wasn't asking what happens if a user provides the string "None" as the interface name.
My question is regarding the fact that you have an if statement (if interface_name is not None:) which returns a value, but there is no corresponding else statement. Thus, if interface_name is of type None (not the string "None"), this function will not return anything. I think this was overlooked, so I was asking for an explanation to see if this is the behavior you expect.
There was a problem hiding this comment.
Joe I got your point. When the interface_name variable type is None, even though else is not defined inside the corresponding function the return type of interface_alias_to_name() will be None. All parent functions are equipped to handle this case. So, Invalid Interface message will be raised to the user. Even if we add an else the behavior will be the same.
"""Shut down interface"""
if get_interface_mode() == "alias":
interface_name = interface_alias_to_name(interface_name)
if interface_name is None:
print "Invalid interface {}".format(interface_name)<--------
raise click.Abort()
)
admin@sonic:~$ sudo config interface shutdown hundredGigE1/1/120
Invalid interface hundredGigE1/1/120
Aborted!
admin@sonic:~$
There was a problem hiding this comment.
I still believe that every path should explicitly return a value. Otherwise the behavior of the function isn't easily determined by the reader. I think you can easily fix this as follows:
if interface_alias is not None:
for port_name in natsorted(port_dict.keys()):
if interface_alias == port_dict[port_name]['alias']:
return port_name
print "Invalid interface {}".format(interface_alias)
return None
config/main.py
Outdated
| print "Please logout and login again!" | ||
|
|
||
| def get_interface_mode(): | ||
| mode = os.getenv('IFMODE') |
There was a problem hiding this comment.
What if the environment variable is not set? You should probably return "default" in this case so that this function consistently returns a value.
There was a problem hiding this comment.
Will address this.
config/main.py
Outdated
|
|
||
| @cli.group() | ||
| def interface_mode(): | ||
| """Interface mode change tasks""" |
There was a problem hiding this comment.
Suggest """Modify interface naming mode for interacting with SONiC CLI"""
config/main.py
Outdated
|
|
||
| @interface_mode.command('default') | ||
| def interface_mode_default(): | ||
| """Set interface mode to DEFAULT""" |
There was a problem hiding this comment.
Suggest """Set CLI interface naming mode to DEFAULT (SONiC port name)"""
config/main.py
Outdated
|
|
||
| @interface_mode.command('alias') | ||
| def interface_mode_alias(): | ||
| """Set interface mode to ALIAS""" |
There was a problem hiding this comment.
Suggest """Set CLI interface naming mode to ALIAS (Vendor port alias)"""
config/main.py
Outdated
| # | ||
|
|
||
| @cli.group() | ||
| def interface_mode(): |
There was a problem hiding this comment.
Suggest change to interface_naming_mode()
| if not sudo_user: | ||
| bashrc = "/{}/.bashrc".format(user) | ||
| user = os.getenv('SUDO_USER') | ||
| bashrc_ifacemode_line = "SONIC_CLI_IFACE_MODE={}".format(mode) |
There was a problem hiding this comment.
I think this line is best moved down just above line 87, right before it is used. That will also keep all of the user name determination code together.
config/main.py
Outdated
| newdata = re.sub(r"IFMODE=\w+",set_mode,filedata) | ||
|
|
||
| newdata = re.sub(r"SONIC_CLI_IFACE_MODE=\w+", bashrc_ifacemode_line, filedata) | ||
config/main.py
Outdated
|
|
||
| if get_interface_mode() == "alias": | ||
| interface_name = interface_alias(interface_name) | ||
| default_name = interface_alias_to_name(interface_name) |
There was a problem hiding this comment.
The introduction of the variable default_name is unnecessary. Simply assign directly to interface_name and compare it to None. You can then also eliminate the else clause:
interface_name = interface_alias_to_name(interface_name)
if interface_name is None:
print "Invalid interface {}".format(interface_name)
raise click.Abort()
config/main.py
Outdated
| interface_name = interface_alias(interface_name) | ||
| default_name = interface_alias_to_name(interface_name) | ||
| if default_name is None: | ||
| print "Invalid interface {}".format(interface_name) |
There was a problem hiding this comment.
interface_name is not defined. This will crash. However, instead of replacing it with default_name, please refactor the code as I suggested in my comment on line 411.
There was a problem hiding this comment.
How do this crash? interface_name is the incoming argument for add_vlan_member() So, argument do have a value when invoked.
def add_vlan_member(ctx, vid, interface_name, untagged):
There was a problem hiding this comment.
You're correct. Technically, Click should prevent interface_name from being None. I think I got a bit lost due the introduction of default_interface. Ignore this comment. #Closed
config/main.py
Outdated
| alias_name = interface_name_to_alias(interface_name) | ||
| if alias_name is None: | ||
| print "Invalid interface {}".format(interface_name) | ||
| sys.exit(0) |
There was a problem hiding this comment.
Please replace sys.exit(0) raise click.Abort(), which will cause the program to exit with code 1.
config/main.py
Outdated
|
|
||
| if get_interface_mode() == "alias": | ||
| interface_name = interface_alias(interface_name) | ||
| default_name = interface_alias_to_name(interface_name) |
There was a problem hiding this comment.
Please refactor this code in the same manner as I suggested in my comment on line 411.
config/main.py
Outdated
| """Shut down interface""" | ||
| if get_interface_mode() == "alias": | ||
| interface_name = interface_alias(interface_name) | ||
| default_name = interface_alias_to_name(interface_name) |
There was a problem hiding this comment.
Please refactor this code in the same manner as I suggested in my comment on line 411.
config/main.py
Outdated
| """Start up interface""" | ||
| if get_interface_mode() == "alias": | ||
| interface_name = interface_alias(interface_name) | ||
| default_name = interface_alias_to_name(interface_name) |
There was a problem hiding this comment.
Please refactor this code in the same manner as I suggested in my comment on line 411.
config/main.py
Outdated
| """Set interface speed""" | ||
| if get_interface_mode() == "alias": | ||
| interface_name = interface_alias(interface_name) | ||
| default_name = interface_alias_to_name(interface_name) |
There was a problem hiding this comment.
Please refactor this code in the same manner as I suggested in my comment on line 411.
portalias/PORTALIAS.md
Outdated
| @@ -0,0 +1,75 @@ | |||
| #Portalias | |||
There was a problem hiding this comment.
A design doc of this sort doesn't belong in this repo alongside the code; if anything it should be placed in the GitHub wiki. Please delete this file.
|
@jleveque addressed all review comments and raised 2 commits with all changes. |
config/main.py
Outdated
| bashrc = "/home/{}/.bashrc".format(user) | ||
| else: | ||
| sys.exit(0) | ||
| raise click.Abort |
There was a problem hiding this comment.
Add parentheses for consistency: raise click.Abort()
config/main.py
Outdated
| interface_name = interface_alias_to_name(interface_name) | ||
| if interface_name is None: | ||
| sys.exit(0) | ||
| raise click.Abort |
There was a problem hiding this comment.
Add parentheses for consistency: raise click.Abort()
config/main.py
Outdated
| interface_name = interface_alias_to_name(interface_name) | ||
| if interface_name is None: | ||
| sys.exit(0) | ||
| raise click.Abort |
There was a problem hiding this comment.
Add parentheses for consistency: raise click.Abort()
config/main.py
Outdated
| interface_name = interface_alias_to_name(interface_name) | ||
| if interface_name is None: | ||
| sys.exit(0) | ||
| raise click.Abort |
There was a problem hiding this comment.
Add parentheses for consistency: raise click.Abort()
config/main.py
Outdated
| interface_name = interface_alias_to_name(interface_name) | ||
| if interface_name is None: | ||
| sys.exit(0) | ||
| raise click.Abort |
There was a problem hiding this comment.
Add parentheses for consistency: raise click.Abort()
config/main.py
Outdated
| interface_name = interface_alias_to_name(interface_name) | ||
| if interface_name is None: | ||
| sys.exit(0) | ||
| raise click.Abort |
There was a problem hiding this comment.
Add parentheses for consistency: raise click.Abort()
|
|
||
| port_dict = json.loads(p.stdout.read()) | ||
|
|
||
| if interface_name is not None: |
There was a problem hiding this comment.
I still believe that every path should explicitly return a value. Otherwise the behavior of the function isn't easily determined by the reader. I think you can easily fix this as follows:
if interface_alias is not None:
for port_name in natsorted(port_dict.keys()):
if interface_alias == port_dict[port_name]['alias']:
return port_name
print "Invalid interface {}".format(interface_alias)
return None
|
|
||
| if "SONIC_CLI_IFACE_MODE" not in filedata: | ||
| newdata = filedata + bashrc_ifacemode_line | ||
| newdata += "\n" |
There was a problem hiding this comment.
I suggest adding the newline directly to bashrc_ifacemode_line on line 78 as follows:
bashrc_ifacemode_line = "SONIC_CLI_IFACE_MODE={}\n".format(mode)
This PR updates the following commits in 202012 1f32e5c (HEAD -> 202012, origin/202012) [ycable][credo] Fix the is_link_active API for Credo Ycable (sonic-net#260) c249681 [Y-Cable][Credo] add theading locker to support thread-safe calling, add SKU check for download_firmware API. (sonic-net#222) Signed-off-by: vaibhav-dahiya <vdahiya@microsoft.com>
…t#260) Signed-off-by: vaibhav-dahiya vdahiya@microsoft.com Description Currently is_link_active API was broken because it was not using the target param in the routine to give back the correct link_status of ecah side. With this change it gives the correct result. Motivation and Context Required for MUX_CABLE_INFO table in streaming telemetry if the cable is disconnected or the link is down. redis-cli -n 6 hgetall "MUX_CABLE_INFO|Ethernet16" " 9) "link_status_self" 10) "down" 11) "link_status_peer" 12) "up" 13) "link_status_nic" 14) "up" How Has This Been Tested? Tested on actual Arista7050cx3 testbed.
…#260 What I did This PR introduces a new monitoring script to check synchronization of LAG (Link Aggregation Group) IDs between the chassis database and ASIC databases on VOQ chassis line cards. The script is designed to be run by Monit and will alert via syslog when mismatches are detected. port sonic-net#4082 to 202405 How I did it Key Changes Added chassis_lag_id_checker script that retrieves and compares LAG IDs from chassis_db and asic_db, reporting mismatches per ASIC namespace Comprehensive test suite with fixtures for mocking Redis dumps and ASIC/device configurations Integration into setup.py for proper installation How to verify it test on voq chassis and UT
portalias-logs.txt
- What I did
Implemented portalias feature for two basic commands "config interface shutdown/startup"
-Introduced new CLI to configure the interface mode:
-config interface_mode "alias"
-config interface_mode "defaut"
User can stick to any mode. Mode selection is unique for each sessions. Default mode stick to sonic ethernet interface style and Alias mode stick to individual switch vendor interface style. User selection sticks to the current logged session. If multiple login session do exists it maintains the user selection seperately.
- How I did it
- How to verify it
--> interface_mode.sh is raised in sonic-net/sonic-buildimage#1751