diff --git a/lib/ansible/module_utils/network/cloudengine/ce.py b/lib/ansible/module_utils/network/cloudengine/ce.py
index beae8185f15..9b813931409 100644
--- a/lib/ansible/module_utils/network/cloudengine/ce.py
+++ b/lib/ansible/module_utils/network/cloudengine/ce.py
@@ -38,13 +38,11 @@ from ansible.module_utils.network.common.utils import to_list, ComplexList
from ansible.module_utils.connection import exec_command
from ansible.module_utils.six import iteritems
from ansible.module_utils._text import to_native
+from ansible.module_utils.network.common.netconf import NetconfConnection
try:
- from ncclient import manager, xml_
- from ncclient.operations.rpc import RPCError
- from ncclient.transport.errors import AuthenticationError
- from ncclient.operations.errors import TimeoutExpiredError
+ from ncclient.xml_ import to_xml
HAS_NCCLIENT = True
except ImportError:
HAS_NCCLIENT = False
@@ -58,10 +56,11 @@ ce_provider_spec = {
'port': dict(type='int'),
'username': dict(fallback=(env_fallback, ['ANSIBLE_NET_USERNAME'])),
'password': dict(fallback=(env_fallback, ['ANSIBLE_NET_PASSWORD']), no_log=True),
+ 'ssh_keyfile': dict(fallback=(env_fallback, ['ANSIBLE_NET_SSH_KEYFILE']), type='path'),
'use_ssl': dict(type='bool'),
'validate_certs': dict(type='bool'),
'timeout': dict(type='int'),
- 'transport': dict(default='cli', choices=['cli']),
+ 'transport': dict(default='cli', choices=['cli', 'netconf']),
}
ce_argument_spec = {
'provider': dict(type='dict', options=ce_provider_spec),
@@ -71,19 +70,25 @@ ce_top_spec = {
'port': dict(removed_in_version=2.9, type='int'),
'username': dict(removed_in_version=2.9),
'password': dict(removed_in_version=2.9, no_log=True),
+ 'ssh_keyfile': dict(removed_in_version=2.9, type='path'),
'use_ssl': dict(removed_in_version=2.9, type='bool'),
'validate_certs': dict(removed_in_version=2.9, type='bool'),
'timeout': dict(removed_in_version=2.9, type='int'),
- 'transport': dict(removed_in_version=2.9, choices=['cli']),
+ 'transport': dict(removed_in_version=2.9, choices=['cli', 'netconf']),
}
ce_argument_spec.update(ce_top_spec)
+def to_string(data):
+ return re.sub(r'|>)', r'" in con_obj_next.xml:
- break
-
- # merge two xml data
- xml_str = merge_nc_xml(xml_str, con_obj_next.xml)
- set_id = get_nc_set_id(con_obj_next.xml)
-
- return xml_str
-
- def execute_action(self, xml_str):
- """huawei execute-action"""
-
- con_obj = None
-
- try:
- con_obj = self.mc.action(action=xml_str)
- except RPCError as err:
- self._module.fail_json(msg='Error: %s' % to_native(err).replace("\r\n", ""))
- except TimeoutExpiredError:
- raise
-
- return con_obj.xml
-
- def execute_cli(self, xml_str):
- """huawei execute-cli"""
-
- con_obj = None
-
- try:
- con_obj = self.mc.cli(command=xml_str)
- except RPCError as err:
- self._module.fail_json(msg='Error: %s' % to_native(err).replace("\r\n", ""))
-
- return con_obj.xml
-
-
def get_nc_connection(module):
global _DEVICE_NC_CONNECTION
if not _DEVICE_NC_CONNECTION:
load_params(module)
- conn = Netconf(module)
+ conn = NetconfConnection(module._socket_path)
_DEVICE_NC_CONNECTION = conn
return _DEVICE_NC_CONNECTION
@@ -432,28 +335,45 @@ def set_nc_config(module, xml_str):
""" set_config """
conn = get_nc_connection(module)
- return conn.set_config(xml_str)
+ try:
+ out = conn.edit_config(target='running', config=xml_str, default_operation='merge',
+ error_option='rollback-on-error')
+ finally:
+ # conn.unlock(target = 'candidate')
+ pass
+ return to_string(to_xml(out))
def get_nc_config(module, xml_str):
""" get_config """
conn = get_nc_connection(module)
- return conn.get_config(xml_str)
+ if xml_str is not None:
+ response = conn.get(xml_str)
+ else:
+ return None
+
+ return to_string(to_xml(response))
def execute_nc_action(module, xml_str):
""" huawei execute-action """
conn = get_nc_connection(module)
- return conn.execute_action(xml_str)
+ response = conn.execute_action(xml_str)
+ return to_string(to_xml(response))
def execute_nc_cli(module, xml_str):
""" huawei execute-cli """
- conn = get_nc_connection(module)
- return conn.execute_cli(xml_str)
+ if xml_str is not None:
+ try:
+ conn = get_nc_connection(module)
+ out = conn.execute_nc_cli(command=xml_str)
+ return to_string(to_xml(out))
+ except Exception as exc:
+ raise Exception(exc)
def check_ip_addr(ipaddr):
diff --git a/lib/ansible/plugins/action/ce.py b/lib/ansible/plugins/action/ce.py
index f896af43692..5ea86fdfc3b 100644
--- a/lib/ansible/plugins/action/ce.py
+++ b/lib/ansible/plugins/action/ce.py
@@ -36,51 +36,70 @@ except ImportError:
from ansible.utils.display import Display
display = Display()
+CLI_SUPPORTED_MODULES = ['ce_config', 'ce_command']
+
class ActionModule(_ActionModule):
def run(self, tmp=None, task_vars=None):
del tmp # tmp no longer has any effect
- if self._play_context.connection != 'local':
- return dict(
- failed=True,
- msg='invalid connection specified, expected connection=local, '
- 'got %s' % self._play_context.connection
- )
-
- provider = load_provider(ce_provider_spec, self._task.args)
- transport = provider['transport'] or 'cli'
-
- display.vvvv('connection transport is %s' % transport, self._play_context.remote_addr)
-
- if transport == 'cli':
- pc = copy.deepcopy(self._play_context)
- pc.connection = 'network_cli'
- pc.network_os = 'ce'
- pc.remote_addr = provider['host'] or self._play_context.remote_addr
- pc.port = int(provider['port'] or self._play_context.port or 22)
- pc.remote_user = provider['username'] or self._play_context.connection_user
- pc.password = provider['password'] or self._play_context.password
- pc.timeout = int(provider['timeout'] or C.PERSISTENT_COMMAND_TIMEOUT)
- self._task.args['provider'] = provider.update(
- host=pc.remote_addr,
- port=pc.port,
- username=pc.remote_user,
- password=pc.password
- )
- display.vvv('using connection plugin %s (was local)' % pc.connection, pc.remote_addr)
- connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin)
-
- socket_path = connection.run()
- display.vvvv('socket_path: %s' % socket_path, pc.remote_addr)
- if not socket_path:
- return {'failed': True,
- 'msg': 'unable to open shell. Please see: ' +
- 'https://docs.ansible.com/ansible/network_debug_troubleshooting.html#unable-to-open-shell'}
-
- # make sure we are in the right cli context which should be
+ socket_path = None
+
+ if self._play_context.connection == 'local':
+ provider = load_provider(ce_provider_spec, self._task.args)
+ transport = provider['transport'] or 'cli'
+
+ display.vvvv('connection transport is %s' % transport, self._play_context.remote_addr)
+
+ if transport == 'cli':
+ pc = copy.deepcopy(self._play_context)
+ pc.connection = 'network_cli'
+ pc.network_os = 'ce'
+ pc.remote_addr = provider['host'] or self._play_context.remote_addr
+ pc.port = int(provider['port'] or self._play_context.port or 22)
+ pc.remote_user = provider['username'] or self._play_context.connection_user
+ pc.password = provider['password'] or self._play_context.password
+ pc.timeout = int(provider['timeout'] or C.PERSISTENT_COMMAND_TIMEOUT)
+ self._task.args['provider'] = provider.update(
+ host=pc.remote_addr,
+ port=pc.port,
+ username=pc.remote_user,
+ password=pc.password
+ )
+ if self._task.action in ['ce_netconf'] or self._task.action not in CLI_SUPPORTED_MODULES:
+ pc.connection = 'netconf'
+ display.vvv('using connection plugin %s (was local)' % pc.connection, pc.remote_addr)
+ connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin)
+
+ socket_path = connection.run()
+ display.vvvv('socket_path: %s' % socket_path, pc.remote_addr)
+ if not socket_path:
+ return {'failed': True,
+ 'msg': 'unable to open shell. Please see: ' +
+ 'https://docs.ansible.com/ansible/network_debug_troubleshooting.html#unable-to-open-shell'}
+
+ task_vars['ansible_socket'] = socket_path
+ # make sure a transport value is set in args
+ self._task.args['transport'] = transport
+ self._task.args['provider'] = provider
+ elif self._play_context.connection in ('netconf', 'network_cli'):
+ provider = self._task.args.get('provider', {})
+ if any(provider.values()):
+ display.warning('provider is unnessary whene using %s and will be ignored' % self._play_context.connection)
+ del self._task.args['provider']
+
+ if (self._play_context.connection == 'network_cli' and self._task.action not in CLI_SUPPORTED_MODULES) or \
+ (self._play_context.connection == 'netconf' and self._task.action in CLI_SUPPORTED_MODULES):
+ return {'failed': True, 'msg': "Connection type '%s' is not valid for '%s' module."
+ % (self._play_context.connection, self._task.action)}
+
+ if (self._play_context.connection == 'local' and transport == 'cli' and self._task.action in CLI_SUPPORTED_MODULES) \
+ or self._play_context.connection == 'network_cli':
+ # make sure we are in the right cli context whitch should be
# enable mode and not config module
+ if socket_path is None:
+ socket_path = self._connection.socket_path
conn = Connection(socket_path)
out = conn.get_prompt()
while to_text(out, errors='surrogate_then_replace').strip().endswith(']'):
@@ -88,11 +107,5 @@ class ActionModule(_ActionModule):
conn.send_command('exit')
out = conn.get_prompt()
- task_vars['ansible_socket'] = socket_path
-
- # make sure a transport value is set in args
- self._task.args['transport'] = transport
- self._task.args['provider'] = provider
-
result = super(ActionModule, self).run(task_vars=task_vars)
return result
diff --git a/lib/ansible/plugins/cliconf/ce.py b/lib/ansible/plugins/cliconf/ce.py
new file mode 100644
index 00000000000..1dea8bf9fa9
--- /dev/null
+++ b/lib/ansible/plugins/cliconf/ce.py
@@ -0,0 +1,93 @@
+#
+# (c) 2017 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 .
+#
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import re
+import json
+
+from itertools import chain
+
+from ansible.module_utils._text import to_text
+from ansible.module_utils.network.common.utils import to_list
+from ansible.plugins.cliconf import CliconfBase, enable_mode
+
+
+class Cliconf(CliconfBase):
+
+ def get_device_info(self):
+ device_info = {}
+
+ device_info['network_os'] = 'ce'
+ reply = self.get(b'display version')
+ data = to_text(reply, errors='surrogate_or_strict').strip()
+
+ match = re.search(r'^Huawei.+\n.+\Version\s+(\S+)', data)
+ if match:
+ device_info['network_os_version'] = match.group(1).strip(',')
+
+ match = re.search(r'^Huawei(.+)\n.+\(\S+\s+\S+\)', data, re.M)
+ if match:
+ device_info['network_os_model'] = match.group(1)
+
+ match = re.search(r'HUAWEI\s+(\S+)\s+uptime', data, re.M)
+ if match:
+ device_info['network_os_hostname'] = match.group(1)
+
+ return device_info
+
+ @enable_mode
+ def get_config(self, source='running', format='text', flags=None):
+ if source not in ('running'):
+ return self.invalid_params("fetching configuration from %s is not supported" % source)
+
+ if not flags:
+ flags = []
+
+ cmd = 'display current-configuration'
+
+ return self.send_command(cmd)
+
+ @enable_mode
+ def edit_config(self, command):
+ results = []
+ for cmd in chain(['configure terminal'], to_list(command), ['end']):
+ if isinstance(cmd, dict):
+ command = cmd['command']
+ prompt = cmd['prompt']
+ answer = cmd['answer']
+ newline = cmd.get('newline', True)
+ else:
+ command = cmd
+ prompt = None
+ answer = None
+ newline = True
+
+ results.append(self.send_command(command, prompt, answer, False, newline))
+ return results[1:-1]
+
+ def get(self, command, prompt=None, answer=None, sendonly=False):
+ return self.send_command(command, prompt=prompt, answer=answer, sendonly=sendonly)
+
+ def get_capabilities(self):
+ result = {}
+ result['rpc'] = self.get_base_rpc()
+ result['network_api'] = 'cliconf'
+ result['device_info'] = self.get_device_info()
+ return json.dumps(result)
diff --git a/lib/ansible/plugins/connection/netconf.py b/lib/ansible/plugins/connection/netconf.py
index abd3989fb43..e3a096d980d 100644
--- a/lib/ansible/plugins/connection/netconf.py
+++ b/lib/ansible/plugins/connection/netconf.py
@@ -160,7 +160,8 @@ logging.getLogger('ncclient').setLevel(logging.INFO)
NETWORK_OS_DEVICE_PARAM_MAP = {
"nxos": "nexus",
"ios": "default",
- "sros": "alu"
+ "sros": "alu",
+ "ce": "huawei"
}
diff --git a/lib/ansible/plugins/netconf/ce.py b/lib/ansible/plugins/netconf/ce.py
new file mode 100644
index 00000000000..b78d4b97694
--- /dev/null
+++ b/lib/ansible/plugins/netconf/ce.py
@@ -0,0 +1,217 @@
+#
+# (c) 2017 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 .
+#
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import json
+import re
+
+from ansible import constants as C
+from ansible.module_utils._text import to_text, to_bytes
+from ansible.errors import AnsibleConnectionFailure, AnsibleError
+from ansible.plugins.netconf import NetconfBase
+from ansible.plugins.netconf import ensure_connected
+
+try:
+ from ncclient import manager
+ from ncclient.operations import RPCError
+ from ncclient.transport.errors import SSHUnknownHostError
+ from ncclient.xml_ import to_ele, to_xml, new_ele
+except ImportError:
+ raise AnsibleError("ncclient is not installed")
+
+try:
+ from __main__ import display
+except ImportError:
+ from ansible.utils.display import Display
+ display = Display()
+
+
+class Netconf(NetconfBase):
+
+ def get_text(self, ele, tag):
+ try:
+ return to_text(ele.find(tag).text, errors='surrogate_then_replace').strip()
+ except AttributeError:
+ pass
+
+ def get_device_info(self):
+ device_info = dict()
+ device_info['network_os'] = 'ce'
+ ele = new_ele('get-software-information')
+ data = self.execute_rpc(to_xml(ele))
+ reply = to_ele(to_bytes(data, errors='surrogate_or_strict'))
+ sw_info = reply.find('.//software-information')
+
+ device_info['network_os_version'] = self.get_text(sw_info, 'ce-version')
+ device_info['network_os_hostname'] = self.get_text(sw_info, 'host-name')
+ device_info['network_os_model'] = self.get_text(sw_info, 'product-model')
+
+ return device_info
+
+ @ensure_connected
+ def execute_rpc(self, name):
+ """RPC to be execute on remote device
+ :name: Name of rpc in string format"""
+ return self.rpc(name)
+
+ @ensure_connected
+ def load_configuration(self, *args, **kwargs):
+ """Loads given configuration on device
+ :format: Format of configuration (xml, text, set)
+ :action: Action to be performed (merge, replace, override, update)
+ :target: is the name of the configuration datastore being edited
+ :config: is the configuration in string format."""
+ if kwargs.get('config'):
+ kwargs['config'] = to_bytes(kwargs['config'], errors='surrogate_or_strict')
+ if kwargs.get('format', 'xml') == 'xml':
+ kwargs['config'] = to_ele(kwargs['config'])
+
+ try:
+ return self.m.load_configuration(*args, **kwargs).data_xml
+ except RPCError as exc:
+ raise Exception(to_xml(exc.xml))
+
+ def get_capabilities(self):
+ result = dict()
+ result['rpc'] = self.get_base_rpc() + ['commit', 'discard_changes', 'validate', 'lock', 'unlock', 'copy_copy',
+ 'execute_rpc', 'load_configuration', 'get_configuration', 'command',
+ 'reboot', 'halt']
+ result['network_api'] = 'netconf'
+ result['device_info'] = self.get_device_info()
+ result['server_capabilities'] = [c for c in self.m.server_capabilities]
+ result['client_capabilities'] = [c for c in self.m.client_capabilities]
+ result['session_id'] = self.m.session_id
+ return json.dumps(result)
+
+ @staticmethod
+ def guess_network_os(obj):
+
+ try:
+ m = manager.connect(
+ host=obj._play_context.remote_addr,
+ port=obj._play_context.port or 830,
+ username=obj._play_context.remote_user,
+ password=obj._play_context.password,
+ key_filename=obj._play_context.private_key_file,
+ hostkey_verify=C.HOST_KEY_CHECKING,
+ look_for_keys=C.PARAMIKO_LOOK_FOR_KEYS,
+ allow_agent=obj._play_context.allow_agent,
+ timeout=obj._play_context.timeout
+ )
+ except SSHUnknownHostError as exc:
+ raise AnsibleConnectionFailure(str(exc))
+
+ guessed_os = None
+ for c in m.server_capabilities:
+ if re.search('huawei', c):
+ guessed_os = 'ce'
+ break
+
+ m.close_session()
+ return guessed_os
+
+ @ensure_connected
+ def get_configuration(self, *args, **kwargs):
+ """Retrieve all or part of a specified configuration.
+ :format: format in configuration should be retrieved
+ :filter: specifies the portion of the configuration to retrieve
+ (by default entire configuration is retrieved)"""
+ return self.m.get_configuration(*args, **kwargs).data_xml
+
+ @ensure_connected
+ def compare_configuration(self, *args, **kwargs):
+ """Compare configuration
+ :rollback: rollback id"""
+ return self.m.compare_configuration(*args, **kwargs).data_xml
+
+ @ensure_connected
+ def execute_action(self, xml_str):
+ """huawei execute-action"""
+ con_obj = None
+ try:
+ con_obj = self.m.action(action=xml_str)
+ except RPCError as exc:
+ raise Exception(to_xml(exc.xml))
+
+ return con_obj.xml
+
+ @ensure_connected
+ def halt(self):
+ """reboot the device"""
+ return self.m.halt().data_xml
+
+ @ensure_connected
+ def reboot(self):
+ """reboot the device"""
+ return self.m.reboot().data_xml
+
+ @ensure_connected
+ def halt(self):
+ """reboot the device"""
+ return self.m.halt().data_xml
+
+ @ensure_connected
+ def get(self, *args, **kwargs):
+ try:
+ return self.m.get(*args, **kwargs).data_xml
+ except RPCError as exc:
+ raise Exception(to_xml(exc.xml))
+
+ @ensure_connected
+ def get_config(self, *args, **kwargs):
+ try:
+ return self.m.get_config(*args, **kwargs).data_xml
+ except RPCError as exc:
+ raise Exception(to_xml(exc.xml))
+
+ @ensure_connected
+ def edit_config(self, *args, **kwargs):
+ try:
+ return self.m.edit_config(*args, **kwargs).xml
+ except RPCError as exc:
+ raise Exception(to_xml(exc.xml))
+
+ @ensure_connected
+ def execute_nc_cli(self, *args, **kwargs):
+ try:
+ return self.m.cli(*args, **kwargs).xml
+ except RPCError as exc:
+ raise Exception(to_xml(exc.xml))
+
+ @ensure_connected
+ def commit(self, *args, **kwargs):
+ try:
+ return self.m.commit(*args, **kwargs).data_xml
+ except RPCError as exc:
+ raise Exception(to_xml(exc.xml))
+
+ @ensure_connected
+ def validate(self, *args, **kwargs):
+ return self.m.validate(*args, **kwargs).data_xml
+
+ @ensure_connected
+ def discard_changes(self, *args, **kwargs):
+ return self.m.discard_changes(*args, **kwargs).data_xml
+
+ @ensure_connected
+ def execute_rpc(self, name):
+ """RPC to be execute on remote device
+ :name: Name of rpc in string format"""
+ return self.rpc(name)