Esempio n. 1
0
def testSupported2(t, env):
    """Check OPEN delegation handling

    FLAGS: open all
    CODE: OPEN200
    """
    # c1 - OPEN - READ with delegation
    c1 = env.c1.new_client("%s_1" % env.testname(t))
    sess1 = c1.create_session()
    res = create_file(sess1, env.testname(t),
                      access=OPEN4_SHARE_ACCESS_READ |
                      OPEN4_SHARE_ACCESS_WANT_READ_DELEG)
    check(res) # STUB Should check delegation was granted
    fh1 = res.resarray[-1].object
    stateid1 = res.resarray[-2].stateid
    # c2 - OPEN - WRITE
    c2 = env.c1.new_client("%s_2" % env.testname(t))
    sess2 = c2.create_session()
    owner = open_owner4(0, "My Open Owner 2")
    claim = open_claim4(CLAIM_NULL, env.testname(t))
    how = openflag4(OPEN4_NOCREATE)
    open_op = op.open(0, OPEN4_SHARE_ACCESS_BOTH, OPEN4_SHARE_DENY_NONE,
                      owner, how, claim)
    res = sess2.compound(env.home + [open_op])
    # STUB - since we are not handling callback, deleg_return never gets done
    print res
    check(res)
    fh2 = res.resarray[-1].object
    stateid2 = res.resarray[-2].stateid
    res = close_file(sess1, fh1, stateid=stateid1)
    check(res)
    res = close_file(sess2, fh2, stateid=stateid2)
    check(res)
Esempio n. 2
0
def testReadWrite(t, env):
    """Do a simple READ and WRITE

    FLAGS: open all
    CODE: OPEN400
    """
    c1 = env.c1.new_client(env.testname(t))
    sess1 = c1.create_session()
    owner = open_owner4(0, "My Open Owner")
    how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
    claim = open_claim4(CLAIM_NULL, env.testname(t))
    open_op = op.open(0, OPEN4_SHARE_ACCESS_BOTH , OPEN4_SHARE_DENY_NONE,
                      owner, how, claim)
    fh_op = op.putrootfh()
    res = sess1.compound([fh_op, open_op, op.getfh()]) # OPEN
    print res
    check(res)
    fh = res.resarray[-1].object
    stateid = res.resarray[-2].stateid
    stateid.seqid = 0
    res = sess1.compound([op.putfh(fh), op.write(stateid, 5, FILE_SYNC4, "write test data")])
    print res
    check(res)
    res = sess1.compound([op.putfh(fh), op.read(stateid, 0, 1000)])
    print res
    check(res)
    res = close_file(sess1, fh, stateid=stateid)
    check(res)
Esempio n. 3
0
def testDeadlock(t, env):
    """Trigger deadlock bug

    FLAGS: debug all
    CODE: DEBUG1
    """
    c1 = env.c1.new_client(env.testname(t))
    sess1 = c1.create_session()
    owner = open_owner4(0, "My Open Owner")
    how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE:0}))
    claim = open_claim4(CLAIM_NULL, env.testname(t))
    open_op = op.open(0, OPEN4_SHARE_ACCESS_BOTH , OPEN4_SHARE_DENY_NONE,
                      owner, how, claim)
    res = sess1.compound(env.home + [open_op, op.getfh()]) # OPEN
    fh = res.resarray[-1].object
    stateid = res.resarray[-2].stateid
    ####
    def ops(i):
        return [op.putfh(fh),
                op.write(stateid, i*1000, UNSTABLE4, chr(97+i)*100),
                op.getattr(42950721818L)
                ]
    xids = [sess1.compound_async(ops(i), slot=i) for i in range(4)]
    for xid in xids:
        res = sess1.listen(xid)
        check(res)
        print res
    res = close_file(sess1, fh, stateid=stateid)
    check(res)
