[CalendarServer-changes] [11437] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Wed Jun 26 01:25:25 PDT 2013


Revision: 11437
          http://trac.calendarserver.org//changeset/11437
Author:   glyph at apple.com
Date:     2013-06-26 01:25:25 -0700 (Wed, 26 Jun 2013)
Log Message:
-----------
CFFI Bindings for LaunchD on-demand socket activation.

Modified Paths:
--------------
    CalendarServer/trunk/calendarserver/tools/agent.py
    CalendarServer/trunk/setup.py

Added Paths:
-----------
    CalendarServer/trunk/twext/python/launchd.py
    CalendarServer/trunk/twext/python/test/test_launchd.py

Property Changed:
----------------
    CalendarServer/trunk/


Property changes on: CalendarServer/trunk
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalDAVTester/trunk:11193-11198
/CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/generic-sqlstore:6167-6191
/CalendarServer/branches/new-store:5594-5934
/CalendarServer/branches/new-store-no-caldavfile:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2:5936-5981
/CalendarServer/branches/release/CalendarServer-4.3-dev:10180-10190,10192
/CalendarServer/branches/users/cdaboo/batchupload-6699:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
/CalendarServer/branches/users/cdaboo/component-set-fixes:8130-8346
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/implicituidrace:8137-8141
/CalendarServer/branches/users/cdaboo/ischedule-dkim:9747-9979
/CalendarServer/branches/users/cdaboo/managed-attachments:9985-10145
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/pods:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar:7085-7206
/CalendarServer/branches/users/cdaboo/pycard:7227-7237
/CalendarServer/branches/users/cdaboo/queued-attendee-refreshes:7740-8287
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/cdaboo/store-scheduling:10876-11129
/CalendarServer/branches/users/cdaboo/timezones:7443-7699
/CalendarServer/branches/users/cdaboo/txn-debugging:8730-8743
/CalendarServer/branches/users/gaya/sharedgroups-3:11088-11204
/CalendarServer/branches/users/glyph/always-abort-txn-on-error:9958-9969
/CalendarServer/branches/users/glyph/case-insensitive-uid:8772-8805
/CalendarServer/branches/users/glyph/conn-limit:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/dalify:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect:6824-6876
/CalendarServer/branches/users/glyph/deploybuild:7563-7572
/CalendarServer/branches/users/glyph/digest-auth-redux:10624-10635
/CalendarServer/branches/users/glyph/disable-quota:7718-7727
/CalendarServer/branches/users/glyph/dont-start-postgres:6592-6614
/CalendarServer/branches/users/glyph/imip-and-admin-html:7866-7984
/CalendarServer/branches/users/glyph/ipv6-client:9054-9105
/CalendarServer/branches/users/glyph/linux-tests:6893-6900
/CalendarServer/branches/users/glyph/migrate-merge:8690-8713
/CalendarServer/branches/users/glyph/misc-portability-fixes:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
/CalendarServer/branches/users/glyph/multiget-delete:8321-8330
/CalendarServer/branches/users/glyph/new-export:7444-7485
/CalendarServer/branches/users/glyph/one-home-list-api:10048-10073
/CalendarServer/branches/users/glyph/oracle:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls:7340-7351
/CalendarServer/branches/users/glyph/other-html:8062-8091
/CalendarServer/branches/users/glyph/parallel-sim:8240-8251
/CalendarServer/branches/users/glyph/parallel-upgrade:8376-8400
/CalendarServer/branches/users/glyph/parallel-upgrade_to_1:8571-8583
/CalendarServer/branches/users/glyph/q:9560-9688
/CalendarServer/branches/users/glyph/queue-locking-and-timing:10204-10289
/CalendarServer/branches/users/glyph/quota:7604-7637
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/shared-pool-fixes:8436-8443
/CalendarServer/branches/users/glyph/shared-pool-take2:8155-8174
/CalendarServer/branches/users/glyph/sharedpool:6490-6550
/CalendarServer/branches/users/glyph/sharing-api:9192-9205
/CalendarServer/branches/users/glyph/skip-lonely-vtimezones:8524-8535
/CalendarServer/branches/users/glyph/sql-store:5929-6073
/CalendarServer/branches/users/glyph/start-service-start-loop:11060-11065
/CalendarServer/branches/users/glyph/subtransactions:7248-7258
/CalendarServer/branches/users/glyph/table-alias:8651-8664
/CalendarServer/branches/users/glyph/uidexport:7673-7676
/CalendarServer/branches/users/glyph/unshare-when-access-revoked:10562-10595
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/glyph/uuid-normalize:9268-9296
/CalendarServer/branches/users/glyph/warning-cleanups:11347-11357
/CalendarServer/branches/users/glyph/xattrs-from-files:7757-7769
/CalendarServer/branches/users/sagen/applepush:8126-8184
/CalendarServer/branches/users/sagen/inboxitems:7380-7381
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
/CalendarServer/branches/users/sagen/testing:10827-10851,10853-10855
/CalendarServer/branches/users/wsanchez/transations:5515-5593
   + /CalDAVTester/trunk:11193-11198
/CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/generic-sqlstore:6167-6191
/CalendarServer/branches/new-store:5594-5934
/CalendarServer/branches/new-store-no-caldavfile:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2:5936-5981
/CalendarServer/branches/release/CalendarServer-4.3-dev:10180-10190,10192
/CalendarServer/branches/users/cdaboo/batchupload-6699:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
/CalendarServer/branches/users/cdaboo/component-set-fixes:8130-8346
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/implicituidrace:8137-8141
/CalendarServer/branches/users/cdaboo/ischedule-dkim:9747-9979
/CalendarServer/branches/users/cdaboo/managed-attachments:9985-10145
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/pods:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar:7085-7206
/CalendarServer/branches/users/cdaboo/pycard:7227-7237
/CalendarServer/branches/users/cdaboo/queued-attendee-refreshes:7740-8287
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/cdaboo/store-scheduling:10876-11129
/CalendarServer/branches/users/cdaboo/timezones:7443-7699
/CalendarServer/branches/users/cdaboo/txn-debugging:8730-8743
/CalendarServer/branches/users/gaya/sharedgroups-3:11088-11204
/CalendarServer/branches/users/glyph/always-abort-txn-on-error:9958-9969
/CalendarServer/branches/users/glyph/case-insensitive-uid:8772-8805
/CalendarServer/branches/users/glyph/conn-limit:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/dalify:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect:6824-6876
/CalendarServer/branches/users/glyph/deploybuild:7563-7572
/CalendarServer/branches/users/glyph/digest-auth-redux:10624-10635
/CalendarServer/branches/users/glyph/disable-quota:7718-7727
/CalendarServer/branches/users/glyph/dont-start-postgres:6592-6614
/CalendarServer/branches/users/glyph/imip-and-admin-html:7866-7984
/CalendarServer/branches/users/glyph/ipv6-client:9054-9105
/CalendarServer/branches/users/glyph/launchd-wrapper-bis:11413-11436
/CalendarServer/branches/users/glyph/linux-tests:6893-6900
/CalendarServer/branches/users/glyph/migrate-merge:8690-8713
/CalendarServer/branches/users/glyph/misc-portability-fixes:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
/CalendarServer/branches/users/glyph/multiget-delete:8321-8330
/CalendarServer/branches/users/glyph/new-export:7444-7485
/CalendarServer/branches/users/glyph/one-home-list-api:10048-10073
/CalendarServer/branches/users/glyph/oracle:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls:7340-7351
/CalendarServer/branches/users/glyph/other-html:8062-8091
/CalendarServer/branches/users/glyph/parallel-sim:8240-8251
/CalendarServer/branches/users/glyph/parallel-upgrade:8376-8400
/CalendarServer/branches/users/glyph/parallel-upgrade_to_1:8571-8583
/CalendarServer/branches/users/glyph/q:9560-9688
/CalendarServer/branches/users/glyph/queue-locking-and-timing:10204-10289
/CalendarServer/branches/users/glyph/quota:7604-7637
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/shared-pool-fixes:8436-8443
/CalendarServer/branches/users/glyph/shared-pool-take2:8155-8174
/CalendarServer/branches/users/glyph/sharedpool:6490-6550
/CalendarServer/branches/users/glyph/sharing-api:9192-9205
/CalendarServer/branches/users/glyph/skip-lonely-vtimezones:8524-8535
/CalendarServer/branches/users/glyph/sql-store:5929-6073
/CalendarServer/branches/users/glyph/start-service-start-loop:11060-11065
/CalendarServer/branches/users/glyph/subtransactions:7248-7258
/CalendarServer/branches/users/glyph/table-alias:8651-8664
/CalendarServer/branches/users/glyph/uidexport:7673-7676
/CalendarServer/branches/users/glyph/unshare-when-access-revoked:10562-10595
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/glyph/uuid-normalize:9268-9296
/CalendarServer/branches/users/glyph/warning-cleanups:11347-11357
/CalendarServer/branches/users/glyph/xattrs-from-files:7757-7769
/CalendarServer/branches/users/sagen/applepush:8126-8184
/CalendarServer/branches/users/sagen/inboxitems:7380-7381
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
/CalendarServer/branches/users/sagen/testing:10827-10851,10853-10855
/CalendarServer/branches/users/wsanchez/transations:5515-5593

