[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