Working with VMware’s API via Python

(Update – Sept. 2, 2015)
In some of the community samples, someone wrote a nice function to get different objects.

As part of streamlining VM creation, I began working with VMware’s API.  The first thing I learned is that there are multiple API implementations.  Until recently, while VMware had their own Python API, they kept it private and did not officially support it.  That prompted folks to write their own bindings and share them via Google Code in a project called pySphere (https://code.google.com/p/pysphere/).  In early 2014, however, VMware did release their bindings to the public (https://github.com/vmware/pyvmomi).

Wanting to go the official route, I downloaded pyVmomi and got to work.  From the beginning I noticed that this API is very thorough and robust.  The downside of that is that it is also very complex and can sometimes be difficult to know just how to get and do what you need.  Fortunately there are many good samples included in the code.  So, while it takes a lot of effort to get things just right, once you get the objects and methods sorted out, it works well.  And fortunately the documentation (vmware python API doc), if you can stay awake while you wade through it, will tell you what you need.

I won’t go into how I did each and every thing.  The goal of this document is to give the reader a decent understanding of how to create and manipulate objects, as well as how to make the method calls.

What to import

The basic script should have the following items.

  • from pyVmomi import vim – this module contains what we need to create objects and make calls
  • from tools import tasks – if we’re going to create anything in vCenter, this module allows us to designate the creation process as a task, then wait for it to finish
  • from pyVim import connect – needed to login
  • import atexit – this module allows us to specify that, when we logout, certain things should happen (such as logging out)
  • import argparse – if we’re going to accept command-line input, this module greatly simplifies managing that input

Building objects

To get information (such as a list of VMs) or do things (such as create a VM), the system requires a session to be established and objects to be passed to the method calls.  Establishing a session is as simple as logging in, which is covered in the sample code included in the project, so I won’t cover that here.  The samples refer to the session as a service instance, often shortened as “si”.  For this document, you  will notice the service-instance object referred to as “si”.

A few basic objects

Once we’re logged in we need a few fundamental objects from which we can build others.  For my scripts, I use these:

  • atexit.register(connect.Disconnect, si)
  • content = si.RetrieveContent()
  • datacenter = content.rootFolder.childEntity[0]
  • hosts = datacenter.hostFolder.childEntity

These are mostly self-explanatory. The second item, “content,” is essentially “all the things” in this vCenter.  From those we get access to pretty much any object that the API allows us to reference.

The fun begins

The challenge began when I wanted to create a new VM by cloning a template.  While the API is, as I said, very thorough and complex, the documentation is equally so.  But it’s also confusing at times.  I’ll demonstrate using parts of the cloning process.

While there is a method called CloneVM_Task, that is the wrong call if you want your VM placed in a certain datastore cluster but do not want to specify which store it lands it.  CloneVM_Task forces you to specify the specific datastore for your new VM.  The proper way to do that is with a combination of RecommendDatastores and ApplyStorageDrsRecommendation_Task.

This method requires one object (called a parameter in the doc).  While the documentation page lists two parameters (_this and storageSpec), the first of those is really a parent object against which we run the method.  I’ll elaborate on that in a second.  But first let me point out that any parameter named “_this” in the documentation means you call the method in question by appending it to an object to which “_this” refers.  Demonstration:

RecommendDatastores lists two parameters: _this and storageSpec.  We actually call it like so:
storage_rec = content.storageResourceManager.RecommendDatastores(storageSpec=storagespec)
So, “_this” gets translated into ” content.storageResourceManager” and we call the method using storageSpec, which is an object we have to have already built.

Now, how the heck would you have known that?  Well, you wouldn’t intrinsically.  There are three ways to find that out: either you figure it out from the included samples, you find it from samples in the discussion lists for pysphere (because pysphere is pretty close to pyVmomi), or you catch a clue from the documentation page.  In this case, the documentation URL for this method ends with this text:
vim.StorageResourceManager.html#recommendDatastores
Additionally, in the left pane of the documentation the method lists its parent namespace in parentheses, i.e. “RecommendDatastores (in StorageResourceManager)”

But you might not have guessed it is called under the “content” object without further digging or experimentation.  Plainly put, it just takes some intuition and hacking sometimes to get this right.

And that’s just the tip of the iceberg.  You could be forgiven for thinking, “Oh, this only has one object.  No worries.”  Why?  Because “storageSpec” contains a lot of other objects.  And many of those contain other objects, etc.  And many of those objects must first be instantiated (a decidedly object-oriented programming term) by a method before they can be used.

Ready to quit yet?  Don’t.  There’s a good payoff to sticking with this stuff.  At the least it will stretch your mind and deepen your understanding of how VMware works under the hood.  And what bit-head doesn’t want that?

Looking at storageSpec, it wants an object of type “StoragePlacementSpec“.  As we drill into that, we see this object consists of several other objects.  And, as I said, some of them have many more objects in their definition.  Let’s pick a somewhat simple one: podSelectionSpec.  This particular data type (StorageDrsPodSelectionSpec) tells VMware which datastore cluster we want to use for the new VM.  Of the two objects that this data type uses, we are only interested in the second one, storagePod.

Unfortunately, because these are custom data types, we cannot simply say
storagePod = “MyStoragePod” and expect the API to intuit what we want.  In place of “MyStoragePod” we need a data type of StoragePod which points to the pod named “MyStoragePod”.  In our case, from the samples I took a bit of code which created a view of the storage pods, then I parsed through that list and compared each pod name to our target (“MyStoragePod”) and, when we got a match, assigned that to a variable named “pod”.  So, below, first we create a view, then we destroy the view so we do not consume too many resources in the vHost.   And finally we parse through the view until we find a match.

 obj_view = content.viewManager.CreateContainerView(content.rootFolder,[vim.StoragePod],True)
 ds_cluster_list = obj_view.view
 obj_view.Destroy()
 for ds in ds_cluster_list:
     if ds.name == "MyStoragePod":
         pod = ds
         break
 # a blank object of type PodSelectionSpec
 podsel = vim.storageDrs.PodSelectionSpec()
 podsel.storagePod = pod

And thus we have our pod selection.  To add that to our primary object, a StoragePlacementSpec data type, I create the blank object, then add to it:

# a blank type of StoragePlacementSpec
 storagespec = vim.storageDrs.StoragePlacementSpec()
 storagespec.podSelectionSpec = podsel

So, in short, we first create a blank parent object (similar to initializing a variable in other languages), then we assign the child object to be a part of the parent object.  Again, the tricky part is finding a way to identify those child objects.

Update – Sept. 2, 2014 –
def get_obj(content, vimtype, name):
    """
    Return an object by name, if name is None the
    first found object is returned
    """
    obj = None
    container = content.viewManager.CreateContainerView(
        content.rootFolder, vimtype, True)
    for c in container.view:
        if name:
            if c.name == name:
                obj = c
                break
        else:
            obj = c
            break

    return obj

Now all you have to do is call that function with the proper “vimtype”.  A few that are useful:

# gets a  datastore cluster (storage pod) matching "DS-Cluster1"
cluster = get_obj(content, [vim.StoragePod], "DS-Cluster1")
# gets a host cluster matching "My-Host-Cluster"
cluster = get_obj(content, [vim.ClusterComputeResource], "My-Host-Cluster")
# gets a  resource pool matching "My-Resource-Pool"
cluster = get_obj(content, [vim.ResourcePool], "My-Resource-Pool")
# gets a  datastore matching "Datastore1"
cluster = get_obj(content, [vim.Datastore], "Datastore1")

Doing stuff to the objects

Once we have built our objects, we can create the VM.  Or, in our case, clone a template into a VM.  Before I discuss the methods, here are the steps I used to create the VM object.

 template = getTemplate(si, tierdata['template'])
 if template == None:
    print "Unable to find template matching %s" % template
# make an empty clone spec object, then customize it
 print("Checkpoint - getting customization data")
 cloneSpec = vim.vm.CloneSpec()
 custSpec = content.customizationSpecManager.GetCustomizationSpec(tierdata['customization_spec']) # dev_gold
 custSpec.spec.nicSettingMap[0].adapter.ip.ipAddress = VMip
 custSpec.spec.nicSettingMap[0].adapter.gateway = gateway
 cloneSpec.customization = custSpec.spec
 # make an empty config spec, then customize it
 config = vim.vm.ConfigSpec()
 config.name = machine_name
 cloneSpec.config = config
 location = vim.vm.RelocateSpec()
 cloneSpec.location = location
 # if we cannot find resource pool with name given by user, use default for this vHost
 print("Checkpoint - getting resource pool data")
 resourcePool = getResourcePool(hosts,tierdata['resource_pool']) # "MyStoragePod"
 if resourcePool == None:
    print("Unable to find resource pool named %s" % tierdata['resource_pool']) # MyStoragePod
    sys.exit(1)
 cloneSpec.location.pool = resourcePool
 cloneSpec.powerOn = powerOn
 # using name of target cluster (i.e. MyStoragePod), parse thru clusters until we find a match
 print("Checkpoint - getting datastore cluster data")
 obj_view = content.viewManager.CreateContainerView(content.rootFolder,[vim.StoragePod],True)
 ds_cluster_list = obj_view.view
 obj_view.Destroy()
 for ds in ds_cluster_list:
    if ds.name == tierdata['datastore']: # MyStoragePod
        pod = ds
        break
 # set thin provisioning on disks
 device = vim.vm.device.VirtualDevice()
 vdisk = vim.vm.device.VirtualDisk()
 backing = vim.vm.device.VirtualDisk.FlatVer2BackingInfo()
 vdisk.backing = backing
 vdisk.backing.thinProvisioned = True
 vdisk.key = -100
 dspec = vim.vm.device.VirtualDeviceSpec()
 dspec.device = vdisk
 config.deviceChange = [dspec]
 # make an empty pod selection spec, then customize it 
 podsel = vim.storageDrs.PodSelectionSpec()
 podsel.storagePod = pod
 # make an empty storage placement spec, then customize it 
 storagespec = vim.storageDrs.StoragePlacementSpec()
 storagespec.podSelectionSpec = podsel
 storagespec.vm = template
 storagespec.type = 'clone'
 print("Checkpoint - getting destination folder data")
 storagespec.folder = getDestFolder(datacenter, "Development", "Applications")
 storagespec.cloneSpec = cloneSpec
 storagespec.cloneName = machine_name
 storagespec.configSpec = config
'''Once that is done, we have built our VM object.  The method we use to
 create the VM is "RecommendDatastores", which we call like so:'''
rec = content.storageResourceManager.RecommendDatastores(storageSpec=storagespec)
'''This asks vCenter to tell us which datastore within the cluster it prefers 
to use for this host.  That method returns an object, which I chose to 
call "rec".  One of the recommendation objects is a key, which we will 
use to actually apply the recommendation.  Interesting note: when you 
create a VM from a template within the GUI, you see a similar process.  
You select a datastore and the GUI returns recommendations.
To apply the recommendation, we use another set of objects VMware provided known as tasks.
Using that framework, we are able to tell the script to wait until the task finishes before continuing.'''
rec_key = rec.recommendations[0].key
task = content.storageResourceManager.ApplyStorageDrsRecommendation_Task(rec_key)
tasks.wait_for_tasks(si, [task])

 

(Note that VMware will proceed with the task whether we execute the last step or not.  It is wise and courteous on our part to let it finish each VM creation before proceeding to the next.)

At this point, in the GUI you will see the clone process taking place.  It typically finishes within 2-3 minutes.  If there are any errors, you will see those in the GUI as well.  You can copy the text of them for research.  Additionally, the Python code will throw an exception which usually matches the GUI message fairly well.

How I got my StartSSL certificates to work with Thunderbird

Short version

(During a brief period where I used StartSSL for my SSL certs…)

Had a problem with my mail server.  Thunderbird was not accepting the Class 1 cert I got from StartCom.  To fix it, I added lines to /etc/dovecot.conf and /etc/postfix/main.cf.  Assume the intermediate CA cert is named “sub.class1.server.ca.pem“.

For dovecot, in /etc/dovecot.conf:

ssl_ca_file = /path/to/certs/sub.class1.server.ca.pem

For postfix, in /etc/postfix/main.cf:

smtpd_tls_CAfile = /path/to/certs/sub.class1.server.ca.pem

Then I restarted both services.

How to install VMware tools in CentOS 5 or 6

1. Install the repo

Actually before you set up the repo, you need the public keys:
# rpm –import https://packages.vmware.com/tools/keys/VMWARE-PACKAGING-GPG-DSA-KEY.pub
# rpm –import https://packages.vmware.com/tools/keys/VMWARE-PACKAGING-GPG-RSA-KEY.pub

Then you can set up the repo by hand or with their RPM.

By repo RPM:

You can do this by installing the repo RPM from VMware:
# yum install https://packages.vmware.com/tools/releases/latest/repos/vmware-tools-repo-RHEL6-10.0.9-1.el6.x86_64.rpm
(or yum install https://packages.vmware.com/tools/releases/latest/repos/vmware-tools-repo-RHEL5-10.0.9-1.el5.x86_64.rpm)

By hand:

Create /etc/yum.repos.d/vmware-tools.repo.  Contents should be as follows.

Note: Make sure the number after “esx” reflects your current version of ESX.  To see a list of available versions, point your browser to http://packages.vmware.com/tools/esx

Also, if you’re installing in RHEL 5 or CentOS 5, change the ‘rhel6’ to ‘rhel5’.

/etc/yum.repos.d/vmware-tools.repo:
 [vmware-tools]
 name=VMware Tools
 baseurl=http://packages.vmware.com/tools/esx/5.0/rhel6/$basearch
 enabled=1
 gpgcheck=1

2. Install the public keys and then tools
# yum -y install vmware-tools-esx-kmods vmware-tools-esx

3. Make sure the services are set to run at boot
# chkconfig vmware-tools-services on

4. If the tools are not already running, start them:
# service vmware-tools-services status
# service vmware-tools-services start

 

How to setup EPEL repos on CentOS

If you’re wanting packages from the EPEL repos that are available to RHEL installations, it’s a simple thing to get them.

You can start at the main Fedora repository for EPEL, and work your way around.  Or if you’re in a hurry, use these instructions.
(Note: these are mirror sites; if they are inaccessible, refer back to the main repo linked abve, and you’ll get redirected to a different mirror in most cases)

For CentOS 5

  1. Install the repo RPM from here.
    # rpm -ivh http://mirror.symnds.com/distributions/fedora-epel/5/x86_64/epel-release-5-4.noarch.rpm
  2. Install the GPG key from here.
    # rpm --import http://mirror.symnds.com/distributions/fedora-epel/RPM-GPG-KEY-EPEL-5

For CentOS 6

  1. Install the repo RPM from here.
    # rpm -ivh http://mirror.symnds.com/distributions/fedora-epel/6/x86_64/epel-release-6-8.noarch.rpm
  2. Install the GPG key from here.
    # rpm --import http://mirror.symnds.com/distributions/fedora-epel/RPM-GPG-KEY-EPEL-6

For either one, if you do not want the repo used by default when you run yum, edit /etc/yum.repos.d/epel.repo, and in the section named [epel] change enabled=1 to enabled=0
Then, to use the repo, add --enabelrepo=epel to your yum commands.  For example:
# yum --enablerepo=epel update

The time I tried compiling an RPM for syslog-ng

Introduction

Had a client for whom I wanted to install syslog-ng.  I’ve never worked with it before, but had read that it can log to a MySQL database.  The project at hand seemed like a great fit for just such a thing, so off I went to get syslog-ng.

(Update on Nov 28, 2012: When I realized that rsyslog comes with RHEL 5 and supports MySQL out of the box, I switched the project to it instead)

Build my own?  No problem.

Having worked with Linx and open source for nearly 2 decades now, I’m accustomed to having a look to find a software package occasionally.  Since most of that time has been spent working with Red Hat, Fedora, and CentOS, I’m also accustomed to having to go out of my way to find RPM versions of packages.  It’s common to find that someone who maintains an application doesn’t by default make an RPM for it.  Sometimes that’s because their experience is in another flavor of Linux or UNIX that doesn’t natively use RPMs (aka Debian, Slackware, *BSD), or because they don’t have time to make an RPM.

Since I didn’t find an RPM readily available for this install (on CentOS 5.8), I started looking around.  My usual sources (rpmforge, etc.) didn’t yield anything.  So I turned to rpmfind.net.  Right away I saw that there is a package available for RHEL 5, thus presumably for CentOS as well.  Unfortunately, though, the CentOs extras repo didn’t have the package.  Bummer.  But the sources for Red Hat’s EPEL packages (http://download.fedoraproject.org/pub/epel/5/SRPMS) had a source RPM.  For me, that’s usually enough to get started.  So, a quick wget and I was on my way:

$ wget http://download.fedoraproject.org/pub/epel/4/SRPMS/syslog-ng-2.1.4-1.el4.src.rpm

From here I usually do either:

rpmbuild --rebuild syslog-ng-2.1.4-1.el4.src.rpm

or I install the src.rpm and do:

rpmbuild -bb /path/to/syslog-ng.spec

But it wasn’t that simple

To make an incredibly long story a bit shorter, I had build errors.  And the reason I did was that this package wanted a devel package that I couldn’t find right away (try finding tcp_wrappers-devel-7.6-40).  What I ended up doing was finding the source RPM for tcp_wrappers and building a new version of it.  Also, I had to build the eventlog RPM that goes along with syslog-ng (same company makes both). Great!  Now all I had to do was build syslog-ng, finally!

Except that it kept croaking, telling me “ld: cannot find -lwrap“.  Now, I thought this thing was looking for libwrap.so, so I made sure it (or some symlink to it) was in all the common lib directories.  Nope, no luck.  Ah, I need to run ldconfig, I thought.  Nope, didn’t help.

Next I popped open the source tarball and look in it.  I saw some references to /usr/local/lib.  I changed those to /usr/lib, re-packed the tarbarll, stuck it in the SOURCES directory in my build environment, and ran it again….nope.  After more digging in Makefiles and configure files, I found that it apparently wanted libwrap.a to be in the lib paths.  But…my tcp_wrappers packages did not install that file.  Odd.  I updated my locate database and did ‘locate libwrap.a‘.  That file existed only in the RPM BUILD directory.  Looking in the spec file, I found that it had been removed around 7.6-42 by the folks at Red Hat.  Perhaps they were purging out old static libraries.  I manipulated the spec file to make it install in /usr/lib and…

Finally

Finally syslog-ng compiled.  So, to get syslog-ng to build, I had to install and/or build these packages:

eventlog-0.2.12-1.x86_64
eventlog-devel-0.2.12-1.x86_64
eventlog-static-0.2.12-1.x86_64
libnet-1.1.5-1.el5.x86_64
libnet-devel-1.1.5-1.el5.x86_64
tcp_wrappers-7.6-59.x86_64
tcp_wrappers-devel-7.6-59.x86_64
tcp_wrappers-libs-7.6-59.x86_64

(the version of tcp_wrappers was 7.6-57 when I got the SRPM, but I bumped the version a couple of times to account for changes I made in the spec file)

Now, this was just on my build server.  On the actual client machine, I installed these RPMs:

 eventlog
eventlog-devel
eventlog-static
syslog-ng
libnet        

The libnet package was already installed, so it was an update.

What’s next?

I know that version 3.2 of syslog-ng comes with RHEL/CentOS 6 EPEL.  I’m going to see if I can get it to build for a CentOS 5 environment, since 2.1 seems rather old by comparison.

Additionally, now I will be working to get the service working with mysql.  But that’s probably another post…  🙂

How I installed WordPress 3.4.2 on CentOS 5.8

Had a real adventure getting a WordPress site going on a CentOS 5.8 LAMP server. It came down to two major areas: packages and permissions.

Packages

I asked the hosting provider to install pretty much just the base package group, knowing we could easily add on from there without having a lot of extra fluff. After a fair amount of troubleshooting (and very patient help from the site developer), I ended up installing the list below. Some of them may be unnecessary in the end, and many were pulled in as dependencies, but we got there.

perl-DBD-MySQL-3.0007-2.el5.x86_64
mysql-server-5.0.95-1.el5_7.1.x86_64
mod_auth_mysql-3.0.0-3.2.el5_3.x86_64
ntp-4.2.2p1-15.el5.centos.1.x86_64
php53-common-5.3.3-13.el5_8.x86_64
php53-cli-5.3.3-13.el5_8.x86_64
php53-pdo-5.3.3-13.el5_8.x86_64
php53-mbstring-5.3.3-13.el5_8.x86_64
php53-mysql-5.3.3-13.el5_8.x86_64
php53-5.3.3-13.el5_8.x86_64
php53-xmlrpc-5.3.3-13.el5_8.x86_64
php53-xml-5.3.3-13.el5_8.x86_64
php53-gd-5.3.3-13.el5_8.x86_64
gd-progs-2.0.33-9.4.el5_4.2.x86_64
libwmf-0.2.8.4-10.2.x86_64
libcroco-0.6.1-2.1.x86_64
lcms-1.18-0.1.beta1.el5_3.2.x86_64
libgsf-1.14.1-6.1.x86_64
librsvg2-2.16.1-1.el5.x86_64
urw-fonts-2.3-6.1.1.noarch
ghostscript-8.70-14.el5_8.1.x86_64
ghostscript-fonts-5.50-13.1.1.noarch
ImageMagick-6.2.8.0-15.el5_8.x86_64
libdrm-2.0.2-1.1.x86_64
libXxf86vm-1.0.1-3.1.x86_64
mesa-libGL-6.5.1-7.10.el5.x86_64
libjpeg-devel-6b-37.x86_64
libXau-devel-1.0.1-3.1.x86_64
bzip2-devel-1.0.3-6.el5_5.x86_64
ghostscript-devel-8.70-14.el5_8.1.x86_64
libtiff-devel-3.8.2-15.el5_8.x86_64
lcms-devel-1.18-0.1.beta1.el5_3.2.x86_64
xorg-x11-proto-devel-7.1-13.el5.x86_64
libXdmcp-devel-1.0.1-2.1.x86_64
libX11-devel-1.0.3-11.el5_7.1.x86_64
libXext-devel-1.0.1-2.1.x86_64
libICE-devel-1.0.1-2.1.x86_64
libSM-devel-1.0.1-3.1.x86_64
libXt-devel-1.0.2-3.2.el5.x86_64
mesa-libGL-devel-6.5.1-7.10.el5.x86_64
ImageMagick-devel-6.2.8.0-15.el5_8.x86_64
imake-1.0.2-3.x86_64
autoconf-2.59-12.noarch
automake-1.9.6-2.3.el5.noarch
php53-devel-5.3.3-13.el5_8.x86_64
php-pear-1.4.9-8.el5.noarch

Also, I had to run ‘pecl install imagick‘.  Newer versions of WordPress require PHP 5.2 and newer.  CentOS provides 5.1 by default, but Red Hat back-ported 5.3 as php53 to accommodate the fact that so many things need it.  One major discovery was that image resizing will break without ‘gd’ support.  This came thru the php53-gd package, as well as the ImageMagic stuff.

Permissions, ownership, etc.

Like many web hosting setups, the apache user needs to be able to read directories, execute files, and in some cases write things.  After lots of trial and error, we settled on these principles:

  • The directory wp-content (and its subdirectories) is owned by the site user, and is in the apache group
  • The site owner is a member of the apache group as an additional group (i.e. usermod -a -G apache user).  You could also just make the apache group the primary group of the site user (usermod -g apache user).
  • All directories under wp-content are setgid (chmod g+s), and are mode 775.
  • All files are mode 664 (644 if you’re a bit paranoid, and for all .php files, but keep an eye out for things that break).

 

RHEL, biosdevname, cobbler

I would include more links, etc., but I’m writing this in a bit of a rush.

It is well-documented that Dell and Red Hat decided to modify how Linux names network devices, starting with Fedora 15.  This project is called Consistent Network Device Naming.  It integrates with the biosdevname package to break improve network device naming.  In theory, each time a system boots, CNDN applies a set of rules so that each NIC is named the same each time.  One of the main factors for this is the problem you might have seen in times past when you added a PCI NIC to a system, and suddenly eth0 is now eth1 or some such.

While the motivation was good, CNDN has created problems, as we expect with any project which involves significant change. One of the primary claims, and a legitimate one, is that so many system tools and scripts (especially custom sysadmin scripts) are hard-coded for eth0.  Referring to eth0 has worked for years.  So bringing along a system-level change that now refers to the first NIC as em1 (for an on-board interface) breaks many scripts.

In our shop, using RHEL and cobbler for kickstarting systems, this reared its ugly head when, after installing a new Dell system with RHEL 6.3, networking was broken.  Logging into the console, we ran ifconfig -a and noticed all 4 NICs, but they were named em1 thru em4, rather than the eth0 thru eth3 we are accustomed to.  Moreover, em1 had no IP address. A quick peek in /var/log/messages showed us this gem:

Sep 21 10:16:08 sandmdm kernel: udev: renamed network interface eth0 to em1

That’s nice.  It would have been better had udev (or something else) also modified the network config files so that networking would actually work after udev makes its changes.  But alas, looking in the default location for its config, we noticed (aside from lo) only one NIC had a config file – eth0.  And of course ifconfig knew nothing about eth0.  So, changing the filename from ifcfg-eth0 to ifcfg-em1, and changing the device name inside the file to em1, we restarted networking and all was well.

But we wanted a better fix.  After lots of research, it turns out cobbler is hard-coded to assume eth0 is the first NIC in a system.  Moreover, even after anaconda (the RHEL installer) refers to it as em1 throughout the whole process, cobbler dutifully puts ifcfg-eth0 in /etc/sysconfig/network-scripts.  To us, it’s a show-stopper to have to login to a console to fix networking after a fresh install.  So what were our options?

  1. Deal with it, by making rules in udev
    Not really an option, because we’d have to put them in the post section of the kickstart.  Seems like too much of  a hack.
  2. Deal with it, by changing the interface setting to em1 in cobbler profiles for all RHEL6 systems.
    I didn’t really care for that option. Would have required some testing, because we’re not sure that would even work.
  3. Use biosdevname=0 in /etc/grub.conf to disable this tomfoolery.
    Not a bad idea.  Workable.  We’d have to modify the kernel options in the post section of kickstart.
  4. Remove the biosdevname package altogether.
    That’s the best, cleanest fix.

So, we decided to disable the installation of biosdevname during kickstart.  But it wasn’t that simple.  After lots of testing, we settled on letting biosdevname install, but disabling it in the kernel options (biosdevname=0).  We also had to, in the kickstart profile in RHN Satellite, add the same ‘biosdevname=0’ setting to both Kernel Options and Post Kernel Options.  That resulted in ‘eth0’ being the first interface after installation.

UPDATE – 10/05/2012

After more consideration and testing, we changed our approach.  While we would like to keep existing functionality (i.e. the first NIC is eth0), we realize that’s not a great option long-term.  We wanted to begin adjusting now to this change, rather than hit some wall later, when perhaps we have no choice but to install new systems with em1.

Our solution has been to change the name of the interface in the cobbler profile.  Steps involved:

-add the system to cobbler
-edit the system to add ’em1′ as an interface, make sure it has a DNS name and a MAC
cobbler system edit --name=somebox --interface=em1 --mac=11:22:33:44:55:66 --dns-name="somebox.example.com"
-remove ‘eth0’ from the profile
cobbler system edit --name=somebox --interface=eth0 --delete-interface
-in the kickstart profile, add ‘--device=em1‘ to the ‘network’ line (on the Advanced tab if you use RHN Satellite)

“The Red Hat Enterprise Linux installation tree in that directory does not seem to match your boot media”

In working with Red Hat Satellite Server 5.4.1, we had a problem with Dell PowerEdge R720 servers.  When we kickstarted those systems to RHEL 5.7, using DHCP, the Tigon3 NIC would flap at the point where the installer enables the NIC to get stage2.img, and DHCP would time out.

While troubleshooting this, I updated the kickstart tree to RHEL 5.8 (as a sidepoint to this post, that worked).  But alas, that tree wanted packages that are too new for RHEL 5.7 (I was hoping we could install RHEL 5.7 from the RHEL 5.8 kickstart images, but no).  So I reverted back to the previous kickstart images, and got the above error.

I found the problem by comparing files from /var/satellite/rhn/kickstart/my-installation-tree with /tftpboot/images/my-installation-tree.  I found that the two files in the latter directory (vmlinuz and initrd.img) were the same ones from the base tree for RHEL 5.8 (I did not change them initially, so I assume “cobbler sync” did).  They should have been the ones from the RHEL 5.7 tree.  So, when I copied the correct ones in and re-ran “cobbler sync“, we were good.

Lesson learned: vmlinuz and initrd.img in
/var/satellite/rhn/kickstart/my-installation-tree/images/pxeboot

and in
/tftpboot/images/my-installation-tree/ks-rhel-x86_64-server-5-57
must match

Moving a Postfix virtual user’s mail to a new address

A user was on too many spam lists and wanted a new mailbox.  He had removed all the spam, and wanted all the existing messages moved over.  The hosting server runs Postfix in a virtual setup.  The script below is what I used.  It makes it easy to move mailboxes in the future for my client.  The script takes two options, and expects the first to be the old email address, the second to be the new email address.  It could stand some error checking, etc., but since I expect it to be run by a clue-possessing administrator, I didn’t put any in here.  This script assumes that the destination mailbox has already been created (in our case, by using PostfixAdmin).

 

#!/bin/bash
mailroot="/var/spool/postfix/virtual"
usersource=$1
userdest=$2
sourcedir="${mailroot}/${usersource}"
destdir="${mailroot}/${userdest}"
echo "Source dir is ${sourcedir}"
echo "Dest dir is ${destdir}"
read -p "Press any key to begin" 
cd ${sourcedir}
for i in *; do
    mv -v $i ${destdir}/
done
cd -

Send a simple message from the command-line

Need to send yourself or someone else a quick email from the Linux command-line?  Use mutt.  First I compose the message in a file, e.g. msg.txt.  Then cat it, and invoke mutt.  The example below also includes an attachment – a  python script I was sending myself.

cat msg.txt | mutt -s "version 2.0" -n -a sample.py me@example.com

Options:
-n – bypass the system configuration
-s – subject
-a – attach a file