Skip to content

Commit

Permalink
Use connection plugin to install sonic image in ONIE. (#536)
Browse files Browse the repository at this point in the history
Signed-off-by: Guohan Lu <gulv@microsoft.com>
  • Loading branch information
lguohan authored Mar 27, 2018
1 parent 0d45743 commit 19222c2
Show file tree
Hide file tree
Showing 3 changed files with 177 additions and 27 deletions.
49 changes: 49 additions & 0 deletions ansible/plugins/action/onie.py
Original file line number Diff line number Diff line change
@@ -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

125 changes: 125 additions & 0 deletions ansible/plugins/connection/onie.py
Original file line number Diff line number Diff line change
@@ -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
30 changes: 3 additions & 27 deletions ansible/upgrade_sonic.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}"

Expand All @@ -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"

Expand Down

0 comments on commit 19222c2

Please sign in to comment.