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

Rework eapi module to support multiple vrfs #390

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 90 additions & 27 deletions plugins/modules/eos_eapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,19 @@
default: 30
vrf:
description:
- The C(vrf) argument will configure eAPI to listen for connections in the specified
VRF. By default, eAPI transports will listen for connections in the global
table. This value requires the VRF to already be created otherwise the task
will fail.
default: default
- B(Deprecated - Use option 'vrfs')
type: str
vrfs:
description:
- The C(vrfs) arguement will configure eAPI to listen for connections in the specified
VRFs. By default, eAPI transports will listen for connections in the global(default)
table. If no other VRFs are desired, this option can be left unused. If providing
a VRF other than 'default' and it is desired that the API be available in the defined
VRF as well as the 'default' VRF, 'default' must also be defined. This value requires
the VRF to already be created otherwise the task will fail.
type: list
elements: str
default: ['default']
config:
description:
- The module, by default, will connect to the remote device and retrieve the current
Expand All @@ -131,7 +138,6 @@
- name: Enable eAPI access with default configuration
arista.eos.eos_eapi:
state: started

- name: Enable eAPI with no HTTP, HTTPS at port 9443, local HTTP at port 80, and socket
enabled
arista.eos.eos_eapi:
Expand All @@ -141,10 +147,30 @@
local_http: yes
local_http_port: 80
socket: yes

- name: Shutdown eAPI access
arista.eos.eos_eapi:
state: stopped
- name: Enable eAPI with HTTP, HTTPS at port 443 in only the PROD VRF
arista.eos.eos_eapi:
state: started
http: true
local_http: no
https: yes
https_port: 443
socket: no
vrfs:
- PROD
- name: Enable eAPI with HTTP, HTTPS at port 443 in the global ('default') and PROD VRF
arista.eos.eos_eapi:
state: started
http: true
local_http: no
https: yes
https_port: 443
socket: no
vrfs:
- default
- PROD
"""

RETURN = """
Expand Down Expand Up @@ -193,7 +219,7 @@ def validate_local_http_port(value, module):
module.fail_json(msg="http_port must be between 1 and 65535")


def validate_vrf(value, module):
def validate_vrfs(value, module):
out = run_commands(module, ["show vrf"])
configured_vrfs = []
lines = out[0].strip().splitlines()[3:]
Expand All @@ -203,12 +229,13 @@ def validate_vrf(value, module):
splitted_line = re.split(r"\s{2,}", line.strip())
if len(splitted_line) > 2:
configured_vrfs.append(splitted_line[0])

configured_vrfs.append("default")
if value not in configured_vrfs:
module.fail_json(
msg="vrf `%s` is not configured on the system" % value
)
if "default" not in configured_vrfs:
configured_vrfs.append("default")
for vrf in value:
if vrf not in configured_vrfs:
module.fail_json(
msg="vrf `%s` is not configured on the system" % vrf
)


def map_obj_to_commands(updates, module, warnings):
Expand Down Expand Up @@ -273,14 +300,22 @@ def add(cmd):
elif want["state"] == "started":
add("no shutdown")

if needs_update("vrf"):
add("vrf %s" % want["vrf"])
# switching operational vrfs here
# need to add the desired state as well
if want["state"] == "stopped":
add("shutdown")
elif want["state"] == "started":
add("no shutdown")
if needs_update("vrfs"):
# Assumes that if the VRF is listed it should be enabled.
# if it is not listed, it should not be in the configuration
# and thus disabled by default.
if want["vrfs"] == ["default"]:
# If the list contains only 'default' we do not want to
# explicitly configure the vrf as that is not the behavior
# of the configuration on the device.
for vrf in set(have["vrfs"]) - set(want["vrfs"]):
add("no vrf %s" % vrf)
else:
for vrf in want["vrfs"]:
add("vrf %s" % vrf)
add("no shutdown")
for vrf in set(have["vrfs"]) - set(want["vrfs"]):
add("no vrf %s" % vrf)

