Skip to content

Commit

Permalink
[system] Update replaced state handling (#388)
Browse files Browse the repository at this point in the history
* system - Update replaced state handling

* Add changelog fragment

* Handle interface naming mode

* Remove unused import
  • Loading branch information
ArunSaravananBalachandran authored Jun 18, 2024
1 parent ebf234d commit 2793017
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 221 deletions.
3 changes: 3 additions & 0 deletions changelogs/fragments/388-system-update-replaced-state.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
minor_changes:
- sonic_system - Update replaced state handling (https://github.com/ansible-collections/dellemc.enterprise_sonic/pull/388).
206 changes: 61 additions & 145 deletions plugins/module_utils/network/sonic/config/system/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
ConfigBase,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
remove_empties,
to_list,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
Expand All @@ -36,7 +37,6 @@
get_new_config,
get_formatted_config_diff
)
from copy import deepcopy

PATCH = 'patch'
DELETE = 'delete'
Expand Down Expand Up @@ -167,10 +167,8 @@ def set_state(self, want, have):
elif state == 'merged':
diff = get_diff(want, have)
commands = self._state_merged(want, have, diff)
elif state == 'overridden':
commands = self._state_overridden(want, have)
elif state == 'replaced':
commands = self._state_replaced(want, have)
elif state in ('overridden', 'replaced'):
commands = self._state_replaced_overridden(want, have)

return commands

Expand Down Expand Up @@ -216,8 +214,8 @@ def _state_deleted(self, want, have):

return commands, requests

def _state_replaced(self, want, have):
""" The command generator when state is replaced
def _state_replaced_overridden(self, want, have):
""" The command generator when state is replaced or overridden
:param want: the desired configuration as a dictionary
:param have: the current configuration as a dictionary
Expand All @@ -227,73 +225,67 @@ def _state_replaced(self, want, have):
to the desired configuration
"""
commands = []
requests = []
merged_commands = []
merged_requests = []

new_want = self.patch_want_with_default(want, ac_address_only=True)
replaced_config = self.get_replaced_config(have, new_want)
if replaced_config:
requests = self.get_delete_all_system_request(replaced_config)
if len(requests) > 0:
commands = update_states(replaced_config, "deleted")
if 'hostname' in commands or 'interface_naming' in commands:
# All existing config is to be deleted.
merged_commands = new_want
else:
# Only the "anycast_address" config is to be deleted.
new_have = deepcopy(have)
new_have.pop('anycast_address', None)
merged_commands = get_diff(new_want, new_have)
add_command = {}
del_command = {}

if merged_commands:
merged_requests = self.get_create_system_request(have, merged_commands)

if len(merged_requests) > 0:
merged_commands = update_states(merged_commands, "replaced")
requests = []
del_requests = []

default_values = {
'hostname': 'sonic',
'interface_naming': 'native',
'anycast_address': {
'ipv4': True,
'ipv6': True
},
'auto_breakout': 'DISABLE'

}
del_request_method = {
'hostname': self.get_hostname_delete_request,
'interface_naming': self.get_intfname_delete_request,
'auto_breakout': self.get_auto_breakout_delete_request
}

new_have = remove_empties(have)
new_want = remove_empties(want)

for option in ('hostname', 'interface_naming', 'auto_breakout'):
if option in new_want:
if new_want[option] != new_have.get(option):
add_command[option] = new_want[option]
else:
merged_commands = []
merged_requests = []

commands.extend(merged_commands)
requests.extend(merged_requests)

return commands, requests

def _state_overridden(self, want, have):
""" The command generator when state is overridden
if option in new_have and new_have[option] != default_values.get(option):
del_command[option] = new_have[option]
del_requests.append(del_request_method[option]())

want_anycast = new_want.get('anycast_address', {})
have_anycast = new_have.get('anycast_address', {})
if want_anycast:
for option in ('ipv4', 'ipv6', 'mac_address'):
if option in want_anycast:
if want_anycast[option] != have_anycast.get(option):
add_command.setdefault('anycast_address', {})
add_command['anycast_address'][option] = want_anycast[option]
else:
if option in have_anycast and have_anycast[option] != default_values['anycast_address'].get(option):
del_command.setdefault('anycast_address', {})
del_command['anycast_address'][option] = have_anycast[option]

:param want: the desired configuration as a dictionary
:param have: the current configuration as a dictionary
:param diff: the difference between want and have
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
requests = []
merged_requests = []
merged_commands = []
if del_command.get('anycast_address'):
del_requests.extend(self.get_anycast_delete_request(del_command['anycast_address']))
else:
if have_anycast:
del_command['anycast_address'] = have_anycast
del_requests.extend(self.get_anycast_delete_request(del_command['anycast_address']))

new_want = self.patch_want_with_default(want)
if have and have != new_want:
requests = self.get_delete_all_system_request(have)
if len(requests) > 0:
commands = update_states(have, "deleted")
else:
requests = []
have = []

if not have and new_want:
merged_commands = deepcopy(new_want)
merged_requests = self.get_create_system_request(have, merged_commands)
if len(merged_requests) > 0:
merged_commands = update_states(merged_commands, "overridden")
else:
merged_commands = []
if del_command:
commands = update_states(del_command, 'deleted')
requests.extend(del_requests)

commands.extend(merged_commands)
requests.extend(merged_requests)
if add_command:
commands.extend(update_states(add_command, self._module.params['state']))
requests.extend(self.get_create_system_request(new_want, add_command))

return commands, requests

Expand Down Expand Up @@ -364,82 +356,6 @@ def build_create_auto_breakout_payload(self, commands):
payload.update({'sonic-device-metadata:auto-breakout': commands["auto_breakout"]})
return payload

def patch_want_with_default(self, want, ac_address_only=False):
new_want = {}
if want is None:
if ac_address_only:
new_want = {'anycast_address': {'ipv4': True, 'ipv6': True, 'mac_address': None}}
else:
new_want = {'hostname': 'sonic', 'interface_naming': 'native',
'anycast_address': {'ipv4': True, 'ipv6': True, 'mac_address': None},
'auto_breakout': 'DISABLE'}
else:
new_want = want.copy()
new_anycast = {}
anycast = want.get('anycast_address', None)
if not anycast:
new_anycast = {'ipv4': True, 'ipv6': True, 'mac_address': None}
else:
new_anycast = anycast.copy()
ipv4 = anycast.get("ipv4", None)
if ipv4 is None:
new_anycast["ipv4"] = True
ipv6 = anycast.get("ipv6", None)
if ipv6 is None:
new_anycast["ipv6"] = True
mac = anycast.get("mac_address", None)
if mac is None:
new_anycast["mac_address"] = None
new_want["anycast_address"] = new_anycast

if not ac_address_only:
hostname = want.get('hostname', None)
if hostname is None:
new_want["hostname"] = 'sonic'
intf_name = want.get('interface_naming', None)
if intf_name is None:
new_want["interface_naming"] = 'native'
auto_breakout_mode = want.get('auto_breakout', None)
if auto_breakout_mode is None:
new_want["auto_breakout"] = 'DISABLE'
return new_want

def get_replaced_config(self, have, want):

replaced_config = dict()
top_level_want = False

w_hostname = want.get('hostname', None)
w_intf_name = want.get('interface_naming', None)
w_auto_breakout_mode = want.get('auto_breakout', None)

if w_hostname is not None or w_intf_name is not None or w_auto_breakout_mode is not None:
top_level_want = True

if top_level_want:
h_hostname = have.get('hostname', None)
if (h_hostname != w_hostname):
replaced_config = have.copy()
return replaced_config

h_intf_name = have.get('interface_naming', None)
if (h_intf_name != w_intf_name):
replaced_config = have.copy()
return replaced_config

h_auto_breakout_mode = have.get('auto_breakout', None)
if (h_auto_breakout_mode != w_auto_breakout_mode) and w_auto_breakout_mode:
replaced_config = have.copy()
return replaced_config

h_ac_addr = have.get('anycast_address', None)
w_ac_addr = want.get('anycast_address', None)
if (h_ac_addr != w_ac_addr) and w_ac_addr:
replaced_config['anycast_address'] = h_ac_addr
return replaced_config

return replaced_config

def remove_default_entries(self, data):
new_data = {}
if not data:
Expand Down
35 changes: 9 additions & 26 deletions plugins/module_utils/network/sonic/facts/system/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,21 +56,22 @@ def get_system(self):
data = {}
return data

def get_naming(self):
"""Get interface_naming type available in chassis"""
def get_intf_naming_auto_breakout(self):
"""Get interface_naming_mode and auto-breakout status available in chassis"""
request = [{"path": "data/sonic-device-metadata:sonic-device-metadata/DEVICE_METADATA/DEVICE_METADATA_LIST=localhost", "method": GET}]
try:
response = edit_config(self._module, to_request(self._module, request))
except ConnectionError as exc:
self._module.fail_json(msg=str(exc), code=exc.code)
data = {}
if ('sonic-device-metadata:DEVICE_METADATA_LIST' in response[0][1]):
intf_data = response[0][1]['sonic-device-metadata:DEVICE_METADATA_LIST']
if 'intf_naming_mode' in intf_data[0]:
if intf_data[0]['intf_naming_mode'] == 'standard-ext':
intf_data[0]['intf_naming_mode'] = 'standard_extended'
data = intf_data[0]
else:
data = {}
data['intf_naming_mode'] = intf_data[0]['intf_naming_mode']
if 'auto-breakout' in intf_data[0]:
data['auto-breakout'] = intf_data[0]['auto-breakout']
return data

def get_anycast_addr(self):
Expand All @@ -86,21 +87,6 @@ def get_anycast_addr(self):
data = {}
return data

def get_auto_breakout(self):
"""Get auto-breakout status available in chassis"""
request = [{"path": "data/sonic-device-metadata:sonic-device-metadata/DEVICE_METADATA/DEVICE_METADATA_LIST=localhost", "method": GET}]
try:
response = edit_config(self._module, to_request(self._module, request))
except ConnectionError as exc:
self._module.fail_json(msg=str(exc), code=exc.code)
data = {}
if ('sonic-device-metadata:DEVICE_METADATA_LIST' in response[0][1]):
auto_breakout_data = response[0][1]['sonic-device-metadata:DEVICE_METADATA_LIST']
if 'auto-breakout' in auto_breakout_data[0]:
auto_breakout_val = auto_breakout_data[0]['auto-breakout']
data = {'auto-breakout': auto_breakout_val}
return data

def populate_facts(self, connection, ansible_facts, data=None):
""" Populate the facts for system
:param connection: the device connection
Expand All @@ -111,15 +97,12 @@ def populate_facts(self, connection, ansible_facts, data=None):
"""
if not data:
data = self.get_system()
intf_naming = self.get_naming()
if intf_naming:
data.update(intf_naming)
intf_naming_auto_breakout = self.get_intf_naming_auto_breakout()
if intf_naming_auto_breakout:
data.update(intf_naming_auto_breakout)
anycast_addr = self.get_anycast_addr()
if anycast_addr:
data.update(anycast_addr)
auto_breakout = self.get_auto_breakout()
if auto_breakout:
data.update(auto_breakout)
objs = []
objs = self.render_config(self.generated_spec, data)
facts = {}
Expand Down
Loading

0 comments on commit 2793017

Please sign in to comment.