Esempio n. 4
0
 def open_file(self, mds_fh):
     seqid=0
     access = const4.OPEN4_SHARE_ACCESS_BOTH
     deny = const4.OPEN4_SHARE_DENY_NONE
     attrs = {const4.FATTR4_MODE: 0777}
     owner = "mds"
     mode = const4.GUARDED4
     verifier = self.sess.c.verifier
     openflag = type4.openflag4(const4.OPEN4_CREATE, type4.createhow4(mode, attrs, verifier))
     name = self.fh_to_name(mds_fh)
     while True:
         if mds_fh in self.filehandles:
             return
         open_op = op4.open(seqid, access, deny,
                           type4.open_owner4(self.sess.client.clientid, owner),
                           openflag, type4.open_claim4(const4.CLAIM_NULL, name))
         res = self._execute(nfs4lib.use_obj(self.path_fh) + [open_op, op4.getfh()], exceptions=[const4.NFS4ERR_EXIST])
         if res.status == const4.NFS4_OK:
              ds_fh = res.resarray[-1].opgetfh.resok4.object
              ds_openstateid = type4.stateid4(0, res.resarray[-2].stateid.other)
              self.filehandles[mds_fh] = (ds_fh, ds_openstateid)
              return
         elif res.status == const4.NFS4ERR_EXIST:
              openflag = type4.openflag4(const4.OPEN4_NOCREATE)
         else:
             raise RuntimeError
Esempio n. 5
0
def testOPENClaimFH(t, env):
    """OPEN file with claim_type is CLAIM_FH

    FLAGS: open all
    CODE: OPEN7
    """
    sess1 = env.c1.new_client_session(env.testname(t))
    res = create_file(sess1, env.testname(t))
    check(res)

    fh = res.resarray[-1].object
    stateid = res.resarray[-2].stateid
    res = close_file(sess1, fh, stateid=stateid)
    check(res)

    claim = open_claim4(CLAIM_FH)
    how = openflag4(OPEN4_NOCREATE)
    oowner = open_owner4(0, "My Open Owner 2")
    open_op = op.open(0, OPEN4_SHARE_ACCESS_BOTH, OPEN4_SHARE_DENY_NONE,
                      oowner, how, claim)
    res = sess1.compound([op.putfh(fh), open_op])
    check(res)

    stateid = res.resarray[-1].stateid
    stateid.seqid = 0
    data = "write test data"
    res = sess1.compound([op.putfh(fh), op.write(stateid, 5, FILE_SYNC4, data)])
    check(res)
    res = sess1.compound([op.putfh(fh), op.read(stateid, 0, 1000)])
    check(res)
    if not res.resarray[-1].eof:
        fail("EOF not set on read")
    desired = "\0"*5 + data
    if res.resarray[-1].data != desired:
        fail("Expected %r, got %r" % (desired, res.resarray[-1].data))
Esempio n. 6
0
def testDeadlock(t, env):
    """Trigger deadlock bug

    FLAGS: debug all
    CODE: DEBUG1
    """
    c1 = env.c1.new_client(env.testname(t))
    sess1 = c1.create_session()
    owner = open_owner4(0, "My Open Owner")
    how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE: 0}))
    claim = open_claim4(CLAIM_NULL, env.testname(t))
    open_op = op.open(0, OPEN4_SHARE_ACCESS_BOTH, OPEN4_SHARE_DENY_NONE, owner,
                      how, claim)
    res = sess1.compound(env.home + [open_op, op.getfh()])  # OPEN
    fh = res.resarray[-1].object
    stateid = res.resarray[-2].stateid

    ####
    def ops(i):
        return [
            op.putfh(fh),
            op.write(stateid, i * 1000, UNSTABLE4,
                     chr(97 + i) * 100),
            op.getattr(42950721818L)
        ]

    xids = [sess1.compound_async(ops(i), slot=i) for i in range(4)]
    for xid in xids:
        res = sess1.listen(xid)
        check(res)
        print res
    res = close_file(sess1, fh, stateid=stateid)
    check(res)