Modified: CalendarServer/trunk/calendarserver/tools/agent.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/agent.py	2013-06-26 08:22:25 UTC (rev 11436)
+++ CalendarServer/trunk/calendarserver/tools/agent.py	2013-06-26 08:25:25 UTC (rev 11437)
@@ -1,5 +1,5 @@
 #!/usr/bin/env python
-
+# -*- test-case-name: calendarserver.tools.test.test_agent -*-
 ##
 # Copyright (c) 2013 Apple Inc. All rights reserved.
 #
@@ -15,6 +15,15 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 ##
+
+"""
+A service spawned on-demand by launchd, meant to handle configuration requests
+from Server.app.  When a request comes in on the socket specified in the launchd
+agent.plist, launchd will run "caldavd -t Agent" which ends up creating this
+service.  Requests are made using HTTP POSTS to /gateway, and are authenticated
+by OpenDirectory.
+"""
+
 from __future__ import print_function
 
 import cStringIO
@@ -36,28 +45,11 @@
 from twisted.web.server import Site, NOT_DONE_YET
 from zope.interface import implements
 
-
-# TODO, implement this:
-# from launchd import getLaunchdSocketFds
-
-def getLaunchdSocketFds():
-    pass
-
-# For the sample client, below:
-from twisted.internet import reactor
-from twisted.internet.protocol import ClientCreator
-
+from twext.python.launchd import getLaunchDSocketFDs
 from twext.python.log import Logger
 log = Logger()
 
 
-"""
-A service spawned on-demand by launchd, meant to handle configuration requests
-from Server.app.  When a request comes in on the socket specified in the launchd
-agent.plist, launchd will run "caldavd -t Agent" which ends up creating this
-service.  Requests are made using HTTP POSTS to /gateway, and are authenticated
-by OpenDirectory.
-"""
 
 class DirectoryServiceChecker:
     """
@@ -232,7 +224,7 @@
     """
     from twisted.internet import reactor
 
-    sockets = getLaunchdSocketFds() 
+    sockets = getLaunchDSocketFDs() 
     fd = sockets["AgentSocket"][0]
     
     family = socket.AF_INET
