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 iosxr grpc plugin and grpc_nw_get module #52857
Closed
Closed
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
# | ||
# (c) 2019 Red Hat, Inc. | ||
# | ||
# This file is part of Ansible | ||
# | ||
# Ansible is free software: you can redistribute it and/or modify | ||
# it under the terms of the GNU General Public License as published by | ||
# the Free Software Foundation, either version 3 of the License, or | ||
# (at your option) any later version. | ||
# | ||
# Ansible is distributed in the hope that it will be useful, | ||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
# GNU General Public License for more details. | ||
# | ||
# You should have received a copy of the GNU General Public License | ||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>. | ||
# | ||
|
||
from ansible.module_utils._text import to_text | ||
from ansible.module_utils.connection import Connection | ||
|
||
|
||
def get_connection(module): | ||
if hasattr(module, '_grpc_connection'): | ||
return module._grpc_connection | ||
|
||
capabilities = get_capabilities(module) | ||
network_api = capabilities.get('network_api') | ||
if network_api == 'grpc': | ||
module._grpc_connection = Connection(module._socket_path) | ||
else: | ||
module.fail_json(msg='Invalid connection type %s' % network_api) | ||
|
||
return module._grpc_connection | ||
|
||
|
||
def get_capabilities(module): | ||
if hasattr(module, '_grpc_capabilities'): | ||
return module._grpc_capabilities | ||
|
||
module._grpc_capabilities = Connection(module._socket_path).get_capabilities() | ||
return module._grpc_capabilities | ||
|
||
|
||
def get(module, section, data_type, check_rc=True): | ||
conn = get_connection(module) | ||
if data_type == 'config': | ||
out = conn.get_config(section) | ||
else: | ||
out = conn.get(section) | ||
|
||
response = out.get('response') | ||
error = out.get('error') | ||
if error: | ||
if check_rc: | ||
module.fail_json(msg=to_text(out['error'], errors='surrogate_then_replace')) | ||
else: | ||
module.warn(to_text(out['error'], errors='surrogate_then_replace')) | ||
|
||
return response.strip(), error.strip() | ||
|
||
|
||
def run_cli(module, command, display, check_rc=True): | ||
conn = get_connection(module) | ||
out = conn.run_cli(command, display) | ||
response = out.get('response') | ||
error = out.get('error') | ||
if error: | ||
if check_rc: | ||
module.fail_json(msg=to_text(out['error'], errors='surrogate_then_replace')) | ||
else: | ||
module.warn(to_text(out['error'], errors='surrogate_then_replace')) | ||
|
||
return response.strip(), error.strip() |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,195 @@ | ||
#!/usr/bin/python | ||
# -*- coding: utf-8 -*- | ||
|
||
# (c) 2019, Ansible by Red Hat, inc | ||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) | ||
|
||
from __future__ import absolute_import, division, print_function | ||
__metaclass__ = type | ||
|
||
|
||
ANSIBLE_METADATA = {'metadata_version': '1.1', | ||
'status': ['preview'], | ||
'supported_by': 'network'} | ||
|
||
|
||
DOCUMENTATION = """ | ||
--- | ||
module: grpc_nw_get | ||
version_added: "2.8" | ||
author: | ||
- "Ganesh Nalawade (@ganeshrn)" | ||
short_description: Fetch configuration/state data from gRPC enabled target hosts. | ||
description: | ||
- gRPC is a high performance, open-source universal RPC framework. | ||
- This module allows the user to fetch configuration and state data from gRPC | ||
enabled devices. | ||
options: | ||
section: | ||
description: | ||
- This option specifies the string which acts as a filter to restrict the portions of | ||
the data to be retrieved from the target host device. If this option is not specified the entire | ||
configuration or state data is returned in response provided it is supported by target host. | ||
command: | ||
description: | ||
- The option specifies the command to be executed on the target host and return the response | ||
in result. This option is supported if the gRPC target host supports executing CLI command | ||
over the gRPC connection. | ||
display: | ||
description: | ||
- Encoding scheme to use when serializing output from the device. The encoding scheme | ||
value depends on the capability of the gRPC server running on the target host. | ||
The values can be I(json), I(text) etc. | ||
data_type: | ||
description: | ||
- The type of data that should be fetched from the target host. The value depends on the | ||
capability of the gRPC server running on target host. The values can be I(config), I(oper) | ||
etc. based on what is supported by the gRPC server. By default it will return both configuration | ||
and operational state data in response. | ||
requirements: | ||
- grpcio | ||
- protobuf | ||
|
||
notes: | ||
- This module requires the gRPC system service be enabled on | ||
the target host being managed. | ||
- This module supports the use of connection=grpc. | ||
- This module requires the value of 'ansible_network_os' be defined as an inventory variable. | ||
- Tested against iosxrv 9k version 6.1.2. | ||
""" | ||
|
||
EXAMPLES = """ | ||
- name: Get bgp configuration data | ||
grpc_nw_get: | ||
section: '{"Cisco-IOS-XR-ipv4-bgp-cfg:bgp": [null]}' | ||
|
||
- name: Get configuration JSON format over secure TLS channel | ||
grpc_nw_get: | ||
display: json | ||
data: config | ||
vars: | ||
ansible_root_certificates_file: /home/username/ems.pem | ||
ansible_grpc_channel_options: | ||
'grpc.ssl_target_name_override': 'ems.cisco.com' | ||
|
||
- name: run cli command | ||
grpc_nw_get: | ||
command: 'show version' | ||
display: text | ||
""" | ||
|
||
RETURN = """ | ||
stdout: | ||
description: The raw string containing configuration or state data | ||
received from the gRPC server. | ||
returned: always apart from low-level errors (such as action plugin) | ||
type: str | ||
sample: '...' | ||
stdout_lines: | ||
description: The value of stdout split into a list | ||
returned: always apart from low-level errors (such as action plugin) | ||
type: list | ||
sample: ['...', '...'] | ||
output: | ||
description: A dictionary representing a JSON-formatted response, if the response | ||
is a valid json string | ||
returned: when the device response is valid JSON | ||
type: list | ||
sample: | | ||
[{ | ||
"Cisco-IOS-XR-ip-static-cfg:router-static": { | ||
"default-vrf": { | ||
"address-family": { | ||
"vrfipv4": { | ||
"vrf-unicast": { | ||
"vrf-prefixes": { | ||
"vrf-prefix": [ | ||
{ | ||
"prefix": "0.0.0.0", | ||
"prefix-length": 0, | ||
"vrf-route": { | ||
"vrf-next-hop-table": { | ||
"vrf-next-hop-interface-name-next-hop-address": [ | ||
{ | ||
"interface-name": "MgmtEth0/RP0/CPU0/0", | ||
"next-hop-address": "10.0.2.2" | ||
} | ||
] | ||
} | ||
} | ||
} | ||
] | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}] | ||
""" | ||
from ansible.module_utils._text import to_text | ||
from ansible.module_utils.basic import AnsibleModule | ||
from ansible.module_utils.connection import ConnectionError | ||
from ansible.module_utils.network.common.utils import to_list | ||
from ansible.module_utils.network.grpc.grpc import get_capabilities, get, run_cli | ||
|
||
|
||
def main(): | ||
"""entry point for module execution | ||
""" | ||
argument_spec = dict( | ||
section=dict(), | ||
command=dict(), | ||
display=dict(), | ||
data_type=dict() | ||
) | ||
|
||
mutually_exclusive = [['section', 'command']] | ||
|
||
module = AnsibleModule(argument_spec=argument_spec, | ||
mutually_exclusive=mutually_exclusive, | ||
supports_check_mode=True) | ||
|
||
capabilities = get_capabilities(module) | ||
|
||
operations = capabilities['server_capabilities'] | ||
|
||
section = module.params['section'] | ||
command = module.params['command'] | ||
display = module.params['display'] | ||
data_type = module.params['data_type'] | ||
|
||
if display and display not in operations.get('display', []): | ||
module.fail_json(msg="display option '%s' is not supported on this target host" % display) | ||
|
||
if command and not operations.get('supports_cli_command', False): | ||
module.fail_json(msg="command option '%s' is not supported on this target host" % command) | ||
|
||
if data_type and data_type not in operations.get('data_type', []): | ||
module.fail_json(msg="data_type option '%s' is not supported on this target host" % data_type) | ||
|
||
result = {'changed': False} | ||
|
||
try: | ||
if command: | ||
response, err = run_cli(module, command, display) | ||
else: | ||
response, err = get(module, section, data_type) | ||
|
||
try: | ||
output = module.from_json(response) | ||
except ValueError: | ||
output = None | ||
except ConnectionError as exc: | ||
module.fail_json(msg=to_text(exc, errors='surrogate_then_replace'), code=exc.code) | ||
|
||
result['stdout'] = response | ||
|
||
if output: | ||
result['output'] = to_list(output) | ||
|
||
module.exit_json(**result) | ||
|
||
|
||
if __name__ == '__main__': | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
# (c) 2019 Ansible Project | ||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) | ||
# | ||
|
||
from __future__ import (absolute_import, division, print_function) | ||
__metaclass__ = type | ||
|
||
from ansible.plugins import AnsiblePlugin | ||
|
||
|
||
class GrpcBase(AnsiblePlugin): | ||
""" | ||
A base class for implementing gRPC abstraction layer | ||
""" | ||
|
||
__rpc__ = ['channel', 'get_config', 'edit_config', 'get'] | ||
|
||
def __init__(self, connection): | ||
super(GrpcBase, self).__init__() | ||
self._connection = connection | ||
|
||
@property | ||
def channel(self): | ||
return self._connection._channel | ||
|
||
def get_config(self, section=None): | ||
""" | ||
Retrieve all or part of a specified configuration | ||
(by default entire configuration is retrieved). | ||
:param section: This argument specifies the portion of the configuration data to retrieve | ||
:return: Returns the response received from gRPC server from target host in string format | ||
""" | ||
pass | ||
|
||
def get(self, section=None): | ||
Qalthos marked this conversation as resolved.
Show resolved
Hide resolved
|
||
""" | ||
Retrieve device state information. | ||
:param section: This argument specifies the portion of the state data to retrieve | ||
(by default entire state data is retrieved) | ||
:return: Returns the json string as a response | ||
""" | ||
pass | ||
|
||
def edit_config(self, config=None, action=None): | ||
Qalthos marked this conversation as resolved.
Show resolved
Hide resolved
|
||
""" | ||
Loads all or part of the specified *config* to the configuration datastore. | ||
:param config: The configuration that needs to be push on target host | ||
:param action: The action to be performed on the configuration datastore for example: 'merge', | ||
'replace', 'delete' etc. | ||
:return: Returns the response received from gRPC server from target host in string format | ||
""" | ||
pass |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The grpc protocol does not define any specific api's or RPC as in the case of netconf and restconf. The gRPC api's support for any platform is defined in the protobuf files, that's the reason these methods are not defined as
abstractmethod
as some of the platform may not have the need to implement these api'sThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess if these methods aren't guaranteed to exist, then why present them in the base class at all? What is the benefit of having this method exist but be unimplemented on an implementation that doesn't use it?