Wednesday, December 18, 2013

Monday, August 12, 2013

SPICE: Improved UX On CentOS 5 KVM Guest

As you probably know, KVM is a great way to run virtual machines on a Linux host.  It may not be as refined as VMware Workstation, but it's very close.  And it's open source.

Not all of guest VM's need to provide a graphical desktop like Gnome.   But when they do, SPICE is one of the protocols you can use to access it.  Like RDP, PCoIP, and VNC, SPICE is a remote desktop access protocol.  I use the term "access" instead of "display" because all of these protocols provide more than just a display output channel.  They also provide input channels for serial devices such as keyboards and mice.

Electrical engineers: don't confuse the SPICE protocol with the SPICE circuit emulator.

When the guest is configured correctly, the serial channels provide seamless mouse traversal from the host to the guest, and allows cut-paste between the two.  Without these channels, you can't paste text into the guest from the host and you have to use keyboard escape sequences to release the mouse from the guest.

In short, it's fairly annoying if you have a need to switch quickly between the two environments.

When running newer Fedora, CentOS, and Red Hat guests on a newer host, SPICE input channels just work.  But if you need to run CentOS/RHEL 5.x, it doesn't.  Not "out of the box", anyway.  So here's how.

I'm working with CentOS 5.9.  After installing the base OS on the guest, follow these steps.
  1. Install the Feodra EPEL repo configuration.  (Note that the domain name may be different than what you see here.)

    rpm -i http://mirror.nexcess.net/epel/5/i386/epel-release-5-4.noarch.rpm
  2. Install the SPICE vdagent.

    yum install -y spice-vdagent

  3. Verify that the vdagent is enabled to start at boot time.

    chkconfig --list|grep spice