@@ -353,8 +345,16 @@
 </plist>"""
 
 def getList():
+    # For the sample client, below:
+    from twisted.internet import reactor
+    from twisted.internet.protocol import ClientCreator
+
     creator = ClientCreator(reactor, amp.AMP)
-    d = creator.connectTCP('sagen.apple.com', 62308)
+    host = '127.0.0.1'
+    import sys
+    if len(sys.argv) > 1:
+        host = sys.argv[1]
+    d = creator.connectTCP(host, 62308)
 
     def connected(ampProto):
         return ampProto.callRemote(GatewayAMPCommand, command=command)
@@ -368,7 +368,7 @@
         print('Done: %s' % (result,))
         reactor.stop()
     d.addCallback(done)
+    reactor.run()
 
 if __name__ == '__main__':
     getList()
-    reactor.run()

Modified: CalendarServer/trunk/setup.py
===================================================================
--- CalendarServer/trunk/setup.py	2013-06-26 08:22:25 UTC (rev 11436)
+++ CalendarServer/trunk/setup.py	2013-06-26 08:25:25 UTC (rev 11437)
@@ -88,8 +88,11 @@
         )
     )
 
+    from twext.python import launchd
+    extensions.append(launchd.ffi.verifier.get_extension())
 
 
+
 #
 # Run setup
 #

Copied: CalendarServer/trunk/twext/python/launchd.py (from rev 11436, CalendarServer/branches/users/glyph/launchd-wrapper-bis/twext/python/launchd.py)
===================================================================
--- CalendarServer/trunk/twext/python/launchd.py	                        (rev 0)
+++ CalendarServer/trunk/twext/python/launchd.py	2013-06-26 08:25:25 UTC (rev 11437)
@@ -0,0 +1,294 @@
+# -*- test-case-name: twext.python.test.test_launchd -*-
+##
+# Copyright (c) 2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Bindings for launchd check-in API.
+
+ at see: U{SampleD.c
+    <http://developer.apple.com/library/mac/#samplecode/SampleD/>}
+
+ at var ffi: a L{cffi.FFI} instance wrapping the functions exposed by C{launch.h}.
+
+ at var lib: a L{cffi} "U{dynamic library object
+    <http://cffi.readthedocs.org/en/release-0.6/#the-verification-step>}"
+    wrapping the functions exposed by C{launch.h}.
+
+ at var constants: Select C{LAUNCH_*} constants from C{launch.h}, exposed as plain
+    Python values.  Note that this is not a complete wrapping, but as the
+    header file suggests, these APIs are only for use during check-in.
+"""
+
+from __future__ import print_function
+
+from cffi import FFI, VerificationError
+
+ffi = FFI()
+
+ffi.cdef("""
+
+static const char* LAUNCH_KEY_CHECKIN;
+static const char* LAUNCH_JOBKEY_LABEL;
+static const char* LAUNCH_JOBKEY_SOCKETS;
+
+typedef enum {
+    LAUNCH_DATA_DICTIONARY = 1,
+    LAUNCH_DATA_ARRAY,
+    LAUNCH_DATA_FD,
+    LAUNCH_DATA_INTEGER,
+    LAUNCH_DATA_REAL,
+    LAUNCH_DATA_BOOL,
+    LAUNCH_DATA_STRING,
+    LAUNCH_DATA_OPAQUE,
+    LAUNCH_DATA_ERRNO,
+    LAUNCH_DATA_MACHPORT,
+} launch_data_type_t;
+
+typedef struct _launch_data *launch_data_t;
+
+bool launch_data_dict_insert(launch_data_t, const launch_data_t, const char *);
+
+launch_data_t launch_data_alloc(launch_data_type_t);
+launch_data_t launch_data_new_string(const char *);
+launch_data_t launch_data_new_integer(long long);
+launch_data_t launch_data_new_fd(int);
+launch_data_t launch_data_new_bool(bool);
+launch_data_t launch_data_new_real(double);
+launch_data_t launch_msg(const launch_data_t);
+
+launch_data_type_t launch_data_get_type(const launch_data_t);
+
+launch_data_t launch_data_dict_lookup(const launch_data_t, const char *);
+size_t launch_data_dict_get_count(const launch_data_t);
+long long launch_data_get_integer(const launch_data_t);
+void launch_data_dict_iterate(
+    const launch_data_t, void (*)(const launch_data_t, const char *, void *),
+    void *);
+
+int launch_data_get_fd(const launch_data_t);
+bool launch_data_get_bool(const launch_data_t);
+const char * launch_data_get_string(const launch_data_t);
+double launch_data_get_real(const launch_data_t);
+
+size_t launch_data_array_get_count(const launch_data_t);
+launch_data_t launch_data_array_get_index(const launch_data_t, size_t);
+bool launch_data_array_set_index(launch_data_t, const launch_data_t, size_t);
+
+void launch_data_free(launch_data_t);
+""")
+
+try:
+    lib = ffi.verify("""
+    #include <launch.h>
+    """,
+    tag=__name__.replace(".", "_"))
+except VerificationError as ve:
+    raise ImportError(ve)
+
+
+
+class _LaunchArray(object):
+    def __init__(self, launchdata):
+        self.launchdata = launchdata
+
+
+    def __len__(self):
+        return lib.launch_data_array_get_count(self.launchdata)
+
+
+    def __getitem__(self, index):
+        if index >= len(self):
+            raise IndexError(index)
+        return _launchify(
+            lib.launch_data_array_get_index(self.launchdata, index)
+        )
+
+
+
+class _LaunchDictionary(object):
+    def __init__(self, launchdata):
+        self.launchdata = launchdata
+
+
+    def keys(self):
+        """
+        Return keys in the dictionary.
+        """
+        keys = []
+        @ffi.callback("void (*)(const launch_data_t, const char *, void *)")
+        def icb(v, k, n):
+            keys.append(ffi.string(k))
+        lib.launch_data_dict_iterate(self.launchdata, icb, ffi.NULL)
+        return keys
+
+
+    def values(self):
+        """
+        Return values in the dictionary.
+        """
+        values = []
+        @ffi.callback("void (*)(const launch_data_t, const char *, void *)")
+        def icb(v, k, n):
+            values.append(_launchify(v))
+        lib.launch_data_dict_iterate(self.launchdata, icb, ffi.NULL)
+        return values
+
+
+    def items(self):
+        """
+        Return items in the dictionary.
+        """
+        values = []
+        @ffi.callback("void (*)(const launch_data_t, const char *, void *)")
+        def icb(v, k, n):
+            values.append((ffi.string(k), _launchify(v)))
+        lib.launch_data_dict_iterate(self.launchdata, icb, ffi.NULL)
+        return values
+
+
+    def __getitem__(self, key):
+        launchvalue = lib.launch_data_dict_lookup(self.launchdata, key)
+        try:
+            return _launchify(launchvalue)
+        except LaunchErrno:
+            raise KeyError(key)
+
+
+    def __len__(self):
+        return lib.launch_data_dict_get_count(self.launchdata)
+
+
+
+def plainPython(x):
+    """
+    Convert a launchd python-like data structure into regular Python
+    dictionaries and lists.
+    """
+    if isinstance(x, _LaunchDictionary):
+        result = {}
+        for k, v in x.items():
+            result[k] = plainPython(v)
+        return result
+    elif isinstance(x, _LaunchArray):
+        return map(plainPython, x)
+    else:
+        return x
+
+
+
+
+class LaunchErrno(Exception):
+    """
+    Error from launchd.
+    """
+
+
+
+def _launchify(launchvalue):
+    """
+    Convert a ctypes value wrapping a C{_launch_data} structure into the
+    relevant Python object (integer, bytes, L{_LaunchDictionary},
+    L{_LaunchArray}).
+    """
+    if launchvalue == ffi.NULL:
+        return None
+    dtype = lib.launch_data_get_type(launchvalue)
+
+    if dtype == lib.LAUNCH_DATA_DICTIONARY:
+        return _LaunchDictionary(launchvalue)
+    elif dtype == lib.LAUNCH_DATA_ARRAY:
+        return _LaunchArray(launchvalue)
+    elif dtype == lib.LAUNCH_DATA_FD:
+        return lib.launch_data_get_fd(launchvalue)
+    elif dtype == lib.LAUNCH_DATA_INTEGER:
+        return lib.launch_data_get_integer(launchvalue)
+    elif dtype == lib.LAUNCH_DATA_REAL:
+        return lib.launch_data_get_real(launchvalue)
+    elif dtype == lib.LAUNCH_DATA_BOOL:
+        return lib.launch_data_get_bool(launchvalue)
+    elif dtype == lib.LAUNCH_DATA_STRING:
+        cvalue = lib.launch_data_get_string(launchvalue)
+        if cvalue == ffi.NULL:
+            return None
+        return ffi.string(cvalue)
+    elif dtype == lib.LAUNCH_DATA_OPAQUE:
+        return launchvalue
+    elif dtype == lib.LAUNCH_DATA_ERRNO:
+        raise LaunchErrno(launchvalue)
+    elif dtype == lib.LAUNCH_DATA_MACHPORT:
+        return lib.launch_data_get_machport(launchvalue)
+    else:
+        raise TypeError("Unknown Launch Data Type", dtype)
+
+
+
+def checkin():
+    """
+    Perform a launchd checkin, returning a Pythonic wrapped data structure
+    representing the retrieved check-in plist.
+
+    @return: a C{dict}-like object.
+    """
+    lkey = lib.launch_data_new_string(lib.LAUNCH_KEY_CHECKIN)
+    msgr = lib.launch_msg(lkey)
+    return _launchify(msgr)
+
+
+
+def _managed(obj):
+    """
+    Automatically free an object that was allocated with a launch_data_*
+    function, or raise L{MemoryError} if it's C{NULL}.
+    """
+    if obj == ffi.NULL:
+        raise MemoryError()
+    else:
+        return ffi.gc(obj, lib.launch_data_free)
+
+
+
+class _Strings(object):
+    """
+    Expose constants as Python-readable values rather than wrapped ctypes
+    pointers.
+    """
+    def __getattribute__(self, name):
+        value = getattr(lib, name)
+        if isinstance(value, int):
+            return value
+        if ffi.typeof(value) != ffi.typeof("char *"):
+            raise AttributeError("no such constant", name)
+        return ffi.string(value)
+
+constants = _Strings()
+
+
+
+def getLaunchDSocketFDs():
+    """
+    Perform checkin via L{checkin} and return just a dictionary mapping the
+    sockets to file descriptors.
+    """
+    return plainPython(checkin()[constants.LAUNCH_JOBKEY_SOCKETS])
+
+
+
+__all__ = [
+    'checkin',
+    'lib',
+    'ffi',
+    'plainPython',
+]