Esempio n. 7
0
def testSupported2(t, env):
    """Check OPEN delegation handling

    FLAGS: open all
    CODE: OPEN200
    """
    # c1 - OPEN - READ with delegation
    c1 = env.c1.new_client("%s_1" % env.testname(t))
    sess1 = c1.create_session()
    res = create_file(sess1,
                      env.testname(t),
                      access=OPEN4_SHARE_ACCESS_READ
                      | OPEN4_SHARE_ACCESS_WANT_READ_DELEG)
    check(res)  # STUB Should check delegation was granted
    fh1 = res.resarray[-1].object
    stateid1 = res.resarray[-2].stateid
    # c2 - OPEN - WRITE
    c2 = env.c1.new_client("%s_2" % env.testname(t))
    sess2 = c2.create_session()
    owner = open_owner4(0, "My Open Owner 2")
    claim = open_claim4(CLAIM_NULL, env.testname(t))
    how = openflag4(OPEN4_NOCREATE)
    open_op = op.open(0, OPEN4_SHARE_ACCESS_BOTH, OPEN4_SHARE_DENY_NONE, owner,
                      how, claim)
    res = sess2.compound(env.home + [open_op])
    # STUB - since we are not handling callback, deleg_return never gets done
    print res
    check(res)
    fh2 = res.resarray[-1].object
    stateid2 = res.resarray[-2].stateid
    res = close_file(sess1, fh1, stateid=stateid1)
    check(res)
    res = close_file(sess2, fh2, stateid=stateid2)
    check(res)
Esempio n. 8
0
def testReadWrite(t, env):
    """Do a simple READ and WRITE

    FLAGS: open all
    CODE: OPEN400
    """
    c1 = env.c1.new_client(env.testname(t))
    sess1 = c1.create_session()
    owner = open_owner4(0, "My Open Owner")
    how = openflag4(OPEN4_CREATE, createhow4(GUARDED4, {FATTR4_SIZE: 0}))
    claim = open_claim4(CLAIM_NULL, env.testname(t))
    open_op = op.open(0, OPEN4_SHARE_ACCESS_BOTH, OPEN4_SHARE_DENY_NONE, owner,
                      how, claim)
    fh_op = op.putrootfh()
    res = sess1.compound([fh_op, open_op, op.getfh()])  # OPEN
    print res
    check(res)
    fh = res.resarray[-1].object
    stateid = res.resarray[-2].stateid
    stateid.seqid = 0
    res = sess1.compound(
        [op.putfh(fh),
         op.write(stateid, 5, FILE_SYNC4, "write test data")])
    print res
    check(res)
    res = sess1.compound([op.putfh(fh), op.read(stateid, 0, 1000)])
    print res
    check(res)
    res = close_file(sess1, fh, stateid=stateid)
    check(res)
Esempio n. 9
0
 def open_file(self, mds_fh):
     seqid = 0
     access = const4.OPEN4_SHARE_ACCESS_BOTH
     deny = const4.OPEN4_SHARE_DENY_NONE
     attrs = {const4.FATTR4_MODE: 0777}
     owner = "mds"
     mode = const4.GUARDED4
     verifier = self.sess.c.verifier
     openflag = type4.openflag4(const4.OPEN4_CREATE,
                                type4.createhow4(mode, attrs, verifier))
     name = self.fh_to_name(mds_fh)
     while True:
         if mds_fh in self.filehandles:
             return
         open_op = op4.open(
             seqid, access, deny,
             type4.open_owner4(self.sess.client.clientid, owner), openflag,
             type4.open_claim4(const4.CLAIM_NULL, name))
         res = self._execute(nfs4lib.use_obj(self.path_fh) +
                             [open_op, op4.getfh()],
                             exceptions=[const4.NFS4ERR_EXIST])
         if res.status == const4.NFS4_OK:
             ds_fh = res.resarray[-1].opgetfh.resok4.object
             ds_openstateid = type4.stateid4(0,
                                             res.resarray[-2].stateid.other)
             self.filehandles[mds_fh] = (ds_fh, ds_openstateid)
             return
         elif res.status == const4.NFS4ERR_EXIST:
             openflag = type4.openflag4(const4.OPEN4_NOCREATE)
         else:
             raise RuntimeError