return commands

Expand All @@ -293,7 +328,28 @@ def parse_state(data):


def map_config_to_obj(module):
out = run_commands(module, ["show management api http-commands | json"])
out = run_commands(
module,
[
"show management api http-commands | json",
"show running-config | json",
],
)
# Its possible that manually configured VRFs exist in the API configuration section
# which are not defined as a system VRF. In this case they will not appear in the
# show command. We must examine the configuration to find such VRFs
if "management api http-commands" in out[1]["cmds"]:
management_api_config = out[1]["cmds"]["management api http-commands"][
"cmds"
]
management_api_vrfs_config = [
k.lstrip("vrf ")
for k, v in management_api_config.items()
if "vrf" in k
]
else:
# If the API is not configured there will not be any configuration to return.
management_api_vrfs_config = []
return {
"http": out[0]["httpServer"]["configured"],
"http_port": out[0]["httpServer"]["port"],
Expand All @@ -302,7 +358,10 @@ def map_config_to_obj(module):
"local_http": out[0]["localHttpServer"]["configured"],
"local_http_port": out[0]["localHttpServer"]["port"],
"socket": out[0]["unixSocketServer"]["configured"],
"vrf": out[0]["vrf"] or "default",
"vrfs": sorted(
list(set(out[0]["vrfs"]) | set(management_api_vrfs_config))
)
or list(["default"]),
"state": parse_state(out),
}

Expand All @@ -316,7 +375,7 @@ def map_params_to_obj(module):
"local_http": module.params["local_http"],
"local_http_port": module.params["local_http_port"],
"socket": module.params["socket"],
"vrf": module.params["vrf"],
"vrfs": sorted(module.params["vrfs"]),
"state": module.params["state"],
}

Expand All @@ -325,7 +384,6 @@ def map_params_to_obj(module):
validator = globals().get("validate_%s" % key)
if validator:
validator(value, module)

return obj


Expand Down Expand Up @@ -387,7 +445,8 @@ def main():
local_http_port=dict(type="int"),
socket=dict(aliases=["enable_socket"], type="bool"),
timeout=dict(type="int", default=30),
vrf=dict(default="default"),
vrfs=dict(type="list", elements="str", default=["default"]),
vrf=dict(type="str"),
config=dict(),
state=dict(default="started", choices=["stopped", "started"]),
)
Expand All @@ -403,6 +462,10 @@ def main():
warnings.append(
"config parameter is no longer necessary and will be ignored"
)
if module.params["vrf"]:
warnings.append(
"vrf parameter is no longer supported and will be ignore. Use the vrfs parameter instead."
)

want = map_params_to_obj(module)
have = map_config_to_obj(module)
Expand Down
6 changes: 3 additions & 3 deletions tests/unit/modules/network/eos/test_eos_eapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ def test_eos_eapi_local_http_port(self):
self.start_configured(changed=True, commands=commands)

def test_eos_eapi_vrf(self):
set_module_args(dict(vrf="test"))
set_module_args(dict(vrfs=["test"]))
commands = [
"management api http-commands",
"no shutdown",
Expand All @@ -167,7 +167,7 @@ def test_eos_eapi_vrf(self):
self.start_unconfigured(changed=True, commands=commands)

def test_eos_eapi_change_from_default_vrf(self):
set_module_args(dict(vrf="test"))
set_module_args(dict(vrfs=["test"]))
commands = ["management api http-commands", "vrf test", "no shutdown"]
self.start_configured(changed=True, commands=commands)

Expand All @@ -176,7 +176,7 @@ def test_eos_eapi_default(self):
self.start_configured(changed=False, commands=[])

def test_eos_eapi_vrf_missing(self):
set_module_args(dict(vrf="missing"))
set_module_args(dict(vrfs=["missing"]))
self.start_unconfigured(failed=True)

def test_eos_eapi_state_absent(self):
Expand Down