# Ubuntu Testing Automation Harness
# Copyright 2012 Canonical Ltd.
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details.
# You should have received a copy of the GNU General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Provide inventory functions specific to bare metal deployments.
"""
import logging
import os
from utah.process import pid_in_use
from utah.provisioning.inventory import (
UTAHProvisioningInventoryException,
SQLiteInventory,
)
[docs]class ManualBaremetalSQLiteInventory(SQLiteInventory):
"""Keep an inventory of manually entered machines.
All columns other than machineid, name, and state are assumed to be
arguments for system creation (i.e., with cobbler).
"""
def __init__(self, db='~/.utah-baremetal-inventory',
lockfile='~/.utah-baremetal-lock', *args, **kw):
db = os.path.expanduser(db)
lockfile = os.path.expanduser(lockfile)
if not os.path.isfile(db):
raise UTAHProvisioningInventoryException(
'No machine database found at {}'.format(db))
super(ManualBaremetalSQLiteInventory, self).__init__(
*args, db=db, lockfile=lockfile, **kw)
machines_count = (self.execute('SELECT COUNT(*) FROM machines')
.fetchall()[0][0])
if machines_count == 0:
raise UTAHProvisioningInventoryException('No machines in database')
self.machines = []
[docs] def request(self, machinetype, name=None, *args, **kw):
"""Return a Machine object meeting the given criteria.
:param machinetype: What machine type to use
:type machinetype: class
:param name: Name of machine to use
:type: str or None
All other parameters are passed to Machine constructor.
:returns: Machine object
:rtype: obj
"""
query = 'SELECT * FROM machines'
queryvars = []
if name is not None:
query += ' WHERE name=?'
queryvars.append(name)
result = self.execute(query, queryvars).fetchall()
if result is None:
raise UTAHProvisioningInventoryException(
'No machines meet criteria')
else:
for minfo in result:
machineinfo = dict(minfo)
if machineinfo['state'] == 'available':
return self._take(machineinfo, machinetype, *args, **kw)
for minfo in result:
machineinfo = dict(minfo)
pid = machineinfo['pid']
proc = pid_in_use(pid, ['utah', 'run_test'])
if not proc:
return self._take(machineinfo, machinetype, *args, **kw)
else:
logging.info('machine in use by: %s', proc.cmdline)
raise UTAHProvisioningInventoryException(
'All machines meeting criteria are currently unavailable')
def _take(self, machineinfo, machinetype, *args, **kw):
machineid = machineinfo.pop('machineid')
name = machineinfo.pop('name')
state = machineinfo.pop('state')
machineinfo.pop('pid')
update = self.execute(
'UPDATE machines '
"SET pid=?, state='provisioned' "
'WHERE machineid=? AND state=?',
[os.getpid(), machineid, state]).rowcount
if update == 1:
machine = machinetype(*args, inventory=self,
machineinfo=machineinfo, name=name, **kw)
self.machines.append(machine)
return machine
elif update == 0:
raise UTAHProvisioningInventoryException(
'Machine was requested by another process')
elif update > 1:
raise UTAHProvisioningInventoryException(
'Multiple machines exist matching those criteria; '
'database {} may be corrupt'.format(self.db))
else:
raise UTAHProvisioningInventoryException(
'Negative rowcount returned when requesting machine')
[docs] def release(self, machine=None, name=None):
"""Release a machine so it can be used by other processes.
:param machine: Machine object to release
:type machine: obj or None
:param name: name of machine to release
:type name: str or None
"""
if machine is not None:
name = machine.name
if name is None:
raise UTAHProvisioningInventoryException(
'name required to release a machine')
query = "UPDATE machines SET state='available' WHERE name=?"
queryvars = [name]
update = self.execute(query, queryvars).rowcount
if update == 1:
if machine is not None:
if machine in self.machines:
self.machines.remove(machine)
elif update == 0:
raise UTAHProvisioningInventoryException(
'SERIOUS ERROR: Another process released this machine '
'before we could, which means two processes provisioned '
'the same machine simultaneously')
elif update > 1:
raise UTAHProvisioningInventoryException(
'Multiple machines exist matching those criteria; '
'database {} may be corrupt'.format(self.db))
else:
raise UTAHProvisioningInventoryException(
'Negative rowcount returned when releasing machine')
# Here is how I currently create the database:
# CREATE TABLE machines (machineid INTEGER PRIMARY KEY,
# name TEXT NOT NULL UNIQUE,
# state TEXT default 'available',
# pid INT,
# [mac-address] TEXT NOT NULL UNIQUE,
# [power-address] TEXT DEFAULT '10.97.0.13',
# [power-id] TEXT,
# [power-user] TEXT DEFAULT 'ubuntu',
# [power-pass] TEXT DEFAULT 'ubuntu',
# [power-type] TEXT DEFAULT 'sentryswitch_cdu');
# Here is how I currently populate the database:
# INSERT INTO machines (name, [mac-address], [power-id])
# VALUES ('acer-veriton-01-Pete', 'd0:27:88:9f:73:ce', 'Veriton_1');
# INSERT INTO machines (name, [mac-address], [power-id])
# VALUES ('acer-veriton-02-Pete', 'd0:27:88:9b:84:5b', 'Veriton_2');