Esempio n. 10
0
def testShareReservation00(t, env):
    """Test OPEN file with OPEN4_SHARE_DENY_WRITE
       1st client opens file with OPEN4_SHARE_DENY_WRITE
       1st client opens same file with OPEN4_SHARE_ACCESS_BOTH and OPEN4_SHARE_DENY_NONE
           expected reply is NFS4ERR_SHARE_DENIED
       2nd client opens file with OPEN4_SHARE_ACCESS_WRITE
           expected reply is NFS4ERR_SHARE_DENIED
       sleep to force lease of client 1 to expire
       3rd client opens file with OPEN4_SHARE_ACCESS_WRITE
           expected reply is NFS4_OK

    FLAGS: courteous all
    CODE: COUR3
    """

    sess1 = env.c1.new_client_session(env.testname(t))
    res = create_file(sess1,
                      env.testname(t),
                      want_deleg=False,
                      deny=OPEN4_SHARE_DENY_WRITE)
    check(res)
    fh = res.resarray[-1].object
    stateid = res.resarray[-2].stateid

    claim = open_claim4(CLAIM_FH)
    how = openflag4(OPEN4_NOCREATE)
    oowner = open_owner4(0, b"My Open Owner 2")
    access = OPEN4_SHARE_ACCESS_BOTH | OPEN4_SHARE_ACCESS_WANT_NO_DELEG
    open_op = op.open(0, access, OPEN4_SHARE_DENY_NONE, oowner, how, claim)
    res = sess1.compound([op.putfh(fh), open_op])
    check(res, NFS4ERR_SHARE_DENIED)
    log.info("local open conflict detected - PASSED\n")
    """ 2nd client """
    sess2 = env.c1.new_client_session(b"%s_2" % env.testname(t))

    name = env.testname(t)
    owner = b"owner_%s" % name
    path = sess1.c.homedir + [name]
    res = open_file(sess2, owner, path, access=OPEN4_SHARE_ACCESS_WRITE)
    check(res, NFS4ERR_SHARE_DENIED)
    log.info("2nd client open conflict detected - PASSED\n")
    """ force lease of both c1 to expire """
    log.info("force lease to expire...\n")
    lease_time = _getleasetime(sess1)
    env.sleep(lease_time + 10, "the lease period + 10 secs")
    """ 3rd client """
    sess3 = env.c1.new_client_session(b"%s_3" % env.testname(t))
    """ should succeed """
    name = env.testname(t)
    owner = b"owner_%s" % name
    path = sess3.c.homedir + [name]
    res = open_file(sess3, owner, path, access=OPEN4_SHARE_ACCESS_WRITE)
    check(res)
    log.info("3nd client opened OK - no conflict detected - PASSED\n")

    fh = res.resarray[-1].object
    stateid = res.resarray[-2].stateid

    res = close_file(sess3, fh, stateid=stateid)
    check(res)
Esempio n. 11
0
def testOPENClaimFH(t, env):
    """OPEN file with claim_type is CLAIM_FH

    FLAGS: open all
    CODE: OPEN7
    """
    sess1 = env.c1.new_client_session(env.testname(t))
    res = create_file(sess1, env.testname(t))
    check(res)

    fh = res.resarray[-1].object
    stateid = res.resarray[-2].stateid
    res = close_file(sess1, fh, stateid=stateid)
    check(res)

    claim = open_claim4(CLAIM_FH)
    how = openflag4(OPEN4_NOCREATE)
    oowner = open_owner4(0, "My Open Owner 2")
    open_op = op.open(0, OPEN4_SHARE_ACCESS_BOTH, OPEN4_SHARE_DENY_NONE,
                      oowner, how, claim)
    res = sess1.compound([op.putfh(fh), open_op])
    check(res)

    stateid = res.resarray[-1].stateid
    stateid.seqid = 0
    data = "write test data"
    res = sess1.compound([op.putfh(fh), op.write(stateid, 5, FILE_SYNC4, data)])
    check(res)
    res = sess1.compound([op.putfh(fh), op.read(stateid, 0, 1000)])
    check(res)
    if not res.resarray[-1].eof:
        fail("EOF not set on read")
    desired = "\0"*5 + data
    if res.resarray[-1].data != desired:
        fail("Expected %r, got %r" % (desired, res.resarray[-1].data))
