testing: use Murphy to control resource access during testing

When multiple different runtests.py instances are active, they must coordinate
access to shared resources like accounts on servers. We can use Murphy
(https://01.org/murphy/) for that.

If runtest.py is started with a D-Bus session address set in the environment,
it expects Murphy to be running there and will lock a resource named after
each operation before running the operation. Some operations (like "compile")
can be granted to each instance, but some (like "memotoo") must be exclusive.

Here's a complete Murphy lua config:

m = murphy.get()

-- try loading console plugin
m:try_load_plugin('console')

-- load the native resource plugin
if m:plugin_exists('resource-native') then
    m:load_plugin('resource-native')
    m:info("native resource plugin loaded")
else
    m:info("No native resource plugin found...")
end

-- load the dbus resource plugin
m:try_load_plugin('resource-dbus', {
    dbus_bus = "session",
    dbus_service = "org.Murphy",
    dbus_track = true,
    default_zone = "driver",
    default_class = "implicit"
  })
m:info("dbus resource plugin loaded")

-- define application classes
application_class { name="implicit" , priority=0 , modal=false, share=true ,
order="fifo" }

-- define zone attributes
zone.attributes {
}

-- define zones
zone {
     name = "driver"
}

-- define resource classes
resource.class {
     name = "audio_playback",
     shareable = true,
     attributes = {
         role = { mdb.string, "music", "rw" },
         pid = { mdb.string, "<unknown>", "rw" },
         policy = { mdb.string, "relaxed", "rw" }
     }
}

-- SyncEvolution resources: one per runtest.py
-- Some tests can run in parallel. Those resources are shareable.
for i,v in pairs {
    -- compiling the source on one platform
    "compile",
    "install",
    "dist",

    -- checking out source
    "libsynthesis",
    "syncevolution",
    "activesyncd",

    -- local tests
    "evolution",
    "dbus",
    "pim",
    } do
    resource.class {
        name = v,
        shareable = true
    }
end

-- TODO (in runtests.py): some of these resources overlap
for i,v in pairs {
    -- tests involving unique peers
    "googlecalendar",
    "googlecontacts",
    "owndrive",
    "yahoo",
    "oracle",
    "davical",
    "apple",
    "googleeas",
    "exchange",
    "edsfile",
    "edseds",
    "edsxfile",
    "davfile",
    "edsdav",
    "mobical",
    "memotoo",
    } do
    resource.class {
        name = v,
        shareable = false
    }
end

-- test for creating selections: don't remove, murphyd won't start without it
-- (E: Failed to enable resolver autoupdate.)
mdb.select {
           name = "audio_owner",
           table = "audio_playback_owner",
           columns = {"application_class"},
           condition = "zone_name = 'driver'",
}
This commit is contained in:
Patrick Ohly 2013-12-17 01:59:13 -08:00
parent 28c0f6d923
commit f28861884e

View file

@ -27,6 +27,30 @@ def log(format, *args):
log.start = time.time()
log.latest = log.start
# Murphy support: as a first step, lock one resource named like the
# test before running the test.
gobject = None
try:
import gobject
except ImportError:
try:
from gi.repository import GObject as gobject
except ImportError:
pass
import dbus
from dbus.mainloop.glib import DBusGMainLoop
DBusGMainLoop(set_as_default=True)
if 'DBUS_SESSION_BUS_ADDRESS' in os.environ:
bus = dbus.SessionBus()
loop = gobject.MainLoop()
murphy = dbus.Interface(bus.get_object('org.Murphy', '/org/murphy/resource'), 'org.murphy.manager')
log('using murphy')
else:
bus = None
loop = None
murphy = None
log('not using murphy')
try:
import gzip
havegzip = True
@ -171,6 +195,8 @@ class Action:
oldout = sys.stdout
olderr = sys.stderr
cwd = os.getcwd()
resourceset = None
locked = False
try:
subdirname = "%d-%s" % (step, self.name)
del_dir(subdirname)
@ -183,6 +209,48 @@ class Action:
os.dup2(fd, 2)
sys.stdout = os.fdopen(fd, "w", 0) # unbuffered output!
sys.stderr = sys.stdout
if murphy:
log('=== locking resource %s ===', self.name)
resourcesetpath = murphy.createResourceSet()
resourceset = dbus.Interface(bus.get_object('org.Murphy', resourcesetpath), 'org.murphy.resourceset')
resourcepath = resourceset.addResource(self.name)
# Allow sharing of the resource. Only works if the resource
# was marked as "shareable" in the murphy config, otherwise
# we get exclusive access.
resource = dbus.Interface(bus.get_object('org.Murphy', resourcepath), 'org.murphy.resource')
resource.setProperty('shared', dbus.Boolean(True, variant_level=1))
# Track pending request separately, because status == 'pending'
# either means something else ('unknown'?) or is buggy/unreliable.
# See https://github.com/01org/murphy/issues/5
pending = False
def propertyChanged(prop, value):
global pending
log('property changed: %s = %s', prop, value)
if prop == 'status':
if value == 'acquired':
# Success!
loop.quit()
elif value == 'lost':
# Not yet?!
log('Murphy request failed, waiting for resource to become available.')
pending = False
elif value == 'pending':
pass
elif value == 'available':
if not pending:
log('Murphy request may succeed now, try again.')
resourceset.request()
pending = True
else:
log('Unexpected status: %s', value)
try:
match = bus.add_signal_receiver(propertyChanged, 'propertyChanged', 'org.murphy.resourceset', 'org.Murphy', resourcesetpath)
resourceset.request()
pending = True
loop.run()
finally:
match.remove()
log('=== starting %s ===', self.name)
self.execute()
self.status = Action.DONE
@ -191,6 +259,18 @@ class Action:
traceback.print_exc()
self.status = Action.FAILED
self.summary = str(inst)
finally:
try:
if locked:
log('=== unlocking resource %s ===', self.name)
resourceset.release()
if resourceset:
log('=== deleting resource set %s ===', self.name)
resourceset.delete()
if locked or resourceset:
log('=== done with Murphy ===')
except Exception:
traceback.print_exc()
log('\n=== %s: %s ===', self.name, self.status)
sys.stdout.flush()