Copied: CalendarServer/trunk/twext/python/test/test_launchd.py (from rev 11436, CalendarServer/branches/users/glyph/launchd-wrapper-bis/twext/python/test/test_launchd.py)
===================================================================
--- CalendarServer/trunk/twext/python/test/test_launchd.py	                        (rev 0)
+++ CalendarServer/trunk/twext/python/test/test_launchd.py	2013-06-26 08:25:25 UTC (rev 11437)
@@ -0,0 +1,397 @@
+##
+# Copyright (c) 2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Tests for L{twext.python.launchd}.
+"""
+
+import sys, os, plistlib, socket, json
+
+if __name__ == '__main__':
+    # This module is loaded as a launchd job by test-cases below; the following
+    # code looks up an appropriate function to run.
+    testID = sys.argv[1]
+    a, b = testID.rsplit(".", 1)
+    from twisted.python.reflect import namedAny
+    try:
+        namedAny(".".join([a, b.replace("test_", "job_")]))()
+    finally:
+        sys.stdout.flush()
+        sys.stderr.flush()
+        skt = socket.socket()
+        skt.connect(("127.0.0.1", int(os.environ["TESTING_PORT"])))
+    sys.exit(0)
+
+
+
+try:
+    from twext.python.launchd import (
+        lib, ffi, _LaunchDictionary, _LaunchArray, _managed, constants,
+        plainPython, checkin, _launchify, getLaunchDSocketFDs
+    )
+except ImportError:
+    skip = "LaunchD not available."
+else:
+    skip = False
+
+from twisted.trial.unittest import TestCase
+from twisted.python.filepath import FilePath
+
+
+class LaunchDataStructures(TestCase):
+    """
+    Tests for L{_launchify} converting data structures from launchd's internals
+    to Python objects.
+    """
+
+    def test_fd(self):
+        """
+        Test converting a launchd FD to an integer.
+        """
+        fd = _managed(lib.launch_data_new_fd(2))
+        self.assertEquals(_launchify(fd), 2)
+
+
+    def test_bool(self):
+        """
+        Test converting a launchd bool to a Python bool.
+        """
+        t = _managed(lib.launch_data_new_bool(True))
+        f = _managed(lib.launch_data_new_bool(False))
+        self.assertEqual(_launchify(t), True)
+        self.assertEqual(_launchify(f), False)
+
+
+    def test_real(self):
+        """
+        Test converting a launchd real to a Python float.
+        """
+        notQuitePi = _managed(lib.launch_data_new_real(3.14158))
+        self.assertEqual(_launchify(notQuitePi), 3.14158)
+
+
+
+class DictionaryTests(TestCase):
+    """
+    Tests for L{_LaunchDictionary}
+    """
+
+    def setUp(self):
+        """
+        Assemble a test dictionary.
+        """
+        self.testDict = _managed(
+            lib.launch_data_alloc(lib.LAUNCH_DATA_DICTIONARY)
+        )
+        key1 = ffi.new("char[]", "alpha")
+        val1 = lib.launch_data_new_string("alpha-value")
+        key2 = ffi.new("char[]", "beta")
+        val2 = lib.launch_data_new_string("beta-value")
+        key3 = ffi.new("char[]", "gamma")
+        val3 = lib.launch_data_new_integer(3)
+        lib.launch_data_dict_insert(self.testDict, val1, key1)
+        lib.launch_data_dict_insert(self.testDict, val2, key2)
+        lib.launch_data_dict_insert(self.testDict, val3, key3)
+        self.assertEquals(lib.launch_data_dict_get_count(self.testDict), 3)
+
+
+    def test_len(self):
+        """
+        C{len(_LaunchDictionary())} returns the number of keys in the
+        dictionary.
+        """
+        self.assertEquals(len(_LaunchDictionary(self.testDict)), 3)
+
+
+    def test_keys(self):
+        """
+        L{_LaunchDictionary.keys} returns keys present in a C{launch_data_dict}.
+        """
+        dictionary = _LaunchDictionary(self.testDict)
+        self.assertEquals(set(dictionary.keys()),
+                          set([b"alpha", b"beta", b"gamma"]))
+
+
+    def test_values(self):
+        """
+        L{_LaunchDictionary.values} returns keys present in a
+        C{launch_data_dict}.
+        """
+        dictionary = _LaunchDictionary(self.testDict)
+        self.assertEquals(set(dictionary.values()),
+                          set([b"alpha-value", b"beta-value", 3]))
+
+
+    def test_items(self):
+        """
+        L{_LaunchDictionary.items} returns all (key, value) tuples present in a
+        C{launch_data_dict}.
+        """
+        dictionary = _LaunchDictionary(self.testDict)
+        self.assertEquals(set(dictionary.items()),
+                          set([(b"alpha", b"alpha-value"),
+                               (b"beta", b"beta-value"), (b"gamma", 3)]))
+
+
+    def test_plainPython(self):
+        """
+        L{plainPython} will convert a L{_LaunchDictionary} into a Python
+        dictionary.
+        """
+        self.assertEquals({b"alpha": b"alpha-value", b"beta": b"beta-value",
+                           b"gamma": 3},
+                           plainPython(_LaunchDictionary(self.testDict)))
+
+
+    def test_plainPythonNested(self):
+        """
+        L{plainPython} will convert a L{_LaunchDictionary} containing another
+        L{_LaunchDictionary} into a nested Python dictionary.
+        """
+        otherDict = lib.launch_data_alloc(lib.LAUNCH_DATA_DICTIONARY)
+        lib.launch_data_dict_insert(otherDict,
+                                    lib.launch_data_new_string("bar"), "foo")
+        lib.launch_data_dict_insert(self.testDict, otherDict, "delta")
+        self.assertEquals({b"alpha": b"alpha-value", b"beta": b"beta-value",
+                           b"gamma": 3, b"delta": {b"foo": b"bar"}},
+                           plainPython(_LaunchDictionary(self.testDict)))
+
+
+class ArrayTests(TestCase):
+    """
+    Tests for L{_LaunchArray}
+    """
+
+    def setUp(self):
+        """
+        Assemble a test array.
+        """
+        self.testArray = ffi.gc(
+            lib.launch_data_alloc(lib.LAUNCH_DATA_ARRAY),
+            lib.launch_data_free
+        )
+
+        lib.launch_data_array_set_index(
+            self.testArray, lib.launch_data_new_string("test-string-1"), 0
+        )
+        lib.launch_data_array_set_index(
+            self.testArray, lib.launch_data_new_string("another string."), 1
+        )
+        lib.launch_data_array_set_index(
+            self.testArray, lib.launch_data_new_integer(4321), 2
+        )
+
+
+    def test_length(self):
+        """
+        C{len(_LaunchArray(...))} returns the number of elements in the array.
+        """
+        self.assertEquals(len(_LaunchArray(self.testArray)), 3)
+
+
+    def test_indexing(self):
+        """
+        C{_LaunchArray(...)[n]} returns the n'th element in the array.
+        """
+        array = _LaunchArray(self.testArray)
+        self.assertEquals(array[0], b"test-string-1")
+        self.assertEquals(array[1], b"another string.")
+        self.assertEquals(array[2], 4321)
+
+
+    def test_indexTooBig(self):
+        """
+        C{_LaunchArray(...)[n]}, where C{n} is greater than the length of the
+        array, raises an L{IndexError}.
+        """
+        array = _LaunchArray(self.testArray)
+        self.assertRaises(IndexError, lambda: array[3])
+
+
+    def test_iterating(self):
+        """
+        Iterating over a C{_LaunchArray} returns each item in sequence.
+        """
+        array = _LaunchArray(self.testArray)
+        i = iter(array)
+        self.assertEquals(i.next(), b"test-string-1")
+        self.assertEquals(i.next(), b"another string.")
+        self.assertEquals(i.next(), 4321)
+        self.assertRaises(StopIteration, i.next)
+
+
+    def test_plainPython(self):
+        """
+        L{plainPython} converts a L{_LaunchArray} into a Python list.
+        """
+        array = _LaunchArray(self.testArray)
+        self.assertEquals(plainPython(array),
+                          [b"test-string-1", b"another string.", 4321])
+
+
+    def test_plainPythonNested(self):
+        """
+        L{plainPython} converts a L{_LaunchArray} containing another
+        L{_LaunchArray} into a Python list.
+        """
+        sub = lib.launch_data_alloc(lib.LAUNCH_DATA_ARRAY)
+        lib.launch_data_array_set_index(sub, lib.launch_data_new_integer(7), 0)
+        lib.launch_data_array_set_index(self.testArray, sub, 3)
+        array = _LaunchArray(self.testArray)
+        self.assertEqual(plainPython(array), [b"test-string-1",
+                                              b"another string.", 4321, [7]])
+
+
+
+class SimpleStringConstants(TestCase):
+    """
+    Tests for bytestring-constants wrapping.
+    """
+
+    def test_constant(self):
+        """
+        C{launchd.constants.LAUNCH_*} will return a bytes object corresponding
+        to a constant.
+        """
+        self.assertEqual(constants.LAUNCH_JOBKEY_SOCKETS,
+                         b"Sockets")
+        self.assertRaises(AttributeError, getattr, constants,
+                          "launch_data_alloc")
+        self.assertEquals(constants.LAUNCH_DATA_ARRAY, 2)
+
+
+
+class CheckInTests(TestCase):
+    """
+    Integration tests making sure that actual checkin with launchd results in
+    the expected values.
+    """
+
+    def setUp(self):
+        fp = FilePath(self.mktemp())
+        fp.makedirs()
+        from twisted.internet.protocol import Protocol, Factory
+        from twisted.internet import reactor, defer
+        d = defer.Deferred()
+        class JustLetMeMoveOn(Protocol):
+            def connectionMade(self):
+                d.callback(None)
+                self.transport.abortConnection()
+        f = Factory()
+        f.protocol = JustLetMeMoveOn
+        port = reactor.listenTCP(0, f, interface="127.0.0.1")
+        @self.addCleanup
+        def goodbyePort():
+            return port.stopListening()
+        env = dict(os.environ)
+        env["TESTING_PORT"] = repr(port.getHost().port)
+        self.stdout = fp.child("stdout.txt")
+        self.stderr = fp.child("stderr.txt")
+        self.launchLabel = ("org.calendarserver.UNIT-TESTS." +
+                            str(os.getpid()) + "." + self.id())
+        plist = {
+            "Label": self.launchLabel,
+            "ProgramArguments": [sys.executable, "-m", __name__, self.id()],
+            "EnvironmentVariables": env,
+            "KeepAlive": False,
+            "StandardOutPath": self.stdout.path,
+            "StandardErrorPath": self.stderr.path,
+            "Sockets": {
+                "Awesome": [{"SecureSocketWithKey": "GeneratedSocket"}]
+            },
+            "RunAtLoad": True,
+        }
+        self.job = fp.child("job.plist")
+        self.job.setContent(plistlib.writePlistToString(plist))
+        os.spawnlp(os.P_WAIT, "launchctl", "launchctl", "load", self.job.path)
+        return d
+
+
+    @staticmethod
+    def job_test():
+        """
+        Do something observable in a subprocess.
+        """
+        sys.stdout.write("Sample Value.")
+        sys.stdout.flush()
+
+
+    def test_test(self):
+        """
+        Since this test framework is somewhat finicky, let's just make sure
+        that a test can complete.
+        """
+        self.assertEquals("Sample Value.", self.stdout.getContent())
+
+
+    @staticmethod
+    def job_checkin():
+        """
+        Check in in the subprocess.
+        """
+        sys.stdout.write(json.dumps(plainPython(checkin())))
+
+
+    def test_checkin(self):
+        """
+        L{checkin} performs launchd checkin and returns a launchd data
+        structure.
+        """
+        d = json.loads(self.stdout.getContent())
+        self.assertEqual(d[constants.LAUNCH_JOBKEY_LABEL], self.launchLabel)
+        self.assertIsInstance(d, dict)
+        sockets = d[constants.LAUNCH_JOBKEY_SOCKETS]
+        self.assertEquals(len(sockets), 1)
+        self.assertEqual(['Awesome'], sockets.keys())
+        awesomeSocket = sockets['Awesome']
+        self.assertEqual(len(awesomeSocket), 1)
+        self.assertIsInstance(awesomeSocket[0], int)
+
+
+    @staticmethod
+    def job_getFDs():
+        """
+        Check-in via the high-level C{getLaunchDSocketFDs} API, that just gives
+        us listening FDs.
+        """
+        sys.stdout.write(json.dumps(getLaunchDSocketFDs()))
+
+
+    def test_getFDs(self):
+        """
+        L{getLaunchDSocketFDs} returns a Python dictionary mapping the names of
+        sockets specified in the property list to lists of integers
+        representing FDs.
+        """
+        sockets = json.loads(self.stdout.getContent())
+        self.assertEquals(len(sockets), 1)
+        self.assertEqual(['Awesome'], sockets.keys())
+        awesomeSocket = sockets['Awesome']
+        self.assertEqual(len(awesomeSocket), 1)
+        self.assertIsInstance(awesomeSocket[0], int)
+
+
+    def tearDown(self):
+        """
+        Un-load the launchd job and report any errors it encountered.
+        """
+        os.spawnlp(os.P_WAIT, "launchctl",
+                   "launchctl", "unload", self.job.path)
+        err = self.stderr.getContent()
+        if 'Traceback' in err:
+            self.fail(err)
+
+
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20130626/439f763a/attachment-0001.html>


More information about the calendarserver-changes mailing list