Esempio n. 12
0
def testDestroy3(t, env):
    """
	- create client (exchangeid)
	- create session
	- trigger callback; check that it arrives, but don't answer it.
	- destroy session
	- create new session
	- check that a new callback is sent over the new session.  Some
	  servers will do this very quickly, some might take longer.

    FLAGS: destroy_session
    CODE: DSESS9003
    """
    recall = threading.Event()
    def pre_hook(arg, env):
        recall.stateid = arg.stateid # NOTE this must be done before set()
        recall.happened = True
        env.notify = recall.set # This is called after compound sent to queue
    def bad_post_hook(arg, env, res):
        return None;
    def good_post_hook(arg, env, res):
        return res
    c = env.c1.new_client(env.testname(t))
    sess1 = c.create_session()
    sess1.compound([op.reclaim_complete(FALSE)])
    res = create_file(sess1, env.testname(t),
                      access=OPEN4_SHARE_ACCESS_READ |
                      OPEN4_SHARE_ACCESS_WANT_READ_DELEG)
    check(res)
    fh = res.resarray[-1].object
    deleg = res.resarray[-2].delegation
    print("OPEN fh =", repr(fh))
    if deleg.delegation_type == OPEN_DELEGATE_NONE:
        fail("Could not get delegation")
    recall.happened = False
    c2 = env.c1.new_client("%s_2" % env.testname(t))
    sess2 = c2.create_session()
    sess2.compound([op.reclaim_complete(FALSE)])
    claim = open_claim4(CLAIM_NULL, env.testname(t))
    owner = open_owner4(0, "My Open Owner 2")
    how = openflag4(OPEN4_NOCREATE)
    open_op = op.open(0, OPEN4_SHARE_ACCESS_BOTH, OPEN4_SHARE_DENY_NONE,
                      owner, how, claim)
    slot = sess2.compound_async(env.home + [open_op])
    c.cb_pre_hook(OP_CB_RECALL, pre_hook)
    c.cb_post_hook(OP_CB_RECALL, bad_post_hook)
    recall.wait(1) # STUB - deal with timeout
    if not recall.happened:
        fail("Did not get callback")
    recall.happened = False
    # since we did not reply to callback, robust server should retry when
    # we give it a new backchannel to use:
    res = c.c.compound([op.destroy_session(sess1.sessionid)])
    c.cb_pre_hook(OP_CB_RECALL, pre_hook)
    c.cb_post_hook(OP_CB_RECALL, good_post_hook)
    sess3 = c.create_session()
    recall.wait(100) # STUB - deal with timeout
    if not recall.happened:
        fail("Did not get callback")
Esempio n. 13
0
def testDestroy3(t, env):
    """
	- create client (exchangeid)
	- create session
	- trigger callback; check that it arrives, but don't answer it.
	- destroy session
	- create new session
	- check that a new callback is sent over the new session.  Some
	  servers will do this very quickly, some might take longer.

    FLAGS: destroy_session
    CODE: DSESS9003
    """
    recall = threading.Event()
    def pre_hook(arg, env):
        recall.stateid = arg.stateid # NOTE this must be done before set()
        recall.happened = True
        env.notify = recall.set # This is called after compound sent to queue
    def bad_post_hook(arg, env, res):
        return None;
    def good_post_hook(arg, env, res):
        return res
    c = env.c1.new_client(env.testname(t))
    sess1 = c.create_session()
    sess1.compound([op.reclaim_complete(FALSE)])
    res = create_file(sess1, env.testname(t),
                      access=OPEN4_SHARE_ACCESS_READ |
                      OPEN4_SHARE_ACCESS_WANT_READ_DELEG)
    check(res)
    fh = res.resarray[-1].object
    deleg = res.resarray[-2].delegation
    print("OPEN fh =", repr(fh))
    if deleg.delegation_type == OPEN_DELEGATE_NONE:
        fail("Could not get delegation")
    recall.happened = False
    c2 = env.c1.new_client("%s_2" % env.testname(t))
    sess2 = c2.create_session()
    sess2.compound([op.reclaim_complete(FALSE)])
    claim = open_claim4(CLAIM_NULL, env.testname(t))
    owner = open_owner4(0, "My Open Owner 2")
    how = openflag4(OPEN4_NOCREATE)
    open_op = op.open(0, OPEN4_SHARE_ACCESS_BOTH, OPEN4_SHARE_DENY_NONE,
                      owner, how, claim)
    slot = sess2.compound_async(env.home + [open_op])
    c.cb_pre_hook(OP_CB_RECALL, pre_hook)
    c.cb_post_hook(OP_CB_RECALL, bad_post_hook)
    recall.wait(1) # STUB - deal with timeout
    if not recall.happened:
        fail("Did not get callback")
    recall.happened = False
    # since we did not reply to callback, robust server should retry when
    # we give it a new backchannel to use:
    res = c.c.compound([op.destroy_session(sess1.sessionid)])
    c.cb_pre_hook(OP_CB_RECALL, pre_hook)
    c.cb_post_hook(OP_CB_RECALL, good_post_hook)
    sess3 = c.create_session()
    recall.wait(100) # STUB - deal with timeout
    if not recall.happened:
        fail("Did not get callback")
