четверг, 23 января 2014 г.

Automating the connection to the SPICE console in Fedora 20 with Python Pexpect module

We're going to discuss the script that would make a connection to SPICE server (CentOS 6.5 with oVirt 3.3 all-in-one plugin in my case) from recently released Fedora 20 more convenient. 
Usually I get access to the virtual machine using the SPICE xpi plugin for Mozilla Firefox but for some unknown reason it had crashed every time I tried to start SPICE session from oVirt user portal (I assume that spice-xpi-2.8.90-1.fc20.x86_64 and firefox-26.0-3.fc20.x86_64 can be incompatible).

So we need a workaround here. oVirt proposes several ways to connect to the SPICE console. The first  and the clearest of them didn't work in my case, thus I decided to automatize the second one that also called "manual". This operation requires the ovirt-shell and virt-viewer utilities. 

First of all, make sure that you have already installed the ones:

yum install virt-viewer ovirt-engine-cli -y

Than you should prepare your ~/.ovirtshellrc file as proposed here. Here is my config (never store passwords in a such way if you care about security):

[cli]
autoconnect = True
autopage = True
[ovirt-shell]
username = admin@<your domain, "internal" by default>
timeout = None
extended_prompt = False
url = https://<your host's fqdn>:443/api
insecure = False
filter = False
session_timeout = None
ca_file = /home/<you>/ca.crt
dont_validate_cert_chain = False
key_file = None
cert_file = None
password = <your_pass>

Don't forget to download certificate from oVirt server Certificate Authority and place it into you homedir:

wget -O ~/ca.crt http://<your host's fqdn>/ca.crt

Now let's met the ovirt-shell. Run it with a -c key and it will read the parameters from the .ovirtshellrc config:

[vitaly@asus ~]$ ovirt-shell -c
 ==========================================
 >>> connected to oVirt manager 3.3.0.0 <<<
 ==========================================

        
 ++++++++++++++++++++++++++++++++++++++++++
 
           Welcome to oVirt shell
 
 ++++++++++++++++++++++++++++++++++++++++++       

Start the desired VM with `action vm ${vm_name} start` , than inspect it with `show vm ${vm_name}`:

[oVirt shell (connected)]# action vm worker start
status-state: complete
vm-id       : 827dc3fc-3631-4886-984a-decfbb3e189d

[oVirt shell (connected)]# show vm worker
<...>
display-port              : 5900                                     <-- you need this value
<...>
host-id                   : a7f81ab7-caa2-40de-9e40-cbe3d77c0542     <-- you need this value

Recieve one-time ticket for a SPICE session:
   
[oVirt shell (connected)]# action vm worker ticket
status-state : complete
ticket-expiry: 7200
ticket-value : QoFFUIuz+l+V                                          <-- you need this value

Inspect the host with a command `show host ${host-id}`:
   
[oVirt shell (connected)]# show host a7f81ab7-caa2-40de-9e40-cbe3d77c0542
<...>
certificate-subject               : O=vitaly.ru,CN=rhevaio.vitaly.ru <-- you need this value

Now you can put it all together into the final command. Close ovirt-shell and return to bash:
 
remote-viewer --spice-ca-file ~/ca.crt --spice-host-subject "${certificate-subject}" spice://${host fqdn}/?port=display-port>\&tls-port=${display-secure_port}

Then the remote-viewer window will pop up and you'll be asked to enter the ticket value. Agree that it took so long and required a sequence of actions with the CLI. Lets make things easier with expect shell alike Python module called Pexpect. Pexpect emulates user interaction with command shell and provides well-known python tools and data structures which are so lacking during the work in any other shell but ipython.

#!/usr/bin/env python
import pexpect
import sys
import re
import os
ca_crt = "~/ca.crt"

#This function receives the output from ovirt-shell, purges all the
#escape sequences and returns a dictionary. Please read the manuals about
#string class, re module and functional programming if you're not aware of
#them. 
def process_text(text):
    #Cleaning the escape sequences: 
    answer = map(lambda x: re.sub(':?\\x1b\[.?\\.?', '', x), text)
    #Creating the dictionary from strings peeled from extra gaps and splitted by the
    #first ':' symbol
    return dict([map(lambda x: x.strip(), i.split(':', 1)) for i in answer])


def handle_vm(vm_name):
    #Starting the new process
    child = pexpect.spawn("ovirt-shell -c")
    #Wait till shell will get ready for your input
    child.expect("#")

    #Starting the vm
    #\r emulates Enter key press
    child.send("action vm {0} start\r".format(vm_name)) 
    child.expect("#")

    #Getting info about the vm
    #f is equal to Page Down, q is the same as in `less` utility
    child.send("show vm {0}\rfq".format(vm_name))
    child.expect("#")
    vm_data = process_text(child.before.split('\n')[2:-2])

    #Getting info about host
    child.send("show host {0}\rfq".format(vm_data["host-id"]))
    child.expect("#")
    host_data = process_text(child.before.split('\n')[2:-2])

    #Getting the ticket
    child.sendline('action vm {0} ticket'.format(vm_name))
    child.expect("#")
    ticket_action = process_text(child.before.split('\n')[2:-2])

    #I prefer the subprocess module for this purposes usually, but for a some reason
    #it crashes the remote-viewer. Primitive os.system() call is better  
    cmd = ['remote-viewer',
           '--spice-ca-file', ca_crt, '--spice-host-subject',
           "'" + host_data['certificate-subject'] + "'",
           'spice://{0}/?port={1}\&tls-port={2}'.format(
           vm_data["display-address"], vm_data['display-port'],
           vm_data['display-secure_port'])]
    #You will have to copy this value to the remote-viewer window manually 
    print(ticket_action['ticket-value']) 
    os.system(" ".join(cmd) + " &")

if __name__ == "__main__":
    handle_vm(sys.argv[1])

Make your script executable and run it with a VM's name appended:
./spice_connect.py ${vm_name} 

Now copy the received password from shell to the password field and enjoy the virtualization.

I would like to thank my girlfriend Anna Gorodetskaya for the picturesque wallpaper that reflects the beauty of Espoo.