{ "info": { "author": "Jim Fulton", "author_email": "jim@zope.com", "bugtrack_url": null, "classifiers": [], "description": "=================\nZooKeeper Recipes\n=================\n\ndevtree\n=======\n\nThe devtree recipe sets up temporary ZooKeeper tree for a buildout::\n\n [myproject]\n recipe = zc.zookeeperrecipes:devtree\n import-file = tree.txt\n\n.. -> conf\n\n *** Basics, default path, ***\n\n >>> def write(name, text):\n ... with open(name, 'w') as f: f.write(text)\n\n >>> write('tree.txt', \"\"\"\n ... x=1\n ... type = 'foo'\n ... /a\n ... /b\n ... /c\n ... \"\"\")\n\n >>> import ConfigParser, StringIO, os\n >>> from zc.zookeeperrecipes import DevTree\n\n >>> here = os.getcwd()\n >>> buildoutbuildout = {\n ... 'directory': here,\n ... 'parts-directory': os.path.join(here, 'parts'),\n ... }\n\n >>> from pprint import pprint\n >>> class Raw:\n ... def __init__(self, parent):\n ... self.parent = parent\n ... def __setitem__(self, key, value):\n ... self.parent[key] = value\n ... print 'Raw: ', key\n ... pprint(value)\n\n >>> class TestBuildout(dict):\n ... @property\n ... def _raw(self):\n ... return Raw(self)\n\n >>> def buildout():\n ... parser = ConfigParser.RawConfigParser()\n ... parser.readfp(StringIO.StringIO(conf))\n ... buildout = TestBuildout((name, dict(parser.items(name)))\n ... for name in parser.sections())\n ... [name] = buildout.keys()\n ... buildout['buildout'] = buildoutbuildout\n ... options = buildout[name]\n ... recipe = DevTree(buildout, name, options)\n ... return recipe, options\n\n\n >>> import zc.zookeeperrecipes, mock\n >>> with mock.patch('zc.zookeeperrecipes.timestamp') as ts:\n ... ts.return_value = '2012-01-26T14:50:14.864772'\n ... recipe, options = buildout()\n\n\n >>> pprint(options)\n {'effective-path': '/myproject2012-01-26T14:50:14.864772',\n 'import-file': 'tree.txt',\n 'import-text': \"\\nx=1\\ntype = 'foo'\\n/a\\n /b\\n/c\\n\",\n 'location': '/testdirectory/parts/myproject',\n 'path': '/myproject',\n 'recipe': 'zc.zookeeperrecipes:devtree',\n 'zookeeper': '127.0.0.1:2181'}\n\n >>> recipe.install()\n ()\n\n >>> def cat(*path):\n ... with open(os.path.join(*path)) as f:\n ... return f.read()\n\n >>> cat('parts', 'myproject')\n '/myproject2012-01-26T14:50:14.864772'\n\n *** Test node name is persistent ***\n\n Updating doesn't change the name:\n\n >>> recipe, options = buildout()\n >>> recipe.update()\n ()\n >>> options['effective-path'] == '/myproject2012-01-26T14:50:14.864772'\n True\n >>> cat('parts', 'myproject')\n '/myproject2012-01-26T14:50:14.864772'\n\n >>> import zc.zk\n >>> zk = zc.zk.ZooKeeper('127.0.0.1:2181')\n >>> zk.print_tree()\n /myproject2012-01-26T14:50:14.864772 : foo\n buildout:location = u'/testdirectory/parts/myproject'\n x = 1\n /a\n /b\n /c\n\n *** Test updating tree source ***\n\n If there are changes, we see them\n\n >>> write('tree.txt', \"\"\"\n ... /a\n ... /d\n ... /c\n ... \"\"\")\n\n >>> buildout()[0].install()\n ()\n >>> zk.print_tree()\n /myproject2012-01-26T14:50:14.864772\n buildout:location = u'/testdirectory/parts/myproject'\n /a\n /d\n /c\n\n Now, if there are ephemeral nodes:\n\n >>> with mock.patch('os.getpid') as getpid:\n ... getpid.return_value = 42\n ... zk.register_server('/myproject2012-01-26T14:50:14.864772/a/d',\n ... 'x:y')\n\n >>> write('tree.txt', \"\"\"\n ... /a\n ... /b\n ... /c\n ... \"\"\")\n\n >>> buildout()[0].install() # doctest: +NORMALIZE_WHITESPACE\n Not deleting /myproject2012-01-26T14:50:14.864772/a/d/x:y\n because it's ephemeral.\n /myproject2012-01-26T14:50:14.864772/a/d\n not deleted due to ephemeral descendent.\n ()\n\n >>> zk.print_tree()\n /myproject2012-01-26T14:50:14.864772\n buildout:location = u'/testdirectory/parts/myproject'\n /a\n /b\n /d\n /x:y\n pid = 42\n /c\n\n The ephemeral node, and the node containing it is left, but a\n warning is issued.\n\n *** Cleanup w different part name ***\n\n Now, let's change out buildout to use a different part name:\n\n >>> conf = \"\"\"\n ... [myproj]\n ... recipe = zc.zookeeperrecipes:devtree\n ... import-file = tree.txt\n ... \"\"\"\n\n >>> os.remove(os.path.join('parts', 'myproject'))\n\n Now, when we rerun the buildout, the old tree will get cleaned up:\n\n >>> import signal\n >>> with mock.patch('os.kill') as kill:\n ... with mock.patch('zc.zookeeperrecipes.timestamp') as ts:\n ... ts.return_value = '2012-01-26T14:50:15.864772'\n ... recipe, options = buildout()\n ... recipe.install()\n ... kill.assert_called_with(42, signal.SIGTERM)\n ()\n\n >>> pprint(options)\n {'effective-path': '/myproj2012-01-26T14:50:15.864772',\n 'import-file': 'tree.txt',\n 'import-text': '\\n/a\\n /b\\n/c\\n',\n 'location': '/testdirectory/parts/myproj',\n 'path': '/myproj',\n 'recipe': 'zc.zookeeperrecipes:devtree',\n 'zookeeper': '127.0.0.1:2181'}\n\n >>> with mock.patch('os.getpid') as getpid:\n ... getpid.return_value = 42\n ... zk.register_server('/myproj2012-01-26T14:50:15.864772/a/b',\n ... 'x:y')\n\n >>> zk.print_tree()\n /myproj2012-01-26T14:50:15.864772\n buildout:location = u'/testdirectory/parts/myproj'\n /a\n /b\n /x:y\n pid = 42\n /c\n\n\n *** Cleanup w different path and explicit path, and creation of base nodes ***\n\n >>> conf = \"\"\"\n ... [myproj]\n ... recipe = zc.zookeeperrecipes:devtree\n ... import-file = tree.txt\n ... path = /ztest/path\n ... \"\"\"\n\n >>> with mock.patch('os.kill') as kill:\n ... with mock.patch('zc.zookeeperrecipes.timestamp') as ts:\n ... ts.return_value = '2012-01-26T14:50:16.864772'\n ... recipe, options = buildout()\n ... recipe.install()\n ... kill.assert_called_with(42, signal.SIGTERM)\n ()\n\n >>> pprint(options)\n {'effective-path': '/ztest/path2012-01-26T14:50:16.864772',\n 'import-file': 'tree.txt',\n 'import-text': '\\n/a\\n /b\\n/c\\n',\n 'location': '/tmp/tmpZ3mohq/testdirectory/parts/myproj',\n 'path': '/ztest/path',\n 'recipe': 'zc.zookeeperrecipes:devtree',\n 'zookeeper': '127.0.0.1:2181'}\n\n >>> with mock.patch('os.getpid') as getpid:\n ... getpid.return_value = 42\n ... zk.register_server('/ztest/path2012-01-26T14:50:16.864772/a/b',\n ... 'x:y')\n\n >>> zk.print_tree()\n /ztest\n /path2012-01-26T14:50:16.864772\n buildout:location = u'/tmp/tmpZ3mohq/testdirectory/parts/myproj'\n /a\n /b\n /x:y\n pid = 42\n /c\n\n *** explicit effective-path ***\n\n We can control the effective-path directly:\n\n >>> conf = \"\"\"\n ... [myproj]\n ... recipe = zc.zookeeperrecipes:devtree\n ... effective-path = /my/path\n ... import-file = tree.txt\n ... \"\"\"\n\n This time, we'll also check\n that kill fail handlers are handled properly.\n\n >>> with mock.patch('os.kill') as kill:\n ... def noway(pid, sig):\n ... raise OSError\n ... kill.side_effect = noway\n ... recipe, options = buildout()\n ... recipe.install()\n ... kill.assert_called_with(42, signal.SIGTERM)\n ()\n\n >>> pprint(options)\n {'effective-path': '/my/path',\n 'import-file': 'tree.txt',\n 'import-text': '\\n/a\\n /b\\n/c\\n',\n 'location': '/testdirectory/parts/myproj',\n 'recipe': 'zc.zookeeperrecipes:devtree',\n 'zookeeper': '127.0.0.1:2181'}\n\n >>> zk.print_tree() # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS\n /my\n /path\n buildout:location = u'/tmp/tmpiKIi2U/testdirectory/parts/myproj'\n /a\n /b\n /c\n /ztest\n\n >>> zk.close()\n\n *** Non-local zookeeper no cleanup no/explicit import text ***\n\n **Note** because of the way zookeeper testing works, there's\n really only one zookeeper \"server\", so even though we're using a\n different connection string, we get the same db.\n\n >>> conf = \"\"\"\n ... [myproj]\n ... recipe = zc.zookeeperrecipes:devtree\n ... effective-path = /my/path\n ... zookeeper = zookeeper.example.com:2181\n ... \"\"\"\n\n >>> recipe, options = buildout()\n >>> recipe.install()\n ()\n\n >>> pprint(options)\n {'effective-path': '/my/path',\n 'location': '/tmp/tmpUAkJkK/testdirectory/parts/myproj',\n 'recipe': 'zc.zookeeperrecipes:devtree',\n 'zookeeper': 'zookeeper.example.com:2181'}\n\n >>> zk = zc.zk.ZooKeeper('zookeeper.example.com:2181')\n >>> with mock.patch('os.getpid') as getpid:\n ... getpid.return_value = 42\n ... zk.register_server('/my/path', 'a:b')\n >>> zk.print_tree()\n /my\n /path\n buildout:location = u'/tmp/tmp2Qp4qX/testdirectory/parts/myproj'\n /a:b\n pid = 42\n /ztest\n\n >>> conf = \"\"\"\n ... [myproj]\n ... recipe = zc.zookeeperrecipes:devtree\n ... import-text = /a\n ... zookeeper = zookeeper.example.com:2181\n ... path =\n ... \"\"\"\n\n\n >>> with mock.patch('os.kill') as kill:\n ... def noway(pid, sig):\n ... print 'wtf killed'\n ... kill.side_effect = noway\n ... with mock.patch('zc.zookeeperrecipes.timestamp') as ts:\n ... ts.return_value = '2012-01-26T14:50:24.864772'\n ... recipe, options = buildout()\n ... recipe.install()\n ()\n\n >>> zk.print_tree()\n /2012-01-26T14:50:24.864772\n buildout:location = u'/tmp/tmpxh1XPP/testdirectory/parts/myproj'\n /a\n /my\n /path\n buildout:location = u'/tmp/tmpxh1XPP/testdirectory/parts/myproj'\n /a:b\n pid = 42\n /ztest\n\n\nIn this example, we're creating a ZooKeeper tree at the path\n``/myprojectYYYY-MM-DDTHH:MM:SS.SSSSSS`` with data imported from the\nbuildout-local file ``tree.txt``, where YYYY-MM-DDTHH:MM:SS.SSSSSS is\nthe ISO date-time when the node was created.\n\nThe ``devtree`` recipe options are:\n\nzookeeper\n Optional ZooKeeper connection string.\n\n It defaults to '127.0.0.1:2181'.\n\npath\n Optional path at which to create the tree.\n\n If not provided, the part name is used, with a leading ``/`` added.\n\n When a ``devtree`` part is installed, a path is created at a path\n derived from the given (or implied) path by adding the current date\n and time to the path in ISO date-time format\n (YYYY-MM-DDTHH:MM:SS.SSSSSS). The derived path is stored a file in\n the buildout parts directory with a name equal to the section name.\n\neffective-path\n Optional path to be used as is.\n\n This option is normally computed by the recipe and is queryable\n from other recipes, but it may also be set explicitly.\n\nimport-file\n Optional import file.\n\n This is the name of a file containing tree-definition text. See the\n ``zc.zk`` documentation for information on the format of this file.\n\nimport-text\n Optional import text.\n\n Unfortunately, because of the way buildout parses configuration\n files, leading whitespace is stripped, making this option hard to\n specify.\n\nhelper-scripts\n Helper-script prefix\n\n If provided, 4 helper scripts will be generated with the given\n prefix:\n\n PREFIXexport\n Export a zookeepeer tree.\n\n PREFIXimport\n Import a zookeepeer tree.\n\n PREFIXprint\n Print a zookeepeer tree.\n\n PREFIXport\n Print the port of the first child of a given path.\n\n Where PREFIX is the profix given to the helper-scripts option.\n\n.. test\n\n >>> conf = \"\"\"\n ... [myproj]\n ... recipe = zc.zookeeperrecipes:devtree\n ... import-text = /a\n ... zookeeper = zookeeper.example.com:2181\n ... helper-scripts = zk-\n ... \"\"\"\n\n\n >>> with mock.patch('os.kill') as kill:\n ... def noway(pid, sig):\n ... print 'wtf killed'\n ... kill.side_effect = noway\n ... with mock.patch('zc.zookeeperrecipes.timestamp') as ts:\n ... ts.return_value = '2012-01-26T14:50:24.864772'\n ... recipe, options = buildout()\n ... recipe.install() # doctest: +NORMALIZE_WHITESPACE\n Raw: zk-scripts\n {'arguments':\n \"['zookeeper.example.com:2181/myproj2012-01-26T14:50:24.864772']\n + sys.argv[1:]\",\n 'eggs': 'zc.zk [static]',\n 'recipe': 'zc.recipe.egg',\n 'scripts': 'zookeeper_export=zk-export zookeeper_import=zk-import'}\n Raw: zk-print\n {'arguments':\n '[\\'zookeeper.example.com:2181/myproj2012-01-26T14:50:24.864772\\', \"-e\"]\n + sys.argv[1:]',\n 'eggs': 'zc.zk [static]',\n 'recipe': 'zc.recipe.egg',\n 'scripts': 'zookeeper_export=zk-print'}\n Raw: zk-port\n {'eggs': 'zc.zk [static]',\n 'entry-points': 'zk-port=time:time',\n 'initialization': \"\\nargs = sys.argv[1:]\\nimport zc.zk\\nzk = zc.zk.ZK('zookeeper.example.com:2181/myproj2012-01-26T14:50:24.864772')\\npath = args.pop(0)\\nif path[-1:] == '/':\\n path += 'providers'\\nchild = zk.get_children(path)[0]\\nif args:\\n [prop] = args\\n child = zk.get_properties(path + '/' + child)[prop]\\nprint child.split(':')[-1]\\nzk.close()\\n\",\n 'recipe': 'zc.recipe.egg',\n 'scripts': 'zk-port'}\n ()\n\n\nCleanup\n-------\n\nWe don't want trees to accumulate indefinately. When using a local\nzookeeper (default), when the recipe is run, the entire tree is\nscanned looking for nodes that have ``buildout:location`` properties\nwith paths that no-longer exist in the local file system paths that\ncontain different ZooKeeper paths.\n\nIf such nodes are found, then the nodes are removed and, if the nodes\nhad any ephemeral subnodes with pids, those pids are sent a SIGTERM\nsignal.\n\nChange History\n==============\n\n0.2.0 (2011-02-22)\n------------------\n\nAdd an option to define buildout-specific helper scripts for working\nwith the buildout's ZooKeeper tree.\n\n0.1.2 (2011-02-13)\n------------------\n\nFixed: default ZooKeeper connection string wasn't set as option and\n was thus not usable from other parts.\n\n0.1.1 (2011-02-09)\n------------------\n\nFixed a packaging bug.\n\n0.1.0 (2011-02-02)\n------------------\n\nInitial release", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "UNKNOWN", "keywords": null, "license": "ZPL 2.1", "maintainer": null, "maintainer_email": null, "name": "zc.zookeeperrecipes", "package_url": "https://pypi.org/project/zc.zookeeperrecipes/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/zc.zookeeperrecipes/", "project_urls": { "Download": "UNKNOWN", "Homepage": "UNKNOWN" }, "release_url": "https://pypi.org/project/zc.zookeeperrecipes/0.2.0/", "requires_dist": null, "requires_python": null, "summary": "=================", "version": "0.2.0" }, "last_serial": 802225, "releases": { "0.1.0": [ { "comment_text": "", "digests": { "md5": "bb42c94227ea90886fa19505bcb9759b", "sha256": "4e01be7d6b05ea6a5a49964a6b010722efbb33914e87ec4682ac3fddbdb62d3e" }, "downloads": -1, "filename": "zc.zookeeperrecipes-0.1.0.tar.gz", "has_sig": false, "md5_digest": "bb42c94227ea90886fa19505bcb9759b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 7452, "upload_time": "2012-02-02T15:58:56", "url": "https://files.pythonhosted.org/packages/02/dd/11d68e8b83ae3285c460b62ac3dc34117bce40ca3cdad30fbc44ebebfea9/zc.zookeeperrecipes-0.1.0.tar.gz" } ], "0.1.1": [ { "comment_text": "", "digests": { "md5": "c3bd8a1add178331324560f77828438b", "sha256": "ecfaad29c3b0c4007f34514ba2fb4c7cb443b0541c57d6b3cecdd68885dfdfb0" }, "downloads": -1, "filename": "zc.zookeeperrecipes-0.1.1.tar.gz", "has_sig": false, "md5_digest": "c3bd8a1add178331324560f77828438b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 9138, "upload_time": "2012-02-10T17:41:16", "url": "https://files.pythonhosted.org/packages/d0/2b/27f9c3c9077b4198c8b733f3aade85ad49d8eec142109ac0783c436ab043/zc.zookeeperrecipes-0.1.1.tar.gz" } ], "0.1.2": [ { "comment_text": "", "digests": { "md5": "9495f116f02261ab3d076e93d35f1dd3", "sha256": "2c3b987f7da30f777c898b5f28e81ecf09f109419ec3fa55e4a43b7af2699482" }, "downloads": -1, "filename": "zc.zookeeperrecipes-0.1.2.tar.gz", "has_sig": false, "md5_digest": "9495f116f02261ab3d076e93d35f1dd3", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 8595, "upload_time": "2012-02-13T22:59:16", "url": "https://files.pythonhosted.org/packages/44/24/7aba136960d48eb14fb7cda0a2194e6e3d4e389c46cf0a9c9afc79f8a091/zc.zookeeperrecipes-0.1.2.tar.gz" } ], "0.2.0": [ { "comment_text": "", "digests": { "md5": "315af53d1c5765b7230da2f5cd18e1fd", "sha256": "da614c01da3e577c55e44c20561e5a5d10c1c30f968da06d822084c276db0343" }, "downloads": -1, "filename": "zc.zookeeperrecipes-0.2.0.tar.gz", "has_sig": false, "md5_digest": "315af53d1c5765b7230da2f5cd18e1fd", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 10788, "upload_time": "2012-02-23T00:56:16", "url": "https://files.pythonhosted.org/packages/26/fb/d02302b4e9646b364ff86b3879dc89d40c45543c26f3bd90df8a4b6aa5da/zc.zookeeperrecipes-0.2.0.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "315af53d1c5765b7230da2f5cd18e1fd", "sha256": "da614c01da3e577c55e44c20561e5a5d10c1c30f968da06d822084c276db0343" }, "downloads": -1, "filename": "zc.zookeeperrecipes-0.2.0.tar.gz", "has_sig": false, "md5_digest": "315af53d1c5765b7230da2f5cd18e1fd", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 10788, "upload_time": "2012-02-23T00:56:16", "url": "https://files.pythonhosted.org/packages/26/fb/d02302b4e9646b364ff86b3879dc89d40c45543c26f3bd90df8a4b6aa5da/zc.zookeeperrecipes-0.2.0.tar.gz" } ] }