Esempio n. 14
0
def testDestroy2(t, env):
    """
	- create client (exchangeid)
	- create session
	- destroy session
	- create a new session for the same client
	- do something that triggers a callback (look at the delegation
	  tests for example)
	- check that we get the callback

    FLAGS: destroy_session
    CODE: DSESS9002
    """
    recall = threading.Event()

    def pre_hook(arg, env):
        recall.stateid = arg.stateid  # NOTE this must be done before set()
        recall.happened = True
        env.notify = recall.set  # This is called after compound sent to queue

    def post_hook(arg, env, res):
        return res

    c = env.c1.new_client(env.testname(t))
    sess1 = c.create_session()
    sess1.compound([op.reclaim_complete(FALSE)])
    res = c.c.compound([op.destroy_session(sess1.sessionid)])
    sess2 = c.create_session()
    res = create_file(sess2,
                      env.testname(t),
                      access=OPEN4_SHARE_ACCESS_READ
                      | OPEN4_SHARE_ACCESS_WANT_READ_DELEG)
    check(res)
    fh = res.resarray[-1].object
    deleg = res.resarray[-2].delegation
    if deleg.delegation_type == OPEN_DELEGATE_NONE:
        fail("Could not get delegation")
    c2 = env.c1.new_client("%s_2" % env.testname(t))
    sess3 = c2.create_session()
    sess3.compound([op.reclaim_complete(FALSE)])
    claim = open_claim4(CLAIM_NULL, env.testname(t))
    owner = open_owner4(0, "My Open Owner 2")
    how = openflag4(OPEN4_NOCREATE)
    open_op = op.open(0, OPEN4_SHARE_ACCESS_BOTH, OPEN4_SHARE_DENY_NONE, owner,
                      how, claim)
    c.cb_pre_hook(OP_CB_RECALL, pre_hook)
    c.cb_post_hook(OP_CB_RECALL, post_hook)
    slot = sess3.compound_async(env.home + [open_op])
    recall.happened = False
    recall.wait(100)  # STUB - deal with timeout
    if not recall.happened:
        fail("Did not get callback")
Esempio n. 15
0
def testDestroy2(t, env):
    """
	- create client (exchangeid)
	- create session
	- destroy session
	- create a new session for the same client
	- do something that triggers a callback (look at the delegation
	  tests for example)
	- check that we get the callback

    FLAGS: destroy_session
    CODE: DSESS9002
    """
    recall = threading.Event()
    def pre_hook(arg, env):
        recall.stateid = arg.stateid # NOTE this must be done before set()
        recall.happened = True
        env.notify = recall.set # This is called after compound sent to queue
    def post_hook(arg, env, res):
        return res
    c = env.c1.new_client(env.testname(t))
    sess1 = c.create_session()
    sess1.compound([op.reclaim_complete(FALSE)])
    res = c.c.compound([op.destroy_session(sess1.sessionid)])
    sess2 = c.create_session()
    res = create_file(sess2, env.testname(t),
                      access=OPEN4_SHARE_ACCESS_READ |
                      OPEN4_SHARE_ACCESS_WANT_READ_DELEG)
    check(res)
    fh = res.resarray[-1].object
    deleg = res.resarray[-2].delegation
    if deleg.delegation_type == OPEN_DELEGATE_NONE:
        fail("Could not get delegation")
    c2 = env.c1.new_client("%s_2" % env.testname(t))
    sess3 = c2.create_session()
    sess3.compound([op.reclaim_complete(FALSE)])
    claim = open_claim4(CLAIM_NULL, env.testname(t))
    owner = open_owner4(0, "My Open Owner 2")
    how = openflag4(OPEN4_NOCREATE)
    open_op = op.open(0, OPEN4_SHARE_ACCESS_BOTH, OPEN4_SHARE_DENY_NONE,
                      owner, how, claim)
    c.cb_pre_hook(OP_CB_RECALL, pre_hook)
    c.cb_post_hook(OP_CB_RECALL, post_hook)
    slot = sess3.compound_async(env.home + [open_op])
    recall.happened = False
    recall.wait(100) # STUB - deal with timeout
    if not recall.happened:
        fail("Did not get callback")