My host is running Fedora 17.  If you're using virt-manager to access your desktops, then you don't really need the SPICE client (it's built in to virt-manager).  But if you just want to connect with the 'spicec' command, then yum install spice-client. 

If you're accessing the desktop from the network, you need to make sure that SPICE is configured to listen on the right network interface.  By default, it only listens on 127.0.0.1.  In a production environment, your choice will depend on security requirements.  In a lab environment, I usually set this to 0.0.0.0 so I can get to the desktop from anywhere.

Things should start working at this point.  When I first got everything configured, it didn't.  I'm not sure what fixed it (logout?  screen save?) but it's OK now.  The mouse cursor is a little clunky compared to the experience on newer guests, but cut/paste is OK and I don't have to use a magic key combo to release the mouse.



Monday, August 5, 2013

Making ISO Files and Burning CD's

Need to create an ISO image of some directory contents and burn it to a CD or DVD? Here's how I do it on Fedora/RHEL/CentOS.

In (overly) simplistic script form, the making of an ISO9660 image goes like this.

#!/bin/bash
contents="path/to/files"
isodir="path/to/empty/dir"
isoimage="my-image.iso"

mkdir $isodir
cp -a $contents $isodir

mkisofs -R -J -T \
    -V "VOLUME_ID" \
    -A "Description of the contents" \
    -publisher "The Company, Inc" \
    -p "Another Company, Inc" \
    -o $isoimage $isodir

The Rock Ridge protocol (-R) and Joliet fileystem (-J) options specify extensions to the ISO9660 standard that help with OS compatibility. Rock Ridge adds POSIX file capabilites for Unix-compatible systems, and Joliet improves Windows compatibility.

The -T option adds a translation table to each directory, which further enhances compatibility on both Windows and Linux systems.

The remaining options are fairly obvious.  The volume ID (-V) can be up to 32 characters.  You might want to avoid whitespace for easier use on Unix systems, but it's not a requirement.

The other strings for application ID (-A), publisher, preparer (-p), are descriptive strings that can be up to 128 characters.

If you don't specify an output file or device (-o), the output will be written to stdout.

If you choose to write to a file, you can use cdrecord (a symlink to wodim on newer versions of Linux) to burn a CD.  You'll need to know the name of the device file for the optical writer on your system.  It's usually /dev/cdrom, /dev/dvdwriter, or something similar.

The command I use to burn CD's and DVD's is
cdrecord dev=/dev/cdrom my-image.iso.

Thursday, July 25, 2013

Email with Mutt

It's been a long time since I used a plain-text mail reader from the command line in Linux.  I was surprised to see that mutt on Linux worked so easily -- with a secure Microsoft Exchange Server, even!

After installing the 'mutt' package with yum, I simply ran mutt.  Then I connected to my company Exchange server.

  • Press 'c' to connect
  • Enter IMAP server.  For example, imaps://exchange.company-intranet.com
  • Accept the certificate if you think it is valid
  • Enter your user name and password
That's it.  You should see a count of mail headers as they are downloaded.  Once that operation completes, you can read, compose, and delete messages at will.  Press '?' for help on key bindings.

Wednesday, June 19, 2013

Accessing services on KVM guests behind a NAT

I have a small web service running on a Fedora 17 VM. The VM lives on the default virtual network provided by libvirt, which allows outbound connections to the external world. But because the IPv4 space for that network (192.168.122.0/24) is private, the host's iptables rules will NAT all packets on their way out.

That means "no way in" for external connections. The virtual network addresses are unseen (unroutable) to the outside world.

Luckily, libvirtd provides hooks so that you can insert custom operations at key points in the start-up/shut-down phases of a guest.   The following procedure shows how to create a hook to modify iptables when a guest is starting (or stopping).
  1. On the host, create a new file called /etc/libvirt/hooks/qemu containing this sample script. (There's a link to a more comprehensive script if you need it. I also wrote a Python replacement below.)
  2. Replace the placeholder variables in the sample script with the guest name, addresses, and ports that are to be forwarded.
  3. Restart the libvirt service.
  4. Verify new DNAT rules with iptables -nvL -t nat.
Make sure that your guest doesn't have any iptables rules that will interfere.  For instance, you'll need to open up TCP port 80 if you want your web service to be accessible.

As an exercise, I rewrote the sample script in Python, adding some logging and removing some redundancy along the way.

#!/usr/bin/env python
#!/usr/bin/env python
import argparse
import logging
import subprocess
import sys

guestAddr = { 'f17-base':'192.168.122.200' }
hostPort = { '80':'8000', '22':'222' }
logFile = '/var/log/qemu-hook.log'
logLevel = logging.DEBUG

logging.basicConfig(filename=logFile, level=logLevel, format='%(asctime)s : %(message)s')

parser = argparse.ArgumentParser()
for arg in [ 'guest', 'op', 'subop', 'extra' ]:
    parser.add_argument(arg)
args = parser.parse_args()

logging.debug('qemu hook: guest %s, op %s' % (args.guest, args.op))
if args.guest not in guestAddr:
    logging.debug('Nothing to do for guest %s' % args.guest)
    sys.exit()

if args.op == 'start':
    natOp = '-A'
    filterOp = '-I'
    opStr = 'Adding'
elif args.op == 'stopped':
    natOp = filerOp = '-D'
    opStr = 'Removing'
else:
    logging.debug('Nothing to do for op %s' % args.op)
    sys.exit()

for gport in hostPort.keys():
    # build the nat command
    natcmd = ['iptables', '-t', 'nat', natOp, 'PREROUTING']
    gAddrPort = "{}:{}".format(guestAddr[args.guest], gport)
    natcmd += ['-p', 'tcp', '--dport', hostPort[gport], '-j', 'DNAT', '--to', gAddrPort]  
    logging.info('%s nat rules for %s' % (opStr, gAddrPort))
    subprocess.call(natcmd)
    # build the filter command
    filtercmd = ['iptables', filterOp, 'FORWARD']
    filtercmd += ['-d', '{}/32'.format(guestAddr[args.guest]), '-p', 'tcp']
    filtercmd += ['-m', 'state', '--state', 'NEW']
    filtercmd += ['-m', 'tcp', '--dport', gport, '-j', 'ACCEPT']
    logging.info('%s forwarding rules for %s' % (opStr, gport))
    subprocess.call(filtercmd)

logging.info('iptables update complete')
sys.exit()

Note that, depending on the version of libvirtd you are running, the hook may not process "reconnect" operations -- i.e., when libvirtd is restarted.  You need 0.9.13 or later for that.  If you have an earlier version, you may have an issue with duplicate rules.

I left those operations out of the Python script because my Fedora 17 system is running 0.9.11.

Cheers!

Fixed DHCP addresses in libvirt

The libvirt DHCP service can deal out fixed IP addresses.  Yes, you could simply configure the guest OS with a static IP, but that's beside the point.  A DHCP service should be able to handle fixed addresses and, earlier in the game, I didn't think libvirt was capable of it.  But it is!

Just add a host element to the default network XML file using virsh net-edit default.

<network>
  <name>default</name>
  <uuid>5c0448c7-4240-4325-9a80-eb9f575c962e</uuid>
  <forward mode='nat'/>
  <bridge name='virbr0' stp='on' delay='0' />
  <mac address='52:54:00:BE:58:83'/>
  <ip address='192.168.122.1' netmask='255.255.255.0'>
    <dhcp>
      <range start='192.168.122.128' end='192.168.122.254' />
      <host mac='52:54:00:c9:06:c5' name='router122' ip='192.168.122.200' />
    </dhcp>
  </ip>
</network>

Of course, this same principle applies to other virtual networks.  The reason I doubted it at first is that virt-manager 0.9.5 doesn't expose this level of customization in its GUI.

Everything you need to know about network configuration can be found at libvirt networking.

Wednesday, April 24, 2013

The Dilettante's Git Cookbook

I use Subversion at work, so I don't use git regularly enough to remember these little things.

Undo and Redo a Commit

First, understand how the git index works. Here's a good intro from the GitGuys.

Now, to undo a commit and redo it (from StackOverflow).
$ git commit ...              (1)
$ git reset --soft HEAD^      (2)
$ edit                        (3)
$ git add ....                (4)
$ git commit -c ORIG_HEAD     (5)
  1. This is what you want to undo
  2. This is most often done when you remembered what you just committed is incomplete, or you misspelled your commit message, or both. Leaves working tree as it was before "reset".
  3. Make corrections to working tree files.
  4. Stage changes for commit.
  5. "reset" copies the old head to .git/ORIG_HEAD; redo the commit by starting with its log message. If you do not need to edit the message further, you can give -C option instead.

Throw Away/Stash All Changes

This is something you might want to do when you’ve pushed changes to your upstream repo that are better than what you have in a (different) downstream.  In other words, you started something in a repo A, finished it in B, pushed from B, and now you don’t care about unfinished changes in A.

You can use git checkout . to forget all changes for tracked files, but it won’t get rid of any untracked files that you don’t want.  Be careful!  It doesn’t ask before blowing away all unstaged changes.

A better approach might be to just stash the changes.

git stash save --keep-index

You can use the stash list and stash apply commands to see your stashes and bring them back. Or they can be removed permanently with git stash drop.

Interactive Staging

It's time to end my practice of committing gobs of unrelated changes just because I'm too lazy to pick them out and separate them.  I didn't realize it was so easy.

git add -p

It's that simple.  You can choose which changes (called hunks in the context of patching) that you want to stage for the next commit.

This is useful if you started working on a new change but forgot to commit the previous work. 

Get the full story on interactive staging from Scott Chacon's free Pro Git book.

Repo Visualization with GitWeb

Although I have a github account, there are other work-related projects that require the use of an internal git repo.  I bounce around between different machines at the office, so it's nice to have a master repo that I can get to from anywhere.

I found GitWeb to be very easy to set up.  I already had Apache httpd running on my workstation, so a simple yum install gitweb added the necessities.

To my surprise (well, not too surprised given the way my brain works), I found that the git project directory /var/lib/git already had stuff in it... from a year ago! The rpm -qf /var/lib/git command showed that the directory was owned by the git-daemon package. The daemon wasn't running and my current research gave me reasons not to run it, so I removed the package.

Anyway, I didn't have to do much other than to restart the httpd service.  Once I did, I could browse to http://myworkstation/git and there were my projects. Cool!

Note that GitWeb only provides the ability to visualize your repositories in a browser.  It's not a vehicle for git operations like cloning and pushing.  See the next section for more info on that.

Remote Repo Access With SSH

For small groups of Linux users, SSH access to git is an easy way to go.  
  • Created bare repositories for each project 
  • Copy the bare repo's to /var/lib/git on the server
  • Create a git user account on the server
  • chown -R git:git /var/lib/git
  • Add the user public keys to ~git/.ssh/authorized_keys
Once you've done that, all users are free to clone from and push to any project using the git account.  Their commit messages will have the correct user info since that information is based on git configuration and not SSH login name.

For larger groups and project-specific access, Gitosis is the next step.

If you just want to clone a repo from one machine to another (which I like to do just for backing up trivial repositories), just use git clone node:path/to/repo.