diff --git a/ansible/plugins/action/onie.py b/ansible/plugins/action/onie.py new file mode 100644 index 00000000000..bedaaa70833 --- /dev/null +++ b/ansible/plugins/action/onie.py @@ -0,0 +1,49 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.plugins.action import ActionBase +from ansible.utils.boolean import boolean +from ansible.utils.unicode import to_unicode + +import ast + +class ActionModule(ActionBase): + + def run(self, tmp=None, task_vars=None): + if task_vars is None: + task_vars = dict() + + self._display.vvv('ActionModule run') + + result = super(ActionModule, self).run(tmp, task_vars) + + _template = self._task.args.get('template', None) + _host = self._task.args.get('host', None) + _install = boolean(self._task.args.get('install', 'no')) + _url = self._task.args.get('url', None) + _timeout = self._task.args.get('timeout', None) + + if _timeout is None: + _timeout = 300 + + if _template is not None: + if self._task._role is not None: + _template = self._loader.path_dwim_relative(self._task._role._role_path, 'templates', _template) + else: + _template = self._loader.path_dwim_relative(self._loader.get_basedir(), 'templates', _template) + + f = open(_template, 'r') + template_data = to_unicode(f.read()) + f.close() + + _template = self._templar.template(template_data) + + self._display.vvv(self._connection.transport) + result['stdout'] = self._connection.exec_command(template=_template, + host=_host, + url=_url, + install=_install, + timeout=_timeout) + + return result + diff --git a/ansible/plugins/connection/onie.py b/ansible/plugins/connection/onie.py new file mode 100644 index 00000000000..3cf430fe7ae --- /dev/null +++ b/ansible/plugins/connection/onie.py @@ -0,0 +1,125 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import subprocess +import shlex +import pipes +import pexpect +import random +import select +import fcntl +import pwd +import time + +from ansible import constants as C +from ansible.errors import AnsibleError, AnsibleConnectionFailure, AnsibleFileNotFound +from ansible.plugins.connection import ConnectionBase + +class Connection(ConnectionBase): + ''' ssh based connections with expect ''' + + def __init__(self, *args, **kwargs): + super(Connection, self).__init__(*args, **kwargs) + + self.host = self._play_context.remote_addr + self.connection_retry_interval = 60 + + @property + def transport(self): + ''' used to identify this connection object from other classes ''' + return 'onie' + + # The connection is created by running expect from the exec_command, so we don't + # need to do any connection management here. + + def _connect(self): + self._connect = True + return self + + def _build_command(self): + self._ssh_command = ['ssh', '-tt', '-q'] + ansible_ssh_args = C.ANSIBLE_SSH_ARGS + if ansible_ssh_args: + self._ssh_command += shlex.split(ansible_ssh_args) + else: + self._ssh_command += ['-o', 'ControlMaster=auto', + '-o', 'ControlPersist=60s', + '-o', 'ControlPath=/tmp/ansible-ssh-%h-%p-%r'] + + if not C.HOST_KEY_CHECKING: + self._ssh_command += ['-o', 'StrictHostKeyChecking=no'] + self._ssh_command += ['-o', 'UserKnownHostsFile=/dev/null'] + + self._ssh_command += ['-o', 'GSSAPIAuthentication=no', + '-o', 'PubkeyAuthentication=no'] + self._ssh_command += ['-o', 'ConnectTimeout=30'] + + def _spawn_connect(self): + client = None + + cmd = self._ssh_command + ['-l', "root", self.host] + client = pexpect.spawn(' '.join(cmd), env={'TERM': 'dumb'}) + client.expect(['#']) + + self.before_backup = client.before.split() + + return client + + def exec_command(self, *args, **kwargs): + + self.template = kwargs['template'] + if kwargs['host'] is not None: + self.host = kwargs['host'] + self.url = kwargs['url'] + self.install = kwargs['install'] + + self._build_command() + + client = self._spawn_connect() + + # Set command timeout after connection is spawned + if kwargs['timeout']: + client.timeout = int(kwargs['timeout']) + + prompts = ["ONIE:.+ #", pexpect.EOF] + + stdout = "" + if self.template: + cmds = self.template.split('\n') + else: + cmds = [] + for cmd in cmds: + self._display.vvv('> %s' % (cmd), host=self.host) + client.sendline(cmd) + client.expect(prompts) + + stdout += client.before + self._display.vvv('< %s' % (client.before), host=self.host) + + if self.install: + client.sendline('onie-discovery-stop') + client.expect(prompts) + stdout += client.before + client.sendline("onie-nos-install %s" % self.url) + i = client.expect(["Installed SONiC base image SONiC-OS successfully"] + prompts) + stdout += client.before + if i != 0: + raise AnsibleError("Failed to install sonic image. %s" % stdout) + self._display.vvv("SONiC installed.", host=self.host) + # for some platform, e.g., DELL S6000, it will do hard reboot, + # which will not give EOF + client.expect([pexpect.EOF, pexpect.TIMEOUT], timeout=15) + stdout += client.before + self._display.vvv("ONIE Rebooted. %s" % stdout, host=self.host) + + return stdout + + def put_file(self, in_path, out_path): + pass + + def fetch_file(self, in_path, out_path): + pass + + def close(self): + self._connected = False diff --git a/ansible/upgrade_sonic.yml b/ansible/upgrade_sonic.yml index 02f7ab5cb35..c5191a3d2df 100644 --- a/ansible/upgrade_sonic.yml +++ b/ansible/upgrade_sonic.yml @@ -9,10 +9,6 @@ gather_facts: no tasks: - - name: Gather minigraph facts for the switch {{ inventory_hostname }} - minigraph_facts: host={{ inventory_hostname }} - tags: always - - set_fact: real_ansible_host: "{{ ansible_ssh_host }}" @@ -39,29 +35,9 @@ timeout: 300 changed_when: false - - name: Stop onie discovery - shell: ssh root@{{ real_ansible_host }} '/bin/ash -ilc "onie-discovery-stop"' - delegate_to: 127.0.0.1 - - # since onie install will reboot the box, the shell command cannot exit in synchronized mode. - # Now, use async mode and set a maximum wait period, the next task will wait for installation - # to finish and switch to reboot - - name: Install the target image from {{ image_url }} - shell: ssh root@{{ real_ansible_host }} '/bin/ash -ilc "onie-nos-install {{ image_url }}"' - delegate_to: 127.0.0.1 - async: 60 - poll: 5 - ignore_errors: true - - - name: Wait for switch to reboot again (ONIE installation finishes) - local_action: wait_for - args: - host: "{{ real_ansible_host }}" - port: 22 - state: stopped - delay: 5 - timeout: 600 - changed_when: false + - name: Install SONiC image in ONIE + action: onie install=yes url={{ image_url }} + connection: onie when: upgrade_type == "onie"