diff --git a/requirements.txt b/requirements.txt index e628e769b..9ba16e77c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -39,3 +39,4 @@ python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0 tosca-parser>=0.5.0 # Apache-2.0 heat-translator>=0.4.0 # Apache-2.0 cryptography!=1.3.0,>=1.0 # BSD/Apache-2.0 +paramiko>=2.0 # LGPLv2.1+ diff --git a/tacker/common/cmd_executer.py b/tacker/common/cmd_executer.py new file mode 100644 index 000000000..7b5d09dbf --- /dev/null +++ b/tacker/common/cmd_executer.py @@ -0,0 +1,107 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from oslo_log import log as logging +import paramiko + +from tacker.common.exceptions import NotAuthorized + +LOG = logging.getLogger(__name__) + + +class CommandResult(object): + """Result class contains command, stdout, stderror and return code.""" + def __init__(self, cmd, stdout, stderr, return_code): + self.__cmd = cmd + self.__stdout = stdout + self.__stderr = stderr + self.__return_code = return_code + + def get_command(self): + return self.__cmd + + def get_stdout(self): + return self.__stdout + + def get_stderr(self): + return self.__stderr + + def get_return_code(self): + return self.__return_code + + def __str__(self): + return "cmd: %s, stdout: %s, stderr: %s, return code: %s" \ + % (self.__cmd, self.__stdout, self.__stderr, self.__return_code) + + def __repr__(self): + return "cmd: %s, stdout: %s, stderr: %s, return code: %s" \ + % (self.__cmd, self.__stdout, self.__stderr, self.__return_code) + + +class RemoteCommandExecutor(object): + """Class to execute a command on remote location""" + def __init__(self, user, password, host, timeout=10): + self.__user = user + self.__password = password + self.__host = host + self.__paramiko_conn = None + self.__ssh = None + self.__timeout = timeout + self.__connect() + + def __connect(self): + try: + self.__ssh = paramiko.SSHClient() + self.__ssh.set_missing_host_key_policy(paramiko.WarningPolicy()) + self.__ssh.connect(self.__host, username=self.__user, + password=self.__password, timeout=self.__timeout) + LOG.info(_("Connected to %s") % self.__host) + except paramiko.AuthenticationException: + LOG.error(_("Authentication failed when connecting to %s") + % self.__host) + raise NotAuthorized + except paramiko.SSHException: + LOG.error(_("Could not connect to %s. Giving up") % self.__host) + raise + + def close_session(self): + self.__ssh.close() + LOG.debug(_("Connection close")) + + def execute_command(self, cmd, input_data=None): + try: + stdin, stdout, stderr = self.__ssh.exec_command(cmd) + if input_data: + stdin.write(input_data) + LOG.debug(_("Input data written successfuly")) + stdin.flush() + LOG.debug(_("Indput data flushed")) + stdin.channel.shutdown_write() + + # NOTE (dkushwaha): There might be a case, when server can take + # too long time to write data in stdout buffer or sometimes hang + # itself, in that case readlines() will stuck for long/infinite + # time. To handle such cases, timeout logic should be introduce + # here. + cmd_out = stdout.readlines() + cmd_err = stderr.readlines() + return_code = stdout.channel.recv_exit_status() + except paramiko.SSHException: + LOG.error(_("Command execution failed at %s. Giving up") + % self.__host) + raise + result = CommandResult(cmd, cmd_out, cmd_err, return_code) + LOG.debug(_("Remote command execution result: %s"), result) + return result + + def __del__(self): + self.close_session() diff --git a/tacker/common/exceptions.py b/tacker/common/exceptions.py index 04526f7c2..fc2d39130 100644 --- a/tacker/common/exceptions.py +++ b/tacker/common/exceptions.py @@ -239,3 +239,7 @@ class DeviceIDNotOwnedByTenant(Conflict): class InvalidCIDR(BadRequest): message = _("Invalid CIDR %(input)s given as IP prefix") + + +class MgmtDriverException(TackerException): + message = _("VNF configuration failed") diff --git a/tacker/vm/mgmt_drivers/openwrt/openwrt.py b/tacker/vm/mgmt_drivers/openwrt/openwrt.py index fbcbb89f9..b5043619a 100644 --- a/tacker/vm/mgmt_drivers/openwrt/openwrt.py +++ b/tacker/vm/mgmt_drivers/openwrt/openwrt.py @@ -19,7 +19,8 @@ from oslo_log import log as logging from oslo_serialization import jsonutils import yaml -from tacker.agent.linux import utils +from tacker.common import cmd_executer +from tacker.common.exceptions import MgmtDriverException from tacker.common import log from tacker.vm.mgmt_drivers import abstract_driver from tacker.vm.mgmt_drivers import constants as mgmt_constants @@ -51,11 +52,16 @@ class DeviceMgmtOpenWRT(abstract_driver.DeviceMGMTAbstractDriver): def _config_service(self, mgmt_ip_address, service, config): user = cfg.CONF.openwrt.user password = cfg.CONF.openwrt.password - cmd = ["sshpass", "-p", "%s" % password, - "ssh", "-o", "StrictHostKeyChecking=no", - "%s@%s" % (user, mgmt_ip_address), - "uci import %s; /etc/init.d/%s restart" % (service, service)] - utils.execute(cmd, process_input=config) + try: + cmd = "uci import %s; /etc/init.d/%s restart" % (service, service) + LOG.debug(_('execute command: %s'), (cmd)) + commander = cmd_executer.RemoteCommandExecutor(user, + password, + mgmt_ip_address) + commander.execute_command(cmd, input_data=config) + except Exception as ex: + LOG.error(_("While executing command on remote: %s"), ex) + raise MgmtDriverException() @log.log def mgmt_call(self, plugin, context, device, kwargs): diff --git a/tacker/vm/plugin.py b/tacker/vm/plugin.py index 8afcad2ec..d59101b53 100644 --- a/tacker/vm/plugin.py +++ b/tacker/vm/plugin.py @@ -25,6 +25,7 @@ from oslo_utils import excutils from tacker.api.v1 import attributes from tacker.common import driver_manager +from tacker.common.exceptions import MgmtDriverException from tacker.db.vm import vm_db from tacker.extensions import vnfm from tacker.i18n import _LE @@ -231,11 +232,11 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin): new_status = constants.ACTIVE try: self.mgmt_call(context, device_dict, kwargs) - except Exception: - LOG.exception(_('create_device_wait')) + except MgmtDriverException: + LOG.error(_('VNF configuration failed')) new_status = constants.ERROR self.set_device_error_status_reason(context, device_id, - 'Unable to configure VDU') + 'Unable to configure VDU') device_dict['status'] = new_status self._create_device_status(context, device_id, new_status) @@ -307,8 +308,8 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin): context=context, device_id=instance_id, auth_attr=vim_auth, region_name=region_name) self.mgmt_call(context, device_dict, kwargs) - except Exception as e: - LOG.exception(_('_update_device_wait')) + except MgmtDriverException as e: + LOG.error(_('VNF configuration failed')) new_status = constants.ERROR self.set_device_error_status_reason(context, device_dict['id'], six.text_type(e))