From ad0964d44140af948ae4e019d0883b978ca17a4b Mon Sep 17 00:00:00 2001 From: Seangchan Ryu Date: Tue, 30 Dec 2014 10:38:44 -0600 Subject: [PATCH] Squashed commit of the following: commit ac28b5ce0c3013c76a513919be63c87a700f44eb Merge: 3e5ec8c c81b8e0 Author: ticoann Date: Tue Dec 30 10:19:54 2014 -0600 Merge pull request #5549 from ticoann/reqmgr2 dashboard -> Dashboard in spec param commit c81b8e0466f0ce59a22e790c908c17f4103e7778 Author: Seangchan Ryu Date: Tue Dec 30 10:18:13 2014 -0600 dashboard -> Dashboard in spec param commit 3e5ec8c9bb95ecd3306a6290cf585b50a3541105 Merge: 3c669c2 2595bd7 Author: ticoann Date: Tue Dec 30 09:56:38 2014 -0600 Merge pull request #5548 from ticoann/reqmgr2 Reqmgr2 commit 2595bd79cea50c4fc5bc6007695fc5ec03a4385f Author: Seangchan Ryu Date: Tue Dec 30 09:54:02 2014 -0600 change maxRSS -> MaxRSS, maxVSize -> MaxVSize, dashboard -> Dashboard in the spec definition commit 50bc21e45e62929e26ba6b35d829323f2c7faff7 Merge: 3c669c2 949e873 Author: Seangchan Ryu Date: Tue Dec 30 09:35:27 2014 -0600 Merge branch 'master' of https://github.com/dmwm/WMCore into reqmgr2 Conflicts: CHANGES commit 3c669c2f1e15b8b0a0892a724c0ff39a6af41faf Merge: d3ee889 4dbbe74 Author: ticoann Date: Tue Dec 30 09:27:37 2014 -0600 Merge pull request #5547 from ticoann/reqmgr2 Reqmgr2 commit 4dbbe741bd2a238ce0f086beb16358597d29d5e6 Merge: d3ee889 619b162 Author: Seangchan Ryu Date: Tue Dec 30 09:25:59 2014 -0600 Merge branch 'master' of https://github.com/dmwm/WMCore into reqmgr2 commit d3ee8897451e927579c13ac2063bbc909f0381ba Merge: 6e399e1 7d406b2 Author: ticoann Date: Mon Dec 29 15:03:31 2014 -0600 Merge pull request #5546 from ticoann/reqmgr2 initial update for the data cache commit 7d406b22a6e2226d364749e7c411637d0a3c271b Author: Seangchan Ryu Date: Mon Dec 29 15:00:01 2014 -0600 initial update for the data cache commit 6e399e10b289628cbb858dc6f9e75b5bc8d87140 Merge: 0d7afc4 a2929f5 Author: ticoann Date: Fri Dec 26 14:48:17 2014 -0600 Merge pull request #5545 from ticoann/reqmgr2 record dn for every status change commit a2929f5751c6cd75873f926825e0fd2d3e46d46e Author: Seangchan Ryu Date: Fri Dec 26 14:46:39 2014 -0600 record dn for every status change commit 0d7afc47d79aa3cfc951d0a2598804052052d662 Merge: 0e55cbd 985b204 Author: ticoann Date: Fri Dec 26 14:16:10 2014 -0600 Merge pull request #5544 from ticoann/reqmgr2 Reqmgr2 commit 985b204bbf436263c21fc3634d2ae2c7c5aa9d34 Merge: 0e55cbd d7ab8ff Author: Seangchan Ryu Date: Fri Dec 26 14:14:52 2014 -0600 Merge branch 'master' of https://github.com/dmwm/WMCore into reqmgr2 Conflicts: CHANGES commit 0e55cbdd97796480ddd1b6f1c5b7dd02941686a9 Merge: d225ab5 11211c0 Author: ticoann Date: Fri Dec 26 14:11:54 2014 -0600 Merge pull request #5543 from ticoann/reqmgr2 add assign_optional property commit 11211c077aaf376b7ae183bfeea3ceef3f6d2b28 Author: Seangchan Ryu Date: Fri Dec 26 14:10:25 2014 -0600 add assign_optional property commit d225ab5ab6b5981cd25f0e9b409b6a2bdcdf4944 Merge: f383307 3ed524a Author: ticoann Date: Fri Dec 26 11:58:24 2014 -0600 Merge pull request #5542 from ticoann/reqmgr2 convert type when validation happen commit 3ed524a0b4b60b39f486e5d4eab28928d59f4f1f Author: Seangchan Ryu Date: Fri Dec 26 11:57:11 2014 -0600 convert type when validation happen commit f383307a1ce199834c86244f4fa056e2c4950300 Merge: eb2a0f6 e861874 Author: ticoann Date: Fri Dec 26 10:36:31 2014 -0600 Merge pull request #5541 from ticoann/reqmgr2 convert possible content to pass the validation commit e861874e942204080000fe094fb16bbf526e7607 Author: Seangchan Ryu Date: Fri Dec 26 10:33:14 2014 -0600 convert possible content to pass the validation commit eb2a0f6338864dccd262c483346c40b367b34024 Merge: 1b94cb8 122accf Author: ticoann Date: Tue Dec 23 14:07:32 2014 -0600 Merge pull request #5540 from vkuznet/reqmgr2 Remove cust_sites/non_cust_sites/auto_approve_sites commit 122accf2443738d09531187f66cbd0e4b4de27a5 Author: Valentin Kuznetsov Date: Tue Dec 23 15:02:27 2014 -0500 Remove cust_sites/non_cust_sites/auto_approve_sites; no defaults in assign template for them; add condtitions to template for them commit 1b94cb840de9f7773c0196f690fabe3ff334983d Merge: 883e6ca c18e310 Author: ticoann Date: Tue Dec 23 13:25:25 2014 -0600 Merge pull request #5539 from ticoann/reqmgr2 add Validation code commit c18e3104b808a9ecab2be1d37c839fd4fe0bff31 Author: Seangchan Ryu Date: Tue Dec 23 13:24:05 2014 -0600 add Validation code commit 883e6ca920a0c02720e19c91002a9b605b2ee1e6 Merge: aba0a03 a33e3f6 Author: ticoann Date: Tue Dec 23 08:20:28 2014 -0600 Merge pull request #5538 from ticoann/reqmgr2 change to use REST directly commit a33e3f6c3e7b3e97d6a7e9ed6b71b56c5a225f7a Author: Seangchan Ryu Date: Tue Dec 23 03:11:40 2014 -0600 change to use REST directly commit aba0a03d7312567581f2cec8efb78efa824bcf3d Merge: 9da18ac c7ffd58 Author: ticoann Date: Fri Dec 19 16:16:27 2014 -0600 Merge pull request #5537 from ticoann/reqmgr2 Reqmgr2 commit c7ffd5822391481e8eb1cf0fd0540b8aef55d50f Merge: 67d8f6b 9da18ac Author: Seangchan Ryu Date: Fri Dec 19 16:09:49 2014 -0600 Merge branch 'reqmgr2' of https://github.com/dmwm/WMCore into reqmgr2 Conflicts: src/python/WMCore/ReqMgr/Web/ReqMgrService.py commit 67d8f6bd1dacf43faef06da0c431ca6ec72d4512 Author: Seangchan Ryu Date: Fri Dec 19 16:00:34 2014 -0600 fix status commit 92e187ff7d0b058bf1b22ded1f1e12b13c82d04d Author: Seangchan Ryu Date: Fri Dec 19 15:58:23 2014 -0600 fix put and get request commit 9da18acf4be4c8fe9d150bf223d138b1df9c978e Merge: c5cfbad bf55a31 Author: ticoann Date: Fri Dec 19 15:57:35 2014 -0600 Merge pull request #5536 from vkuznet/reqmgr2 Changes for assign interface commit bf55a3113d278033c8c2ea57f386447446e8da7f Author: Valentin Kuznetsov Date: Fri Dec 19 13:59:50 2014 -0500 use RequestStatus commit db23be89cd0eedadbf85947ffe82919db8ac35f0 Author: Valentin Kuznetsov Date: Fri Dec 19 10:46:31 2014 -0500 Exclude status from kwds when pass them to updateRequestProperty and add separate call to update request status commit 1809493791f08feeb60e57c788023f0688b157b5 Author: Valentin Kuznetsov Date: Fri Dec 19 10:45:38 2014 -0500 Remove Name input tag and comment out dashboard activity since they're not validated in ReqMgr code commit c5cfbad9d39acc6f88c0f0b9262af4bc50a8f197 Merge: cf91fd4 e933810 Author: ticoann Date: Thu Dec 18 09:18:47 2014 -0600 Merge pull request #5533 from vkuznet/reqmgr2 Support response status notifications commit e9338106b32a01c51bafeb4868346a1a3a394028 Author: Valentin Kuznetsov Date: Thu Dec 18 10:07:16 2014 -0500 Update status once request dict is updated commit b22ae4383439a1319b956dd0c448760772cc390c Author: Valentin Kuznetsov Date: Thu Dec 18 10:02:32 2014 -0500 Define response status for ajax callback function commit 44e610a0803ad6f5327cc6df25c664e5c2d40d9a Author: Valentin Kuznetsov Date: Thu Dec 18 10:02:09 2014 -0500 Refactor code to support responses and provide proper notification based on response status commit cf91fd429a86de5fe76e9067b0284527272a8559 Merge: 9b4d5e2 2989253 Author: ticoann Date: Wed Dec 17 15:58:54 2014 -0600 Merge pull request #5532 from vkuznet/reqmgr2 Cosmetic changes with styles commit 298925388df8ba010b8df4763e15328ef6b9cc6c Author: Valentin Kuznetsov Date: Wed Dec 17 16:45:27 2014 -0500 Add printout for status (temp) commit 6fa8e5ba0c46785c9084ccacea98cc1ec6550cc2 Author: Valentin Kuznetsov Date: Wed Dec 17 16:41:55 2014 -0500 wrap content into placeholder where we can apply styles commit 000ad2fc1f99eb9452ae90b5d0f18118f0760916 Author: Valentin Kuznetsov Date: Wed Dec 17 16:41:34 2014 -0500 use small buttons commit f186414e36426a58ba6340f5581180d175350357 Author: Valentin Kuznetsov Date: Wed Dec 17 16:41:30 2014 -0500 use small buttons commit 69766768bcd786a5296aa8512cbb297cbd014367 Author: Valentin Kuznetsov Date: Wed Dec 17 16:41:27 2014 -0500 use small buttons commit 286419e4d086f736094e2e2682b04160dc64b5fd Author: Valentin Kuznetsov Date: Wed Dec 17 16:41:21 2014 -0500 use small buttons commit 82b292cbdb9a1a81d922b486402a34bf45047e00 Author: Valentin Kuznetsov Date: Wed Dec 17 16:40:50 2014 -0500 New font for body commit 9b4d5e26c160694b12a559be47a846f651d01042 Merge: 18614a1 4e8b05d Author: ticoann Date: Wed Dec 17 11:27:37 2014 -0600 Merge pull request #5531 from vkuznet/reqmgr2 Add confirmation for actions, new kube css library commit 4e8b05d2bd065d603e088e1d0a2d2d3bd01e7514 Author: Valentin Kuznetsov Date: Wed Dec 17 12:24:36 2014 -0500 Remove old kube library commit d14184aa5f01bffeaef46e07d529034a8e5cc0a6 Author: Valentin Kuznetsov Date: Wed Dec 17 12:23:47 2014 -0500 New version of kube library commit 372a99fecead354753fc23195cfd7b4b618ac83a Author: Valentin Kuznetsov Date: Wed Dec 17 12:23:30 2014 -0500 Clean-up commit d7d0f7db43e769f3a3e9ab784ce635be039a5f4c Author: Valentin Kuznetsov Date: Wed Dec 17 12:23:19 2014 -0500 Clean-up commit add3f03d327e8a0f2590c0a2f192128e67ac86b6 Author: Valentin Kuznetsov Date: Wed Dec 17 12:23:08 2014 -0500 Add confirmation placeholder commit 4b51bb33e896cf3a29f3831c534cd691d58bb8a9 Author: Valentin Kuznetsov Date: Wed Dec 17 12:22:52 2014 -0500 Add confirmation CSS commit 8a199412f4f0c9a942180501e53122bf9168a3fc Author: Valentin Kuznetsov Date: Wed Dec 17 12:22:32 2014 -0500 Expand ajax action to provide users with confirmation commit 18614a19cb43f65f35b55e9f67db42c7e57c4c95 Merge: 0e33022 edaab32 Author: ticoann Date: Wed Dec 17 10:36:09 2014 -0600 Merge pull request #5530 from vkuznet/reqmgr2 Patches to use ajax instead of forms in approve/assign interfaces commit edaab327dce34d155cc307fb384d22fde8cf4bd8 Author: Valentin Kuznetsov Date: Wed Dec 17 11:13:30 2014 -0500 Load settings from config (e.g. dbs_url, etc); remove obsolete code and JSON example since create interface works now commit d4c5c250daa77ea9b7501ef4c25239d974685cd1 Author: Valentin Kuznetsov Date: Wed Dec 17 09:35:25 2014 -0500 Remove scripts method; load script in create action commit 8ba20ade9a98f404f5d590d786f8a2e560a076d4 Author: Valentin Kuznetsov Date: Wed Dec 17 09:34:33 2014 -0500 Remove ajaxScript since it is obsolete now commit dbb07154a5b274ad53d36051785399d6013d014b Author: Valentin Kuznetsov Date: Wed Dec 17 09:34:13 2014 -0500 Extend code to parse json data commit 5fd37a6298ae4a6997c0b52bb98e86572f1f5612 Author: Valentin Kuznetsov Date: Tue Dec 16 20:54:14 2014 -0500 Add create handler in ajax_action (need to be tested) commit ea96a6dd508972cc8699f1ea2477aa61ee8d17e6 Author: Valentin Kuznetsov Date: Tue Dec 16 20:53:42 2014 -0500 Convert to ajax form commit 7c81b464bd69102517b393bdc0071ddf5e0c3df7 Author: Valentin Kuznetsov Date: Tue Dec 16 20:53:25 2014 -0500 Fix JS bug commit 54d3ec75391efd526035121f412f61cb32d43488 Author: Valentin Kuznetsov Date: Tue Dec 16 20:53:09 2014 -0500 Fix comments commit bd4b14aff01220f901a577b31387691f5beedc62 Author: Valentin Kuznetsov Date: Tue Dec 16 20:52:53 2014 -0500 Fix option input tag bug commit 3256c266e9ded2109b4591d170dd78fed6e64ba4 Author: Valentin Kuznetsov Date: Tue Dec 16 14:58:37 2014 -0500 Added reject buttons; change templates to use ajax function instead of forms commit 986003d2892afa2d134c13f1212f11d66bd84eff Author: Valentin Kuznetsov Date: Tue Dec 16 14:58:03 2014 -0500 Change ajax_approve into generic ajax_action commit f08b2b5da6ec3efc7d772bf2e138acff88ece910 Author: Valentin Kuznetsov Date: Tue Dec 16 14:57:26 2014 -0500 New version of ajaxRequest function commit 9cb9025a9435d3ce6568dd0706904c2cfdc1d88a Author: Valentin Kuznetsov Date: Tue Dec 16 14:57:05 2014 -0500 New version of the library commit 0e3302219ea1a7e9d480b88a8e914044b3b41c0d Merge: 35a0853 dbb734b Author: ticoann Date: Tue Dec 16 10:25:11 2014 -0600 Merge pull request #5529 from vkuznet/reqmgr2 web UI polishing commit dbb734b4fac5b2b041ca625d7b319ee17a7be929 Author: Valentin Kuznetsov Date: Tue Dec 16 11:13:26 2014 -0500 Use placeholder for inputs which requires changes commit db1e61cf3863874ef429696ba3b1cef1d9571b6c Author: Valentin Kuznetsov Date: Tue Dec 16 11:13:02 2014 -0500 Add placeholder styles commit 35a08536201d31dc5d17a74deb41a742f21a5821 Merge: bf1f3ad 3e40d08 Author: ticoann Date: Tue Dec 16 09:46:18 2014 -0600 Merge pull request #5528 from vkuznet/reqmgr2 Reqmgr2 create template changes commit 3e40d08d75993cdc47c45f487e8fa18344f4ce22 Author: Valentin Kuznetsov Date: Tue Dec 16 10:42:02 2014 -0500 Adjust templates to satisfy spec requirements commit 7a5a68caa8756153ee8898a450b7668af851cb8f Author: Valentin Kuznetsov Date: Tue Dec 16 10:41:39 2014 -0500 Add check for types of JSON templates; add acdc urls for templates commit 43e47e3deb7603d4f936d677a39417da9812c479 Author: Valentin Kuznetsov Date: Mon Dec 15 19:18:30 2014 -0500 Add appropriate css classes to input tag forms commit a1620a67f488163049d51d07cfb1f20385d0fb70 Author: Valentin Kuznetsov Date: Mon Dec 15 19:17:58 2014 -0500 Add replace css commit a6b7571e7446ed125db3f9e3062f448efa47dbf8 Author: Valentin Date: Mon Dec 15 22:24:32 2014 +0100 Rename template to follow new naming conventions commit f2fc8a076e24eb3c6840391506d7578b439d75ec Author: Valentin Kuznetsov Date: Mon Dec 15 16:19:36 2014 -0500 Read specs and use them for verification commit 62985c1a3051f74976c4187c1dbacd19c8b35d95 Author: Valentin Kuznetsov Date: Mon Dec 15 16:19:07 2014 -0500 New new naming convention commit 35d93fda74f5940a30fd77113d69d6f58d235493 Author: Valentin Kuznetsov Date: Mon Dec 15 16:18:50 2014 -0500 New helper function to get couch url, should read it elsewhere commit 4e08ab43ac032300edd2df80f2dcbc1a9ce6635c Author: Valentin Kuznetsov Date: Mon Dec 15 16:18:16 2014 -0500 New naming convention commit bf1f3ada43abd5d47c4cb91b7e400c06b2706f1d Merge: 55dddac 708b0f3 Author: ticoann Date: Mon Dec 15 11:57:25 2014 -0600 Merge pull request #5527 from ticoann/reqmgr2 clean up state transition commit 708b0f389956a04349b995b3096dbba0ea0fcf02 Author: Seangchan Ryu Date: Mon Dec 15 11:56:19 2014 -0600 clean up state transition commit 55dddac9e09edc0efdb16e3161c2702544931833 Merge: 871a6e9 b2d3f3b Author: ticoann Date: Mon Dec 15 09:47:56 2014 -0600 Merge pull request #5523 from ticoann/reqmgr2 Intermediate change to server call commit 871a6e94bd5e376024075d1f4480a3990da52e12 Merge: e2e427e bd1d27a Author: ticoann Date: Mon Dec 15 09:47:11 2014 -0600 Merge pull request #5526 from vkuznet/reqmgr2 New changes for create action commit b2d3f3b6c0938d4e24f93b5fd438a93aa58417b1 Author: Seangchan Ryu Date: Thu Dec 11 11:34:28 2014 -0600 Intermediate change to server call commit bd1d27abe7992aac629e033f3e7608803a898549 Author: Valentin Kuznetsov Date: Mon Dec 15 10:39:21 2014 -0500 Fix statuses and work on creation action; add dbs_url, cc_url, cc_id into create template commit aea9583e179f9e2598a88a793205c42387e89c19 Author: Valentin Kuznetsov Date: Mon Dec 15 10:38:35 2014 -0500 Adjust to proper structure commit 23199fe704bad9a381cb4fe96a60a75bd79510ef Author: Valentin Kuznetsov Date: Mon Dec 15 10:38:18 2014 -0500 Replace empty list w/ input text commit 1eeeefbbcb7c3dadd890995564e15fe5f1db0246 Author: Valentin Kuznetsov Date: Mon Dec 15 10:37:55 2014 -0500 Fix typo commit e2e427ec36f2d2cd65543085e6fa72c91208e510 Merge: 0b0aa56 d4d2abe Author: ticoann Date: Wed Dec 10 15:35:59 2014 -0600 Merge pull request #5521 from vkuznet/reqmgr2 Re-factor code to use validated python scripts commit d4d2abe3daba9df13799378104f8f3f7c5c55f86 Author: Valentin Kuznetsov Date: Wed Dec 10 14:43:55 2014 -0600 Re-factor code to now use snippets and load python scripts in create action commit 0b0aa5647250957ed1595ea5fb08dcb5d0e69feb Merge: ce65921 329cd30 Author: ticoann Date: Wed Dec 10 12:56:25 2014 -0600 Merge pull request #5520 from ticoann/reqmgr2 Reqmgr2 commit 329cd30c574e513fcc29e2f45f1e8558469700e2 Merge: da1d91c 6b7122f Author: Seangchan Ryu Date: Wed Dec 10 12:55:11 2014 -0600 Merge remote-tracking branch 'upstream/master' into reqmgr2 commit da1d91ce420caab74739102fcbf3b0ab69395017 Author: Valentin Kuznetsov Date: Wed Dec 10 09:46:41 2014 -0600 Bug fix commit 71852ac693a9ccb0943fac1a0956731675b37aa3 Author: Valentin Kuznetsov Date: Wed Dec 10 09:42:40 2014 -0600 Fix location of jsdir commit ce6592182e541a01f16081489cb4043db6c4f987 Merge: d291a88 0cca2a9 Author: ticoann Date: Wed Dec 10 09:51:10 2014 -0600 Merge pull request #5519 from vkuznet/reqmgr2 Fix location of jsdir commit 0cca2a9f28f91cf0500b8e9307ed1cc2fe0a9b8f Author: Valentin Kuznetsov Date: Wed Dec 10 09:46:41 2014 -0600 Bug fix commit 9fdb8428bc8e59a10a405aeecdb2496c135ae3b2 Author: Valentin Kuznetsov Date: Wed Dec 10 09:42:40 2014 -0600 Fix location of jsdir commit d291a88db497a56cde47586303e9f9c266a66e55 Merge: 2694f9b 6183046 Author: ticoann Date: Tue Dec 9 22:50:27 2014 -0600 Merge pull request #5517 from ticoann/reqmgr2 Reqmgr2 commit 6183046345e53fb5e85c037ec020a1ac3c43ac59 Author: Seangchan Ryu Date: Tue Dec 9 15:12:05 2014 -0600 add temp validation error message commit d8183d268f992b4ff235175ef4c3b0619a30d991 Merge: b5b8d97 b5b21a0 Author: Seangchan Ryu Date: Tue Dec 9 15:11:09 2014 -0600 Merge branch 'master' of https://github.com/dmwm/WMCore into reqmgr2 Conflicts: CHANGES src/python/WMCore/JobSplitting/Harvest.py src/python/WMCore/__init__.py commit 2694f9b19b3d27bae3fbedc67aabad04b1c9ff8d Merge: 21b8cdb 31e7ddd Author: ticoann Date: Tue Dec 9 14:37:03 2014 -0600 Merge pull request #5515 from vkuznet/reqmgr2 Changes for testing web UI with actual reqmgr2 server commit 31e7dddfda18ab0b6519d0d5bd188858e2727cda Author: Valentin Kuznetsov Date: Tue Dec 9 14:30:33 2014 -0600 Remove yui; replace reqmgr_db.document with getRequestByNames; turn on new reqmgr2 APIs; use example doc for testing the injection commit 21b8cdbd5063146cdf09ea2ed14ac2f38bf9ba84 Merge: 3c2a4ba b5b8d97 Author: ticoann Date: Wed Dec 3 09:53:04 2014 -0600 Merge pull request #5503 from ticoann/reqmgr2 add comments commit b5b8d97fc8b8dc4a5e019272509f37cffd1de835 Author: Seangchan Ryu Date: Wed Dec 3 09:51:50 2014 -0600 add comments commit 3c2a4ba8b8bd540da10c2c7ece8d00ab0bcb3e41 Merge: 17c1f67 179e0c5 Author: ticoann Date: Wed Dec 3 09:23:32 2014 -0600 Merge pull request #5502 from vkuznet/reqmgr2 Add sort capability commit 179e0c57aeab86a56301530e5dd45c9900c3977e Author: Valentin Kuznetsov Date: Wed Dec 3 09:23:59 2014 -0500 Change updateRequestStatus to updateRequestProperty in assign method commit ce398c73f52c268ac342745907a201581021aede Author: Valentin Kuznetsov Date: Wed Dec 3 09:15:51 2014 -0500 Add sort capability commit 17c1f67ba37334e36e1ef8a90cbcd17894c8e37d Merge: b1ef8f4 fd9db61 Author: ticoann Date: Tue Dec 2 14:38:50 2014 -0600 Merge pull request #5500 from vkuznet/reqmgr2 Adjust create/assign/approve to new reqmgr2 APIs commit fd9db6103706d05eb0f0447e125f3915ca8c0727 Author: Valentin Kuznetsov Date: Tue Dec 2 14:00:57 2014 -0500 Adjust web UI new to new reqmgr2 APIs commit c66acffc92cc1ea6ec224f3f43c0fe1e8f0aced1 Author: Valentin Kuznetsov Date: Tue Dec 2 14:00:21 2014 -0500 Display status commit 4cfeed10b1e8edcc0fc91f5bc08bf73bf094cfdc Author: Valentin Kuznetsov Date: Tue Dec 2 13:59:56 2014 -0500 Change method since we may pass quite a lot of ids commit b1ef8f459a396b7142fa7aed890bc0153fd07eb6 Merge: 4d77c77 741ef2b Author: ticoann Date: Tue Dec 2 12:04:47 2014 -0600 Merge pull request #5499 from ticoann/reqmgr2 Reqmgr2 commit 741ef2bfd58dac861b6e57dfe9154a813483b14f Author: Seangchan Ryu Date: Sun Nov 30 10:37:19 2014 -0600 1.0.2.pre1 Signed-off-by: Seangchan Ryu commit 7d367078917e76f05b27ee24fd13ca74927c276f Author: Seangchan Ryu Date: Sun Nov 30 10:10:35 2014 -0600 add lumilist in schema in str format commit 11fb295dd4a8990dcea518f5a7e66f2424187b34 Author: Seangchan Ryu Date: Wed Nov 12 11:16:48 2014 -0600 Add multi run harvesting commit 8f1ffcea0e82a1c25ed768eaf3f40d062731323c Author: Seangchan Ryu Date: Mon Nov 17 15:01:24 2014 -0600 patch for setting prep ids per task in task chain commit 4ec03d9d4659c8beef49d8c45d743c9f76770578 Author: Seangchan Ryu Date: Fri Nov 28 22:38:52 2014 -0600 move the dbs import statement within the function since this package is imported in Runtime. DBS client is not in the sandbox. commit 5a1a79bf59588cec08dbfa13c811b617570a65ca Author: Alan Date: Fri Nov 21 13:59:44 2014 +0100 new json template for RelVal multi-run harvesting remove multicore parameter fix DQMConfigCacheID commit c0e5e1209a5486d5a41acc2f4657c92ccbad4240 Author: Eric Vaandering Date: Fri Nov 21 13:36:28 2014 -0600 Also fetch McM data if total events is defined and approval time is not commit d6f36c4ef4b887798daf60e0b3111bab0f845354 Author: Seangchan Ryu Date: Tue Dec 2 11:52:45 2014 -0600 fix format add comments commit e0ebe08795b3d8fb5668c0ace9a903906171ac7e Author: Valentin Kuznetsov Date: Mon Dec 1 13:47:07 2014 -0500 Added sort functionality into approve interface commit 4de6bcc11f5d0227633200eda03495c9c4d71c52 Author: Valentin Kuznetsov Date: Mon Dec 1 13:46:15 2014 -0500 Replace menu with URL calls commit d49e637ee850d89749fb1e6535e3f40b5812116f Author: Valentin Kuznetsov Date: Mon Dec 1 13:45:46 2014 -0500 Add web codes and throw URLError; added sort helper function commit 4d77c77dfe4235c7ab8faffd990377cad2be15f9 Merge: ba943af 1abda41 Author: ticoann Date: Mon Dec 1 14:02:35 2014 -0600 Merge pull request #5498 from vkuznet/reqmgr2 Work on approve/assign interfaces, added sort into approve commit 1abda412f41e9e230e1377bab48aa07598243612 Author: Valentin Kuznetsov Date: Mon Dec 1 13:47:07 2014 -0500 Added sort functionality into approve interface commit 8c32e10b4481a267b90ae3705d046507a40bb536 Author: Valentin Kuznetsov Date: Mon Dec 1 13:46:15 2014 -0500 Replace menu with URL calls commit 3d91b0aa33f9a40fddcef5409347502cedde6f2a Author: Valentin Kuznetsov Date: Mon Dec 1 13:45:46 2014 -0500 Add web codes and throw URLError; added sort helper function commit ba943af36c26c6dafae481a6f2a942dddf88614b Merge: 6829f22 f9fbc79 Author: ticoann Date: Mon Dec 1 10:16:10 2014 -0600 Merge pull request #5497 from vkuznet/reqmgr2 Changes to use new reqmgr2 APIs commit f9fbc7919f052b4c611fd79e7f23834980fc0b85 Author: Valentin Kuznetsov Date: Mon Dec 1 10:56:12 2014 -0500 Change assignment-approved status commit 780e4664b0826beeaec6d8c356c44270255bfc72 Author: Valentin Kuznetsov Date: Mon Dec 1 10:04:23 2014 -0500 New reqmgr2 APIs commit 6829f22be6a1fb114281114871898709c57c43a4 Merge: 3bc739a 9944aaa Author: ticoann Date: Wed Nov 26 15:40:54 2014 -0600 Merge pull request #5490 from ticoann/reqmgr2 fixed cache for Service.py commit 9944aaacfea4474d3dd9efeed0241a72461359e5 Author: Seangchan Ryu Date: Wed Nov 26 14:12:57 2014 -0600 fixed cache commit 3bc739ac462d77bcbf115e1e90334c6b3eb50e20 Merge: 5bb30b4 0be14c5 Author: ticoann Date: Wed Nov 26 12:56:29 2014 -0600 Merge pull request #5489 from vkuznet/reqmgr2 Reqmgr2 commit 0be14c5aeff8c9957b7fab886b26117bbd6f703b Author: Valentin Kuznetsov Date: Wed Nov 26 10:16:31 2014 -0500 Remove extra line commit ef885579ee737e6047bfbed590611523fb3c4a51 Author: Valentin Kuznetsov Date: Wed Nov 26 10:12:15 2014 -0500 Remove conflict statements commit 0701ccd69c145a987ad2f287ad4d7ec4134f945c Merge: ecf0bfb aa6a071 Author: Valentin Kuznetsov Date: Wed Nov 26 10:05:39 2014 -0500 Merge branch 'reqmgr2' of github.com:vkuznet/WMCore into reqmgr2 commit ecf0bfb3f61a1b59bceeff94f3b220645be718b0 Author: Valentin Kuznetsov Date: Tue Jun 24 11:29:25 2014 -0400 New code for ReqMgr web UI including changes for underlying WMCore commit 46457068c96e4c12a57701fcdc501875def21029 Author: Seangchan Ryu Date: Fri Aug 23 13:37:23 2013 -0500 Squashed commit of the following: commit fdeacde5bb86927273c1075f73fc35d48c7d51c7 Author: Seangchan Ryu Date: Fri Aug 23 13:34:11 2013 -0500 add clone test and fix assignment permission commit cc187d0a6f0bee300d4d5ff27ace13b2f4d7d1c6 Merge: 4468319 b356e27 Author: Seangchan Ryu Date: Mon Aug 19 11:29:50 2013 -0500 reqmgr rest api - work in progress Merge branch 'master' of https://github.com/dmwm/WMCore into validation_update Conflicts: src/python/WMCore/WMSpec/StdSpecs/StdBase.py commit 44683192d447f164c5b8b8d67d824920b8501785 Author: Seangchan Ryu Date: Wed Aug 7 11:19:38 2013 -0500 add validation for assignment - workin progress commit 957e6a35d63ccc8860a51e1e73ca0cff43b0fe40 Merge: 6518f43 0923ed4 Author: Seangchan Ryu Date: Tue Jul 23 16:41:03 2013 -0500 Merge branch 'master' of https://github.com/dmwm/WMCore into validation_update commit 6518f43f6b5b1b148963183ca75311d87d0a853c Author: Seangchan Ryu Date: Tue Jul 23 16:39:30 2013 -0500 validation work in progress commit e010744fcb7b0b182f71c5948040ec35dec05770 Author: Seangchan Ryu Date: Fri Jul 5 12:01:18 2013 -0500 initial argument update validation commit 3f0aa75ab24622e5dc84ea25cc8f693ea9b07900 Merge: 7d6a00d f7a5f8e Author: Seangchan Ryu Date: Fri Jul 5 10:47:18 2013 -0500 Merge branch 'master' of https://github.com/dmwm/WMCore into reqmgr2_rest_7_3 Conflicts: src/python/WMCore/ReqMgr/Service/Request.py test/python/WMCore_t/ReqMgr_t/Service_t/ReqMgr_t.py commit 7d6a00d2a275bbc22e0fbd78d1df649c19e2f535 Author: Seangchan Ryu Date: Fri Jul 5 10:19:17 2013 -0500 remove aux.json commit 7fe07f6880ae0a8ddabbceded564d0143a9a3fc0 Author: Seangchan Ryu Date: Fri Aug 23 13:37:23 2013 -0500 Squashed commit of the following: commit fdeacde5bb86927273c1075f73fc35d48c7d51c7 Author: Seangchan Ryu Date: Fri Aug 23 13:34:11 2013 -0500 add clone test and fix assignment permission commit cc187d0a6f0bee300d4d5ff27ace13b2f4d7d1c6 Merge: 4468319 b356e27 Author: Seangchan Ryu Date: Mon Aug 19 11:29:50 2013 -0500 reqmgr rest api - work in progress Merge branch 'master' of https://github.com/dmwm/WMCore into validation_update Conflicts: src/python/WMCore/WMSpec/StdSpecs/StdBase.py commit 44683192d447f164c5b8b8d67d824920b8501785 Author: Seangchan Ryu Date: Wed Aug 7 11:19:38 2013 -0500 add validation for assignment - workin progress commit 957e6a35d63ccc8860a51e1e73ca0cff43b0fe40 Merge: 6518f43 0923ed4 Author: Seangchan Ryu Date: Tue Jul 23 16:41:03 2013 -0500 Merge branch 'master' of https://github.com/dmwm/WMCore into validation_update commit 6518f43f6b5b1b148963183ca75311d87d0a853c Author: Seangchan Ryu Date: Tue Jul 23 16:39:30 2013 -0500 validation work in progress commit e010744fcb7b0b182f71c5948040ec35dec05770 Author: Seangchan Ryu Date: Fri Jul 5 12:01:18 2013 -0500 initial argument update validation commit 3f0aa75ab24622e5dc84ea25cc8f693ea9b07900 Merge: 7d6a00d f7a5f8e Author: Seangchan Ryu Date: Fri Jul 5 10:47:18 2013 -0500 Merge branch 'master' of https://github.com/dmwm/WMCore into reqmgr2_rest_7_3 Conflicts: src/python/WMCore/ReqMgr/Service/Request.py test/python/WMCore_t/ReqMgr_t/Service_t/ReqMgr_t.py commit 7d6a00d2a275bbc22e0fbd78d1df649c19e2f535 Author: Seangchan Ryu Date: Fri Jul 5 10:19:17 2013 -0500 remove aux.json commit 34b729404d033e37d7d910524a9cf55a2e2ab4cc Author: Valentin Kuznetsov Date: Mon Nov 10 12:00:18 2014 -0500 Comment out authz_fake commit 9845ca5489e3ae22409c20fc2a1193479d034ed9 Author: Valentin Kuznetsov Date: Mon Nov 10 11:59:38 2014 -0500 Comment out printouts commit 6c028c29b29d845f0f05d3b4cbb6640844059044 Author: Valentin Kuznetsov Date: Mon Nov 10 11:59:08 2014 -0500 Add activate actions for user/team managers commit 92b4f28e4b51ef2d751f1331f114f8db3c5aaf39 Author: Valentin Kuznetsov Date: Tue Sep 2 10:32:46 2014 -0400 Chnage default threshold for reload commit 18a080892d1ad550cf6f79edad419949ad30a735 Author: Valentin Kuznetsov Date: Tue Sep 2 10:32:09 2014 -0400 Add update_scripts method to automatically update scripts re-loading from its area commit d583a815455d0655c29b9afe739c599f2078d6db Author: Valentin Kuznetsov Date: Wed Aug 27 11:02:18 2014 -0400 Add scripts API to look-up script name commit 6c68fc1d94b6a635bdff14919f0195ca19828601 Author: Valentin Kuznetsov Date: Wed Aug 27 11:01:36 2014 -0400 Use ajaxScript to update code snippet textarea commit fa2158e590d976335abfc3d1c880bda03acb6f7a Author: Valentin Kuznetsov Date: Wed Aug 27 11:01:12 2014 -0400 Add ajaxScript function to look-up code snippet commit 3dd8320f9ab3d81b9f660c7f35993ba070a40689 Author: Valentin Kuznetsov Date: Mon Aug 25 11:13:02 2014 -0400 Add prototype how to select existing python snippets, TODO: create repository python snippets and pass dict with them to template commit 5bb30b432a621d5f1d6d9155e8876d0262a8dbd4 Merge: bec4f9a 3c36646 Author: ticoann Date: Tue Nov 25 14:32:04 2014 -0600 Merge pull request #5488 from ticoann/reqmgr2 Reqmgr2 commit 3c36646af878c5ce2ed9685034cc00c29c768a0b Author: Seangchan Ryu Date: Tue Nov 25 14:30:44 2014 -0600 add reqmgr2 python client commit aa6a0712f7e3043a442e91415ae266e55ffb3231 Merge: c0474fb 76e361c9 Author: Valentin Kuznetsov Date: Sun Nov 23 08:45:29 2014 -0500 Add setup dependencies commit c0474fb40400b611fd54cf75de8aeea62b8dbe2b Author: Valentin Kuznetsov Date: Tue Jun 24 11:29:25 2014 -0400 New code for ReqMgr web UI including changes for underlying WMCore commit 2e8f6d772ff55ecb2fc77d1938951d254db551b8 Author: Seangchan Ryu Date: Fri Aug 23 13:37:23 2013 -0500 Squashed commit of the following: commit fdeacde5bb86927273c1075f73fc35d48c7d51c7 Author: Seangchan Ryu Date: Fri Aug 23 13:34:11 2013 -0500 add clone test and fix assignment permission commit cc187d0a6f0bee300d4d5ff27ace13b2f4d7d1c6 Merge: 4468319 b356e27 Author: Seangchan Ryu Date: Mon Aug 19 11:29:50 2013 -0500 reqmgr rest api - work in progress Merge branch 'master' of https://github.com/dmwm/WMCore into validation_update Conflicts: src/python/WMCore/WMSpec/StdSpecs/StdBase.py commit 44683192d447f164c5b8b8d67d824920b8501785 Author: Seangchan Ryu Date: Wed Aug 7 11:19:38 2013 -0500 add validation for assignment - workin progress commit 957e6a35d63ccc8860a51e1e73ca0cff43b0fe40 Merge: 6518f43 0923ed4 Author: Seangchan Ryu Date: Tue Jul 23 16:41:03 2013 -0500 Merge branch 'master' of https://github.com/dmwm/WMCore into validation_update commit 6518f43f6b5b1b148963183ca75311d87d0a853c Author: Seangchan Ryu Date: Tue Jul 23 16:39:30 2013 -0500 validation work in progress commit e010744fcb7b0b182f71c5948040ec35dec05770 Author: Seangchan Ryu Date: Fri Jul 5 12:01:18 2013 -0500 initial argument update validation commit 3f0aa75ab24622e5dc84ea25cc8f693ea9b07900 Merge: 7d6a00d f7a5f8e Author: Seangchan Ryu Date: Fri Jul 5 10:47:18 2013 -0500 Merge branch 'master' of https://github.com/dmwm/WMCore into reqmgr2_rest_7_3 Conflicts: src/python/WMCore/ReqMgr/Service/Request.py test/python/WMCore_t/ReqMgr_t/Service_t/ReqMgr_t.py commit 7d6a00d2a275bbc22e0fbd78d1df649c19e2f535 Author: Seangchan Ryu Date: Fri Jul 5 10:19:17 2013 -0500 remove aux.json commit 6b0692958cf9b21f4995419f4ca2c812f31f4775 Author: Seangchan Ryu Date: Fri Nov 21 10:16:59 2014 -0600 fix priority mapping commit 5eb78fb3d4212f5d4838ba60f81b8ff00b67908e Author: Alan Date: Wed Nov 19 13:29:10 2014 +0100 fix job splitting for ACDC MCFakeFile now also accepts a different job splitting fix typo commit d2ff9b6161c37a85a45cf02e406525bdf1a65051 Author: Valentin Kuznetsov Date: Mon Nov 10 12:00:18 2014 -0500 Comment out authz_fake commit 58427d766348d937617a83aa05aea823e3438e32 Author: Valentin Kuznetsov Date: Mon Nov 10 11:59:38 2014 -0500 Comment out printouts commit f3e1f2dabd4bb953b1c9f5754fe54a8f493249b9 Author: Valentin Kuznetsov Date: Mon Nov 10 11:59:08 2014 -0500 Add activate actions for user/team managers commit a7f7fc6c05dd38a76a40b26777d97ffd9e1d7248 Author: Valentin Kuznetsov Date: Tue Sep 2 10:32:46 2014 -0400 Chnage default threshold for reload commit 51a9590cf51135dc4a9d774f85be8cf9ff0f217e Author: Valentin Kuznetsov Date: Tue Sep 2 10:32:09 2014 -0400 Add update_scripts method to automatically update scripts re-loading from its area commit 57b5f9527ce8d380d15b358ede1ee683b15fff5c Author: Valentin Kuznetsov Date: Wed Aug 27 11:02:18 2014 -0400 Add scripts API to look-up script name commit 3facb627db428d502a286a266e1ca31b0a86e57a Author: Valentin Kuznetsov Date: Wed Aug 27 11:01:36 2014 -0400 Use ajaxScript to update code snippet textarea commit fba405b29e858fbc68ccdf99982152947593d181 Author: Valentin Kuznetsov Date: Wed Aug 27 11:01:12 2014 -0400 Add ajaxScript function to look-up code snippet commit 260ea4af7ee7c800c2d92976db5f75acbe606335 Author: Valentin Kuznetsov Date: Mon Aug 25 11:13:02 2014 -0400 Add prototype how to select existing python snippets, TODO: create repository python snippets and pass dict with them to template commit 76e361c97ca3c9aa59f1ee5f19d74cab0a4cd720 Merge: 6849658 8fcd55e Author: Valentin Kuznetsov Date: Sun Nov 23 08:27:45 2014 -0500 Fix git merge conflicts commit 68496583588fcd0a7779216507df9eabeca91f5d Author: Valentin Kuznetsov Date: Mon Nov 10 12:00:18 2014 -0500 Comment out authz_fake commit 8159085eb60362ef76a763c04e67a1b9c608a045 Author: Valentin Kuznetsov Date: Mon Nov 10 11:59:38 2014 -0500 Comment out printouts commit 09a92455a6503a36ed35e8671371121e1e041635 Author: Valentin Kuznetsov Date: Mon Nov 10 11:59:08 2014 -0500 Add activate actions for user/team managers commit e911d180a8905cf227164d6705f89cb83afb0686 Author: Valentin Kuznetsov Date: Tue Sep 2 10:32:46 2014 -0400 Chnage default threshold for reload commit ed88195247f3b50b0bcab9a0bdcd7ebfc3e48b50 Author: Valentin Kuznetsov Date: Tue Sep 2 10:32:09 2014 -0400 Add update_scripts method to automatically update scripts re-loading from its area commit 5a3a008ab6958cee3eec9c9818004d099e8ba8f2 Author: Valentin Kuznetsov Date: Wed Aug 27 11:02:18 2014 -0400 Add scripts API to look-up script name commit e611db812ef5c9e3d663d60fdd27c589ff3cbf64 Author: Valentin Kuznetsov Date: Wed Aug 27 11:01:36 2014 -0400 Use ajaxScript to update code snippet textarea commit 40af4b47d1c85ed9b14bc038ac3cf2a771a79af0 Author: Valentin Kuznetsov Date: Wed Aug 27 11:01:12 2014 -0400 Add ajaxScript function to look-up code snippet commit 400b61db93c9f7d02dc2e280cc38a9fe4968beac Author: Valentin Kuznetsov Date: Mon Aug 25 11:13:02 2014 -0400 Add prototype how to select existing python snippets, TODO: create repository python snippets and pass dict with them to template commit ad92f71dae7557b2acb709a6d5603c3230a0c3cb Author: Valentin Kuznetsov Date: Mon Jul 7 15:07:54 2014 -0400 Adjust ReqMgrServer to new framework requirements commit 2c92e239d33f65b52c93419be11dae82b6db891f Author: Valentin Kuznetsov Date: Mon Jul 7 15:07:15 2014 -0400 Fix response.status commit ed544edbd8651a7011065f61053a944d0d257c2a Author: Valentin Kuznetsov Date: Tue Jun 24 11:29:25 2014 -0400 New code for ReqMgr web UI including changes for underlying WMCore commit 479a04d3d78ff9b104333debf1531c7c373e28ce Author: Seangchan Ryu Date: Fri Aug 23 13:37:23 2013 -0500 Squashed commit of the following: commit fdeacde5bb86927273c1075f73fc35d48c7d51c7 Author: Seangchan Ryu Date: Fri Aug 23 13:34:11 2013 -0500 add clone test and fix assignment permission commit cc187d0a6f0bee300d4d5ff27ace13b2f4d7d1c6 Merge: 4468319 b356e27 Author: Seangchan Ryu Date: Mon Aug 19 11:29:50 2013 -0500 reqmgr rest api - work in progress Merge branch 'master' of https://github.com/dmwm/WMCore into validation_update Conflicts: src/python/WMCore/WMSpec/StdSpecs/StdBase.py commit 44683192d447f164c5b8b8d67d824920b8501785 Author: Seangchan Ryu Date: Wed Aug 7 11:19:38 2013 -0500 add validation for assignment - workin progress commit 957e6a35d63ccc8860a51e1e73ca0cff43b0fe40 Merge: 6518f43 0923ed4 Author: Seangchan Ryu Date: Tue Jul 23 16:41:03 2013 -0500 Merge branch 'master' of https://github.com/dmwm/WMCore into validation_update commit 6518f43f6b5b1b148963183ca75311d87d0a853c Author: Seangchan Ryu Date: Tue Jul 23 16:39:30 2013 -0500 validation work in progress commit e010744fcb7b0b182f71c5948040ec35dec05770 Author: Seangchan Ryu Date: Fri Jul 5 12:01:18 2013 -0500 initial argument update validation commit 3f0aa75ab24622e5dc84ea25cc8f693ea9b07900 Merge: 7d6a00d f7a5f8e Author: Seangchan Ryu Date: Fri Jul 5 10:47:18 2013 -0500 Merge branch 'master' of https://github.com/dmwm/WMCore into reqmgr2_rest_7_3 Conflicts: src/python/WMCore/ReqMgr/Service/Request.py test/python/WMCore_t/ReqMgr_t/Service_t/ReqMgr_t.py commit 7d6a00d2a275bbc22e0fbd78d1df649c19e2f535 Author: Seangchan Ryu Date: Fri Jul 5 10:19:17 2013 -0500 remove aux.json commit 70f784415a8d3a2cb11fef55d050c90bf63d2b00 Merge: c21fcb8 548a73a Author: Seangchan Ryu Date: Fri Nov 21 11:33:03 2014 -0600 Merge branch 'master' of https://github.com/dmwm/WMCore into reqmgr2 commit bec4f9a51ebef283d8fc1e12bdbf81c8144b9201 Merge: 268b447 c21fcb8 Author: ticoann Date: Thu Nov 20 09:49:02 2014 -0600 Merge pull request #5480 from ticoann/reqmgr2 fix config cache refactor update unit test commit c21fcb82f0c72e10af82fb032e23ccd58cce8db9 Author: Seangchan Ryu Date: Thu Nov 20 09:40:25 2014 -0600 fix config cache refactor update unit test commit 268b447a79cfe4b1ad22ac16f21a37e338508749 Merge: c84ad34 8eee784 Author: ticoann Date: Tue Nov 18 14:33:15 2014 -0600 Merge pull request #5477 from ticoann/reqmgr2 Reqmgr2 commit 8eee78442af4a5e2926c79c85a538744706ee19d Merge: c84ad34 f63b43d Author: Seangchan Ryu Date: Tue Nov 18 14:16:15 2014 -0600 Merge branch 'master' of https://github.com/dmwm/WMCore into reqmgr2 Conflicts: setup_dependencies.py src/couchapps/ReqMgr/updates/updaterequest.js src/python/WMCore/Lexicon.py src/python/WMCore/ReqMgr/DataStructs/RequestStatus.py src/python/WMCore/ReqMgr/Service/Request.py src/python/WMCore/WMSpec/StdSpecs/StdBase.py src/python/WMCore/WMSpec/WMWorkloadTools.py test/data/ReqMgr/requests/ReReco.json commit 8fcd55e0c3a92c7af001e80448e0ed2a11fee435 Author: Valentin Kuznetsov Date: Mon Nov 10 12:00:18 2014 -0500 Comment out authz_fake commit 8f67f964d5c5e8bf461eff111a1eafd71b472e91 Author: Valentin Kuznetsov Date: Mon Nov 10 11:59:38 2014 -0500 Comment out printouts commit def5e780e0c4a243f1a77a5167267bb9dc8b3ceb Author: Valentin Kuznetsov Date: Mon Nov 10 11:59:08 2014 -0500 Add activate actions for user/team managers commit b6dd457046a35ec1d5c13811c8de50e5adceec06 Author: Valentin Kuznetsov Date: Tue Sep 2 10:32:46 2014 -0400 Chnage default threshold for reload commit 67ff2610c9ff066f76719175f979a13ec35720b4 Author: Valentin Kuznetsov Date: Tue Sep 2 10:32:09 2014 -0400 Add update_scripts method to automatically update scripts re-loading from its area commit 6f998b662f9a731edfd4562536575abf5cb0b0bd Author: Valentin Kuznetsov Date: Wed Aug 27 11:02:18 2014 -0400 Add scripts API to look-up script name commit b5c1b3921b5a0dc8920f6af16a4b3a5e2162424d Author: Valentin Kuznetsov Date: Wed Aug 27 11:01:36 2014 -0400 Use ajaxScript to update code snippet textarea commit c3418cc1048710dc7f8e55f25dcaf0f86c154044 Author: Valentin Kuznetsov Date: Wed Aug 27 11:01:12 2014 -0400 Add ajaxScript function to look-up code snippet commit ef6c6412ac5fe954029f5d13ba915fca565ed2c3 Author: Valentin Kuznetsov Date: Mon Aug 25 11:13:02 2014 -0400 Add prototype how to select existing python snippets, TODO: create repository python snippets and pass dict with them to template commit c84ad34fdebf974d39dada10527e02f3d205ffab Merge: 74bc2bb 9b30b68 Author: ticoann Date: Mon Jul 28 09:25:49 2014 -0500 Merge pull request #5211 from vkuznet/reqmgr2 Reqmgr changes commit 9b30b687be4ba8c6eef82213a27294829c620883 Author: Valentin Kuznetsov Date: Mon Jul 7 15:07:54 2014 -0400 Adjust ReqMgrServer to new framework requirements commit f2692a294dd565ad16f9dde781622f6685767849 Author: Valentin Kuznetsov Date: Mon Jul 7 15:07:15 2014 -0400 Fix response.status commit 74bc2bb1a3019c4561734b8b79d732090729e280 Merge: 237abac f2a2647 Author: ticoann Date: Mon Jun 30 09:57:39 2014 -0500 Merge pull request #5203 from vkuznet/reqmgr2 New code for ReqMgr web UI including changes for underlying WMCore commit f2a26474ba0893a4d2edd1c4b1dc443afc787a70 Author: Valentin Kuznetsov Date: Tue Jun 24 11:29:25 2014 -0400 New code for ReqMgr web UI including changes for underlying WMCore commit 237abac86ec49774ac2be1332aabbb32fcc5d2f0 Merge: aadc02c 021a666 Author: ticoann Date: Mon Aug 26 07:09:20 2013 -0700 Merge pull request #4755 from ticoann/reqmgr2 Add dashboard dependency to reqmgr2. commit 021a666826a263bde99a556be4c3799335167cb7 Author: Diego da Silva Gomes Date: Tue Jul 2 16:31:43 2013 +0200 Add dashboard dependency to reqmgr2. commit aadc02c80253de857be8cc136aa65c1b8e0f88b5 Merge: 61a1da1 50af1fc Author: ticoann Date: Fri Aug 23 12:04:46 2013 -0700 Merge pull request #4754 from ticoann/reqmgr2 reqmgr2 september deployment candidate merge to reqmgr2 branch commit 50af1fc4cc6f4d7f6d3b1366343a416148c1e64c Author: Seangchan Ryu Date: Fri Aug 23 13:37:23 2013 -0500 Squashed commit of the following: commit fdeacde5bb86927273c1075f73fc35d48c7d51c7 Author: Seangchan Ryu Date: Fri Aug 23 13:34:11 2013 -0500 add clone test and fix assignment permission commit cc187d0a6f0bee300d4d5ff27ace13b2f4d7d1c6 Merge: 4468319 b356e27 Author: Seangchan Ryu Date: Mon Aug 19 11:29:50 2013 -0500 reqmgr rest api - work in progress Merge branch 'master' of https://github.com/dmwm/WMCore into validation_update Conflicts: src/python/WMCore/WMSpec/StdSpecs/StdBase.py commit 44683192d447f164c5b8b8d67d824920b8501785 Author: Seangchan Ryu Date: Wed Aug 7 11:19:38 2013 -0500 add validation for assignment - workin progress commit 957e6a35d63ccc8860a51e1e73ca0cff43b0fe40 Merge: 6518f43 0923ed4 Author: Seangchan Ryu Date: Tue Jul 23 16:41:03 2013 -0500 Merge branch 'master' of https://github.com/dmwm/WMCore into validation_update commit 6518f43f6b5b1b148963183ca75311d87d0a853c Author: Seangchan Ryu Date: Tue Jul 23 16:39:30 2013 -0500 validation work in progress commit e010744fcb7b0b182f71c5948040ec35dec05770 Author: Seangchan Ryu Date: Fri Jul 5 12:01:18 2013 -0500 initial argument update validation commit 3f0aa75ab24622e5dc84ea25cc8f693ea9b07900 Merge: 7d6a00d f7a5f8e Author: Seangchan Ryu Date: Fri Jul 5 10:47:18 2013 -0500 Merge branch 'master' of https://github.com/dmwm/WMCore into reqmgr2_rest_7_3 Conflicts: src/python/WMCore/ReqMgr/Service/Request.py test/python/WMCore_t/ReqMgr_t/Service_t/ReqMgr_t.py commit 7d6a00d2a275bbc22e0fbd78d1df649c19e2f535 Author: Seangchan Ryu Date: Fri Jul 5 10:19:17 2013 -0500 remove aux.json --- bin/wmstats-update-status.py | 21 + src/couchapps/ReqMgr/updates/updaterequest.js | 11 +- src/couchapps/ReqMgr/views/bycampaign/map.js | 2 +- src/html/ReqMgr/img/cms_logo.jpg | Bin 0 -> 11891 bytes src/html/ReqMgr/javascript/ajax_utils.js | 35 + src/html/ReqMgr/javascript/prototype.js | 7510 +++++++++++++++++ src/html/ReqMgr/javascript/utils.js | 86 +- src/html/ReqMgr/style/kube.min.css | 1 + src/html/ReqMgr/style/main.css | 125 + src/html/ReqMgr/templates/admin.tmpl | 217 + src/html/ReqMgr/templates/apis.tmpl | 36 + src/html/ReqMgr/templates/approve.tmpl | 113 + src/html/ReqMgr/templates/assign.tmpl | 364 + src/html/ReqMgr/templates/confirm.tmpl | 35 + src/html/ReqMgr/templates/create.tmpl | 115 + src/html/ReqMgr/templates/error.tmpl | 6 + src/html/ReqMgr/templates/generic.tmpl | 6 + .../ReqMgr/templates/json/DataProcessing.tmpl | 34 + .../ReqMgr/templates/json/MonteCarlo.tmpl | 29 + src/html/ReqMgr/templates/json/ReDigi.tmpl | 41 + src/html/ReqMgr/templates/json/ReReco.tmpl | 38 + .../ReqMgr/templates/json/Resubmission.tmpl | 22 + .../ReqMgr/templates/json/StoreResults.tmpl | 24 + src/html/ReqMgr/templates/main.tmpl | 35 + src/html/ReqMgr/templates/menu.tmpl | 15 + src/html/ReqMgr/templates/requests.tmpl | 51 + src/html/ReqMgr/templates/search.tmpl | 22 + src/html/ReqMgr/templates/validate.tmpl | 9 + src/html/ReqMgr/templates/workflow.tmpl | 34 + src/python/WMCore/Cache/WMConfigCache.py | 102 +- .../HTTPFrontEnd/RequestManager/Assign.py | 6 +- src/python/WMCore/REST/Main.py | 2 +- src/python/WMCore/REST/Test.py | 11 +- src/python/WMCore/ReqMgr/Auth.py | 47 + .../ReqMgr/CherryPyThreads/DataCacheUpdate.py | 39 + .../WMCore/ReqMgr/DataStructs/DataCache.py | 40 + .../WMCore/ReqMgr/DataStructs/Request.py | 145 +- .../WMCore/ReqMgr/DataStructs/RequestError.py | 10 + .../ReqMgr/DataStructs/RequestStatus.py | 156 +- .../WMCore/ReqMgr/ExternalResourceTool.py | 52 + src/python/WMCore/ReqMgr/ReqMgrCouch.py | 2 + src/python/WMCore/ReqMgr/Service/Auxiliary.py | 11 +- src/python/WMCore/ReqMgr/Service/Request.py | 621 +- .../WMCore/ReqMgr/Tools/WorkflowTools.py | 608 ++ src/python/WMCore/ReqMgr/Tools/__init__.py | 0 src/python/WMCore/ReqMgr/Tools/cms.py | 164 + .../WMCore/ReqMgr/Tools/reqMgrClient.py | 339 + src/python/WMCore/ReqMgr/Utils/Validation.py | 98 + src/python/WMCore/ReqMgr/Utils/__init__.py | 0 .../ReqMgr/Utils/jsonwrapper/.__init__.py.un~ | Bin 0 -> 5269 bytes .../ReqMgr/Utils/jsonwrapper/__init__.py | 172 + .../ReqMgr/Utils/jsonwrapper/__init__.py~ | 138 + src/python/WMCore/ReqMgr/Utils/url_utils.py | 144 + src/python/WMCore/ReqMgr/Web/ReqMgrService.py | 742 ++ src/python/WMCore/ReqMgr/Web/__init__.py | 0 src/python/WMCore/ReqMgr/Web/tools.py | 185 + src/python/WMCore/ReqMgr/Web/utils.py | 222 + src/python/WMCore/Services/ReqMgr/ReqMgr.py | 173 + src/python/WMCore/Services/ReqMgr/__init__.py | 0 .../Services/RequestDB/RequestDBReader.py | 59 +- .../Services/RequestDB/RequestDBWriter.py | 7 +- src/python/WMCore/Services/Service.py | 9 +- .../WMCore/Services/WMStats/WMStatsReader.py | 35 +- src/python/WMCore/WMException.py | 3 + src/python/WMCore/WMSpec/StdSpecs/ReReco.py | 4 +- src/python/WMCore/WMSpec/StdSpecs/StdBase.py | 162 +- .../WMCore/WMSpec/StdSpecs/TaskChain.py | 12 +- src/python/WMCore/WMSpec/WMWorkload.py | 119 + src/python/WMCore/WMSpec/WMWorkloadTools.py | 103 +- .../REST/RESTBaseUnitTestWithDBBackend.py | 7 +- src/python/WMQuality/REST/ServerSetup.py | 14 +- src/python/WMQuality/TestInit.py | 3 +- .../WebTools/RequestManager/Assign.tmpl | 6 +- .../data/ReqMgr/requests/DMWM/MonteCarlo.json | 8 +- .../requests/DMWM/MonteCarloFromGEN.json | 8 +- .../ReqMgr/requests/DMWM/MonteCarlo_Ext.json | 8 +- .../ReqMgr/requests/DMWM/MonteCarlo_LHE.json | 8 +- .../ReqMgr/requests/DMWM/MonteCarlo_eff.json | 8 +- test/data/ReqMgr/requests/DMWM/ReDigi.json | 8 +- .../ReqMgr/requests/DMWM/ReDigi_cmsRun2.json | 8 +- test/data/ReqMgr/requests/DMWM/ReReco.json | 8 +- .../DMWM/TaskChainRunJet2012C_multiRun.json | 8 +- .../ReqMgr/requests/DMWM/TaskChain_Data.json | 8 +- .../ReqMgr/requests/DMWM/TaskChain_MC.json | 8 +- .../ReqMgr/requests/DMWM/TaskChain_MC_PU.json | 8 +- test/data/ReqMgr/requests/LHEStep0.json | 8 +- test/data/ReqMgr/requests/MonteCarlo.json | 8 +- .../ReqMgr/requests/MonteCarloFromGEN.json | 8 +- test/data/ReqMgr/requests/MonteCarloLHE.json | 8 +- test/data/ReqMgr/requests/MonteCarloLHEB.json | 8 +- .../ReqMgr/requests/MonteCarlo_GENSIM.json | 8 +- test/data/ReqMgr/requests/MonteCarlo_LHE.json | 8 +- test/data/ReqMgr/requests/ReDigi.json | 8 +- test/data/ReqMgr/requests/ReDigi2.json | 8 +- test/data/ReqMgr/requests/ReReco.json | 10 +- test/data/ReqMgr/requests/ReRecoSkim.json | 8 +- .../TaskChainH130GGgluonfusion_PU.json | 8 +- .../ReqMgr/requests/TaskChainProdMinBias.json | 8 +- .../TaskChainPyquenZeemumuJets_PU.json | 8 +- .../ReqMgr/requests/TaskChainRunTau2012A.json | 8 +- .../TaskChainSingleMuPt100FastSim.json | 8 +- test/data/ReqMgr/requests/TaskChainTTbar.json | 8 +- .../requests/TaskChainZJetsLNu_LHE.json | 8 +- .../data/ReqMgr/requests/TaskChainZTT_PU.json | 8 +- .../WMCore_t/ReqMgr_t/Service_t/ReqMgr_t.py | 202 +- test/python/WMCore_t/ReqMgr_t/TestConfig.py | 2 +- .../CherryPyPeriodicTask_t.py | 50 + .../CherryPyThread_t/__init__.py | 0 .../WMCore_t/Services_t/ReqMgr_t/ReqMgr_t.py | 119 + .../WMCore_t/Services_t/ReqMgr_t/__init__.py | 0 test/python/cherrypyTest.py | 0 111 files changed, 13632 insertions(+), 800 deletions(-) create mode 100644 bin/wmstats-update-status.py create mode 100644 src/html/ReqMgr/img/cms_logo.jpg create mode 100644 src/html/ReqMgr/javascript/ajax_utils.js create mode 100644 src/html/ReqMgr/javascript/prototype.js create mode 100644 src/html/ReqMgr/style/kube.min.css create mode 100644 src/html/ReqMgr/style/main.css create mode 100644 src/html/ReqMgr/templates/admin.tmpl create mode 100644 src/html/ReqMgr/templates/apis.tmpl create mode 100644 src/html/ReqMgr/templates/approve.tmpl create mode 100644 src/html/ReqMgr/templates/assign.tmpl create mode 100644 src/html/ReqMgr/templates/confirm.tmpl create mode 100644 src/html/ReqMgr/templates/create.tmpl create mode 100644 src/html/ReqMgr/templates/error.tmpl create mode 100644 src/html/ReqMgr/templates/generic.tmpl create mode 100644 src/html/ReqMgr/templates/json/DataProcessing.tmpl create mode 100644 src/html/ReqMgr/templates/json/MonteCarlo.tmpl create mode 100644 src/html/ReqMgr/templates/json/ReDigi.tmpl create mode 100644 src/html/ReqMgr/templates/json/ReReco.tmpl create mode 100644 src/html/ReqMgr/templates/json/Resubmission.tmpl create mode 100644 src/html/ReqMgr/templates/json/StoreResults.tmpl create mode 100644 src/html/ReqMgr/templates/main.tmpl create mode 100644 src/html/ReqMgr/templates/menu.tmpl create mode 100644 src/html/ReqMgr/templates/requests.tmpl create mode 100644 src/html/ReqMgr/templates/search.tmpl create mode 100644 src/html/ReqMgr/templates/validate.tmpl create mode 100644 src/html/ReqMgr/templates/workflow.tmpl create mode 100644 src/python/WMCore/ReqMgr/Auth.py create mode 100644 src/python/WMCore/ReqMgr/CherryPyThreads/DataCacheUpdate.py create mode 100644 src/python/WMCore/ReqMgr/DataStructs/DataCache.py create mode 100644 src/python/WMCore/ReqMgr/DataStructs/RequestError.py create mode 100644 src/python/WMCore/ReqMgr/ExternalResourceTool.py create mode 100644 src/python/WMCore/ReqMgr/Tools/WorkflowTools.py create mode 100644 src/python/WMCore/ReqMgr/Tools/__init__.py create mode 100644 src/python/WMCore/ReqMgr/Tools/cms.py create mode 100644 src/python/WMCore/ReqMgr/Tools/reqMgrClient.py create mode 100644 src/python/WMCore/ReqMgr/Utils/Validation.py create mode 100644 src/python/WMCore/ReqMgr/Utils/__init__.py create mode 100644 src/python/WMCore/ReqMgr/Utils/jsonwrapper/.__init__.py.un~ create mode 100644 src/python/WMCore/ReqMgr/Utils/jsonwrapper/__init__.py create mode 100644 src/python/WMCore/ReqMgr/Utils/jsonwrapper/__init__.py~ create mode 100644 src/python/WMCore/ReqMgr/Utils/url_utils.py create mode 100644 src/python/WMCore/ReqMgr/Web/ReqMgrService.py create mode 100644 src/python/WMCore/ReqMgr/Web/__init__.py create mode 100644 src/python/WMCore/ReqMgr/Web/tools.py create mode 100644 src/python/WMCore/ReqMgr/Web/utils.py create mode 100644 src/python/WMCore/Services/ReqMgr/ReqMgr.py create mode 100644 src/python/WMCore/Services/ReqMgr/__init__.py create mode 100644 test/python/WMCore_t/RequestManager_t/CherryPyThread_t/CherryPyPeriodicTask_t.py create mode 100644 test/python/WMCore_t/RequestManager_t/CherryPyThread_t/__init__.py create mode 100644 test/python/WMCore_t/Services_t/ReqMgr_t/ReqMgr_t.py create mode 100644 test/python/WMCore_t/Services_t/ReqMgr_t/__init__.py create mode 100644 test/python/cherrypyTest.py diff --git a/bin/wmstats-update-status.py b/bin/wmstats-update-status.py new file mode 100644 index 0000000000..4a03e0a3ce --- /dev/null +++ b/bin/wmstats-update-status.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +from optparse import OptionParser +from WMCore.Services.WMStats.WMStatsWriter import WMStatsWriter + +def updateRequestStatus(couchURL, requestList, status): + ww = WMStatsWriter(couchURL) + for request in requestList: + ww.updateRequestStatus(request, status) + print "%s is udated to %s" % (request, status) + +if __name__ == "__main__": + parser = OptionParser() + parser.add_option("--url", dest = "url", + help = "type couch db url") + parser.add_option("--status", dest = "status", + help = "type purge or delete url") + parser.add_option("--requests", dest = "requests", + help = "type last seq") + (options, args) = parser.parse_args() + if options.url: + updateRequestStatus(options.url, options.requests, options.status) diff --git a/src/couchapps/ReqMgr/updates/updaterequest.js b/src/couchapps/ReqMgr/updates/updaterequest.js index 2afd27493f..4d1e8bc70d 100644 --- a/src/couchapps/ReqMgr/updates/updaterequest.js +++ b/src/couchapps/ReqMgr/updates/updaterequest.js @@ -1,14 +1,14 @@ // update function // input: valueKey (what to change), value - new value -function(doc, req) -{ +function(doc, req) { if (doc === null) { return [null, "Error: document not found"]; }; function updateTransition() { var currentTS = Math.round((new Date()).getTime() / 1000); - var statusObj = {"Status": doc.RequestStatus, "UpdateTime": currentTS}; + var dn = doc.DN || null; + var statusObj = {"Status": doc.RequestStatus, "UpdateTime": currentTS, "DN": dn}; if (!doc.RequestTransition) { doc.RequestTransition = new Array(); @@ -30,6 +30,9 @@ function(doc, req) key == "InputDatasetTypes" || key == "InputDatasets" || key == "OutputDatasets" || + key == "CustodialSites" || + key == "NoneCustodialSites" || + key == "AutoApproveSubscriptionSites" || key == "Teams") { doc[key] = JSON.parse(newValues[key]); @@ -42,4 +45,4 @@ function(doc, req) } } return [doc, "OK"]; -} \ No newline at end of file +} diff --git a/src/couchapps/ReqMgr/views/bycampaign/map.js b/src/couchapps/ReqMgr/views/bycampaign/map.js index 0da0a564d1..8801b5d6b1 100644 --- a/src/couchapps/ReqMgr/views/bycampaign/map.js +++ b/src/couchapps/ReqMgr/views/bycampaign/map.js @@ -1,5 +1,5 @@ function(doc) { if (doc.Campaign){ - emit(doc.Camaign, null) ; + emit(doc.Campaign, null) ; } } \ No newline at end of file diff --git a/src/html/ReqMgr/img/cms_logo.jpg b/src/html/ReqMgr/img/cms_logo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8769ce2770a837dd6cb6d9c1c481c7db10e2256e GIT binary patch literal 11891 zcmbt)XH?V8^KTFp0RicqsDN~kCdEj8iV{IdAoL=60O?W!B0+&Dy`yxg4?>6mLhn+9 z1bFC5?~u@>Ct$pTr<$q*8?yx zFaRE(cfc75@b(eP%^3hNGz7>30DvoiOAK59#&e3{yaO170GIwJ4FDK02>&+?XOR9c zo(lj#lpEmUfALtKkN>uF&HqaO*LdOY3;)e=|8K_sP5;Y!HVe@G*Pj2c_OFuvF9rV! zpM3>zUIE+(+-GFq1YF=`VB};tBLaZuNG|;^-T#{VZ)3Q?c=6I@rYp=Wtmh1M*8mq7 z7#S~IWW03g;<+Ojg3rGLE^=Ptx}kjkGPkik(@i9g%DWE*SHvGwb@0N!QzTR!d_$R8 z`1l0`g(PoDNz2Hpso&Ai)Y8^{_(%`(Sl__J)Xdz%(#qQLxs$VttDC#uOaE5^sKD1@ z;SrHh(J`^fDXD4c8JQomJ{1;Wim{(@CDk>xb@dI6P0gJIVpn(1KfQe;qhsR}lT*_( zB=Yjg>e~8`jZNzA-tYZ`L)xFCe{nGY82{U4|3_TsPGGoj@gn0zrhjoUTnP9VIOoMn zHCOf92c+J%4#iI=*&;a2KG&`(CV=>ESc z{xXBRu-V-q54J%KEojlaI$zUYGnmU6-&VzL(>gMmeF-ifi=84p4ULCX+JB0GYr=(I zmXK}T$c2=0Gm(cfP1dHLN}y$%Zz>{R?u5?pB=EbHfuT^oF51cbb?IiC4hbw6*^3jhtteSh#!HdOF#|fd2ku%csl4 zofCP$_GnC}iuPs+L6d?JpOiimn>=tk$)>B4vm0)Akh}$huF$LsHZ5ut*LQ#0RBQ|9 z0*P!l+l zdngTH(%N=&L`zxB)S(!?QIG6&(`Ch9EKtKPa6r~kpUU?a2nC(grO_GNdibx2FMf8D z^B!Zy$3h`J_%v`edV54qWRIdmidlDLzfb94lC^sL*L`y*@vGBH5@R_zx#ZUMV4jo) zNZXg4&wm_7lVoGvVe{V?(@M9{{rliy*y**!E9h^fq(wUS6q*|Qn?t+5`uM}~@UnO8 zsTA$eF+&i~L=f8>v9#q1J{s#16TzE#D}!025*1)f&4_sUj~D16PSf?#+K1vPyI$;| zG_%L&K{r%TSA#3!I6sV%ip&%P7DmUH*!%nu74f>69AH)eVTkyQ>(j7U%hlK0cm{d6 z^9r3a9iksLNQaO!5-}{apayNS$eThbw43zWR9)ZF%vxOPa+wKo#(N=2l+hMhC%*1qir4CorVIrm z*zwcSDA|N8vmHV`WxhDB)LkPB+Eh-z@uj{t>FHzzwk=!#3pFo-9aHRHfAp;N@Ajrf zt8(E#`9u&n2F0RfZrtNTbi~CAAoF0f*bFGtWa`H6ac``aP(r-5I+0v?EJZ|6GJmT<7#pc26DtvG~ z!`|H`^)||OsL1T89zUbmB!Se5fyknqqWxuq9q=q(YE`_2y)0?V#-mu4pwDD-pRGa^ z`ROcchvmSe3Mog@l~g@2sf~HUmNZxhVKWfO8C=2{7oOxtbT9MWY-41!m?8U23EGq5 zDlE8vTAlj(|Auq}+n!LK2L+!*>Tm^V)K8cYPOoQ^I69KJXd1R3B+_T~sTq+$QU!<~ z0otJtsvpk;Z69j;uaxqP%s_&?977@THuGlyU!U$9ODaR6-F^A@1e^pk9pE{EtC1BM z_PDVqxkvbQ`Lnm|Bc+?s5A!m{DOI_Y={!!7lN9_~IaQ8RFV(wWu#*QX z)ROG3nkp&KpY}*INZ+v$A>O_05>KG&ARr8O*jiH;)Uph&X*eSd!>rl$s2h}Ml3MmN z#KoKN8kkfEOfo_pcb~FFLB1=1oqn=*_~~B87ke}_(w2#5fNNeL*m4E4HSB7CKq&;X z@#?jCt{<*G*SmO&Z*QP}+d6NZ#B_!_gKX>fdASYHndHcws}P8I@SJ?aML-Db!&FN zlH%U+8MMP28CCMfO|@2H7|Z3&dgi(UnT74U{{c2f(a z7s^)o<| z8@9^kzE->OPo`VIEo~M-G1%c?(YcJIqk8Q+tA1V~pLB~b>`!GLq}FHg6)^2pQ&; zGiSE)C+nahR3lH3V)4@heT=Z7-)*D$*`q0)lS}nUls%%kH4|NaG3lu8Fh-%}5!urb z{tcvFy7{X4(ohCw+S+PE=vQf}@K3%)SOb+mZncK;v*`U?i1>0lug`+vSCp670M)W= zGDfw9oGukc3%Eg;=vfP#@Q4Z{81+{VNWxsq%ef5j-al=-OR!=6xIX_?CH{$%{Jk0FFgggF2IX290t`%#q+ zrp-#nZ~WB!Pc$htFB`b_p9{VvMaR_EI=5+zKhu@I-LM3!4H zS1htQHc32mr9mQX&YRWB>Q-9n$-X#!x^xUQden82b;`V@fv(Zmrhc|W5uSqGY21{= z6M1zYyWw$)-_kwy<-Rvt_dKseFwfN7i!c9S+snPecPgFq^EtWg8o6aXyk_`PqX)R3 zW}-2u`GzB|7ll>r-3!uVl;|Fo>YTudI5XYte8Q{o z8UtbqLC5k`j_B%^SHS{n`)-cDwhik2GLEWp&H^PqrXEg>pW!B5eLUQ`7J8{}&1qHK zzAIb)?Euss0oi}2@NZGQzMG3w6-sNr$L%6?>sqkt_QC6s3+*TyH|y~KXk+t=^+VCm zXokmzrwKaZ;~lFxkW80TE*DgtUOWz2EEGm+BztHphedI(Oy$@;3oVe>yNNWTg{JEZ zLO4K=yh|0WIZt0KFBUMx2Z=-YWB&1(_7+`ol_B4vd5#g*%Hy5P4MF+9Z7G{;8`9&s zpmyQ7l!LaRT>f6|nq|*l*ISYX>T(sYty;au$^J}p%{AA(p8C8fo%qo~Z^l74nsf&E zkB=su-Q!*7nw2w{HOGn8qUOF-^R&KJxl#TVUy^ONn1OylC(Q3o5f3G$!p=u zCfR?OL^B+!bDq8612aZ{b+)vwlb$3vx;glujuaKNM$-AY1AUAaG*T#P5RyMsnQ_y;RM*cGW=y`?|EET(@c$u&F-)L_B*2HLtQqE zh#Kw62p=r*(TM!$B&J0b^Ejkk)w$Iw1|(g9nqWXZ@iisB2PoN>!C9qOn-w*;U!=*Z zZII?s6X;1ht`mXV(+w;nx(D?*x&$Tj8{IjHljjSJ`3e&xbHq0GiFIO+8c0B9GA0V2 zw~SPm1&PVUV)QA$AVR2kJJ~wx%)q~9%YJh8dGsI0ZPX=%9F0vc(n8l}?%ug`*(v*! z^L(q_;M+4~iVV7*Aw4NYI0(@2-OR|RYEW-gknedWthQs%r!nGXql;=6GKtRxRjx*r z(+J08Bb^oFB88dt8wXeDs-Xw98_z$o30lc=h7ukAT9D#|7vfnsDM3C*kTEJ_)NT?B zU6T|f6}||ub=>_efBH9fY4WHhh zyz}ZOj|$3x9JrqgF{~L;@cO9(>p7w;q^zKx9%>G_*`vZ{;T@}jdc04BbEqKfz~|LJ zcIj`RJ=r$)rq#{3una|BE0`cmwLLrRA1#3OJ;PRX;o&dast&dRyKjx z&R-f{*!w^;9&*SeOlst3$RWs6_V|+qt3lXHFa;v@mX0c+TxO`U4tZ^`hWX zf2+#@SlQ>JIn!9?|5QvU9V~2~C?#yc5OxO8&N?dG8$Et_l3cOvg?3t;uAX7tx*Jyt zdy8N)e`BdwkWQZOAuO_Is1`xj1OY5pQkfk|f|pehdvD0~!h#W0;Uvkpg4jY4h%ogb zrMy+%OxAzHV#tprRZ8Ey^FgkG9~Q2d^Vt+9l#?^IIrvQX*y4KbRVXA?UH+ciNWYIy zavH)@2woV8G3@wW{0k%YNDFssX%()}btmcXx$4XFUs(j6OV8%3dL%D9zPOm`rV&15 zD(xe@BndTC*NFanl9ha39n={dQdEs!Y%q$trw*GjNnPjvf_1}M%r>+S%esGwPr-1w zej=&gDk?CXHi`93k|;G$$sW<}zMT{oWKD@8P%#J;fhaT>_0~K2vFA-{*yY*Jnx!T^NA1hs5RR_q< zE-b?-iO8zZia#mGFFls3Y#4X+=vS&j?bwa3CqL%eBTQ(g^7EnDfA>nTBz)f7D5dK? zUQlThrAYoh&~9Bk9{jo>=hAM_l|!P5$52Erb+o7_6sA(^o)+FCxQN}H7*IxmbZ!FJt(8~tmzCJMaoeQ&m)7R=s$dD6!UU$oJ)?O6r&?k=*Nfy) zw%C~t;UEARQFe)30t0bjDj>Cc<>D#>H7v>%*<|+z@H{Xuse~$+9-P$GlNhIS=xo;IgRv9VR(a8CDb_N1AKSLYw@v8x*JX)wq|Y z8(E1$jm3UiU}cCZ#@O(~i&`-O#@^+>(j>n9CMnd0$&nGfHtx;9$aD}uYDDeAgq;Sp zn(o7myR}^8Q*czqlp%s9~A^zZo$G!;qHk*#jm80rg);6 zd&}l9gyf>5gev%*J%|GT7+!)fK0g&eJn@+JqKpH;O1;&J`-bZgOlFEZZb_e6`5CG*KQhT0xg;P3ly! z70h$1171DJf>4SJ#d}43Yiy=F{74^HP=`<6kTx*R&bijGUevmDiv7gv?9_0??a z@ay!N%W=RHP)>hxH67AermP;MR;Cb4apqwJedtRw%?)XF$>V^CAV}OW%>e}(DHaNP z)G3(f*ePPTZ~F`s%+gd2iN=NIiL=HRXxURusgwkHGZAw>%nLqA)7+=>f584Q8q@6Q z$f^Hc+erQ%;S6vg=}-ROzVOqhka^%PxMg@+qcene68#)F4P|p7G>c(4PjW(-awlIK z=5`THhAI67m3v)v1U=`5EOT~hrUXAH8(!qYoJ!Wd>eGBl@>SNa$jkcaE&_tzQo5X` zClK!jD#tfE*>4}T+Z39IVa=MLMUqf|5nF3xtU46iQ|6_?7>_ey$RtzL3*+jAEUg=3 z{n7%*^BYSZl&u1NLAB%*cjbeizCvTvB2Rbj5}ThcrJeBMGpeEIRH9}~Jx(cnQak%e z(Z;DjUEtp>)b-dFkJpp0qXQBrSpH}tyqZ0biI|F_kNoO$ar03&Msw>CxN z-k?uX*lr6Uf;Os(*Am1)~stnq0EA`0nX52(7Tyn+D_{sB+r zN6*)zX(+T6N-l93dh@Gu4y3zj2q;VG7srdVtB?mm5c@gn%0pD6cuJ5=hoBz+W}0SC zEOG=|&Lmv`HSQplI4FNi3#tyc=ihS)-+wvbpGmhM;dRh6++_dQXI5Zhu)(k44&cQQ z(I?lzUf)B6-6!2DraIiX7|%cmHt`H#A~OC7y6J?jqCpC%vH}`*ll8bP0j>&4@a~y4VSA1ql*jK#632zTh6GAORO2G6h$l$VtjqS1 zZk*h6TiuV8l`78CxDJwxwqW`N%b8l8s+j(@&r32!-B1qB&>YN@j6E@>f9M)yP_?Dd zf*@r_przalInC?MCpSU1ZQ#i5R6R-;7bVz&5mCD|lkm0*kU2}aoLa==rUwEa)rU?U zYervYzscN3Y@0Ae{}jYKkJ0smakRp&X0xA#y>2+SDq2z(e{VucNM|Ej*1mb6chM7?q6g3 z=!MR+c#PRlgsc7Smw9~ke5PaNCFd0%-XQxU^OIXt^~R*mxy88Krvg3;kFp)B2WAOL zSA0P=1VvUP!Sv+>Uparc+o~pglEH*S+cMT?s79pItaAUy7+$FTOWldxros(Uw%r#G zR=icGB_WXQWGC9cKPKSzGhxC^P(YppRei4GL5b@G|4vhQ%CLL(Dxz%}!GmIbi+dmf z%QuSDF8f5$VrS7T8@)FWI98y`mhN-Pp7zvS5wHK?rHpAQ3y)i$QBp%}d=6Ni683|% z3`rQL@&ZS-co#FAOo6W!ROQyS4*y=m!gX7&_PS85^( zk5Z~Cw9~&o8p}F6?Wwr$!9Vi*r_Rx&=}3!$pJdtuKA?13D<3*Z50DH$Y<|k+x~JkU zCQ7-S_C_b)rR*U{m)vS%KJoL#%U0Gsd46_eRJ`DX4e?amiMLUSumB`}A8dR=koR{o zxi_o|Qnvzq(O7P-IvA5FlYhRsq#0<2(~EfRvDI#AjD_a^Rt)Sp~tjs_V4w7H2PfnGfxjoe1h~FC)Il>=fR7f$kD~aNHJ#0t+^`e zka0>BHLec5{wV~GrZ%7cb%4s-I|Fo-o&i$8t#~k)&U(sxq8l;+zKY-w+${Wo1g`{Qjf z9@Y|i<3wC@A@U9JkKSEb(l*pAdCe*j5!N#nsljBI`+|RxG9`qHjpMhA z@b-`g`H6JDtaN`_Ic58@^0%jXI-gzA_npoV3F;wnyC>o{%IYs#fP}xQm?BxEfyuhf zDQ#uS7EZAL;q=|#F#?~zuKu~KK@yW#8quFF$Bp2Q!|Sq!A7r9!X;vpq)b}3E?b$pa|7W0m^tI#s&PT|sDlp!~4P8Ni}3;Ld>bX!s9@#^F15 zGnU)8xN@yc`e@)=SPRy7@s<5*L|v0j5|Vi z30cJcQZOMNF4iJC?kO3KmZ`N~p6xilcnXqQlD;|GqS%}wQ#+#6a(>JtMGC zGpd#zhk8XEam~L(7HE|RWQZd`7)-b=(mGSO97NDLG)E9=L3CILpGsp}R&){B<2AS1 z5kO@SZSA9p&I0sD{sNB3CJok=X)!B(2PtSOlDA|{VB6nyI6c>Vtz=My$rAQ&_6mrYn#jeVTIJoWiB@*>q}U*zpOk1q|r__m*N$y5wi%$ zwjx5CYb~BSwcXDkXuz4EA(XoLToXku6dZ|{WX6re<8UUi@#0d)W;)kNf%A$Cq(Aza zLp+r&#@&5tt=Ya&wH-RQ(p;PR>_!TGv2Y{z+qvpPi$iG6mK*-0d~V6s^|%(l@_a70 z;W8NR7Upl1dWAo$aGZyNv9}Eg8v)|RY4TNX+!w4H=N32QOz8CT-cLFRq+T{#J_B5# z<})n&C2B8$8VCkWo~*XV2DD8vzdzph~QX|6j}kz)pD#H z=MJw@4F`v|WrQdYI-c%CqWVKy07oE4)p7+(x}rt`t^X}@F0f1U#jI3;?y($O#2LV* zX8Twc15acr%cm`xL=UnSiB{gEJbsT~JBh znd|1O^|$I(V^jr~m0zY=!c9sowo)B_d`~Tr9u4zvAB6Gfd^puXkt;vC#E3~!rMR7v zb6KolOiK>CiR_&Y?xrh*C7&S9&PMq!62)oeKS(!VQn?Q|zZdBFi)?mP5ADIm%GSS( zS_k59w&wNq+i=YqVo>3;MPcUc-&m@eo4)<-rxlL;HU69lMrAMX#OW6$ZMW)7>=^YB zb~ta470-*>f_z4#4lC$gw)E$qC-5R(9hDw9PCIZyT$V5vk2R6*$FiVaTeT=fb(=-# z8^@2tFBUF)zfGfG>Z61DQ0*}kWhy$h+B!$SJA`kE;(N3nF|DVgX+0+wN-hg3qKs)` z?-mHrwh>obz>}IG2~Sms#1x*@QNpzee>XTKeL2s z!WtwDhH~y;1HCB>e~@?+QATbN@e|`8uEZPomZq+89`dVOr(g_<$bY;}@bj_;*Sj@mhJ^le3_ct{(;uPPjzKN~XMixo8Q>PWBxp)!!`3tqx+J|5PPKnQPnRBV?aJsR{0KESng_^ z@RMV4aeVgbX8C>g8$F~S^5>5Vgk|v}PgPpf5#?lM%ei2wxZ)B5Ke(38>$o(F2GZ41 zh6KI3x_}5lu7iZ6nPv7c+9k51$}SoBSNY=PL5^1? zD%0%sGRB;)npr0)B_N!l3FtgjJ7y;_$RyuEJZf2JjxBW!RrX5J3KBIXqQnYE#qO5I z95fDum_5h~&UKWOmRb;t zveZ)>NvYFN@7*qhsva08h$m(w*eRLdgx*sfi5??qR(*6QVt=l2gh6U{o0GPG;Fi*B zt}wIUvZ9=I_lvYCV&u!cu7rNICOx-g3AWQ~s2Fm4vl{lTwn-NGG<8k;0rt=2XX^-`X5@gW}E%(R>eiz$&@0V+W7Mb&dy`}oA z)G0w0Y0u}Z49RjN*M6=%sk>htkX5l2zv^I|Vvoz+Jnjs!l6yk^*)9_Z83eKtanM1$ zbPz)X>9t>NF`aqHuYIp;EQ4<*vWC)pK>vt<)Ww=Me+dY5R_*l_#l)hwhNu7ZH2wlp z3BV4zsxAXb%rBP$^^C@Mfw(hPElZ~66d{Qn%L`8uI-E6`N$&Qt$1Vu=f@fs=I4ra8 zD8_gwf3IPrKM}=!gHjKIb)R0KA;``^1#*0GI#8m^8RXa5_*yfkXDibw(|$phxErp_ zW1F+QO&P;!Gn-`jL`cHA6>A$SmK*EG>xMbE>Md9$EVQ4kE=ILz6>ciu-d$j`>nE*8 z(gg#{W_p3S-T}p%N0@^TZ#+Tu4M#7iZwpT{E0%PmqP5Q7aF8>iLFg2dQs({}Lap`F_MxA(z6%P<4N7H=DKTEjssRsORT`CI{+fbXWC%&nVWbHY>)ejz z!9e4FzxHdI1gQiAXs$ua-Aq(ej?2`GY~JCRl7fV<&&R5aaC)&3A+P=3p(AGi?`}p4 zcwP_Xi5*&4n3z$EU6`1dnb0KF)z-f=pP3oqNt~IuvK%=hP!R8R(!-EW@-1WMknO*1 zY5PU4-xc2JG$ntBFP)oBm)Ria1~g4bO%;_&j8=L zkgM&x%3sk_Ui|-Fx$1-fMbN}{wO{~vdF2eyeg>$$6f(>~o;U+kc$|lAk;ds}EBfF7 zX-3C)gLf^Eu#?ol_0uvcWJ4=}9!CKmFh!o5aQpWD8G!aX>a;E84DfImVN8;ur#C7c rUmO7M^SSgR&j9q4aqzYh>$!E0SGJDE=(nW-e{*DP?d|P{olXA_qO3&M literal 0 HcmV?d00001 diff --git a/src/html/ReqMgr/javascript/ajax_utils.js b/src/html/ReqMgr/javascript/ajax_utils.js new file mode 100644 index 0000000000..18d03ea8ad --- /dev/null +++ b/src/html/ReqMgr/javascript/ajax_utils.js @@ -0,0 +1,35 @@ +function cleanConfirmation () { + var doc = document.getElementById('confirmation'); + doc.innerHTML=''; + doc.className=''; +} +function ajaxRequest(path, parameters) { + // path is an URI binded to certain server method + // parameters is dict of parameters passed to the server function + new Ajax.Updater('response', path, + { method: 'post' , + parameters : parameters, + onCreate: function() { + var doc = document.getElementById('confirmation'); + doc.innerHTML='Your request has been submitted'; + doc.className='tools-alert tools-alert-blue confirmation shadow'; + }, + onException: function() { + var doc = document.getElementById('confirmation'); + doc.innerHTML='ERROR! Your request has been failed'; + doc.className='tools-alert tools-alert-red confirmation shadow'; + setTimeout(cleanConfirmation, 5000); + }, + onComplete : function(response) { + var doc = document.getElementById('confirmation'); + if (response.status==200 || response.status==201) { + doc.innerHTML='SUCCESS! Your request has been processed with status '+response.status; + doc.className='tools-alert tools-alert-green confirmation shadow'; + } else { + doc.innerHTML='WARNING! Your request has been processed with status '+response.status; + doc.className='tools-alert tools-alert-yellow confirmation shadow'; + } + setTimeout(cleanConfirmation, 5000); + } + }); +} diff --git a/src/html/ReqMgr/javascript/prototype.js b/src/html/ReqMgr/javascript/prototype.js new file mode 100644 index 0000000000..5c7ed2bd4c --- /dev/null +++ b/src/html/ReqMgr/javascript/prototype.js @@ -0,0 +1,7510 @@ +/* Prototype JavaScript framework, version 1.7.2 + * (c) 2005-2010 Sam Stephenson + * + * Prototype is freely distributable under the terms of an MIT-style license. + * For details, see the Prototype web site: http://www.prototypejs.org/ + * + *--------------------------------------------------------------------------*/ + +var Prototype = { + + Version: '1.7.2', + + Browser: (function(){ + var ua = navigator.userAgent; + var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]'; + return { + IE: !!window.attachEvent && !isOpera, + Opera: isOpera, + WebKit: ua.indexOf('AppleWebKit/') > -1, + Gecko: ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1, + MobileSafari: /Apple.*Mobile/.test(ua) + } + })(), + + BrowserFeatures: { + XPath: !!document.evaluate, + + SelectorsAPI: !!document.querySelector, + + ElementExtensions: (function() { + var constructor = window.Element || window.HTMLElement; + return !!(constructor && constructor.prototype); + })(), + SpecificElementExtensions: (function() { + if (typeof window.HTMLDivElement !== 'undefined') + return true; + + var div = document.createElement('div'), + form = document.createElement('form'), + isSupported = false; + + if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) { + isSupported = true; + } + + div = form = null; + + return isSupported; + })() + }, + + ScriptFragment: ']*>([\\S\\s]*?)<\/script\\s*>', + JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, + + emptyFunction: function() { }, + + K: function(x) { return x } +}; + +if (Prototype.Browser.MobileSafari) + Prototype.BrowserFeatures.SpecificElementExtensions = false; +/* Based on Alex Arnell's inheritance implementation. */ + +var Class = (function() { + + var IS_DONTENUM_BUGGY = (function(){ + for (var p in { toString: 1 }) { + if (p === 'toString') return false; + } + return true; + })(); + + function subclass() {}; + function create() { + var parent = null, properties = $A(arguments); + if (Object.isFunction(properties[0])) + parent = properties.shift(); + + function klass() { + this.initialize.apply(this, arguments); + } + + Object.extend(klass, Class.Methods); + klass.superclass = parent; + klass.subclasses = []; + + if (parent) { + subclass.prototype = parent.prototype; + klass.prototype = new subclass; + parent.subclasses.push(klass); + } + + for (var i = 0, length = properties.length; i < length; i++) + klass.addMethods(properties[i]); + + if (!klass.prototype.initialize) + klass.prototype.initialize = Prototype.emptyFunction; + + klass.prototype.constructor = klass; + return klass; + } + + function addMethods(source) { + var ancestor = this.superclass && this.superclass.prototype, + properties = Object.keys(source); + + if (IS_DONTENUM_BUGGY) { + if (source.toString != Object.prototype.toString) + properties.push("toString"); + if (source.valueOf != Object.prototype.valueOf) + properties.push("valueOf"); + } + + for (var i = 0, length = properties.length; i < length; i++) { + var property = properties[i], value = source[property]; + if (ancestor && Object.isFunction(value) && + value.argumentNames()[0] == "$super") { + var method = value; + value = (function(m) { + return function() { return ancestor[m].apply(this, arguments); }; + })(property).wrap(method); + + value.valueOf = (function(method) { + return function() { return method.valueOf.call(method); }; + })(method); + + value.toString = (function(method) { + return function() { return method.toString.call(method); }; + })(method); + } + this.prototype[property] = value; + } + + return this; + } + + return { + create: create, + Methods: { + addMethods: addMethods + } + }; +})(); +(function() { + + var _toString = Object.prototype.toString, + _hasOwnProperty = Object.prototype.hasOwnProperty, + NULL_TYPE = 'Null', + UNDEFINED_TYPE = 'Undefined', + BOOLEAN_TYPE = 'Boolean', + NUMBER_TYPE = 'Number', + STRING_TYPE = 'String', + OBJECT_TYPE = 'Object', + FUNCTION_CLASS = '[object Function]', + BOOLEAN_CLASS = '[object Boolean]', + NUMBER_CLASS = '[object Number]', + STRING_CLASS = '[object String]', + ARRAY_CLASS = '[object Array]', + DATE_CLASS = '[object Date]', + NATIVE_JSON_STRINGIFY_SUPPORT = window.JSON && + typeof JSON.stringify === 'function' && + JSON.stringify(0) === '0' && + typeof JSON.stringify(Prototype.K) === 'undefined'; + + + + var DONT_ENUMS = ['toString', 'toLocaleString', 'valueOf', + 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', 'constructor']; + + var IS_DONTENUM_BUGGY = (function(){ + for (var p in { toString: 1 }) { + if (p === 'toString') return false; + } + return true; + })(); + + function Type(o) { + switch(o) { + case null: return NULL_TYPE; + case (void 0): return UNDEFINED_TYPE; + } + var type = typeof o; + switch(type) { + case 'boolean': return BOOLEAN_TYPE; + case 'number': return NUMBER_TYPE; + case 'string': return STRING_TYPE; + } + return OBJECT_TYPE; + } + + function extend(destination, source) { + for (var property in source) + destination[property] = source[property]; + return destination; + } + + function inspect(object) { + try { + if (isUndefined(object)) return 'undefined'; + if (object === null) return 'null'; + return object.inspect ? object.inspect() : String(object); + } catch (e) { + if (e instanceof RangeError) return '...'; + throw e; + } + } + + function toJSON(value) { + return Str('', { '': value }, []); + } + + function Str(key, holder, stack) { + var value = holder[key]; + if (Type(value) === OBJECT_TYPE && typeof value.toJSON === 'function') { + value = value.toJSON(key); + } + + var _class = _toString.call(value); + + switch (_class) { + case NUMBER_CLASS: + case BOOLEAN_CLASS: + case STRING_CLASS: + value = value.valueOf(); + } + + switch (value) { + case null: return 'null'; + case true: return 'true'; + case false: return 'false'; + } + + var type = typeof value; + switch (type) { + case 'string': + return value.inspect(true); + case 'number': + return isFinite(value) ? String(value) : 'null'; + case 'object': + + for (var i = 0, length = stack.length; i < length; i++) { + if (stack[i] === value) { + throw new TypeError("Cyclic reference to '" + value + "' in object"); + } + } + stack.push(value); + + var partial = []; + if (_class === ARRAY_CLASS) { + for (var i = 0, length = value.length; i < length; i++) { + var str = Str(i, value, stack); + partial.push(typeof str === 'undefined' ? 'null' : str); + } + partial = '[' + partial.join(',') + ']'; + } else { + var keys = Object.keys(value); + for (var i = 0, length = keys.length; i < length; i++) { + var key = keys[i], str = Str(key, value, stack); + if (typeof str !== "undefined") { + partial.push(key.inspect(true)+ ':' + str); + } + } + partial = '{' + partial.join(',') + '}'; + } + stack.pop(); + return partial; + } + } + + function stringify(object) { + return JSON.stringify(object); + } + + function toQueryString(object) { + return $H(object).toQueryString(); + } + + function toHTML(object) { + return object && object.toHTML ? object.toHTML() : String.interpret(object); + } + + function keys(object) { + if (Type(object) !== OBJECT_TYPE) { throw new TypeError(); } + var results = []; + for (var property in object) { + if (_hasOwnProperty.call(object, property)) + results.push(property); + } + + if (IS_DONTENUM_BUGGY) { + for (var i = 0; property = DONT_ENUMS[i]; i++) { + if (_hasOwnProperty.call(object, property)) + results.push(property); + } + } + + return results; + } + + function values(object) { + var results = []; + for (var property in object) + results.push(object[property]); + return results; + } + + function clone(object) { + return extend({ }, object); + } + + function isElement(object) { + return !!(object && object.nodeType == 1); + } + + function isArray(object) { + return _toString.call(object) === ARRAY_CLASS; + } + + var hasNativeIsArray = (typeof Array.isArray == 'function') + && Array.isArray([]) && !Array.isArray({}); + + if (hasNativeIsArray) { + isArray = Array.isArray; + } + + function isHash(object) { + return object instanceof Hash; + } + + function isFunction(object) { + return _toString.call(object) === FUNCTION_CLASS; + } + + function isString(object) { + return _toString.call(object) === STRING_CLASS; + } + + function isNumber(object) { + return _toString.call(object) === NUMBER_CLASS; + } + + function isDate(object) { + return _toString.call(object) === DATE_CLASS; + } + + function isUndefined(object) { + return typeof object === "undefined"; + } + + extend(Object, { + extend: extend, + inspect: inspect, + toJSON: NATIVE_JSON_STRINGIFY_SUPPORT ? stringify : toJSON, + toQueryString: toQueryString, + toHTML: toHTML, + keys: Object.keys || keys, + values: values, + clone: clone, + isElement: isElement, + isArray: isArray, + isHash: isHash, + isFunction: isFunction, + isString: isString, + isNumber: isNumber, + isDate: isDate, + isUndefined: isUndefined + }); +})(); +Object.extend(Function.prototype, (function() { + var slice = Array.prototype.slice; + + function update(array, args) { + var arrayLength = array.length, length = args.length; + while (length--) array[arrayLength + length] = args[length]; + return array; + } + + function merge(array, args) { + array = slice.call(array, 0); + return update(array, args); + } + + function argumentNames() { + var names = this.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1] + .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '') + .replace(/\s+/g, '').split(','); + return names.length == 1 && !names[0] ? [] : names; + } + + + function bind(context) { + if (arguments.length < 2 && Object.isUndefined(arguments[0])) + return this; + + if (!Object.isFunction(this)) + throw new TypeError("The object is not callable."); + + var nop = function() {}; + var __method = this, args = slice.call(arguments, 1); + + var bound = function() { + var a = merge(args, arguments); + var c = this instanceof bound ? this : context; + return __method.apply(c, a); + }; + + nop.prototype = this.prototype; + bound.prototype = new nop(); + + return bound; + } + + function bindAsEventListener(context) { + var __method = this, args = slice.call(arguments, 1); + return function(event) { + var a = update([event || window.event], args); + return __method.apply(context, a); + } + } + + function curry() { + if (!arguments.length) return this; + var __method = this, args = slice.call(arguments, 0); + return function() { + var a = merge(args, arguments); + return __method.apply(this, a); + } + } + + function delay(timeout) { + var __method = this, args = slice.call(arguments, 1); + timeout = timeout * 1000; + return window.setTimeout(function() { + return __method.apply(__method, args); + }, timeout); + } + + function defer() { + var args = update([0.01], arguments); + return this.delay.apply(this, args); + } + + function wrap(wrapper) { + var __method = this; + return function() { + var a = update([__method.bind(this)], arguments); + return wrapper.apply(this, a); + } + } + + function methodize() { + if (this._methodized) return this._methodized; + var __method = this; + return this._methodized = function() { + var a = update([this], arguments); + return __method.apply(null, a); + }; + } + + var extensions = { + argumentNames: argumentNames, + bindAsEventListener: bindAsEventListener, + curry: curry, + delay: delay, + defer: defer, + wrap: wrap, + methodize: methodize + }; + + if (!Function.prototype.bind) + extensions.bind = bind; + + return extensions; +})()); + + + +(function(proto) { + + + function toISOString() { + return this.getUTCFullYear() + '-' + + (this.getUTCMonth() + 1).toPaddedString(2) + '-' + + this.getUTCDate().toPaddedString(2) + 'T' + + this.getUTCHours().toPaddedString(2) + ':' + + this.getUTCMinutes().toPaddedString(2) + ':' + + this.getUTCSeconds().toPaddedString(2) + 'Z'; + } + + + function toJSON() { + return this.toISOString(); + } + + if (!proto.toISOString) proto.toISOString = toISOString; + if (!proto.toJSON) proto.toJSON = toJSON; + +})(Date.prototype); + + +RegExp.prototype.match = RegExp.prototype.test; + +RegExp.escape = function(str) { + return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); +}; +var PeriodicalExecuter = Class.create({ + initialize: function(callback, frequency) { + this.callback = callback; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + execute: function() { + this.callback(this); + }, + + stop: function() { + if (!this.timer) return; + clearInterval(this.timer); + this.timer = null; + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.execute(); + this.currentlyExecuting = false; + } catch(e) { + this.currentlyExecuting = false; + throw e; + } + } + } +}); +Object.extend(String, { + interpret: function(value) { + return value == null ? '' : String(value); + }, + specialChar: { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '\\': '\\\\' + } +}); + +Object.extend(String.prototype, (function() { + var NATIVE_JSON_PARSE_SUPPORT = window.JSON && + typeof JSON.parse === 'function' && + JSON.parse('{"test": true}').test; + + function prepareReplacement(replacement) { + if (Object.isFunction(replacement)) return replacement; + var template = new Template(replacement); + return function(match) { return template.evaluate(match) }; + } + + function isNonEmptyRegExp(regexp) { + return regexp.source && regexp.source !== '(?:)'; + } + + + function gsub(pattern, replacement) { + var result = '', source = this, match; + replacement = prepareReplacement(replacement); + + if (Object.isString(pattern)) + pattern = RegExp.escape(pattern); + + if (!(pattern.length || isNonEmptyRegExp(pattern))) { + replacement = replacement(''); + return replacement + source.split('').join(replacement) + replacement; + } + + while (source.length > 0) { + match = source.match(pattern) + if (match && match[0].length > 0) { + result += source.slice(0, match.index); + result += String.interpret(replacement(match)); + source = source.slice(match.index + match[0].length); + } else { + result += source, source = ''; + } + } + return result; + } + + function sub(pattern, replacement, count) { + replacement = prepareReplacement(replacement); + count = Object.isUndefined(count) ? 1 : count; + + return this.gsub(pattern, function(match) { + if (--count < 0) return match[0]; + return replacement(match); + }); + } + + function scan(pattern, iterator) { + this.gsub(pattern, iterator); + return String(this); + } + + function truncate(length, truncation) { + length = length || 30; + truncation = Object.isUndefined(truncation) ? '...' : truncation; + return this.length > length ? + this.slice(0, length - truncation.length) + truncation : String(this); + } + + function strip() { + return this.replace(/^\s+/, '').replace(/\s+$/, ''); + } + + function stripTags() { + return this.replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi, ''); + } + + function stripScripts() { + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); + } + + function extractScripts() { + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'), + matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + return (this.match(matchAll) || []).map(function(scriptTag) { + return (scriptTag.match(matchOne) || ['', ''])[1]; + }); + } + + function evalScripts() { + return this.extractScripts().map(function(script) { return eval(script); }); + } + + function escapeHTML() { + return this.replace(/&/g,'&').replace(//g,'>'); + } + + function unescapeHTML() { + return this.stripTags().replace(/</g,'<').replace(/>/g,'>').replace(/&/g,'&'); + } + + + function toQueryParams(separator) { + var match = this.strip().match(/([^?#]*)(#.*)?$/); + if (!match) return { }; + + return match[1].split(separator || '&').inject({ }, function(hash, pair) { + if ((pair = pair.split('='))[0]) { + var key = decodeURIComponent(pair.shift()), + value = pair.length > 1 ? pair.join('=') : pair[0]; + + if (value != undefined) { + value = value.gsub('+', ' '); + value = decodeURIComponent(value); + } + + if (key in hash) { + if (!Object.isArray(hash[key])) hash[key] = [hash[key]]; + hash[key].push(value); + } + else hash[key] = value; + } + return hash; + }); + } + + function toArray() { + return this.split(''); + } + + function succ() { + return this.slice(0, this.length - 1) + + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); + } + + function times(count) { + return count < 1 ? '' : new Array(count + 1).join(this); + } + + function camelize() { + return this.replace(/-+(.)?/g, function(match, chr) { + return chr ? chr.toUpperCase() : ''; + }); + } + + function capitalize() { + return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); + } + + function underscore() { + return this.replace(/::/g, '/') + .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2') + .replace(/([a-z\d])([A-Z])/g, '$1_$2') + .replace(/-/g, '_') + .toLowerCase(); + } + + function dasherize() { + return this.replace(/_/g, '-'); + } + + function inspect(useDoubleQuotes) { + var escapedString = this.replace(/[\x00-\x1f\\]/g, function(character) { + if (character in String.specialChar) { + return String.specialChar[character]; + } + return '\\u00' + character.charCodeAt().toPaddedString(2, 16); + }); + if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; + return "'" + escapedString.replace(/'/g, '\\\'') + "'"; + } + + function unfilterJSON(filter) { + return this.replace(filter || Prototype.JSONFilter, '$1'); + } + + function isJSON() { + var str = this; + if (str.blank()) return false; + str = str.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'); + str = str.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'); + str = str.replace(/(?:^|:|,)(?:\s*\[)+/g, ''); + return (/^[\],:{}\s]*$/).test(str); + } + + function evalJSON(sanitize) { + var json = this.unfilterJSON(), + cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + if (cx.test(json)) { + json = json.replace(cx, function (a) { + return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }); + } + try { + if (!sanitize || json.isJSON()) return eval('(' + json + ')'); + } catch (e) { } + throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); + } + + function parseJSON() { + var json = this.unfilterJSON(); + return JSON.parse(json); + } + + function include(pattern) { + return this.indexOf(pattern) > -1; + } + + function startsWith(pattern, position) { + position = Object.isNumber(position) ? position : 0; + return this.lastIndexOf(pattern, position) === position; + } + + function endsWith(pattern, position) { + pattern = String(pattern); + position = Object.isNumber(position) ? position : this.length; + if (position < 0) position = 0; + if (position > this.length) position = this.length; + var d = position - pattern.length; + return d >= 0 && this.indexOf(pattern, d) === d; + } + + function empty() { + return this == ''; + } + + function blank() { + return /^\s*$/.test(this); + } + + function interpolate(object, pattern) { + return new Template(this, pattern).evaluate(object); + } + + return { + gsub: gsub, + sub: sub, + scan: scan, + truncate: truncate, + strip: String.prototype.trim || strip, + stripTags: stripTags, + stripScripts: stripScripts, + extractScripts: extractScripts, + evalScripts: evalScripts, + escapeHTML: escapeHTML, + unescapeHTML: unescapeHTML, + toQueryParams: toQueryParams, + parseQuery: toQueryParams, + toArray: toArray, + succ: succ, + times: times, + camelize: camelize, + capitalize: capitalize, + underscore: underscore, + dasherize: dasherize, + inspect: inspect, + unfilterJSON: unfilterJSON, + isJSON: isJSON, + evalJSON: NATIVE_JSON_PARSE_SUPPORT ? parseJSON : evalJSON, + include: include, + startsWith: String.prototype.startsWith || startsWith, + endsWith: String.prototype.endsWith || endsWith, + empty: empty, + blank: blank, + interpolate: interpolate + }; +})()); + +var Template = Class.create({ + initialize: function(template, pattern) { + this.template = template.toString(); + this.pattern = pattern || Template.Pattern; + }, + + evaluate: function(object) { + if (object && Object.isFunction(object.toTemplateReplacements)) + object = object.toTemplateReplacements(); + + return this.template.gsub(this.pattern, function(match) { + if (object == null) return (match[1] + ''); + + var before = match[1] || ''; + if (before == '\\') return match[2]; + + var ctx = object, expr = match[3], + pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; + + match = pattern.exec(expr); + if (match == null) return before; + + while (match != null) { + var comp = match[1].startsWith('[') ? match[2].replace(/\\\\]/g, ']') : match[1]; + ctx = ctx[comp]; + if (null == ctx || '' == match[3]) break; + expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); + match = pattern.exec(expr); + } + + return before + String.interpret(ctx); + }); + } +}); +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; + +var $break = { }; + +var Enumerable = (function() { + function each(iterator, context) { + try { + this._each(iterator, context); + } catch (e) { + if (e != $break) throw e; + } + return this; + } + + function eachSlice(number, iterator, context) { + var index = -number, slices = [], array = this.toArray(); + if (number < 1) return array; + while ((index += number) < array.length) + slices.push(array.slice(index, index+number)); + return slices.collect(iterator, context); + } + + function all(iterator, context) { + iterator = iterator || Prototype.K; + var result = true; + this.each(function(value, index) { + result = result && !!iterator.call(context, value, index, this); + if (!result) throw $break; + }, this); + return result; + } + + function any(iterator, context) { + iterator = iterator || Prototype.K; + var result = false; + this.each(function(value, index) { + if (result = !!iterator.call(context, value, index, this)) + throw $break; + }, this); + return result; + } + + function collect(iterator, context) { + iterator = iterator || Prototype.K; + var results = []; + this.each(function(value, index) { + results.push(iterator.call(context, value, index, this)); + }, this); + return results; + } + + function detect(iterator, context) { + var result; + this.each(function(value, index) { + if (iterator.call(context, value, index, this)) { + result = value; + throw $break; + } + }, this); + return result; + } + + function findAll(iterator, context) { + var results = []; + this.each(function(value, index) { + if (iterator.call(context, value, index, this)) + results.push(value); + }, this); + return results; + } + + function grep(filter, iterator, context) { + iterator = iterator || Prototype.K; + var results = []; + + if (Object.isString(filter)) + filter = new RegExp(RegExp.escape(filter)); + + this.each(function(value, index) { + if (filter.match(value)) + results.push(iterator.call(context, value, index, this)); + }, this); + return results; + } + + function include(object) { + if (Object.isFunction(this.indexOf) && this.indexOf(object) != -1) + return true; + + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw $break; + } + }); + return found; + } + + function inGroupsOf(number, fillWith) { + fillWith = Object.isUndefined(fillWith) ? null : fillWith; + return this.eachSlice(number, function(slice) { + while(slice.length < number) slice.push(fillWith); + return slice; + }); + } + + function inject(memo, iterator, context) { + this.each(function(value, index) { + memo = iterator.call(context, memo, value, index, this); + }, this); + return memo; + } + + function invoke(method) { + var args = $A(arguments).slice(1); + return this.map(function(value) { + return value[method].apply(value, args); + }); + } + + function max(iterator, context) { + iterator = iterator || Prototype.K; + var result; + this.each(function(value, index) { + value = iterator.call(context, value, index, this); + if (result == null || value >= result) + result = value; + }, this); + return result; + } + + function min(iterator, context) { + iterator = iterator || Prototype.K; + var result; + this.each(function(value, index) { + value = iterator.call(context, value, index, this); + if (result == null || value < result) + result = value; + }, this); + return result; + } + + function partition(iterator, context) { + iterator = iterator || Prototype.K; + var trues = [], falses = []; + this.each(function(value, index) { + (iterator.call(context, value, index, this) ? + trues : falses).push(value); + }, this); + return [trues, falses]; + } + + function pluck(property) { + var results = []; + this.each(function(value) { + results.push(value[property]); + }); + return results; + } + + function reject(iterator, context) { + var results = []; + this.each(function(value, index) { + if (!iterator.call(context, value, index, this)) + results.push(value); + }, this); + return results; + } + + function sortBy(iterator, context) { + return this.map(function(value, index) { + return { + value: value, + criteria: iterator.call(context, value, index, this) + }; + }, this).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + } + + function toArray() { + return this.map(); + } + + function zip() { + var iterator = Prototype.K, args = $A(arguments); + if (Object.isFunction(args.last())) + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + return iterator(collections.pluck(index)); + }); + } + + function size() { + return this.toArray().length; + } + + function inspect() { + return '#'; + } + + + + + + + + + + return { + each: each, + eachSlice: eachSlice, + all: all, + every: all, + any: any, + some: any, + collect: collect, + map: collect, + detect: detect, + findAll: findAll, + select: findAll, + filter: findAll, + grep: grep, + include: include, + member: include, + inGroupsOf: inGroupsOf, + inject: inject, + invoke: invoke, + max: max, + min: min, + partition: partition, + pluck: pluck, + reject: reject, + sortBy: sortBy, + toArray: toArray, + entries: toArray, + zip: zip, + size: size, + inspect: inspect, + find: detect + }; +})(); + +function $A(iterable) { + if (!iterable) return []; + if ('toArray' in Object(iterable)) return iterable.toArray(); + var length = iterable.length || 0, results = new Array(length); + while (length--) results[length] = iterable[length]; + return results; +} + + +function $w(string) { + if (!Object.isString(string)) return []; + string = string.strip(); + return string ? string.split(/\s+/) : []; +} + +Array.from = $A; + + +(function() { + var arrayProto = Array.prototype, + slice = arrayProto.slice, + _each = arrayProto.forEach; // use native browser JS 1.6 implementation if available + + function each(iterator, context) { + for (var i = 0, length = this.length >>> 0; i < length; i++) { + if (i in this) iterator.call(context, this[i], i, this); + } + } + if (!_each) _each = each; + + function clear() { + this.length = 0; + return this; + } + + function first() { + return this[0]; + } + + function last() { + return this[this.length - 1]; + } + + function compact() { + return this.select(function(value) { + return value != null; + }); + } + + function flatten() { + return this.inject([], function(array, value) { + if (Object.isArray(value)) + return array.concat(value.flatten()); + array.push(value); + return array; + }); + } + + function without() { + var values = slice.call(arguments, 0); + return this.select(function(value) { + return !values.include(value); + }); + } + + function reverse(inline) { + return (inline === false ? this.toArray() : this)._reverse(); + } + + function uniq(sorted) { + return this.inject([], function(array, value, index) { + if (0 == index || (sorted ? array.last() != value : !array.include(value))) + array.push(value); + return array; + }); + } + + function intersect(array) { + return this.uniq().findAll(function(item) { + return array.indexOf(item) !== -1; + }); + } + + + function clone() { + return slice.call(this, 0); + } + + function size() { + return this.length; + } + + function inspect() { + return '[' + this.map(Object.inspect).join(', ') + ']'; + } + + function indexOf(item, i) { + if (this == null) throw new TypeError(); + + var array = Object(this), length = array.length >>> 0; + if (length === 0) return -1; + + i = Number(i); + if (isNaN(i)) { + i = 0; + } else if (i !== 0 && isFinite(i)) { + i = (i > 0 ? 1 : -1) * Math.floor(Math.abs(i)); + } + + if (i > length) return -1; + + var k = i >= 0 ? i : Math.max(length - Math.abs(i), 0); + for (; k < length; k++) + if (k in array && array[k] === item) return k; + return -1; + } + + + function lastIndexOf(item, i) { + if (this == null) throw new TypeError(); + + var array = Object(this), length = array.length >>> 0; + if (length === 0) return -1; + + if (!Object.isUndefined(i)) { + i = Number(i); + if (isNaN(i)) { + i = 0; + } else if (i !== 0 && isFinite(i)) { + i = (i > 0 ? 1 : -1) * Math.floor(Math.abs(i)); + } + } else { + i = length; + } + + var k = i >= 0 ? Math.min(i, length - 1) : + length - Math.abs(i); + + for (; k >= 0; k--) + if (k in array && array[k] === item) return k; + return -1; + } + + function concat(_) { + var array = [], items = slice.call(arguments, 0), item, n = 0; + items.unshift(this); + for (var i = 0, length = items.length; i < length; i++) { + item = items[i]; + if (Object.isArray(item) && !('callee' in item)) { + for (var j = 0, arrayLength = item.length; j < arrayLength; j++) { + if (j in item) array[n] = item[j]; + n++; + } + } else { + array[n++] = item; + } + } + array.length = n; + return array; + } + + + function wrapNative(method) { + return function() { + if (arguments.length === 0) { + return method.call(this, Prototype.K); + } else if (arguments[0] === undefined) { + var args = slice.call(arguments, 1); + args.unshift(Prototype.K); + return method.apply(this, args); + } else { + return method.apply(this, arguments); + } + }; + } + + + function map(iterator) { + if (this == null) throw new TypeError(); + iterator = iterator || Prototype.K; + + var object = Object(this); + var results = [], context = arguments[1], n = 0; + + for (var i = 0, length = object.length >>> 0; i < length; i++) { + if (i in object) { + results[n] = iterator.call(context, object[i], i, object); + } + n++; + } + results.length = n; + return results; + } + + if (arrayProto.map) { + map = wrapNative(Array.prototype.map); + } + + function filter(iterator) { + if (this == null || !Object.isFunction(iterator)) + throw new TypeError(); + + var object = Object(this); + var results = [], context = arguments[1], value; + + for (var i = 0, length = object.length >>> 0; i < length; i++) { + if (i in object) { + value = object[i]; + if (iterator.call(context, value, i, object)) { + results.push(value); + } + } + } + return results; + } + + if (arrayProto.filter) { + filter = Array.prototype.filter; + } + + function some(iterator) { + if (this == null) throw new TypeError(); + iterator = iterator || Prototype.K; + var context = arguments[1]; + + var object = Object(this); + for (var i = 0, length = object.length >>> 0; i < length; i++) { + if (i in object && iterator.call(context, object[i], i, object)) { + return true; + } + } + + return false; + } + + if (arrayProto.some) { + var some = wrapNative(Array.prototype.some); + } + + + function every(iterator) { + if (this == null) throw new TypeError(); + iterator = iterator || Prototype.K; + var context = arguments[1]; + + var object = Object(this); + for (var i = 0, length = object.length >>> 0; i < length; i++) { + if (i in object && !iterator.call(context, object[i], i, object)) { + return false; + } + } + + return true; + } + + if (arrayProto.every) { + var every = wrapNative(Array.prototype.every); + } + + var _reduce = arrayProto.reduce; + function inject(memo, iterator) { + iterator = iterator || Prototype.K; + var context = arguments[2]; + return _reduce.call(this, iterator.bind(context), memo); + } + + if (!arrayProto.reduce) { + var inject = Enumerable.inject; + } + + Object.extend(arrayProto, Enumerable); + + if (!arrayProto._reverse) + arrayProto._reverse = arrayProto.reverse; + + Object.extend(arrayProto, { + _each: _each, + + map: map, + collect: map, + select: filter, + filter: filter, + findAll: filter, + some: some, + any: some, + every: every, + all: every, + inject: inject, + + clear: clear, + first: first, + last: last, + compact: compact, + flatten: flatten, + without: without, + reverse: reverse, + uniq: uniq, + intersect: intersect, + clone: clone, + toArray: clone, + size: size, + inspect: inspect + }); + + var CONCAT_ARGUMENTS_BUGGY = (function() { + return [].concat(arguments)[0][0] !== 1; + })(1,2); + + if (CONCAT_ARGUMENTS_BUGGY) arrayProto.concat = concat; + + if (!arrayProto.indexOf) arrayProto.indexOf = indexOf; + if (!arrayProto.lastIndexOf) arrayProto.lastIndexOf = lastIndexOf; +})(); +function $H(object) { + return new Hash(object); +}; + +var Hash = Class.create(Enumerable, (function() { + function initialize(object) { + this._object = Object.isHash(object) ? object.toObject() : Object.clone(object); + } + + + function _each(iterator, context) { + var i = 0; + for (var key in this._object) { + var value = this._object[key], pair = [key, value]; + pair.key = key; + pair.value = value; + iterator.call(context, pair, i); + i++; + } + } + + function set(key, value) { + return this._object[key] = value; + } + + function get(key) { + if (this._object[key] !== Object.prototype[key]) + return this._object[key]; + } + + function unset(key) { + var value = this._object[key]; + delete this._object[key]; + return value; + } + + function toObject() { + return Object.clone(this._object); + } + + + + function keys() { + return this.pluck('key'); + } + + function values() { + return this.pluck('value'); + } + + function index(value) { + var match = this.detect(function(pair) { + return pair.value === value; + }); + return match && match.key; + } + + function merge(object) { + return this.clone().update(object); + } + + function update(object) { + return new Hash(object).inject(this, function(result, pair) { + result.set(pair.key, pair.value); + return result; + }); + } + + function toQueryPair(key, value) { + if (Object.isUndefined(value)) return key; + + value = String.interpret(value); + + value = value.gsub(/(\r)?\n/, '\r\n'); + value = encodeURIComponent(value); + value = value.gsub(/%20/, '+'); + return key + '=' + value; + } + + function toQueryString() { + return this.inject([], function(results, pair) { + var key = encodeURIComponent(pair.key), values = pair.value; + + if (values && typeof values == 'object') { + if (Object.isArray(values)) { + var queryValues = []; + for (var i = 0, len = values.length, value; i < len; i++) { + value = values[i]; + queryValues.push(toQueryPair(key, value)); + } + return results.concat(queryValues); + } + } else results.push(toQueryPair(key, values)); + return results; + }).join('&'); + } + + function inspect() { + return '#'; + } + + function clone() { + return new Hash(this); + } + + return { + initialize: initialize, + _each: _each, + set: set, + get: get, + unset: unset, + toObject: toObject, + toTemplateReplacements: toObject, + keys: keys, + values: values, + index: index, + merge: merge, + update: update, + toQueryString: toQueryString, + inspect: inspect, + toJSON: toObject, + clone: clone + }; +})()); + +Hash.from = $H; +Object.extend(Number.prototype, (function() { + function toColorPart() { + return this.toPaddedString(2, 16); + } + + function succ() { + return this + 1; + } + + function times(iterator, context) { + $R(0, this, true).each(iterator, context); + return this; + } + + function toPaddedString(length, radix) { + var string = this.toString(radix || 10); + return '0'.times(length - string.length) + string; + } + + function abs() { + return Math.abs(this); + } + + function round() { + return Math.round(this); + } + + function ceil() { + return Math.ceil(this); + } + + function floor() { + return Math.floor(this); + } + + return { + toColorPart: toColorPart, + succ: succ, + times: times, + toPaddedString: toPaddedString, + abs: abs, + round: round, + ceil: ceil, + floor: floor + }; +})()); + +function $R(start, end, exclusive) { + return new ObjectRange(start, end, exclusive); +} + +var ObjectRange = Class.create(Enumerable, (function() { + function initialize(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + } + + function _each(iterator, context) { + var value = this.start, i; + for (i = 0; this.include(value); i++) { + iterator.call(context, value, i); + value = value.succ(); + } + } + + function include(value) { + if (value < this.start) + return false; + if (this.exclusive) + return value < this.end; + return value <= this.end; + } + + return { + initialize: initialize, + _each: _each, + include: include + }; +})()); + + + +var Abstract = { }; + + +var Try = { + these: function() { + var returnValue; + + for (var i = 0, length = arguments.length; i < length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) { } + } + + return returnValue; + } +}; + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new XMLHttpRequest()}, + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')} + ) || false; + }, + + activeRequestCount: 0 +}; + +Ajax.Responders = { + responders: [], + + _each: function(iterator, context) { + this.responders._each(iterator, context); + }, + + register: function(responder) { + if (!this.include(responder)) + this.responders.push(responder); + }, + + unregister: function(responder) { + this.responders = this.responders.without(responder); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (Object.isFunction(responder[callback])) { + try { + responder[callback].apply(responder, [request, transport, json]); + } catch (e) { } + } + }); + } +}; + +Object.extend(Ajax.Responders, Enumerable); + +Ajax.Responders.register({ + onCreate: function() { Ajax.activeRequestCount++ }, + onComplete: function() { Ajax.activeRequestCount-- } +}); +Ajax.Base = Class.create({ + initialize: function(options) { + this.options = { + method: 'post', + asynchronous: true, + contentType: 'application/x-www-form-urlencoded', + encoding: 'UTF-8', + parameters: '', + evalJSON: true, + evalJS: true + }; + Object.extend(this.options, options || { }); + + this.options.method = this.options.method.toLowerCase(); + + if (Object.isHash(this.options.parameters)) + this.options.parameters = this.options.parameters.toObject(); + } +}); +Ajax.Request = Class.create(Ajax.Base, { + _complete: false, + + initialize: function($super, url, options) { + $super(options); + this.transport = Ajax.getTransport(); + this.request(url); + }, + + request: function(url) { + this.url = url; + this.method = this.options.method; + var params = Object.isString(this.options.parameters) ? + this.options.parameters : + Object.toQueryString(this.options.parameters); + + if (!['get', 'post'].include(this.method)) { + params += (params ? '&' : '') + "_method=" + this.method; + this.method = 'post'; + } + + if (params && this.method === 'get') { + this.url += (this.url.include('?') ? '&' : '?') + params; + } + + this.parameters = params.toQueryParams(); + + try { + var response = new Ajax.Response(this); + if (this.options.onCreate) this.options.onCreate(response); + Ajax.Responders.dispatch('onCreate', this, response); + + this.transport.open(this.method.toUpperCase(), this.url, + this.options.asynchronous); + + if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1); + + this.transport.onreadystatechange = this.onStateChange.bind(this); + this.setRequestHeaders(); + + this.body = this.method == 'post' ? (this.options.postBody || params) : null; + this.transport.send(this.body); + + /* Force Firefox to handle ready state 4 for synchronous requests */ + if (!this.options.asynchronous && this.transport.overrideMimeType) + this.onStateChange(); + + } + catch (e) { + this.dispatchException(e); + } + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState > 1 && !((readyState == 4) && this._complete)) + this.respondToReadyState(this.transport.readyState); + }, + + setRequestHeaders: function() { + var headers = { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Prototype-Version': Prototype.Version, + 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' + }; + + if (this.method == 'post') { + headers['Content-type'] = this.options.contentType + + (this.options.encoding ? '; charset=' + this.options.encoding : ''); + + /* Force "Connection: close" for older Mozilla browsers to work + * around a bug where XMLHttpRequest sends an incorrect + * Content-length header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType && + (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) + headers['Connection'] = 'close'; + } + + if (typeof this.options.requestHeaders == 'object') { + var extras = this.options.requestHeaders; + + if (Object.isFunction(extras.push)) + for (var i = 0, length = extras.length; i < length; i += 2) + headers[extras[i]] = extras[i+1]; + else + $H(extras).each(function(pair) { headers[pair.key] = pair.value }); + } + + for (var name in headers) + if (headers[name] != null) + this.transport.setRequestHeader(name, headers[name]); + }, + + success: function() { + var status = this.getStatus(); + return !status || (status >= 200 && status < 300) || status == 304; + }, + + getStatus: function() { + try { + if (this.transport.status === 1223) return 204; + return this.transport.status || 0; + } catch (e) { return 0 } + }, + + respondToReadyState: function(readyState) { + var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this); + + if (state == 'Complete') { + try { + this._complete = true; + (this.options['on' + response.status] + || this.options['on' + (this.success() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(response, response.headerJSON); + } catch (e) { + this.dispatchException(e); + } + + var contentType = response.getHeader('Content-type'); + if (this.options.evalJS == 'force' + || (this.options.evalJS && this.isSameOrigin() && contentType + && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) + this.evalResponse(); + } + + try { + (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON); + Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON); + } catch (e) { + this.dispatchException(e); + } + + if (state == 'Complete') { + this.transport.onreadystatechange = Prototype.emptyFunction; + } + }, + + isSameOrigin: function() { + var m = this.url.match(/^\s*https?:\/\/[^\/]*/); + return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({ + protocol: location.protocol, + domain: document.domain, + port: location.port ? ':' + location.port : '' + })); + }, + + getHeader: function(name) { + try { + return this.transport.getResponseHeader(name) || null; + } catch (e) { return null; } + }, + + evalResponse: function() { + try { + return eval((this.transport.responseText || '').unfilterJSON()); + } catch (e) { + this.dispatchException(e); + } + }, + + dispatchException: function(exception) { + (this.options.onException || Prototype.emptyFunction)(this, exception); + Ajax.Responders.dispatch('onException', this, exception); + } +}); + +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + + + + + + + + +Ajax.Response = Class.create({ + initialize: function(request){ + this.request = request; + var transport = this.transport = request.transport, + readyState = this.readyState = transport.readyState; + + if ((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { + this.status = this.getStatus(); + this.statusText = this.getStatusText(); + this.responseText = String.interpret(transport.responseText); + this.headerJSON = this._getHeaderJSON(); + } + + if (readyState == 4) { + var xml = transport.responseXML; + this.responseXML = Object.isUndefined(xml) ? null : xml; + this.responseJSON = this._getResponseJSON(); + } + }, + + status: 0, + + statusText: '', + + getStatus: Ajax.Request.prototype.getStatus, + + getStatusText: function() { + try { + return this.transport.statusText || ''; + } catch (e) { return '' } + }, + + getHeader: Ajax.Request.prototype.getHeader, + + getAllHeaders: function() { + try { + return this.getAllResponseHeaders(); + } catch (e) { return null } + }, + + getResponseHeader: function(name) { + return this.transport.getResponseHeader(name); + }, + + getAllResponseHeaders: function() { + return this.transport.getAllResponseHeaders(); + }, + + _getHeaderJSON: function() { + var json = this.getHeader('X-JSON'); + if (!json) return null; + + try { + json = decodeURIComponent(escape(json)); + } catch(e) { + } + + try { + return json.evalJSON(this.request.options.sanitizeJSON || + !this.request.isSameOrigin()); + } catch (e) { + this.request.dispatchException(e); + } + }, + + _getResponseJSON: function() { + var options = this.request.options; + if (!options.evalJSON || (options.evalJSON != 'force' && + !(this.getHeader('Content-type') || '').include('application/json')) || + this.responseText.blank()) + return null; + try { + return this.responseText.evalJSON(options.sanitizeJSON || + !this.request.isSameOrigin()); + } catch (e) { + this.request.dispatchException(e); + } + } +}); + +Ajax.Updater = Class.create(Ajax.Request, { + initialize: function($super, container, url, options) { + this.container = { + success: (container.success || container), + failure: (container.failure || (container.success ? null : container)) + }; + + options = Object.clone(options); + var onComplete = options.onComplete; + options.onComplete = (function(response, json) { + this.updateContent(response.responseText); + if (Object.isFunction(onComplete)) onComplete(response, json); + }).bind(this); + + $super(url, options); + }, + + updateContent: function(responseText) { + var receiver = this.container[this.success() ? 'success' : 'failure'], + options = this.options; + + if (!options.evalScripts) responseText = responseText.stripScripts(); + + if (receiver = $(receiver)) { + if (options.insertion) { + if (Object.isString(options.insertion)) { + var insertion = { }; insertion[options.insertion] = responseText; + receiver.insert(insertion); + } + else options.insertion(receiver, responseText); + } + else receiver.update(responseText); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { + initialize: function($super, container, url, options) { + $super(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = (this.options.decay || 1); + + this.updater = { }; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.options.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(response) { + if (this.options.decay) { + this.decay = (response.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = response.responseText; + } + this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency); + }, + + onTimerEvent: function() { + this.updater = new Ajax.Updater(this.container, this.url, this.options); + } +}); + +(function(GLOBAL) { + + var UNDEFINED; + var SLICE = Array.prototype.slice; + + var DIV = document.createElement('div'); + + + function $(element) { + if (arguments.length > 1) { + for (var i = 0, elements = [], length = arguments.length; i < length; i++) + elements.push($(arguments[i])); + return elements; + } + + if (Object.isString(element)) + element = document.getElementById(element); + return Element.extend(element); + } + + GLOBAL.$ = $; + + + if (!GLOBAL.Node) GLOBAL.Node = {}; + + if (!GLOBAL.Node.ELEMENT_NODE) { + Object.extend(GLOBAL.Node, { + ELEMENT_NODE: 1, + ATTRIBUTE_NODE: 2, + TEXT_NODE: 3, + CDATA_SECTION_NODE: 4, + ENTITY_REFERENCE_NODE: 5, + ENTITY_NODE: 6, + PROCESSING_INSTRUCTION_NODE: 7, + COMMENT_NODE: 8, + DOCUMENT_NODE: 9, + DOCUMENT_TYPE_NODE: 10, + DOCUMENT_FRAGMENT_NODE: 11, + NOTATION_NODE: 12 + }); + } + + var ELEMENT_CACHE = {}; + + function shouldUseCreationCache(tagName, attributes) { + if (tagName === 'select') return false; + if ('type' in attributes) return false; + return true; + } + + var HAS_EXTENDED_CREATE_ELEMENT_SYNTAX = (function(){ + try { + var el = document.createElement(''); + return el.tagName.toLowerCase() === 'input' && el.name === 'x'; + } + catch(err) { + return false; + } + })(); + + + var oldElement = GLOBAL.Element; + function Element(tagName, attributes) { + attributes = attributes || {}; + tagName = tagName.toLowerCase(); + + if (HAS_EXTENDED_CREATE_ELEMENT_SYNTAX && attributes.name) { + tagName = '<' + tagName + ' name="' + attributes.name + '">'; + delete attributes.name; + return Element.writeAttribute(document.createElement(tagName), attributes); + } + + if (!ELEMENT_CACHE[tagName]) + ELEMENT_CACHE[tagName] = Element.extend(document.createElement(tagName)); + + var node = shouldUseCreationCache(tagName, attributes) ? + ELEMENT_CACHE[tagName].cloneNode(false) : document.createElement(tagName); + + return Element.writeAttribute(node, attributes); + } + + GLOBAL.Element = Element; + + Object.extend(GLOBAL.Element, oldElement || {}); + if (oldElement) GLOBAL.Element.prototype = oldElement.prototype; + + Element.Methods = { ByTag: {}, Simulated: {} }; + + var methods = {}; + + var INSPECT_ATTRIBUTES = { id: 'id', className: 'class' }; + function inspect(element) { + element = $(element); + var result = '<' + element.tagName.toLowerCase(); + + var attribute, value; + for (var property in INSPECT_ATTRIBUTES) { + attribute = INSPECT_ATTRIBUTES[property]; + value = (element[property] || '').toString(); + if (value) result += ' ' + attribute + '=' + value.inspect(true); + } + + return result + '>'; + } + + methods.inspect = inspect; + + + function visible(element) { + return $(element).style.display !== 'none'; + } + + function toggle(element, bool) { + element = $(element); + if (Object.isUndefined(bool)) + bool = !Element.visible(element); + Element[bool ? 'show' : 'hide'](element); + + return element; + } + + function hide(element) { + element = $(element); + element.style.display = 'none'; + return element; + } + + function show(element) { + element = $(element); + element.style.display = ''; + return element; + } + + + Object.extend(methods, { + visible: visible, + toggle: toggle, + hide: hide, + show: show + }); + + + function remove(element) { + element = $(element); + element.parentNode.removeChild(element); + return element; + } + + var SELECT_ELEMENT_INNERHTML_BUGGY = (function(){ + var el = document.createElement("select"), + isBuggy = true; + el.innerHTML = ""; + if (el.options && el.options[0]) { + isBuggy = el.options[0].nodeName.toUpperCase() !== "OPTION"; + } + el = null; + return isBuggy; + })(); + + var TABLE_ELEMENT_INNERHTML_BUGGY = (function(){ + try { + var el = document.createElement("table"); + if (el && el.tBodies) { + el.innerHTML = "test"; + var isBuggy = typeof el.tBodies[0] == "undefined"; + el = null; + return isBuggy; + } + } catch (e) { + return true; + } + })(); + + var LINK_ELEMENT_INNERHTML_BUGGY = (function() { + try { + var el = document.createElement('div'); + el.innerHTML = ""; + var isBuggy = (el.childNodes.length === 0); + el = null; + return isBuggy; + } catch(e) { + return true; + } + })(); + + var ANY_INNERHTML_BUGGY = SELECT_ELEMENT_INNERHTML_BUGGY || + TABLE_ELEMENT_INNERHTML_BUGGY || LINK_ELEMENT_INNERHTML_BUGGY; + + var SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING = (function () { + var s = document.createElement("script"), + isBuggy = false; + try { + s.appendChild(document.createTextNode("")); + isBuggy = !s.firstChild || + s.firstChild && s.firstChild.nodeType !== 3; + } catch (e) { + isBuggy = true; + } + s = null; + return isBuggy; + })(); + + function update(element, content) { + element = $(element); + + var descendants = element.getElementsByTagName('*'), + i = descendants.length; + while (i--) purgeElement(descendants[i]); + + if (content && content.toElement) + content = content.toElement(); + + if (Object.isElement(content)) + return element.update().insert(content); + + + content = Object.toHTML(content); + var tagName = element.tagName.toUpperCase(); + + if (tagName === 'SCRIPT' && SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING) { + element.text = content; + return element; + } + + if (ANY_INNERHTML_BUGGY) { + if (tagName in INSERTION_TRANSLATIONS.tags) { + while (element.firstChild) + element.removeChild(element.firstChild); + + var nodes = getContentFromAnonymousElement(tagName, content.stripScripts()); + for (var i = 0, node; node = nodes[i]; i++) + element.appendChild(node); + + } else if (LINK_ELEMENT_INNERHTML_BUGGY && Object.isString(content) && content.indexOf(' -1) { + while (element.firstChild) + element.removeChild(element.firstChild); + + var nodes = getContentFromAnonymousElement(tagName, + content.stripScripts(), true); + + for (var i = 0, node; node = nodes[i]; i++) + element.appendChild(node); + } else { + element.innerHTML = content.stripScripts(); + } + } else { + element.innerHTML = content.stripScripts(); + } + + content.evalScripts.bind(content).defer(); + return element; + } + + function replace(element, content) { + element = $(element); + + if (content && content.toElement) { + content = content.toElement(); + } else if (!Object.isElement(content)) { + content = Object.toHTML(content); + var range = element.ownerDocument.createRange(); + range.selectNode(element); + content.evalScripts.bind(content).defer(); + content = range.createContextualFragment(content.stripScripts()); + } + + element.parentNode.replaceChild(content, element); + return element; + } + + var INSERTION_TRANSLATIONS = { + before: function(element, node) { + element.parentNode.insertBefore(node, element); + }, + top: function(element, node) { + element.insertBefore(node, element.firstChild); + }, + bottom: function(element, node) { + element.appendChild(node); + }, + after: function(element, node) { + element.parentNode.insertBefore(node, element.nextSibling); + }, + + tags: { + TABLE: ['', '
', 1], + TBODY: ['', '
', 2], + TR: ['', '
', 3], + TD: ['
', '
', 4], + SELECT: ['', 1] + } + }; + + var tags = INSERTION_TRANSLATIONS.tags; + + Object.extend(tags, { + THEAD: tags.TBODY, + TFOOT: tags.TBODY, + TH: tags.TD + }); + + function replace_IE(element, content) { + element = $(element); + if (content && content.toElement) + content = content.toElement(); + if (Object.isElement(content)) { + element.parentNode.replaceChild(content, element); + return element; + } + + content = Object.toHTML(content); + var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); + + if (tagName in INSERTION_TRANSLATIONS.tags) { + var nextSibling = Element.next(element); + var fragments = getContentFromAnonymousElement( + tagName, content.stripScripts()); + + parent.removeChild(element); + + var iterator; + if (nextSibling) + iterator = function(node) { parent.insertBefore(node, nextSibling) }; + else + iterator = function(node) { parent.appendChild(node); } + + fragments.each(iterator); + } else { + element.outerHTML = content.stripScripts(); + } + + content.evalScripts.bind(content).defer(); + return element; + } + + if ('outerHTML' in document.documentElement) + replace = replace_IE; + + function isContent(content) { + if (Object.isUndefined(content) || content === null) return false; + + if (Object.isString(content) || Object.isNumber(content)) return true; + if (Object.isElement(content)) return true; + if (content.toElement || content.toHTML) return true; + + return false; + } + + function insertContentAt(element, content, position) { + position = position.toLowerCase(); + var method = INSERTION_TRANSLATIONS[position]; + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + method(element, content); + return element; + } + + content = Object.toHTML(content); + var tagName = ((position === 'before' || position === 'after') ? + element.parentNode : element).tagName.toUpperCase(); + + var childNodes = getContentFromAnonymousElement(tagName, content.stripScripts()); + + if (position === 'top' || position === 'after') childNodes.reverse(); + + for (var i = 0, node; node = childNodes[i]; i++) + method(element, node); + + content.evalScripts.bind(content).defer(); + } + + function insert(element, insertions) { + element = $(element); + + if (isContent(insertions)) + insertions = { bottom: insertions }; + + for (var position in insertions) + insertContentAt(element, insertions[position], position); + + return element; + } + + function wrap(element, wrapper, attributes) { + element = $(element); + + if (Object.isElement(wrapper)) { + $(wrapper).writeAttribute(attributes || {}); + } else if (Object.isString(wrapper)) { + wrapper = new Element(wrapper, attributes); + } else { + wrapper = new Element('div', wrapper); + } + + if (element.parentNode) + element.parentNode.replaceChild(wrapper, element); + + wrapper.appendChild(element); + + return wrapper; + } + + function cleanWhitespace(element) { + element = $(element); + var node = element.firstChild; + + while (node) { + var nextNode = node.nextSibling; + if (node.nodeType === Node.TEXT_NODE && !/\S/.test(node.nodeValue)) + element.removeChild(node); + node = nextNode; + } + return element; + } + + function empty(element) { + return $(element).innerHTML.blank(); + } + + function getContentFromAnonymousElement(tagName, html, force) { + var t = INSERTION_TRANSLATIONS.tags[tagName], div = DIV; + + var workaround = !!t; + if (!workaround && force) { + workaround = true; + t = ['', '', 0]; + } + + if (workaround) { + div.innerHTML = ' ' + t[0] + html + t[1]; + div.removeChild(div.firstChild); + for (var i = t[2]; i--; ) + div = div.firstChild; + } else { + div.innerHTML = html; + } + + return $A(div.childNodes); + } + + function clone(element, deep) { + if (!(element = $(element))) return; + var clone = element.cloneNode(deep); + if (!HAS_UNIQUE_ID_PROPERTY) { + clone._prototypeUID = UNDEFINED; + if (deep) { + var descendants = Element.select(clone, '*'), + i = descendants.length; + while (i--) + descendants[i]._prototypeUID = UNDEFINED; + } + } + return Element.extend(clone); + } + + function purgeElement(element) { + var uid = getUniqueElementID(element); + if (uid) { + Element.stopObserving(element); + if (!HAS_UNIQUE_ID_PROPERTY) + element._prototypeUID = UNDEFINED; + delete Element.Storage[uid]; + } + } + + function purgeCollection(elements) { + var i = elements.length; + while (i--) + purgeElement(elements[i]); + } + + function purgeCollection_IE(elements) { + var i = elements.length, element, uid; + while (i--) { + element = elements[i]; + uid = getUniqueElementID(element); + delete Element.Storage[uid]; + delete Event.cache[uid]; + } + } + + if (HAS_UNIQUE_ID_PROPERTY) { + purgeCollection = purgeCollection_IE; + } + + + function purge(element) { + if (!(element = $(element))) return; + purgeElement(element); + + var descendants = element.getElementsByTagName('*'), + i = descendants.length; + + while (i--) purgeElement(descendants[i]); + + return null; + } + + Object.extend(methods, { + remove: remove, + update: update, + replace: replace, + insert: insert, + wrap: wrap, + cleanWhitespace: cleanWhitespace, + empty: empty, + clone: clone, + purge: purge + }); + + + + function recursivelyCollect(element, property, maximumLength) { + element = $(element); + maximumLength = maximumLength || -1; + var elements = []; + + while (element = element[property]) { + if (element.nodeType === Node.ELEMENT_NODE) + elements.push(Element.extend(element)); + + if (elements.length === maximumLength) break; + } + + return elements; + } + + + function ancestors(element) { + return recursivelyCollect(element, 'parentNode'); + } + + function descendants(element) { + return Element.select(element, '*'); + } + + function firstDescendant(element) { + element = $(element).firstChild; + while (element && element.nodeType !== Node.ELEMENT_NODE) + element = element.nextSibling; + + return $(element); + } + + function immediateDescendants(element) { + var results = [], child = $(element).firstChild; + + while (child) { + if (child.nodeType === Node.ELEMENT_NODE) + results.push(Element.extend(child)); + + child = child.nextSibling; + } + + return results; + } + + function previousSiblings(element) { + return recursivelyCollect(element, 'previousSibling'); + } + + function nextSiblings(element) { + return recursivelyCollect(element, 'nextSibling'); + } + + function siblings(element) { + element = $(element); + var previous = previousSiblings(element), + next = nextSiblings(element); + return previous.reverse().concat(next); + } + + function match(element, selector) { + element = $(element); + + if (Object.isString(selector)) + return Prototype.Selector.match(element, selector); + + return selector.match(element); + } + + + function _recursivelyFind(element, property, expression, index) { + element = $(element), expression = expression || 0, index = index || 0; + if (Object.isNumber(expression)) { + index = expression, expression = null; + } + + while (element = element[property]) { + if (element.nodeType !== 1) continue; + if (expression && !Prototype.Selector.match(element, expression)) + continue; + if (--index >= 0) continue; + + return Element.extend(element); + } + } + + + function up(element, expression, index) { + element = $(element); + + if (arguments.length === 1) return $(element.parentNode); + return _recursivelyFind(element, 'parentNode', expression, index); + } + + function down(element, expression, index) { + if (arguments.length === 1) return firstDescendant(element); + element = $(element), expression = expression || 0, index = index || 0; + + if (Object.isNumber(expression)) + index = expression, expression = '*'; + + var node = Prototype.Selector.select(expression, element)[index]; + return Element.extend(node); + } + + function previous(element, expression, index) { + return _recursivelyFind(element, 'previousSibling', expression, index); + } + + function next(element, expression, index) { + return _recursivelyFind(element, 'nextSibling', expression, index); + } + + function select(element) { + element = $(element); + var expressions = SLICE.call(arguments, 1).join(', '); + return Prototype.Selector.select(expressions, element); + } + + function adjacent(element) { + element = $(element); + var expressions = SLICE.call(arguments, 1).join(', '); + var siblings = Element.siblings(element), results = []; + for (var i = 0, sibling; sibling = siblings[i]; i++) { + if (Prototype.Selector.match(sibling, expressions)) + results.push(sibling); + } + + return results; + } + + function descendantOf_DOM(element, ancestor) { + element = $(element), ancestor = $(ancestor); + while (element = element.parentNode) + if (element === ancestor) return true; + return false; + } + + function descendantOf_contains(element, ancestor) { + element = $(element), ancestor = $(ancestor); + if (!ancestor.contains) return descendantOf_DOM(element, ancestor); + return ancestor.contains(element) && ancestor !== element; + } + + function descendantOf_compareDocumentPosition(element, ancestor) { + element = $(element), ancestor = $(ancestor); + return (element.compareDocumentPosition(ancestor) & 8) === 8; + } + + var descendantOf; + if (DIV.compareDocumentPosition) { + descendantOf = descendantOf_compareDocumentPosition; + } else if (DIV.contains) { + descendantOf = descendantOf_contains; + } else { + descendantOf = descendantOf_DOM; + } + + + Object.extend(methods, { + recursivelyCollect: recursivelyCollect, + ancestors: ancestors, + descendants: descendants, + firstDescendant: firstDescendant, + immediateDescendants: immediateDescendants, + previousSiblings: previousSiblings, + nextSiblings: nextSiblings, + siblings: siblings, + match: match, + up: up, + down: down, + previous: previous, + next: next, + select: select, + adjacent: adjacent, + descendantOf: descendantOf, + + getElementsBySelector: select, + + childElements: immediateDescendants + }); + + + var idCounter = 1; + function identify(element) { + element = $(element); + var id = Element.readAttribute(element, 'id'); + if (id) return id; + + do { id = 'anonymous_element_' + idCounter++ } while ($(id)); + + Element.writeAttribute(element, 'id', id); + return id; + } + + + function readAttribute(element, name) { + return $(element).getAttribute(name); + } + + function readAttribute_IE(element, name) { + element = $(element); + + var table = ATTRIBUTE_TRANSLATIONS.read; + if (table.values[name]) + return table.values[name](element, name); + + if (table.names[name]) name = table.names[name]; + + if (name.include(':')) { + if (!element.attributes || !element.attributes[name]) return null; + return element.attributes[name].value; + } + + return element.getAttribute(name); + } + + function readAttribute_Opera(element, name) { + if (name === 'title') return element.title; + return element.getAttribute(name); + } + + var PROBLEMATIC_ATTRIBUTE_READING = (function() { + DIV.setAttribute('onclick', []); + var value = DIV.getAttribute('onclick'); + var isFunction = Object.isArray(value); + DIV.removeAttribute('onclick'); + return isFunction; + })(); + + if (PROBLEMATIC_ATTRIBUTE_READING) { + readAttribute = readAttribute_IE; + } else if (Prototype.Browser.Opera) { + readAttribute = readAttribute_Opera; + } + + + function writeAttribute(element, name, value) { + element = $(element); + var attributes = {}, table = ATTRIBUTE_TRANSLATIONS.write; + + if (typeof name === 'object') { + attributes = name; + } else { + attributes[name] = Object.isUndefined(value) ? true : value; + } + + for (var attr in attributes) { + name = table.names[attr] || attr; + value = attributes[attr]; + if (table.values[attr]) + name = table.values[attr](element, value) || name; + if (value === false || value === null) + element.removeAttribute(name); + else if (value === true) + element.setAttribute(name, name); + else element.setAttribute(name, value); + } + + return element; + } + + var PROBLEMATIC_HAS_ATTRIBUTE_WITH_CHECKBOXES = (function () { + if (!HAS_EXTENDED_CREATE_ELEMENT_SYNTAX) { + return false; + } + var checkbox = document.createElement(''); + checkbox.checked = true; + var node = checkbox.getAttributeNode('checked'); + return !node || !node.specified; + })(); + + function hasAttribute(element, attribute) { + attribute = ATTRIBUTE_TRANSLATIONS.has[attribute] || attribute; + var node = $(element).getAttributeNode(attribute); + return !!(node && node.specified); + } + + function hasAttribute_IE(element, attribute) { + if (attribute === 'checked') { + return element.checked; + } + return hasAttribute(element, attribute); + } + + GLOBAL.Element.Methods.Simulated.hasAttribute = + PROBLEMATIC_HAS_ATTRIBUTE_WITH_CHECKBOXES ? + hasAttribute_IE : hasAttribute; + + function classNames(element) { + return new Element.ClassNames(element); + } + + var regExpCache = {}; + function getRegExpForClassName(className) { + if (regExpCache[className]) return regExpCache[className]; + + var re = new RegExp("(^|\\s+)" + className + "(\\s+|$)"); + regExpCache[className] = re; + return re; + } + + function hasClassName(element, className) { + if (!(element = $(element))) return; + + var elementClassName = element.className; + + if (elementClassName.length === 0) return false; + if (elementClassName === className) return true; + + return getRegExpForClassName(className).test(elementClassName); + } + + function addClassName(element, className) { + if (!(element = $(element))) return; + + if (!hasClassName(element, className)) + element.className += (element.className ? ' ' : '') + className; + + return element; + } + + function removeClassName(element, className) { + if (!(element = $(element))) return; + + element.className = element.className.replace( + getRegExpForClassName(className), ' ').strip(); + + return element; + } + + function toggleClassName(element, className, bool) { + if (!(element = $(element))) return; + + if (Object.isUndefined(bool)) + bool = !hasClassName(element, className); + + var method = Element[bool ? 'addClassName' : 'removeClassName']; + return method(element, className); + } + + var ATTRIBUTE_TRANSLATIONS = {}; + + var classProp = 'className', forProp = 'for'; + + DIV.setAttribute(classProp, 'x'); + if (DIV.className !== 'x') { + DIV.setAttribute('class', 'x'); + if (DIV.className === 'x') + classProp = 'class'; + } + + var LABEL = document.createElement('label'); + LABEL.setAttribute(forProp, 'x'); + if (LABEL.htmlFor !== 'x') { + LABEL.setAttribute('htmlFor', 'x'); + if (LABEL.htmlFor === 'x') + forProp = 'htmlFor'; + } + LABEL = null; + + function _getAttr(element, attribute) { + return element.getAttribute(attribute); + } + + function _getAttr2(element, attribute) { + return element.getAttribute(attribute, 2); + } + + function _getAttrNode(element, attribute) { + var node = element.getAttributeNode(attribute); + return node ? node.value : ''; + } + + function _getFlag(element, attribute) { + return $(element).hasAttribute(attribute) ? attribute : null; + } + + DIV.onclick = Prototype.emptyFunction; + var onclickValue = DIV.getAttribute('onclick'); + + var _getEv; + + if (String(onclickValue).indexOf('{') > -1) { + _getEv = function(element, attribute) { + var value = element.getAttribute(attribute); + if (!value) return null; + value = value.toString(); + value = value.split('{')[1]; + value = value.split('}')[0]; + return value.strip(); + }; + } + else if (onclickValue === '') { + _getEv = function(element, attribute) { + var value = element.getAttribute(attribute); + if (!value) return null; + return value.strip(); + }; + } + + ATTRIBUTE_TRANSLATIONS.read = { + names: { + 'class': classProp, + 'className': classProp, + 'for': forProp, + 'htmlFor': forProp + }, + + values: { + style: function(element) { + return element.style.cssText.toLowerCase(); + }, + title: function(element) { + return element.title; + } + } + }; + + ATTRIBUTE_TRANSLATIONS.write = { + names: { + className: 'class', + htmlFor: 'for', + cellpadding: 'cellPadding', + cellspacing: 'cellSpacing' + }, + + values: { + checked: function(element, value) { + element.checked = !!value; + }, + + style: function(element, value) { + element.style.cssText = value ? value : ''; + } + } + }; + + ATTRIBUTE_TRANSLATIONS.has = { names: {} }; + + Object.extend(ATTRIBUTE_TRANSLATIONS.write.names, + ATTRIBUTE_TRANSLATIONS.read.names); + + var CAMEL_CASED_ATTRIBUTE_NAMES = $w('colSpan rowSpan vAlign dateTime ' + + 'accessKey tabIndex encType maxLength readOnly longDesc frameBorder'); + + for (var i = 0, attr; attr = CAMEL_CASED_ATTRIBUTE_NAMES[i]; i++) { + ATTRIBUTE_TRANSLATIONS.write.names[attr.toLowerCase()] = attr; + ATTRIBUTE_TRANSLATIONS.has.names[attr.toLowerCase()] = attr; + } + + Object.extend(ATTRIBUTE_TRANSLATIONS.read.values, { + href: _getAttr2, + src: _getAttr2, + type: _getAttr, + action: _getAttrNode, + disabled: _getFlag, + checked: _getFlag, + readonly: _getFlag, + multiple: _getFlag, + onload: _getEv, + onunload: _getEv, + onclick: _getEv, + ondblclick: _getEv, + onmousedown: _getEv, + onmouseup: _getEv, + onmouseover: _getEv, + onmousemove: _getEv, + onmouseout: _getEv, + onfocus: _getEv, + onblur: _getEv, + onkeypress: _getEv, + onkeydown: _getEv, + onkeyup: _getEv, + onsubmit: _getEv, + onreset: _getEv, + onselect: _getEv, + onchange: _getEv + }); + + + Object.extend(methods, { + identify: identify, + readAttribute: readAttribute, + writeAttribute: writeAttribute, + classNames: classNames, + hasClassName: hasClassName, + addClassName: addClassName, + removeClassName: removeClassName, + toggleClassName: toggleClassName + }); + + + function normalizeStyleName(style) { + if (style === 'float' || style === 'styleFloat') + return 'cssFloat'; + return style.camelize(); + } + + function normalizeStyleName_IE(style) { + if (style === 'float' || style === 'cssFloat') + return 'styleFloat'; + return style.camelize(); + } + + function setStyle(element, styles) { + element = $(element); + var elementStyle = element.style, match; + + if (Object.isString(styles)) { + elementStyle.cssText += ';' + styles; + if (styles.include('opacity')) { + var opacity = styles.match(/opacity:\s*(\d?\.?\d*)/)[1]; + Element.setOpacity(element, opacity); + } + return element; + } + + for (var property in styles) { + if (property === 'opacity') { + Element.setOpacity(element, styles[property]); + } else { + var value = styles[property]; + if (property === 'float' || property === 'cssFloat') { + property = Object.isUndefined(elementStyle.styleFloat) ? + 'cssFloat' : 'styleFloat'; + } + elementStyle[property] = value; + } + } + + return element; + } + + + function getStyle(element, style) { + element = $(element); + style = normalizeStyleName(style); + + var value = element.style[style]; + if (!value || value === 'auto') { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css[style] : null; + } + + if (style === 'opacity') return value ? parseFloat(value) : 1.0; + return value === 'auto' ? null : value; + } + + function getStyle_Opera(element, style) { + switch (style) { + case 'height': case 'width': + if (!Element.visible(element)) return null; + + var dim = parseInt(getStyle(element, style), 10); + + if (dim !== element['offset' + style.capitalize()]) + return dim + 'px'; + + return Element.measure(element, style); + + default: return getStyle(element, style); + } + } + + function getStyle_IE(element, style) { + element = $(element); + style = normalizeStyleName_IE(style); + + var value = element.style[style]; + if (!value && element.currentStyle) { + value = element.currentStyle[style]; + } + + if (style === 'opacity' && !STANDARD_CSS_OPACITY_SUPPORTED) + return getOpacity_IE(element); + + if (value === 'auto') { + if ((style === 'width' || style === 'height') && Element.visible(element)) + return Element.measure(element, style) + 'px'; + return null; + } + + return value; + } + + function stripAlphaFromFilter_IE(filter) { + return (filter || '').replace(/alpha\([^\)]*\)/gi, ''); + } + + function hasLayout_IE(element) { + if (!element.currentStyle || !element.currentStyle.hasLayout) + element.style.zoom = 1; + return element; + } + + var STANDARD_CSS_OPACITY_SUPPORTED = (function() { + DIV.style.cssText = "opacity:.55"; + return /^0.55/.test(DIV.style.opacity); + })(); + + function setOpacity(element, value) { + element = $(element); + if (value == 1 || value === '') value = ''; + else if (value < 0.00001) value = 0; + element.style.opacity = value; + return element; + } + + function setOpacity_IE(element, value) { + if (STANDARD_CSS_OPACITY_SUPPORTED) + return setOpacity(element, value); + + element = hasLayout_IE($(element)); + var filter = Element.getStyle(element, 'filter'), + style = element.style; + + if (value == 1 || value === '') { + filter = stripAlphaFromFilter_IE(filter); + if (filter) style.filter = filter; + else style.removeAttribute('filter'); + return element; + } + + if (value < 0.00001) value = 0; + + style.filter = stripAlphaFromFilter_IE(filter) + + 'alpha(opacity=' + (value * 100) + ')'; + + return element; + } + + + function getOpacity(element) { + return Element.getStyle(element, 'opacity'); + } + + function getOpacity_IE(element) { + if (STANDARD_CSS_OPACITY_SUPPORTED) + return getOpacity(element); + + var filter = Element.getStyle(element, 'filter'); + if (filter.length === 0) return 1.0; + var match = (filter || '').match(/alpha\(opacity=(.*)\)/); + if (match && match[1]) return parseFloat(match[1]) / 100; + return 1.0; + } + + + Object.extend(methods, { + setStyle: setStyle, + getStyle: getStyle, + setOpacity: setOpacity, + getOpacity: getOpacity + }); + + if ('styleFloat' in DIV.style) { + methods.getStyle = getStyle_IE; + methods.setOpacity = setOpacity_IE; + methods.getOpacity = getOpacity_IE; + } + + var UID = 0; + + GLOBAL.Element.Storage = { UID: 1 }; + + function getUniqueElementID(element) { + if (element === window) return 0; + + if (typeof element._prototypeUID === 'undefined') + element._prototypeUID = Element.Storage.UID++; + return element._prototypeUID; + } + + function getUniqueElementID_IE(element) { + if (element === window) return 0; + if (element == document) return 1; + return element.uniqueID; + } + + var HAS_UNIQUE_ID_PROPERTY = ('uniqueID' in DIV); + if (HAS_UNIQUE_ID_PROPERTY) + getUniqueElementID = getUniqueElementID_IE; + + function getStorage(element) { + if (!(element = $(element))) return; + + var uid = getUniqueElementID(element); + + if (!Element.Storage[uid]) + Element.Storage[uid] = $H(); + + return Element.Storage[uid]; + } + + function store(element, key, value) { + if (!(element = $(element))) return; + var storage = getStorage(element); + if (arguments.length === 2) { + storage.update(key); + } else { + storage.set(key, value); + } + return element; + } + + function retrieve(element, key, defaultValue) { + if (!(element = $(element))) return; + var storage = getStorage(element), value = storage.get(key); + + if (Object.isUndefined(value)) { + storage.set(key, defaultValue); + value = defaultValue; + } + + return value; + } + + + Object.extend(methods, { + getStorage: getStorage, + store: store, + retrieve: retrieve + }); + + + var Methods = {}, ByTag = Element.Methods.ByTag, + F = Prototype.BrowserFeatures; + + if (!F.ElementExtensions && ('__proto__' in DIV)) { + GLOBAL.HTMLElement = {}; + GLOBAL.HTMLElement.prototype = DIV['__proto__']; + F.ElementExtensions = true; + } + + function checkElementPrototypeDeficiency(tagName) { + if (typeof window.Element === 'undefined') return false; + if (!HAS_EXTENDED_CREATE_ELEMENT_SYNTAX) return false; + var proto = window.Element.prototype; + if (proto) { + var id = '_' + (Math.random() + '').slice(2), + el = document.createElement(tagName); + proto[id] = 'x'; + var isBuggy = (el[id] !== 'x'); + delete proto[id]; + el = null; + return isBuggy; + } + + return false; + } + + var HTMLOBJECTELEMENT_PROTOTYPE_BUGGY = + checkElementPrototypeDeficiency('object'); + + function extendElementWith(element, methods) { + for (var property in methods) { + var value = methods[property]; + if (Object.isFunction(value) && !(property in element)) + element[property] = value.methodize(); + } + } + + var EXTENDED = {}; + function elementIsExtended(element) { + var uid = getUniqueElementID(element); + return (uid in EXTENDED); + } + + function extend(element) { + if (!element || elementIsExtended(element)) return element; + if (element.nodeType !== Node.ELEMENT_NODE || element == window) + return element; + + var methods = Object.clone(Methods), + tagName = element.tagName.toUpperCase(); + + if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); + + extendElementWith(element, methods); + EXTENDED[getUniqueElementID(element)] = true; + return element; + } + + function extend_IE8(element) { + if (!element || elementIsExtended(element)) return element; + + var t = element.tagName; + if (t && (/^(?:object|applet|embed)$/i.test(t))) { + extendElementWith(element, Element.Methods); + extendElementWith(element, Element.Methods.Simulated); + extendElementWith(element, Element.Methods.ByTag[t.toUpperCase()]); + } + + return element; + } + + if (F.SpecificElementExtensions) { + extend = HTMLOBJECTELEMENT_PROTOTYPE_BUGGY ? extend_IE8 : Prototype.K; + } + + function addMethodsToTagName(tagName, methods) { + tagName = tagName.toUpperCase(); + if (!ByTag[tagName]) ByTag[tagName] = {}; + Object.extend(ByTag[tagName], methods); + } + + function mergeMethods(destination, methods, onlyIfAbsent) { + if (Object.isUndefined(onlyIfAbsent)) onlyIfAbsent = false; + for (var property in methods) { + var value = methods[property]; + if (!Object.isFunction(value)) continue; + if (!onlyIfAbsent || !(property in destination)) + destination[property] = value.methodize(); + } + } + + function findDOMClass(tagName) { + var klass; + var trans = { + "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph", + "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList", + "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading", + "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote", + "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION": + "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD": + "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR": + "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET": + "FrameSet", "IFRAME": "IFrame" + }; + if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName.capitalize() + 'Element'; + if (window[klass]) return window[klass]; + + var element = document.createElement(tagName), + proto = element['__proto__'] || element.constructor.prototype; + + element = null; + return proto; + } + + function addMethods(methods) { + if (arguments.length === 0) addFormMethods(); + + if (arguments.length === 2) { + var tagName = methods; + methods = arguments[1]; + } + + if (!tagName) { + Object.extend(Element.Methods, methods || {}); + } else { + if (Object.isArray(tagName)) { + for (var i = 0, tag; tag = tagName[i]; i++) + addMethodsToTagName(tag, methods); + } else { + addMethodsToTagName(tagName, methods); + } + } + + var ELEMENT_PROTOTYPE = window.HTMLElement ? HTMLElement.prototype : + Element.prototype; + + if (F.ElementExtensions) { + mergeMethods(ELEMENT_PROTOTYPE, Element.Methods); + mergeMethods(ELEMENT_PROTOTYPE, Element.Methods.Simulated, true); + } + + if (F.SpecificElementExtensions) { + for (var tag in Element.Methods.ByTag) { + var klass = findDOMClass(tag); + if (Object.isUndefined(klass)) continue; + mergeMethods(klass.prototype, ByTag[tag]); + } + } + + Object.extend(Element, Element.Methods); + Object.extend(Element, Element.Methods.Simulated); + delete Element.ByTag; + delete Element.Simulated; + + Element.extend.refresh(); + + ELEMENT_CACHE = {}; + } + + Object.extend(GLOBAL.Element, { + extend: extend, + addMethods: addMethods + }); + + if (extend === Prototype.K) { + GLOBAL.Element.extend.refresh = Prototype.emptyFunction; + } else { + GLOBAL.Element.extend.refresh = function() { + if (Prototype.BrowserFeatures.ElementExtensions) return; + Object.extend(Methods, Element.Methods); + Object.extend(Methods, Element.Methods.Simulated); + + EXTENDED = {}; + }; + } + + function addFormMethods() { + Object.extend(Form, Form.Methods); + Object.extend(Form.Element, Form.Element.Methods); + Object.extend(Element.Methods.ByTag, { + "FORM": Object.clone(Form.Methods), + "INPUT": Object.clone(Form.Element.Methods), + "SELECT": Object.clone(Form.Element.Methods), + "TEXTAREA": Object.clone(Form.Element.Methods), + "BUTTON": Object.clone(Form.Element.Methods) + }); + } + + Element.addMethods(methods); + + function destroyCache_IE() { + DIV = null; + ELEMENT_CACHE = null; + } + + if (window.attachEvent) + window.attachEvent('onunload', destroyCache_IE); + +})(this); +(function() { + + function toDecimal(pctString) { + var match = pctString.match(/^(\d+)%?$/i); + if (!match) return null; + return (Number(match[1]) / 100); + } + + function getRawStyle(element, style) { + element = $(element); + + var value = element.style[style]; + if (!value || value === 'auto') { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css[style] : null; + } + + if (style === 'opacity') return value ? parseFloat(value) : 1.0; + return value === 'auto' ? null : value; + } + + function getRawStyle_IE(element, style) { + var value = element.style[style]; + if (!value && element.currentStyle) { + value = element.currentStyle[style]; + } + return value; + } + + function getContentWidth(element, context) { + var boxWidth = element.offsetWidth; + + var bl = getPixelValue(element, 'borderLeftWidth', context) || 0; + var br = getPixelValue(element, 'borderRightWidth', context) || 0; + var pl = getPixelValue(element, 'paddingLeft', context) || 0; + var pr = getPixelValue(element, 'paddingRight', context) || 0; + + return boxWidth - bl - br - pl - pr; + } + + if ('currentStyle' in document.documentElement) { + getRawStyle = getRawStyle_IE; + } + + + function getPixelValue(value, property, context) { + var element = null; + if (Object.isElement(value)) { + element = value; + value = getRawStyle(element, property); + } + + if (value === null || Object.isUndefined(value)) { + return null; + } + + if ((/^(?:-)?\d+(\.\d+)?(px)?$/i).test(value)) { + return window.parseFloat(value); + } + + var isPercentage = value.include('%'), isViewport = (context === document.viewport); + + if (/\d/.test(value) && element && element.runtimeStyle && !(isPercentage && isViewport)) { + var style = element.style.left, rStyle = element.runtimeStyle.left; + element.runtimeStyle.left = element.currentStyle.left; + element.style.left = value || 0; + value = element.style.pixelLeft; + element.style.left = style; + element.runtimeStyle.left = rStyle; + + return value; + } + + if (element && isPercentage) { + context = context || element.parentNode; + var decimal = toDecimal(value), whole = null; + + var isHorizontal = property.include('left') || property.include('right') || + property.include('width'); + + var isVertical = property.include('top') || property.include('bottom') || + property.include('height'); + + if (context === document.viewport) { + if (isHorizontal) { + whole = document.viewport.getWidth(); + } else if (isVertical) { + whole = document.viewport.getHeight(); + } + } else { + if (isHorizontal) { + whole = $(context).measure('width'); + } else if (isVertical) { + whole = $(context).measure('height'); + } + } + + return (whole === null) ? 0 : whole * decimal; + } + + return 0; + } + + function toCSSPixels(number) { + if (Object.isString(number) && number.endsWith('px')) + return number; + return number + 'px'; + } + + function isDisplayed(element) { + while (element && element.parentNode) { + var display = element.getStyle('display'); + if (display === 'none') { + return false; + } + element = $(element.parentNode); + } + return true; + } + + var hasLayout = Prototype.K; + if ('currentStyle' in document.documentElement) { + hasLayout = function(element) { + if (!element.currentStyle.hasLayout) { + element.style.zoom = 1; + } + return element; + }; + } + + function cssNameFor(key) { + if (key.include('border')) key = key + '-width'; + return key.camelize(); + } + + Element.Layout = Class.create(Hash, { + initialize: function($super, element, preCompute) { + $super(); + this.element = $(element); + + Element.Layout.PROPERTIES.each( function(property) { + this._set(property, null); + }, this); + + if (preCompute) { + this._preComputing = true; + this._begin(); + Element.Layout.PROPERTIES.each( this._compute, this ); + this._end(); + this._preComputing = false; + } + }, + + _set: function(property, value) { + return Hash.prototype.set.call(this, property, value); + }, + + set: function(property, value) { + throw "Properties of Element.Layout are read-only."; + }, + + get: function($super, property) { + var value = $super(property); + return value === null ? this._compute(property) : value; + }, + + _begin: function() { + if (this._isPrepared()) return; + + var element = this.element; + if (isDisplayed(element)) { + this._setPrepared(true); + return; + } + + + var originalStyles = { + position: element.style.position || '', + width: element.style.width || '', + visibility: element.style.visibility || '', + display: element.style.display || '' + }; + + element.store('prototype_original_styles', originalStyles); + + var position = getRawStyle(element, 'position'), width = element.offsetWidth; + + if (width === 0 || width === null) { + element.style.display = 'block'; + width = element.offsetWidth; + } + + var context = (position === 'fixed') ? document.viewport : + element.parentNode; + + var tempStyles = { + visibility: 'hidden', + display: 'block' + }; + + if (position !== 'fixed') tempStyles.position = 'absolute'; + + element.setStyle(tempStyles); + + var positionedWidth = element.offsetWidth, newWidth; + if (width && (positionedWidth === width)) { + newWidth = getContentWidth(element, context); + } else if (position === 'absolute' || position === 'fixed') { + newWidth = getContentWidth(element, context); + } else { + var parent = element.parentNode, pLayout = $(parent).getLayout(); + + newWidth = pLayout.get('width') - + this.get('margin-left') - + this.get('border-left') - + this.get('padding-left') - + this.get('padding-right') - + this.get('border-right') - + this.get('margin-right'); + } + + element.setStyle({ width: newWidth + 'px' }); + + this._setPrepared(true); + }, + + _end: function() { + var element = this.element; + var originalStyles = element.retrieve('prototype_original_styles'); + element.store('prototype_original_styles', null); + element.setStyle(originalStyles); + this._setPrepared(false); + }, + + _compute: function(property) { + var COMPUTATIONS = Element.Layout.COMPUTATIONS; + if (!(property in COMPUTATIONS)) { + throw "Property not found."; + } + + return this._set(property, COMPUTATIONS[property].call(this, this.element)); + }, + + _isPrepared: function() { + return this.element.retrieve('prototype_element_layout_prepared', false); + }, + + _setPrepared: function(bool) { + return this.element.store('prototype_element_layout_prepared', bool); + }, + + toObject: function() { + var args = $A(arguments); + var keys = (args.length === 0) ? Element.Layout.PROPERTIES : + args.join(' ').split(' '); + var obj = {}; + keys.each( function(key) { + if (!Element.Layout.PROPERTIES.include(key)) return; + var value = this.get(key); + if (value != null) obj[key] = value; + }, this); + return obj; + }, + + toHash: function() { + var obj = this.toObject.apply(this, arguments); + return new Hash(obj); + }, + + toCSS: function() { + var args = $A(arguments); + var keys = (args.length === 0) ? Element.Layout.PROPERTIES : + args.join(' ').split(' '); + var css = {}; + + keys.each( function(key) { + if (!Element.Layout.PROPERTIES.include(key)) return; + if (Element.Layout.COMPOSITE_PROPERTIES.include(key)) return; + + var value = this.get(key); + if (value != null) css[cssNameFor(key)] = value + 'px'; + }, this); + return css; + }, + + inspect: function() { + return "#"; + } + }); + + Object.extend(Element.Layout, { + PROPERTIES: $w('height width top left right bottom border-left border-right border-top border-bottom padding-left padding-right padding-top padding-bottom margin-top margin-bottom margin-left margin-right padding-box-width padding-box-height border-box-width border-box-height margin-box-width margin-box-height'), + + COMPOSITE_PROPERTIES: $w('padding-box-width padding-box-height margin-box-width margin-box-height border-box-width border-box-height'), + + COMPUTATIONS: { + 'height': function(element) { + if (!this._preComputing) this._begin(); + + var bHeight = this.get('border-box-height'); + if (bHeight <= 0) { + if (!this._preComputing) this._end(); + return 0; + } + + var bTop = this.get('border-top'), + bBottom = this.get('border-bottom'); + + var pTop = this.get('padding-top'), + pBottom = this.get('padding-bottom'); + + if (!this._preComputing) this._end(); + + return bHeight - bTop - bBottom - pTop - pBottom; + }, + + 'width': function(element) { + if (!this._preComputing) this._begin(); + + var bWidth = this.get('border-box-width'); + if (bWidth <= 0) { + if (!this._preComputing) this._end(); + return 0; + } + + var bLeft = this.get('border-left'), + bRight = this.get('border-right'); + + var pLeft = this.get('padding-left'), + pRight = this.get('padding-right'); + + if (!this._preComputing) this._end(); + return bWidth - bLeft - bRight - pLeft - pRight; + }, + + 'padding-box-height': function(element) { + var height = this.get('height'), + pTop = this.get('padding-top'), + pBottom = this.get('padding-bottom'); + + return height + pTop + pBottom; + }, + + 'padding-box-width': function(element) { + var width = this.get('width'), + pLeft = this.get('padding-left'), + pRight = this.get('padding-right'); + + return width + pLeft + pRight; + }, + + 'border-box-height': function(element) { + if (!this._preComputing) this._begin(); + var height = element.offsetHeight; + if (!this._preComputing) this._end(); + return height; + }, + + 'border-box-width': function(element) { + if (!this._preComputing) this._begin(); + var width = element.offsetWidth; + if (!this._preComputing) this._end(); + return width; + }, + + 'margin-box-height': function(element) { + var bHeight = this.get('border-box-height'), + mTop = this.get('margin-top'), + mBottom = this.get('margin-bottom'); + + if (bHeight <= 0) return 0; + + return bHeight + mTop + mBottom; + }, + + 'margin-box-width': function(element) { + var bWidth = this.get('border-box-width'), + mLeft = this.get('margin-left'), + mRight = this.get('margin-right'); + + if (bWidth <= 0) return 0; + + return bWidth + mLeft + mRight; + }, + + 'top': function(element) { + var offset = element.positionedOffset(); + return offset.top; + }, + + 'bottom': function(element) { + var offset = element.positionedOffset(), + parent = element.getOffsetParent(), + pHeight = parent.measure('height'); + + var mHeight = this.get('border-box-height'); + + return pHeight - mHeight - offset.top; + }, + + 'left': function(element) { + var offset = element.positionedOffset(); + return offset.left; + }, + + 'right': function(element) { + var offset = element.positionedOffset(), + parent = element.getOffsetParent(), + pWidth = parent.measure('width'); + + var mWidth = this.get('border-box-width'); + + return pWidth - mWidth - offset.left; + }, + + 'padding-top': function(element) { + return getPixelValue(element, 'paddingTop'); + }, + + 'padding-bottom': function(element) { + return getPixelValue(element, 'paddingBottom'); + }, + + 'padding-left': function(element) { + return getPixelValue(element, 'paddingLeft'); + }, + + 'padding-right': function(element) { + return getPixelValue(element, 'paddingRight'); + }, + + 'border-top': function(element) { + return getPixelValue(element, 'borderTopWidth'); + }, + + 'border-bottom': function(element) { + return getPixelValue(element, 'borderBottomWidth'); + }, + + 'border-left': function(element) { + return getPixelValue(element, 'borderLeftWidth'); + }, + + 'border-right': function(element) { + return getPixelValue(element, 'borderRightWidth'); + }, + + 'margin-top': function(element) { + return getPixelValue(element, 'marginTop'); + }, + + 'margin-bottom': function(element) { + return getPixelValue(element, 'marginBottom'); + }, + + 'margin-left': function(element) { + return getPixelValue(element, 'marginLeft'); + }, + + 'margin-right': function(element) { + return getPixelValue(element, 'marginRight'); + } + } + }); + + if ('getBoundingClientRect' in document.documentElement) { + Object.extend(Element.Layout.COMPUTATIONS, { + 'right': function(element) { + var parent = hasLayout(element.getOffsetParent()); + var rect = element.getBoundingClientRect(), + pRect = parent.getBoundingClientRect(); + + return (pRect.right - rect.right).round(); + }, + + 'bottom': function(element) { + var parent = hasLayout(element.getOffsetParent()); + var rect = element.getBoundingClientRect(), + pRect = parent.getBoundingClientRect(); + + return (pRect.bottom - rect.bottom).round(); + } + }); + } + + Element.Offset = Class.create({ + initialize: function(left, top) { + this.left = left.round(); + this.top = top.round(); + + this[0] = this.left; + this[1] = this.top; + }, + + relativeTo: function(offset) { + return new Element.Offset( + this.left - offset.left, + this.top - offset.top + ); + }, + + inspect: function() { + return "#".interpolate(this); + }, + + toString: function() { + return "[#{left}, #{top}]".interpolate(this); + }, + + toArray: function() { + return [this.left, this.top]; + } + }); + + function getLayout(element, preCompute) { + return new Element.Layout(element, preCompute); + } + + function measure(element, property) { + return $(element).getLayout().get(property); + } + + function getHeight(element) { + return Element.getDimensions(element).height; + } + + function getWidth(element) { + return Element.getDimensions(element).width; + } + + function getDimensions(element) { + element = $(element); + var display = Element.getStyle(element, 'display'); + + if (display && display !== 'none') { + return { width: element.offsetWidth, height: element.offsetHeight }; + } + + var style = element.style; + var originalStyles = { + visibility: style.visibility, + position: style.position, + display: style.display + }; + + var newStyles = { + visibility: 'hidden', + display: 'block' + }; + + if (originalStyles.position !== 'fixed') + newStyles.position = 'absolute'; + + Element.setStyle(element, newStyles); + + var dimensions = { + width: element.offsetWidth, + height: element.offsetHeight + }; + + Element.setStyle(element, originalStyles); + + return dimensions; + } + + function getOffsetParent(element) { + element = $(element); + + if (isDocument(element) || isDetached(element) || isBody(element) || isHtml(element)) + return $(document.body); + + var isInline = (Element.getStyle(element, 'display') === 'inline'); + if (!isInline && element.offsetParent) return $(element.offsetParent); + + while ((element = element.parentNode) && element !== document.body) { + if (Element.getStyle(element, 'position') !== 'static') { + return isHtml(element) ? $(document.body) : $(element); + } + } + + return $(document.body); + } + + + function cumulativeOffset(element) { + element = $(element); + var valueT = 0, valueL = 0; + if (element.parentNode) { + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + } + return new Element.Offset(valueL, valueT); + } + + function positionedOffset(element) { + element = $(element); + + var layout = element.getLayout(); + + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + if (isBody(element)) break; + var p = Element.getStyle(element, 'position'); + if (p !== 'static') break; + } + } while (element); + + valueL -= layout.get('margin-top'); + valueT -= layout.get('margin-left'); + + return new Element.Offset(valueL, valueT); + } + + function cumulativeScrollOffset(element) { + var valueT = 0, valueL = 0; + do { + if (element === document.body) { + var bodyScrollNode = document.documentElement || document.body.parentNode || document.body; + valueT += !Object.isUndefined(window.pageYOffset) ? window.pageYOffset : bodyScrollNode.scrollTop || 0; + valueL += !Object.isUndefined(window.pageXOffset) ? window.pageXOffset : bodyScrollNode.scrollLeft || 0; + break; + } else { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } + } while (element); + return new Element.Offset(valueL, valueT); + } + + function viewportOffset(forElement) { + var valueT = 0, valueL = 0, docBody = document.body; + + forElement = $(forElement); + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == docBody && + Element.getStyle(element, 'position') == 'absolute') break; + } while (element = element.offsetParent); + + element = forElement; + do { + if (element != docBody) { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } + } while (element = element.parentNode); + return new Element.Offset(valueL, valueT); + } + + function absolutize(element) { + element = $(element); + + if (Element.getStyle(element, 'position') === 'absolute') { + return element; + } + + var offsetParent = getOffsetParent(element); + var eOffset = element.viewportOffset(), + pOffset = offsetParent.viewportOffset(); + + var offset = eOffset.relativeTo(pOffset); + var layout = element.getLayout(); + + element.store('prototype_absolutize_original_styles', { + position: element.getStyle('position'), + left: element.getStyle('left'), + top: element.getStyle('top'), + width: element.getStyle('width'), + height: element.getStyle('height') + }); + + element.setStyle({ + position: 'absolute', + top: offset.top + 'px', + left: offset.left + 'px', + width: layout.get('width') + 'px', + height: layout.get('height') + 'px' + }); + + return element; + } + + function relativize(element) { + element = $(element); + if (Element.getStyle(element, 'position') === 'relative') { + return element; + } + + var originalStyles = + element.retrieve('prototype_absolutize_original_styles'); + + if (originalStyles) element.setStyle(originalStyles); + return element; + } + + + function scrollTo(element) { + element = $(element); + var pos = Element.cumulativeOffset(element); + window.scrollTo(pos.left, pos.top); + return element; + } + + + function makePositioned(element) { + element = $(element); + var position = Element.getStyle(element, 'position'), styles = {}; + if (position === 'static' || !position) { + styles.position = 'relative'; + if (Prototype.Browser.Opera) { + styles.top = 0; + styles.left = 0; + } + Element.setStyle(element, styles); + Element.store(element, 'prototype_made_positioned', true); + } + return element; + } + + function undoPositioned(element) { + element = $(element); + var storage = Element.getStorage(element), + madePositioned = storage.get('prototype_made_positioned'); + + if (madePositioned) { + storage.unset('prototype_made_positioned'); + Element.setStyle(element, { + position: '', + top: '', + bottom: '', + left: '', + right: '' + }); + } + return element; + } + + function makeClipping(element) { + element = $(element); + + var storage = Element.getStorage(element), + madeClipping = storage.get('prototype_made_clipping'); + + if (Object.isUndefined(madeClipping)) { + var overflow = Element.getStyle(element, 'overflow'); + storage.set('prototype_made_clipping', overflow); + if (overflow !== 'hidden') + element.style.overflow = 'hidden'; + } + + return element; + } + + function undoClipping(element) { + element = $(element); + var storage = Element.getStorage(element), + overflow = storage.get('prototype_made_clipping'); + + if (!Object.isUndefined(overflow)) { + storage.unset('prototype_made_clipping'); + element.style.overflow = overflow || ''; + } + + return element; + } + + function clonePosition(element, source, options) { + options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, options || {}); + + source = $(source); + element = $(element); + var p, delta, layout, styles = {}; + + if (options.setLeft || options.setTop) { + p = Element.viewportOffset(source); + delta = [0, 0]; + if (Element.getStyle(element, 'position') === 'absolute') { + var parent = Element.getOffsetParent(element); + if (parent !== document.body) delta = Element.viewportOffset(parent); + } + } + + if (options.setWidth || options.setHeight) { + layout = Element.getLayout(source); + } + + if (options.setLeft) + styles.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; + if (options.setTop) + styles.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + + if (options.setWidth) + styles.width = layout.get('border-box-width') + 'px'; + if (options.setHeight) + styles.height = layout.get('border-box-height') + 'px'; + + return Element.setStyle(element, styles); + } + + + if (Prototype.Browser.IE) { + getOffsetParent = getOffsetParent.wrap( + function(proceed, element) { + element = $(element); + + if (isDocument(element) || isDetached(element) || isBody(element) || isHtml(element)) + return $(document.body); + + var position = element.getStyle('position'); + if (position !== 'static') return proceed(element); + + element.setStyle({ position: 'relative' }); + var value = proceed(element); + element.setStyle({ position: position }); + return value; + } + ); + + positionedOffset = positionedOffset.wrap(function(proceed, element) { + element = $(element); + if (!element.parentNode) return new Element.Offset(0, 0); + var position = element.getStyle('position'); + if (position !== 'static') return proceed(element); + + var offsetParent = element.getOffsetParent(); + if (offsetParent && offsetParent.getStyle('position') === 'fixed') + hasLayout(offsetParent); + + element.setStyle({ position: 'relative' }); + var value = proceed(element); + element.setStyle({ position: position }); + return value; + }); + } else if (Prototype.Browser.Webkit) { + cumulativeOffset = function(element) { + element = $(element); + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) { + if (Element.getStyle(element, 'position') == 'absolute') break; + } + + element = element.offsetParent; + } while (element); + + return new Element.Offset(valueL, valueT); + }; + } + + + Element.addMethods({ + getLayout: getLayout, + measure: measure, + getWidth: getWidth, + getHeight: getHeight, + getDimensions: getDimensions, + getOffsetParent: getOffsetParent, + cumulativeOffset: cumulativeOffset, + positionedOffset: positionedOffset, + cumulativeScrollOffset: cumulativeScrollOffset, + viewportOffset: viewportOffset, + absolutize: absolutize, + relativize: relativize, + scrollTo: scrollTo, + makePositioned: makePositioned, + undoPositioned: undoPositioned, + makeClipping: makeClipping, + undoClipping: undoClipping, + clonePosition: clonePosition + }); + + function isBody(element) { + return element.nodeName.toUpperCase() === 'BODY'; + } + + function isHtml(element) { + return element.nodeName.toUpperCase() === 'HTML'; + } + + function isDocument(element) { + return element.nodeType === Node.DOCUMENT_NODE; + } + + function isDetached(element) { + return element !== document.body && + !Element.descendantOf(element, document.body); + } + + if ('getBoundingClientRect' in document.documentElement) { + Element.addMethods({ + viewportOffset: function(element) { + element = $(element); + if (isDetached(element)) return new Element.Offset(0, 0); + + var rect = element.getBoundingClientRect(), + docEl = document.documentElement; + return new Element.Offset(rect.left - docEl.clientLeft, + rect.top - docEl.clientTop); + } + }); + } + + +})(); + +(function() { + + var IS_OLD_OPERA = Prototype.Browser.Opera && + (window.parseFloat(window.opera.version()) < 9.5); + var ROOT = null; + function getRootElement() { + if (ROOT) return ROOT; + ROOT = IS_OLD_OPERA ? document.body : document.documentElement; + return ROOT; + } + + function getDimensions() { + return { width: this.getWidth(), height: this.getHeight() }; + } + + function getWidth() { + return getRootElement().clientWidth; + } + + function getHeight() { + return getRootElement().clientHeight; + } + + function getScrollOffsets() { + var x = window.pageXOffset || document.documentElement.scrollLeft || + document.body.scrollLeft; + var y = window.pageYOffset || document.documentElement.scrollTop || + document.body.scrollTop; + + return new Element.Offset(x, y); + } + + document.viewport = { + getDimensions: getDimensions, + getWidth: getWidth, + getHeight: getHeight, + getScrollOffsets: getScrollOffsets + }; + +})(); +window.$$ = function() { + var expression = $A(arguments).join(', '); + return Prototype.Selector.select(expression, document); +}; + +Prototype.Selector = (function() { + + function select() { + throw new Error('Method "Prototype.Selector.select" must be defined.'); + } + + function match() { + throw new Error('Method "Prototype.Selector.match" must be defined.'); + } + + function find(elements, expression, index) { + index = index || 0; + var match = Prototype.Selector.match, length = elements.length, matchIndex = 0, i; + + for (i = 0; i < length; i++) { + if (match(elements[i], expression) && index == matchIndex++) { + return Element.extend(elements[i]); + } + } + } + + function extendElements(elements) { + for (var i = 0, length = elements.length; i < length; i++) { + Element.extend(elements[i]); + } + return elements; + } + + + var K = Prototype.K; + + return { + select: select, + match: match, + find: find, + extendElements: (Element.extend === K) ? K : extendElements, + extendElement: Element.extend + }; +})(); +Prototype._original_property = window.Sizzle; +/*! + * Sizzle CSS Selector Engine v@VERSION + * http://sizzlejs.com/ + * + * Copyright 2013 jQuery Foundation, Inc. and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: @DATE + */ +(function( window ) { + +var i, + support, + Expr, + getText, + isXML, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + expando = "sizzle" + -(new Date()), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + strundefined = typeof undefined, + MAX_NEGATIVE = 1 << 31, + + hasOwn = ({}).hasOwnProperty, + arr = [], + pop = arr.pop, + push_native = arr.push, + push = arr.push, + slice = arr.slice, + indexOf = arr.indexOf || function( elem ) { + var i = 0, + len = this.length; + for ( ; i < len; i++ ) { + if ( this[i] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", + + + whitespace = "[\\x20\\t\\r\\n\\f]", + characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", + + identifier = characterEncoding.replace( "w", "w#" ), + + attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace + + "*(?:([*^$|!~]?=)" + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]", + + pseudos = ":(" + characterEncoding + ")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|" + attributes.replace( 3, 8 ) + ")*)|.*)\\)|)", + + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), + + rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + characterEncoding + ")" ), + "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ), + "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + rescape = /'|\\/g, + + runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), + funescape = function( _, escaped, escapedWhitespace ) { + var high = "0x" + escaped - 0x10000; + return high !== high || escapedWhitespace ? + escaped : + high < 0 ? + String.fromCharCode( high + 0x10000 ) : + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }; + +try { + push.apply( + (arr = slice.call( preferredDoc.childNodes )), + preferredDoc.childNodes + ); + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + function( target, els ) { + push_native.apply( target, slice.call(els) ); + } : + + function( target, els ) { + var j = target.length, + i = 0; + while ( (target[j++] = els[i++]) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var match, elem, m, nodeType, + i, groups, old, nid, newContext, newSelector; + + if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { + setDocument( context ); + } + + context = context || document; + results = results || []; + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) { + return []; + } + + if ( documentIsHTML && !seed ) { + + if ( (match = rquickExpr.exec( selector )) ) { + if ( (m = match[1]) ) { + if ( nodeType === 9 ) { + elem = context.getElementById( m ); + if ( elem && elem.parentNode ) { + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + } else { + if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) && + contains( context, elem ) && elem.id === m ) { + results.push( elem ); + return results; + } + } + + } else if ( match[2] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + } else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) { + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { + nid = old = expando; + newContext = context; + newSelector = nodeType === 9 && selector; + + if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + groups = tokenize( selector ); + + if ( (old = context.getAttribute("id")) ) { + nid = old.replace( rescape, "\\$&" ); + } else { + context.setAttribute( "id", nid ); + } + nid = "[id='" + nid + "'] "; + + i = groups.length; + while ( i-- ) { + groups[i] = nid + toSelector( groups[i] ); + } + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context; + newSelector = groups.join(","); + } + + if ( newSelector ) { + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch(qsaError) { + } finally { + if ( !old ) { + context.removeAttribute("id"); + } + } + } + } + } + + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {Function(string, Object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + if ( keys.push( key + " " ) > Expr.cacheLength ) { + delete cache[ keys.shift() ]; + } + return (cache[ key + " " ] = value); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created div and expects a boolean result + */ +function assert( fn ) { + var div = document.createElement("div"); + + try { + return !!fn( div ); + } catch (e) { + return false; + } finally { + if ( div.parentNode ) { + div.parentNode.removeChild( div ); + } + div = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split("|"), + i = attrs.length; + + while ( i-- ) { + Expr.attrHandle[ arr[i] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + ( ~b.sourceIndex || MAX_NEGATIVE ) - + ( ~a.sourceIndex || MAX_NEGATIVE ); + + if ( diff ) { + return diff; + } + + if ( cur ) { + while ( (cur = cur.nextSibling) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction(function( argument ) { + argument = +argument; + return markFunction(function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + while ( i-- ) { + if ( seed[ (j = matchIndexes[i]) ] ) { + seed[j] = !(matches[j] = seed[j]); + } + } + }); + }); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== strundefined && context; +} + +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + var documentElement = elem && (elem.ownerDocument || elem).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, + doc = node ? node.ownerDocument || node : preferredDoc, + parent = doc.defaultView; + + if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + document = doc; + docElem = doc.documentElement; + + documentIsHTML = !isXML( doc ); + + if ( parent && parent !== parent.top ) { + if ( parent.addEventListener ) { + parent.addEventListener( "unload", function() { + setDocument(); + }, false ); + } else if ( parent.attachEvent ) { + parent.attachEvent( "onunload", function() { + setDocument(); + }); + } + } + + /* Attributes + ---------------------------------------------------------------------- */ + + support.attributes = assert(function( div ) { + div.className = "i"; + return !div.getAttribute("className"); + }); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + support.getElementsByTagName = assert(function( div ) { + div.appendChild( doc.createComment("") ); + return !div.getElementsByTagName("*").length; + }); + + support.getElementsByClassName = rnative.test( doc.getElementsByClassName ) && assert(function( div ) { + div.innerHTML = "
"; + + div.firstChild.className = "i"; + return div.getElementsByClassName("i").length === 2; + }); + + support.getById = assert(function( div ) { + docElem.appendChild( div ).id = expando; + return !doc.getElementsByName || !doc.getElementsByName( expando ).length; + }); + + if ( support.getById ) { + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== strundefined && documentIsHTML ) { + var m = context.getElementById( id ); + return m && m.parentNode ? [m] : []; + } + }; + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute("id") === attrId; + }; + }; + } else { + delete Expr.find["ID"]; + + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id"); + return node && node.value === attrId; + }; + }; + } + + Expr.find["TAG"] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== strundefined ) { + return context.getElementsByTagName( tag ); + } + } : + function( tag, context ) { + var elem, + tmp = [], + i = 0, + results = context.getElementsByTagName( tag ); + + if ( tag === "*" ) { + while ( (elem = results[i++]) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== strundefined && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + + rbuggyMatches = []; + + rbuggyQSA = []; + + if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) { + assert(function( div ) { + div.innerHTML = ""; + + if ( div.querySelectorAll("[t^='']").length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + if ( !div.querySelectorAll("[selected]").length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + if ( !div.querySelectorAll(":checked").length ) { + rbuggyQSA.push(":checked"); + } + }); + + assert(function( div ) { + var input = doc.createElement("input"); + input.setAttribute( "type", "hidden" ); + div.appendChild( input ).setAttribute( "name", "D" ); + + if ( div.querySelectorAll("[name=d]").length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + if ( !div.querySelectorAll(":enabled").length ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + div.querySelectorAll("*,:x"); + rbuggyQSA.push(",.*:"); + }); + } + + if ( (support.matchesSelector = rnative.test( (matches = docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector) )) ) { + + assert(function( div ) { + support.disconnectedMatch = matches.call( div, "div" ); + + matches.call( div, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + }); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } : + function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + sortOrder = hasCompare ? + function( a, b ) { + + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + 1; + + if ( compare & 1 || + (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { + + if ( a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { + return -1; + } + if ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { + return 1; + } + + return sortInput ? + ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + if ( !aup || !bup ) { + return a === doc ? -1 : + b === doc ? 1 : + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : + 0; + + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + cur = a; + while ( (cur = cur.parentNode) ) { + ap.unshift( cur ); + } + cur = b; + while ( (cur = cur.parentNode) ) { + bp.unshift( cur ); + } + + while ( ap[i] === bp[i] ) { + i++; + } + + return i ? + siblingCheck( ap[i], bp[i] ) : + + ap[i] === preferredDoc ? -1 : + bp[i] === preferredDoc ? 1 : + 0; + }; + + return doc; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + expr = expr.replace( rattributeQuotes, "='$1']" ); + + if ( support.matchesSelector && documentIsHTML && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + if ( ret || support.disconnectedMatch || + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch(e) {} + } + + return Sizzle( expr, document, null, [elem] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + if ( ( context.ownerDocument || context ) !== document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + (val = elem.getAttributeNode(name)) && val.specified ? + val.value : + null; +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( (elem = results[i++]) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + while ( (node = elem[i++]) ) { + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + + return ret; +}; + +Expr = Sizzle.selectors = { + + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[1] = match[1].replace( runescape, funescape ); + + match[3] = ( match[4] || match[5] || "" ).replace( runescape, funescape ); + + if ( match[2] === "~=" ) { + match[3] = " " + match[3] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[1] = match[1].toLowerCase(); + + if ( match[1].slice( 0, 3 ) === "nth" ) { + if ( !match[3] ) { + Sizzle.error( match[0] ); + } + + match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); + match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + + } else if ( match[3] ) { + Sizzle.error( match[0] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[5] && match[2]; + + if ( matchExpr["CHILD"].test( match[0] ) ) { + return null; + } + + if ( match[3] && match[4] !== undefined ) { + match[2] = match[4]; + + } else if ( unquoted && rpseudo.test( unquoted ) && + (excess = tokenize( unquoted, true )) && + (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + + match[0] = match[0].slice( 0, excess ); + match[2] = unquoted.slice( 0, excess ); + } + + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { return true; } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && + classCache( className, function( elem ) { + return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== strundefined && elem.getAttribute("class") || "" ); + }); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + }; + }, + + "CHILD": function( type, what, argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, context, xml ) { + var cache, outerCache, node, diff, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType; + + if ( parent ) { + + if ( simple ) { + while ( dir ) { + node = elem; + while ( (node = node[ dir ]) ) { + if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { + return false; + } + } + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + if ( forward && useCache ) { + outerCache = parent[ expando ] || (parent[ expando ] = {}); + cache = outerCache[ type ] || []; + nodeIndex = cache[0] === dirruns && cache[1]; + diff = cache[0] === dirruns && cache[2]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( (node = ++nodeIndex && node && node[ dir ] || + + (diff = nodeIndex = 0) || start.pop()) ) { + + if ( node.nodeType === 1 && ++diff && node === elem ) { + outerCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) { + diff = cache[1]; + + } else { + while ( (node = ++nodeIndex && node && node[ dir ] || + (diff = nodeIndex = 0) || start.pop()) ) { + + if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { + if ( useCache ) { + (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + if ( fn[ expando ] ) { + return fn( argument ); + } + + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction(function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf.call( seed, matched[i] ); + seed[ idx ] = !( matches[ idx ] = matched[i] ); + } + }) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + "not": markFunction(function( selector ) { + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction(function( seed, matches, context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + while ( i-- ) { + if ( (elem = unmatched[i]) ) { + seed[i] = !(matches[i] = elem); + } + } + }) : + function( elem, context, xml ) { + input[0] = elem; + matcher( input, null, xml, results ); + return !results.pop(); + }; + }), + + "has": markFunction(function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + }), + + "contains": markFunction(function( text ) { + return function( elem ) { + return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; + }; + }), + + "lang": markFunction( function( lang ) { + if ( !ridentifier.test(lang || "") ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( (elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); + return false; + }; + }), + + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + }, + + "enabled": function( elem ) { + return elem.disabled === false; + }, + + "disabled": function( elem ) { + return elem.disabled === true; + }, + + "checked": function( elem ) { + var nodeName = elem.nodeName.toLowerCase(); + return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + }, + + "selected": function( elem ) { + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + "empty": function( elem ) { + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos["empty"]( elem ); + }, + + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); + }, + + "first": createPositionalPseudo(function() { + return [ 0 ]; + }), + + "last": createPositionalPseudo(function( matchIndexes, length ) { + return [ length - 1 ]; + }), + + "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + }), + + "even": createPositionalPseudo(function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "odd": createPositionalPseudo(function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }) + } +}; + +Expr.pseudos["nth"] = Expr.pseudos["eq"]; + +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +function tokenize( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( match ) { + soFar = soFar.slice( match[0].length ) || soFar; + } + groups.push( (tokens = []) ); + } + + matched = false; + + if ( (match = rcombinators.exec( soFar )) ) { + matched = match.shift(); + tokens.push({ + value: matched, + type: match[0].replace( rtrim, " " ) + }); + soFar = soFar.slice( matched.length ); + } + + for ( type in Expr.filter ) { + if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || + (match = preFilters[ type ]( match ))) ) { + matched = match.shift(); + tokens.push({ + value: matched, + type: type, + matches: match + }); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + tokenCache( selector, groups ).slice( 0 ); +} + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[i].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + checkNonElements = base && dir === "parentNode", + doneName = done++; + + return combinator.first ? + function( elem, context, xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + } : + + function( elem, context, xml ) { + var oldCache, outerCache, + newCache = [ dirruns, doneName ]; + + if ( xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || (elem[ expando ] = {}); + if ( (oldCache = outerCache[ dir ]) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + return (newCache[ 2 ] = oldCache[ 2 ]); + } else { + outerCache[ dir ] = newCache; + + if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { + return true; + } + } + } + } + } + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[i]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[0]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[i], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( (elem = unmatched[i]) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction(function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + [] : + + results : + matcherIn; + + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + i = temp.length; + while ( i-- ) { + if ( (elem = temp[i]) ) { + matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) ) { + temp.push( (matcherIn[i] = elem) ); + } + } + postFinder( null, (matcherOut = []), temp, xml ); + } + + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) && + (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) { + + seed[temp] = !(results[temp] = elem); + } + } + } + + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + }); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[0].type ], + implicitRelative = leadingRelative || Expr.relative[" "], + i = leadingRelative ? 1 : 0, + + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf.call( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + return ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + (checkContext = context).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + } ]; + + for ( ; i < len; i++ ) { + if ( (matcher = Expr.relative[ tokens[i].type ]) ) { + matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; + } else { + matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + + if ( matcher[ expando ] ) { + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[j].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), + dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), + len = elems.length; + + if ( outermost ) { + outermostContext = context !== document && context; + } + + for ( ; i !== len && (elem = elems[i]) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + while ( (matcher = elementMatchers[j++]) ) { + if ( matcher( elem, context, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + if ( bySet ) { + if ( (elem = !matcher && elem) ) { + matchedCount--; + } + + if ( seed ) { + unmatched.push( elem ); + } + } + } + + matchedCount += i; + if ( bySet && i !== matchedCount ) { + j = 0; + while ( (matcher = setMatchers[j++]) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !(unmatched[i] || setMatched[i]) ) { + setMatched[i] = pop.call( results ); + } + } + } + + setMatched = condense( setMatched ); + } + + push.apply( results, setMatched ); + + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[i] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + + cached.selector = selector; + } + return cached; +}; + +/** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +select = Sizzle.select = function( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( (selector = compiled.selector || selector) ); + + results = results || []; + + if ( match.length === 1 ) { + + tokens = match[0] = match[0].slice( 0 ); + if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && + support.getById && context.nodeType === 9 && documentIsHTML && + Expr.relative[ tokens[1].type ] ) { + + context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; + if ( !context ) { + return results; + + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[i]; + + if ( Expr.relative[ (type = token.type) ] ) { + break; + } + if ( (find = Expr.find[ type ]) ) { + if ( (seed = find( + token.matches[0].replace( runescape, funescape ), + rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context + )) ) { + + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +}; + + +support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; + +support.detectDuplicates = !!hasDuplicate; + +setDocument(); + +support.sortDetached = assert(function( div1 ) { + return div1.compareDocumentPosition( document.createElement("div") ) & 1; +}); + +if ( !assert(function( div ) { + div.innerHTML = ""; + return div.firstChild.getAttribute("href") === "#" ; +}) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + }); +} + +if ( !support.attributes || !assert(function( div ) { + div.innerHTML = ""; + div.firstChild.setAttribute( "value", "" ); + return div.firstChild.getAttribute( "value" ) === ""; +}) ) { + addHandle( "value", function( elem, name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + }); +} + +if ( !assert(function( div ) { + return div.getAttribute("disabled") == null; +}) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + (val = elem.getAttributeNode( name )) && val.specified ? + val.value : + null; + } + }); +} + +if ( typeof define === "function" && define.amd ) { + define(function() { return Sizzle; }); +} else if ( typeof module !== "undefined" && module.exports ) { + module.exports = Sizzle; +} else { + window.Sizzle = Sizzle; +} + +})( window ); + +;(function(engine) { + var extendElements = Prototype.Selector.extendElements; + + function select(selector, scope) { + return extendElements(engine(selector, scope || document)); + } + + function match(element, selector) { + return engine.matches(selector, [element]).length == 1; + } + + Prototype.Selector.engine = engine; + Prototype.Selector.select = select; + Prototype.Selector.match = match; +})(Sizzle); + +window.Sizzle = Prototype._original_property; +delete Prototype._original_property; + +var Form = { + reset: function(form) { + form = $(form); + form.reset(); + return form; + }, + + serializeElements: function(elements, options) { + if (typeof options != 'object') options = { hash: !!options }; + else if (Object.isUndefined(options.hash)) options.hash = true; + var key, value, submitted = false, submit = options.submit, accumulator, initial; + + if (options.hash) { + initial = {}; + accumulator = function(result, key, value) { + if (key in result) { + if (!Object.isArray(result[key])) result[key] = [result[key]]; + result[key] = result[key].concat(value); + } else result[key] = value; + return result; + }; + } else { + initial = ''; + accumulator = function(result, key, values) { + if (!Object.isArray(values)) {values = [values];} + if (!values.length) {return result;} + var encodedKey = encodeURIComponent(key).gsub(/%20/, '+'); + return result + (result ? "&" : "") + values.map(function (value) { + value = value.gsub(/(\r)?\n/, '\r\n'); + value = encodeURIComponent(value); + value = value.gsub(/%20/, '+'); + return encodedKey + "=" + value; + }).join("&"); + }; + } + + return elements.inject(initial, function(result, element) { + if (!element.disabled && element.name) { + key = element.name; value = $(element).getValue(); + if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted && + submit !== false && (!submit || key == submit) && (submitted = true)))) { + result = accumulator(result, key, value); + } + } + return result; + }); + } +}; + +Form.Methods = { + serialize: function(form, options) { + return Form.serializeElements(Form.getElements(form), options); + }, + + + getElements: function(form) { + var elements = $(form).getElementsByTagName('*'); + var element, results = [], serializers = Form.Element.Serializers; + + for (var i = 0; element = elements[i]; i++) { + if (serializers[element.tagName.toLowerCase()]) + results.push(Element.extend(element)); + } + return results; + }, + + getInputs: function(form, typeName, name) { + form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) return $A(inputs).map(Element.extend); + + for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || (name && input.name != name)) + continue; + matchingInputs.push(Element.extend(input)); + } + + return matchingInputs; + }, + + disable: function(form) { + form = $(form); + Form.getElements(form).invoke('disable'); + return form; + }, + + enable: function(form) { + form = $(form); + Form.getElements(form).invoke('enable'); + return form; + }, + + findFirstElement: function(form) { + var elements = $(form).getElements().findAll(function(element) { + return 'hidden' != element.type && !element.disabled; + }); + var firstByIndex = elements.findAll(function(element) { + return element.hasAttribute('tabIndex') && element.tabIndex >= 0; + }).sortBy(function(element) { return element.tabIndex }).first(); + + return firstByIndex ? firstByIndex : elements.find(function(element) { + return /^(?:input|select|textarea)$/i.test(element.tagName); + }); + }, + + focusFirstElement: function(form) { + form = $(form); + var element = form.findFirstElement(); + if (element) element.activate(); + return form; + }, + + request: function(form, options) { + form = $(form), options = Object.clone(options || { }); + + var params = options.parameters, action = form.readAttribute('action') || ''; + if (action.blank()) action = window.location.href; + options.parameters = form.serialize(true); + + if (params) { + if (Object.isString(params)) params = params.toQueryParams(); + Object.extend(options.parameters, params); + } + + if (form.hasAttribute('method') && !options.method) + options.method = form.method; + + return new Ajax.Request(action, options); + } +}; + +/*--------------------------------------------------------------------------*/ + + +Form.Element = { + focus: function(element) { + $(element).focus(); + return element; + }, + + select: function(element) { + $(element).select(); + return element; + } +}; + +Form.Element.Methods = { + + serialize: function(element) { + element = $(element); + if (!element.disabled && element.name) { + var value = element.getValue(); + if (value != undefined) { + var pair = { }; + pair[element.name] = value; + return Object.toQueryString(pair); + } + } + return ''; + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + return Form.Element.Serializers[method](element); + }, + + setValue: function(element, value) { + element = $(element); + var method = element.tagName.toLowerCase(); + Form.Element.Serializers[method](element, value); + return element; + }, + + clear: function(element) { + $(element).value = ''; + return element; + }, + + present: function(element) { + return $(element).value != ''; + }, + + activate: function(element) { + element = $(element); + try { + element.focus(); + if (element.select && (element.tagName.toLowerCase() != 'input' || + !(/^(?:button|reset|submit)$/i.test(element.type)))) + element.select(); + } catch (e) { } + return element; + }, + + disable: function(element) { + element = $(element); + element.disabled = true; + return element; + }, + + enable: function(element) { + element = $(element); + element.disabled = false; + return element; + } +}; + +/*--------------------------------------------------------------------------*/ + +var Field = Form.Element; + +var $F = Form.Element.Methods.getValue; + +/*--------------------------------------------------------------------------*/ + +Form.Element.Serializers = (function() { + function input(element, value) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + return inputSelector(element, value); + default: + return valueSelector(element, value); + } + } + + function inputSelector(element, value) { + if (Object.isUndefined(value)) + return element.checked ? element.value : null; + else element.checked = !!value; + } + + function valueSelector(element, value) { + if (Object.isUndefined(value)) return element.value; + else element.value = value; + } + + function select(element, value) { + if (Object.isUndefined(value)) + return (element.type === 'select-one' ? selectOne : selectMany)(element); + + var opt, currentValue, single = !Object.isArray(value); + for (var i = 0, length = element.length; i < length; i++) { + opt = element.options[i]; + currentValue = this.optionValue(opt); + if (single) { + if (currentValue == value) { + opt.selected = true; + return; + } + } + else opt.selected = value.include(currentValue); + } + } + + function selectOne(element) { + var index = element.selectedIndex; + return index >= 0 ? optionValue(element.options[index]) : null; + } + + function selectMany(element) { + var values, length = element.length; + if (!length) return null; + + for (var i = 0, values = []; i < length; i++) { + var opt = element.options[i]; + if (opt.selected) values.push(optionValue(opt)); + } + return values; + } + + function optionValue(opt) { + return Element.hasAttribute(opt, 'value') ? opt.value : opt.text; + } + + return { + input: input, + inputSelector: inputSelector, + textarea: valueSelector, + select: select, + selectOne: selectOne, + selectMany: selectMany, + optionValue: optionValue, + button: valueSelector + }; +})(); + +/*--------------------------------------------------------------------------*/ + + +Abstract.TimedObserver = Class.create(PeriodicalExecuter, { + initialize: function($super, element, frequency, callback) { + $super(callback, frequency); + this.element = $(element); + this.lastValue = this.getValue(); + }, + + execute: function() { + var value = this.getValue(); + if (Object.isString(this.lastValue) && Object.isString(value) ? + this.lastValue != value : String(this.lastValue) != String(value)) { + this.callback(this.element, value); + this.lastValue = value; + } + } +}); + +Form.Element.Observer = Class.create(Abstract.TimedObserver, { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(Abstract.TimedObserver, { + getValue: function() { + return Form.serialize(this.element); + } +}); + +/*--------------------------------------------------------------------------*/ + +Abstract.EventObserver = Class.create({ + initialize: function(element, callback) { + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + if (this.element.tagName.toLowerCase() == 'form') + this.registerFormCallbacks(); + else + this.registerCallback(this.element); + }, + + onElementEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + }, + + registerFormCallbacks: function() { + Form.getElements(this.element).each(this.registerCallback, this); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; + default: + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } + } + } +}); + +Form.Element.EventObserver = Class.create(Abstract.EventObserver, { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(Abstract.EventObserver, { + getValue: function() { + return Form.serialize(this.element); + } +}); +(function(GLOBAL) { + var DIV = document.createElement('div'); + var docEl = document.documentElement; + var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl + && 'onmouseleave' in docEl; + + var Event = { + KEY_BACKSPACE: 8, + KEY_TAB: 9, + KEY_RETURN: 13, + KEY_ESC: 27, + KEY_LEFT: 37, + KEY_UP: 38, + KEY_RIGHT: 39, + KEY_DOWN: 40, + KEY_DELETE: 46, + KEY_HOME: 36, + KEY_END: 35, + KEY_PAGEUP: 33, + KEY_PAGEDOWN: 34, + KEY_INSERT: 45 + }; + + + var isIELegacyEvent = function(event) { return false; }; + + if (window.attachEvent) { + if (window.addEventListener) { + isIELegacyEvent = function(event) { + return !(event instanceof window.Event); + }; + } else { + isIELegacyEvent = function(event) { return true; }; + } + } + + var _isButton; + + function _isButtonForDOMEvents(event, code) { + return event.which ? (event.which === code + 1) : (event.button === code); + } + + var legacyButtonMap = { 0: 1, 1: 4, 2: 2 }; + function _isButtonForLegacyEvents(event, code) { + return event.button === legacyButtonMap[code]; + } + + function _isButtonForWebKit(event, code) { + switch (code) { + case 0: return event.which == 1 && !event.metaKey; + case 1: return event.which == 2 || (event.which == 1 && event.metaKey); + case 2: return event.which == 3; + default: return false; + } + } + + if (window.attachEvent) { + if (!window.addEventListener) { + _isButton = _isButtonForLegacyEvents; + } else { + _isButton = function(event, code) { + return isIELegacyEvent(event) ? _isButtonForLegacyEvents(event, code) : + _isButtonForDOMEvents(event, code); + } + } + } else if (Prototype.Browser.WebKit) { + _isButton = _isButtonForWebKit; + } else { + _isButton = _isButtonForDOMEvents; + } + + function isLeftClick(event) { return _isButton(event, 0) } + + function isMiddleClick(event) { return _isButton(event, 1) } + + function isRightClick(event) { return _isButton(event, 2) } + + function element(event) { + return Element.extend(_element(event)); + } + + function _element(event) { + event = Event.extend(event); + + var node = event.target, type = event.type, + currentTarget = event.currentTarget; + + if (currentTarget && currentTarget.tagName) { + if (type === 'load' || type === 'error' || + (type === 'click' && currentTarget.tagName.toLowerCase() === 'input' + && currentTarget.type === 'radio')) + node = currentTarget; + } + + return node.nodeType == Node.TEXT_NODE ? node.parentNode : node; + } + + function findElement(event, expression) { + var element = _element(event), selector = Prototype.Selector; + if (!expression) return Element.extend(element); + while (element) { + if (Object.isElement(element) && selector.match(element, expression)) + return Element.extend(element); + element = element.parentNode; + } + } + + function pointer(event) { + return { x: pointerX(event), y: pointerY(event) }; + } + + function pointerX(event) { + var docElement = document.documentElement, + body = document.body || { scrollLeft: 0 }; + + return event.pageX || (event.clientX + + (docElement.scrollLeft || body.scrollLeft) - + (docElement.clientLeft || 0)); + } + + function pointerY(event) { + var docElement = document.documentElement, + body = document.body || { scrollTop: 0 }; + + return event.pageY || (event.clientY + + (docElement.scrollTop || body.scrollTop) - + (docElement.clientTop || 0)); + } + + + function stop(event) { + Event.extend(event); + event.preventDefault(); + event.stopPropagation(); + + event.stopped = true; + } + + + Event.Methods = { + isLeftClick: isLeftClick, + isMiddleClick: isMiddleClick, + isRightClick: isRightClick, + + element: element, + findElement: findElement, + + pointer: pointer, + pointerX: pointerX, + pointerY: pointerY, + + stop: stop + }; + + var methods = Object.keys(Event.Methods).inject({ }, function(m, name) { + m[name] = Event.Methods[name].methodize(); + return m; + }); + + if (window.attachEvent) { + function _relatedTarget(event) { + var element; + switch (event.type) { + case 'mouseover': + case 'mouseenter': + element = event.fromElement; + break; + case 'mouseout': + case 'mouseleave': + element = event.toElement; + break; + default: + return null; + } + return Element.extend(element); + } + + var additionalMethods = { + stopPropagation: function() { this.cancelBubble = true }, + preventDefault: function() { this.returnValue = false }, + inspect: function() { return '[object Event]' } + }; + + Event.extend = function(event, element) { + if (!event) return false; + + if (!isIELegacyEvent(event)) return event; + + if (event._extendedByPrototype) return event; + event._extendedByPrototype = Prototype.emptyFunction; + + var pointer = Event.pointer(event); + + Object.extend(event, { + target: event.srcElement || element, + relatedTarget: _relatedTarget(event), + pageX: pointer.x, + pageY: pointer.y + }); + + Object.extend(event, methods); + Object.extend(event, additionalMethods); + + return event; + }; + } else { + Event.extend = Prototype.K; + } + + if (window.addEventListener) { + Event.prototype = window.Event.prototype || document.createEvent('HTMLEvents').__proto__; + Object.extend(Event.prototype, methods); + } + + var EVENT_TRANSLATIONS = { + mouseenter: 'mouseover', + mouseleave: 'mouseout' + }; + + function getDOMEventName(eventName) { + return EVENT_TRANSLATIONS[eventName] || eventName; + } + + if (MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) + getDOMEventName = Prototype.K; + + function getUniqueElementID(element) { + if (element === window) return 0; + + if (typeof element._prototypeUID === 'undefined') + element._prototypeUID = Element.Storage.UID++; + return element._prototypeUID; + } + + function getUniqueElementID_IE(element) { + if (element === window) return 0; + if (element == document) return 1; + return element.uniqueID; + } + + if ('uniqueID' in DIV) + getUniqueElementID = getUniqueElementID_IE; + + function isCustomEvent(eventName) { + return eventName.include(':'); + } + + Event._isCustomEvent = isCustomEvent; + + function getRegistryForElement(element, uid) { + var CACHE = GLOBAL.Event.cache; + if (Object.isUndefined(uid)) + uid = getUniqueElementID(element); + if (!CACHE[uid]) CACHE[uid] = { element: element }; + return CACHE[uid]; + } + + function destroyRegistryForElement(element, uid) { + if (Object.isUndefined(uid)) + uid = getUniqueElementID(element); + delete GLOBAL.Event.cache[uid]; + } + + + function register(element, eventName, handler) { + var registry = getRegistryForElement(element); + if (!registry[eventName]) registry[eventName] = []; + var entries = registry[eventName]; + + var i = entries.length; + while (i--) + if (entries[i].handler === handler) return null; + + var uid = getUniqueElementID(element); + var responder = GLOBAL.Event._createResponder(uid, eventName, handler); + var entry = { + responder: responder, + handler: handler + }; + + entries.push(entry); + return entry; + } + + function unregister(element, eventName, handler) { + var registry = getRegistryForElement(element); + var entries = registry[eventName]; + if (!entries) return; + + var i = entries.length, entry; + while (i--) { + if (entries[i].handler === handler) { + entry = entries[i]; + break; + } + } + + if (!entry) return; + + var index = entries.indexOf(entry); + entries.splice(index, 1); + + return entry; + } + + + function observe(element, eventName, handler) { + element = $(element); + var entry = register(element, eventName, handler); + + if (entry === null) return element; + + var responder = entry.responder; + if (isCustomEvent(eventName)) + observeCustomEvent(element, eventName, responder); + else + observeStandardEvent(element, eventName, responder); + + return element; + } + + function observeStandardEvent(element, eventName, responder) { + var actualEventName = getDOMEventName(eventName); + if (element.addEventListener) { + element.addEventListener(actualEventName, responder, false); + } else { + element.attachEvent('on' + actualEventName, responder); + } + } + + function observeCustomEvent(element, eventName, responder) { + if (element.addEventListener) { + element.addEventListener('dataavailable', responder, false); + } else { + element.attachEvent('ondataavailable', responder); + element.attachEvent('onlosecapture', responder); + } + } + + function stopObserving(element, eventName, handler) { + element = $(element); + var handlerGiven = !Object.isUndefined(handler), + eventNameGiven = !Object.isUndefined(eventName); + + if (!eventNameGiven && !handlerGiven) { + stopObservingElement(element); + return element; + } + + if (!handlerGiven) { + stopObservingEventName(element, eventName); + return element; + } + + var entry = unregister(element, eventName, handler); + + if (!entry) return element; + removeEvent(element, eventName, entry.responder); + return element; + } + + function stopObservingStandardEvent(element, eventName, responder) { + var actualEventName = getDOMEventName(eventName); + if (element.removeEventListener) { + element.removeEventListener(actualEventName, responder, false); + } else { + element.detachEvent('on' + actualEventName, responder); + } + } + + function stopObservingCustomEvent(element, eventName, responder) { + if (element.removeEventListener) { + element.removeEventListener('dataavailable', responder, false); + } else { + element.detachEvent('ondataavailable', responder); + element.detachEvent('onlosecapture', responder); + } + } + + + + function stopObservingElement(element) { + var uid = getUniqueElementID(element), registry = GLOBAL.Event.cache[uid]; + if (!registry) return; + + destroyRegistryForElement(element, uid); + + var entries, i; + for (var eventName in registry) { + if (eventName === 'element') continue; + + entries = registry[eventName]; + i = entries.length; + while (i--) + removeEvent(element, eventName, entries[i].responder); + } + } + + function stopObservingEventName(element, eventName) { + var registry = getRegistryForElement(element); + var entries = registry[eventName]; + if (!entries) return; + delete registry[eventName]; + + var i = entries.length; + while (i--) + removeEvent(element, eventName, entries[i].responder); + } + + + function removeEvent(element, eventName, handler) { + if (isCustomEvent(eventName)) + stopObservingCustomEvent(element, eventName, handler); + else + stopObservingStandardEvent(element, eventName, handler); + } + + + + function getFireTarget(element) { + if (element !== document) return element; + if (document.createEvent && !element.dispatchEvent) + return document.documentElement; + return element; + } + + function fire(element, eventName, memo, bubble) { + element = getFireTarget($(element)); + if (Object.isUndefined(bubble)) bubble = true; + memo = memo || {}; + + var event = fireEvent(element, eventName, memo, bubble); + return Event.extend(event); + } + + function fireEvent_DOM(element, eventName, memo, bubble) { + var event = document.createEvent('HTMLEvents'); + event.initEvent('dataavailable', bubble, true); + + event.eventName = eventName; + event.memo = memo; + + element.dispatchEvent(event); + return event; + } + + function fireEvent_IE(element, eventName, memo, bubble) { + var event = document.createEventObject(); + event.eventType = bubble ? 'ondataavailable' : 'onlosecapture'; + + event.eventName = eventName; + event.memo = memo; + + element.fireEvent(event.eventType, event); + return event; + } + + var fireEvent = document.createEvent ? fireEvent_DOM : fireEvent_IE; + + + + Event.Handler = Class.create({ + initialize: function(element, eventName, selector, callback) { + this.element = $(element); + this.eventName = eventName; + this.selector = selector; + this.callback = callback; + this.handler = this.handleEvent.bind(this); + }, + + + start: function() { + Event.observe(this.element, this.eventName, this.handler); + return this; + }, + + stop: function() { + Event.stopObserving(this.element, this.eventName, this.handler); + return this; + }, + + handleEvent: function(event) { + var element = Event.findElement(event, this.selector); + if (element) this.callback.call(this.element, event, element); + } + }); + + function on(element, eventName, selector, callback) { + element = $(element); + if (Object.isFunction(selector) && Object.isUndefined(callback)) { + callback = selector, selector = null; + } + + return new Event.Handler(element, eventName, selector, callback).start(); + } + + Object.extend(Event, Event.Methods); + + Object.extend(Event, { + fire: fire, + observe: observe, + stopObserving: stopObserving, + on: on + }); + + Element.addMethods({ + fire: fire, + + observe: observe, + + stopObserving: stopObserving, + + on: on + }); + + Object.extend(document, { + fire: fire.methodize(), + + observe: observe.methodize(), + + stopObserving: stopObserving.methodize(), + + on: on.methodize(), + + loaded: false + }); + + if (GLOBAL.Event) Object.extend(window.Event, Event); + else GLOBAL.Event = Event; + + GLOBAL.Event.cache = {}; + + function destroyCache_IE() { + GLOBAL.Event.cache = null; + } + + if (window.attachEvent) + window.attachEvent('onunload', destroyCache_IE); + + DIV = null; + docEl = null; +})(this); + +(function(GLOBAL) { + /* Code for creating leak-free event responders is based on work by + John-David Dalton. */ + + var docEl = document.documentElement; + var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl + && 'onmouseleave' in docEl; + + function isSimulatedMouseEnterLeaveEvent(eventName) { + return !MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED && + (eventName === 'mouseenter' || eventName === 'mouseleave'); + } + + function createResponder(uid, eventName, handler) { + if (Event._isCustomEvent(eventName)) + return createResponderForCustomEvent(uid, eventName, handler); + if (isSimulatedMouseEnterLeaveEvent(eventName)) + return createMouseEnterLeaveResponder(uid, eventName, handler); + + return function(event) { + if (!Event.cache) return; + + var element = Event.cache[uid].element; + Event.extend(event, element); + handler.call(element, event); + }; + } + + function createResponderForCustomEvent(uid, eventName, handler) { + return function(event) { + var element = Event.cache[uid].element; + + if (Object.isUndefined(event.eventName)) + return false; + + if (event.eventName !== eventName) + return false; + + Event.extend(event, element); + handler.call(element, event); + }; + } + + function createMouseEnterLeaveResponder(uid, eventName, handler) { + return function(event) { + var element = Event.cache[uid].element; + + Event.extend(event, element); + var parent = event.relatedTarget; + + while (parent && parent !== element) { + try { parent = parent.parentNode; } + catch(e) { parent = element; } + } + + if (parent === element) return; + handler.call(element, event); + } + } + + GLOBAL.Event._createResponder = createResponder; + docEl = null; +})(this); + +(function(GLOBAL) { + /* Support for the DOMContentLoaded event is based on work by Dan Webb, + Matthias Miller, Dean Edwards, John Resig, and Diego Perini. */ + + var TIMER; + + function fireContentLoadedEvent() { + if (document.loaded) return; + if (TIMER) window.clearTimeout(TIMER); + document.loaded = true; + document.fire('dom:loaded'); + } + + function checkReadyState() { + if (document.readyState === 'complete') { + document.detachEvent('onreadystatechange', checkReadyState); + fireContentLoadedEvent(); + } + } + + function pollDoScroll() { + try { + document.documentElement.doScroll('left'); + } catch (e) { + TIMER = pollDoScroll.defer(); + return; + } + + fireContentLoadedEvent(); + } + + + if (document.readyState === 'complete') { + fireContentLoadedEvent(); + return; + } + + if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false); + } else { + document.attachEvent('onreadystatechange', checkReadyState); + if (window == top) TIMER = pollDoScroll.defer(); + } + + Event.observe(window, 'load', fireContentLoadedEvent); +})(this); + + +Element.addMethods(); +/*------------------------------- DEPRECATED -------------------------------*/ + +Hash.toQueryString = Object.toQueryString; + +var Toggle = { display: Element.toggle }; + +Element.Methods.childOf = Element.Methods.descendantOf; + +var Insertion = { + Before: function(element, content) { + return Element.insert(element, {before:content}); + }, + + Top: function(element, content) { + return Element.insert(element, {top:content}); + }, + + Bottom: function(element, content) { + return Element.insert(element, {bottom:content}); + }, + + After: function(element, content) { + return Element.insert(element, {after:content}); + } +}; + +var $continue = new Error('"throw $continue" is deprecated, use "return" instead'); + +var Position = { + includeScrollOffsets: false, + + prepare: function() { + this.deltaX = window.pageXOffset + || document.documentElement.scrollLeft + || document.body.scrollLeft + || 0; + this.deltaY = window.pageYOffset + || document.documentElement.scrollTop + || document.body.scrollTop + || 0; + }, + + within: function(element, x, y) { + if (this.includeScrollOffsets) + return this.withinIncludingScrolloffsets(element, x, y); + this.xcomp = x; + this.ycomp = y; + this.offset = Element.cumulativeOffset(element); + + return (y >= this.offset[1] && + y < this.offset[1] + element.offsetHeight && + x >= this.offset[0] && + x < this.offset[0] + element.offsetWidth); + }, + + withinIncludingScrolloffsets: function(element, x, y) { + var offsetcache = Element.cumulativeScrollOffset(element); + + this.xcomp = x + offsetcache[0] - this.deltaX; + this.ycomp = y + offsetcache[1] - this.deltaY; + this.offset = Element.cumulativeOffset(element); + + return (this.ycomp >= this.offset[1] && + this.ycomp < this.offset[1] + element.offsetHeight && + this.xcomp >= this.offset[0] && + this.xcomp < this.offset[0] + element.offsetWidth); + }, + + overlap: function(mode, element) { + if (!mode) return 0; + if (mode == 'vertical') + return ((this.offset[1] + element.offsetHeight) - this.ycomp) / + element.offsetHeight; + if (mode == 'horizontal') + return ((this.offset[0] + element.offsetWidth) - this.xcomp) / + element.offsetWidth; + }, + + + cumulativeOffset: Element.Methods.cumulativeOffset, + + positionedOffset: Element.Methods.positionedOffset, + + absolutize: function(element) { + Position.prepare(); + return Element.absolutize(element); + }, + + relativize: function(element) { + Position.prepare(); + return Element.relativize(element); + }, + + realOffset: Element.Methods.cumulativeScrollOffset, + + offsetParent: Element.Methods.getOffsetParent, + + page: Element.Methods.viewportOffset, + + clone: function(source, target, options) { + options = options || { }; + return Element.clonePosition(target, source, options); + } +}; + +/*--------------------------------------------------------------------------*/ + +if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){ + function iter(name) { + return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]"; + } + + instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ? + function(element, className) { + className = className.toString().strip(); + var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className); + return cond ? document._getElementsByXPath('.//*' + cond, element) : []; + } : function(element, className) { + className = className.toString().strip(); + var elements = [], classNames = (/\s/.test(className) ? $w(className) : null); + if (!classNames && !className) return elements; + + var nodes = $(element).getElementsByTagName('*'); + className = ' ' + className + ' '; + + for (var i = 0, child, cn; child = nodes[i]; i++) { + if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) || + (classNames && classNames.all(function(name) { + return !name.toString().blank() && cn.include(' ' + name + ' '); + })))) + elements.push(Element.extend(child)); + } + return elements; + }; + + return function(className, parentElement) { + return $(parentElement || document.body).getElementsByClassName(className); + }; +}(Element.Methods); + +/*--------------------------------------------------------------------------*/ + +Element.ClassNames = Class.create(); +Element.ClassNames.prototype = { + initialize: function(element) { + this.element = $(element); + }, + + _each: function(iterator, context) { + this.element.className.split(/\s+/).select(function(name) { + return name.length > 0; + })._each(iterator, context); + }, + + set: function(className) { + this.element.className = className; + }, + + add: function(classNameToAdd) { + if (this.include(classNameToAdd)) return; + this.set($A(this).concat(classNameToAdd).join(' ')); + }, + + remove: function(classNameToRemove) { + if (!this.include(classNameToRemove)) return; + this.set($A(this).without(classNameToRemove).join(' ')); + }, + + toString: function() { + return $A(this).join(' '); + } +}; + +Object.extend(Element.ClassNames.prototype, Enumerable); + +/*--------------------------------------------------------------------------*/ + +(function() { + window.Selector = Class.create({ + initialize: function(expression) { + this.expression = expression.strip(); + }, + + findElements: function(rootElement) { + return Prototype.Selector.select(this.expression, rootElement); + }, + + match: function(element) { + return Prototype.Selector.match(element, this.expression); + }, + + toString: function() { + return this.expression; + }, + + inspect: function() { + return "#"; + } + }); + + Object.extend(Selector, { + matchElements: function(elements, expression) { + var match = Prototype.Selector.match, + results = []; + + for (var i = 0, length = elements.length; i < length; i++) { + var element = elements[i]; + if (match(element, expression)) { + results.push(Element.extend(element)); + } + } + return results; + }, + + findElement: function(elements, expression, index) { + index = index || 0; + var matchIndex = 0, element; + for (var i = 0, length = elements.length; i < length; i++) { + element = elements[i]; + if (Prototype.Selector.match(element, expression) && index === matchIndex++) { + return Element.extend(element); + } + } + }, + + findChildElements: function(element, expressions) { + var selector = expressions.toArray().join(', '); + return Prototype.Selector.select(selector, element || document); + } + }); +})(); diff --git a/src/html/ReqMgr/javascript/utils.js b/src/html/ReqMgr/javascript/utils.js index af9d76e6cc..4452f88271 100644 --- a/src/html/ReqMgr/javascript/utils.js +++ b/src/html/ReqMgr/javascript/utils.js @@ -171,4 +171,88 @@ var utils = } -} // utils \ No newline at end of file +} // utils + +/* + * ReqMgr specific utilities + * Author: Valentin Kuznetsov + */ +function getTagValue(tag) +{ + return document.getElementById(tag).value; +} +function updateTag(tag, val) { + var id = document.getElementById(tag); + if (id) { + id.value=val; + } +} +function ClearTag(tag) { + var id=document.getElementById(tag); + if (id) { + id.innerHTML=""; + } +} +function HideTag(tag) { + var id=document.getElementById(tag); + if (id) { + id.className="hide"; + } +} +function ShowTag(tag) { + var id=document.getElementById(tag); + if (id) { + id.className="show"; + } +} +function FlipTag(tag) { + var id=document.getElementById(tag); + if (id) { + if (id.className == "show") { + id.className="hide"; + } else { + id.className="show"; + } + } +} +function ToggleTag(tag, link_tag) { + var id=document.getElementById(tag); + if (id) { + if (id.className == "show") { + id.className="hide"; + } else { + id.className="show"; + } + } + var lid=document.getElementById(link_tag); + if (lid) { + if (lid.innerHTML == "show") { + lid.innerHTML="hide"; + } else { + lid.innerHTML="show"; + } + } +} +function load(url) { + window.location.href=url; +} +function reload() { + load(window.location.href); +} +// Select all checkboxes +function CheckAll(bx) { + var cbs = document.getElementsByTagName('input'); + for(var i=0; i < cbs.length; i++) { + if(cbs[i].type == 'checkbox') { + cbs[i].checked = bx.checked; + } + } +} +// change menu items +function ChangeMenuItem(cls, tag) { + var items = document.getElementsByClassName(cls); + for (var i = 0; i < items.length; i++ ) { + items[i].className=cls; + } + tag.parentNode.className = cls+" active underline" +} diff --git a/src/html/ReqMgr/style/kube.min.css b/src/html/ReqMgr/style/kube.min.css new file mode 100644 index 0000000000..57e8ad4883 --- /dev/null +++ b/src/html/ReqMgr/style/kube.min.css @@ -0,0 +1 @@ +*,*:before,*:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}html,body,div,span,object,iframe,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video,h1,h2,h3,h4,h5,h6{margin:0;padding:0;border:0;outline:0;font-size:100%;vertical-align:baseline;background:transparent}a:active,a:hover{outline:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,embed,object,iframe,audio,video,canvas,progress,meter,output,textarea{display:block}audio:not([controls]){display:none;height:0}blockquote,q{quotes:none}blockquote p:before,blockquote p:after,q:before,q:after{content:'';content:none}table{border-collapse:collapse;border-spacing:0}caption,th,td{text-align:left;vertical-align:top}thead th,thead td{font-weight:bold;vertical-align:bottom}a img,th img,td img{vertical-align:top}button,input,select,textarea{margin:0}textarea{overflow:auto;vertical-align:top;resize:vertical}button{width:auto;overflow:visible}input[type="reset"],input[type="submit"],input[type="file"],input[type="radio"],input[type="checkbox"],select,button{cursor:pointer}input[type="radio"],input[type="checkbox"]{font-size:110%;position:relative;top:-1px;margin-right:3px}input[type="search"]{-webkit-appearance:textfield}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}hr{display:block;height:1px;border:0;border-top:1px solid #ddd}img,video,audio,embed,object{max-width:100%}img,video,embed,object{height:auto}embed,object{height:100%}img{vertical-align:middle;-ms-interpolation-mode:bicubic}body{font-family:"Helvetica Neue",Helvetica,Tahoma,sans-serif;font-size:.9375em;line-height:1.65em;background:#fff;color:#222}a{color:#134da5}a:focus,a:hover{color:#de2c3b}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:"Helvetica Neue",Helvetica,Tahoma,sans-serif;font-weight:bold;color:#000;text-rendering:optimizeLegibility;margin:0 0 .5em 0}h1,.h1,h2,.h2{line-height:1.1}h3,.h3,h4,.h4{line-height:1.3}h1,.h1{font-size:2.25em}h2,.h2{font-size:1.5em;margin-bottom:.7em}h3,.h3{font-size:1.3125em}h4,.h4{font-size:1.125em}h5,.h5{font-size:1em}h6,.h6{font-size:.75em;text-transform:uppercase}.lead{font-size:1.3125em;line-height:1.5;margin-bottom:1.0999999999999999em}p,ul,ol,dl,dd,dt,blockquote,td,th{line-height:1.65em}ul,ol,ul ul,ol ol,ul ol,ol ul{margin:0 0 0 3.3em}ul li,ol li{text-align:left}ol ol li{list-style-type:lower-alpha}ol ol ol li{list-style-type:lower-roman}p,ul,ol,dl,blockquote,hr,pre,table,form,fieldset,figure,address{margin-bottom:1.65em}blockquote{position:relative;font-style:italic;font-size:1.125em;margin-left:2.4749999999999996em;padding-left:1.65em;border-left:2px solid #ddd}blockquote p{margin-bottom:.5em}blockquote small,cite{color:rgba(0,0,0,0.4);font-style:italic}small,blockquote cite{font-size:12.75px;line-height:1}address{font-style:normal}dl dt{font-weight:bold}dd{margin-left:1.65em}s,del{text-decoration:line-through}abbr[title],dfn[title]{border-bottom:1px dotted #0f0f0f;cursor:help}strong,b{font-weight:bold}em,i{font-style:italic}sub,sup{font-size:11.25px;line-height:0;position:relative}sup{top:-0.5em}sub{bottom:-0.25em}figcaption{margin:.3em 0;font-size:12.75px;font-style:italic}ins,u{text-decoration:underline}mark{background-color:#ffc800;color:#0f0f0f;text-decoration:none}pre,code,kbd,samp,var,output{font-size:90%;font-style:normal;font-family:Menlo,Monaco,Consolas,"Courier New",monospace}pre{margin-top:1.65em;font-size:100%;line-height:1.5;color:#222;overflow:auto}code,samp,kbd{padding:3px 6px 2px 6px;display:inline-block;line-height:1;border-radius:2px}code{background:#f4f4f4;border:1px solid #d4d4d4}pre code{font-size:100%;border:0;padding:0;background:0;line-height:1.65em}var{color:rgba(0,0,0,0.5)}samp{background:#d3e3fb;border:1px solid #b2cef8}kbd{background:#0f0f0f;color:rgba(255,255,255,0.85);white-space:nowrap}button:active,button:focus{outline:0}textarea,select{font-family:"Helvetica Neue",Helvetica,Tahoma,sans-serif;font-size:1em;box-shadow:none}textarea,select[multiple],select[multiple="multiple"]{padding:.3em .35em;line-height:1.35em;width:100%}input[type="text"],input[type="password"],input[type="email"],input[type="url"],input[type="phone"],input[type="tel"],input[type="number"],input[type="datetime"],input[type="date"],input[type="month"],input[type="color"],input[type="time"],input[type="search"],input[type="datetime-local"]{font-family:"Helvetica Neue",Helvetica,Tahoma,sans-serif;font-size:1em;box-shadow:none;padding:.3em .35em;line-height:1.65em;border-radius:0;outline:0}select[multiple],select[multiple="multiple"],textarea,input[type="text"],input[type="password"],input[type="email"],input[type="url"],input[type="phone"],input[type="tel"],input[type="number"],input[type="datetime"],input[type="date"],input[type="month"],input[type="color"],input[type="time"],input[type="search"],input[type="datetime-local"]{background:#fff;border:1px solid #ccc;position:relative;z-index:2;-webkit-appearance:none}textarea[disabled],input[type="text"][disabled],input[type="password"][disabled],input[type="email"][disabled],input[type="url"][disabled],input[type="phone"][disabled],input[type="tel"][disabled],input[type="number"][disabled],input[type="datetime"][disabled],input[type="date"][disabled],input[type="month"][disabled],input[type="color"][disabled],input[type="time"][disabled],input[type="search"][disabled],input[type="datetime-local"][disabled]{resize:none;color:rgba(0,0,0,0.5)}select[disabled],input[type="checkbox"][disabled],input[type="radio"][disabled]{cursor:default}input::-moz-focus-inner,button::-moz-focus-inner{border:0;padding:0}input[type="range"]{position:relative;top:3px}select{margin-bottom:0!important}fieldset{padding:1.65em;margin-bottom:1.65em;border:1px solid #ccc}fieldset *:last-child{margin-bottom:0!important}legend{font-weight:bold;padding:0 1em;margin-left:-1em}table{max-width:100%;width:100%;empty-cells:show}table caption{text-transform:uppercase;padding:0 1.0999999999999999em;color:rgba(0,0,0,0.4);font-size:12.75px}table th,table td{border-bottom:1px solid #eee;padding:.825em 1.0999999999999999em}table tfoot th,table tfoot td{color:rgba(0,0,0,0.4)}.units-container:after,.units-row:after{content:"";display:table;clear:both}.units-container:after,.units-row:after{content:"";display:table;clear:both}.units-container{padding-top:1px;margin-top:-1px}.units-row{margin-bottom:1.65em}.width-100,.unit-100{width:100%}.width-90,.unit-90{width:90%}.width-80,.unit-80{width:80%}.width-75,.unit-75{width:75%}.width-70,.unit-70{width:70%}.width-66,.unit-66{width:66.6%}.width-65,.unit-65{width:65%}.width-60,.unit-60{width:60%}.width-50,.unit-50{width:50%}.width-40,.unit-40{width:40%}.width-35,.unit-35{width:35%}.width-33,.unit-33{width:33.3%}.width-30,.unit-30{width:30%}.width-25,.unit-25{width:25%}.width-20,.unit-20{width:20%}.width-10,.unit-10{width:10%}.units-row .unit-90,.units-row .unit-80,.units-row .unit-75,.units-row .unit-70,.units-row .unit-66,.units-row .unit-65,.units-row .unit-60,.units-row .unit-50,.units-row .unit-40,.units-row .unit-35,.units-row .unit-33,.units-row .unit-30,.units-row .unit-25,.units-row .unit-20,.units-row .unit-10{float:left;margin-left:3%}.units-row .unit-90:first-child,.units-row .unit-80:first-child,.units-row .unit-75:first-child,.units-row .unit-70:first-child,.units-row .unit-66:first-child,.units-row .unit-65:first-child,.units-row .unit-60:first-child,.units-row .unit-50:first-child,.units-row .unit-40:first-child,.units-row .unit-35:first-child,.units-row .unit-33:first-child,.units-row .unit-30:first-child,.units-row .unit-25:first-child,.units-row .unit-20:first-child,.units-row .unit-10:first-child{margin-left:0}.units-row .unit-90{width:89.7%}.units-row .unit-80{width:79.4%}.units-row .unit-75{width:74.25%}.units-row .unit-70{width:69.1%}.units-row .unit-66{width:65.66666666666666%}.units-row .unit-65{width:65.66666666666666%}.units-row .unit-60{width:58.800000000000004%}.units-row .unit-50{width:48.5%}.units-row .unit-40{width:38.2%}.units-row .unit-35{width:31.333333333333332%}.units-row .unit-33{width:31.333333333333332%}.units-row .unit-30{width:27.9%}.units-row .unit-25{width:22.75%}.units-row .unit-20{width:17.6%}.units-row .unit-10{width:7.3%}.unit-push-90,.unit-push-80,.unit-push-75,.unit-push-70,.unit-push-66,.unit-push-65,.unit-push-60,.unit-push-50,.unit-push-40,.unit-push-35,.unit-push-33,.unit-push-30,.unit-push-25,.unit-push-20,.unit-push-10{position:relative}.unit-push-90{left:92.7%}.unit-push-80{left:82.4%}.unit-push-75{left:77.25%}.unit-push-70{left:72.1%}.unit-push-66{left:68.66666666666666%}.unit-push-65{left:68.66666666666666%}.unit-push-60{left:61.800000000000004%}.unit-push-50{left:51.5%}.unit-push-40{left:41.2%}.unit-push-35{left:34.33333333333333%}.unit-push-33{left:34.33333333333333%}.unit-push-30{left:30.9%}.unit-push-25{left:25.75%}.unit-push-20{left:20.6%}.unit-push-10{left:10.3%}.units-row .unit-push-right{float:right}.units-row .unit-role-right{margin-left:3%;float:right}.units-row .unit-role-left{margin-left:0}.centered,.unit-centered{float:none!important;margin:0 auto!important}.unit-padding{padding:1.65em}.units-padding .unit-100,.units-padding .unit-90,.units-padding .unit-80,.units-padding .unit-75,.units-padding .unit-70,.units-padding .unit-66,.units-padding .unit-65,.units-padding .unit-60,.units-padding .unit-50,.units-padding .unit-40,.units-padding .unit-35,.units-padding .unit-33,.units-padding .unit-30,.units-padding .unit-25,.units-padding .unit-20,.units-padding .unit-10{padding:1.65em}.units-split .unit-90,.units-split .unit-80,.units-split .unit-75,.units-split .unit-70,.units-split .unit-66,.units-split .unit-65,.units-split .unit-60,.units-split .unit-50,.units-split .unit-40,.units-split .unit-35,.units-split .unit-33,.units-split .unit-30,.units-split .unit-25,.units-split .unit-20,.units-split .unit-10{margin-left:0}.units-split .unit-90{width:90%}.units-split .unit-80{width:80%}.units-split .unit-75{width:75%}.units-split .unit-70{width:70%}.units-split .unit-66{width:66.6%}.units-split .unit-65{width:65%}.units-split .unit-60{width:60%}.units-split .unit-50{width:50%}.units-split .unit-40{width:40%}.units-split .unit-35{width:35%}.units-split .unit-33{width:33.3%}.units-split .unit-30{width:30%}.units-split .unit-25{width:25%}.units-split .unit-20{width:20%}.units-split .unit-10{width:10%}.blocks-2,.blocks-3,.blocks-4,.blocks-5,.blocks-6{padding-left:0;list-style:none;margin-left:-3%}.blocks-2:after,.blocks-3:after,.blocks-4:after,.blocks-5:after,.blocks-6:after{content:"";display:table;clear:both}.blocks-2:after,.blocks-3:after,.blocks-4:after,.blocks-5:after,.blocks-6:after{content:"";display:table;clear:both}.blocks-2>li,.blocks-3>li,.blocks-4>li,.blocks-5>li,.blocks-6>li{height:auto;float:left;margin-bottom:1.65em;margin-left:3%}.blocks-2>li ul,.blocks-3>li ul,.blocks-4>li ul,.blocks-5>li ul,.blocks-6>li ul{list-style-type:disc}.blocks-2>li ul ul,.blocks-3>li ul ul,.blocks-4>li ul ul,.blocks-5>li ul ul,.blocks-6>li ul ul{list-style-type:circle}.blocks-2>li li,.blocks-3>li li,.blocks-4>li li,.blocks-5>li li,.blocks-6>li li{float:none;margin:0}.blocks-2>li{width:47%}.blocks-3>li{width:30.333333333333332%}.blocks-4>li{width:22%}.blocks-5>li{width:17%}.blocks-6>li{width:13.666666666666666%}.block-first{clear:both}table.table-bordered td,table.table-bordered th{border:1px solid #eee}table.table-simple td,table.table-simple th,table.table-simple caption{border:0;padding-left:0}table.table-flat td,table.table-flat th,table.table-flat caption{border:0;padding:0}table.table-stroked td,table.table-stroked th{border-bottom:1px solid #eee}table.table-stripped tbody tr:nth-child(odd) td{background:#f8f8f8}table.table-hovered tbody tr:hover td{background-color:#f4f4f4}.table-container{width:100%;overflow:auto;margin-bottom:1.65em}.table-container table{margin-bottom:0}.table-container::-webkit-scrollbar{-webkit-appearance:none;width:14px;height:14px}.table-container::-webkit-scrollbar-thumb{border-radius:8px;border:3px solid #fff;background-color:rgba(0,0,0,0.3)}.forms .btn,.forms input[type="submit"].btn,.forms button,.forms input[type="submit"],.forms input[type="reset"]{padding:.3625em 1.3em}.forms .btn-outline{padding:.3em 1.3em}.forms .btn-outline.bold{padding:.2375em 1.3em}.forms label{display:block;margin-bottom:1.0999999999999999em}.forms input[type="text"],.forms input[type="password"],.forms input[type="email"],.forms input[type="url"],.forms input[type="phone"],.forms input[type="tel"],.forms input[type="number"],.forms input[type="datetime"],.forms input[type="date"],.forms input[type="month"],.forms input[type="color"],.forms input[type="time"],.forms input[type="search"],.forms input[type="range"],.forms input[type="file"],.forms input[type="datetime-local"],.forms textarea,.forms select{display:block}.forms-inline input[type="text"],.forms-inline-list input[type="text"],.forms-inline input[type="password"],.forms-inline-list input[type="password"],.forms-inline input[type="email"],.forms-inline-list input[type="email"],.forms-inline input[type="url"],.forms-inline-list input[type="url"],.forms-inline input[type="phone"],.forms-inline-list input[type="phone"],.forms-inline input[type="tel"],.forms-inline-list input[type="tel"],.forms-inline input[type="number"],.forms-inline-list input[type="number"],.forms-inline input[type="datetime"],.forms-inline-list input[type="datetime"],.forms-inline input[type="date"],.forms-inline-list input[type="date"],.forms-inline input[type="month"],.forms-inline-list input[type="month"],.forms-inline input[type="color"],.forms-inline-list input[type="color"],.forms-inline input[type="time"],.forms-inline-list input[type="time"],.forms-inline input[type="search"],.forms-inline-list input[type="search"],.forms-inline input[type="range"],.forms-inline-list input[type="range"],.forms-inline input[type="file"],.forms-inline-list input[type="file"],.forms-inline input[type="datetime-local"],.forms-inline-list input[type="datetime-local"],.forms-inline textarea,.forms-inline-list textarea,.forms-inline select,.forms-inline-list select{display:inline-block}.forms-list,.forms-inline,.forms-inline-list{margin:0;padding:0;margin-bottom:1.0999999999999999em;list-style:none}.forms-list label,.forms-inline label,.forms-inline-list li,.forms-inline-list li label{display:inline-block;margin-bottom:0}.forms-inline-list li label{margin-right:.825em}.forms-inline-list li{margin-bottom:3px}.forms-list li{margin-bottom:6px}.forms-desc{margin-top:4px;color:rgba(0,0,0,0.4);font-size:12.75px;line-height:1.4em}input[type="text"],input[type="password"],input[type="email"],input[type="url"],input[type="phone"],input[type="tel"],input[type="number"],input[type="datetime"],input[type="date"],input[type="month"],input[type="color"],input[type="time"],input[type="search"],input[type="datetime-local"],textarea{-moz-transition:border ease .5s;transition:border ease .5s}.error,.success{font-weight:normal;font-size:12.75px}input.input-error,textarea.input-error,select.input-error,.input-error{border-color:#de2c3b;box-shadow:0 0 0 2px rgba(222,44,59,0.3),0 1px 2px rgba(0,0,0,0.2) inset}input.input-success,textarea.input-success,select.input-success,.input-success{border-color:#2c9f42;box-shadow:0 0 0 2px rgba(44,159,66,0.3),0 1px 2px rgba(0,0,0,0.2) inset}input.input-gray,textarea.input-gray,select.input-gray,.input-gray{border-color:#b3b6b7;box-shadow:0 0 0 2px rgba(179,182,183,0.4),0 1px 2px rgba(0,0,0,0.2) inset}input[type="text"]:focus,input[type="password"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="phone"]:focus,input[type="tel"]:focus,input[type="number"]:focus,input[type="datetime"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="color"]:focus,input[type="time"]:focus,input[type="search"]:focus,input[type="datetime-local"]:focus,textarea:focus{outline:0;border-color:#2575ed;box-shadow:0 0 0 2px rgba(37,117,237,0.3),0 1px 2px rgba(0,0,0,0.2) inset}input.input-search,input[type="search"]{padding-right:.5em;padding-left:.5em;margin-bottom:0;border-radius:15px}input.input-on-black{border:1px solid rgba(255,255,255,0.1);background:rgba(255,255,255,0.35)}input.input-on-black::-webkit-input-placeholder{color:rgba(255,255,255,0.6)}input.input-on-black::-moz-placeholder{color:rgba(255,255,255,0.6)}input.input-on-black:focus,input.input-on-black.active{border:1px solid #fff;background:#fff;box-shadow:none}input.input-on-black:focus::-webkit-input-placeholder,input.input-on-black.active::-webkit-input-placeholder{color:#aaa}input.input-on-black:focus::-moz-placeholder,input.input-on-black.active::-moz-placeholder{color:#aaa}input.input-big{font-size:18px}input.input-small{font-size:12.75px}input.input-smaller{font-size:11.25px}.input-groups{display:table!important}.input-groups input{width:100%}.input-groups input,.input-groups .input-append,.input-groups .input-prepend,.input-groups .btn-append{display:table-cell!important}.input-groups .btn-append,.input-groups .input-append,.input-groups .input-prepend{width:1%;vertical-align:middle}.input-groups .input-append,.input-groups .input-prepend{background-color:#f4f4f4;border:1px solid #d4d4d4;margin:0;padding:.45em .75em .15em .75em;color:rgba(0,0,0,0.6);line-height:1.65em;font-size:12.75px;white-space:nowrap}.input-groups .input-prepend{border-right:0}.input-groups .input-append{position:relative;z-index:1;border-left:none}.input-groups .btn-append .btn{display:block;height:auto;border-radius:0 2px 2px 0}.navbar{font-size:105%;margin-bottom:1.65em}.navbar:after{content:"";display:table;clear:both}.navbar:after{content:"";display:table;clear:both}.navbar.navbar-left{float:left}.navbar.navbar-right{float:right}.navbar ul{list-style:none;margin:0}.navbar ul:after{content:"";display:table;clear:both}.navbar ul:after{content:"";display:table;clear:both}.navbar li{float:left;margin-right:1.65em}.navbar.navbar-right li{margin-right:0;margin-left:1.65em}.navbar a,.navbar span{display:block;text-decoration:none}.navbar a:hover{color:#de2c3b;text-decoration:underline}.navbar li.active a,.navbar span{text-decoration:none;cursor:text;color:rgba(0,0,0,0.4)}.navigation-toggle{display:none;text-transform:uppercase;position:relative;color:#0f0f0f;background-color:#fff}.navigation-toggle:after{position:absolute;z-index:1;top:50%;margin-top:-12px;left:10px;content:"\2630";font-size:24px;line-height:1}.navigation-toggle.navigation-toggle-black{background-color:#0f0f0f;color:#fff}.navigation-toggle.navigation-toggle-show{margin-bottom:1.65em}.navigation-toggle span{position:relative;z-index:2;cursor:pointer;display:block;padding:10px 20px 10px 40px}.navigation-fixed{position:fixed;top:0;left:0;z-index:101}.navbar-pills a,.navbar-pills span{padding:.825em 1.65em}.navbar-pills a:hover{color:#0f0f0f;background:#eee}.navbar-pills li.active a,.navbar-pills span{text-decoration:none;cursor:text;color:rgba(0,0,0,0.4);background:#eee}.navbar-pills li{margin-right:1px}.navbar-pills.navbar-right li{margin-left:1px}.fullwidth ul{width:100%}.fullwidth li{float:none!important;margin:0;display:table-cell;width:1%;text-align:center}.fullwidth li a,.fullwidth li span{display:block}.nav{margin-bottom:1.65em}.nav ul{list-style:none;margin:0}.nav ul li ul{margin-left:2em;font-size:.95em}.nav a,.nav span{display:block;padding:.5em 0}.nav a{color:#0f0f0f;text-decoration:none}.nav a:hover{color:#de2c3b;text-decoration:underline}.nav li.active a,.nav span{text-decoration:none;cursor:text;color:rgba(0,0,0,0.4)}.nav i.fa{width:1.65em}.nav-stacked li{margin-bottom:1px}.nav-stacked a,.nav-stacked span{padding:.6em .7em}.nav-stacked a{background:#f6f6f6}.nav-stacked a:hover{color:#000;background:#eee}.nav-stacked li.active a,.nav-stacked span{background:#fff;text-decoration:none;cursor:text;color:rgba(0,0,0,0.3)}.nav-stats li{position:relative}.nav-stats a,.nav-stats span{padding-right:50px}.nav-stats sup,.nav-stats .badge{position:absolute;top:50%;right:0}.nav-stats sup{color:rgba(0,0,0,0.4)}.nav-stats .badge{margin-top:-8px}.nav-tabs{border-bottom:1px solid #e3e3e3;margin-bottom:1.65em}.nav-tabs:after{content:"";display:table;clear:both}.nav-tabs:after{content:"";display:table;clear:both}.nav-tabs ul{list-style:none;margin:0}.nav-tabs li{float:left;margin-right:2px}.nav-tabs a,.nav-tabs span{display:block;line-height:1;padding:.825em 1.65em;border:1px solid transparent}.nav-tabs a{color:rgba(0,0,0,0.5);text-decoration:none}.nav-tabs a:focus,.nav-tabs a:hover{color:#0f0f0f;text-decoration:underline;background-color:#eee}.nav-tabs li.active a,.nav-tabs span{color:#0f0f0f;background:#fff;position:relative;border:1px solid #ddd;border-bottom:1px solid #fff;bottom:-1px;cursor:default;text-decoration:none}.breadcrumbs{margin-bottom:1.65em}.breadcrumbs:after{content:"";display:table;clear:both}.breadcrumbs:after{content:"";display:table;clear:both}.breadcrumbs ul{font-size:.9em;color:rgba(0,0,0,0.4);list-style:none;margin:0}.breadcrumbs ul:after{content:"";display:table;clear:both}.breadcrumbs ul:after{content:"";display:table;clear:both}.breadcrumbs li{float:left;margin-right:5px}.breadcrumbs li+li:before{content:" > ";color:#aaa;font-size:12px;margin:0 7px 0 5px;position:relative;top:-1px}.breadcrumbs.breadcrumbs-path li+li:before{content:" / ";top:0}.breadcrumbs a{color:#0f0f0f;text-decoration:none}.breadcrumbs a:hover{color:#0f0f0f;text-decoration:underline}.breadcrumbs li.active a,.breadcrumbs span,.breadcrumbs li.active a:hover{text-decoration:none;cursor:text;color:rgba(0,0,0,0.4)}.pagination{position:relative;left:-9px;margin-left:0;list-style:none}.pagination:after{content:"";display:table;clear:both}.pagination:after{content:"";display:table;clear:both}.pagination li{float:left;margin-right:2px}.pagination a,.pagination span{display:block;padding:7px 9px;line-height:1;border-radius:2px;color:#0f0f0f;text-decoration:none}.pagination span,.pagination li.active a,.pagination li.active a:hover{color:#fff;background-color:#0f0f0f;cursor:text}.pagination a:focus,.pagination a:hover{text-decoration:none;background-color:#0f0f0f;color:#fff}.btn,input[type="submit"].btn{display:inline-block;vertical-align:top;font-family:"Helvetica Neue",Helvetica,Tahoma,sans-serif;font-size:1em;font-weight:400;line-height:1.65em;text-align:center;text-decoration:none;color:#222;-webkit-appearance:none;outline:0;margin:0;border:0;border-radius:2px;box-shadow:none;cursor:pointer;background:#e0e3e5;padding:.55em 2.5em}.btn:hover,input[type="submit"].btn:hover{color:rgba(0,0,0,0.5);background:#b3b6b7}.btn::-moz-focus-inner{border:0;padding:0}.btn-big,input[type="submit"].btn-big{font-size:18px}.btn-small,input[type="submit"].btn-small{font-size:12.75px}.btn-smaller,input[type="submit"].btn-smaller{font-size:11.25px;vertical-align:baseline}.btn-round{border-radius:15px}.btn-outline,input[type="submit"].btn-outline{background:0;padding:.48750000000000004em 2.5em;border:1px solid #0f0f0f}.btn-outline:hover,input[type="submit"].btn-outline:hover{border-color:#b3b6b7}.btn-outline.btn-active{padding:.55em 2.5em}.btn-outline.bold{border-width:2px;padding:.42500000000000004em 2.5em}.btn-active,.btn[disabled],.btn-disabled{background:0;background:#b3b6b7;color:rgba(0,0,0,0.5)}.btn-active:hover,.btn[disabled]:hover,.btn-disabled:hover{color:rgba(0,0,0,0.5);background:#b3b6b7}.btn-active{box-shadow:0 1px 3px rgba(0,0,0,0.4) inset}.btn-outline.btn[disabled],.btn-outline.btn-disabled{background:0;box-shadow:none;color:rgba(0,0,0,0.3);border:1px solid rgba(0,0,0,0.1)}.btn-outline.btn-active{background:0;color:rgba(0,0,0,0.4);border:0;box-shadow:0 1px 3px rgba(0,0,0,0.2) inset}.btn[disabled],.btn-disabled{cursor:default;box-shadow:none}.btn-blue,input[type="submit"].btn-blue{color:rgba(255,255,255,0.9);background:#2575ed}.btn-blue:hover,input[type="submit"].btn-blue:hover{color:rgba(255,255,255,0.6);background:#1a52a5}.btn-blue.btn-active,input[type="submit"].btn-blue.btn-active{box-shadow:0 1px 4px rgba(0,0,0,0.5) inset}.btn-blue.btn-active,input[type="submit"].btn-blue.btn-active,.btn-blue.btn-disabled,input[type="submit"].btn-blue.btn-disabled,.btn-blue.btn[disabled],input[type="submit"].btn-blue.btn[disabled]{color:rgba(255,255,255,0.5);background:#1a52a5}.btn-blue.btn-active:hover,input[type="submit"].btn-blue.btn-active:hover,.btn-blue.btn-disabled:hover,input[type="submit"].btn-blue.btn-disabled:hover,.btn-blue.btn[disabled]:hover,input[type="submit"].btn-blue.btn[disabled]:hover{color:rgba(255,255,255,0.5)}.btn-blue.btn-outline,input[type="submit"].btn-blue.btn-outline{background:0;border-color:#2575ed;color:#2575ed}.btn-blue.btn-outline:hover,input[type="submit"].btn-blue.btn-outline:hover{color:rgba(255,255,255,0.9);background:#2575ed}.btn-blue.btn-outline.btn[disabled],input[type="submit"].btn-blue.btn-outline.btn[disabled],.btn-blue.btn-outline.btn-disabled,input[type="submit"].btn-blue.btn-outline.btn-disabled{background:0;box-shadow:none;color:rgba(37,117,237,0.4);border:1px solid rgba(37,117,237,0.3)}.btn-blue.btn-outline.btn-active,input[type="submit"].btn-blue.btn-outline.btn-active{background:0;color:rgba(37,117,237,0.6);border:0;box-shadow:0 1px 3px rgba(26,82,165,0.6) inset}.btn-blue:hover,input[type="submit"].btn-blue:hover{color:rgba(255,255,255,0.6);background:#1a52a5}.btn-blue.btn-active,input[type="submit"].btn-blue.btn-active{box-shadow:0 1px 4px rgba(0,0,0,0.5) inset}.btn-blue.btn-active,input[type="submit"].btn-blue.btn-active,.btn-blue.btn-disabled,input[type="submit"].btn-blue.btn-disabled,.btn-blue.btn[disabled],input[type="submit"].btn-blue.btn[disabled]{color:rgba(255,255,255,0.5);background:#1a52a5}.btn-blue.btn-active:hover,input[type="submit"].btn-blue.btn-active:hover,.btn-blue.btn-disabled:hover,input[type="submit"].btn-blue.btn-disabled:hover,.btn-blue.btn[disabled]:hover,input[type="submit"].btn-blue.btn[disabled]:hover{color:rgba(255,255,255,0.5)}.btn-blue.btn-outline,input[type="submit"].btn-blue.btn-outline{background:0;border-color:#2575ed;color:#2575ed}.btn-blue.btn-outline:hover,input[type="submit"].btn-blue.btn-outline:hover{color:rgba(255,255,255,0.9);background:#2575ed}.btn-blue.btn-outline.btn[disabled],input[type="submit"].btn-blue.btn-outline.btn[disabled],.btn-blue.btn-outline.btn-disabled,input[type="submit"].btn-blue.btn-outline.btn-disabled{background:0;box-shadow:none;color:rgba(37,117,237,0.4);border:1px solid rgba(37,117,237,0.3)}.btn-blue.btn-outline.btn-active,input[type="submit"].btn-blue.btn-outline.btn-active{background:0;color:rgba(37,117,237,0.6);border:0;box-shadow:0 1px 3px rgba(26,82,165,0.6) inset}.btn-red,input[type="submit"].btn-red{color:rgba(255,255,255,0.9);background:#de2c3b}.btn-red:hover,input[type="submit"].btn-red:hover{color:rgba(255,255,255,0.6);background:#b2232f}.btn-red.btn-active,input[type="submit"].btn-red.btn-active{box-shadow:0 1px 4px rgba(0,0,0,0.5) inset}.btn-red.btn-active,input[type="submit"].btn-red.btn-active,.btn-red.btn-disabled,input[type="submit"].btn-red.btn-disabled,.btn-red.btn[disabled],input[type="submit"].btn-red.btn[disabled]{color:rgba(255,255,255,0.5);background:#b2232f}.btn-red.btn-active:hover,input[type="submit"].btn-red.btn-active:hover,.btn-red.btn-disabled:hover,input[type="submit"].btn-red.btn-disabled:hover,.btn-red.btn[disabled]:hover,input[type="submit"].btn-red.btn[disabled]:hover{color:rgba(255,255,255,0.5)}.btn-red.btn-outline,input[type="submit"].btn-red.btn-outline{background:0;border-color:#de2c3b;color:#de2c3b}.btn-red.btn-outline:hover,input[type="submit"].btn-red.btn-outline:hover{color:rgba(255,255,255,0.9);background:#de2c3b}.btn-red.btn-outline.btn[disabled],input[type="submit"].btn-red.btn-outline.btn[disabled],.btn-red.btn-outline.btn-disabled,input[type="submit"].btn-red.btn-outline.btn-disabled{background:0;box-shadow:none;color:rgba(222,44,59,0.4);border:1px solid rgba(222,44,59,0.3)}.btn-red.btn-outline.btn-active,input[type="submit"].btn-red.btn-outline.btn-active{background:0;color:rgba(222,44,59,0.6);border:0;box-shadow:0 1px 3px rgba(178,35,47,0.6) inset}.btn-red:hover,input[type="submit"].btn-red:hover{color:rgba(255,255,255,0.6);background:#b2232f}.btn-red.btn-active,input[type="submit"].btn-red.btn-active{box-shadow:0 1px 4px rgba(0,0,0,0.5) inset}.btn-red.btn-active,input[type="submit"].btn-red.btn-active,.btn-red.btn-disabled,input[type="submit"].btn-red.btn-disabled,.btn-red.btn[disabled],input[type="submit"].btn-red.btn[disabled]{color:rgba(255,255,255,0.5);background:#b2232f}.btn-red.btn-active:hover,input[type="submit"].btn-red.btn-active:hover,.btn-red.btn-disabled:hover,input[type="submit"].btn-red.btn-disabled:hover,.btn-red.btn[disabled]:hover,input[type="submit"].btn-red.btn[disabled]:hover{color:rgba(255,255,255,0.5)}.btn-red.btn-outline,input[type="submit"].btn-red.btn-outline{background:0;border-color:#de2c3b;color:#de2c3b}.btn-red.btn-outline:hover,input[type="submit"].btn-red.btn-outline:hover{color:rgba(255,255,255,0.9);background:#de2c3b}.btn-red.btn-outline.btn[disabled],input[type="submit"].btn-red.btn-outline.btn[disabled],.btn-red.btn-outline.btn-disabled,input[type="submit"].btn-red.btn-outline.btn-disabled{background:0;box-shadow:none;color:rgba(222,44,59,0.4);border:1px solid rgba(222,44,59,0.3)}.btn-red.btn-outline.btn-active,input[type="submit"].btn-red.btn-outline.btn-active{background:0;color:rgba(222,44,59,0.6);border:0;box-shadow:0 1px 3px rgba(178,35,47,0.6) inset}.btn-green,input[type="submit"].btn-green{color:rgba(255,255,255,0.9);background:#2c9f42}.btn-green:hover,input[type="submit"].btn-green:hover{color:rgba(255,255,255,0.6);background:#237f35}.btn-green.btn-active,input[type="submit"].btn-green.btn-active{box-shadow:0 1px 4px rgba(0,0,0,0.5) inset}.btn-green.btn-active,input[type="submit"].btn-green.btn-active,.btn-green.btn-disabled,input[type="submit"].btn-green.btn-disabled,.btn-green.btn[disabled],input[type="submit"].btn-green.btn[disabled]{color:rgba(255,255,255,0.5);background:#237f35}.btn-green.btn-active:hover,input[type="submit"].btn-green.btn-active:hover,.btn-green.btn-disabled:hover,input[type="submit"].btn-green.btn-disabled:hover,.btn-green.btn[disabled]:hover,input[type="submit"].btn-green.btn[disabled]:hover{color:rgba(255,255,255,0.5)}.btn-green.btn-outline,input[type="submit"].btn-green.btn-outline{background:0;border-color:#2c9f42;color:#2c9f42}.btn-green.btn-outline:hover,input[type="submit"].btn-green.btn-outline:hover{color:rgba(255,255,255,0.9);background:#2c9f42}.btn-green.btn-outline.btn[disabled],input[type="submit"].btn-green.btn-outline.btn[disabled],.btn-green.btn-outline.btn-disabled,input[type="submit"].btn-green.btn-outline.btn-disabled{background:0;box-shadow:none;color:rgba(44,159,66,0.4);border:1px solid rgba(44,159,66,0.3)}.btn-green.btn-outline.btn-active,input[type="submit"].btn-green.btn-outline.btn-active{background:0;color:rgba(44,159,66,0.6);border:0;box-shadow:0 1px 3px rgba(35,127,53,0.6) inset}.btn-green:hover,input[type="submit"].btn-green:hover{color:rgba(255,255,255,0.6);background:#237f35}.btn-green.btn-active,input[type="submit"].btn-green.btn-active{box-shadow:0 1px 4px rgba(0,0,0,0.5) inset}.btn-green.btn-active,input[type="submit"].btn-green.btn-active,.btn-green.btn-disabled,input[type="submit"].btn-green.btn-disabled,.btn-green.btn[disabled],input[type="submit"].btn-green.btn[disabled]{color:rgba(255,255,255,0.5);background:#237f35}.btn-green.btn-active:hover,input[type="submit"].btn-green.btn-active:hover,.btn-green.btn-disabled:hover,input[type="submit"].btn-green.btn-disabled:hover,.btn-green.btn[disabled]:hover,input[type="submit"].btn-green.btn[disabled]:hover{color:rgba(255,255,255,0.5)}.btn-green.btn-outline,input[type="submit"].btn-green.btn-outline{background:0;border-color:#2c9f42;color:#2c9f42}.btn-green.btn-outline:hover,input[type="submit"].btn-green.btn-outline:hover{color:rgba(255,255,255,0.9);background:#2c9f42}.btn-green.btn-outline.btn[disabled],input[type="submit"].btn-green.btn-outline.btn[disabled],.btn-green.btn-outline.btn-disabled,input[type="submit"].btn-green.btn-outline.btn-disabled{background:0;box-shadow:none;color:rgba(44,159,66,0.4);border:1px solid rgba(44,159,66,0.3)}.btn-green.btn-outline.btn-active,input[type="submit"].btn-green.btn-outline.btn-active{background:0;color:rgba(44,159,66,0.6);border:0;box-shadow:0 1px 3px rgba(35,127,53,0.6) inset}.btn-black,input[type="submit"].btn-black{color:rgba(255,255,255,0.9);background:#0f0f0f}.btn-black:hover,input[type="submit"].btn-black:hover{color:rgba(255,255,255,0.6);background:#363738}.btn-black.btn-active,input[type="submit"].btn-black.btn-active{box-shadow:0 1px 4px rgba(0,0,0,0.7) inset}.btn-black.btn-active,input[type="submit"].btn-black.btn-active,.btn-black.btn-disabled,input[type="submit"].btn-black.btn-disabled,.btn-black.btn[disabled],input[type="submit"].btn-black.btn[disabled]{color:rgba(255,255,255,0.5);background:#363738}.btn-black.btn-active:hover,input[type="submit"].btn-black.btn-active:hover,.btn-black.btn-disabled:hover,input[type="submit"].btn-black.btn-disabled:hover,.btn-black.btn[disabled]:hover,input[type="submit"].btn-black.btn[disabled]:hover{color:rgba(255,255,255,0.5)}.btn-black.btn-outline,input[type="submit"].btn-black.btn-outline{background:0;border-color:#0f0f0f;color:#0f0f0f}.btn-black.btn-outline:hover,input[type="submit"].btn-black.btn-outline:hover{color:rgba(255,255,255,0.9);background:#0f0f0f}.btn-black.btn-outline.btn[disabled],input[type="submit"].btn-black.btn-outline.btn[disabled],.btn-black.btn-outline.btn-disabled,input[type="submit"].btn-black.btn-outline.btn-disabled{background:0;box-shadow:none;color:rgba(15,15,15,0.4);border:1px solid rgba(15,15,15,0.3)}.btn-black.btn-outline.btn-active,input[type="submit"].btn-black.btn-outline.btn-active{background:0;color:rgba(15,15,15,0.6);border:0;box-shadow:0 1px 3px rgba(54,55,56,0.6) inset}.btn-black:hover,input[type="submit"].btn-black:hover{color:rgba(255,255,255,0.6);background:#363738}.btn-black.btn-active,input[type="submit"].btn-black.btn-active{box-shadow:0 1px 4px rgba(0,0,0,0.7) inset}.btn-black.btn-active,input[type="submit"].btn-black.btn-active,.btn-black.btn-disabled,input[type="submit"].btn-black.btn-disabled,.btn-black.btn[disabled],input[type="submit"].btn-black.btn[disabled]{color:rgba(255,255,255,0.5);background:#363738}.btn-black.btn-active:hover,input[type="submit"].btn-black.btn-active:hover,.btn-black.btn-disabled:hover,input[type="submit"].btn-black.btn-disabled:hover,.btn-black.btn[disabled]:hover,input[type="submit"].btn-black.btn[disabled]:hover{color:rgba(255,255,255,0.5)}.btn-black.btn-outline,input[type="submit"].btn-black.btn-outline{background:0;border-color:#0f0f0f;color:#0f0f0f}.btn-black.btn-outline:hover,input[type="submit"].btn-black.btn-outline:hover{color:rgba(255,255,255,0.9);background:#0f0f0f}.btn-black.btn-outline.btn[disabled],input[type="submit"].btn-black.btn-outline.btn[disabled],.btn-black.btn-outline.btn-disabled,input[type="submit"].btn-black.btn-outline.btn-disabled{background:0;box-shadow:none;color:rgba(15,15,15,0.4);border:1px solid rgba(15,15,15,0.3)}.btn-black.btn-outline.btn-active,input[type="submit"].btn-black.btn-outline.btn-active{background:0;color:rgba(15,15,15,0.6);border:0;box-shadow:0 1px 3px rgba(54,55,56,0.6) inset}.btn-yellow,input[type="submit"].btn-yellow{color:rgba(0,0,0,0.9);background:#ffc800}.btn-yellow:hover,input[type="submit"].btn-yellow:hover{color:rgba(0,0,0,0.6);background:#cca000}.btn-yellow.btn-active,input[type="submit"].btn-yellow.btn-active{box-shadow:0 1px 4px rgba(0,0,0,0.5) inset}.btn-yellow.btn-active,input[type="submit"].btn-yellow.btn-active,.btn-yellow.btn-disabled,input[type="submit"].btn-yellow.btn-disabled,.btn-yellow.btn[disabled],input[type="submit"].btn-yellow.btn[disabled]{color:rgba(0,0,0,0.5);background:#cca000}.btn-yellow.btn-active:hover,input[type="submit"].btn-yellow.btn-active:hover,.btn-yellow.btn-disabled:hover,input[type="submit"].btn-yellow.btn-disabled:hover,.btn-yellow.btn[disabled]:hover,input[type="submit"].btn-yellow.btn[disabled]:hover{color:rgba(0,0,0,0.5)}.btn-yellow.btn-outline,input[type="submit"].btn-yellow.btn-outline{background:0;border-color:#ffc800;color:#ffc800}.btn-yellow.btn-outline:hover,input[type="submit"].btn-yellow.btn-outline:hover{color:rgba(0,0,0,0.9);background:#ffc800}.btn-yellow.btn-outline.btn[disabled],input[type="submit"].btn-yellow.btn-outline.btn[disabled],.btn-yellow.btn-outline.btn-disabled,input[type="submit"].btn-yellow.btn-outline.btn-disabled{background:0;box-shadow:none;color:rgba(255,200,0,0.4);border:1px solid rgba(255,200,0,0.3)}.btn-yellow.btn-outline.btn-active,input[type="submit"].btn-yellow.btn-outline.btn-active{background:0;color:rgba(255,200,0,0.6);border:0;box-shadow:0 1px 3px rgba(204,160,0,0.6) inset}.btn-yellow:hover,input[type="submit"].btn-yellow:hover{color:rgba(0,0,0,0.6);background:#cca000}.btn-yellow.btn-active,input[type="submit"].btn-yellow.btn-active{box-shadow:0 1px 4px rgba(0,0,0,0.5) inset}.btn-yellow.btn-active,input[type="submit"].btn-yellow.btn-active,.btn-yellow.btn-disabled,input[type="submit"].btn-yellow.btn-disabled,.btn-yellow.btn[disabled],input[type="submit"].btn-yellow.btn[disabled]{color:rgba(0,0,0,0.5);background:#cca000}.btn-yellow.btn-active:hover,input[type="submit"].btn-yellow.btn-active:hover,.btn-yellow.btn-disabled:hover,input[type="submit"].btn-yellow.btn-disabled:hover,.btn-yellow.btn[disabled]:hover,input[type="submit"].btn-yellow.btn[disabled]:hover{color:rgba(0,0,0,0.5)}.btn-yellow.btn-outline,input[type="submit"].btn-yellow.btn-outline{background:0;border-color:#ffc800;color:#ffc800}.btn-yellow.btn-outline:hover,input[type="submit"].btn-yellow.btn-outline:hover{color:rgba(0,0,0,0.9);background:#ffc800}.btn-yellow.btn-outline.btn[disabled],input[type="submit"].btn-yellow.btn-outline.btn[disabled],.btn-yellow.btn-outline.btn-disabled,input[type="submit"].btn-yellow.btn-outline.btn-disabled{background:0;box-shadow:none;color:rgba(255,200,0,0.4);border:1px solid rgba(255,200,0,0.3)}.btn-yellow.btn-outline.btn-active,input[type="submit"].btn-yellow.btn-outline.btn-active{background:0;color:rgba(255,200,0,0.6);border:0;box-shadow:0 1px 3px rgba(204,160,0,0.6) inset}.btn-white,input[type="submit"].btn-white{color:rgba(0,0,0,0.9);background:#fff}.btn-white:hover,input[type="submit"].btn-white:hover{color:rgba(0,0,0,0.6);background:#ededed}.btn-white.btn-active,input[type="submit"].btn-white.btn-active{box-shadow:0 1px 4px rgba(0,0,0,0.2) inset}.btn-white.btn-active,input[type="submit"].btn-white.btn-active,.btn-white.btn-disabled,input[type="submit"].btn-white.btn-disabled,.btn-white.btn[disabled],input[type="submit"].btn-white.btn[disabled]{color:rgba(0,0,0,0.5);background:#ededed}.btn-white.btn-active:hover,input[type="submit"].btn-white.btn-active:hover,.btn-white.btn-disabled:hover,input[type="submit"].btn-white.btn-disabled:hover,.btn-white.btn[disabled]:hover,input[type="submit"].btn-white.btn[disabled]:hover{color:rgba(0,0,0,0.5)}.btn-white.btn-outline,input[type="submit"].btn-white.btn-outline{background:0;border-color:#fff;color:#fff}.btn-white.btn-outline:hover,input[type="submit"].btn-white.btn-outline:hover{color:rgba(0,0,0,0.9);background:#fff}.btn-white.btn-outline.btn[disabled],input[type="submit"].btn-white.btn-outline.btn[disabled],.btn-white.btn-outline.btn-disabled,input[type="submit"].btn-white.btn-outline.btn-disabled{background:0;box-shadow:none;color:rgba(255,255,255,0.4);border:1px solid rgba(255,255,255,0.3)}.btn-white.btn-outline.btn-active,input[type="submit"].btn-white.btn-outline.btn-active{background:0;color:rgba(255,255,255,0.6);border:0;box-shadow:0 1px 3px rgba(237,237,237,0.6) inset}.btn-white:hover,input[type="submit"].btn-white:hover{color:rgba(0,0,0,0.6);background:#ededed}.btn-white.btn-active,input[type="submit"].btn-white.btn-active{box-shadow:0 1px 4px rgba(0,0,0,0.2) inset}.btn-white.btn-active,input[type="submit"].btn-white.btn-active,.btn-white.btn-disabled,input[type="submit"].btn-white.btn-disabled,.btn-white.btn[disabled],input[type="submit"].btn-white.btn[disabled]{color:rgba(0,0,0,0.5);background:#ededed}.btn-white.btn-active:hover,input[type="submit"].btn-white.btn-active:hover,.btn-white.btn-disabled:hover,input[type="submit"].btn-white.btn-disabled:hover,.btn-white.btn[disabled]:hover,input[type="submit"].btn-white.btn[disabled]:hover{color:rgba(0,0,0,0.5)}.btn-white.btn-outline,input[type="submit"].btn-white.btn-outline{background:0;border-color:#fff;color:#fff}.btn-white.btn-outline:hover,input[type="submit"].btn-white.btn-outline:hover{color:rgba(0,0,0,0.9);background:#fff}.btn-white.btn-outline.btn[disabled],input[type="submit"].btn-white.btn-outline.btn[disabled],.btn-white.btn-outline.btn-disabled,input[type="submit"].btn-white.btn-outline.btn-disabled{background:0;box-shadow:none;color:rgba(255,255,255,0.4);border:1px solid rgba(255,255,255,0.3)}.btn-white.btn-outline.btn-active,input[type="submit"].btn-white.btn-outline.btn-active{background:0;color:rgba(255,255,255,0.6);border:0;box-shadow:0 1px 3px rgba(237,237,237,0.6) inset}.btn-white.btn-outline.btn-active{box-shadow:none;border:1px solid rgba(255,255,255,0.3);padding:.48750000000000004em 2.5em}.btn-single,.btn-group{display:inline-block;margin-right:2px;vertical-align:bottom}.btn-single:after,.btn-group:after{content:"";display:table;clear:both}.btn-single:after,.btn-group:after{content:"";display:table;clear:both}.btn-single>.btn,.btn-single>input,.btn-group>.btn,.btn-group>input{float:left;border-radius:0;margin-left:-1px}.btn-single>.btn{border-radius:2px}.btn-group>.btn:first-child{border-radius:2px 0 0 2px}.btn-group>.btn:last-child{border-radius:0 2px 2px 0}.btn-group>.btn.btn-round:first-child,.btn-group>.input-search:first-child{border-radius:15px 0 0 15px}.btn-group>.btn.btn-round:last-child,.btn-group>.input-search:last-child{border-radius:0 15px 15px 0}.tools-alert{padding:12px 15px;background:#f7f8f8;color:#0f0f0f;margin-bottom:1.65em}.tools-message{display:none;position:fixed;z-index:100;top:10px;right:10px;max-width:350px;line-height:1.5;font-size:95%;padding:12px 15px;color:#0f0f0f;background:#e0e3e5}.tools-message ul{margin:0;list-style:none}.tools-message-black,.tools-message-blue,.tools-message-red,.tools-message-green{color:rgba(255,255,255,0.95)}.tools-message-black{background:#0f0f0f}.tools-message-blue{background:#2575ed}.tools-message-red{background:#de2c3b}.tools-message-yellow{background:#ffc800}.tools-message-green{background:#2c9f42}.tools-alert-black{background:#dadada}.tools-alert-blue{background:#d3e3fb}.tools-alert-red{background:#f8d5d8}.tools-alert-yellow{background:#fff4cc}.tools-alert-green{background:#d5ecd9}.label,.badge{background:#e0e3e5;font-size:12.75px;display:inline-block;line-height:1;padding:4px 7px 3px 7px;color:#0f0f0f;text-align:center;font-weight:normal;text-transform:uppercase}.label-outline{background:0;border:1px solid #0f0f0f;padding:3px 6px 2px 6px}.badge{border-radius:15px}.badge-small{font-size:11.25px;padding:3px 5px}.label-black,.label-blue,.label-red,.label-green,.badge-black,.badge-blue,.badge-red,.badge-green{color:#fff}.label-black,.badge-black{background:#0f0f0f}.label-blue,.badge-blue{background:#2575ed}.label-red,.badge-red{background:#de2c3b}.label-green,.badge-green{background:#2c9f42}.label-yellow,.badge-yellow{background:#ffc800}.label-white,.badge-white{background:#fff}.label-black.label-outline,.label-blue.label-outline,.label-red.label-outline,.label-green.label-outline,.label-yellow.label-outline,.label-white.label-outline{background:0;color:#0f0f0f}.label-blue.label-outline{border-color:#2575ed;color:#2575ed}.label-red.label-outline{border-color:#de2c3b;color:#de2c3b}.label-green.label-outline{border-color:#2c9f42;color:#2c9f42}.label-yellow.label-outline{border-color:#ffc800;color:#ffc800}.label-white.label-outline{border-color:#fff;color:#fff}#tools-progress{position:fixed;top:0;left:0;width:100%;z-index:1000000;height:10px}#tools-progress span{display:block;width:100%;height:100%;background-color:#1a52a5;background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.2) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.2) 50%,rgba(255,255,255,0.2) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.2) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.2) 50%,rgba(255,255,255,0.2) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.2) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.2) 50%,rgba(255,255,255,0.2) 75%,transparent 75%,transparent);-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite;background-size:40px 40px}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.accordion-title{position:relative;display:block;margin:0;margin-bottom:2px;padding:12px 50px 12px 18px;color:#000;text-decoration:none;font-weight:normal;font-size:.9375em;line-height:1,4em;background:#f7f8f8;cursor:pointer;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.accordion-title.accordion-title-opened{margin-bottom:0;font-weight:bold;background:#f0f1f2}.accordion-title a,.accordion-title:hover{color:#000;text-decoration:none}.accordion-panel{padding:1.65em;margin-bottom:10px}.accordion-toggle{position:absolute;top:50%;margin-top:-8px;right:20px;padding:0;font-size:0;line-height:1}.accordion-toggle-closed{background:#000;width:1px;height:15px;margin-left:1px}.accordion-toggle-closed:before{position:absolute;top:7px;left:-7px;content:"";width:15px;height:1px;background:#000}.accordion-toggle-opened:before{position:absolute;top:7px;left:-8px;content:"";width:15px;height:1px;background:#000}.filterbox{position:relative}.filterbox input{padding-right:30px}.filterbox span{position:absolute;z-index:2;top:0;right:1px;width:26px;height:100%;cursor:pointer}.filterbox span:after{content:"";display:inline-block;position:relative;top:50%;margin-left:8px;margin-top:-21px;width:0;height:0;vertical-align:middle;border-top:5px solid rgba(0,0,0,0.6);border-right:5px solid transparent;border-left:5px solid transparent}.filterbox-list{z-index:1000;position:absolute;left:0;display:none;margin:0;list-style:none;background:#fff;width:100%;box-shadow:0 1px 3px rgba(0,0,0,0.2);max-height:250px;overflow:auto}.filterbox-list li{padding:4px 10px;color:#000;cursor:pointer}.filterbox-list li:hover{background:#f0f1f2}.filterbox-list li.active{background:#2575ed;color:#fff}.tooltip{position:absolute;z-index:10000;display:inline-block;color:#fff;padding:2px 10px;font-size:12.75px;line-height:1.5em;max-width:250px;background:#0f0f0f}.tooltip-theme-red{background:#de2c3b}.tooltip-theme-blue{background:#2575ed}.tooltip-theme-green{background:#2c9f42}.tooltip-theme-yellow{background:#ffc800}.tooltip-theme-white{background:#fff}.tooltip-theme-yellow,.tooltip-theme-white{color:#000}.dropdown{display:none;position:absolute;z-index:102;top:0;right:0;width:250px;color:#0f0f0f;background:#fff;box-shadow:0 1px 5px rgba(0,0,0,0.3);overflow:auto}.dropdown section{max-height:250px;overflow:auto;padding:20px}.dropdown footer{padding:20px}ul.dropdown{max-height:300px;list-style:none;margin:0;line-height:1.5;font-size:95%;padding:0}ul.dropdown a{display:block;padding:7px 15px;text-decoration:none;color:#0f0f0f}ul.dropdown a:hover{background:#eee}ul.dropdown li.divider{border-bottom:1px solid #e2e2e2}.caret{display:inline-block;width:0;height:0;margin-left:.3em;vertical-align:middle;border-top:5px solid;border-right:5px solid transparent;border-left:5px solid transparent}.caret.caret-up{border-top:0;border-bottom:4px solid}.livesearch-box{position:relative;display:inline-block;width:100%}.livesearch-box input{padding-left:30px}.livesearch-box .close{position:absolute;top:.2em;right:5px;z-index:2;padding:4px 6px;line-height:1;font-size:20px;cursor:pointer;color:#000;text-decoration:none;filter:alpha(opacity=50);-moz-opacity:.5;opacity:.5}.livesearch-box .close:before{content:'\00D7'}.livesearch-box .close:hover{filter:alpha(opacity=100);-moz-opacity:1;opacity:1}.livesearch-box .close:before{content:'\00D7'}.livesearch-box .close:hover{filter:alpha(opacity=100);-moz-opacity:1;opacity:1}.livesearch-icon{position:absolute;top:53%;left:10px;z-index:2}.livesearch-icon:before,.livesearch-icon:after{content:"";position:absolute;top:50%;left:0;margin:-8px 0 0;background:rgba(0,0,0,0.5)}.livesearch-icon:before{width:10px;height:10px;border:2px solid rgba(0,0,0,0.5);background:transparent;border-radius:12px}.livesearch-icon:after{left:10px;width:2px;height:7px;margin-top:0;-webkit-transform:rotate(-45deg);-moz-transform:rotate(-45deg);-ms-transform:rotate(-45deg);-o-transform:rotate(-45deg);transform:rotate(-45deg)}.tools-droparea{position:relative;overflow:hidden;padding:80px 20px;border:3px dashed rgba(0,0,0,0.1)}.tools-droparea.drag-hover{background:rgba(200,222,250,0.75)}.tools-droparea.drag-drop{background:rgba(250,248,200,0.5)}.tools-droparea-placeholder{text-align:center;font-size:11px;color:rgba(0,0,0,0.5)}.autocomplete{position:absolute;z-index:1000;left:0;display:none;margin:0;list-style:none;background:#fff;width:250px;box-shadow:0 1px 3px rgba(0,0,0,0.2);max-height:250px;overflow:auto}.autocomplete a{padding:4px 10px;color:#000;display:block;text-decoration:none}.autocomplete a:hover{background:#f0f1f2}.autocomplete a.active{background:#2575ed;color:#fff}#modal-overlay{position:fixed;top:0;left:0;margin:auto;overflow:auto;width:100%;height:100%;background-color:#000!important;filter:alpha(opacity=30);-moz-opacity:.3;opacity:.3;z-index:100}.modal-blur{-webkit-filter:blur(3px);-moz-filter:blur(3px);-ms-filter:blur(3px);filter:blur(3px)}.modal-box{position:fixed;top:0;left:0;bottom:0;right:0;overflow-x:hidden;overflow-y:auto;z-index:101}.modal{position:relative;margin:auto;margin-bottom:20px;padding:0;background:#fff;color:#000;box-shadow:0 1px 70px rgba(0,0,0,0.5)}.modal header{padding:30px 40px 5px 40px;font-size:18px;font-weight:bold}.modal section{padding:30px 40px 50px 40px}.modal footer button{width:100%;border-radius:0}.modal-close{position:absolute;top:8px;right:12px;width:30px;height:30px;text-align:right;color:#bbb;font-size:30px;font-weight:300;cursor:pointer}.modal-close:hover{color:#000}.group:after{content:"";display:table;clear:both}.group:after{content:"";display:table;clear:both}.hide{display:none}.highlight{background-color:#f7f3e2}.big{font-size:18px}.small{font-size:12.75px}.smaller{font-size:11.25px}.nowrap,.nowrap td{white-space:nowrap}.req,.required{font-weight:normal;color:#de2c3b}.error{color:#de2c3b}.success{color:#2c9f42}.text-centered{text-align:center}.text-right{text-align:right}.last{margin-right:0!important}.pause{margin-bottom:.825em!important}.end{margin-bottom:0!important}.normal{font-weight:normal}.light{font-weight:300}.bold{font-weight:bold}.italic{font-style:italic}.left{float:left}.right{float:right}.upper{text-transform:uppercase}.list-flat{margin-left:0;list-style:none}.color-black{color:#0f0f0f}.color-white{color:#fff}.color-gray-10{color:rgba(0,0,0,0.1)}.color-gray-20{color:rgba(0,0,0,0.2)}.color-gray-30{color:rgba(0,0,0,0.3)}.color-gray-40{color:rgba(0,0,0,0.4)}.color-gray-50{color:rgba(0,0,0,0.5)}.color-gray-60{color:rgba(0,0,0,0.6)}.color-gray-70{color:rgba(0,0,0,0.7)}.color-gray-80{color:rgba(0,0,0,0.8)}.color-gray-90{color:rgba(0,0,0,0.9)}.color-white-10{color:rgba(255,255,255,0.1)}.color-white-20{color:rgba(255,255,255,0.2)}.color-white-30{color:rgba(255,255,255,0.3)}.color-white-40{color:rgba(255,255,255,0.4)}.color-white-50{color:rgba(255,255,255,0.5)}.color-white-60{color:rgba(255,255,255,0.6)}.color-white-70{color:rgba(255,255,255,0.7)}.color-white-80{color:rgba(255,255,255,0.8)}.color-white-90{color:rgba(255,255,255,0.9)}.video-wrapper{height:0;padding-bottom:56.25%;position:relative;margin-bottom:1.65em}.video-wrapper iframe,.video-wrapper object,.video-wrapper embed{position:absolute;top:0;left:0;width:100%;height:100%}@media only screen and (max-width:767px){.left,.right{float:none}.hide-on-mobile{display:none}}.str{color:#d14}.kwd{color:#333}.com{color:#998}.typ{color:#458}.lit{color:#458}.pun{color:#888}.opn{color:#333}.clo{color:#333}.tag{color:#367ac3}.atn{color:#51a7c9}.atv{color:#709c1a}.dec{color:#666}.var{color:teal}.fun{color:#900}.linenums ol li{list-style-type:none;counter-increment:list;position:relative}.linenums ol li:after{content:counter(list);position:absolute;left:-3.3em;border-right:1px solid #e5e5e5;padding-right:9px;width:2.45em;text-align:right;color:rgba(0,0,0,0.3);font-size:12px}@media only screen and (max-width:767px){.mobile-width-100{width:100%}.units-row .unit-90,.units-row .unit-80,.units-row .unit-75,.units-row .unit-70,.units-row .unit-66,.units-row .unit-65,.units-row .unit-60,.units-row .unit-50,.units-row .unit-40,.units-row .unit-35,.units-row .unit-33,.units-row .unit-30,.units-row .unit-25,.units-row .unit-20,.units-row .unit-10{width:100%;float:none;margin-left:0;margin-bottom:1.65em}.unit-push-90,.unit-push-80,.unit-push-75,.unit-push-70,.unit-push-66,.unit-push-65,.unit-push-60,.unit-push-50,.unit-push-40,.unit-push-35,.unit-push-33,.unit-push-30,.unit-push-25,.unit-push-20,.unit-push-10{left:0}.units-row .unit-push-right{float:none}.units-mobile-50 .unit-90,.units-mobile-50 .unit-80,.units-mobile-50 .unit-75,.units-mobile-50 .unit-70,.units-mobile-50 .unit-66,.units-mobile-50 .unit-65,.units-mobile-50 .unit-60,.units-mobile-50 .unit-40,.units-mobile-50 .unit-30,.units-mobile-50 .unit-35,.units-mobile-50 .unit-33,.units-mobile-50 .unit-25,.units-mobile-50 .unit-20,.units-mobile-50 .unit-10{float:left;margin-left:3%;width:48.5%}.units-mobile-50 .unit-90:first-child,.units-mobile-50 .unit-80:first-child,.units-mobile-50 .unit-75:first-child,.units-mobile-50 .unit-70:first-child,.units-mobile-50 .unit-66:first-child,.units-mobile-50 .unit-65:first-child,.units-mobile-50 .unit-60:first-child,.units-mobile-50 .unit-40:first-child,.units-mobile-50 .unit-35:first-child,.units-mobile-50 .unit-30:first-child,.units-mobile-50 .unit-33:first-child,.units-mobile-50 .unit-25:first-child,.units-mobile-50 .unit-20:first-child,.units-mobile-50 .unit-10:first-child{margin-left:0}}@media only screen and (max-width:767px){.blocks-2,.blocks-3,.blocks-4,.blocks-5,.blocks-6{margin-left:0;margin-bottom:1.65em}.blocks-2>li,.blocks-3>li,.blocks-4>li,.blocks-5>li,.blocks-6>li{float:none;margin-left:0;width:100%}.blocks-mobile-50>li,.blocks-mobile-33>li{float:left;margin-left:3%}.blocks-mobile-33,.blocks-mobile-50{margin-left:-3%}.blocks-mobile-50>li{width:47%}.blocks-mobile-33>li{width:30.333333333333332%}}@media(min-width:768px) and (max-width:979px){h1{font-size:2.25em;line-height:1.125}h2{font-size:1.5em;line-height:1.25}h3{font-size:1.3125em;line-height:1.25}h4{font-size:1.125em;line-height:1.22222222}h5{font-size:1em}h6{font-size:.75em}}@media(max-width:767px){h1{font-size:2.25em;line-height:1.25}h2{font-size:1.5em;line-height:1.15384615}h3{font-size:1.3125em;line-height:1.13636364}h4{font-size:1.125em;line-height:1.11111111}h5{font-size:1em}h6{font-size:.75em}.lead{font-size:1.2em}ul,ol,ul ul,ol ol,ul ol,ol ul{margin-left:1.65em}blockquote{margin-left:0}}@media only screen and (max-width:767px){.navbar.navbar-left,.navbar.navbar-right,.navbar li,.navbar.navbar-left li,.navbar.navbar-right li{float:none;text-align:left;width:auto}.navbar li,.navbar.navbar-right li{margin-left:0;margin-right:0}.fullwidth ul,.fullwidth li{width:auto}.fullwidth li{display:block}}@media only screen and (max-width:767px){.forms-list label{display:inline-block}}@media print{*{text-shadow:none!important;color:#000!important;background:transparent!important;box-shadow:none!important;font-size:12pt}.h1,h1{font-size:36pt}.h2,h2{font-size:24pt}.h3,h3{font-size:18pt}.h4,h4{font-size:14pt}.h5,h5{font-size:12pt}.h6,h6{font-size:12pt}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{page-break-inside:avoid}blockquote{border:0;font-style:italic}img{max-width:100%!important}select{background:#fff!important}} \ No newline at end of file diff --git a/src/html/ReqMgr/style/main.css b/src/html/ReqMgr/style/main.css new file mode 100644 index 0000000000..0aca3b950c --- /dev/null +++ b/src/html/ReqMgr/style/main.css @@ -0,0 +1,125 @@ +.main-font { + font-size: 12px; +} +.main-menu-item { + font-size: 16px; + color: #7C81B2; +} +.header { + background-color: #CCCCCC; + padding-left: 20px; + padding-right: 20px; + padding-top: 10px; + border-bottom: 1px solid #000000; + font: "Lucida Grande","Lucida Sans Unicode","Lucida Sans",Helvetica,Arial,sans-serif; + font-weight: bold; + color: #808080; +} +.shadow { + -webkit-box-shadow: 10px 10px 20px #666666; + -moz-box-shadow: 10px 10px 20px #666666; + -o-box-shadow: 10px 10px 20px #666666; + box-shadow: 10px 10px 20px #666666; +} +.all-side-shadow { + -webkit-box-shadow: 2px 2px 10px 10px #666; + -moz-box-shadow: 2px 2px 10px 10px #666; + -o-box-shadow: 2px 2px 10px 10px #666; + box-shadow: 2px 2px 10px 10px #666; +} +.borders { + -webkit-border-radius: 40px; + -khtml-border-radius: 40px; + -moz-border-radius: 40px; + border-radius: 40px; +} + +.editor { + width:100%; + height:400px; + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace; + font-weight: bold; + font-size: 1.2em; + color: rgb(64, 64, 64); + position: relative; +/* overflow: hidden;*/ +/* resize: none;*/ +/* white-space: nowrap;*/ +} +.height-200 { + height:200px; +} + +// http://css-tricks.com/snippets/css/glowing-blue-input-highlights/ +input[type=text], textarea { + -webkit-appearance: textarea; + -webkit-transition: all 0.30s ease-in-out; + -moz-transition: all 0.30s ease-in-out; + -ms-transition: all 0.30s ease-in-out; + -o-transition: all 0.30s ease-in-out; + outline: none; + padding: 3px 0px 3px 3px; + margin: 5px 1px 3px 0px; + border: 1px solid #DDDDDD; +} + +input[type=text]:focus, textarea:focus { + box-shadow: 0 0 5px #333333; + border: 1px solid #808080; + outline-color: #333333; + background-color: #F5F5F5; +} +.underline {border-bottom: 3px solid #808080;} +.hide { + display: none; +} +.show { + display: block; + margin-top:6px; + border-top-width:1px; +} +.replace { + color: blue; + font-weight: bold; +} +.ticket { + color: #000000; + background-color: #F5C669; + border-width: 1px 1px 1px 1px; + padding: 3px 3px 3px 3px; + border-style: dashed; + border-color: #000; + cursor: pointer; +} +.confirmation { + font-weight: bold; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index:999; + opacity: 0; + -webkit-transition: opacity 4s ease-in; + -moz-transition: opacity 4s ease-in; + -o-transition: opacity 4s ease-in; + -ms-transition: opacity 4s ease-in; + transition: opacity 4s ease-in; +} +::-webkit-input-placeholder { + color: red; + font-weight: bold; +} +:-moz-placeholder { /* Firefox 18- */ + color: red; + font-weight: bold; +} +::-moz-placeholder { /* Firefox 19+ */ + color: red; + font-weight: bold; +} +:-ms-input-placeholder { + color: red; + font-weight: bold; +} +tr:nth-child(even){ background:#F4F4F4; } +th { background: #B3B3B3; color: white; font-weight: bold; } diff --git a/src/html/ReqMgr/templates/admin.tmpl b/src/html/ReqMgr/templates/admin.tmpl new file mode 100644 index 0000000000..293b55b5d9 --- /dev/null +++ b/src/html/ReqMgr/templates/admin.tmpl @@ -0,0 +1,217 @@ +
+ + +
+
+ +
+ + + +
+
Team management
+
+
+ + + +

+ +

+
+
+
+ + + + +
+
User management
+
+
+
+

+ + + + +

+
+
+ The roles will be assigned according with user DN registered in SiteDB +
+
+
+
+
+
+

+ + + + +

+
+
+ Upon this request given user name will be deleted from ReqMgr +
+
+
+
+ +
+ + + +
+ +

Existing accounts

+
+ + + + + + + + + + + + + + + + + + + + + +
UsersRoles
User 1Role 1
User 2Role 2
User 3Role 3
+
+ +
+ + + +
+ +

Existing teams

+
+Team 1: some description +
+ +
+ +
+Team 2: some description +
+ +
+ +
+Team 3: some description +
+ +
+ +
+ + diff --git a/src/html/ReqMgr/templates/apis.tmpl b/src/html/ReqMgr/templates/apis.tmpl new file mode 100644 index 0000000000..d6f785a180 --- /dev/null +++ b/src/html/ReqMgr/templates/apis.tmpl @@ -0,0 +1,36 @@ +

ReqMgr web UI mock-up

+
+This is a prototype web UI interface. All menus are click-able +but their actions are fakes. Please take a few minutes to +explore and provide your feedback. Thank you. + +
+
+ +
ReqMgr APIs
+
+This section will contain information about public APIs. We believe that +web UI will fully utilize those APIs. + + +
+ +
ReqMgr scripts
+
+This section will provide information about various scripts used by data-ops. +We think that sharing this information in one common place is beneficials for +everyone. + + diff --git a/src/html/ReqMgr/templates/approve.tmpl b/src/html/ReqMgr/templates/approve.tmpl new file mode 100644 index 0000000000..ab43ab5549 --- /dev/null +++ b/src/html/ReqMgr/templates/approve.tmpl @@ -0,0 +1,113 @@ +#from WMCore.ReqMgr.Web.utils import gen_color +#from WMCore.ReqMgr.Tools.cms import next_status + +

Approve interface

+
+ +
+ + +
+ +
+#set ids=[] +#for rdict in $requests +#set rstat=$rdict['RequestStatus'] +#set keys=$rdict.keys() +#silent $keys.sort() +#silent $keys.remove('RequestName') ## we want RequestName to be first in a list +#for key in ['RequestName']+$keys +#set val=$rdict[$key] +#if $key=="RequestDate" +#set val="%s"%$val +#elif $key=="RequestName" +#set id="request-"+$val +#silent $ids.append($id) +#set val='%s'%($base,$val,$val) +#end if +#if $key=="RequestStatus" + $key.capitalize(): + #set color=$gen_color($rstat) + $val +#elif $key=="RequestName" +
+ + +
+#else + $key.capitalize(): + $val +
+#end if +#end for +Request seen by data-ops on 02/02/02. +
+#end for + + diff --git a/src/html/ReqMgr/templates/assign.tmpl b/src/html/ReqMgr/templates/assign.tmpl new file mode 100644 index 0000000000..39a17b06a8 --- /dev/null +++ b/src/html/ReqMgr/templates/assign.tmpl @@ -0,0 +1,364 @@ +
+ + +
+
+ +
+
+ + +
+ +
+ + + + + + + + + + + + + #for doc in $requests + #set id=$doc['RequestName'] + + + + + + + + + #end for + +
+ + Select + RequestsUsersGroupsDatesStatus
+ + $id$doc['Requestor']$doc['Group']$doc['RequestDate']$doc['RequestStatus']
+
+
+ +
+
+

Assign settings

+ +
Site lists
+
    +
  • Whitelist
    + +
  • +
  • Blacklist
    + +
  • +
+ +
+ +
PhEDEx subscription
+
    +
  • Custodial sites
    + +
  • +
  • Non-Custodial sites
    + +
  • +
  • Auto-Approve subscription sites
    + +
  • +
+ +
+ +
Misc Options
+
+ + +#* + + + + + + + + + + + + + + + + + + + +*# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +#* + + + + +*# + + + + + + + + +#* + + + + +*# + + + + + + +
CMSSW version + +
SCRAM architecture + +
Global Tag
Campaign
Subscription Priority + +
Custodial Subscription Type + +
Merged LFN Base + +
Unmerged LFN Base + +
Min Merge Size
Max Merge Size
Max Merge Events
Memory limit RSS (KiBytes)
Memory limit VSS (KiBytes)
Timeout (Seconds)
GracePeriod (Seconds)
Block close timeout (Seconds)
Block close max number of files
Block close max number of events
Block close max block size (Bytes)
Acquisition Era:
Processing Version:
Processing String:
Dashboard Activity: + +
Group name + +
+
+ + + +
+ +#* +
+
+ +
+
+*# +
+ + diff --git a/src/html/ReqMgr/templates/confirm.tmpl b/src/html/ReqMgr/templates/confirm.tmpl new file mode 100644 index 0000000000..7693b40dac --- /dev/null +++ b/src/html/ReqMgr/templates/confirm.tmpl @@ -0,0 +1,35 @@ +#import time +
Confirmation
+
+
+#if isinstance($ticket, list) +#for t in $ticket +Your request  $t  +has been created on $time.ctime() +
+#end for +#else +Your request  $ticket  +has been created on $time.ctime() +#end if +
+#if $status=="ok" +#set scol="green" +#else +#set scol="red" +#end if +Status: $status +
+
+
Further actions
+
    +#if not isinstance($ticket, list) +
  • +Check status of this request +
  • +#end if +
  • +Check status of all requests +
  • +
+
diff --git a/src/html/ReqMgr/templates/create.tmpl b/src/html/ReqMgr/templates/create.tmpl new file mode 100644 index 0000000000..dd852697a8 --- /dev/null +++ b/src/html/ReqMgr/templates/create.tmpl @@ -0,0 +1,115 @@ +

Create request interface

+
+
+ + +
+ +
+
+ +Please use the following table to fill out template form. If you need to extend it +fill free to switch to JSON format and edit JSON directly. + +$table + + + +
+ Apply a script: + + + if provided, it will generate series of requests from given template
+ +
+

+ +

+
+ +
+
+ + + + + +
+ Apply a script: + + + if provided, it will generate series of requests from given template
+ +
+

+ +

+
+ +
+ + diff --git a/src/html/ReqMgr/templates/error.tmpl b/src/html/ReqMgr/templates/error.tmpl new file mode 100644 index 0000000000..cd11f2a368 --- /dev/null +++ b/src/html/ReqMgr/templates/error.tmpl @@ -0,0 +1,6 @@ +

Error page

+
+Something bad happens with your request and you end-up on this page. +Here is description of the error:
+ +$content diff --git a/src/html/ReqMgr/templates/generic.tmpl b/src/html/ReqMgr/templates/generic.tmpl new file mode 100644 index 0000000000..5933c99c95 --- /dev/null +++ b/src/html/ReqMgr/templates/generic.tmpl @@ -0,0 +1,6 @@ +
+ $menu +
+
$content
+
+
diff --git a/src/html/ReqMgr/templates/json/DataProcessing.tmpl b/src/html/ReqMgr/templates/json/DataProcessing.tmpl new file mode 100644 index 0000000000..262c65c44f --- /dev/null +++ b/src/html/ReqMgr/templates/json/DataProcessing.tmpl @@ -0,0 +1,34 @@ +{ +"RequestString":"REPLACE-REQUEST", +"PrepID":"REPLACE-PREPID", +"Requestor":$user, +"RequestorDN":$dn, +"Group":$groups, +"RequestPriority":1, +"CMSSWVersion":$releases, +"ScramArch": $arch, +"IncludeParents": [true, false], +"Campaign":"", +"MultiCore":"", +"Scenario":$scenarios, +"GlobalTag":"REPLACE-TAG", +"EnableHarvesting": false, +"DQMConfigCacheID":"", +"DQMSequences":"", +"DQMUploadURL":$dqm_urls, +"InputDataset":"REPLACE-/A/B/C", +"OpenRunningTimeout":0, +"DbsUrl":$dbs_url, +"CouchDBName":$couch_dbname, +"CouchURL":$couch_url, +"CouchWorkloadDBName":$couch_wdbname, +"RunWhitelist":[], +"RunBlacklist":[], +"BlockWhitelist":[], +"BlockBlacklist":[], +"TimePerEvent":12.0, +"Memory":2300.0, +"SizePerEvent": 512.0, +"RequestType": "DataProcessing" +} + diff --git a/src/html/ReqMgr/templates/json/MonteCarlo.tmpl b/src/html/ReqMgr/templates/json/MonteCarlo.tmpl new file mode 100644 index 0000000000..69e89131d8 --- /dev/null +++ b/src/html/ReqMgr/templates/json/MonteCarlo.tmpl @@ -0,0 +1,29 @@ +{ +"RequestString":"REPLACE-REQUEST", +"PrepID":"REPLACE-PREPID", +"Requestor":$user, +"RequestorDN":$dn, +"Group":$groups, +"RequestPriority":1, +"CMSSWVersion":$releases, +"ScramArch": $arch, +"RequestNumEvents": 1000, +"FilterEfficiency": 1.0, +"TotalTime": 32400, +"TimePerEvent":12.0, +"Memory":2300.0, +"SizePerEvent": 512.0, +"FirstEvent": 1, +"ConfigCacheUrl":$cc_url, +"ConfigCacheID":$cc_id, +"CouchDBName":$couch_dbname, +"CouchURL":$couch_url, +"CouchWorkloadDBName":$couch_wdbname, +"Scenario":$scenarios, +"GlobalTag":"REPLACE-TAG", +"PrimaryDataset":"REPLACE-ABC", +"DataPileup":"", +"MCPileup":"", +"RequestType": "MonteCarlo" +} + diff --git a/src/html/ReqMgr/templates/json/ReDigi.tmpl b/src/html/ReqMgr/templates/json/ReDigi.tmpl new file mode 100644 index 0000000000..884ef57550 --- /dev/null +++ b/src/html/ReqMgr/templates/json/ReDigi.tmpl @@ -0,0 +1,41 @@ +{ +"RequestString":"REPLACE-REQUEST", +"PrepID":"REPLACE-PREPID", +"Requestor":$user, +"RequestorDN":$dn, +"Group":$groups, +"RequestPriority":1, +"CMSSWVersion":$releases, +"ScramArch": $arch, +"IncludeParents": [true, false], +"Campaign":"REPLACE-CAMPAING", +"InputDataset":"REPLACE-/A/B/C", +"OpenRunningTimeout":0, +"DbsUrl":$dbs_url, +"EnableHarvesting": false, +"DQMConfigCacheID":"", +"DQMSequences":"", +"DQMUploadURL":$dqm_urls, +"StepOneConfigCacheID":"REPLACE-CACHEID", +"StepOneOutputModuleName":"", +"StepTwoConfigCacheID":"", +"StepTwoOutputModuleName":"", +"StepThreeConfigCacheID":"", +"KeepStepOneOutput": [true, false], +"KeepStepTwoOutput": [true, false], +"GlobalTag":"REPLACE-TAG", +"DataPileup":"", +"MCPileup":"", +"RunWhitelist":[], +"RunBlacklist":[], +"BlockWhitelist":[], +"BlockBlacklist":[], +"TimePerEvent":12.0, +"Memory":2300.0, +"SizePerEvent": 512.0, +"CouchDBName":$couch_dbname, +"CouchURL":$couch_url, +"CouchWorkloadDBName":$couch_wdbname, +"RequestType": "DataProcessing" +} + diff --git a/src/html/ReqMgr/templates/json/ReReco.tmpl b/src/html/ReqMgr/templates/json/ReReco.tmpl new file mode 100644 index 0000000000..cf8deb2d06 --- /dev/null +++ b/src/html/ReqMgr/templates/json/ReReco.tmpl @@ -0,0 +1,38 @@ +{ +"RequestString":"REPLACE-REQUEST", +"PrepID":"REPLACE-PREPID", +"Requestor":$user, +"RequestorDN":$dn, +"Group":$groups, +"RequestPriority":1, +"CMSSWVersion":$releases, +"ScramArch": $arch, +"IncludeParents": [true, false], +"Campaign": "REPLACE-CAMPAING", +"Scenario":$scenarios, +"GlobalTag":"REPLACE-TAG", +"EnableHarvesting": false, +"DQMConfigCacheID":"", +"DQMSequences":"", +"DQMUploadURL":$dqm_urls, +"InputDataset":"REPLACE-/A/B/C", +"OpenRunningTimeout":0, +"DbsUrl":$dbs_url, +"RunWhitelist":[], +"RunBlacklist":[], +"BlockWhitelist":[], +"BlockBlacklist":[], +"FilterEfficiency": 1, +"ConfigCacheUrl":$cc_url, +"ConfigCacheID":$cc_id, +"CouchDBName":$couch_dbname, +"CouchURL":$couch_url, +"CouchWorkloadDBName":$couch_wdbname, +"SplittingAlgo": "LumiBased", +"LumisPerJob": 8, +"inputMode": "couchDB", +"TimePerEvent":12.0, +"Memory":2300.0, +"SizePerEvent": 512.0, +"RequestType": "ReReco" +} diff --git a/src/html/ReqMgr/templates/json/Resubmission.tmpl b/src/html/ReqMgr/templates/json/Resubmission.tmpl new file mode 100644 index 0000000000..bef744a97b --- /dev/null +++ b/src/html/ReqMgr/templates/json/Resubmission.tmpl @@ -0,0 +1,22 @@ +{ +"RequestName":"REPLACE-REQUEST", +"PrepID":"REPLACE-PREPID", +"Requestor":$user, +"RequestorDN":$dn, +"Group":$groups, +"RequestPriority":1, +"OriginalRequestName": "", +"InitialTaskPath":"", +"ACDCServer":"", +"ACDCDatabase":"", +"IgnoredOutputModules": "", +"CollectionName":"", +"TimePerEvent":12.0, +"Memory":2300.0, +"SizePerEvent": 512.0, +"ACDCDatabase": $acdc_dbname, +"ACDCServer": $acdc_url, +"InitialTaskPath": "REPLACE-PATH", +"RequestType": "Resubmission" +} + diff --git a/src/html/ReqMgr/templates/json/StoreResults.tmpl b/src/html/ReqMgr/templates/json/StoreResults.tmpl new file mode 100644 index 0000000000..4fee05a074 --- /dev/null +++ b/src/html/ReqMgr/templates/json/StoreResults.tmpl @@ -0,0 +1,24 @@ +{ +"RequestString":"REPLACE-REQUEST", +"PrepID":"REPLACE-PREPID", +"Requestor":$user, +"RequestorDN":$dn, +"Group":$groups, +"RequestPriority":1, +"AcquisitionEra":"", +"ProcessingVersion":"", +"GlobalTag":"REPLACE-TAG", +"CmsPath":"REPLACE-PATH", +"CMSSWVersion":$releases, +"ScramArch": $arch, +"InputDataset":"REPLACE-/A/B/C", +"OpenRunningTimeout":"", +"DbsUrl":$dbs_url, +"TimePerEvent":12.0, +"Memory":2300.0, +"SizePerEvent": 512.0, +"CouchURL":$couch_url, +"CouchWorkloadDBName":$couch_wdbname, +"RequestType": "StoreResults" +} + diff --git a/src/html/ReqMgr/templates/main.tmpl b/src/html/ReqMgr/templates/main.tmpl new file mode 100644 index 0000000000..6efa043813 --- /dev/null +++ b/src/html/ReqMgr/templates/main.tmpl @@ -0,0 +1,35 @@ + + + + ReqMgr Prototype + + + + + + + +
+ + +
+
+ +
+ $content +
+
+ + + diff --git a/src/html/ReqMgr/templates/menu.tmpl b/src/html/ReqMgr/templates/menu.tmpl new file mode 100644 index 0000000000..4fd0d43c99 --- /dev/null +++ b/src/html/ReqMgr/templates/menu.tmpl @@ -0,0 +1,15 @@ +
+
+ +
diff --git a/src/html/ReqMgr/templates/requests.tmpl b/src/html/ReqMgr/templates/requests.tmpl new file mode 100644 index 0000000000..d3df611887 --- /dev/null +++ b/src/html/ReqMgr/templates/requests.tmpl @@ -0,0 +1,51 @@ +

Requests

+
+
+ + +
+
+ +#for rdict in $requests +
+ +
+
+#end for diff --git a/src/html/ReqMgr/templates/search.tmpl b/src/html/ReqMgr/templates/search.tmpl new file mode 100644 index 0000000000..4ebb29affc --- /dev/null +++ b/src/html/ReqMgr/templates/search.tmpl @@ -0,0 +1,22 @@ +

Search interface

+
+DB statistics: N-docs, Number TB, last update: 20140303 + +
+This form will provide search capabilities. For example you can enter +the following dataset +
+/RelValSingleMuPt10/CMSSW_6_2_0-PRE_ST62_V8-v1/GEN-SIM-RECO +
+to see how it works. + +
+ +
+ +
$content
diff --git a/src/html/ReqMgr/templates/validate.tmpl b/src/html/ReqMgr/templates/validate.tmpl new file mode 100644 index 0000000000..851004086b --- /dev/null +++ b/src/html/ReqMgr/templates/validate.tmpl @@ -0,0 +1,9 @@ +

Validate interface

+
+Paste your JSON into provided box: +
+ + +
+ +
diff --git a/src/html/ReqMgr/templates/workflow.tmpl b/src/html/ReqMgr/templates/workflow.tmpl new file mode 100644 index 0000000000..9522211b0a --- /dev/null +++ b/src/html/ReqMgr/templates/workflow.tmpl @@ -0,0 +1,34 @@ +
+
+ + +
+
+ +
+
+ + + +
+ +  + +  + +
+
+ +
+ diff --git a/src/python/WMCore/Cache/WMConfigCache.py b/src/python/WMCore/Cache/WMConfigCache.py index 6ccb0faf59..4cb8018d93 100644 --- a/src/python/WMCore/Cache/WMConfigCache.py +++ b/src/python/WMCore/Cache/WMConfigCache.py @@ -36,8 +36,59 @@ class ConfigCacheException(WMException): """ +class Singleton(type): + """Implementation of Singleton class""" + _instances = {} + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + cls._instances[cls] = \ + super(Singleton, cls).__call__(*args, **kwargs) + return cls._instances[cls] +class DocumentCache(object): + """DocumentCache holds config ids. Use this class as singleton""" + __metaclass__ = Singleton + def __init__(self, database, detail = True): + super(DocumentCache, self).__init__() + self.cache = {} + self.database = database + # flag to decide whether update detail or not when it is prefetched + self.detail = detail + def __getitem__(self, configID): + """ + Internal get method to fetch document based on provided ID + """ + if configID not in self.cache: + self.prefetch([configID]) + return self.cache[configID] + + def cleanup(self, ids): + """ + _cleanup_ + + Clean-up given ids from local cache + """ + for rid in ids: + if rid in self.cache: + del self.cache[rid] + + def prefetch(self, keys): + """ + _fetch_ + + Pre-fetch given list of documents from CouchDB + """ + if self.detail: + options = {'include_docs':True} + else: + options = {} + result = self.database.allDocs(options=options, keys=keys) + for row in result['rows']: + if self.detail: + self.cache[row['id']] = row['doc'] + else: + self.cache[row['id']] = True class ConfigCache(WMObject): """ @@ -46,10 +97,11 @@ class ConfigCache(WMObject): The class that handles the upload and download of configCache artifacts from Couch """ - def __init__(self, dbURL, couchDBName = None, id = None, rev = None, usePYCurl = False, ckey = None, cert = None, capath = None): + def __init__(self, dbURL, couchDBName = None, id = None, rev = None, usePYCurl = False, + ckey = None, cert = None, capath = None, detail = True): self.dbname = couchDBName self.dburl = dbURL - + self.detail = detail try: self.couchdb = CouchServer(self.dburl, usePYCurl=usePYCurl, ckey=ckey, cert=cert, capath=capath) if self.dbname not in self.couchdb.listDatabases(): @@ -62,6 +114,9 @@ def __init__(self, dbURL, couchDBName = None, id = None, rev = None, usePYCurl = logging.error(msg) raise ConfigCacheException(message = msg) + # local cache + self.docs_cache = DocumentCache(self.database, self.detail) + # UserGroup variables self.group = None self.owner = None @@ -212,6 +267,14 @@ def saveAttachment(self, name, attachment): return + def loadDocument(self, configID): + """ + _loadDocument_ + + Load a document from the document cache given its couchID + """ + self.document = self.docs_cache[configID] + def loadByID(self, configID): """ _loadByID_ @@ -496,3 +559,38 @@ def __str__(self): """ return self.document.__str__() + + def validate(self, configID): + + try: + self.loadDocument(configID = configID) + except Exception, ex: + raise ConfigCacheException("Failure to load ConfigCache while validating workload: %s" % str(ex)) + + if self.detail: + duplicateCheck = {} + try: + outputModuleInfo = self.getOutputModuleInfo() + except Exception, ex: + # Something's gone wrong with trying to open the configCache + msg = "Error in getting output modules from ConfigCache during workload validation. Check ConfigCache formatting!" + raise ConfigCacheException("%s: %s" % (msg, str(ex))) + for outputModule in outputModuleInfo.values(): + dataTier = outputModule.get('dataTier', None) + filterName = outputModule.get('filterName', None) + if not dataTier: + raise ConfigCacheException("No DataTier in output module.") + + # Add dataTier to duplicate dictionary + if not dataTier in duplicateCheck.keys(): + duplicateCheck[dataTier] = [] + if filterName in duplicateCheck[dataTier]: + # Then we've seen this combination before + raise ConfigCacheException("Duplicate dataTier/filterName combination.") + else: + duplicateCheck[dataTier].append(filterName) + return outputModuleInfo + else: + return True + + diff --git a/src/python/WMCore/HTTPFrontEnd/RequestManager/Assign.py b/src/python/WMCore/HTTPFrontEnd/RequestManager/Assign.py index ccf3756e75..777708f534 100644 --- a/src/python/WMCore/HTTPFrontEnd/RequestManager/Assign.py +++ b/src/python/WMCore/HTTPFrontEnd/RequestManager/Assign.py @@ -339,8 +339,8 @@ def assignWorkload(self, requestName, kwargs): helper.setMergeParameters(int(kwargs.get("MinMergeSize", 2147483648)), int(kwargs.get("MaxMergeSize", 4294967296)), int(kwargs.get("MaxMergeEvents", 50000))) - helper.setupPerformanceMonitoring(int(kwargs.get("maxRSS", 2411724)), - int(kwargs.get("maxVSize", 20411724)), + helper.setupPerformanceMonitoring(int(kwargs.get("MaxRSS", 2411724)), + int(kwargs.get("MaxVSize", 20411724)), int(kwargs.get("SoftTimeout", 129600)), int(kwargs.get("GracePeriod", 300))) @@ -375,7 +375,7 @@ def assignWorkload(self, requestName, kwargs): helper.setBlockCloseSettings(blockCloseMaxWaitTime, blockCloseMaxFiles, blockCloseMaxEvents, blockCloseMaxSize) - helper.setDashboardActivity(kwargs.get("dashboard", "")) + helper.setDashboardActivity(kwargs.get("Dashboard", "")) Utilities.saveWorkload(helper, request['RequestWorkflow'], self.wmstatWriteURL) # update AcquisitionEra in the Couch document (#4380) diff --git a/src/python/WMCore/REST/Main.py b/src/python/WMCore/REST/Main.py index ffde1ca394..df1c1cb668 100644 --- a/src/python/WMCore/REST/Main.py +++ b/src/python/WMCore/REST/Main.py @@ -94,7 +94,7 @@ def access(self): 'H': self.host, 'h': remote.name or remote.ip, 'r': request.request_line, - 's': response.status.split(" ", 1)[0], + 's': response.status, # request.rfile.rfile.bytes_read is a custom CMS web # cherrypy patch not always available, hence the test 'i': (getattr(request.rfile, 'rfile', None) diff --git a/src/python/WMCore/REST/Test.py b/src/python/WMCore/REST/Test.py index a39da018c4..372f193502 100644 --- a/src/python/WMCore/REST/Test.py +++ b/src/python/WMCore/REST/Test.py @@ -9,7 +9,7 @@ def fake_authz_headers(hmac_key, method = 'HNLogin', login = 'test', name = 'Test User', - dn = None, roles = {}): + dn = None, roles = {}, format = "list"): """Create fake authentication and authorisation headers compatible with the CMSWEB front-ends. Assumes you have the HMAC signing key the back-end will use to validate the headers. @@ -50,14 +50,17 @@ def fake_authz_headers(hmac_key, method = 'HNLogin', cksum = hmac.new(hmac_key, prefix + "#" + suffix, hashlib.sha1).hexdigest() headers['cms-authn-hmac'] = cksum - return headers.items() + if format == "list": + return headers.items() + else: + return headers -def fake_authz_key_file(): +def fake_authz_key_file(delete=True): """Create temporary file for fake authorisation hmac signing key. :returns: Instance of :class:`~.NamedTemporaryFile`, whose *data* attribute contains the HMAC signing binary key.""" - t = NamedTemporaryFile() + t = NamedTemporaryFile(delete=delete) t.data = open("/dev/urandom").read(20) t.write(t.data) t.seek(0) diff --git a/src/python/WMCore/ReqMgr/Auth.py b/src/python/WMCore/ReqMgr/Auth.py new file mode 100644 index 0000000000..7895487c6c --- /dev/null +++ b/src/python/WMCore/ReqMgr/Auth.py @@ -0,0 +1,47 @@ +""" +Define Authentiction roles and groups for the access permission for writing the the database +WMCore.REST.Auth authz_match +""" + +DEFAULT_PERMISSION = {'role': ['Developer', 'Admin', 'Data Manager', 'developer', 'admin', 'data-manager'], + 'group': []} + +ADMIN_PERMISSION = {'role': ['Admin', 'admin'], 'group': ['ReqMgr', 'reqmgr']} + +DEFAULT_STATUS_PERMISSION = {'role': ['Developer', 'Admin', 'Data Manager', 'developer', 'admin', 'data-manager'], + 'group': []} + +CREATE_PERMISSION = {'role': ['Developer', 'Admin', 'Data Manager', 'developer', 'admin', 'data-manager'], + 'group': ['ReqMgr', 'reqmgr']} + +ASSIGN_PERMISSION = DEFAULT_STATUS_PERMISSION + +APPROVE_PERMISSION = DEFAULT_STATUS_PERMISSION + +STORE_RESULT_ASSIGN_PERMISSION = {'role': ['Developer', 'Admin', 'Data Manager', 'developer', 'admin', 'data-manager'], + 'group': []} + +STORE_RESULT_APPROVE_PERMISSION = {'role': ['Developer', 'Admin', 'Data Manager', 'developer', 'admin', 'data-manager'], + 'group': []} + +STORE_RESULT_CREATE_PERMISSION = CREATE_PERMISSION + +def getPermissionByStatusType(requestType, requestStatus): + if requestType == 'StoreResult': + return {'assigned': STORE_RESULT_ASSIGN_PERMISSION, + 'approved': STORE_RESULT_APPROVE_PERMISSION, + 'new': CREATE_PERMISSION + }.get(requestStatus, DEFAULT_STATUS_PERMISSION) + else: + return {'assigned': ASSIGN_PERMISSION, + 'approved': APPROVE_PERMISSION, + 'new': CREATE_PERMISSION + }.get(requestStatus, DEFAULT_STATUS_PERMISSION) + +def getWritePermission(request_args, config=None): + requestType = request_args["RequestType"] + requestStatus = request_args.get("RequestStatus", None) + if requestStatus: + return getPermissionByStatusType(requestType, requestStatus); + else: + return ADMIN_PERMISSION diff --git a/src/python/WMCore/ReqMgr/CherryPyThreads/DataCacheUpdate.py b/src/python/WMCore/ReqMgr/CherryPyThreads/DataCacheUpdate.py new file mode 100644 index 0000000000..1e7f700c99 --- /dev/null +++ b/src/python/WMCore/ReqMgr/CherryPyThreads/DataCacheUpdate.py @@ -0,0 +1,39 @@ +''' + +''' +import cherrypy + +from WMCore.ReqMgr.DataStructs.DataCache import DataCache +from WMCore.ReqMgr.DataStructs.RequestStatus import ACTIVE_STATUS +from WMCore.ReqMgr.CherryPyThreads.CherryPyPeriodicTask import CherryPyPeriodicTask +from WMCore.Services.RequestDB.RequestDBReader import RequestDBReader +from WMCore.Services.WMStats.WMStatsReader import WMStatsReader + +class CouchDBCleanup(CherryPyPeriodicTask): + + def __init__(self, rest, config): + + CherryPyPeriodicTask.__init__(self, config) + + def setConcurrentTasks(self, config): + """ + sets the list of functions which + """ + self.concurrentTasks = [{'func': self.agentDataUpdate, 'duration': config.agentUpdateDuration}] + + def gatherActiveDataStats(self, config): + """ + gather active data statistics + """ + try: + if DataCache.islatestJobDataExpired(): + reqDB = RequestDBReader(config.requestDBURL) + wmstatsDB = WMStatsReader(config.wmstatsURL) + + requestNames = reqDB.getRequestByStatus(ACTIVE_STATUS) + jobData = wmstatsDB.getLatestJobInfoByRequests(requestNames) + DataCache.setlatestJobData(jobData) + + except Exception, ex: + cherrypy.log.error(str(ex)) + return \ No newline at end of file diff --git a/src/python/WMCore/ReqMgr/DataStructs/DataCache.py b/src/python/WMCore/ReqMgr/DataStructs/DataCache.py new file mode 100644 index 0000000000..2ff7c7741c --- /dev/null +++ b/src/python/WMCore/ReqMgr/DataStructs/DataCache.py @@ -0,0 +1,40 @@ +import time + +class DataCache(object): + # TODO: need to change to store in db instead of storing in the memory + # When mulitple server run for load balancing it could have different result + # from each server. + _duration = 300 # 5 minitues + _lastedActiveDataFromAgent = {}; + + @staticmethod + def getDuration(): + return DataCache._duration; + + @staticmethod + def setDuration(sec): + DataCache._duration = sec; + + @staticmethod + def getlatestJobData(): + if (DataCache._lastedActiveDataFromAgent): + return DataCache._lastedActiveDataFromAgent["data"] + else: + return None + + + @staticmethod + def setlatestJobData(jobData): + DataCache._lastedActiveDataFromAgent["time"] = int(time.time()) + DataCache._lastedActiveDataFromAgent["data"] = jobData + + + @staticmethod + def islatestJobDataExpired(): + if not DataCache._lastedActiveDataFromAgent: + return True + + if (int(time.time()) - DataCache._lastedActiveDataFromAgen["time"]) > DataCache._duration: + return True + return False + diff --git a/src/python/WMCore/ReqMgr/DataStructs/Request.py b/src/python/WMCore/ReqMgr/DataStructs/Request.py index 7fca4fd591..020285e61d 100644 --- a/src/python/WMCore/ReqMgr/DataStructs/Request.py +++ b/src/python/WMCore/ReqMgr/DataStructs/Request.py @@ -15,101 +15,60 @@ necessary request parameters and not any optional ones. """ +import time +import cherrypy +from WMCore.ReqMgr.DataStructs.RequestStatus import REQUEST_START_STATE - -class RequestDataError(Exception): - pass - - - -class Request(dict): - """ - Data Object representing a request. - +def initialize_request_args(request, config, clone = False): """ - - def __init__(self): - dict.__init__(self) - # TODO - # should discuss if list here all possible request arguments - # although some make sense only for certain workloads or - # only the common ones ... - - # these arguments are figured out automatically by ReqMgr, - # ReqMgr fails to inject a request specifying any of these - # arguments in the user input - self.setdefault("RequestName", None) - self.setdefault("RequestStatus", None) - self.setdefault("Requestor", None) - self.setdefault("RequestWorkflow", None) - self.setdefault("RequestDate", None), + Request data class request is a dictionary representing + a being injected / created request. This method initializes + various request fields. This should be the ONLY method to + manipulate request arguments upon injection so that various + levels or arguments manipulation does not occur accros several + modules and across about 7 various methods like in ReqMgr1. + + request is changed here. + + """ + + #user information for cert. (which is converted to cherry py log in) + request["Requestor"] = cherrypy.request.user["login"] + request["RequestorDN"] = cherrypy.request.user.get("dn", "unknown") + + # assign first starting status, should be 'new' + request["RequestStatus"] = REQUEST_START_STATE + request["RequestTransition"] = [{"Status": request["RequestStatus"], + "UpdateTime": int(time.time()), "DN": request["RequestorDN"]}] + request["RequestDate"] = list(time.gmtime()[:6]) + + #TODO: generate this automatically from the spec + # generate request name using request + generateRequestName(request) + + if not clone: + #update the information from config + request["CouchURL"] = config.couch_host + request["CouchWorkloadDBName"] = config.couch_reqmgr_db + request["CouchDBName"] = config.couch_config_cache_db + request.setdefault("SoftwareVersions", []) + if request["CMSSWVersion"] and (request["CMSSWVersion"] not in request["SoftwareVersions"]): + request["SoftwareVersions"].append(request["CMSSWVersion"]) + # TODO - # reassess if these CouchDB related details are necessary to be stored - # in the request document! ReqMgr1 has all of these 3. - self.setdefault("CouchURL", None) - # name of the ConfigCache database in Couch (historicaly misleading naming) - self.setdefault("CouchDBName", None) - # name of the main ReqMgr CouchDB database - self.setdefault("CouchWorkloadDBName", None) - self._automatic = self.keys() - - # normal input request arguments - to be present in the user - # request input specification - self.setdefault("RequestString", None) - self.setdefault("RequestType", None) - self.setdefault("RequestPriority", None) - self.setdefault("RequestNumEvents", None) - self.setdefault("RequestSizeFiles", None) - self.setdefault("Group", None) - self.setdefault("OutputDatasets", []) - # particular CMSSW version to run on - self.setdefault("CMSSWVersion", None) - # a list of possible CMSSW versions (both these arguments are necessary) - self.setdefault("SoftwareVersions", []) - self.setdefault("InputDatasets", []) - self.setdefault("InputDatasetTypes", {}) - self.setdefault("SizePerEvent", 0) - self.setdefault("PrepID", None) - self.setdefault("ScramArch", None) - self.setdefault("GlobalTag", None) - self.setdefault("ConfigCacheID", None) - self.setdefault("ConfigCacheUrl", None) - self.setdefault("RunWhitelist", None) - self.setdefault("Team", None) - self.setdefault("TotalTime", None) - self.setdefault("TimePerEvent", None) - self.setdefault("Memory", None) - self.setdefault("Campaign", None) - # processing scenario, mutually exclusive with ConfigCacheID - self.setdefault("Scenario", None) - self.setdefault("EnableDQMHarvest", False) - self.setdefault("EnableHarvesting", False) - - - def validate_automatic_args_empty(self): - for arg in self._automatic: - if self[arg]: - msg = "ERROR: Request parameter %s can't be specified by the user." % arg - raise RequestDataError(msg) - - - def lexicon(self, field, validator): - if self.get(field, None) != None: - try: - validator(self[field]) - except AssertionError: - msg = "Request argument validation failed, bad value for %s" % field - raise RequestDataError(msg) - + # do we need InputDataset and InputDatasets? when one is just a list + # containing the other? ; could be related to #3743 problem + if request.has_key("InputDataset"): + request["InputDatasets"] = [request["InputDataset"]] - def __to_json__(self, thunker): - """ - This is here to prevent the serializer from attempting to serialize - this object and adding a bunch of keys that couch won't understand. - - """ - json_dict = {} - for key in self.keys(): - json_dict[key] = self[key] - return json_dict \ No newline at end of file +def generateRequestName(request): + + current_time = time.strftime('%y%m%d_%H%M%S', time.localtime(time.time())) + seconds = int(10000 * (time.time() % 1.0)) + request_string = request.get("RequestString", "") + if request_string != "": + request["RequestName"] = "%s_%s" % (request["Requestor"], request_string) + else: + request["RequestName"] = request["Requestor"] + request["RequestName"] += "_%s_%s" % (current_time, seconds) \ No newline at end of file diff --git a/src/python/WMCore/ReqMgr/DataStructs/RequestError.py b/src/python/WMCore/ReqMgr/DataStructs/RequestError.py new file mode 100644 index 0000000000..48dfa7062e --- /dev/null +++ b/src/python/WMCore/ReqMgr/DataStructs/RequestError.py @@ -0,0 +1,10 @@ +from WMCore.REST.Error import RESTError + +class InvalidStateTransition(RESTError): + "The specified object is invalid." + http_code = 400 + app_code = 1101 + + def __init__(self, current_state, new_state): + RESTError.__init__(self) + self.message = "InvalidStatus Transition: %s to %s" % (current_state, new_state) \ No newline at end of file diff --git a/src/python/WMCore/ReqMgr/DataStructs/RequestStatus.py b/src/python/WMCore/ReqMgr/DataStructs/RequestStatus.py index c6e6f71afd..9e1a8ab13f 100644 --- a/src/python/WMCore/ReqMgr/DataStructs/RequestStatus.py +++ b/src/python/WMCore/ReqMgr/DataStructs/RequestStatus.py @@ -4,90 +4,69 @@ """ # make this list to ensure insertion order here -REQUEST_STATUS_TRANSITION = [ - {"new": ["new", - "testing-approved", - "assignment-approved", - "rejected", - "failed", - "aborted"] - }, - {"testing-approved": ["testing-approved", - "testing", - "test-failed", - "aborted"] - }, - {"testing": ["testing", - "tested", - "test-failed", - "aborted"] - }, - {"tested": ["tested", - "assignment-approved", - "failed", - "rejected", - "aborted"] - }, - {"test-failed": ["test-failed", - "testing-approved", - "rejected", - "aborted"] - }, - {"assignment-approved": ["assignment-approved", - "assigned", - "rejected", - "aborted"] - }, - {"assigned": ["assigned", - "negotiating", - "acquired", - "aborted", - "rejected", - "failed"] - }, - {"negotiating": ["acquired", - "assigned", - "rejected", - "aborted", - "failed", - "negotiating"] - }, - {"acquired": ["running-open", - "failed", - "completed", - "acquired", - "aborted"] - }, - {"running": ["completed", - "aborted", - "failed"] - }, - {"running-open": ["running-closed", - "aborted", - "failed"] - }, - {"running-closed": ["completed", - "aborted", - "failed"] - }, - {"failed": ["failed", - "testing-approved", - "assigned"] - }, - {"completed": ["completed", - "closed-out", - "rejected"] - }, - {"closed-out": ["announced"]}, - {"announced": ["normal-archived"]}, - {"aborted": ["aborted-completed"]}, - {"aborted-completed": ["aborted-archived"]}, - {"rejected": ["rejected-archived"]}, +REQUEST_START_STATE = "new" +REQUEST_STATE_TRANSITION = { + REQUEST_START_STATE: [REQUEST_START_STATE, + "assignment-approved", + "rejected"], + + "assignment-approved": ["assigned", #manual transition + "rejected"], #manual transition + + "assigned": ["negotiating", + "acquired", + "aborted", # manual transition + "failed"], + + "negotiating": ["acquired", + "assigned", + "aborted", + "failed"], + + "acquired": ["running-open", + "completed", + "acquired", + "aborted"], + + "running": ["completed", + "aborted", # manual transition + "failed"], + + "running-open": ["running-closed", + "aborted", # manual transition + "failed"], + + "running-closed": ["force-complete", # manual transition + "completed", + "aborted", # manual transition + "failed"], + + "force-complete" : ["completed"], + + "failed": ["rejected", # manual transition + "assigned"], # manual transition + + "completed": ["completed", + "closed-out", + "rejected"], # manual transition + + "closed-out": ["announced"], # manual transition + + "announced": ["normal-archived"], + + "aborted": ["aborted-completed"], + + "aborted-completed": ["aborted-archived"], + + "rejected": ["rejected-archived"], + # final status - {"normal-archived": []}, - {"aborted-archived": []}, - {"rejected-archived": []}, -] + "normal-archived": [], + + "aborted-archived": [], + + "rejected-archived": [] + } ACTIVE_STATUS = ["new", "assignment-approved", @@ -107,4 +86,13 @@ # each item from STATUS_TRANSITION is a dictionary with 1 item, the key # is name of the status -REQUEST_STATUS_LIST = [s.keys()[0] for s in REQUEST_STATUS_TRANSITION] \ No newline at end of file +REQUEST_STATE_LIST = REQUEST_STATE_TRANSITION.keys() + +def check_allowed_transition(preState, postState): + stateList = REQUEST_STATE_TRANSITION.get(preState, []) + if postState in stateList: + return True + else: + return False + + diff --git a/src/python/WMCore/ReqMgr/ExternalResourceTool.py b/src/python/WMCore/ReqMgr/ExternalResourceTool.py new file mode 100644 index 0000000000..f784fc7a59 --- /dev/null +++ b/src/python/WMCore/ReqMgr/ExternalResourceTool.py @@ -0,0 +1,52 @@ +""" +collection of functions tools to connect external resource. +""" +import re +from WMCore.Services.SiteDB.SiteDB import SiteDBJSON + +def handlePreSideEffect(workloadHelper, request_args): + # handle some thing other than standard procedure of update + # before the REQMGR rest api succeeded + # currently just handling store result assigment-approve and assigned + if workloadHelper.requestType() == "StoreResult": + if request_args.get("RequestStatus") == "assignment-approved": + #TODO: call migration dbs + # 1. dbsapi.migrate + # 2. get location information from dbs and put in xrootd + pass + elif request_args.get("RequestStatus") == "assigned": + # check + pass + +def handlePostSideEffect(workloadHelper, request_args): + # handle some thing other than standard procedure of update + # after the REQMGR rest api succeeded + # currently just handling store result assigment-approve and assigned + if workloadHelper.requestType() == "StoreResult": + if request_args.get("RequestStatus") == "new": + # 1. get physics group from site db and email address + # 2. email them the list + pass + + +def getSiteInfo(config): + sitedb = SiteDBJSON() + sites = sitedb.getAllCMSNames() + sites.sort() + wildcardKeys = getattr(config, 'wildcardKeys', {'T1*': 'T1_*', + 'T2*': 'T2_*', + 'T3*': 'T3_*'}) + wildcardSites = {} + + for k in wildcardKeys.keys(): + reValue = wildcardKeys.get(k) + found = False + for s in sites: + if re.search(reValue, s): + found = True + if not k in wildcardSites.keys(): + wildcardSites[k] = [] + wildcardSites[k].append(s) + if found: + sites.append(k) + return sites diff --git a/src/python/WMCore/ReqMgr/ReqMgrCouch.py b/src/python/WMCore/ReqMgr/ReqMgrCouch.py index c98f6a3705..7425ec4949 100644 --- a/src/python/WMCore/ReqMgr/ReqMgrCouch.py +++ b/src/python/WMCore/ReqMgr/ReqMgrCouch.py @@ -57,6 +57,8 @@ def get_db(self, db_name): db = getattr(self, db_name) return db except AttributeError: + import traceback + import sys trace = traceback.format_exception(*sys.exc_info()) trace_str = ''.join(trace) msg = "Wrong database name '%s':\n%s" % (db_name, trace_str) diff --git a/src/python/WMCore/ReqMgr/Service/Auxiliary.py b/src/python/WMCore/ReqMgr/Service/Auxiliary.py index e033921c82..ed883401ab 100644 --- a/src/python/WMCore/ReqMgr/Service/Auxiliary.py +++ b/src/python/WMCore/ReqMgr/Service/Auxiliary.py @@ -71,10 +71,11 @@ def get(self): # from HTTP headers. CMS web frontend puts this information into # headers as read from SiteDB (or on private VM from a fake # SiteDB file) - print "DN: %s" % cherrypy.request.user['dn'] - print "Requestor/login: %s" % cherrypy.request.user['login'] - print "cherrypy.request: %s" % cherrypy.request - print "cherrypy.request.user: %s" % cherrypy.request.user +# print "cherrypy.request", cherrypy.request +# print "DN: %s" % cherrypy.request.user['dn'] +# print "Requestor/login: %s" % cherrypy.request.user['login'] +# print "cherrypy.request: %s" % cherrypy.request +# print "cherrypy.request.user: %s" % cherrypy.request.user # from WMCore.REST.Auth import authz_match # authz_match(role=["Global Admin"], group=["global"]) # check SiteDB/DataWhoAmI.py @@ -402,4 +403,4 @@ def update_software(config_file): #print msg else: print "CMSSW versions identical, no database update necessary." - """ \ No newline at end of file + """ diff --git a/src/python/WMCore/ReqMgr/Service/Request.py b/src/python/WMCore/ReqMgr/Service/Request.py index 9b977d9f79..2077ea75d5 100644 --- a/src/python/WMCore/ReqMgr/Service/Request.py +++ b/src/python/WMCore/ReqMgr/Service/Request.py @@ -3,64 +3,219 @@ """ -import time import cherrypy -from datetime import datetime, timedelta -import WMCore.Lexicon -from WMCore.Database.CMSCouch import Database, CouchError -from WMCore.WMSpec.WMWorkload import WMWorkloadHelper -from WMCore.WMSpec.StdSpecs.StdBase import WMSpecFactoryException +from WMCore.REST.Error import InvalidParameter +from WMCore.Database.CMSCouch import CouchError +from WMCore.WMSpec.WMWorkloadTools import loadSpecByType from WMCore.Wrappers import JsonWrapper + from WMCore.REST.Server import RESTEntity, restcall, rows -from WMCore.REST.Tools import tools -from WMCore.REST.Validation import validate_str, validate_strlist +from WMCore.REST.Validation import validate_str import WMCore.ReqMgr.Service.RegExp as rx - -from WMCore.ReqMgr.DataStructs.Request import RequestDataError -from WMCore.ReqMgr.DataStructs.RequestStatus import REQUEST_STATUS_LIST -from WMCore.ReqMgr.DataStructs.RequestStatus import REQUEST_STATUS_TRANSITION +from WMCore.ReqMgr.DataStructs.Request import initialize_request_args +from WMCore.ReqMgr.DataStructs.RequestStatus import REQUEST_STATE_LIST +from WMCore.ReqMgr.DataStructs.RequestStatus import REQUEST_STATE_TRANSITION from WMCore.ReqMgr.DataStructs.RequestType import REQUEST_TYPES -from WMCore.ReqMgr.DataStructs.Request import Request as RequestData - +from WMCore.ReqMgr.Utils.Validation import validate_request_create_args, \ + validate_request_update_args +from WMCore.Services.RequestDB.RequestDBWriter import RequestDBWriter class Request(RESTEntity): def __init__(self, app, api, config, mount): # main CouchDB database where requests/workloads are stored RESTEntity.__init__(self, app, api, config, mount) self.reqmgr_db = api.db_handler.get_db(config.couch_reqmgr_db) + self.reqmgr_db_service = RequestDBWriter(self.reqmgr_db, couchapp = "ReqMgr") # this need for the post validtiaon self.reqmgr_aux_db = api.db_handler.get_db(config.couch_reqmgr_aux_db) + + def _requestArgMapFromBrowser(self, request_args): + """ + This is specific mapping function data from browser + + TO: give a key word so it doesn't have to loop though in general + """ + docs = [] + for doc in request_args: + for key in doc.keys(): + if key.startswith('request'): + rid = key.split('request-')[-1] + if rid != 'all': + docs.append(rid) + del doc[key] + return docs + + def _validateGET(self, param, safe): + #TODO: need proper validation but for now pass everything + args_length = len(param.args) + if args_length == 1: + safe.kwargs["name"] = param.args[0] + param.args.pop() + return + + for prop in param.kwargs: + safe.kwargs[prop] = param.kwargs[prop] + + for prop in safe.kwargs: + del param.kwargs[prop] + + return + + def _validateRequestBase(self, param, safe, valFunc, requestName = None): + + data = cherrypy.request.body.read() + if data: + request_args = JsonWrapper.loads(data) + if requestName: + request_args["RequestName"] = requestName + if isinstance(request_args, dict): + request_args = [request_args] + + else: + # actually this is error case + cherrypy.log(str(param.kwargs)) + request_args = {} + for prop in param.kwargs: + request_args[prop] = param.kwargs[prop] + + for prop in request_args: + del param.kwargs[prop] + if requestName: + request_args["RequestName"] = requestName + request_args = [request_args] + + + safe.kwargs['workload_pair_list'] = [] + if isinstance(request_args, dict): + request_args = [request_args] + for args in request_args: + workload, r_args = valFunc(args, self.config, self.reqmgr_db_service, param) + safe.kwargs['workload_pair_list'].append((workload, r_args)) + def _get_request_names(self, ids): + "Extract request names from given documents" + cherrypy.log("***** do this ids %s" % ids) + doc = {} + if isinstance(ids, list): + for rid in ids: + doc[rid] = 'on' + elif isinstance(ids, basestring): + doc[ids] = 'on' + + docs = [] + for key in doc.keys(): + if key.startswith('request'): + rid = key.split('request-')[-1] + if rid != 'all': + docs.append(rid) + del doc[key] + return docs + + def _getMultiRequestArgs(self, multiRequestForm): + request_args = {} + cherrypy.log("***** do this multi %s" % multiRequestForm) + for prop in multiRequestForm: + if prop == "ids": + request_names = self._get_request_names(multiRequestForm["ids"]) + elif prop == "new_status": + request_args["RequestStatus"] = multiRequestForm[prop] + # remove this + #elif prop in ["CustodialSites", "AutoApproveSubscriptionSites"]: + # request_args[prop] = [multiRequestForm[prop]] + else: + request_args[prop] = multiRequestForm[prop] + return request_names, request_args + + def _validateMultiRequests(self, param, safe, valFunc): + + data = cherrypy.request.body.read() + if data: + request_names, request_args = self._getMultiRequestArgs(JsonWrapper.loads(data)) + else: + # actually this is error case + cherrypy.log(str(param.kwargs)) + request_names, request_args = self._getMultiRequestArgs(param.kwargs) + + for prop in request_args: + if prop == "RequestStatus": + del param.kwargs["new_status"] + else: + del param.kwargs[prop] + + del param.kwargs["ids"] + + #remove this + #tmp = [] + #for prop in param.kwargs: + # tmp.append(prop) + #for prop in tmp: + # del param.kwargs[prop] + + safe.kwargs['workload_pair_list'] = [] + + for request_name in request_names: + request_args["RequestName"] = request_name + workload, r_args = valFunc(request_args, self.config, self.reqmgr_db_service, param) + safe.kwargs['workload_pair_list'].append((workload, r_args)) + + safe.kwargs["multi_update_flag"] = True + def validate(self, apiobj, method, api, param, safe): # to make validate successful # move the validated argument to safe # make param empty # other wise raise the error + try: + if method in ['GET']: + self._validateGET(param, safe) + + if method == 'PUT': + args_length = len(param.args) + if args_length == 1: + requestName = param.args[0] + param.args.pop() + else: + requestName = None + self._validateRequestBase(param, safe, validate_request_update_args, requestName) + #TO: handle multiple clone +# if len(param.args) == 2: +# #validate clone case +# if param.args[0] == "clone": +# param.args.pop() +# return None, request_args + + if method == 'POST': + args_length = len(param.args) + if args_length == 1 and param.args[0] == "multi_update": + #special case for multi update from browser. + param.args.pop() + self._validateMultiRequests(param, safe, validate_request_update_args) + else: + self._validateRequestBase(param, safe, validate_request_create_args) + + except Exception, ex: + #TODO add proper error message instead of trace back + import traceback + msg = traceback.format_exc() + print msg + raise InvalidParameter("Missing parameter: %s\n%s" % (str(ex), msg)) + + def initialize_clone(self, request_name): + requests = self.reqmgr_db_service.getRequestByNames(request_name) + clone_args = requests.values()[0] + # overwrite the name and time stamp. + initialize_request_args(clone_args, self.config, clone=True) + # timestamp status update - if method in ['GET', 'PUT', 'POST']: - for prop in param.kwargs: - safe.kwargs[prop] = param.kwargs[prop] - - for prop in safe.kwargs: - del param.kwargs[prop] -# -# permittedParams = ["statusList", "names", "type", "prepID", "inputDataset", -# "outputDataset", "dateRange", "campaign", "workqueue", "team"] -# validate_strlist("statusList", param, safe, '*') -# validate_strlist("names", param, safe, rx.RX_REQUEST_NAME) -# validate_str("type", param, safe, "*", optional=True) -# validate_str("prepID", param, safe, "*", optional=True) -# validate_str("inputDataset", param, safe, rx.RX_REQUEST_NAME, optional=True) -# validate_str("outputDataset", param, safe, rx.RX_REQUEST_NAME, optional=True) -# validate_strlist("dateRagne", param, safe, rx.RX_REQUEST_NAME) -# validate_str("campaign", param, safe, "*", optional=True) -# validate_str("workqueue", param, safe, "*", optional=True) -# validate_str("team", param, safe, "*", optional=True) + spec = loadSpecByType(clone_args["RequestType"]) + workload = spec.factoryWorkloadConstruction(clone_args["RequestName"], + clone_args) + return (workload, clone_args) - + @restcall def get(self, **kwargs): """ @@ -86,7 +241,7 @@ def get(self, **kwargs): status = kwargs.get("status", False) # list of request names name = kwargs.get("name", False) - type = kwargs.get("type", False) + request_type = kwargs.get("request_type", False) prep_id = kwargs.get("prep_id", False) inputdataset = kwargs.get("inputdataset", False) outputdataset = kwargs.get("outputdataset", False) @@ -94,70 +249,82 @@ def get(self, **kwargs): campaign = kwargs.get("campaign", False) workqueue = kwargs.get("workqueue", False) team = kwargs.get("team", False) + # eventhing should be stale view. this only needs for test _nostale = kwargs.get("_nostale", False) option = {} - if not _nostale: - option['stale'] = "update_after" + if _nostale: + self.reqmgr_db_service._setNoStale() request_info =[] - if status and not team: - request_info.append(self.get_reqmgr_view("bystatus" , option, status, "list")) + if status and not team and not request_type: + request_info.append(self.reqmgr_db_service.getRequestByCouchView("bystatus" , option, status)) if status and team: - request_info.append(self.get_reqmgr_view("byteamandstatus", option, team, "list")) + request_info.append(self.reqmgr_db_service.getRequestByCouchView("byteamandstatus", option, [[team, status]])) + if status and request_type: + request_info.append(self.reqmgr_db_service.getRequestByCouchView("byteamandstatus", option, [[team, status]])) if name: - request_doc = self.reqmgr_db.document(name) - request_info.append(rows([request_doc])) + request_info.append(self.reqmgr_db_service.getRequestByNames(name)) if prep_id: - request_info.append(self.get_reqmgr_view("byprepid", option, prep_id, "list")) + request_info.append(self.reqmgr_db_service.getRequestByCouchView("byprepid", option, prep_id)) if inputdataset: - request_info.append(self.get_reqmgr_view("byinputdataset", option, inputdataset, "list")) + request_info.append(self.reqmgr_db_service.getRequestByCouchView("byinputdataset", option, inputdataset)) if outputdataset: - request_info.append(self.get_reqmgr_view("byoutputdataset", option, outputdataset, "list")) + request_info.append(self.reqmgr_db_service.getRequestByCouchView("byoutputdataset", option, outputdataset)) if date_range: - request_info.append(self.get_reqmgr_view("bydate", option, date_range, "list")) + request_info.append(self.reqmgr_db_service.getRequestByCouchView("bydate", option, date_range)) if campaign: - request_info.append( self.get_reqmgr_view("bycampaign", option, campaign, "list")) + request_info.append(self.reqmgr_db_service.getRequestByCouchView("bycampaign", option, campaign)) if workqueue: - request_info.append(self.get_reqmgr_view("byworkqueue", option, workqueue, "list")) + request_info.append(self.reqmgr_db_service.getRequestByCouchView("byworkqueue", option, workqueue)) #get interaction of the request - return self._intersection_of_request_info(request_info); + result = self._intersection_of_request_info(request_info); + if len(result) == 0: + return {} + return rows([result]) def _intersection_of_request_info(self, request_info): - return request_info[0] + requests = {} + if len(request_info) < 1: + return requests + + request_key_set = set(request_info[0].keys()) + for info in request_info: + request_key_set = set(request_key_set) & set(info.keys()) + #TODO: need to assume some data maight not contains include docs + for request_name in request_key_set: + requests[request_name] = request_info[0][request_name] + return requests - def _get_couch_view(self, couchdb, couchapp, view, options, keys, format): + def _get_couch_view(self, couchdb, couchapp, view, options, keys): if not options: options = {} options.setdefault("include_docs", True) - if type(keys) == str: + if isinstance(keys, basestring): keys = [keys] result = couchdb.loadView(couchapp, view, options, keys) - if format == "dict": - request_info = {} - for item in result["rows"]: - request_info[item["id"]] = None - return request_info - else: - request_info = [] - for item in result["rows"]: - request_info.append(item["id"]) - return request_info - + request_info = {} + for item in result["rows"]: + request_info[item["id"]] = item.get('doc', None) + if request_info[item["id"]] != None: + self.filterCouchInfo(request_info[item["id"]]) + return request_info - def get_reqmgr_view(self, view, options, keys, format): - return self._get_couch_view(self.reqmgr_db, "ReqMgr", view, - options, keys, format) + #TODO move this out of this class + def filterCouchInfo(self, couchInfo): + for key in ['_rev', '_attachments']: + if key in couchInfo: + del couchInfo[key] + - def get_wmstats_view(self, view, options, keys, format): + def get_wmstats_view(self, view, options, keys): return self._get_couch_view(self.wmstatsCouch, "WMStats", view, - options, keys, format) - + options, keys) def _combine_request(self, request_info, requestAgentUrl, cache): keys = {} @@ -174,6 +341,30 @@ def _combine_request(self, request_info, requestAgentUrl, cache): return requestAgentUrlList; + def _updateRequest(self, workload, request_args): + + if workload == None: + (workload, request_args) = self.initialize_clone(request_args["OriginalRequestName"]) + return self.post(workload, request_args) + + # if is not just updating status + if len(request_args) > 1 or not request_args.has_key("RequestStatus"): + workload.updateArguments(request_args) + # trailing / is needed for the savecouchUrl function + workload.saveCouch(self.config.couch_host, self.config.couch_reqmgr_db) + dn = cherrypy.request.user.get("dn", "unknown") + report = self.reqmgr_db_service.updateRequestProperty(workload.name(), request_args, dn) + return report + + @restcall + def put(self, workload_pair_list): + "workloadPairList is a list of tuple containing (workload, requeat_args)" + report = [] + for workload, request_args in workload_pair_list: + report = self._updateRequest(workload, request_args) + + return report + @restcall def delete(self, request_name): cherrypy.log("INFO: Deleting request document '%s' ..." % request_name) @@ -189,17 +380,12 @@ def delete(self, request_name): @restcall - def post(self): + def post(self, workload_pair_list, multi_update_flag = False): """ - Create / inject a new request. Request input schema is specified in - the body of the request as JSON encoded data. - - ReqMgr related request arguments validation to happen in - DataStructs.Request.validate(), the rest in spec. - - ReqMgr related arguments manipulation to happen in the .request_initialize(), - before the spec is instantiated. - + Create and update couchDB with a new request. + request argument is passed from validation + (validation convert cherrypy.request.body data to argument) + TODO: this method will have some parts factored out so that e.g. clone call can share functionality. @@ -212,274 +398,21 @@ def post(self): (from ReqMgrRESTModel.putRequest) """ - json_input_request_args = cherrypy.request.body.read() - request_input_dict = JsonWrapper.loads(json_input_request_args) - - cherrypy.log("INFO: Create request, input args: %s ..." % request_input_dict) - - request = RequestData() # this returns a new request dictionary - request.update(request_input_dict) - - try: - request.validate_automatic_args_empty() - # fill in automatic request arguments and further request args meddling - self.request_initialize(request) - self.request_validate(request) - except RequestDataError, ex: - cherrypy.log(ex.message) - raise cherrypy.HTTPError(400, ex.message) - - cherrypy.log("INFO: Request initialization and validation succeeded." - " Instantiating spec/workload ...") - # TODO - # watch the above instantiation, it seems to take rather long time ... - - # will be stored under request["WorkloadSpec"] - self.create_workload_attach_to_request(request, request_input_dict) - - cherrypy.log("INFO: Request corresponding workload instantiated, storing ...") - - helper = WMWorkloadHelper(request["WorkloadSpec"]) - # TODO - # this should be revised for ReqMgr2 - #4378 - ACDC (Resubmission) requests should inherit the Campaign ... - # for Resubmission request, there already is previous Campaign set - # this call would override it with initial request arguments where - # it is not specified, so would become '' - # TODO - # these kind of calls should go into some workload initialization - if not helper.getCampaign(): - helper.setCampaign(request["Campaign"]) - - if request.has_key("RunWhitelist"): - helper.setRunWhitelist(request["RunWhitelist"]) # storing the request document into Couch - - # can't save Request object directly, because it makes it hard to retrieve - # the _rev - # TODO - # don't understand this. may just be possible to keep dealing with - # 'request' and not create this metadata - metadata = {} - metadata.update(request) - - # TODO - # this should be verified and straighten up in ReqMgr2, should not need this - # Add the output datasets if necessary - # for some bizarre reason OutpuDatasets is list of lists, when cloning - # [['/MinimumBias/WMAgentCommissioning10-v2/RECO'], ['/MinimumBias/WMAgentCommissioning10-v2/ALCARECO']] - # #3743 - #if not clone: - # for ds in helper.listOutputDatasets(): - # if ds not in request['OutputDatasets']: - # request['OutputDatasets'].append(ds) - - # Store new request into Couch - try: - # don't want to JSONify the whole workflow - del metadata["WorkloadSpec"] - workload_url = helper.saveCouch(request["CouchURL"], - request["CouchWorkloadDBName"], - metadata=metadata) - # TODO - # this will have to be updated now, when the Couch url is known. The question - # is whether this request argument is necessary at all since it should - # always be CouchUrl/DbName/RequestName/spec so it can easily be derived - # if this not necessary, this below step of updating the document is - # not necessary unlike it was the case in ReqMgr1 - request["RequestWorkflow"] = workload_url - params_to_update = ["RequestWorkflow"] - fields = {} - for key in params_to_update: - fields[key] = request[key] - self.reqmgr_db.updateDocument(request["RequestName"], "ReqMgr", "updaterequest", - fields=fields) - except CouchError, ex: - # TODO simulate exception here to see how much gets exposed to the client - # and how much gets logged when it's like this - msg = "ERROR: Storing into Couch failed, reason: %s" % ex.reason - cherrypy.log(msg) - raise cherrypy.HTTPError(500, msg) - - cherrypy.log("INFO: Request '%s' created and stored." % request["RequestName"]) - # do not want to return to client spec data - del request["WorkloadSpec"] - return rows([request]) - - - def create_workload_attach_to_request(self, request, request_input_dict): - try: - factory_name = "%sWorkloadFactory" % request["RequestType"] - mod = __import__("WMCore.WMSpec.StdSpecs.%s" % request["RequestType"], - globals(), locals(), [factory_name]) - Factory = getattr(mod, factory_name) - except ImportError: - msg = "ERROR: Spec type '%s' not found in WMCore.WMSpec.StdSpecs" % request["RequestType"] - cherrypy.log(msg) - raise RuntimeError, msg - except AttributeError, ex: - msg = "ERROR: Factory not found in Spec for type '%s'" % request["RequestType"] - cherrypy.log(msg) - raise RuntimeError, msg - - try: - factory = Factory() - # TODO - # this method is only used by ReqMgr1, once ReqMgr1 is gone, - # there can be only 1 argument to this method - workload = factory.factoryWorkloadConstruction(workloadName=request["RequestName"], - arguments=request) - self.request_initilize_attach_input_to_workload(workload, request_input_dict) - except WMSpecFactoryException, ex: - msg = "ERROR: Error in spec/workload validation: %s" % ex._message - cherrypy.log(msg) - raise cherrypy.HTTPError(400, msg) - - # make instantiated spec part of the request instance - request["WorkloadSpec"] = workload.data - - def request_validate(self, request): - """ - Validate input request arguments. - Upon call of this method, all automatic request arguments are - already figured out. - - TODO: - Some of these validations will be removed once #4705 is in, in - favour of validation done in specs during instantiation. - - NOTE: - Checking user/group membership? probably impossible, groups is nothing - that would be SiteDB ... (and there is no internal user management here) - - """ - for identifier in ["ScramArch", "RequestName", "Group", "Requestor", - "RequestName", "Campaign", "ConfigCacheID"]: - request.lexicon(identifier, WMCore.Lexicon.identifier) - request.lexicon("CMSSWVersion", WMCore.Lexicon.cmsswversion) - for dataset in ["InputDataset", "OutputDataset"]: - request.lexicon(dataset, WMCore.Lexicon.dataset) - if request["Scenario"] and request["ConfigCacheID"]: - msg = "ERROR: Scenario and ConfigCacheID are mutually exclusive." - raise RequestDataError(msg) - if request["RequestType"] not in REQUEST_TYPES: - msg = "ERROR: Request/Workload type '%s' not known." % request["RequestType"] - raise RequestDataError(msg) - - # check that newly created RequestName does not exist in Couch - # database or requests already, by any chance. - try: - doc = self.reqmgr_db.document(request["RequestName"]) - msg = ("ERROR: Request '%s' already exists in the database: %s." % - (request["RequestName"], doc)) - raise RequestDataError(msg) - except CouchError: - # this is what we want here to happen - document does not exist - pass - - # check that specified ScramArch, CMSSWVersion, SoftwareVersions all - # exist and match - sw = self.reqmgr_aux_db.document("software") - if request["ScramArch"] not in sw.keys(): - msg = ("Specified ScramArch '%s not present in ReqMgr database " - "(data is taken from TC, available ScramArch: %s)." % - (request["ScramArch"], sw.keys())) - raise RequestDataError(msg) - # from previously called request_initialize(), SoftwareVersions contains - # the value from CMSSWVersion, it's enough to validate only SoftwareVersions - for version in request.get("SoftwareVersions", []): - if version not in sw[request["ScramArch"]]: - msg = ("Specified software version '%s' not found for " - "ScramArch '%s'. Supported versions: %s." % - (version, request["ScramArch"], sw[request["ScramArch"]])) - raise RequestDataError(msg) - - - def request_initialize(self, request): - """ - Request data class request is a dictionary representing - a being injected / created request. This method initializes - various request fields. This should be the ONLY method to - manipulate request arguments upon injection so that various - levels or arguments manipulation does not occur accros several - modules and across about 7 various methods like in ReqMgr1. - - request is changed here. - - """ - request["CouchURL"] = self.config.couch_host - request["CouchWorkloadDBName"] = self.config.couch_reqmgr_db - request["CouchDBName"] = self.config.couch_config_cache_db - request["Requestor"] = cherrypy.request.user["login"] - request["RequestorDN"] = cherrypy.request.user.get("dn", "unknown") - # assign first starting status, should be 'new' - request["RequestStatus"] = REQUEST_STATUS_LIST[0] - request["RequestTransition"] = [{"Status": request["RequestStatus"], "UpdateTime": int(time.time())}] - current_time = time.strftime('%y%m%d_%H%M%S', time.localtime(time.time())) - seconds = int(10000 * (time.time() % 1.0)) - request_string = request.get("RequestString", "") - if request_string != "": - request["RequestName"] = "%s_%s" % (request["Requestor"], request_string) - else: - request["RequestName"] = request["Requestor"] - request["RequestName"] += "_%s_%s" % (current_time, seconds) - request["RequestDate"] = list(time.gmtime()[:6]) - - if request["CMSSWVersion"] and request["CMSSWVersion"] not in request["SoftwareVersions"]: - request["SoftwareVersions"].append(request["CMSSWVersion"]) - - # TODO - # do we need InputDataset and InputDatasets? when one is just a list - # containing the other? ; could be related to #3743 problem - if request.has_key("InputDataset"): - request["InputDatasets"] = [request["InputDataset"]] - + if multi_update_flag: + return self.put(workload_pair_list) - def request_initilize_attach_input_to_workload(self, workload, request_input_dict): - """ - request_input_dict are input arguments for request injection - workload is a corresponding newly created workload instance and - request_input_dict is attached to workload under 'schema' - - ReqMgr1 does this in RequestMaker.Registry.loadRequestSchema(), and - this method is only a slight modification of it. - - Storing this original injection time information is probably not - crucially necessary but is definitely practical, keep that. + request_args_list = [] + for workload, request_args in workload_pair_list: + cherrypy.log("INFO: Create request, input args: %s ..." % request_args) + request_args_list.append(request_args) + workload.saveCouch(request_args["CouchURL"], request_args["CouchWorkloadDBName"], + metadata=request_args) + #TODO should return something else instead on whole schema + return request_args_list - """ - wl = workload - rid = request_input_dict - schema = wl.data.request.section_("schema") - for key, value in rid.iteritems(): - try: - setattr(schema, key, value) - except Exception, ex: - # attach TaskChain tasks - # TODO this may be a good example where recursion would be practical - if (type(value) == dict and rid["RequestType"] == 'TaskChain' and - "Task" in key): - new_section = schema.section_(key) - for k, v in rid[key].iteritems(): - try: - setattr(new_section, k, v) - except Exception, ex: - # what does this mean then? - pass - else: - pass - schema.timeStamp = int(time.time()) - wl.data.owner.Group = schema.Group - # TODO - # ReqMgr2 does not allow Requestor request argument specified in the - # injection input so it can't be set here based on request_input_dict. - # If it's absolutely necessary, it can be passed to his method from the - # caller, where it's known, and set on the workload. But I doubt there - # needs to be so much data duplication yet again between stuff stored - # in Couch and on workload. - #wl.data.owner.Requestor = schema.Requestor class RequestStatus(RESTEntity): def __init__(self, app, api, config, mount): @@ -499,9 +432,9 @@ def get(self, transition): """ if transition == "true": - return rows(REQUEST_STATUS_TRANSITION) + return rows(REQUEST_STATE_TRANSITION) else: - return rows(REQUEST_STATUS_LIST) + return rows(REQUEST_STATE_LIST) diff --git a/src/python/WMCore/ReqMgr/Tools/WorkflowTools.py b/src/python/WMCore/ReqMgr/Tools/WorkflowTools.py new file mode 100644 index 0000000000..cd7aeac6f7 --- /dev/null +++ b/src/python/WMCore/ReqMgr/Tools/WorkflowTools.py @@ -0,0 +1,608 @@ +#!/usr/bin/env python +#-*- coding: utf-8 -*- +#pylint: disable= +""" +File : WorkflowManager.py +Author : Valentin Kuznetsov +Description: Workflow management tools +""" + +# system modules +import os +import re +import json +import httplib + +# ReqMgr modules +from ReqMgr.tools.reqMgrClient import WorkflowManager + +# DBS modules +import dbs3Client as dbs3 +from dbs.apis.dbsClient import DbsApi + +# DBS3 helper functions +DBS3 = r'https://cmsweb.cern.ch/dbs/prod/global/DBSReader' +def getDatasets(dataset_pattern): + "Return list of dataset for given dataset pattern" + dbsapi = DbsApi(url=DBS3, verifypeer=False) + reply = dbsapi.listDatasets(dataset=dataset_pattern, dataset_access_type='*') + return reply + +def getDatasetStatus(dataset): + "Return dataset status" + dbsapi = DbsApi(url=DBS3, verifypeer=False) + reply = dbsapi.listDatasets(dataset=dataset, dataset_access_type='*', detail=True) + return reply[0]['dataset_access_type'] + +def getWorkload(url, workflow): + "Return workload list" + conn = httplib.HTTPSConnection(url, + cert_file = os.getenv('X509_USER_PROXY'), + key_file = os.getenv('X509_USER_PROXY')) + r1=conn.request("GET",'/reqmgr/view/showWorkload?requestName='+workflow) + r2=conn.getresponse() + workload=r2.read() + return workload.split('\n') + +class WorkflowDataOpsMgr(WorkflowManager): + def __init__(self, workflow, **kwds): + """ + Extend WorkflowManager and assign data-ops attributes for + given workflow. The order of calls does matter !!! + """ + self.kwds = kwds + self.url = self.get('url', 'cmsweb.cern.ch') + WorkflowManager.__init__(self, workflow, self.url) + self.workload = getWorkload(self.url, workflow) + self.cacheID = self.winfo.get('StepOneConfigCacheID', '') + self.config = getConfig(self.url, self.cacheID) + self.pileup_dataset = self._pileup_dataset() + self.priority = self._priority() + self.era = self.get('era', 'Summer12') + self.lfn = self.get('lfn', '/store/mc') + self.special_name = self.get('specialName', '') + self.max_rss = self.get('maxRSS', 2300000) + self.max_vsize = self.get('maxVSize', 4100000000) + self.input_dataset = '' + self.pileup_scenario = '' + self.global_tag = self.get('globalTag', '') + self.campaign = self.get('campaign', '') + self.max_merge_events = self.get('maxMergeEvents', 50000) + self.activity = self.get('activity', 'reprocessing') + self.restrict = self.get('restrict', 'None') + self.site_use = self.get('site', None) + self.site_cust = self.get('site_cust', None) + self.xrootd = self.get('xrootd', 0) + self.ext_tag = self.get('ext', '') + self.team = self.get('team', '') + + # perform various initialization + self._init() + + # custom settings + # Construct processed dataset version + if self.pileup_scenario: + self.pileup_scenario = self.pileup_scenario+'_' + + specialprocstring = kwds.get('specialName', '') + if specialprocstring: + self.special_name = specialprocstring + '_' + + # ProcessingString + inprocstring = kwds.get('procstring', '') + if inprocstring: + self.procstring = inprocstring + else: + self.procstring = self.special_name + self.pileup_scenario +\ + self.global_tag + self.ext_tag + + # ProcessingVersion + inprocversion = kwds.get('procversion', '') + if inprocversion: + self.procversion = inprocversion + else: + self.procversion = self.dataset_version(self.era, self.procstring) + + def dataset_version(self, era, partialProcVersion): + versionNum = 1 + outputs = self.output_datasets + for output in outputs: + bits = output.split('/') + outputCheck = '/'+bits[1]+'/'+era+'-'+partialProcVersion+'*/'+bits[len(bits)-1] + + datasets = getDatasets(outputCheck) + for dataset in datasets: + datasetName = dataset['dataset'] + matchObj = re.match(r".*-v(\d+)/.*", datasetName) + if matchObj: + currentVersionNum = int(matchObj.group(1)) + if versionNum <= currentVersionNum: + versionNum=versionNum+1 + + return versionNum + + ### private methods + def _init(self): + "Perform initialization and cross-checks" + self.input_dataset = self._input_dataset() + self.global_tag = self._global_tag() + self.ext_tag = self._ext_tag() + self.campaign = self._campaign() + self.era, self.lfn, self.special_name = self._era_lfn_name() + self.pileup_scenario = self._pileup_scenario() + self.max_rss = self._max_rss() + self.max_merge_events = self._max_merge_events() + self.team = self._team() + self.site_use, self.site_cust = self._sites() + + # Checks attributes + checklist = [(self.era, ''), (self.lfn, ''), (self.pileup_scenario, 'Unknown')] + for att, val in checklist: + if att == val: + raise Exception('ERROR: %s == "%s"' % (att, val)) + + # Check status of input dataset + inputDatasetStatus = getDatasetStatus(self.input_dataset) + if inputDatasetStatus != 'VALID' and inputDatasetStatus != 'PRODUCTION': + raise Exception('ERROR: Input dataset is not PRODUCTION or VALID, status=%s' % inputDatasetStatus) + + def get(self, key, default=''): + "Get extension tag" + val = self.kwds.get(key) + if not val: + val = default + return val + + def _ext_tag(self): + "Get extension tag" + if self.ext_tag: + ext_tag = '_ext' + self.ext_tag + else: + ext_tag = '' + return ext_tag + + def _global_tag(self): + "Extract required part of global tag from workflow info" + return self.winfo.get('GlobalTag', '').split('::')[0] + + def _campaign(self): + "Return campaign from workflow info" + return self.winfo.get('Campaign', '') + + def _max_rss(self): + "Return maxRSS" + max_rss = self.max_rss + if ('HiFall11' in self.workflow or 'HiFall13DR53X' in self.workflow) and \ + 'IN2P3' in self.site_use: + max_rss = 4000000 + return max_rss + + def _max_merge_events(self): + "Return max number of merge events" + if 'DR61SLHCx' in self.workflow: + return 5000 + return self.max_merge_events + + def _input_dataset(self): + "Return input dataset of workflow" + dataset = self.winfo.get('InputDataset', '') + if not dataset: + raise Exception("Error: no input dataset found for %s" % self.workflow) + return dataset + + def _era_lfn_name(self): + """ + Return era/lfn/name for given workflow, so far we have hard-coded cases, + later it should be stored persistently and we should have APIs: get/put + to fetch/store/update this info in DB. + """ + workflow = self.workflow + campaign = self.campaign + era = 'Summer12' + lfn = '/store/mc' + specialName = '' + + # Set era, lfn and campaign-dependent part of name if necessary + if 'Summer12_DR51X' in workflow: + era = 'Summer12' + lfn = '/store/mc' + + if 'Summer12_DR52X' in workflow: + era = 'Summer12' + lfn = '/store/mc' + + if 'Summer12_DR53X' in workflow or ('Summer12' in workflow and 'DR53X' in workflow): + era = 'Summer12_DR53X' + lfn = '/store/mc' + + #this is incorrect for HiFall11 workflows, but is changed further down + if 'Fall11_R' in workflow or 'Fall11R' in workflow: + era = 'Fall11' + lfn = '/store/mc' + + if 'Summer13dr53X' in workflow: + era = 'Summer13dr53X' + lfn = '/store/mc' + + if 'Summer11dr53X' in workflow: + era = 'Summer11dr53X' + lfn = '/store/mc' + + if 'Fall11_HLTMuonia' in workflow: + era = 'Fall11' + lfn = '/store/mc' + specialName = 'HLTMuonia_' + + if 'Summer11_R' in workflow: + era = 'Summer11' + lfn = '/store/mc' + + if 'LowPU2010_DR42' in workflow or 'LowPU2010DR42' in workflow: + era = 'Summer12' + lfn = '/store/mc' + specialName = 'LowPU2010_DR42_' + + if 'UpgradeL1TDR_DR6X' in workflow: + era = 'Summer12' + lfn = '/store/mc' + + if 'HiWinter13' in self.input_dataset: + era = 'HiWinter13' + lfn = '/store/himc' + + if 'Spring14dr' in workflow: + era = 'Spring14dr' + lfn = '/store/mc' + if '_castor_' in workflow: + specialName = 'castor_' + + if 'Winter13' in workflow and 'DR53X' in workflow: + era = 'HiWinter13' + lfn = '/store/himc' + + if 'Summer11LegDR' in campaign: + era = 'Summer11LegDR' + lfn = '/store/mc' + + if 'UpgradePhase1Age' in campaign: + era = 'Summer13' + lfn = '/store/mc' + specialName = campaign + '_' + + if campaign == 'UpgradePhase2LB4PS_2013_DR61SLHCx': + era = 'Summer13' + lfn = '/store/mc' + specialName = campaign + '_' + + if campaign == 'UpgradePhase2BE_2013_DR61SLHCx': + era = 'Summer13' + lfn = '/store/mc' + specialName = campaign + '_' + + if campaign == 'UpgradePhase2LB6PS_2013_DR61SLHCx': + era = 'Summer13' + lfn = '/store/mc' + specialName = campaign + '_' + + if campaign == 'UpgradePhase1Age0DES_DR61SLHCx': + era = 'Summer13' + lfn = '/store/mc' + specialName = campaign + '_' + + if campaign == 'UpgradePhase1Age0START_DR61SLHCx': + era = 'Summer13' + lfn = '/store/mc' + specialName = campaign + '_' + + if campaign == 'UpgradePhase1Age3H_DR61SLHCx': + era = 'Summer13' + lfn = '/store/mc' + specialName = campaign + '_' + + if campaign == 'UpgradePhase1Age5H_DR61SLHCx': + era = 'Summer13' + lfn = '/store/mc' + specialName = campaign + '_' + + if campaign == 'UpgradePhase1Age1K_DR61SLHCx': + era = 'Summer13' + lfn = '/store/mc' + specialName = campaign + '_' + + if campaign == 'UpgradePhase1Age3K_DR61SLHCx': + era = 'Summer13' + lfn = '/store/mc' + specialName = campaign + '_' + + #change back to old campaign names for UpgradePhase1 + if 'UpgradePhase1Age' in campaign and 'dr61SLHCx' in specialName: + specialName = specialName.replace("dr61SLHCx","_DR61SLHCx") + if 'dr61SLHCx' in specialName: + print 'WARNING: using new campaign name format' + + if campaign == 'HiFall11_DR44X' or campaign == 'HiFall11DR44': + era = 'HiFall11' + lfn = '/store/himc' + specialName = 'HiFall11_DR44X' + '_' + + if campaign == 'HiFall13DR53X': + era = 'HiFall13DR53X' + lfn = '/store/himc' + + if campaign == 'UpgFall13d': + era = campaign + lfn = '/store/mc' + + if campaign == 'Fall13dr': + era = campaign + lfn = '/store/mc' + if '_castor_tsg_' in workflow: + specialName = 'castor_tsg_' + elif '_castor_' in workflow: + specialName = 'castor_' + elif '_tsg_' in workflow: + specialName = 'tsg_' + elif '__' in workflow: + specialName = '' + else: + print 'ERROR: unexpected special name string in workflow name' + sys.exit(0) + + # Handle NewG4Phys + if campaign == 'Summer12DR53X' and 'NewG4Phys' in workflow: + specialName = 'NewG4Phys_' + + # Handle Ext30 + if campaign == 'Summer12DR53X' and 'Ext30' in workflow: + specialName = 'Ext30_' + + # Handle BS2011 + if campaign == 'LowPU2010DR42' and 'BS2011' in workflow: + specialName = 'LowPU2010_DR42_BS2011_' + + return era, lfn, specialName + + def _pileup_scenario(self): + """ + Return pileup scenario name based on given workflow + Code should be replaced with persistent store. + """ + workflow = self.workflow + campaign = self.campaign + pileupDataset = self._pileup_dataset() + if pileupDataset != 'None': + [subscribedOurSite, subscribedOtherSite] = checkAcceptedSubscriptionRequest(self.url, pileupDataset, siteSE) + if not subscribedOurSite: + print 'ERROR: pileup dataset not subscribed/approved to required Disk endpoint' + sys.exit(0) + + # Determine pileup scenario + # - Fall11_R2 & Fall11_R4 don't add pileup so extract pileup scenario from input + pileupScenario = '' + pileupScenario = getPileupScenario(self.winfo, self.config) + if campaign == 'Summer12_DR53X_RD': + pileupScenario = 'PU_RD1' + if pileupScenario == 'Unknown' and 'MinBias' in pileupDataset and 'LowPU2010DR42' not in workflow: + print 'ERROR: unable to determine pileup scenario' + sys.exit(0) + elif 'Fall11_R2' in workflow or 'Fall11_R4' in workflow or 'Fall11R2' in workflow or 'Fall11R4' in workflow: + matchObj = re.match(r".*Fall11-(.*)_START.*", inputDataset) + if matchObj: + pileupScenario = matchObj.group(1) + else: + pileupScenario == 'Unknown' + elif pileupScenario == 'Unknown' and 'MinBias' not in pileupDataset: + pileupScenario = 'NoPileUp' + + if pileupScenario == 'Unknown': + pileupScenario = '' + + if 'LowPU2010_DR42' in workflow or 'LowPU2010DR42' in workflow: + pileupScenario = 'PU_S0' + if 'HiWinter13' in workflow and 'DR53X' in workflow: + pileupScenario = '' + if 'pAWinter13' in workflow and 'DR53X' in workflow: + pileupScenario = 'pa' # not actually the pileup scenario of course + if 'ppWinter13' in workflow and 'DR53X' in workflow: + pileupScenario = 'pp' # not actually the pileup scenario of course + return pileupScenario + + def _pileup_dataset(self): + pileupDataset = 'None' + for line in self.workload: + if 'request.schema.MCPileup' in line: + pileupDataset = line[line.find("'")+1:line.find("'",line.find("'")+1)] + return pileupDataset + + + def _priority(self): + priority = -1 + for line in self.workload: + if 'request.schema.RequestPriority' in line: + priority = line[line.find("=")+1:line.find("
1: + siteUse = findCustodialLocation(self.url, self.input_dataset) + if siteUse == 'None': + raise Exception('ERROR: No custodial site found for dataset=%s' % self.input_dataset) + siteUse = siteUse[:-4] + + # Set the custodial location if necessary + if not self.site_use or self.site_use != 'T2_US': + if not self.site_cust: + siteCust = siteUse + else: + siteCust = self.site_cust + + # Check if input dataset subscribed to disk endpoint + if 'T2_CH_CERN' in siteUse: + siteSE = 'T2_CH_CERN' + else: + siteSE = siteUse + '_Disk' + subscribedOurSite, subscribedOtherSite = \ + checkAcceptedSubscriptionRequest(self.url, self.input_dataset, siteSE) + if not subscribedOurSite and not self.xrootd and 'Fall11R2' not in workflow: + raise Exception('ERROR: input dataset not subscribed/approved to required Disk endpoint') + if self.xrootd and not subscribedOtherSite: + raise Exception('ERROR: input dataset not subscribed/approved to any Disk endpoint') + if siteUse not in sites and options.site != 'T2_US' and \ + siteUse != ['T2_CH_CERN_AI', 'T2_CH_CERN_HLT', 'T2_CH_CERN']: + raise Exception('ERROR: invalid site=%s' % siteUse) + + if not siteCust: + raise Exception('ERROR: A custodial site must be specified') + + return siteUse, siteCust + +def getScenario(ps): + pss = 'Unknown' + + if ps == 'SimGeneral.MixingModule.mix_E8TeV_AVE_16_BX_25ns_cfi': + pss = 'PU140Bx25' + if ps == 'SimGeneral.MixingModule.mix_2012_Summer_50ns_PoissonOOTPU_cfi': + pss = 'PU_S10' + if ps == 'SimGeneral.MixingModule.mix_E7TeV_Fall2011_Reprocess_50ns_PoissonOOTPU_cfi': + pss = 'PU_S6' + if ps == 'SimGeneral.MixingModule.mix_E8TeV_AVE_10_BX_25ns_300ns_spread_cfi': + pss = 'PU10bx25' + if ps == 'SimGeneral.MixingModule.mix_E8TeV_AVE_10_BX_50ns_300ns_spread_cfi': + pss = 'PU10bx50' + if ps == 'SimGeneral.MixingModule.mix_2011_FinalDist_OOTPU_cfi': + pss = 'PU_S13' + if ps == 'SimGeneral.MixingModule.mix_fromDB_cfi': + pss = 'PU_RD1' + if ps == 'SimGeneral.MixingModule.mix_2012C_Profile_PoissonOOTPU_cfi': + pss = 'PU2012CExt' + if ps == 'SimGeneral.MixingModule.mixNoPU_cfi': + pss = 'NoPileUp' + if ps == 'SimGeneral.MixingModule.mix_POISSON_average_cfi': + pss = 'PU' + if ps == 'SimGeneral.MixingModule.mix_CSA14_50ns_PoissonOOTPU_cfi': + pss = 'PU_S14' + + return pss + +def getPileupScenario(winfo, config): + "Get pileup scanario for given workflow dict and configuration" + workflow = winfo['RequestName'] + pileup, meanPileUp, bunchSpacing, cmdLineOptions = getPileup(config) + scenario = getScenario(pileup) + if scenario == 'PU140Bx25' and meanPileUp != 'Unknown': + scenario = 'PU' + meanPileUp + 'bx25' + if scenario == 'PU140bx25' and 'Upgrade' in workflow: + scenario = 'PU140Bx25' + if scenario == 'PU': + scenario = 'PU' + meanPileUp + 'bx' + bunchSpacing + if meanPileUp == 'None' or bunchSpacing == 'None': + print 'ERROR: unexpected pileup settings in config' + sys.exit(0) + if scenario == 'PU_RD1' and cmdLineOptions != 'None': + if '--runsAndWeightsForMC [(190482,0.924) , (194270,4.811), (200466,7.21), (207214,7.631)]' in cmdLineOptions: + scenario = 'PU_RD2' + return scenario + +def getPileup(config): + "Helper function used in getPileupScenario" + pu = 'Unknown' + vmeanpu = 'None' + bx = 'None' + cmdLineOptions = 'None' + lines = config.split('\n') + for line in lines: + if 'process.load' and 'MixingModule' in line: + pu = line[line.find("'")+1:line.find("'",line.find("'")+1)] + if 'process.mix.input.nbPileupEvents.averageNumber' in line: + meanpu = line[line.find("(")+1:line.find(")")].split('.', 1) + vmeanpu = meanpu[0] + if 'process.mix.bunchspace' in line: + bx = line[line.find("(")+1:line.find(")")] + if 'with command line options' in line: + cmdLineOptions = line + return pu, vmeanpu, bx, cmdLineOptions + +def getConfig(url, cacheID): + "Helper function to get configuration for given cacheID" + conn = httplib.HTTPSConnection(url, + cert_file = os.getenv('X509_USER_PROXY'), + key_file = os.getenv('X509_USER_PROXY')) + conn.request("GET",'/couchdb/reqmgr_config_cache/'+cacheID+'/configFile') + config = conn.getresponse().read() + return config + +def findCustodialLocation(url, dataset): + "Helper function to find custodial location for given dataset" + conn = httplib.HTTPSConnection(url, cert_file = os.getenv('X509_USER_PROXY'), key_file = os.getenv('X509_USER_PROXY')) + r1=conn.request("GET",'/phedex/datasvc/json/prod/blockreplicas?dataset='+dataset) + r2=conn.getresponse() + result = json.loads(r2.read()) + request=result['phedex'] + if 'block' not in request.keys(): + return "No Site" + if len(request['block'])==0: + return "No Site" + for replica in request['block'][0]['replica']: + if replica['custodial']=="y" and replica['node']!="T0_CH_CERN_MSS": + return replica['node'] + return "None" + +def checkAcceptedSubscriptionRequest(url, dataset, site): + "Helper function" + conn = httplib.HTTPSConnection(url, + cert_file = os.getenv('X509_USER_PROXY'), + key_file = os.getenv('X509_USER_PROXY')) + conn.request("GET",'/phedex/datasvc/json/prod/requestlist?dataset='+dataset+'&type=xfer') + resp = conn.getresponse() + result = json.load(resp) + requests=result['phedex'] + if 'request' not in requests.keys(): + return [False, False] + ourNode = False + otherNode = False + for request in result['phedex']['request']: + for node in request['node']: + if node['name']==site and node['decision']=='approved': + ourNode = True + elif 'Disk' in node['name'] and node['decision']=='approved': + otherNode = True + return ourNode, otherNode + diff --git a/src/python/WMCore/ReqMgr/Tools/__init__.py b/src/python/WMCore/ReqMgr/Tools/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/python/WMCore/ReqMgr/Tools/cms.py b/src/python/WMCore/ReqMgr/Tools/cms.py new file mode 100644 index 0000000000..395d5208fb --- /dev/null +++ b/src/python/WMCore/ReqMgr/Tools/cms.py @@ -0,0 +1,164 @@ +from WMCore.RequestManager.RequestDB.Settings.RequestStatus import StatusList, NextStatus +from WMCore.Services.SiteDB.SiteDB import SiteDBJSON + +def next_status(status=None): + "Return next ReqMgr status for given status" + if status: + if status in NextStatus: + return NextStatus[status] + else: + return 'N/A' + return StatusList + +def sites(): + "Return known CMS site list from SiteDB" + try: + # Download a list of all the sites from SiteDB, uses v2 API. + sitedb = SiteDBJSON() + sites = sitedb.getAllCMSNames() + sites.sort() + except Exception as exc: + msg = "ERROR: Could not retrieve sites from SiteDB, reason: %s" % str(exc) + raise Exception(msg) + return sites + +def site_white_list(): + "site white list, default all T1" + t1_sites = [s for s in sites() if s.startswith('T1_')] + return t1_sites + +def site_black_list(): + "site black list, default all T3" + t3_sites = [s for s in sites() if s.startswith('T3_')] + return t3_sites + +def lfn_bases(): + "Return LFN Base list" + storeResultLFNBase = [ + "/store/backfill/1", + "/store/backfill/2", + "/store/data", + "/store/mc", + "/store/generator", + "/store/relval", + "/store/hidata", + "/store/himc", + "/store/results/analysisops", + "/store/results/b_physics", + "/store/results/b_tagging", + "/store/results/b2g", + "/store/results/e_gamma_ecal", + "/store/results/ewk", + "/store/results/exotica", + "/store/results/forward", + "/store/results/heavy_ions", + "/store/results/higgs", + "/store/results/jets_met_hcal", + "/store/results/muon", + "/store/results/qcd", + "/store/results/susy", + "/store/results/tau_pflow", + "/store/results/top", + "/store/results/tracker_dpg", + "/store/results/tracker_pog", + "/store/results/trigger"] + return storeResultLFNBase + +def lfn_unmerged_bases(): + "Return list of LFN unmerged bases" + out = ["/store/data", "/store/temp"] + return out + +def web_ui_names(): + "Return dict of web UI JSON naming conventions" + maps = {"InputDataset": "Input Dataset", + "IncludeParents": "Include Parents", + "PrepID": "Optional Prep ID String", + "BlockBlacklist": "Block black list", + "RequestPriority": "Request Priority", + "TimePerEvent": "Time per event (Seconds)", + "RunWhitelist": "Run white list", + "BlockWhitelist": "Block white list", + "OpenRunningTimeout": "Open Running Timeout", + "DQLUploadURL": "DQM URL", + "DqmSequences": "DQM Sequences", + "SizePerEvent": "Size per event (KBytes)", + "ScramArch": "Architecture", + "EnableHarvesting": "Enable DQM Harvesting", + "DQMConfigCacheID": "DQM Config CacheID", + "Memory": "Memory (MBytes)", + "RunBlacklist": "Run black list", + "RequestString": "Optional Request ID String", + "CMSSWVersion": "Software Releases", + "DQMUploadURL":"DQM URL", + "DQMSequences": "DQM sequences", + "DataPileup": "Data Pileup", + "FilterEfficiency": "Filter Efficiency", + "GlobalTag": "Global tag", + "MCPileup": "MonteCarlo Pileup", + "PrimaryDataset": "Parimary dataset", + "Acquisitionera": "Acquisition era", + "CmsPath": "CMS path", + "DBS": "DBS urls", + "ProcessingVersion": "Processing version", + "RequestType": "Request type", + "ACDCDatabase": "ACDC database", + "ACDCServer": "ACDC server", + "CollectionName": "Collection name", + "IgnoredOutputModules": "Ignored output modules", + "InitialTaskPath": "Initial task path", + "KeepStepOneOutput": "Keep step one output", + "KeepStepTwoOutput": "Keep step two output", + "StepOneConfigCacheID": "Step one config cache id", + "StepOneOutputModuleName": "Step one output module name", + "StepThreeConfigCacheID": "Step three config cache id", + "StepTwoConfigCacheID": "Step two config cache id", + "StepTwoOutputModuleName": "Step two output module name", + } + return maps + +def dqm_urls(): + "Return list of DQM urls" + urls = [ + "https://cmsweb.cern.ch/dqm/dev", + "https://cmsweb.cern.ch/dqm/offline", + "https://cmsweb.cern.ch/dqm/relval", + "https://cmsweb-testbed.cern.ch/dqm/dev", + "https://cmsweb-testbed.cern.ch/dqm/offline", + "https://cmsweb-testbed.cern.ch/dqm/relval", + ] + return urls + +def dbs_urls(): + "Return list of DBS urls" + urls = [] + base = "https://cmsweb.cern.ch/dbs/prod/global/DBSReader/" + for inst in ["prod", "phys01", "phys02", "phys03"]: + urls.append(base.replace("prod", inst)) + return urls + +def couch_url(): + "Return central couch url" + url = "https://cmsweb.cern.ch/couchdb" + return url + +def releases(): + "Return list of CMSSW releases" + releases=["CMSSW_7_0_0", "CMSSW_6_8_1"] + return releases + +def architectures(): + "Return list of CMSSW architectures" + arch=["slc5_amd64_gcc472", "slc5_ad4_gcc481"] + return arch + +def scenarios(): + "Return list of scenarios" + slist = ["pp", "cosmics", "hcalnzs", "preprodmc", "prodmc"] + return slist + +def cms_groups(): + "Return list of CMS data-ops groups" + groups = ["DATAOPS"] + return groups + diff --git a/src/python/WMCore/ReqMgr/Tools/reqMgrClient.py b/src/python/WMCore/ReqMgr/Tools/reqMgrClient.py new file mode 100644 index 0000000000..defa0eaa54 --- /dev/null +++ b/src/python/WMCore/ReqMgr/Tools/reqMgrClient.py @@ -0,0 +1,339 @@ +#!/usr/bin/env python +""" + This client encapsulates several basic queries to request manager. + This uses ReqMgr rest api through HTTP + url parameter is normally 'cmsweb.cern.ch' +""" + +# system modules +import os +import re +import sys +import json +import urllib +import urllib2 +import httplib + +# default headers for PUT and POST methods +HEADERS={"Content-type": "application/x-www-form-urlencoded","Accept": "text/plain"} + +def requestManagerGet(url, request, retries=4): + """ + Queries ReqMgr through a HTTP GET method + in every request manager query + url: the instance used, i.e. url='cmsweb.cern.ch' + request: the request suffix url + retries: number of retries + """ + cert = os.getenv('X509_USER_PROXY') + ckey = os.getenv('X509_USER_PROXY') + for _ in range(retries): + conn = httplib.HTTPSConnection(url, cert_file=cert, key_file=ckey) + conn.request("GET", request) + resp = conn.getresponse() + request = json.load(resp) + if 'exception' not in request: + return request + raise Exception('Maximum queries to ReqMgr exceeded') + +def requestManagerPost(url, request, params, head = HEADERS): + """ + Performs some operation on ReqMgr through + an HTTP POST method. + url: the instance used, i.e. url='cmsweb.cern.ch' + request: the request suffix url for the POST method + params: a dict with the POST parameters + """ + conn = httplib.HTTPSConnection(url, cert_file = os.getenv('X509_USER_PROXY'), + key_file = os.getenv('X509_USER_PROXY')) + headers = head + encodedParams = urllib.urlencode(params) + conn.request("POST", request, encodedParams, headers) + response = conn.getresponse() + data = response.read() + conn.close() + return data + +def requestManagerPut(url, request, params, head = HEADERS): + """ + Performs some operation on ReqMgr through + an HTTP PUT method. + url: the instance used, i.e. url='cmsweb.cern.ch' + request: the request suffix url for the POST method + params: a dict with the PUT parameters + head: optional headers param. If not given it takes default value (HEADERS) + """ + conn = httplib.HTTPSConnection(url, cert_file = os.getenv('X509_USER_PROXY'), + key_file = os.getenv('X509_USER_PROXY')) + headers = head + encodedParams = urllib.urlencode(params) + conn.request("PUT", request, encodedParams, headers) + response = conn.getresponse() + data = response.read() + conn.close() + return data + +class WorkflowManager(object): + def __init__(self, workflow, url='cmsweb.cern.ch'): + "Request workflow information from ReqMgr and provide APIs for its attributes" + self.workflow = workflow + self.winfo = requestManagerGet(url,'/reqmgr/reqMgr/request?requestName='+workflow) + self.output_datasets = requestManagerGet(url,'/reqmgr/reqMgr/outputDatasetsByRequestName?requestName='+workflow) + + def request_status(self): + """Retrieves workflow status""" + return self.winfo.get('RequestStatus', '') + + def request_type(self): + return self.winfo.get('RequestType', '') + + def request_priority(self): + return self.winfo.get('RequestPriority', 0) + + def run_whitelist(self): + return self.winfo.get('RunWhiteList', []) + + def block_whitelist(self): + return self.winfo.get('BlockWhiteList', []) + + def input_dataset(self): + return self.winfo.get('InputDataset', '') + + def team(self): + """Retrieves the team on which the wf is assigned""" + return self.winfo.get('teams', ['NoTeam'])[0] + + def output_datasets(self): + """returns the output datasets for a given workfow""" + return self.output_datasets + + def input_events(self): + """ + Gets the inputs events of a given workflow + depending of the kind of workflow + """ + request = self.winfo + requestType = request['RequestType'] + #if request is montecarlo or Step0, the numer of + #input events is by the requsted events + if requestType == 'MonteCarlo' or requestType == 'LHEStepZero': + if 'RequestNumEvents' in request: + if request['RequestNumEvents']>0: + return request['RequestNumEvents'] + if 'RequestSizeEvents' in request: + return request['RequestSizeEvents'] + else: + return 0 + if requestType == 'TaskChain': + return handleTaskChain(request) + + #if request is not montecarlo, then we need to check the size + #of input datasets + #This loops fixes the white and blacklists in the workflow + #information, + for listitem in ["RunWhitelist", "RunBlacklist", + "BlockWhitelist", "BlockBlacklist"]: + if listitem in request: + #if empty + if request[listitem]=='[]' or request[listitem]=='': + request[listitem]=[] + #if there is not a list but some elements it creates a list + if type(request[listitem]) is not list: + # if doesn't contain "[" is a single block + if '[' not in request[listitem]: + #wrap in a list + request[listitem] = [request[listitem]] + #else parse a list + else: + request[listitem]= eval(request[listitem]) + #if not, an empty list will do + else: + request[listitem]=[] + + inputDataSet=request['InputDataset'] + + #it the request is rereco, we valiate white/black lists + if requestType=='ReReco': + # if there is block whte list, count only the selected block + if request['BlockWhitelist']: + events = dbs3.getEventCountDataSetBlockList(inputDataSet,request['BlockWhitelist']) + # if there is block black list, substract them from the total + if request['BlockBlacklist']: + events = (dbs3.getEventCountDataSet(inputDataSet) - + dbs3.getEventCountDataSet(inputDataSet,request['BlockBlacklist'])) + return events + # same if a run whitelist + if request['RunWhitelist']: + events = dbs3.getEventCountDataSetRunList(inputDataSet, request['RunWhitelist']) + return events + # otherwize, the full lumi count + else: + events = dbs3.getEventCountDataset(inputDataSet) + return events + + events = dbs3.getEventCountDataSet(inputDataSet) + # if black list, subsctract them + if request['BlockBlacklist']: + events=events-dbs3.getEventCountDataSetBlockList(inputDataSet, request['BlockBlacklist']) + # if white list, only the ones in the whitelist. + if request['RunWhitelist']: + events=dbs3.getEventCountDataSetRunList(inputDataSet, request['RunWhitelist']) + # if white list of blocks + if request['BlockWhitelist']: + events=dbs3.getEventCountDataSetBlockList(inputDataSet, request['BlockWhitelist']) + + if 'FilterEfficiency' in request: + return float(request['FilterEfficiency'])*events + else: + return events + +def getOutputEvents(dataset): + """ + Gets the output events depending on the type + if the request + """ + return dbs3.getEventCountDataSet(dataset) + +def closeOutWorkflow(url, workflowname): + """ + Closes out a workflow by changing the state to closed-out + This does not care about cascade workflows + """ + params = {"requestName" : workflowname,"status" : "closed-out"} + data = requestManagerPut(url,"/reqmgr/reqMgr/request", params) + return data + +def closeOutWorkflowCascade(url, workflowname): + """ + Closes out a workflow, it will search for any Resubmission requests + for which the given request is a parent and announce them too. + """ + params = {"requestName" : workflowname, "cascade" : True} + data = requestManagerPost(url,"/reqmgr/reqMgr/closeout", params) + return data + +def announceWorkflow(url, workflowname): + """ + Sets a workflow state to announced + This does not care about cascade workflows + """ + params = {"requestName" : workflowname,"status" : "announced"} + data = requestManagerPut(url,"/reqmgr/reqMgr/request", params) + return data + +def announceWorkflowCascade(url, workflowname): + """ + Sets a workflow state to announced, it will search for any Resubmission requests + for which the given request is a parent and announce them too. + """ + params = {"requestName" : workflowname, "cascade" : True} + data = requestManagerPost(url,"/reqmgr/reqMgr/announce", params) + return data + + +def setWorkflowApproved(url, workflowname): + """ + Sets a workflow state to assignment-approved + """ + params = {"requestName" : workflowname,"status" : "assignment-approved"} + data = requestManagerPut(url,"/reqmgr/reqMgr/request", params) + return data + +def setWorkflowRunning(url, workflowname): + """ + Sets a workflow state to running + """ + params = {"requestName" : workflowname,"status" : "running"} + data = requestManagerPut(url,"/reqmgr/reqMgr/request", params) + return data + +def rejectWorkflow(url, workflowname): + """ + Sets a workflow state to rejected + """ + params = {"requestName" : workflowname,"status" : "rejected"} + data = requestManagerPut(url,"/reqmgr/reqMgr/request", params) + return data + +def abortWorkflow(url, workflowname): + """ + Sets a workflow state to aborted + """ + params = {"requestName" : workflowname,"status" : "aborted"} + data = requestManagerPut(url,"/reqmgr/reqMgr/request", params) + return data + +def cloneWorkflow(url, workflowname): + """ + This clones a request + """ + headers={"Content-Length": 0} + params = {} + data = requestManagerPut(url,"/reqmgr/reqMgr/clone/", params, headers) + return data + +def submitWorkflow(url, schema): + """ + This submits a workflow into the ReqMgr, can be used for cloning + and resubmitting workflows + url: the instance ued, i.e. 'cmsweb.cern.ch' + schema: A dictionary with the parameters needed to create + the workflow + + """ + data = requestManagerPost(url,"/reqmgr/create/makeSchema", schema) + return data + +def handleTaskChain(request): + # Check if it's MC from scratch + if 'RequestNumEvents' in request['Task1']: + if request['Task1']['RequestNumEvents'] is not None: + return request['Task1']['RequestNumEvents'] + + blockWhitelist = blockBlacklist = runWhitelist = runBlacklist = [] + if 'InputDataset' in request['Task1']: + inputDataSet=request['Task1']['InputDataset'] + if 'BlockWhitelist' in request['Task1']: + blockWhitelist=request['Task1']['BlockWhitelist'] + if 'BlockBlacklist' in request['Task1']: + blockBlacklist=request['Task1']['BlockBlacklist'] + if 'RunWhitelist' in request['Task1']: + runWhitelist=request['Task1']['RunWhitelist'] + if 'RunBlacklist' in request['Task1']: + runBlacklist=request['Task1']['RunBlacklist'] + + if blockWhitelist: + return dbs3.getEventCountDataSetBlockList(inputDataSet,blockWhitelist) + if blockBlacklist: + return dbs3.getEventCountDataset(inputDataSet) - dbs3.getEventCountDataSetBlockList(inputDataSet,blockBlacklist) + if runWhitelist: + return dbs3.getEventCountDataSetRunList(inputDataSet, runWhitelist) + else: + return dbs3.getEventCountDataset(inputDataSet) + +### TODO: implement multi white/black list +# if len(blockWhitelist)>0 and len(runWhitelist)>0: +# print "Hey, you have block and run white list :-D" +# return getRunLumiCountDatasetBlockList(inputDataSet,BlockWhitelist) +# elif len(blockWhitelist)>0 and len(runWhitelist)==0: +# print "Hey, you have block white list but NOT run white list :-D" +# elif len(blockWhitelist)==0 and len(runWhitelist)>0: +# print "Hey, you have NO block white list but you do have run white list :-D" +# return getRunLumiCountDatasetList(inputDataSet, runWhitelist) +# elif len(blockWhitelist)==0 and len(runWhitelist)==0: +# print "Hey, you have NO block and run white list :-D" +# +# if len(BlockBlacklist)>0 and len(runBlacklist)>0: +# print "Hey, you have block and run black list :-(" +# return getRunLumiCountDataset(inputDataSet)-getRunLumiCountDatasetBlockList(inputDataSet,BlockBlacklist) +# elif len(BlockBlacklist)>0 and len(runBlacklist)==0: +# print "Hey, you have block black list but NOT run black list :-(" +# elif len(BlockBlacklist)==0 and len(runBlacklist)>0: +# print "Hey, you have NO block black list but you do have run black list :-(" +# elif len(BlockBlacklist)==0 and len(runBlacklist)==0: +# print "Hey, you have NO block and run black list :-(" + + + + + diff --git a/src/python/WMCore/ReqMgr/Utils/Validation.py b/src/python/WMCore/ReqMgr/Utils/Validation.py new file mode 100644 index 0000000000..9068a6cbd1 --- /dev/null +++ b/src/python/WMCore/ReqMgr/Utils/Validation.py @@ -0,0 +1,98 @@ +""" +ReqMgr request handling. + +""" +from WMCore.WMSpec.WMWorkload import WMWorkloadHelper +from WMCore.WMSpec.WMWorkloadTools import loadSpecByType +from WMCore.REST.Auth import authz_match + +from WMCore.ReqMgr.Auth import getWritePermission +from WMCore.ReqMgr.DataStructs.Request import initialize_request_args +from WMCore.ReqMgr.DataStructs.RequestStatus import check_allowed_transition +from WMCore.ReqMgr.DataStructs.RequestError import InvalidStateTransition + +def validate_request_update_args(request_args, config, reqmgr_db_service, param): + """ + param and safe structure is RESTArgs structure: named tuple + RESTArgs(args=[], kwargs={}) + + validate post request + 1. read data from body + 2. validate the permission (authentication) + 3. validate state transition (against previous state from couchdb) + 2. validate using workload validation + 3. convert data from body to arguments (spec instance, argument with default setting) + + TODO: rasie right kind of error with clear message + """ + + request_name = request_args["RequestName"] + # this need to be deleted for validation + del request_args["RequestName"] + couchurl = '%s/%s' % (config.couch_host, config.couch_reqmgr_db) + workload = WMWorkloadHelper() + # param structure is RESTArgs structure. + workload.loadSpecFromCouch(couchurl, request_name) + + # first validate the permission by status and request type. + # if the status is not set only ReqMgr Admin can change the the values + # TODO for each step, assigned, approved, announce find out what other values + # can be set + request_args["RequestType"] = workload.requestType() + permission = getWritePermission(request_args) + authz_match(permission['role'], permission['group']) + del request_args["RequestType"] + + #validate the status + if request_args.has_key("RequestStatus"): + validate_state_transition(reqmgr_db_service, request_name, request_args["RequestStatus"]) + # delete request_args since it is not part of spec argument sand validation + args_without_status = {} + args_without_status.update(request_args) + del args_without_status["RequestStatus"] + else: + args_without_status = request_args + # validate the arguments against the spec argumentSpecdefinition + workload.validateArgument(args_without_status) + # to update request_args with type conversion + request_args.update(args_without_status) + + return workload, request_args + +def validate_request_create_args(request_args, config, *args, **kwargs): + """ + *arg and **kwargs are only for the interface + validate post request + 1. read data from body + 2. validate using spec validation + 3. convert data from body to arguments (spec instance, argument with default setting) + TODO: rasie right kind of error with clear message + """ + + initialize_request_args(request_args, config) + print "xxxxx" + from pprint import pprint + pprint(request_args) + #check the permission for creating the request + permission = getWritePermission(request_args) + authz_match(permission['role'], permission['group']) + + # get the spec type and validate arguments + spec = loadSpecByType(request_args["RequestType"]) + workload = spec.factoryWorkloadConstruction(request_args["RequestName"], + request_args) + return workload, request_args + +def validate_state_transition(reqmgr_db_service, request_name, new_state) : + """ + validate state transition by getting the current data from + couchdb + """ + requests = reqmgr_db_service.getRequestByNames(request_name) + # generator object can't be subscribed: need to loop. + # only one row should be returned + for request in requests.values(): + current_state = request["RequestStatus"] + if not check_allowed_transition(current_state, new_state): + raise InvalidStateTransition(current_state, new_state) + return \ No newline at end of file diff --git a/src/python/WMCore/ReqMgr/Utils/__init__.py b/src/python/WMCore/ReqMgr/Utils/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/python/WMCore/ReqMgr/Utils/jsonwrapper/.__init__.py.un~ b/src/python/WMCore/ReqMgr/Utils/jsonwrapper/.__init__.py.un~ new file mode 100644 index 0000000000000000000000000000000000000000..0f98b716813248917077b70c4b2d8059089abcbd GIT binary patch literal 5269 zcmeI0%WD%+7{$jL1F_b3)VdmiI2o*gBn?ed6ki(!7hPDFA&_dvnn}n^TT1An_zwuS zK9TOb>7U`og$OR)2qJFW=}zi7GxzG9G{llHyL;fwedW$1-*3Ljojd0=w(hyNnqQ5P z#>c|UyN%85_hSdm(O)w^esAq`E?@ifIayDoo;Xjx7>4l{E}BBl1JhhwskI+^X3edO zYHp$CRTt-1vzFIv`>c7EUBR?v_$+)JJ`Yo)FzZ2kc6VnYaR9BPQT>2|IuRDZkrR)? z3AT12gCEw4DFUmvi3El;NM+oN`Y?Rgmcx<0!TZl8qU=r8x z7=%~_c$ZXo3_X!NhokT~atz*tB|O49#kLSZ5`Q3_c`_0MmWQFd9AkN`etj@Uue43M_$v-)t4GAFWb)B6Or!6&05{d_WYnNl9hZ<^Xx)oPwOYG zjL0yh%2=(dyl<+!8TvEuOE{XW97)43V41A4k6jc&9u8_a=wy|V7_ej{Woq!hOV$Z5 zs3TeLsPIx4I0C9wJSxCv|j(nhzwJz z@W`ZBR^AWb9SRq(3_V{m-oR0K9O3eoSS38!$1aK>mqn=`sf_@i5l`Yk^wP0D{K11)#1;LgKgN+376Udot zGi>XH5Tg*Mz}D`^_G(CWFWZ0nMEiiOCNF;tffMp|HZ){LAx;5uxUY~5|5v*o%{TrJ P*>v2#TZN_dZ`*$Wb{Zfp literal 0 HcmV?d00001 diff --git a/src/python/WMCore/ReqMgr/Utils/jsonwrapper/__init__.py b/src/python/WMCore/ReqMgr/Utils/jsonwrapper/__init__.py new file mode 100644 index 0000000000..97115ee84e --- /dev/null +++ b/src/python/WMCore/ReqMgr/Utils/jsonwrapper/__init__.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python +#-*- coding: ISO-8859-1 -*- + +""" +JSON wrapper around different JSON python implementations. +We use simplejson (json), cjson and yajl JSON implementation. + +NOTE: different JSON implementation handle floats in different way +Here are few examples + + +..doctest:: + + r1={"ts":time.time()} + print r1 + {'ts': 1374255843.891289} + +Python json: + +..doctest:: + + print json.dumps(r1), json.loads(json.dumps(r1)) + {"ts": 1374255843.891289} {u'ts': 1374255843.891289} + +CJSON: + +..doctest:: + print cjson.encode(r1), cjson.decode(cjson.encode(r1)) + {"ts": 1374255843.89} {'ts': 1374255843.89} + +YAJL: + +..doctest:: + + print yajl.dumps(r1), yajl.loads(yajl.dumps(r1)) + {"ts":1.37426e+09} {u'ts': 1374260000.0} + +Therefore when records contains timestamp it is ADVISED to round it to integer. +Then json/cjson implementations will agreee on input/output, while yajl will +still differ (for that reason we can't use yajl). +""" + +__author__ = "Valentin Kuznetsov " + +MODULE = None + +try: + import yajl + MODULE = "yajl" +except: + pass + +try: + import cjson + MODULE = "cjson" +except: + pass + +import json +if not MODULE: # use default JSON module + MODULE = "json" + +#print "### DAS uses %s JSON module" % MODULE + +def loads(idict, **kwargs): + """ + Based on default MODULE invoke appropriate JSON decoding API call + """ + if MODULE == 'json': + return json.loads(idict, **kwargs) + elif MODULE == 'cjson': + return cjson.decode(idict) + elif MODULE == 'yajl': + try: # yajl.loads("123") will fail + res = yajl.loads(idict) + except: # fall back into default python JSON + res = json.loads(idict, **kwargs) + return res + else: + raise Exception("Not support JSON module: %s" % MODULE) + +def load(source): + """ + Use json.load for back-ward compatibility, since cjson doesn't + provide this method. The load method works on file-descriptor + objects. + """ + if MODULE == 'json': + return json.load(source) + elif MODULE == 'cjson': + data = source.read() + return cjson.decode(data) + elif MODULE == 'yajl': + return yajl.load(source) + else: + raise Exception("Not support JSON module: %s" % MODULE) + +def dumps(idict, **kwargs): + """ + Based on default MODULE invoke appropriate JSON encoding API call + """ + if MODULE == 'json': + return json.dumps(idict, **kwargs) + elif MODULE == 'cjson': + return cjson.encode(idict) + elif MODULE == 'yajl': + return yajl.dumps(idict) + else: + raise Exception("JSON module %s is not supported" % MODULE) + +def dump(doc, source): + """ + Use json.dump for back-ward compatibility, since cjson doesn't + provide this method. The dump method works on file-descriptor + objects. + """ + if MODULE == 'json': + return json.dump(doc, source) + elif MODULE == 'cjson': + stj = cjson.encode(doc) + return source.write(stj) + elif MODULE == 'yajl': + return yajl.dump(doc, source) + else: + raise Exception("JSON module %s is not supported" % MODULE) + +class JSONEncoder(object): + """ + JSONEncoder wrapper + """ + def __init__(self, **kwargs): + self.encoder = json.JSONEncoder(**kwargs) + if kwargs and 'sort_keys' in kwargs: + self.module = 'default' + else: + self.module = MODULE + + def encode(self, idict): + """Decode JSON method""" + if self.module == 'cjson': + return cjson.encode(idict) + elif self.module == 'yajl': + return yajl.Encoder().encode(idict) + return self.encoder.encode(idict) + + def iterencode(self, idict): + "Encode input dict" + return self.encoder.iterencode(idict) + +class JSONDecoder(object): + """ + JSONDecoder wrapper + """ + def __init__(self, **kwargs): + self.decoder = json.JSONDecoder(**kwargs) + if kwargs: + self.module = 'default' + else: + self.module = MODULE + + def decode(self, istring): + """Decode JSON method""" + if MODULE == 'cjson': + return cjson.decode(istring) + elif MODULE == 'yajl': + return yajl.Decoder().decode(istring) + return self.decoder.decode(istring) + + def raw_decode(self, istring): + "Decode given string" + return self.decoder.raw_decode(istring) + diff --git a/src/python/WMCore/ReqMgr/Utils/jsonwrapper/__init__.py~ b/src/python/WMCore/ReqMgr/Utils/jsonwrapper/__init__.py~ new file mode 100644 index 0000000000..9c9cba0caa --- /dev/null +++ b/src/python/WMCore/ReqMgr/Utils/jsonwrapper/__init__.py~ @@ -0,0 +1,138 @@ +#!/usr/bin/env python +#-*- coding: ISO-8859-1 -*- + +""" +JSON wrapper around different JSON python implementations. +We use simplejson (json), cjson and yajl JSON implementation. +""" + +__author__ = "Valentin Kuznetsov " + +MODULE = None + +try: + import yajl + MODULE = "yajl" +except: + pass + +try: + import cjson + MODULE = "cjson" +except: + pass + +import json +if not MODULE: # use default JSON module + MODULE = "json" + +print "DAS uses %s JSON module" % MODULE + +def loads(idict, **kwargs): + """ + Based on default MODULE invoke appropriate JSON decoding API call + """ + if MODULE == 'json': + return json.loads(idict, **kwargs) + elif MODULE == 'cjson': + return cjson.decode(idict) + elif MODULE == 'yajl': + try: # yajl.loads("123") will fail + res = yajl.loads(idict) + except: # fall back into default python JSON + res = json.loads(idict, **kwargs) + return res + else: + raise Exception("Not support JSON module: %s" % MODULE) + +def load(source): + """ + Use json.load for back-ward compatibility, since cjson doesn't + provide this method. The load method works on file-descriptor + objects. + """ + if MODULE == 'json': + return json.load(source) + elif MODULE == 'cjson': + data = source.read() + return cjson.decode(data) + elif MODULE == 'yajl': + return yajl.load(source) + else: + raise Exception("Not support JSON module: %s" % MODULE) + +def dumps(idict, **kwargs): + """ + Based on default MODULE invoke appropriate JSON encoding API call + """ + if MODULE == 'json': + return json.dumps(idict, **kwargs) + elif MODULE == 'cjson': + return cjson.encode(idict) + elif MODULE == 'yajl': + return yajl.dumps(idict) + else: + raise Exception("JSON module %s is not supported" % MODULE) + +def dump(doc, source): + """ + Use json.dump for back-ward compatibility, since cjson doesn't + provide this method. The dump method works on file-descriptor + objects. + """ + if MODULE == 'json': + return json.dump(doc, source) + elif MODULE == 'cjson': + stj = cjson.encode(doc) + return source.write(stj) + elif MODULE == 'yajl': + return yajl.dump(doc, source) + else: + raise Exception("JSON module %s is not supported" % MODULE) + +class JSONEncoder(object): + """ + JSONEncoder wrapper + """ + def __init__(self, **kwargs): + self.encoder = json.JSONEncoder(**kwargs) + if kwargs and kwargs.has_key('sort_keys'): + self.module = 'default' + else: + self.module = MODULE + + def encode(self, idict): + """Decode JSON method""" + if self.module == 'cjson': + return cjson.encode(idict) + elif self.module == 'yajl': + return yajl.Encoder().encode(idict) + return self.encoder.encode(idict) + + def iterencode(self, idict): + "Encode input dict" + return self.encoder.iterencode(idict) + +class JSONDecoder(object): + """ + JSONDecoder wrapper + """ + def __init__(self, **kwargs): + self.decoder = json.JSONDecoder(**kwargs) + if kwargs: + self.module = 'default' + else: + self.module = MODULE + + def decode(self, istring): + """Decode JSON method""" + if MODULE == 'cjson': + return cjson.decode(istring) + elif MODULE == 'yajl': + return yajl.Decoder().decode(istring) + return self.decoder.decode(istring) + + def raw_decode(self, istring): + "Decode given string" + return self.decoder.raw_decode(istring) + diff --git a/src/python/WMCore/ReqMgr/Utils/url_utils.py b/src/python/WMCore/ReqMgr/Utils/url_utils.py new file mode 100644 index 0000000000..886c87567b --- /dev/null +++ b/src/python/WMCore/ReqMgr/Utils/url_utils.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python +#-*- coding: utf-8 -*- +#pylint: disable= +""" +File : url_utils.py +Author : Valentin Kuznetsov +Description: +""" + +# system modules +import os +import time +import urllib +import urllib2 +import httplib +import WMCore.ReqMgr.Utils.jsonwrapper as json + +def get_key_cert(): + """ + Get user key/certificate + """ + key = None + cert = None + globus_key = os.path.join(os.environ['HOME'], '.globus/userkey.pem') + globus_cert = os.path.join(os.environ['HOME'], '.globus/usercert.pem') + if os.path.isfile(globus_key): + key = globus_key + if os.path.isfile(globus_cert): + cert = globus_cert + + # First presendence to HOST Certificate, RARE + if 'X509_HOST_CERT' in os.environ: + cert = os.environ['X509_HOST_CERT'] + key = os.environ['X509_HOST_KEY'] + + # Second preference to User Proxy, very common + elif 'X509_USER_PROXY' in os.environ: + cert = os.environ['X509_USER_PROXY'] + key = cert + + # Third preference to User Cert/Proxy combinition + elif 'X509_USER_CERT' in os.environ: + cert = os.environ['X509_USER_CERT'] + key = os.environ['X509_USER_KEY'] + + # Worst case, look for cert at default location /tmp/x509up_u$uid + elif not key or not cert: + uid = os.getuid() + cert = '/tmp/x509up_u'+str(uid) + key = cert + + if not os.path.exists(cert): + raise Exception("Certificate PEM file %s not found" % key) + if not os.path.exists(key): + raise Exception("Key PEM file %s not found" % key) + + return key, cert + +def disable_urllib2Proxy(): + """ + Setup once and forever urllib2 proxy, see + http://kember.net/articles/obscure-python-urllib2-proxy-gotcha + """ + proxy_support = urllib2.ProxyHandler({}) + opener = urllib2.build_opener(proxy_support) + urllib2.install_opener(opener) + +class HTTPSClientAuthHandler(urllib2.HTTPSHandler): + """ + Simple HTTPS client authentication class based on provided + key/ca information + """ + def __init__(self, key=None, cert=None, level=0): + if level > 1: + urllib2.HTTPSHandler.__init__(self, debuglevel=1) + else: + urllib2.HTTPSHandler.__init__(self) + self.key = key + self.cert = cert + + def https_open(self, req): + """Open request method""" + #Rather than pass in a reference to a connection class, we pass in + # a reference to a function which, for all intents and purposes, + # will behave as a constructor + return self.do_open(self.get_connection, req) + + def get_connection(self, host, timeout=300): + """Connection method""" + if self.key: + return httplib.HTTPSConnection(host, key_file=self.key, + cert_file=self.cert) + return httplib.HTTPSConnection(host) + +def getdata(url, params, headers=None, post=None, verbose=False): + """ + Invoke URL call and retrieve data from data-service based + on provided URL and set of parameters. Use post=True to + invoke POST request. + """ + encoded_data = urllib.urlencode(params) + if not post: + url = url + '?' + encoded_data + if not headers: + headers = {} + if verbose: + print '+++ getdata, url=%s, headers=%s' % (url, headers) + req = urllib2.Request(url) + for key, val in headers.iteritems(): + req.add_header(key, val) + if verbose > 1: + handler = urllib2.HTTPHandler(debuglevel=1) + opener = urllib2.build_opener(handler) + urllib2.install_opener(opener) + ckey, cert = get_key_cert() + handler = HTTPSClientAuthHandler(ckey, cert, verbose) + opener = urllib2.build_opener(handler) + urllib2.install_opener(opener) + try: + if post: + data = urllib2.urlopen(req, encoded_data) + else: + data = urllib2.urlopen(req) + info = data.info() + code = data.getcode() + if verbose > 1: + print "+++ response code:", code + print "+++ response info\n", info + data = json.load(data) + except urllib2.HTTPError as httperror: + msg = 'HTTPError, url=%s, args=%s, headers=%s' \ + % (url, params, headers) + data = {'error': 'Unable to contact %s' % url , 'reason': msg} + try: + data.update({'httperror':extract_http_error(httperror.read())}) + except Exception as exp: + data.update({'httperror': None}) + data = json.dumps(data) + except Exception as exp: + msg = 'HTTPError, url=%s, args=%s, headers=%s, error=%s' \ + % (url, params, headers, str(exp)) + data = {'error': 'Unable to contact %s' % url, 'reason': msg} + data = json.dumps(data) + return data diff --git a/src/python/WMCore/ReqMgr/Web/ReqMgrService.py b/src/python/WMCore/ReqMgr/Web/ReqMgrService.py new file mode 100644 index 0000000000..3ae24d27e3 --- /dev/null +++ b/src/python/WMCore/ReqMgr/Web/ReqMgrService.py @@ -0,0 +1,742 @@ +#!/usr/bin/env python +#-*- coding: ISO-8859-1 -*- + +""" +web server. +""" + +__author__ = "Valentin Kuznetsov" + +# system modules +import os +import sys +import time +import json +import pprint +from types import GeneratorType +try: + import cStringIO as StringIO +except: + import StringIO + +# cherrypy modules +import cherrypy +from cherrypy import expose, response, tools +from cherrypy.lib.static import serve_file +from cherrypy import config as cherryconf + +# ReqMgrSrv modules +from WMCore.ReqMgr.Web.tools import exposecss, exposejs, exposejson, TemplatedPage +from WMCore.ReqMgr.Web.utils import json2table, genid, checkargs, tstamp, sort +from WMCore.ReqMgr.Utils.url_utils import getdata +from WMCore.ReqMgr.Tools.cms import dqm_urls, dbs_urls, releases, architectures +from WMCore.ReqMgr.Tools.cms import scenarios, cms_groups, couch_url +from WMCore.ReqMgr.Tools.cms import web_ui_names, next_status, sites +from WMCore.ReqMgr.Tools.cms import lfn_bases, lfn_unmerged_bases +from WMCore.ReqMgr.Tools.cms import site_white_list, site_black_list + +# WMCore modules +from WMCore.WMSpec.WMWorkloadTools import loadSpecByType +from WMCore.WMSpec.WMWorkload import WMWorkloadHelper +from WMCore.ReqMgr.Service.Auxiliary import Info, Group, Team, Software +from WMCore.ReqMgr.Service.Request import Request +from WMCore.ReqMgr.Service.RestApiHub import RestApiHub +from WMCore.REST.Main import RESTMain + +# WMCore specs +from WMCore.WMSpec.StdSpecs.StdBase import StdBase +from WMCore.WMSpec.StdSpecs.ReReco import ReRecoWorkloadFactory +from WMCore.WMSpec.StdSpecs.MonteCarlo import MonteCarloWorkloadFactory +from WMCore.WMSpec.StdSpecs.StoreResults import StoreResultsWorkloadFactory +from WMCore.WMSpec.StdSpecs.DataProcessing import DataProcessing +from WMCore.WMSpec.StdSpecs.Resubmission import ResubmissionWorkloadFactory +from WMCore.WMSpec.StdSpecs.ReDigi import ReDigiWorkloadFactory +from WMCore.ReqMgr.DataStructs.RequestStatus import REQUEST_START_STATE, REQUEST_STATE_TRANSITION + +# new reqmgr2 APIs +from WMCore.Services.ReqMgr.ReqMgr import ReqMgr + +def sort_bold(docs): + "Return sorted list of bold items from provided doc list" + return ', '.join(['%s'%m for m in sorted(docs)]) + +def set_headers(itype, size=0): + """ + Set response header Content-type (itype) and Content-Length (size). + """ + if size > 0: + response.headers['Content-Length'] = size + response.headers['Content-Type'] = itype + response.headers['Expires'] = 'Sat, 14 Oct 2027 00:59:30 GMT' + +def set_no_cache_flags(): + "Set cherrypy flags to prevent caching" + cherrypy.response.headers['Cache-Control'] = 'no-cache' + cherrypy.response.headers['Pragma'] = 'no-cache' + cherrypy.response.headers['Expires'] = 'Sat, 01 Dec 2001 00:00:00 GMT' + +def set_cache_flags(): + "Set cherrypy flags to prevent caching" + headers = cherrypy.response.headers + for key in ['Cache-Control', 'Pragma']: + if key in headers: + del headers[key] + +def minify(content): + """ + Remove whitespace in provided content. + """ + content = content.replace('\n', ' ') + content = content.replace('\t', ' ') + content = content.replace(' ', ' ') + content = content.replace(' ', ' ') + return content + +def menus(active='search'): + "Return dict of menus" + items = ['admin', 'assign', 'approve', 'create', 'requests'] + mdict = dict(zip(items, ['']*len(items))) + mdict[active] = 'active' + return mdict + +def request_attr(doc, attrs=None): + "Return request attributes/values in separate document" + if not attrs: + attrs = ['RequestName', 'Requestdate', 'Inputdataset', \ + 'Prepid', 'Group', 'Requestor', 'RequestDate', \ + 'RequestStatus'] + rdict = {} + for key in attrs: + if key in doc: + if key=='RequestDate': + tval = doc[key] + if isinstance(tval, list): + while len(tval) < 9: + tval.append(0) + gmt = time.gmtime(time.mktime(tval)) + rdict[key] = time.strftime("%Y-%m-%d %H:%M:%S GMT", gmt) + else: + rdictp[key] = tval + else: + rdict[key] = doc[key] + return rdict + +class ActionMgr(object): + def __init__(self, reqmgr): + "Action manager" + self.reqmgr = reqmgr + + def parse_data(self, jdict): + "Parse json dictionary and align its values" + # TODO: I need to convert strings to int's for some attributes, e.g. RunList, etc. + for key, val in jdict.items(): + if isinstance(val, basestring) and val.find(",") != -1: # comma list + jdict[key] = val.split(",") + return jdict + + def create(self, req): + """ + Create action: + create new request and send it to Requset Manager via POST method + """ + self.add_request('create', req) + if isinstance(req, dict): + docs = [req] + elif isinstance(req, list) or isinstance(req, GeneratorType): + docs = req + else: + raise Exception('Unsupported request type') + for jsondata in docs: + doc = self.parse_data(jsondata) + print "self.reqmgr.insertRequests(jsondata)" + print pprint.pformat(doc) + try: + response = self.reqmgr.insertRequests(doc) + print "### ActionMgr::create response", pprint.pformat(response) + except Exception as exc: + print "ERROR", str(exc) + return 'fail' + return 'ok' + + def approve(self, req, new_status): + """ + Approve action + should get list of requests to approve via Request::get(status) + and change request status from new to status (assigned/rejected) + """ + self.add_request('approve', req) + status = req.get('status', '') + docs = self.get_request_names(req) + for rname in docs: + print "self.reqmgr.updateRequestStatus(%s, %s)" % (rname, new_status) + try: + self.reqmgr.updateRequestStatus(rname, new_status) + except Exception as exc: + print "ERROR", str(exc) + return 'fail' + return 'ok' + + def assign(self, req, new_status, kwds): + """ + Assign action + should get list of requests to assign via Request::get(status) + and change request status from assignment-approved to assigned/rejected. + Additional parameters are passed via kwds dict. + """ + self.add_request('assign', req) + docs = self.get_request_names(req) + kwds.update({"RequestStatus": new_status}) + for rname in docs: + print "self.reqmgr.updateRequestProperty(%s, %s)" % (rname, kwds) + try: + response = self.reqmgr.updateRequestProperty(rname, kwds) + print "response", response + except Exception as exc: + print "ERROR", str(exc) + return 'fail' + return 'ok' + + def add_request(self, action, req): + """ + Add request to internal cache or log it. + """ + print "\n### add_request %s\n%s" % (action, pprint.pformat(req)) + + def get_request_names(self, doc): + "Extract request names from given documents" + docs = [] + for key in doc.keys(): + if key.startswith('request'): + rid = key.split('request-')[-1] + if rid != 'all': + docs.append(rid) + del doc[key] + return docs + +class ReqMgrService(TemplatedPage): + """ + Request Manager web service class + """ + def __init__(self, app, config, mount): + print "\n### Configuration:" + print config + self.base = config.base + if config and not isinstance(config, dict): + web_config = config.dictionary_() + if not config: + web_config = {'base': self.base} + pprint.pprint(web_config) + TemplatedPage.__init__(self, web_config) + imgdir = os.environ.get('RM_IMAGESPATH', os.getcwd()+'/images') + self.imgdir = web_config.get('imgdir', imgdir) + cssdir = os.environ.get('RM_CSSPATH', os.getcwd()+'/css') + self.cssdir = web_config.get('cssdir', cssdir) + jsdir = os.environ.get('RM_JSPATH', os.getcwd()+'/js') + self.jsdir = web_config.get('jsdir', jsdir) + # read scripts area and initialize data-ops scripts + self.sdir = os.environ.get('RM_SCRIPTS', os.getcwd()+'/scripts') + self.sdir = web_config.get('sdir', self.sdir) + self.sdict_thr = web_config.get('sdict_thr', 600) # put reasonable 10 min interval + self.sdict = {'ts':time.time()} # placeholder for data-ops scripts + self.update_scripts(force=True) + + # To be filled at run time + self.cssmap = {} + self.jsmap = {} + self.imgmap = {} + self.yuimap = {} + + # keep track of specs + self.specs = {'StdBase': StdBase().getWorkloadArguments(), + 'ReReco': ReRecoWorkloadFactory().getWorkloadArguments(), + 'MonteCarlo': MonteCarloWorkloadFactory().getWorkloadArguments(), + 'StoreResults': StoreResultsWorkloadFactory().getWorkloadArguments(), + 'DataProcessing': DataProcessing().getWorkloadArguments(), + 'Resubmission': ResubmissionWorkloadFactory().getWorkloadArguments(), + 'ReDigi': ReDigiWorkloadFactory().getWorkloadArguments()} + + # Update CherryPy configuration + mime_types = ['text/css'] + mime_types += ['application/javascript', 'text/javascript', + 'application/x-javascript', 'text/x-javascript'] + cherryconf.update({'tools.encode.on': True, + 'tools.gzip.on': True, + 'tools.gzip.mime_types': mime_types, + }) + self._cache = {} + + # initialize rest API + statedir = '/tmp' + app = RESTMain(config, statedir) # REST application + mount = '/rest' # mount point for cherrypy service + api = RestApiHub(app, config.reqmgr, mount) + + # initialize access to reqmgr2 APIs +# url = "https://localhost:8443/reqmgr2" + self.reqmgr = ReqMgr(config.reqmgr.reqmgr2_url) + + # admin helpers + self.admin_info = Info(app, api, config.reqmgr, mount=mount+'/info') + self.admin_group = Group(app, api, config.reqmgr, mount=mount+'/group') + self.admin_team = Team(app, api, config.reqmgr, mount=mount+'/team') + + # action manager (will be replaced with appropriate class + self.actionmgr = ActionMgr(self.reqmgr) + + # get fields which we'll use in templates + cdict = config.reqmgr.dictionary_() + self.couch_url = cdict.get('couch_host', '') + self.couch_dbname = cdict.get('couch_reqmgr_db', '') + self.couch_wdbname = cdict.get('couch_workload_summary_db', '') + self.acdc_url = cdict.get('acdc_host', '') + self.acdc_dbname = cdict.get('acdc_db', '') + self.configcache_url = cdict.get('couch_config_cache_url', self.couch_url) + self.dbs_url = cdict.get('dbs_url', '') + self.dqm_url = cdict.get('dqm_url', '') + self.sw_ver = cdict.get('default_sw_version', 'CMSSW_5_2_5') + self.sw_arch = cdict.get('default_sw_scramarch', 'slc5_amd64_gcc434') + + def user(self): + """ + Return user name associated with this instance. + This method should implement fetching user parameters through passed DN + """ + return 'testuser' + + def user_dn(self): + """ + Return user DN. + This method should implement fetching user DN + """ + return '/CN/bla/foo/' + + def update_scripts(self, force=False): + "Update scripts dict" + if force or abs(time.time()-self.sdict['ts']) > self.sdict_thr: + for item in os.listdir(self.sdir): + with open(os.path.join(self.sdir, item), 'r') as istream: + self.sdict[item.split('.')[0]] = istream.read() + self.sdict['ts'] = time.time() + + def abs_page(self, tmpl, content): + """generate abstract page""" + menu = self.templatepage('menu', menus=menus(tmpl)) + if tmpl == 'main': + body = self.templatepage('generic', menu=menu, content=content) + page = self.templatepage('main', content=body, user=self.user()) + else: + body = self.templatepage(tmpl, menu=menu, content=content) + page = self.templatepage('main', content=body, user=self.user()) + return page + + def page(self, content): + """ + Provide page wrapped with top/bottom templates. + """ + return self.templatepage('main', content=content) + + def error(self, content): + "Generate common error page" + content = self.templatepage('error', content=content) + return self.abs_page('generic', content) + + @expose + def index(self, **kwds): + """Main page""" + apis = {} + scripts = {} + for idx in range(5): + key = 'api_%s' % idx + val = '%s description' % key + apis[key] = val + skey = 'script_%s' % idx + sval = '%s description' % skey + scripts[skey] = sval + content = self.templatepage('apis', apis=apis, scripts=scripts) + return self.abs_page('generic', content) + + ### Admin actions ### + + @expose + def admin(self, **kwds): + """admin page""" + print "\n### ADMIN PAGE" + rows = self.admin_info.get() + print "rows", [r for r in rows] + + content = self.templatepage('admin') + return self.abs_page('generic', content) + + @expose + def add_user(self, **kwds): + """add_user action""" + rid = genid(kwds) + status = "ok" # chagne to whatever it would be + content = self.templatepage('confirm', ticket=rid, user=self.user(), status=status) + return self.abs_page('generic', content) + + @expose + def add_group(self, **kwds): + """add_group action""" + rows = self.admin_group.get() + print "\n### GROUPS", [r for r in rows] + rid = genid(kwds) + status = "ok" # chagne to whatever it would be + content = self.templatepage('confirm', ticket=rid, user=self.user(), status=status) + return self.abs_page('generic', content) + + @expose + def add_team(self, **kwds): + """add_team action""" + rows = self.admin_team.get() + print "\n### TEAMS", kwds, [r for r in rows] + print "request to add", kwds + rid = genid(kwds) + status = "ok" # chagne to whatever it would be + content = self.templatepage('confirm', ticket=rid, user=self.user(), status=status) + return self.abs_page('generic', content) + + ### Request actions ### + + @expose + @checkargs(['status', 'sort']) + def assign(self, **kwds): + """assign page""" + if not kwds: + kwds = {} + if 'status' not in kwds: + kwds.update({'status': 'assignment-approved'}) + docs = [] + attrs = ['RequestName', 'RequestDate', 'Group', 'Requestor', 'RequestStatus'] + data = self.reqmgr.getRequestByStatus(statusList=[kwds['status']]) + for row in data: + for key, val in row.items(): + docs.append(request_attr(val, attrs)) + sortby = kwds.get('sort', 'status') + docs = sort(docs, sortby) + content = self.templatepage('assign', sort=sortby, + site_white_list=site_white_list(), + site_black_list=site_black_list(), + user=self.user(), user_dn=self.user_dn(), requests=docs, + cmssw_versions=releases(), scram_arch=architectures(), + sites=sites(), lfn_bases=lfn_bases(), + lfn_unmerged_bases=lfn_unmerged_bases()) + return self.abs_page('generic', content) + + @expose + @checkargs(['status', 'sort']) + def approve(self, **kwds): + """ + Approve page: get list of request associated with user DN. + Fetch their status list from ReqMgr and display if requests + were seen by data-ops. + """ + if not kwds: + kwds = {} + if 'status' not in kwds: + kwds.update({'status': 'new'}) + kwds.update({'_nostale':True}) + docs = [] + attrs = ['RequestName', 'RequestDate', 'Group', 'Requestor', 'RequestStatus'] + data = self.reqmgr.getRequestByStatus(statusList=[kwds['status']]) + for row in data: + for key, val in row.items(): + docs.append(request_attr(val, attrs)) + sortby = kwds.get('sort', 'status') + docs = sort(docs, sortby) + content = self.templatepage('approve', requests=docs, date=tstamp(), + sort=sortby) + return self.abs_page('generic', content) + + @expose + def ajax_action(self, action, ids, new_status, **kwds): + """ + AJAX action creates request dictionary and pass it to + action manager method. + """ + req = {} + status = None + if isinstance(ids, list): + for rid in ids: + req[rid] = 'on' + elif isinstance(ids, basestring): + req[ids] = 'on' + else: + cherrypy.response.status = 501 + return + if action == 'approve': + status = getattr(self.actionmgr, action)(req, new_status) + if status == 'ok': + cherrypy.response.status = 200 + else: + cherrypy.response.status = 400 + return + elif action == 'assign': + status = getattr(self.actionmgr, action)(req, new_status, kwds) + if status == 'ok': + cherrypy.response.status = 200 + else: + cherrypy.response.status = 400 + return + elif action == 'create': + script = kwds.get('script', '') + if script: + docs = self.generate_objs(script, kwds) + else: + docs = [kwds] + status = getattr(self.actionmgr, action)(docs) + if status == 'ok': + cherrypy.response.status = 201 + else: + cherrypy.response.status = 400 + return + else: + cherrypy.response.status = 501 + return + + @expose + def create(self, **kwds): + """create page""" + spec = kwds.get('form', 'ReReco') + fname = 'json/%s' % str(spec) + # request form + jsondata = self.templatepage(fname, + user=json.dumps(self.user()), + dn=json.dumps(self.user_dn()), + groups=json.dumps(cms_groups()), + releases=json.dumps(self.sw_ver), + arch=json.dumps(self.sw_arch), + scenarios=json.dumps(scenarios()), + dqm_urls=json.dumps(self.dqm_url), + couch_url=json.dumps(self.couch_url), + couch_dbname=json.dumps(self.couch_dbname), + couch_wdbname=json.dumps(self.couch_wdbname), + dbs_url=json.dumps(self.dbs_url), + cc_url=json.dumps(self.configcache_url), + cc_id=json.dumps("some_id"), # TODO: get it elsewhere + acdc_url=json.dumps(self.acdc_url), + acdc_dbname=json.dumps(self.acdc_dbname), + ) + try: + jsondata = json.loads(jsondata) + except Exception as exp: + msg = '
Fail to load JSON for %s workflow
\n' % spec + msg += '
Error: %s
\n' % str(exp) + msg += '
JSON: %s
' % jsondata + return self.error(msg) + + # check if JSON template provides all required attributes + required = [k for k,v in self.specs[spec].items() if v['optional']==False] + if set(jsondata.keys()) & set(required) != set(required): + missing = list(set(required)-set(jsondata.keys())) + content = '%s spec template does not contain all required attributes.' \ + % spec + content += '
Missing attributes: %s' \ + % sort_bold(missing) + return self.error(content) + + # check if JSON template contains all required values + vdict = {} # dict of empty values + tdict = {} # dict of type mismatches + dropdowns = ['ScramArch', 'Group', 'CMSSWVersion'] + for key in required: + value = jsondata[key] + if not value: + vdict[key] = jsondata[key] + type1 = str if type(value) == unicode else type(value) + stype = self.specs[spec][key]['type'] + type2 = str if stype == unicode else stype + if type1 != type2 and key not in dropdowns: + tdict[key] = (type(value), self.specs[spec][key]['type']) + if vdict.keys(): + content = 'Empty values in %s spec: %s'\ + % (spec, sort_bold(vdict.keys())) + return self.error(content) + if tdict.keys(): + types = [] + for key, val in tdict.items(): + type0 = str(val[0]).replace('>', '').replace('<', '') + type1 = str(val[1]).replace('>', '').replace('<', '') + types.append('%s: %s, should be %s' % (key, type0, type1)) + content = '
Type mismatches in %s spec:
%s'\ + % (spec, '
'.join(types)) + return self.error(content) + + # create templatized page out of provided forms + self.update_scripts() + content = self.templatepage('create', table=json2table(jsondata, web_ui_names()), + jsondata=json.dumps(jsondata, indent=2), name=spec, + scripts=[s for s in self.sdict.keys() if s!='ts']) + return self.abs_page('generic', content) + + @expose + def confirm_action(self, **kwds): + """ + Confirm action method is called from web UI forms. It grabs input parameters + and passed them to Action manager. + """ + try: + action = kwds.pop('action') + if 'script' in kwds: # we got a script to apply + script = kwds.pop('script') + jsondict = json.loads(kwds.get('jsondict', "{}")) + if not jsondict: + jsondict = kwds + if script == "": + docs = [jsondict] + rids = genid(jsondict) + else: + docs = self.generate_objs(script, jsondict) + rids = [genid(o) for o in docs] + status = getattr(self.actionmgr, action)(docs) + else: + status = getattr(self.actionmgr, action)(kwds) + rids = genid(kwds) + content = self.templatepage('confirm', ticket=rids, user=self.user(), status=status) + return self.abs_page('generic', content) + except: + msg = '
No action is specified
' + self.error(msg) + + def generate_objs(self, script, jsondict): + """Generate objects from givem JSON template""" + self.update_scripts() + code = self.sdict.get(script, '') + if code.find('def genobjs(jsondict)') == -1: + return self.error("Improper python snippet, your code should start with def genobjs(jsondict) function") + exec(code) # code snippet must starts with genobjs function + return [r for r in genobjs(jsondict)] + + @exposejson + def fetch(self, rid): + "Fetch document for given id" + return self.reqmgr.getRequestByNames(rid) + + def doc(self, rid): + "Fetch document for given id" + return self.reqmgr.getRequestByNames(rid) + + @expose + def requests(self, **kwds): + """Check status of requests""" + if not kwds: + kwds = {} + if 'status' not in kwds: + kwds.update({'status': 'acquired'}) + results = self.reqmgr.getRequestByStatus(kwds['status']) + docs = [] + for req in results: + for key, doc in req.items(): + docs.append(request_attr(doc)) + sortby = kwds.get('sort', 'status') + docs = sort(docs, sortby) + content = self.templatepage('requests', requests=docs, sort=sortby) + return self.abs_page('generic', content) + + @expose + def request(self, **kwargs): + "Get data example and expose it as json" + dataset = kwargs.get('uinput', '') + if not dataset: + return {'error':'no input dataset'} + url = 'https://cmsweb.cern.ch/reqmgr/rest/outputdataset/%s' % dataset + params = {} + headers = {'Accept': 'application/json;text/json'} + wdata = getdata(url, params) + wdict = dict(date=time.ctime(), team='Team-A', status='Running', ID=genid(wdata)) + winfo = self.templatepage('workflow', wdict=wdict, + dataset=dataset, code=pprint.pformat(wdata)) + content = self.templatepage('search', content=winfo) + return self.abs_page('generic', content) + + ### Aux methods ### + + @expose + def images(self, *args, **kwargs): + """ + Serve static images. + """ + args = list(args) + self.check_scripts(args, self.imgmap, self.imgdir) + mime_types = ['*/*', 'image/gif', 'image/png', + 'image/jpg', 'image/jpeg'] + accepts = cherrypy.request.headers.elements('Accept') + for accept in accepts: + if accept.value in mime_types and len(args) == 1 \ + and args[0] in self.imgmap: + image = self.imgmap[args[0]] + # use image extension to pass correct content type + ctype = 'image/%s' % image.split('.')[-1] + cherrypy.response.headers['Content-type'] = ctype + return serve_file(image, content_type=ctype) + + def serve(self, kwds, imap, idir, datatype='', minimize=False): + "Serve files for high level APIs (yui/css/js)" + args = [] + for key, val in kwds.items(): + if key == 'f': # we only look-up files from given kwds dict + if isinstance(val, list): + args += val + else: + args.append(val) + scripts = self.check_scripts(args, imap, idir) + return self.serve_files(args, scripts, imap, datatype, minimize) + + @exposecss + @tools.gzip() + def css(self, **kwargs): + """ + Serve provided CSS files. They can be passed as + f=file1.css&f=file2.css + """ + resource = kwargs.get('resource', 'css') + if resource == 'css': + return self.serve(kwargs, self.cssmap, self.cssdir, 'css', True) + + @exposejs + @tools.gzip() + def js(self, **kwargs): + """ + Serve provided JS scripts. They can be passed as + f=file1.js&f=file2.js with optional resource parameter + to speficy type of JS files, e.g. resource=yui. + """ + resource = kwargs.get('resource', 'js') + if resource == 'js': + return self.serve(kwargs, self.jsmap, self.jsdir) + + def serve_files(self, args, scripts, resource, datatype='', minimize=False): + """ + Return asked set of files for JS, YUI, CSS. + """ + idx = "-".join(scripts) + if idx not in self._cache.keys(): + data = '' + if datatype == 'css': + data = '@CHARSET "UTF-8";' + for script in args: + path = os.path.join(sys.path[0], resource[script]) + path = os.path.normpath(path) + ifile = open(path) + data = "\n".join ([data, ifile.read().\ + replace('@CHARSET "UTF-8";', '')]) + ifile.close() + if datatype == 'css': + set_headers("text/css") + if minimize: + self._cache[idx] = minify(data) + else: + self._cache[idx] = data + return self._cache[idx] + + def check_scripts(self, scripts, resource, path): + """ + Check a script is known to the resource map + and that the script actually exists + """ + for script in scripts: + if script not in resource.keys(): + spath = os.path.normpath(os.path.join(path, script)) + if os.path.isfile(spath): + resource.update({script: spath}) + return scripts diff --git a/src/python/WMCore/ReqMgr/Web/__init__.py b/src/python/WMCore/ReqMgr/Web/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/python/WMCore/ReqMgr/Web/tools.py b/src/python/WMCore/ReqMgr/Web/tools.py new file mode 100644 index 0000000000..04d80f76d5 --- /dev/null +++ b/src/python/WMCore/ReqMgr/Web/tools.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python +#-*- coding: ISO-8859-1 -*- + +""" +Web tools. +""" + +__revision__ = "$Id: tools.py,v 1.5 2010/04/07 18:19:31 valya Exp $" +__author__ = "Valentin Kuznetsov" +__email__ = "vkuznet@gmail.com" + +# system modules +import os +import types +import logging + +from datetime import datetime, timedelta +from time import mktime +from wsgiref.handlers import format_date_time + +# cherrypy modules +import cherrypy +from cherrypy import log as cplog +from cherrypy import expose + +# cheetag modules +from Cheetah.Template import Template +from Cheetah import Version + +import WMCore.ReqMgr.Utils.jsonwrapper as json +from json import JSONEncoder + +class Page(object): + """ + __Page__ + + Page is a base class that holds a configuration + """ + def __init__(self): + self.name = "Page" + + def warning(self, msg): + """Define warning log""" + if msg: + self.log(msg, logging.WARNING) + + def exception(self, msg): + """Define exception log""" + if msg: + self.log(msg, logging.ERROR) + + def debug(self, msg): + """Define debug log""" + if msg: + self.log(msg, logging.DEBUG) + + def info(self, msg): + """Define info log""" + if msg: + self.log(msg, logging.INFO) + + def log(self, msg, severity): + """Define log level""" + if type(msg) != str: + msg = str(msg) + if msg: + cplog(msg, context=self.name, + severity=severity, traceback=False) + +class TemplatedPage(Page): + """ + TemplatedPage is a class that provides simple Cheetah templating + """ + def __init__(self, config): + Page.__init__(self) + tmpldir = os.environ.get('RM_TMPLPATH', os.getcwd()+'/templates') + self.templatedir = config.get('tmpldir', tmpldir) + self.name = "TemplatedPage" + self.base = config.get('base', '') + verbose = config.get('verbose', 0) + if verbose: + self.info("Templates are located in: %s" % self.templatedir) + self.info("Using Cheetah version: %s" % Version) + + def templatepage(self, ifile=None, *args, **kwargs): + """ + Template page method. + """ + search_list = [{'base':self.base}] + if len(args) > 0: + search_list.append(args) + if len(kwargs) > 0: + search_list.append(kwargs) + templatefile = os.path.join(self.templatedir, ifile + '.tmpl') + if os.path.exists(templatefile): + # little workaround to fix '#include' + search_list.append({'templatedir': self.templatedir}) + template = Template(file=templatefile, searchList=search_list) + return template.respond() + + else: + self.warning("%s not found at %s" % (ifile, self.templatedir)) + return "Template %s not known" % ifile + +def exposetext (func): + """CherryPy expose Text decorator""" + @expose + def wrapper (self, *args, **kwds): + """Decorator wrapper""" + data = func (self, *args, **kwds) + cherrypy.response.headers['Content-Type'] = "text/plain" + return data + return wrapper + +def jsonstreamer(func): + """JSON streamer decorator""" + def wrapper (self, *args, **kwds): + """Decorator wrapper""" + cherrypy.response.headers['Content-Type'] = "application/json" + func._cp_config = {'response.stream': True} + head, data = func (self, *args, **kwds) + yield json.dumps(head)[:-1] # do not yield } + yield ', "data": [' + if isinstance(data, dict): + for chunk in JSONEncoder().iterencode(data): + yield chunk + elif isinstance(data, list) or isinstance(data, types.GeneratorType): + sep = '' + for rec in data: + if sep: + yield sep + for chunk in JSONEncoder().iterencode(rec): + yield chunk + if not sep: + sep = ', ' + else: + msg = 'jsonstreamer, improper data type %s' % type(data) + raise Exception(msg) + yield ']}' + return wrapper + +def exposejson (func): + """CherryPy expose JSON decorator""" + @expose + def wrapper (self, *args, **kwds): + """Decorator wrapper""" + encoder = JSONEncoder() + data = func (self, *args, **kwds) + cherrypy.response.headers['Content-Type'] = "text/json" + try: + jsondata = encoder.encode(data) + return jsondata + except: + Exception("Fail to JSONtify obj '%s' type '%s'" \ + % (data, type(data))) + return wrapper + +def exposejs (func): + """CherryPy expose JavaScript decorator""" + @expose + def wrapper (self, *args, **kwds): + """Decorator wrapper""" + data = func (self, *args, **kwds) + cherrypy.response.headers['Content-Type'] = "text/javascript" + return data + return wrapper + +def exposecss (func): + """CherryPy expose CSS decorator""" + @expose + def wrapper (self, *args, **kwds): + """Decorator wrapper""" + data = func (self, *args, **kwds) + cherrypy.response.headers['Content-Type'] = "text/css" + return data + return wrapper + +def make_timestamp(seconds=0): + """Create timestamp""" + then = datetime.now() + timedelta(seconds=seconds) + return mktime(then.timetuple()) + +def make_rfc_timestamp(seconds=0): + """Create RFC timestamp""" + return format_date_time(make_timestamp(seconds)) diff --git a/src/python/WMCore/ReqMgr/Web/utils.py b/src/python/WMCore/ReqMgr/Web/utils.py new file mode 100644 index 0000000000..578b6baf47 --- /dev/null +++ b/src/python/WMCore/ReqMgr/Web/utils.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python +#-*- coding: utf-8 -*- +#pylint: disable= +""" +File : utils.py +Author : Valentin Kuznetsov +Description: +""" + +# system modules +import cgi +import json +import time +import hashlib +import cherrypy +from urllib2 import URLError + +def tstamp(): + "Generic time stamp" + return time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime()) + +def gen_color(val): + "Generate unique color code for given string value" + keyhash = hashlib.md5() + keyhash.update(val) + col = '#%s' % keyhash.hexdigest()[:6] + return col + +def quote(data): + """ + Sanitize the data using cgi.escape. + """ + if isinstance(data, int) or isinstance(data, float): + res = data + elif isinstance(data, dict): + res = data + elif isinstance(data, list): + res = data + elif isinstance(data, long) or isinstance(data, int) or\ + isinstance(data, float): + res = data + else: + try: + if data: + res = cgi.escape(data, quote=True) + else: + res = "" + except Exception as exc: + print_exc(exc) + print "Unable to cgi.escape(%s, quote=True)" % data + res = "" + return res + +def json2table(jsondata, web_ui_map): + """ + Convert input json dict into HTML table based on assumtion that + input json is in a simple key:value form. + """ + table = """\n""" + table += "\n" + keys = sorted(jsondata.keys()) + for key in keys: + val = jsondata[key] + if isinstance(val, list) and not val: # empty list replace with input text tag + val = "" + if isinstance(val, list): + sel = "" + val = sel + elif isinstance(val, basestring): + if val.startswith('REPLACE-'): + val = ''\ + % (key, val) + elif len(val) < 80: + val = '' % (key, val) + else: + val = '' % (key, val) + else: + val = '' % (key, val) + if key in web_ui_map: + kname = web_ui_map[key] + else: + kname = key.capitalize().replace('_', ' ') + table += "\n" % (kname, val) + table += "
FieldValue
%s%s
" + return table + +def genid(kwds): + "Generate id for given field" + if isinstance(kwds, dict): + record = dict(kwds) + data = json.JSONEncoder(sort_keys=True).encode(record) + else: + data = str(kwds) + keyhash = hashlib.md5() + keyhash.update(data) + return keyhash.hexdigest() + +def checkarg(kwds, arg): + """Check arg in a dict that it has str/unicode type""" + data = kwds.get(arg, None) + cond = data and (isinstance(data, str) or isinstance(data, unicode)) + return cond + +def checkargs(supported): + """ + Decorator to check arguments in provided supported list + """ + def wrap(func): + """Wrap input function""" + + def require_string(val): + """Check that provided input is a string""" + if not (isinstance(val, str) or isinstance(val, unicode)): + code = web_code('Invalid input') + raise URLError('code=%s' % code) + + def wrapped_f(self, *args, **kwds): + """Wrap function arguments""" + # check request headers. For methods POST/PUT + # we need to read request body to get parameters + if cherrypy.request.method == 'POST' or\ + cherrypy.request.method == 'PUT': + try: + body = cherrypy.request.body.read() + except: + body = None + if args and kwds: + code = web_code('Misleading request') + raise URLError('code=%s' % code) + if body: + jsondict = json.loads(body, encoding='latin-1') + else: + jsondict = kwds + for key, val in jsondict.iteritems(): + kwds[str(key)] = str(val) + + if not kwds: + if args: + kwds = args[-1] + keys = [] + if not isinstance(kwds, dict): + code = web_code('Unsupported kwds') + raise URLError('code=%s' % code) + if kwds: + keys = [i for i in kwds.keys() if i not in supported] + if keys: + code = web_code('Unsupported key') + raise URLError('code=%s' % code) + if checkarg(kwds, 'status'): + if kwds['status'] not in \ + ['new', 'assigned']: + code = web_code('Unsupported view') + raise URLError('code=%s' % code) + data = func (self, *args, **kwds) + return data + wrapped_f.__doc__ = func.__doc__ + wrapped_f.__name__ = func.__name__ + wrapped_f.exposed = True + return wrapped_f + wrap.exposed = True + return wrap + +WEB_CODES = [ + (0 , 'N/A'), + (1 , 'Unsupported key'), + (2 , 'Unsupported value'), + (3 , 'Unsupported method'), + (4 , 'Unsupported collection'), + (5 , 'Unsupported database'), + (6 , 'Unsupported view'), + (7 , 'Unsupported format'), + (8 , 'Wrong type'), + (9 , 'Misleading request'), + (10 , 'Invalid query'), + (11 , 'Exception'), + (12 , 'Invalid input'), + (13 , 'Unsupported expire value'), + (14 , 'Unsupported order value'), + (15 , 'Unsupported skey value'), + (16 , 'Unsupported idx value'), + (17 , 'Unsupported limit value'), + (18 , 'Unsupported dir value'), + (19 , 'Unsupported sort value'), + (20 , 'Unsupported ajax value'), + (21 , 'Unsupported show value'), + (22 , 'Unsupported dasquery value'), + (23 , 'Unsupported dbcoll value'), + (24 , 'Unsupported msg value'), + (25 , 'Unable to start DASCore'), + (26 , 'No file id'), + (27 , 'Unsupported id value'), + (28 , 'Server error'), + (29 , 'Query is not suitable for this view'), + (30 , 'Parser error'), + (31 , 'Unsupported pid value'), + (32 , 'Unsupported interval value'), + (33 , 'Unsupported kwds'), +] +def decode_code(code): + """Return human readable string for provided code ID""" + for idx, msg in WEB_CODES: + if code == idx: + return msg + return 'N/A' + +def web_code(error): + """Return WEB code for provided error string""" + for idx, msg in WEB_CODES: + if msg.lower() == error.lower(): + return idx + return -1 + +def sort(docs, sortby): + "Sort given documents by sortby attribute" + for doc in docs: + yield doc diff --git a/src/python/WMCore/Services/ReqMgr/ReqMgr.py b/src/python/WMCore/Services/ReqMgr/ReqMgr.py new file mode 100644 index 0000000000..b55879b4ea --- /dev/null +++ b/src/python/WMCore/Services/ReqMgr/ReqMgr.py @@ -0,0 +1,173 @@ +from WMCore.Wrappers import JsonWrapper +from WMCore.Services.Service import Service + +class ReqMgr(Service): + + """ + API for dealing with retrieving information from RequestManager dataservice + + """ + + def __init__(self, url, header = {}): + """ + responseType will be either xml or json + """ + + httpDict = {} + # url is end point + httpDict['endpoint'] = "%s/data" % url + + # cherrypy converts request.body to params when content type is set + # application/x-www-form-urlencodeds + httpDict.setdefault("content_type", 'application/json') + httpDict.setdefault('cacheduration', 0) + httpDict.setdefault("accept_type", "application/json") + httpDict.update(header) + self.encoder = JsonWrapper.dumps + Service.__init__(self, httpDict) + # This is only for the unittest: never set it true unless it is unittest + self._noStale = False + + + def _getResult(self, callname, clearCache = True, + args = None, verb = "GET", encoder = JsonWrapper.loads, + decoder = JsonWrapper.loads, + contentType = None): + """ + _getResult_ + + """ + result = '' + file = callname.replace("/", "_") + if clearCache: + self.clearCache(file, args, verb) + + f = self.refreshCache(file, callname, args, encoder = encoder, + verb = verb, contentType = contentType) + result = f.read() + f.close() + + if result and decoder: + result = decoder(result) + return result + + def _createQuery(self, queryDict): + """ + _createQuery + :param queryDict: is the format of {name: values} fair. value can be sting, int or list + :type queryDict: dict + :returns: url query string + + """ + if self._noStale: + args = "_nostale=true&" + else: + args = "" + for name, values in queryDict.items(): + if isinstance(values, basestring) or isinstance(values, int): + values = [values] + for val in values: + args +='%s=%s&' % (name, val) + + return args.rstrip('&') + + def getRequestByNames(self, names): + + """ + _getRequestByNames_ + + :param names: list or sting of request name(s) + :type statusList: list, str + :returns: list of dict or list of request names depending on the detail value + -- [{'test_RequestString-OVERRIDE-ME_141125_142331_4966': {'BlockBlacklist': [], + 'BlockWhitelist': [], + 'CMSSWVersion': 'CMSSW_4_4_2_patch2', + .... + '_id': 'test_RequestString-OVERRIDE-ME_141125_142331_4966', + 'inputMode': 'couchDB'}}] + TODO: need proper error handling if status is not 200 from orignal reporting. + + """ + + query = self._createQuery({'name': names}) + callname = 'request?%s' % query + return self._getResult(callname, verb = "GET")['result'] + + def getRequestByStatus(self, statusList, detail = False): + """ + _getRequestByStatus_ + + :param statusList: list of status + :type statusList: list + :param detail: boolean of request list. + :type detail: boolean + :returns: list of dict or list of request names depending on the detail value + -- [{'test_RequestString-OVERRIDE-ME_141125_142331_4966': {'BlockBlacklist': [], + 'BlockWhitelist': [], + 'CMSSWVersion': 'CMSSW_4_4_2_patch2', + .... + '_id': 'test_RequestString-OVERRIDE-ME_141125_142331_4966', + 'inputMode': 'couchDB'}}] + TODO: need proper error handling if status is not 200 from orignal reporting. + """ + + query = self._createQuery({'status': statusList}) + callname = 'request?%s' % query + return self._getResult(callname, verb = "GET")['result'] + + + def insertRequests(self, requestDict): + """ + _insertRequests_ + + :param requestDict: request argument dictionary + :type requestDict: dict + :returns: list of dictionary -- [{'test_RequestString-OVERRIDE-ME_141125_142331_4966': {'BlockBlacklist': [], + 'BlockWhitelist': [], + 'CMSSWVersion': 'CMSSW_4_4_2_patch2', + .... + '_id': 'test_RequestString-OVERRIDE-ME_141125_142331_4966', + 'inputMode': 'couchDB'}}] + TODO: need proper error handling if status is not 200 from orignal reporting. + """ + return self["requests"].post('request', requestDict)[0]['result'] + + def updateRequestStatus(self, request, status): + """ + _updateRequestStatus_ + + :param request: request(workflow name) + :type reqeust: str + :param status: status of workflow to update (i.e. 'assigned') + :type status: str + :returns: list of dictionary -- [{'test_RequestString-OVERRIDE-ME_141125_142331_4966': {'BlockBlacklist': [], + 'BlockWhitelist': [], + 'CMSSWVersion': 'CMSSW_4_4_2_patch2', + .... + '_id': 'test_RequestString-OVERRIDE-ME_141125_142331_4966', + 'inputMode': 'couchDB'}}] + TODO: need proper error handling if status is not 200 from orignal reporting. + """ + + status = {"RequestStatus": status} + status["RequestName"] = request + return self["requests"].put('request', status)[0]['result'] + + def updateRequestProperty(self, request, propDict): + """ + _updateRequestProperty_ + :param request: request(workflow name) + :type reqeust: str + :param propDict: request property with key value -- {"SiteWhitelist": ["ABC"], "SiteBlacklist": ["A"], "RequestStatus": "assigned"} + :type propDict: dict + :returns: list of dictionary -- [{'test_RequestString-OVERRIDE-ME_141125_142331_4966': {'BlockBlacklist': [], + 'BlockWhitelist': [], + 'CMSSWVersion': 'CMSSW_4_4_2_patch2', + .... + '_id': 'test_RequestString-OVERRIDE-ME_141125_142331_4966', + 'inputMode': 'couchDB'}}] + """ + propDict["RequestName"] = request + return self["requests"].put('request/%s' % request, propDict)[0]['result'] + + diff --git a/src/python/WMCore/Services/ReqMgr/__init__.py b/src/python/WMCore/Services/ReqMgr/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/python/WMCore/Services/RequestDB/RequestDBReader.py b/src/python/WMCore/Services/RequestDB/RequestDBReader.py index d977d10b29..c6de50cf73 100644 --- a/src/python/WMCore/Services/RequestDB/RequestDBReader.py +++ b/src/python/WMCore/Services/RequestDB/RequestDBReader.py @@ -1,6 +1,6 @@ import time import logging -from WMCore.Database.CMSCouch import CouchServer +from WMCore.Database.CMSCouch import CouchServer, Database from WMCore.Lexicon import splitCouchServiceURL, sanitizeURL from WMCore.Wrappers.JsonWrapper import JSONEncoder @@ -16,13 +16,19 @@ def _commonInit(self, couchURL, dbName, couchapp): setting up comon variables for inherited class. inherited class should call this in their init function """ - if dbName: - self.couchURL = couchURL - self.dbName = dbName - else: - self.couchURL, self.dbName = splitCouchServiceURL(couchURL) - self.couchServer = CouchServer(self.couchURL) - self.couchDB = self.couchServer.connectDatabase(self.dbName, False) + if isinstance(couchURL, Database): + self.couchDB = couchURL + self.couchURL = self.couchDB['host'] + self.dbName = self.couchDB.name + self.couchServer = CouchServer(self.couchURL) + else: + if dbName: + self.couchURL = couchURL + self.dbName = dbName + else: + self.couchURL, self.dbName = splitCouchServiceURL(couchURL) + self.couchServer = CouchServer(self.couchURL) + self.couchDB = self.couchServer.connectDatabase(self.dbName, False) self.couchapp = couchapp self.defaultStale = {"stale": "update_after"} @@ -44,20 +50,28 @@ def _getCouchView(self, view, options, keys = []): options = self.setDefaultStaleOptions(options) - if keys and type(keys) == str: + if keys and isinstance(keys, basestring): keys = [keys] return self.couchDB.loadView(self.couchapp, view, options, keys) - - def _formatCouchData(self, data, key = "id"): - detail = False + + def _filterCouchInfo(self, couchInfo): + # remove the couch specific information + for key in ['_rev', '_attachments']: + if key in couchInfo: + del couchInfo[key] + return + + + def _formatCouchData(self, data, key = "id", detail = True, filterCouch = True): result = {} for row in data['rows']: if row.has_key('error'): continue if row.has_key("doc"): + if filterCouch: + self._filterCouchInfo(row["doc"]) result[row[key]] = row["doc"] - detail = True else: result[row[key]] = None if detail: @@ -65,7 +79,7 @@ def _formatCouchData(self, data, key = "id"): else: return result.keys() - def _getRequestByNames(self, requestNames, detail = True): + def _getRequestByNames(self, requestNames, detail): """ 'status': list of the status """ @@ -105,18 +119,25 @@ def getDBInstance(self): return self.couchDB - def getRequestByNames(self, requestNames): + def getRequestByNames(self, requestNames, detail = True): + if isinstance(requestNames, basestring): + requestNames = [requestNames] if len(requestNames) == 0: return {} - data = self._getRequestByNames(requestNames, True) + data = self._getRequestByNames(requestNames, detail = detail) - requestInfo = self._formatCouchData(data) + requestInfo = self._formatCouchData(data, detail = detail) return requestInfo def getRequestByStatus(self, statusList, detail = False, limit = None, skip = None): data = self._getRequestByStatus(statusList, detail, limit, skip) - requestInfo = self._formatCouchData(data) + requestInfo = self._formatCouchData(data, detail = detail) return requestInfo - \ No newline at end of file + + def getRequestByCouchView(self, view, options, keys = []): + options.setdefault("include_docs", True) + data = self._getCouchView(view, options, keys) + requestInfo = self._formatCouchData(data) + return requestInfo \ No newline at end of file diff --git a/src/python/WMCore/Services/RequestDB/RequestDBWriter.py b/src/python/WMCore/Services/RequestDB/RequestDBWriter.py index 602494fd82..1539d4548a 100644 --- a/src/python/WMCore/Services/RequestDB/RequestDBWriter.py +++ b/src/python/WMCore/Services/RequestDB/RequestDBWriter.py @@ -30,13 +30,14 @@ def updateRequestStatus(self, request, status): return self.couchDB.updateDocument(request, self.couchapp, "updaterequest", status) - def updateRequestProperty(self, request, propDict): + def updateRequestProperty(self, request, propDict, dn = None): encodeProperty = {} for key, value in propDict.items(): - if key in self._propertyNeedToBeEncoded: + if isinstance(value, list) or isinstance(value, dict): encodeProperty[key] = JSONEncoder().encode(value) else: encodeProperty[key] = value - + if dn: + encodeProperty["DN"] = dn return self.couchDB.updateDocument(request, self.couchapp, "updaterequest", encodeProperty) \ No newline at end of file diff --git a/src/python/WMCore/Services/Service.py b/src/python/WMCore/Services/Service.py index 8ad073ab7f..152ebcb5ff 100644 --- a/src/python/WMCore/Services/Service.py +++ b/src/python/WMCore/Services/Service.py @@ -90,7 +90,7 @@ def cache_expired(cache, delta = 0): from urlparse import urlparse -from WMCore.Services.Requests import Requests +from WMCore.Services.Requests import Requests, JSONRequests from WMCore.WMException import WMException from WMCore.Wrappers import JsonWrapper as json @@ -136,6 +136,8 @@ def __init__(self, cfg_dict = {}): # either passed as param to __init__, determine via scheme or default if type(self.get('requests')) == types.TypeType: requests = self['requests'] + elif (self.get('accept_type') == "application/json" and self.get('content_type') == "application/json"): + requests = JSONRequests else: requests = Requests # Instantiate a Request @@ -290,7 +292,10 @@ def getData(self, cachefile, url, inputdata = {}, incoming_headers = {}, cachefile.seek (0, 0) # return to beginning of file else: f = open(cachefile, 'w') - f.write(str(data)) + if isinstance(data, dict) or isinstance(data, list): + f.write(json.dumps(data)) + else: + f.write(str(data)) f.close() diff --git a/src/python/WMCore/Services/WMStats/WMStatsReader.py b/src/python/WMCore/Services/WMStats/WMStatsReader.py index 984a6fb340..4114f47383 100644 --- a/src/python/WMCore/Services/WMStats/WMStatsReader.py +++ b/src/python/WMCore/Services/WMStats/WMStatsReader.py @@ -50,12 +50,18 @@ def setDefaultStaleOptions(self, options): if not options.has_key('stale'): options.update(self.defaultStale) return options - - def _updateReuestInfoWithJobInfo(self, requestInfo): - if len(requestInfo.keys()) != 0: - requestAndAgentKey = self._getRequestAndAgent(requestInfo.keys()) + + def getLatestJobInfoByRequests(self, requestNames): + jobInfoByRequestAndAgent = {} + if len(requestNames) > 0: + requestAndAgentKey = self._getRequestAndAgent(requestNames) jobDocIds = self._getLatestJobInfo(requestAndAgentKey) jobInfoByRequestAndAgent = self._getAllDocsByIDs(jobDocIds) + return jobInfoByRequestAndAgent + + def _updateRequestInfoWithJobInfo(self, requestInfo): + if len(requestInfo.keys()) != 0: + jobInfoByRequestAndAgent = self.getLatestJobInfoByRequests(requestInfo.keys()) self._combineRequestAndJobData(requestInfo, jobInfoByRequestAndAgent) def _getCouchView(self, view, options, keys = []): @@ -125,14 +131,15 @@ def _combineRequestAndJobData(self, requestData, jobData): "agent_url":"vocms231.cern.ch:9999", "type":"agent_request"}} """ - for row in jobData["rows"]: - # condition checks if documents are deleted between calls. - # just ignore in that case - if row["doc"]: - jobInfo = requestData[row["doc"]["workflow"]] - jobInfo.setdefault("AgentJobInfo", {}) - jobInfo["AgentJobInfo"][row["doc"]["agent_url"]] = row["doc"] - + if jobData: + for row in jobData["rows"]: + # condition checks if documents are deleted between calls. + # just ignore in that case + if row["doc"]: + jobInfo = requestData[row["doc"]["workflow"]] + jobInfo.setdefault("AgentJobInfo", {}) + jobInfo["AgentJobInfo"][row["doc"]["agent_url"]] = row["doc"] + def _getRequestByNames(self, requestNames, detail = True): """ @@ -259,7 +266,7 @@ def getRequestByNames(self, requestNames, jobInfoFlag = False): requestInfo = self._formatCouchData(data) if jobInfoFlag: # get request and agent info - self._updateReuestInfoWithJobInfo(requestInfo) + self._updateRequestInfoWithJobInfo(requestInfo) return requestInfo def getActiveData(self, jobInfoFlag = False): @@ -273,6 +280,6 @@ def getRequestByStatus(self, statusList, jobInfoFlag = False, limit = None, skip if jobInfoFlag: # get request and agent info - self._updateReuestInfoWithJobInfo(requestInfo) + self._updateRequestInfoWithJobInfo(requestInfo) return requestInfo \ No newline at end of file diff --git a/src/python/WMCore/WMException.py b/src/python/WMCore/WMException.py index 7a81f42aec..e54735b021 100644 --- a/src/python/WMCore/WMException.py +++ b/src/python/WMCore/WMException.py @@ -143,3 +143,6 @@ def __str__(self): strg += self.traceback strg += '\n' return strg + + def message(self): + return self._message diff --git a/src/python/WMCore/WMSpec/StdSpecs/ReReco.py b/src/python/WMCore/WMSpec/StdSpecs/ReReco.py index 5eae286dcc..29a30a0f50 100644 --- a/src/python/WMCore/WMSpec/StdSpecs/ReReco.py +++ b/src/python/WMCore/WMSpec/StdSpecs/ReReco.py @@ -6,7 +6,7 @@ """ from WMCore.WMSpec.StdSpecs.DataProcessing import DataProcessing -from WMCore.WMSpec.WMWorkloadTools import makeList, validateArguments +from WMCore.WMSpec.WMWorkloadTools import makeList, validateArgumentsCreate class ReRecoWorkloadFactory(DataProcessing): """ @@ -256,7 +256,7 @@ def validateSchema(self, schema): for argument in skimArguments.keys(): realArg = argument.replace("#N", str(skimIndex)) instanceArguments[realArg] = skimArguments[argument] - msg = validateArguments(schema, instanceArguments) + msg = validateArgumentsCreate(schema, instanceArguments) if msg is not None: self.raiseValidationException(msg) diff --git a/src/python/WMCore/WMSpec/StdSpecs/StdBase.py b/src/python/WMCore/WMSpec/StdSpecs/StdBase.py index a43e8142cc..62d538ff7c 100644 --- a/src/python/WMCore/WMSpec/StdSpecs/StdBase.py +++ b/src/python/WMCore/WMSpec/StdSpecs/StdBase.py @@ -8,11 +8,12 @@ from WMCore.Cache.WMConfigCache import ConfigCache, ConfigCacheException from WMCore.Configuration import ConfigSection -from WMCore.Lexicon import lfnBase, identifier, acqname, cmsswversion, cmsname +from WMCore.Lexicon import lfnBase, identifier, acqname, cmsswversion, cmsname, couchurl from WMCore.Services.Dashboard.DashboardReporter import DashboardReporter from WMCore.WMException import WMException from WMCore.WMSpec.WMWorkload import newWorkload -from WMCore.WMSpec.WMWorkloadTools import makeList, makeLumiList, strToBool, validateArguments, checkDBSUrl +from WMCore.WMSpec.WMWorkloadTools import makeList, makeLumiList, strToBool, checkDBSUrl, validateArgumentsCreate + analysisTaskTypes = ['Analysis', 'PrivateMC'] @@ -53,6 +54,8 @@ def __init__(self): # Internal parameters self.workloadName = None self.multicoreNCores = None + self.schema = None + self.config_cache = {} return @@ -64,15 +67,20 @@ def __call__(self, workloadName, arguments): method and pull out any that are setup by this base class. """ self.workloadName = workloadName + self.schema = {} argumentDefinition = self.getWorkloadArguments() for arg in argumentDefinition: if arg in arguments: if arguments[arg] is None: setattr(self, argumentDefinition[arg]["attr"], arguments[arg]) else: - setattr(self, argumentDefinition[arg]["attr"], argumentDefinition[arg]["type"](arguments[arg])) + value = argumentDefinition[arg]["type"](arguments[arg]) + setattr(self, argumentDefinition[arg]["attr"], value) + self.schema[arg] = value elif argumentDefinition[arg]["optional"]: - setattr(self, argumentDefinition[arg]["attr"], argumentDefinition[arg]["default"]) + defaultValue = argumentDefinition[arg]["default"] + setattr(self, argumentDefinition[arg]["attr"], defaultValue) + self.schema[arg] = defaultValue # Definition of parameters that depend on the value of others if hasattr(self, "multicore") and self.multicore: @@ -96,8 +104,12 @@ def determineOutputModules(self, scenarioFunc = None, scenarioArgs = None, outputModules = {} if configDoc != None and configDoc != "": url = configCacheUrl or couchURL - configCache = ConfigCache(url, couchDBName) - configCache.loadByID(configDoc) + if (url, couchDBName) in self.config_cache: + configCache = self.config_cache[(url, couchDBName)] + else: + configCache = ConfigCache(url, couchDBName) + self.config_cache[(url, couchDBName)] = configCache + configCache.loadDocument(configDoc) outputModules = configCache.getOutputModuleInfo() else: if 'outputs' in scenarioArgs and scenarioFunc in [ "promptReco", "expressProcessing", "repack" ]: @@ -206,6 +218,8 @@ def createWorkload(self): workload.setValidStatus(validStatus = self.validStatus) workload.setLumiList(lumiLists = self.lumiList) workload.setPriority(self.priority) + workload.setCampaign(self.campaign) + workload.setRequestType(self.requestType) workload.setPrepID(self.prepID) return workload @@ -706,6 +720,41 @@ def validateWorkload(self, workload): """ pass + def factoryWorkloadConstruction4docs(self, docs): + """ + _factoryWorkloadConstruction_ + + Build workloads from given list of of request documents. + Provided list of docs should have similar parameters, such as + request type, couch url/db, etc. + """ + if len(set([d['RequestType'] for d in docs])) != 1: + raise Exception('Provided list of docs has different request type') + ids = set() + for doc in docs: + for key, val in doc.iteritems(): + if key.endswith('ConfigCacheID'): + ids.add(val) + ids = list(ids) + couchURL = docs[0]['CouchURL'] + couchDBName = docs[0]['CouchDBName'] + if (couchURL, couchDBName) in self.config_cache: + configCache = self.config_cache[(couchURL, couchDBName)] + else: + configCache = ConfigCache(dbURL=couchURL, couchDBName=couchDBName) + self.config_cache[(couchURL, couchDBName)] = configCache + configCache.docs_cache.prefetch(ids) + workloads = [] + for doc in docs: + workloadName = doc['RequestName'] + self.masterValidation(schema=doc) + self.validateSchema(schema=doc) + workload = self.__call__(workloadName=workloadName, arguments=doc) + self.validateWorkload(workload) + workloads.append(workload) + configCache.docs_cache.cleanup(ids) + return workloads + def factoryWorkloadConstruction(self, workloadName, arguments): """ _factoryWorkloadConstruction_ @@ -736,7 +785,7 @@ def masterValidation(self, schema): """ # Validate the arguments according to the workload arguments definition argumentDefinition = self.getWorkloadArguments() - msg = validateArguments(schema, argumentDefinition) + msg = validateArgumentsCreate(schema, argumentDefinition) if msg is not None: self.raiseValidationException(msg) return @@ -763,39 +812,21 @@ def validateConfigCacheExists(self, configID, couchURL, couchDBName, if configID == '' or configID == ' ': self.raiseValidationException(msg = "ConfigCacheID is invalid and cannot be loaded") - configCache = ConfigCache(dbURL = couchURL, couchDBName = couchDBName, - id = configID) - try: - configCache.loadByID(configID = configID) - except ConfigCacheException: - self.raiseValidationException(msg = "Failure to load ConfigCache while validating workload") - - duplicateCheck = {} + if (couchURL, couchDBName) in self.config_cache: + configCache = self.config_cache[(couchURL, couchDBName)] + else: + configCache = ConfigCache(dbURL = couchURL, couchDBName = couchDBName, detail = getOutputModules) + self.config_cache[(couchURL, couchDBName)] = configCache + try: - outputModuleInfo = configCache.getOutputModuleInfo() - except Exception: - # Something's gone wrong with trying to open the configCache - msg = "Error in getting output modules from ConfigCache during workload validation. Check ConfigCache formatting!" - self.raiseValidationException(msg = msg) - for outputModule in outputModuleInfo.values(): - dataTier = outputModule.get('dataTier', None) - filterName = outputModule.get('filterName', None) - if not dataTier: - self.raiseValidationException(msg = "No DataTier in output module.") - - # Add dataTier to duplicate dictionary - if not dataTier in duplicateCheck.keys(): - duplicateCheck[dataTier] = [] - if filterName in duplicateCheck[dataTier]: - # Then we've seen this combination before - self.raiseValidationException(msg = "Duplicate dataTier/filterName combination.") - else: - duplicateCheck[dataTier].append(filterName) - - if getOutputModules: - return outputModuleInfo + # if dtail option is set return outputModules + return configCache.validate(configID) + except ConfigCacheException, ex: + self.raiseValidationException(ex.message()) - return + + def getSchema(self): + return self.schema @staticmethod def getWorkloadArguments(): @@ -832,7 +863,9 @@ def getWorkloadArguments(): self.priority = arguments.get("RequestPriority", 0) """ - arguments = {"RequestPriority": {"default" : 0, "type" : int, + arguments = {"RequestType" : {"default" : "unknown", "optional" : False, + "attr" : "requestType"}, + "RequestPriority": {"default" : 0, "type" : int, "optional" : False, "validate" : lambda x : (x >= 0 and x < 1e6), "attr" : "priority"}, "Requestor": {"default" : "unknown", "optional" : False, @@ -843,9 +876,10 @@ def getWorkloadArguments(): "attr" : "group"}, "VoGroup" : {"default" : "DEFAULT", "attr" : "owner_vogroup"}, "VoRole" : {"default" : "DEFAULT", "attr" : "owner_vorole"}, + "Campaign" : {"default" : None, "optional" : True, "attr" : "campaign"}, "AcquisitionEra" : {"default" : "None", "attr" : "acquisitionEra", "validate" : acqname}, - "CMSSWVersion" : {"default" : "CMSSW_5_3_7", "validate" : cmsswversion, + "CMSSWVersion" : {"default" : "", "validate" : cmsswversion, "optional" : False, "attr" : "frameworkVersion"}, "ScramArch" : {"default" : "slc5_amd64_gcc462", "optional" : False}, "GlobalTag" : {"default" : None, "type" : str, @@ -907,6 +941,54 @@ def getWorkloadArguments(): "IncludeParents" : {"default" : False, "type" : strToBool}, "Multicore" : {"default" : None, "null" : True, "validate" : lambda x : x == "auto" or (int(x) > 0)}, + #from assignment: performance monitoring data + "MaxRSS" : {"default" : 2411724, "type" : int, "validate" : lambda x : x > 0}, + "MaxVSize" : {"default" : 20411724, "type" : int, "validate" : lambda x : x > 0}, + "SoftTimeout" : {"default" : 129600, "type" : int, "validate" : lambda x : x > 0}, + "GracePeriod" : {"default" : 300, "type" : int, "validate" : lambda x : x > 0}, + "UseSiteListAsLocation" : {"default" : False, "type" : bool}, + + # Set phedex subscription information + "CustodialSites" : {"default" : [], "type" : makeList, "assign_optional": False, + "validate" : lambda x: all([cmsname(y) for y in x])}, + "NonCustodialSites" : {"default" : [], "type" : makeList, "assign_optional": False, + "validate" : lambda x: all([cmsname(y) for y in x])}, + "AutoApproveSubscriptionSites" : {"default" : [], "type" : makeList, "assign_optional": False, + "validate" : lambda x: all([cmsname(y) for y in x])}, + # should be Low, Normal, High + "SubscriptionPriority" : {"default" : "Low", "type" : str, "assign_optional": False, + "validate" : lambda x: x in ["Low", "Normal", "High"]}, + # shouldbe Move Replica + "CustodialSubType" : {"default" : "Move", "type" : str, "assign_optional": False, + "validate" : lambda x: x in ["Move", "Replica"]}, + + # Block closing informaiont + "BlockCloseMaxWaitTime" : {"default" : 66400, "type" : int, "validate" : lambda x : x > 0}, + "BlockCloseMaxFiles" : {"default" : 500, "type" : int, "validate" : lambda x : x > 0}, + "BlockCloseMaxEvents" : {"default" : 25000000, "type" : int, "validate" : lambda x : x > 0}, + "BlockCloseMaxSize" : {"default" : 5000000000000, "type" : int, "validate" : lambda x : x > 0}, + + # dashboard activity + "Dashboard" : {"default" : "", "type" : str}, + # team name + "Team" : {"default" : "", "type" : str}, + + # this is specified automatically by reqmgr. +# "RequestName" : {"default" : "AnotherRequest", "type" : str, +# "optional" : False, "validate" : None, +# "attr" : "requestName", "null" : False}, + "CouchURL" : {"default" : "http://localhost:5984", "type" : str, + "optional" : False, "validate" : couchurl, + "attr" : "couchURL", "null" : False}, + "CouchDBName" : {"default" : "dp_configcache", "type" : str, + "optional" : True, "validate" : identifier, + "attr" : "couchDBName", "null" : False}, + "ConfigCacheUrl" : {"default" : None, "type" : str, + "optional" : True, "validate" : None, + "attr" : "configCacheUrl", "null" : True}, + "CouchWorkloadDBName" : {"default" : "reqmgr_workload_cache", "type" : str, + "optional" : False, "validate" : identifier, + "attr" : "couchWorkloadDBName", "null" : False}, "PrepID": {"default" : None, "null" : True}} # Set defaults for the argument specification diff --git a/src/python/WMCore/WMSpec/StdSpecs/TaskChain.py b/src/python/WMCore/WMSpec/StdSpecs/TaskChain.py index a170be7252..61bb8bf7c2 100644 --- a/src/python/WMCore/WMSpec/StdSpecs/TaskChain.py +++ b/src/python/WMCore/WMSpec/StdSpecs/TaskChain.py @@ -87,8 +87,8 @@ from WMCore.Lexicon import identifier, couchurl, block, primdataset, dataset from WMCore.WMSpec.StdSpecs.StdBase import StdBase -from WMCore.WMSpec.WMWorkloadTools import makeList, strToBool, validateArguments,\ - parsePileupConfig +from WMCore.WMSpec.WMWorkloadTools import makeList, strToBool,\ + validateArgumentsCreate, parsePileupConfig # # simple utils for data mining the request dictionary @@ -655,15 +655,19 @@ def validateTask(self, taskConf, taskArgumentDefinition): Validate the task information against the given argument description """ - msg = validateArguments(taskConf, taskArgumentDefinition) + msg = validateArgumentsCreate(taskConf, taskArgumentDefinition) if msg is not None: self.raiseValidationException(msg) # Also retrieve the "main" arguments which may be overriden in the task # Change them all to optional for validation + #TODO: can this just called + #validateArgumentsUpdate(taskConf, baseArgs) baseArgs = self.getWorkloadArguments() + validateArgumentsCreate(taskConf, baseArgs) + for arg in baseArgs: baseArgs[arg]["optional"] = True - msg = validateArguments(taskConf, baseArgs) + msg = validateArgumentsCreate(taskConf, baseArgs) if msg is not None: self.raiseValidationException(msg) return diff --git a/src/python/WMCore/WMSpec/WMWorkload.py b/src/python/WMCore/WMSpec/WMWorkload.py index 82ec916485..1e54589491 100644 --- a/src/python/WMCore/WMSpec/WMWorkload.py +++ b/src/python/WMCore/WMSpec/WMWorkload.py @@ -9,6 +9,8 @@ from WMCore.Configuration import ConfigSection from WMCore.WMSpec.ConfigSectionTree import findTop from WMCore.WMSpec.Persistency import PersistencyHelper +from WMCore.WMSpec.WMWorkloadTools import validateArgumentsUpdate, \ + loadSpecClassByType, setArgumentsNoneValueWithDefault from WMCore.WMSpec.WMTask import WMTask, WMTaskHelper from WMCore.Lexicon import lfnBase, sanitizeURL from WMCore.WMException import WMException @@ -87,6 +89,12 @@ def setName(self, workloadName): self.data._internal_name = workloadName return + def requestType(self): + return self.data.requestType + + def setRequestType(self, requestType): + self.data.requestType = requestType + def getInitialJobCount(self): """ _getInitialJobCount_ @@ -1694,6 +1702,113 @@ def locationDataSourceFlag(self): for task in self.getTopLevelTask(): return task.inputLocationFlag() return False + + def validateArgument(self, schema): + specClass = loadSpecClassByType(self.requestType()) + argumentDefinition = specClass.getWorkloadArguments() + msg = validateArgumentsUpdate(schema, argumentDefinition) + if msg is not None: + from WMCore.WMSpec.StdSpecs.StdBase import WMSpecFactoryException + raise WMSpecFactoryException(message = msg) + return + + def _checkKeys(self, kwargs, keys): + """ + check whether list of keys exist in the kwargs + if no keys exist return False + if all keys exist return True + if partial keys exsit raise Exception + """ + if isinstance(keys, basestring): + keys = [keys] + validKey = 0 + for key in keys: + if key in kwargs: + validKey += 1 + if validKey == 0: + return False + elif validKey == len(keys): + return True + else: + #TODO raise proper exception + raise Exception("not all the key is specified %s" % keys) + + def updateArguments(self, kwargs, wildcardSites = {}): + """ + set up all the argument related to assigning request. + args are validated before update. + assignment is common for all different types spec. + """ + + specClass = loadSpecClassByType(self.requestType()) + argumentDefinition = specClass.getWorkloadArguments() + setArgumentsNoneValueWithDefault(kwargs, argumentDefinition) + + if self._checkKeys(kwargs, ["SiteWhitelist", "SiteBlacklist"]): + self.setSiteWildcardsLists(siteWhitelist = kwargs["SiteWhitelist"], + siteBlacklist = kwargs["SiteBlacklist"], + wildcardDict = wildcardSites) + #FIXME not validated + if self._checkKeys(kwargs, ["MergedLFNBase", "UnmergedLFNBase"]): + self.setLFNBase(kwargs["MergedLFNBase"], kwargs["UnmergedLFNBase"]) + + if self._checkKeys(kwargs, ["MinMergeSize", "MaxMergeSize", "MaxMergeEvents"]): + self.setMergeParameters(int(kwargs["MinMergeSize"]), + int(kwargs["MaxMergeSize"]), + int(kwargs["MaxMergeEvents"])) + + # Set ProcessingVersion and AcquisitionEra, which could be json encoded dicts + # it should be processed once LFNBase are set + if self._checkKeys(kwargs, "ProcessingVersion"): + self.setProcessingVersion(kwargs["ProcessingVersion"]) + if self._checkKeys(kwargs, "AcquisitionEra"): + self.setAcquisitionEra(kwargs["AcquisitionEra"]) + if self._checkKeys(kwargs, "ProcessingString"): + self.setProcessingString(kwargs["ProcessingString"]) + + if self._checkKeys(kwargs, ["MaxRSS", "MaxVSize", "SoftTimeout", "GracePeriod"]): + self.setupPerformanceMonitoring(int(kwargs["MaxRSS"]), + int(kwargs["MaxVSize"]), + int(kwargs["SoftTimeout"]), + int(kwargs["GracePeriod"])) + + # Check whether we should check location for the data + if self._checkKeys(kwargs, "useSiteListAsLocation"): + self.setLocationDataSourceFlag() + + # Set phedex subscription information + + if self._checkKeys(kwargs, ["CustodialSites", "NonCustodialSites", + "AutoApproveSubscriptionSites", + "CustodialSubType", "SubscriptionPriority"]): + self.setSubscriptionInformationWildCards(wildcardDict = wildcardSites, + custodialSites = kwargs["CustodialSites"], + nonCustodialSites = kwargs["NonCustodialSites"], + autoApproveSites = kwargs["AutoApproveSubscriptionSites"], + custodialSubType = kwargs["CustodialSubType"], + priority = kwargs["SubscriptionPriority"]) + + # Block closing information + if self._checkKeys(kwargs, ["BlockCloseMaxWaitTime", "BlockCloseMaxFiles", + "BlockCloseMaxEvents", "BlockCloseMaxSize"]): + self.setBlockCloseSettings(kwargs["BlockCloseMaxWaitTime"], + kwargs["BlockCloseMaxFiles"], + kwargs["BlockCloseMaxEvents"], + kwargs["BlockCloseMaxSize"]) + + if self._checkKeys(kwargs, "DashboardActivity"): + self.setDashboardActivity(kwargs["DashboardActivity"]) + + + return kwargs + + + def loadSpecFromCouch(self, couchurl, requestName): + """ + This depends on PersitencyHelper.py saveCouch (That method better be decomposed) + """ + return self.load("%s/%s/spec" % (couchurl, requestName)) + def setTaskPropertiesFromWorkload(self): """ @@ -1769,6 +1884,10 @@ def __init__(self, name="test"): self.section_("tasks") self.tasks.tasklist = [] + # worklaod spec type + self.section_("request_type") + self.requestType = "" + self.sandbox = None self.initialJobCount = 0 diff --git a/src/python/WMCore/WMSpec/WMWorkloadTools.py b/src/python/WMCore/WMSpec/WMWorkloadTools.py index 381aca47c0..4ef5bf1a0f 100644 --- a/src/python/WMCore/WMSpec/WMWorkloadTools.py +++ b/src/python/WMCore/WMSpec/WMWorkloadTools.py @@ -21,6 +21,9 @@ class WMWorkloadToolsException(WMException): """ pass +class InvlaidSpecArgumentError(WMException): + pass + def makeLumiList(lumiDict): try: ll = LumiList(compactList = lumiDict) @@ -66,10 +69,16 @@ def strToBool(string): def checkDBSUrl(dbsUrl): # use the import statement here since this is packed and used in RunTime code. # dbs client is not shipped with it. - from WMCore.Services.DBS.DBS3Reader import DBS3Reader + if dbsUrl: try: - DBS3Reader(dbsUrl).dbs.serverinfo() + #from WMCore.Services.DBS.DBS3Reader import DBS3Reader + #DBS3Reader(dbsUrl).dbs.serverinfo() + from WMCore.Services.Requests import JSONRequests + jsonSender = JSONRequests(dbsUrl) + result = jsonSender.get("/serverinfo") + if not result[1] == 200: + raise WMWorkloadToolsException("DBS is not connected: %s : %s" % (dbsUrl, str(result))) except: raise WMWorkloadToolsException("DBS is not responding: %s" % dbsUrl) @@ -90,14 +99,37 @@ def parsePileupConfig(mcPileup, dataPileup): pileUpConfig['data'] = [dataPileup] return pileUpConfig -def validateArguments(arguments, argumentDefinition): +def _validateArgument(argument, value, argumentDefinition): + validNull = argumentDefinition[argument]["null"] + if not validNull and value is None: + raise InvlaidSpecArgumentError("Argument %s can't be None" % argument) + elif validNull and value is None: + return value + + try: + value = argumentDefinition[argument]["type"](value) + except Exception: + raise InvlaidSpecArgumentError("Argument %s type is incorrect in schema." % argument) + + validateFunction = argumentDefinition[argument]["validate"] + if validateFunction is not None: + try: + if not validateFunction(value): + raise InvlaidSpecArgumentError("Argument %s doesn't pass the validation function." % argument) + except Exception, ex: + # Some validation functions (e.g. Lexicon) will raise errors instead of returning False + raise InvlaidSpecArgumentError("Validation failed: %s, %s, %s" % (argument, value, str(ex))) + return value + +def validateArgumentsCreate(arguments, argumentDefinition): """ _validateArguments_ Validate a set of arguments against and argument definition as defined in StdBase.getWorkloadArguments. It returns an error message if the validation went wrong, - otherwise returns None + otherwise returns None, this is used for spec creation + checks the whether argument is optional as well as validation """ for argument in argumentDefinition: optional = argumentDefinition[argument]["optional"] @@ -105,22 +137,51 @@ def validateArguments(arguments, argumentDefinition): return "Argument %s is required." % argument elif optional and argument not in arguments: continue - validNull = argumentDefinition[argument]["null"] - if not validNull and arguments[argument] is None: - return "Argument %s can't be None" % argument - elif validNull and arguments[argument] is None: + arguments[argument] = _validateArgument(argument, arguments[argument], argumentDefinition) + + return + +def validateArgumentsUpdate(arguments, argumentDefinition): + """ + _validateArgumentsUpdate_ + + Validate a set of arguments against and argument definition + as defined in StdBase.getWorkloadArguments. It returns + an error message if the validation went wrong, + otherwise returns None + """ + for argument in argumentDefinition: + optional = argumentDefinition[argument].get("assign_optional", True) + if not optional and argument not in arguments: + if argumentDefinition[argument].has_key("default"): + arguments[argument] = argumentDefinition[argument]["default"] + else: + return "Argument %s is required." % argument + elif optional and argument not in arguments: continue - try: - argType = argumentDefinition[argument]["type"] - convertedArg = argType(arguments[argument]) - except Exception: - return "Argument %s type is incorrect in schema." % argument - validateFunction = argumentDefinition[argument]["validate"] - if validateFunction is not None: - try: - if not validateFunction(convertedArg): - raise Exception - except: - # Some validation functions (e.g. Lexicon) will raise errors instead of returning False - return "Argument %s doesn't pass validation. %s" % (argument, convertedArg) + + for argument in arguments: + arguments[argument] = _validateArgument(argument, arguments[argument], argumentDefinition) return + +def setArgumentsNoneValueWithDefault(arguments, argumentDefinition): + """ + sets the default value if arguments value is specified as None + """ + for argument in arguments: + if arguments[argument] == None: + argumentDefinition[argument]["default"] + return + +def loadSpecClassByType(specType): + factoryName = "%sWorkloadFactory" % specType + mod = __import__("WMCore.WMSpec.StdSpecs.%s" % specType, + globals(), locals(), [factoryName]) + specClass = getattr(mod, factoryName) + + return specClass + +def loadSpecByType(specType): + specClass = loadSpecClassByType(specType) + return specClass() + diff --git a/src/python/WMQuality/REST/RESTBaseUnitTestWithDBBackend.py b/src/python/WMQuality/REST/RESTBaseUnitTestWithDBBackend.py index 784de30e83..df1ebb8791 100644 --- a/src/python/WMQuality/REST/RESTBaseUnitTestWithDBBackend.py +++ b/src/python/WMQuality/REST/RESTBaseUnitTestWithDBBackend.py @@ -62,13 +62,14 @@ def setUp(self): self.server = RESTMainTestServer(self.config, os.getcwd(), self._testMethodName) CherrypyTestInit.start(self.server) self.jsonSender = self.server.jsonSender - #TODO: find the way to check the api with the permission - #self.adminHeader = self.server.header - return + # find the way to check the api with the permission + self.test_authz_key = self.server.test_authz_key + print "init root" def tearDown(self): if self.initRoot: CherrypyTestInit.stop(self.server) + self.test_authz_key = None if self.schemaModules: self.testInit.clearDatabase() diff --git a/src/python/WMQuality/REST/ServerSetup.py b/src/python/WMQuality/REST/ServerSetup.py index 32e0aa2e85..b3eaa06627 100644 --- a/src/python/WMQuality/REST/ServerSetup.py +++ b/src/python/WMQuality/REST/ServerSetup.py @@ -12,13 +12,16 @@ class RESTMainTestServer(object): def __init__(self, cfg, statedir, testName): self.server = RESTMain(cfg, statedir) self.testName = testName - self.cofig = cfg + self.config = cfg self.port = cfg.main.port self.host = '127.0.0.1' self.serverUrl = "http://%s:%s/%s/" % (self.host, self.port, cfg.main.application) - ## test permission - #test_authz_key = fake_authz_key_file() - #self.header = fake_authz_headers(test_authz_key.data, roles = {"Global Admin": {'group': ['global']}}) + + ## test authentication using fake filepermission + self.test_authz_key = fake_authz_key_file(False) + self.config.main.tools.cms_auth.key_file = self.test_authz_key.name + #self.header = fake_authz_headers(test_authz_key.data, roles = {"Admin": {'group': ['ReqMgr']}}) + self.jsonSender = JSONRequests(self.serverUrl) def getLastTest(self): @@ -58,3 +61,6 @@ def stop(self): for name, server in getattr(cherrypy, 'servers', {}).items(): server.unsubscribe() del cherrypy.servers[name] + + self.test_authz_key.close() + os.remove(self.test_authz_key.name) \ No newline at end of file diff --git a/src/python/WMQuality/TestInit.py b/src/python/WMQuality/TestInit.py index fa787cf0aa..f30fff2615 100644 --- a/src/python/WMQuality/TestInit.py +++ b/src/python/WMQuality/TestInit.py @@ -29,7 +29,8 @@ try: from WMCore.Database.DBFormatter import DBFormatter from WMCore.WMInit import WMInit -except ImportError: +except ImportError, ex: + print str(ex) print "NOTE: TestInit is being loaded without database support" hasDatabase = False diff --git a/src/templates/WMCore/WebTools/RequestManager/Assign.tmpl b/src/templates/WMCore/WebTools/RequestManager/Assign.tmpl index 0ffe4ab99e..7fb27c6ac8 100644 --- a/src/templates/WMCore/WebTools/RequestManager/Assign.tmpl +++ b/src/templates/WMCore/WebTools/RequestManager/Assign.tmpl @@ -132,8 +132,8 @@ $optionsMenu($unmergedLFNBases, $reqUnmergedBase) Min Merge Size Max Merge Size Max Merge Events
-Memory limits: RSS (KiBytes) -VSS (KiBytes)
+Memory limits: RSS (KiBytes) +VSS (KiBytes)
Wallclock limits: Timeout (Seconds) GracePeriod (Seconds)
Block Closing Settings: Block timeout (Seconds) @@ -149,7 +149,7 @@ Acquisition Era:
Processing String:
Dashboard Activity: - #set $dashboards = ["reprocessing", "production", "relval", "integration", "test", "analysis"] $optionsMenu($dashboards, $dashboardActivity) diff --git a/test/data/ReqMgr/requests/DMWM/MonteCarlo.json b/test/data/ReqMgr/requests/DMWM/MonteCarlo.json index 70a218abdf..12f0bf32bc 100644 --- a/test/data/ReqMgr/requests/DMWM/MonteCarlo.json +++ b/test/data/ReqMgr/requests/DMWM/MonteCarlo.json @@ -37,11 +37,11 @@ }, "assignRequest": { - "SiteWhitelist": "SiteWhitelist-OVERRIDE-ME", + "SiteWhitelist": [], "Team": "Team-OVERRIDE-ME", "AcquisitionEra": "AcquisitionEra-OVERRIDE-ME", "ProcessingString": "ProcessingString-OVERRIDE-ME", - "dashboard": "dashboard-OVERRIDE-ME", + "Dashboard": "dashboard-OVERRIDE-ME", "SiteBlacklist": [], "MergedLFNBase": "/store/backfill/1", "UnmergedLFNBase": "/store/unmerged", @@ -49,8 +49,8 @@ "MaxMergeSize": 4294967296, "MaxMergeEvents": 50000, "ProcessingVersion": 1, - "maxRSS": 2352000, - "maxVSize": 20294967, + "MaxRSS": 2352000, + "MaxVSize": 20294967, "SoftTimeout": 129600, "GracePeriod": 300, "CustodialSites": [], diff --git a/test/data/ReqMgr/requests/DMWM/MonteCarloFromGEN.json b/test/data/ReqMgr/requests/DMWM/MonteCarloFromGEN.json index 11f3e8457d..1a5f578284 100644 --- a/test/data/ReqMgr/requests/DMWM/MonteCarloFromGEN.json +++ b/test/data/ReqMgr/requests/DMWM/MonteCarloFromGEN.json @@ -34,11 +34,11 @@ }, "assignRequest": { - "SiteWhitelist": "SiteWhitelist-OVERRIDE-ME", + "SiteWhitelist": [], "Team": "Team-OVERRIDE-ME", "AcquisitionEra": "AcquisitionEra-OVERRIDE-ME", "ProcessingString": "ProcessingString-OVERRIDE-ME", - "dashboard": "dashboard-OVERRIDE-ME", + "Dashboard": "dashboard-OVERRIDE-ME", "SiteBlacklist": [], "MergedLFNBase": "/store/backfill/1", "UnmergedLFNBase": "/store/unmerged", @@ -46,8 +46,8 @@ "MaxMergeSize": 4294967296, "MaxMergeEvents": 50000, "ProcessingVersion": 1, - "maxRSS": 2352000, - "maxVSize": 20294967, + "MaxRSS": 2352000, + "MaxVSize": 20294967, "SoftTimeout": 129600, "GracePeriod": 300, "CustodialSites": [], diff --git a/test/data/ReqMgr/requests/DMWM/MonteCarlo_Ext.json b/test/data/ReqMgr/requests/DMWM/MonteCarlo_Ext.json index d697d6c91b..c9bf5451e2 100644 --- a/test/data/ReqMgr/requests/DMWM/MonteCarlo_Ext.json +++ b/test/data/ReqMgr/requests/DMWM/MonteCarlo_Ext.json @@ -37,11 +37,11 @@ }, "assignRequest": { - "SiteWhitelist": "SiteWhitelist-OVERRIDE-ME", + "SiteWhitelist": [], "Team": "Team-OVERRIDE-ME", "AcquisitionEra": "AcquisitionEra-OVERRIDE-ME", "ProcessingString": "ProcessingString-OVERRIDE-ME", - "dashboard": "dashboard-OVERRIDE-ME", + "Dashboard": "dashboard-OVERRIDE-ME", "SiteBlacklist": [], "MergedLFNBase": "/store/backfill/1", "UnmergedLFNBase": "/store/unmerged", @@ -49,8 +49,8 @@ "MaxMergeSize": 4294967296, "MaxMergeEvents": 50000, "ProcessingVersion": 1, - "maxRSS": 2352000, - "maxVSize": 20294967, + "MaxRSS": 2352000, + "MaxVSize": 20294967, "SoftTimeout": 129600, "GracePeriod": 300, "CustodialSites": [], diff --git a/test/data/ReqMgr/requests/DMWM/MonteCarlo_LHE.json b/test/data/ReqMgr/requests/DMWM/MonteCarlo_LHE.json index 749b695495..99766ae2c8 100644 --- a/test/data/ReqMgr/requests/DMWM/MonteCarlo_LHE.json +++ b/test/data/ReqMgr/requests/DMWM/MonteCarlo_LHE.json @@ -27,11 +27,11 @@ }, "assignRequest": { - "SiteWhitelist": "SiteWhitelist-OVERRIDE-ME", + "SiteWhitelist": [], "Team": "Team-OVERRIDE-ME", "AcquisitionEra": "AcquisitionEra-OVERRIDE-ME", "ProcessingString": "ProcessingString-OVERRIDE-ME", - "dashboard": "dashboard-OVERRIDE-ME", + "Dashboard": "dashboard-OVERRIDE-ME", "SiteBlacklist": [], "MergedLFNBase": "/store/generator", "UnmergedLFNBase": "/store/unmerged", @@ -39,8 +39,8 @@ "MaxMergeSize": 4294967296, "MaxMergeEvents": 5000000, "ProcessingVersion": 1, - "maxRSS": 2352000, - "maxVSize": 20294967, + "MaxRSS": 2352000, + "MaxVSize": 20294967, "SoftTimeout": 129600, "GracePeriod": 300, "CustodialSites": [], diff --git a/test/data/ReqMgr/requests/DMWM/MonteCarlo_eff.json b/test/data/ReqMgr/requests/DMWM/MonteCarlo_eff.json index 363cbd121e..23e1d0c8b3 100644 --- a/test/data/ReqMgr/requests/DMWM/MonteCarlo_eff.json +++ b/test/data/ReqMgr/requests/DMWM/MonteCarlo_eff.json @@ -37,11 +37,11 @@ }, "assignRequest": { - "SiteWhitelist": "SiteWhitelist-OVERRIDE-ME", + "SiteWhitelist": [], "Team": "Team-OVERRIDE-ME", "AcquisitionEra": "AcquisitionEra-OVERRIDE-ME", "ProcessingString": "ProcessingString-OVERRIDE-ME", - "dashboard": "dashboard-OVERRIDE-ME", + "Dashboard": "dashboard-OVERRIDE-ME", "SiteBlacklist": [], "MergedLFNBase": "/store/backfill/1", "UnmergedLFNBase": "/store/unmerged", @@ -49,8 +49,8 @@ "MaxMergeSize": 4294967296, "MaxMergeEvents": 50000, "ProcessingVersion": 1, - "maxRSS": 2352000, - "maxVSize": 20294967, + "MaxRSS": 2352000, + "MaxVSize": 20294967, "SoftTimeout": 129600, "GracePeriod": 300, "CustodialSites": [], diff --git a/test/data/ReqMgr/requests/DMWM/ReDigi.json b/test/data/ReqMgr/requests/DMWM/ReDigi.json index 601bbc7fc7..2ced7b6f6b 100644 --- a/test/data/ReqMgr/requests/DMWM/ReDigi.json +++ b/test/data/ReqMgr/requests/DMWM/ReDigi.json @@ -41,11 +41,11 @@ }, "assignRequest": { - "SiteWhitelist": "SiteWhitelist-OVERRIDE-ME", + "SiteWhitelist": [], "Team": "Team--OVERRIDE-ME", "AcquisitionEra": "AcquisitionEra-OVERRIDE-ME", "ProcessingString" : "ProcessingString-OVERRIDE-ME", - "dashboard": "dashboard-OVERRIDE-ME", + "Dashboard": "dashboard-OVERRIDE-ME", "SiteBlacklist": [], "MergedLFNBase": "/store/backfill/1", "UnmergedLFNBase": "/store/unmerged", @@ -53,8 +53,8 @@ "MaxMergeSize": 4294967296, "MaxMergeEvents": 50000, "ProcessingVersion": 1, - "maxRSS": 2352000, - "maxVSize": 20294967, + "MaxRSS": 2352000, + "MaxVSize": 20294967, "SoftTimeout": 129600, "GracePeriod": 300, "CustodialSites": [], diff --git a/test/data/ReqMgr/requests/DMWM/ReDigi_cmsRun2.json b/test/data/ReqMgr/requests/DMWM/ReDigi_cmsRun2.json index 57ed21880c..ccc7da3e28 100644 --- a/test/data/ReqMgr/requests/DMWM/ReDigi_cmsRun2.json +++ b/test/data/ReqMgr/requests/DMWM/ReDigi_cmsRun2.json @@ -43,11 +43,11 @@ }, "assignRequest": { - "SiteWhitelist": "SiteWhitelist-OVERRIDE-ME", + "SiteWhitelist": [], "Team": "Team-OVERRIDE-ME", "AcquisitionEra": "AcquisitionEra-OVERRIDE-ME", "ProcessingString": "ProcessingString-OVERRIDE-ME", - "dashboard": "dashboard-OVERRIDE-ME", + "Dashboard": "dashboard-OVERRIDE-ME", "SiteBlacklist": [], "MergedLFNBase": "/store/backfill/1", "UnmergedLFNBase": "/store/unmerged", @@ -55,8 +55,8 @@ "MaxMergeSize": 4294967296, "MaxMergeEvents": 50000, "ProcessingVersion": 1, - "maxRSS": 2352000, - "maxVSize": 20294967, + "MaxRSS": 2352000, + "MaxVSize": 20294967, "SoftTimeout": 129600, "GracePeriod": 300, "CustodialSites": [], diff --git a/test/data/ReqMgr/requests/DMWM/ReReco.json b/test/data/ReqMgr/requests/DMWM/ReReco.json index 189352107b..7249290406 100644 --- a/test/data/ReqMgr/requests/DMWM/ReReco.json +++ b/test/data/ReqMgr/requests/DMWM/ReReco.json @@ -40,11 +40,11 @@ }, "assignRequest": { - "SiteWhitelist": "SiteWhitelist-OVERRIDE-ME", + "SiteWhitelist": [], "Team": "Team-OVERRIDE-ME", "AcquisitionEra": "AcquisitionEra-OVERRIDE-ME", "ProcessingString": "ProcessingString-OVERRIDE-ME", - "dashboard": "dashboard-OVERRIDE-ME", + "Dashboard": "dashboard-OVERRIDE-ME", "SiteBlacklist": [], "MergedLFNBase": "/store/backfill/1", "UnmergedLFNBase": "/store/unmerged", @@ -52,8 +52,8 @@ "MaxMergeSize": 4294967296, "MaxMergeEvents": 50000, "ProcessingVersion": 1, - "maxRSS": 2352000, - "maxVSize": 20294967, + "MaxRSS": 2352000, + "MaxVSize": 20294967, "SoftTimeout": 129600, "GracePeriod": 300, "CustodialSites": [], diff --git a/test/data/ReqMgr/requests/DMWM/TaskChainRunJet2012C_multiRun.json b/test/data/ReqMgr/requests/DMWM/TaskChainRunJet2012C_multiRun.json index 674e92cc4c..ab6053303e 100644 --- a/test/data/ReqMgr/requests/DMWM/TaskChainRunJet2012C_multiRun.json +++ b/test/data/ReqMgr/requests/DMWM/TaskChainRunJet2012C_multiRun.json @@ -47,13 +47,13 @@ "assignRequest": { - "SiteWhitelist": "SiteWhitelist-OVERRIDE-ME", + "SiteWhitelist": [], "Team": "Team-OVERRIDE-ME", "AcquisitionEra": {"HLTD" : "AcquisitionEra-OVERRIDE-ME", "RECODreHLT" : "AcquisitionEra-1-OVERRIDE-ME"}, "ProcessingString" : {"HLTD" : "ProcessingString-OVERRIDE-ME", "RECODreHLT" : "ProcessingString-1-OVERRIDE-ME"}, - "dashboard": "dashboard-OVERRIDE-ME", + "Dashboard": "dashboard-OVERRIDE-ME", "ProcessingVersion": 1, "SiteBlacklist": [], "MergedLFNBase": "/store/mc", @@ -61,8 +61,8 @@ "MinMergeSize": 2147483648, "MaxMergeEvents": 100000, "MaxMergeSize": 4294967296, - "maxRSS": 8000000, - "maxVSize": 25294967, + "MaxRSS": 8000000, + "MaxVSize": 25294967, "SoftTimeout": 129600, "GracePeriod": 1000, "CustodialSites": [], diff --git a/test/data/ReqMgr/requests/DMWM/TaskChain_Data.json b/test/data/ReqMgr/requests/DMWM/TaskChain_Data.json index ea7eae9693..c06962c79b 100644 --- a/test/data/ReqMgr/requests/DMWM/TaskChain_Data.json +++ b/test/data/ReqMgr/requests/DMWM/TaskChain_Data.json @@ -44,13 +44,13 @@ "assignRequest": { - "SiteWhitelist": "SiteWhitelist-OVERRIDE-ME", + "SiteWhitelist": [], "Team": "Team-OVERRIDE-ME", "AcquisitionEra": {"RECOCOSD" : "AcquisitionEra-OVERRIDE-ME", "ALCACOSD" : "AcquisitionEra-1-OVERRIDE-ME"}, "ProcessingString" : {"RECOCOSD" : "ProcessingString-OVERRIDE-ME", "ALCACOSD" : "ProcessingString-1-OVERRIDE-ME"}, - "dashboard": "dashboard-OVERRIDE-ME", + "Dashboard": "dashboard-OVERRIDE-ME", "SiteBlacklist": [], "MergedLFNBase": "/store/relval", "UnmergedLFNBase": "/store/unmerged", @@ -58,8 +58,8 @@ "MaxMergeSize": 4294967296, "MaxMergeEvents": 400000, "ProcessingVersion": 1, - "maxRSS": 3072000, - "maxVSize": 20294967, + "MaxRSS": 3072000, + "MaxVSize": 20294967, "SoftTimeout": 129600, "GracePeriod": 1000, "CustodialSites": [], diff --git a/test/data/ReqMgr/requests/DMWM/TaskChain_MC.json b/test/data/ReqMgr/requests/DMWM/TaskChain_MC.json index 9ff46376f1..8a69f41dc2 100644 --- a/test/data/ReqMgr/requests/DMWM/TaskChain_MC.json +++ b/test/data/ReqMgr/requests/DMWM/TaskChain_MC.json @@ -53,7 +53,7 @@ }, "assignRequest": { - "SiteWhitelist": "SiteWhitelist-OVERRIDE-ME", + "SiteWhitelist": [], "Team": "Team-OVERRIDE-ME", "AcquisitionEra": {"ProdMinBias" : "AcquisitionEra-OVERRIDE-ME", "DIGIPROD1" : "AcquisitionEra-1-OVERRIDE-ME", @@ -61,7 +61,7 @@ "ProcessingString" : {"ProdMinBias" : "ProcessingString-OVERRIDE-ME", "DIGIPROD1" : "ProcessingString-1-OVERRIDE-ME", "RECODreHLT" : "ProcessingString-2-OVERRIDE-ME"}, - "dashboard": "dashboard-OVERRIDE-ME", + "Dashboard": "dashboard-OVERRIDE-ME", "SiteBlacklist": [], "MergedLFNBase": "/store/relval", "UnmergedLFNBase": "/store/unmerged", @@ -69,8 +69,8 @@ "MaxMergeSize": 4294967296, "MaxMergeEvents": 100000, "ProcessingVersion": 1, - "maxRSS": 3072000, - "maxVSize": 20294967, + "MaxRSS": 3072000, + "MaxVSize": 20294967, "SoftTimeout": 129600, "GracePeriod": 1000, "CustodialSites": [], diff --git a/test/data/ReqMgr/requests/DMWM/TaskChain_MC_PU.json b/test/data/ReqMgr/requests/DMWM/TaskChain_MC_PU.json index 695c160151..1d1f9d49fe 100644 --- a/test/data/ReqMgr/requests/DMWM/TaskChain_MC_PU.json +++ b/test/data/ReqMgr/requests/DMWM/TaskChain_MC_PU.json @@ -60,7 +60,7 @@ "assignRequest": { - "SiteWhitelist": "SiteWhitelist-OVERRIDE-ME", + "SiteWhitelist": [], "Team": "Team-OVERRIDE-ME", "AcquisitionEra": {"ZTT" : "AcquisitionEra-OVERRIDE-ME", "DIGIPU1" : "AcquisitionEra-1-OVERRIDE-ME", @@ -68,7 +68,7 @@ "ProcessingString" : {"ZTT" : "ProcessingString-OVERRIDE-ME", "DIGIPU1" : "ProcessingString-1-OVERRIDE-ME", "RECOPU1" : "ProcessingString-2-OVERRIDE-ME"}, - "dashboard": "dashboard-OVERRIDE-ME", + "Dashboard": "dashboard-OVERRIDE-ME", "SiteBlacklist": [], "MergedLFNBase": "/store/relval", "UnmergedLFNBase": "/store/unmerged", @@ -76,8 +76,8 @@ "MaxMergeSize": 4294967296, "MaxMergeEvents": 100000, "ProcessingVersion": 1, - "maxRSS": 3072000, - "maxVSize": 20294967, + "MaxRSS": 3072000, + "MaxVSize": 20294967, "SoftTimeout": 129600, "GracePeriod": 1000, "CustodialSites": [], diff --git a/test/data/ReqMgr/requests/LHEStep0.json b/test/data/ReqMgr/requests/LHEStep0.json index 817dc1184f..25c5b1890e 100644 --- a/test/data/ReqMgr/requests/LHEStep0.json +++ b/test/data/ReqMgr/requests/LHEStep0.json @@ -27,7 +27,7 @@ }, "assignRequest": { - "SiteWhitelist": "SiteWhitelist-OVERRIDE-ME", + "SiteWhitelist": [], "SiteBlacklist": [], "MergedLFNBase": "/store/generator", "UnmergedLFNBase": "/store/unmerged", @@ -37,11 +37,11 @@ "AcquisitionEra": "AcquisitionEra-OVERRIDE-ME", "ProcessingVersion": 1, "ProcessingString" : "ProcessingString-OVERRIDE-ME", - "maxRSS": 2294967, - "maxVSize": 4394967000, + "MaxRSS": 2294967, + "MaxVSize": 4394967000, "SoftTimeout": 216000, "GracePeriod": 300, - "dashboard": "dashboard-OVERRIDE-ME", + "Dashboard": "dashboard-OVERRIDE-ME", "Team": "Team--OVERRIDE-ME" } } diff --git a/test/data/ReqMgr/requests/MonteCarlo.json b/test/data/ReqMgr/requests/MonteCarlo.json index 4d60a27dfa..f6a0bd767c 100644 --- a/test/data/ReqMgr/requests/MonteCarlo.json +++ b/test/data/ReqMgr/requests/MonteCarlo.json @@ -34,7 +34,7 @@ }, "assignRequest": { - "SiteWhitelist": "SiteWhitelist-OVERRIDE-ME", + "SiteWhitelist": [], "SiteBlacklist": [], "MergedLFNBase": "/store/backfill/1", "UnmergedLFNBase": "/store/unmerged", @@ -44,11 +44,11 @@ "AcquisitionEra": "AcquisitionEra-OVERRIDE-ME", "ProcessingVersion": 1, "ProcessingString" : "ProcessingString-OVERRIDE-ME", - "maxRSS": 4294967296, - "maxVSize": 4294967296, + "MaxRSS": 4294967296, + "MaxVSize": 4294967296, "SoftTimeout": 129600, "GracePeriod": 300, - "dashboard": "dashboard-OVERRIDE-ME", + "Dashboard": "dashboard-OVERRIDE-ME", "Team": "Team--OVERRIDE-ME", "CustodialSites": [], "NonCustodialSites": [], diff --git a/test/data/ReqMgr/requests/MonteCarloFromGEN.json b/test/data/ReqMgr/requests/MonteCarloFromGEN.json index 4d152d1976..2926a8a613 100644 --- a/test/data/ReqMgr/requests/MonteCarloFromGEN.json +++ b/test/data/ReqMgr/requests/MonteCarloFromGEN.json @@ -30,7 +30,7 @@ }, "assignRequest": { - "SiteWhitelist": "SiteWhitelist-OVERRIDE-ME", + "SiteWhitelist": [], "SiteBlacklist": [], "MergedLFNBase": "/store/mc", "UnmergedLFNBase": "/store/unmerged", @@ -40,11 +40,11 @@ "AcquisitionEra": "AcquisitionEra-OVERRIDE-ME", "ProcessingVersion": 1, "ProcessingString" : "ProcessingString-OVERRIDE-ME", - "maxRSS": 4294967296, - "maxVSize": 4294967296, + "MaxRSS": 4294967296, + "MaxVSize": 4294967296, "SoftTimeout": 129600, "GracePeriod": 300, - "dashboard": "dashboard-OVERRIDE-ME", + "Dashboard": "dashboard-OVERRIDE-ME", "Team": "Team--OVERRIDE-ME", "CustodialSites": [], "NonCustodialSites": [], diff --git a/test/data/ReqMgr/requests/MonteCarloLHE.json b/test/data/ReqMgr/requests/MonteCarloLHE.json index 9ba0fed2ef..2a630da362 100644 --- a/test/data/ReqMgr/requests/MonteCarloLHE.json +++ b/test/data/ReqMgr/requests/MonteCarloLHE.json @@ -35,7 +35,7 @@ }, "assignRequest": { - "SiteWhitelist": "SiteWhitelist-OVERRIDE-ME", + "SiteWhitelist": [], "SiteBlacklist": [], "MergedLFNBase": "/store/backfill/1", "UnmergedLFNBase": "/store/unmerged", @@ -45,11 +45,11 @@ "AcquisitionEra": "AcquisitionEra-OVERRIDE-ME", "ProcessingVersion": 1, "ProcessingString" : "ProcessingString-OVERRIDE-ME", - "maxRSS": 4294967296, - "maxVSize": 4294967296, + "MaxRSS": 4294967296, + "MaxVSize": 4294967296, "SoftTimeout": 129600, "GracePeriod": 300, - "dashboard": "dashboard-OVERRIDE-ME", + "Dashboard": "dashboard-OVERRIDE-ME", "Team": "Team--OVERRIDE-ME", "CustodialSites": [], "NonCustodialSites": [], diff --git a/test/data/ReqMgr/requests/MonteCarloLHEB.json b/test/data/ReqMgr/requests/MonteCarloLHEB.json index 3b15b4dfdf..47c307e31a 100644 --- a/test/data/ReqMgr/requests/MonteCarloLHEB.json +++ b/test/data/ReqMgr/requests/MonteCarloLHEB.json @@ -35,7 +35,7 @@ }, "assignRequest": { - "SiteWhitelist": "SiteWhitelist-OVERRIDE-ME", + "SiteWhitelist": [], "SiteBlacklist": [], "MergedLFNBase": "/store/backfill/1", "UnmergedLFNBase": "/store/unmerged", @@ -45,11 +45,11 @@ "AcquisitionEra": "AcquisitionEra-OVERRIDE-ME", "ProcessingVersion": 1, "ProcessingString" : "ProcessingString-OVERRIDE-ME", - "maxRSS": 4294967296, - "maxVSize": 4294967296, + "MaxRSS": 4294967296, + "MaxVSize": 4294967296, "SoftTimeout": 129600, "GracePeriod": 300, - "dashboard": "dashboard-OVERRIDE-ME", + "Dashboard": "dashboard-OVERRIDE-ME", "Team": "Team--OVERRIDE-ME", "CustodialSites": [], "NonCustodialSites": [], diff --git a/test/data/ReqMgr/requests/MonteCarlo_GENSIM.json b/test/data/ReqMgr/requests/MonteCarlo_GENSIM.json index b3b7c5664b..49e5328a7e 100644 --- a/test/data/ReqMgr/requests/MonteCarlo_GENSIM.json +++ b/test/data/ReqMgr/requests/MonteCarlo_GENSIM.json @@ -37,7 +37,7 @@ "assignRequest": { - "SiteWhitelist": "SiteWhitelist-OVERRIDE-ME", + "SiteWhitelist": [], "SiteBlacklist": [], "MergedLFNBase": "/store/mc", "UnmergedLFNBase": "/store/unmerged", @@ -47,11 +47,11 @@ "AcquisitionEra": "AcquisitionEra-OVERRIDE-ME", "ProcessingVersion": 1, "ProcessingString" : "ProcessingString-OVERRIDE-ME", - "maxRSS": 4294967296, - "maxVSize": 4294967296, + "MaxRSS": 4294967296, + "MaxVSize": 4294967296, "SoftTimeout": 129600, "GracePeriod": 300, - "dashboard": "test", + "Dashboard": "test", "Team": "Team--OVERRIDE-ME", "CustodialSites": [], "NonCustodialSites": [], diff --git a/test/data/ReqMgr/requests/MonteCarlo_LHE.json b/test/data/ReqMgr/requests/MonteCarlo_LHE.json index 011b1b3e8d..481dd46d16 100644 --- a/test/data/ReqMgr/requests/MonteCarlo_LHE.json +++ b/test/data/ReqMgr/requests/MonteCarlo_LHE.json @@ -26,7 +26,7 @@ "assignRequest": { - "SiteWhitelist": "SiteWhitelist-OVERRIDE-ME", + "SiteWhitelist": [], "SiteBlacklist": [], "MergedLFNBase": "/store/generator", "UnmergedLFNBase": "/store/unmerged", @@ -36,11 +36,11 @@ "AcquisitionEra": "AcquisitionEra-OVERRIDE-ME", "ProcessingVersion": 1, "ProcessingString" : "ProcessingString-OVERRIDE-ME", - "maxRSS": 4294967296, - "maxVSize": 4294967296, + "MaxRSS": 4294967296, + "MaxVSize": 4294967296, "SoftTimeout": 129600, "GracePeriod": 300, - "dashboard": "test", + "Dashboard": "test", "Team": "Team--OVERRIDE-ME", "CustodialSites": [], "NonCustodialSites": [], diff --git a/test/data/ReqMgr/requests/ReDigi.json b/test/data/ReqMgr/requests/ReDigi.json index 85834d1652..8155faf4d1 100644 --- a/test/data/ReqMgr/requests/ReDigi.json +++ b/test/data/ReqMgr/requests/ReDigi.json @@ -43,7 +43,7 @@ }, "assignRequest": { - "SiteWhitelist": "SiteWhitelist-OVERRIDE-ME", + "SiteWhitelist": [], "SiteBlacklist": [], "MergedLFNBase": "/store/backfill/1", "UnmergedLFNBase": "/store/unmerged", @@ -53,11 +53,11 @@ "AcquisitionEra": "AcquisitionEra-OVERRIDE-ME", "ProcessingVersion": 1, "ProcessingString" : "ProcessingString-OVERRIDE-ME", - "maxRSS": 4294967296, - "maxVSize": 4294967296, + "MaxRSS": 4294967296, + "MaxVSize": 4294967296, "SoftTimeout": 129600, "GracePeriod": 300, - "dashboard": "dashboard-OVERRIDE-ME", + "Dashboard": "dashboard-OVERRIDE-ME", "Team": "Team--OVERRIDE-ME", "CustodialSites": [], "NonCustodialSites": [], diff --git a/test/data/ReqMgr/requests/ReDigi2.json b/test/data/ReqMgr/requests/ReDigi2.json index 4ec0bfb01e..5cb8c43b1d 100644 --- a/test/data/ReqMgr/requests/ReDigi2.json +++ b/test/data/ReqMgr/requests/ReDigi2.json @@ -39,7 +39,7 @@ }, "assignRequest": { - "SiteWhitelist": "SiteWhitelist-OVERRIDE-ME", + "SiteWhitelist": [], "SiteBlacklist": [], "MergedLFNBase": "/store/backfill/1", "UnmergedLFNBase": "/store/unmerged", @@ -49,11 +49,11 @@ "AcquisitionEra": "AcquisitionEra-OVERRIDE-ME", "ProcessingVersion": 1, "ProcessingString" : "ProcessingString-OVERRIDE-ME", - "maxRSS": 4294967296, - "maxVSize": 4294967296, + "MaxRSS": 4294967296, + "MaxVSize": 4294967296, "SoftTimeout": 129600, "GracePeriod": 300, - "dashboard": "dashboard-OVERRIDE-ME", + "Dashboard": "dashboard-OVERRIDE-ME", "Team": "Team--OVERRIDE-ME", "CustodialSites": [], "NonCustodialSites": [], diff --git a/test/data/ReqMgr/requests/ReReco.json b/test/data/ReqMgr/requests/ReReco.json index 36496c325b..5bc5124b88 100644 --- a/test/data/ReqMgr/requests/ReReco.json +++ b/test/data/ReqMgr/requests/ReReco.json @@ -1,6 +1,6 @@ { "createRequest": - { + { "CMSSWVersion": "CMSSW_4_4_2_patch2", "GlobalTag": "FT_R_44_V11::All", "Campaign": "Campaign-OVERRIDE-ME", @@ -43,7 +43,7 @@ "assignRequest": { - "SiteWhitelist": "SiteWhitelist-OVERRIDE-ME", + "SiteWhitelist": [], "SiteBlacklist": [], "MergedLFNBase": "/store/backfill/1", "UnmergedLFNBase": "/store/unmerged", @@ -53,11 +53,11 @@ "AcquisitionEra": "AcquisitionEra-OVERRIDE-ME", "ProcessingVersion": 1, "ProcessingString" : "ProcessingString-OVERRIDE-ME", - "maxRSS": 4294967296, - "maxVSize": 4294967296, + "MaxRSS": 4294967296, + "MaxVSize": 4294967296, "SoftTimeout": 129600, "GracePeriod": 300, - "dashboard": "dashboard-OVERRIDE-ME", + "Dashboard": "dashboard-OVERRIDE-ME", "Team": "Team--OVERRIDE-ME", "CustodialSites": [], "NonCustodialSites": [], diff --git a/test/data/ReqMgr/requests/ReRecoSkim.json b/test/data/ReqMgr/requests/ReRecoSkim.json index ec8270378f..3b3090e3b8 100644 --- a/test/data/ReqMgr/requests/ReRecoSkim.json +++ b/test/data/ReqMgr/requests/ReRecoSkim.json @@ -47,7 +47,7 @@ }, "assignRequest": { - "SiteWhitelist": "SiteWhitelist-OVERRIDE-ME", + "SiteWhitelist": [], "SiteBlacklist": [], "MergedLFNBase": "/store/backfill/1", "UnmergedLFNBase": "/store/unmerged", @@ -57,11 +57,11 @@ "AcquisitionEra": "AcquisitionEra-OVERRIDE-ME", "ProcessingVersion": 1, "ProcessingString" : "ProcessingString-OVERRIDE-ME", - "maxRSS": 4294967296, - "maxVSize": 4294967296, + "MaxRSS": 4294967296, + "MaxVSize": 4294967296, "SoftTimeout": 129600, "GracePeriod": 300, - "dashboard": "dashboard-OVERRIDE-ME", + "Dashboard": "dashboard-OVERRIDE-ME", "Team": "Team--OVERRIDE-ME", "CustodialSites": [], "NonCustodialSites": [], diff --git a/test/data/ReqMgr/requests/TaskChainH130GGgluonfusion_PU.json b/test/data/ReqMgr/requests/TaskChainH130GGgluonfusion_PU.json index b5e9ba62ff..32fee63b7c 100644 --- a/test/data/ReqMgr/requests/TaskChainH130GGgluonfusion_PU.json +++ b/test/data/ReqMgr/requests/TaskChainH130GGgluonfusion_PU.json @@ -48,7 +48,7 @@ "assignRequest": { - "SiteWhitelist": "SiteWhitelist-OVERRIDE-ME", + "SiteWhitelist": [], "SiteBlacklist": [], "MergedLFNBase": "/store/relval", "UnmergedLFNBase": "/store/unmerged", @@ -61,11 +61,11 @@ "RECOPU1" : "ProcessingString-1-OVERRIDE-ME"}, "ProcessingVersion": {"DIGIPU1" : "ProcessingVersion-OVERRIDE-ME", "RECOPU1" : "ProcessingVersion-1-OVERRIDE-ME"}, - "maxRSS": 3072000, - "maxVSize": 20294967, + "MaxRSS": 3072000, + "MaxVSize": 20294967, "SoftTimeout": 129600, "GracePeriod": 1000, - "dashboard": "dashboard-OVERRIDE-ME", + "Dashboard": "dashboard-OVERRIDE-ME", "Team": "Team-OVERRIDE-ME", "CustodialSites": [], "NonCustodialSites": [], diff --git a/test/data/ReqMgr/requests/TaskChainProdMinBias.json b/test/data/ReqMgr/requests/TaskChainProdMinBias.json index 3388eb3ebd..6b7b2d650c 100644 --- a/test/data/ReqMgr/requests/TaskChainProdMinBias.json +++ b/test/data/ReqMgr/requests/TaskChainProdMinBias.json @@ -76,7 +76,7 @@ "assignRequest": { - "SiteWhitelist": "SiteWhitelist-OVERRIDE-ME", + "SiteWhitelist": [], "SiteBlacklist": [], "MergedLFNBase": "/store/relval", "UnmergedLFNBase": "/store/unmerged", @@ -92,11 +92,11 @@ "ProcessingVersion": {"ProdMinBias" : "ProcessingVersion-OVERRIDE-ME", "DIGIPROD1" : "ProcessingVersion-1-OVERRIDE-ME", "RECOPROD1" : "ProcessingVersion-2-OVERRIDE-ME"}, - "maxRSS": 3072000, - "maxVSize": 20294967, + "MaxRSS": 3072000, + "MaxVSize": 20294967, "SoftTimeout": 129600, "GracePeriod": 1000, - "dashboard": "dashboard-OVERRIDE-ME", + "Dashboard": "dashboard-OVERRIDE-ME", "Team": "Team-OVERRIDE-ME", "CustodialSites": [], "NonCustodialSites": [], diff --git a/test/data/ReqMgr/requests/TaskChainPyquenZeemumuJets_PU.json b/test/data/ReqMgr/requests/TaskChainPyquenZeemumuJets_PU.json index 1af1021604..99efecc941 100644 --- a/test/data/ReqMgr/requests/TaskChainPyquenZeemumuJets_PU.json +++ b/test/data/ReqMgr/requests/TaskChainPyquenZeemumuJets_PU.json @@ -63,7 +63,7 @@ "assignRequest": { - "SiteWhitelist": "SiteWhitelist-OVERRIDE-ME", + "SiteWhitelist": [], "SiteBlacklist": [], "MergedLFNBase": "/store/relval", "UnmergedLFNBase": "/store/unmerged", @@ -79,11 +79,11 @@ "ProcessingVersion": {"Pyquen_ZeemumuJets_pt10_2760GeV" : "ProcessingVersion-OVERRIDE-ME", "DIGIHISt3" : "ProcessingVersion-1-OVERRIDE-ME", "RECOHISt4" : "ProcessingVersion-2-OVERRIDE-ME"}, - "maxRSS": 3072000, - "maxVSize": 20294967, + "MaxRSS": 3072000, + "MaxVSize": 20294967, "SoftTimeout": 129600, "GracePeriod": 1000, - "dashboard": "dashboard-OVERRIDE-ME", + "Dashboard": "dashboard-OVERRIDE-ME", "Team": "Team-OVERRIDE-ME", "CustodialSites": [], "NonCustodialSites": [], diff --git a/test/data/ReqMgr/requests/TaskChainRunTau2012A.json b/test/data/ReqMgr/requests/TaskChainRunTau2012A.json index 1e10370f93..f78f2059f2 100644 --- a/test/data/ReqMgr/requests/TaskChainRunTau2012A.json +++ b/test/data/ReqMgr/requests/TaskChainRunTau2012A.json @@ -47,7 +47,7 @@ "assignRequest": { - "SiteWhitelist": "SiteWhitelist-OVERRIDE-ME", + "SiteWhitelist": [], "SiteBlacklist": [], "MergedLFNBase": "/store/relval", "UnmergedLFNBase": "/store/unmerged", @@ -60,11 +60,11 @@ "RECODreHLT" : "ProcessingString-1-OVERRIDE-ME"}, "ProcessingVersion": {"HLTD" : "ProcessingVersion-OVERRIDE-ME", "RECODreHLT" : "ProcessingVersion-1-OVERRIDE-ME"}, - "maxRSS": 3072000, - "maxVSize": 20294967, + "MaxRSS": 3072000, + "MaxVSize": 20294967, "SoftTimeout": 129600, "GracePeriod": 1000, - "dashboard": "dashboard-OVERRIDE-ME", + "Dashboard": "dashboard-OVERRIDE-ME", "Team": "Team-OVERRIDE-ME", "CustodialSites": [], "NonCustodialSites": [], diff --git a/test/data/ReqMgr/requests/TaskChainSingleMuPt100FastSim.json b/test/data/ReqMgr/requests/TaskChainSingleMuPt100FastSim.json index 5620b248ec..65e64d88f9 100644 --- a/test/data/ReqMgr/requests/TaskChainSingleMuPt100FastSim.json +++ b/test/data/ReqMgr/requests/TaskChainSingleMuPt100FastSim.json @@ -38,7 +38,7 @@ "assignRequest": { - "SiteWhitelist": "SiteWhitelist-OVERRIDE-ME", + "SiteWhitelist": [], "SiteBlacklist": [], "MergedLFNBase": "/store/relval", "UnmergedLFNBase": "/store/unmerged", @@ -48,11 +48,11 @@ "AcquisitionEra": {"SingleMuPt100FS" : "AcquisitionEra-OVERRIDE-ME"}, "ProcessingString" : {"SingleMuPt100FS" : "ProcessingString-OVERRIDE-ME"}, "ProcessingVersion": {"SingleMuPt100FS" : "ProcessingVersion-OVERRIDE-ME"}, - "maxRSS": 3072000, - "maxVSize": 20294967, + "MaxRSS": 3072000, + "MaxVSize": 20294967, "SoftTimeout": 129600, "GracePeriod": 1000, - "dashboard": "dashboard-OVERRIDE-ME", + "Dashboard": "dashboard-OVERRIDE-ME", "Team": "Team-OVERRIDE-ME", "CustodialSites": [], "NonCustodialSites": [], diff --git a/test/data/ReqMgr/requests/TaskChainTTbar.json b/test/data/ReqMgr/requests/TaskChainTTbar.json index e7c064cbcc..2d4ab5a5d2 100644 --- a/test/data/ReqMgr/requests/TaskChainTTbar.json +++ b/test/data/ReqMgr/requests/TaskChainTTbar.json @@ -56,7 +56,7 @@ "assignRequest": { - "SiteWhitelist": "SiteWhitelist-OVERRIDE-ME", + "SiteWhitelist": [], "SiteBlacklist": [], "MergedLFNBase": "/store/relval", "UnmergedLFNBase": "/store/unmerged", @@ -72,11 +72,11 @@ "ProcessingVersion": {"DIGI" : "ProcessingVersion-OVERRIDE-ME", "RECO" : "ProcessingVersion-1-OVERRIDE-ME", "ALCATT" : "ProcessingVersion-2-OVERRIDE-ME"}, - "maxRSS": 3072000, - "maxVSize": 20294967, + "MaxRSS": 3072000, + "MaxVSize": 20294967, "SoftTimeout": 129600, "GracePeriod": 1000, - "dashboard": "dashboard-OVERRIDE-ME", + "Dashboard": "dashboard-OVERRIDE-ME", "Team": "Team-OVERRIDE-ME", "CustodialSites": [], "NonCustodialSites": [], diff --git a/test/data/ReqMgr/requests/TaskChainZJetsLNu_LHE.json b/test/data/ReqMgr/requests/TaskChainZJetsLNu_LHE.json index da73fc18a7..2320165776 100644 --- a/test/data/ReqMgr/requests/TaskChainZJetsLNu_LHE.json +++ b/test/data/ReqMgr/requests/TaskChainZJetsLNu_LHE.json @@ -40,7 +40,7 @@ "assignRequest": { - "SiteWhitelist": "SiteWhitelist-OVERRIDE-ME", + "SiteWhitelist": [], "SiteBlacklist": [], "MergedLFNBase": "/store/relval", "UnmergedLFNBase": "/store/unmerged", @@ -50,11 +50,11 @@ "AcquisitionEra": {"ZJetsLNu_Tune4C_8TeV_madgraph-pythia8" : "AcquisitionEra-OVERRIDE-ME"}, "ProcessingString" : {"ZJetsLNu_Tune4C_8TeV_madgraph-pythia8" : "ProcessingString-OVERRIDE-ME"}, "ProcessingVersion": {"ZJetsLNu_Tune4C_8TeV_madgraph-pythia8" : "ProcessingVersion-OVERRIDE-ME"}, - "maxRSS": 3072000, - "maxVSize": 20294967, + "MaxRSS": 3072000, + "MaxVSize": 20294967, "SoftTimeout": 129600, "GracePeriod": 1000, - "dashboard": "dashboard-OVERRIDE-ME", + "Dashboard": "dashboard-OVERRIDE-ME", "Team": "Team-OVERRIDE-ME", "CustodialSites": [], "NonCustodialSites": [], diff --git a/test/data/ReqMgr/requests/TaskChainZTT_PU.json b/test/data/ReqMgr/requests/TaskChainZTT_PU.json index a1dbf67974..2b5de09978 100644 --- a/test/data/ReqMgr/requests/TaskChainZTT_PU.json +++ b/test/data/ReqMgr/requests/TaskChainZTT_PU.json @@ -62,7 +62,7 @@ "assignRequest": { - "SiteWhitelist": "SiteWhitelist-OVERRIDE-ME", + "SiteWhitelist": [], "SiteBlacklist": [], "MergedLFNBase": "/store/relval", "UnmergedLFNBase": "/store/unmerged", @@ -78,11 +78,11 @@ "ProcessingVersion": {"ZTT" : "ProcessingVersion-OVERRIDE-ME", "DIGIPU1" : "ProcessingVersion-1-OVERRIDE-ME", "RECOPU1" : "ProcessingVersion-2-OVERRIDE-ME"}, - "maxRSS": 3072000, - "maxVSize": 20294967, + "MaxRSS": 3072000, + "MaxVSize": 20294967, "SoftTimeout": 129600, "GracePeriod": 1000, - "dashboard": "dashboard-OVERRIDE-ME", + "Dashboard": "dashboard-OVERRIDE-ME", "Team": "Team-OVERRIDE-ME", "CustodialSites": [], "NonCustodialSites": [], diff --git a/test/python/WMCore_t/ReqMgr_t/Service_t/ReqMgr_t.py b/test/python/WMCore_t/ReqMgr_t/Service_t/ReqMgr_t.py index 384b6c7501..139240d616 100644 --- a/test/python/WMCore_t/ReqMgr_t/Service_t/ReqMgr_t.py +++ b/test/python/WMCore_t/ReqMgr_t/Service_t/ReqMgr_t.py @@ -10,6 +10,9 @@ from WMCore.Wrappers import JsonWrapper from WMCore.WMBase import getWMBASE from WMQuality.REST.RESTBaseUnitTestWithDBBackend import RESTBaseUnitTestWithDBBackend +from WMCore.ReqMgr.Auth import ADMIN_PERMISSION, DEFAULT_STATUS_PERMISSION, \ + CREATE_PERMISSION, DEFAULT_PERMISSION, ASSIGN_PERMISSION +from WMCore.REST.Test import fake_authz_headers # this needs to move in better location def insertDataToCouch(couchUrl, couchDBName, data): @@ -20,6 +23,12 @@ def insertDataToCouch(couchUrl, couchDBName, data): doc = database.commit(data) return doc +def getAuthHeader(hmacData, reqAuth): + roles = {} + for role in reqAuth['role']: + roles[role] = {'group': reqAuth['group']} + + return fake_authz_headers(hmacData, dn = "/TEST/DN/CN", roles = roles, format = "dict") class ReqMgrTest(RESTBaseUnitTestWithDBBackend): @@ -33,22 +42,46 @@ class ReqMgrTest(RESTBaseUnitTestWithDBBackend): Not the correctness of functions. That will be tested in different module. """ + def setFakeDN(self): + # put into ReqMgr auxiliary database under "software" document scram/cmsms + # which we'll need a little for request injection + #Warning: this assumes the same structure in jenkins wmcore_root/test + self.admin_header = getAuthHeader(self.test_authz_key.data, ADMIN_PERMISSION) + self.create_header = getAuthHeader(self.test_authz_key.data, CREATE_PERMISSION) + self.default_header = getAuthHeader(self.test_authz_key.data, DEFAULT_PERMISSION) + self.assign_header = getAuthHeader(self.test_authz_key.data, ASSIGN_PERMISSION) + self.default_status_header = getAuthHeader(self.test_authz_key.data, DEFAULT_STATUS_PERMISSION) + def setUp(self): self.setConfig(config) self.setCouchDBs([(config.views.data.couch_reqmgr_db, "ReqMgr"), (config.views.data.couch_reqmgr_aux_db, None)]) self.setSchemaModules([]) + RESTBaseUnitTestWithDBBackend.setUp(self) - # put into ReqMgr auxiliary database under "software" document scram/cmsms - # which we'll need a little for request injection - #Warning: this assumes the same structure in jenkins wmcore_root/test + self.setFakeDN() + #print "%s" % self.test_authz_key.data + self.default_status_header = getAuthHeader(self.test_authz_key.data, DEFAULT_STATUS_PERMISSION) + requestPath = os.path.join(getWMBASE(), "test", "data", "ReqMgr", "requests") - mcFile = open(os.path.join(requestPath, "MonteCarlo.json"), 'r') - self.mcArgs = JsonWrapper.load(mcFile)["createRequest"] + rerecoFile = open(os.path.join(requestPath, "ReReco.json"), 'r') + + rerecoArgs = JsonWrapper.load(rerecoFile) + self.rerecoCreateArgs = rerecoArgs["createRequest"] + self.rerecoAssignArgs = rerecoArgs["assignRequest"] + # overwrite rereco args + self.rerecoAssignArgs["AcquisitionEra"] = "test_aqc" + + lheFile = open(os.path.join(requestPath, "LHEStep0.json"), 'r') + lheArgs = JsonWrapper.load(lheFile) + self.lheStep0CreateArgs = lheArgs["createRequest"] + self.lheStep0AssignArgs = lheArgs["assignRequest"] + self.lheStep0AssignArgs["AcquisitionEra"] = "test_aqc" + cmsswDoc = {"_id": "software"} - cmsswDoc[self.mcArgs["ScramArch"]] = [] - cmsswDoc[self.mcArgs["ScramArch"]].append(self.mcArgs["CMSSWVersion"]) + cmsswDoc[self.rerecoCreateArgs["ScramArch"]] = [] + cmsswDoc[self.rerecoCreateArgs["ScramArch"]].append(self.rerecoCreateArgs["CMSSWVersion"]) insertDataToCouch(os.getenv("COUCHURL"), config.views.data.couch_reqmgr_aux_db, cmsswDoc) @@ -56,16 +89,153 @@ def tearDown(self): RESTBaseUnitTestWithDBBackend.tearDown(self) - def testRequest(self): - self.jsonSender.post('data/request', self.mcArgs) - #TODO: need to make stale option disabled to have the correct record - result = self.jsonSender.get('data/request?status=new&status=assigned&_nostale=true')[0]['result'] - # TODO - # the above call should return request values. assert some of them - # against self.mcArgs - self.jsonSender.get('data/request?prep_id=%s&_nostale=true' % self.mcArgs["PrepID"])[0]['result'] + def getRequestWithNoStale(self, query): + prefixWithNoStale = "data/request?_nostale=true&" + return self.jsonSender.get(prefixWithNoStale + query, + incoming_headers=self.default_header) + + def postRequestWithAuth(self, data): + return self.jsonSender.post('data/request', data, incoming_headers=self.create_header) + + def putRequestWithAuth(self, requestName, data): + """ + WMCore.REST doesn take query for the put request. + data need to send on the body + """ + return self.jsonSender.put('data/request/%s' % requestName, data, + incoming_headers=self.assign_header) + + def cloneRequestWithAuth(self, requestName, params = {}): + """ + WMCore.REST doesn take query for the put request. + data need to send on the body + """ + params["OriginalRequestName"] = requestName + return self.jsonSender.put('data/request/clone', params, + incoming_headers=self.assign_header) + + def resultLength(self, response, format="dict"): + # result is dict format + if format == "dict": + return len(response[0]['result'][0]) + elif format == "list": + return len(response[0]['result']) + + def insertRequest(self, args): + # test post method + respond = self.postRequestWithAuth(self.rerecoCreateArgs) + self.assertEqual(respond[1], 200) + + requestName = respond[0]['result'][0]['RequestName'] + return requestName + + def testRequestSimpleCycle(self): + """ + test request cycle with one request without composite get condition. + post, get, put + """ + + # test post method + requestName = self.insertRequest(self.rerecoCreateArgs) + + ## test get method + # get by name + respond = self.getRequestWithNoStale('name=%s' % requestName) + self.assertEqual(respond[1], 200, "get by name") + self.assertEqual(self.resultLength(respond), 1) + + # get by status + respond = self.getRequestWithNoStale('status=new') + self.assertEqual(respond[1], 200, "get by status") + self.assertEqual(self.resultLength(respond), 1) + + #this create cache + # need to find the way to reste Etag or not getting from the cache +# respond = self.getRequestWithNoStale('status=assigned') +# self.assertEqual(respond[1], 200, "get by status") +# self.assertEqual(self.resultLength(respond), 0) + + # get by prepID + respond = self.getRequestWithNoStale('prep_id=%s' % self.rerecoCreateArgs["PrepID"]) + self.assertEqual(respond[1], 200) + self.assertEqual(self.resultLength(respond), 1) + #import pdb + #pdb.set_trace() + respond = self.getRequestWithNoStale('campaign=%s' % self.rerecoCreateArgs["Campaign"]) + self.assertEqual(respond[1], 200) + self.assertEqual(self.resultLength(respond), 1) + + respond = self.getRequestWithNoStale('inputdataset=%s' % self.rerecoCreateArgs["InputDataset"]) + self.assertEqual(respond[1], 200) + self.assertEqual(self.resultLength(respond), 1) + # test put request with just status change + data = {'RequestStatus': 'assignment-approved'} + self.putRequestWithAuth(requestName, data) + respond = self.getRequestWithNoStale('status=assignment-approved') + self.assertEqual(respond[1], 200, "put request status change") + self.assertEqual(self.resultLength(respond), 1) + # assign with team + # test put request with just status change + data = {'RequestStatus': 'assigned'} + data.update(self.rerecoAssignArgs) + self.putRequestWithAuth(requestName, data) + respond = self.getRequestWithNoStale('status=assigned') + self.assertEqual(respond[1], 200, "put request status change") + self.assertEqual(self.resultLength(respond), 1) + + respond = self.getRequestWithNoStale('status=assigned&team=%s' % + self.rerecoAssignArgs['Team']) + self.assertEqual(respond[1], 200, "put request status change") + self.assertEqual(self.resultLength(respond), 1) + + #respond = self.cloneRequestWithAuth(requestName) + #self.assertEqual(respond[1], 200, "put request clone") + #respond = self.getRequestWithNoStale('status=new') + #self.assertEqual(self.resultLength(respond), 1) + + def atestRequestCombinedGetCall(self): + """ + test request composite get call + """ + + # test post method + from pprint import pprint + del self.rerecoCreateArgs["CMSSWVersion"] + pprint(self.rerecoCreateArgs) + rerecoReqName = self.insertRequest(self.rerecoCreateArgs) + lheReqName = self.insertRequest(self.lheStep0CreateArgs) + ## test get method + # get by name + respond = self.getRequestWithNoStale('name=%s&name=%s' % (rerecoReqName, lheReqName)) + self.assertEqual(respond[1], 200, "get by name") + self.assertEqual(self.resultLength(respond), 2) + + # get by status + respond = self.getRequestWithNoStale('status=new') + self.assertEqual(respond[1], 200, "get by status") + self.assertEqual(self.resultLength(respond), 2) + + # get by prepID + respond = self.getRequestWithNoStale('prep_id=%s' % self.rerecoCreateArgs["PrepID"]) + self.assertEqual(respond[1], 200) + self.assertEqual(self.resultLength(respond), 2) + #import pdb + #pdb.set_trace() + respond = self.getRequestWithNoStale('campaign=%s' % self.rerecoCreateArgs["Campaign"]) + self.assertEqual(respond[1], 200) + self.assertEqual(self.resultLength(respond), 2) + + + def atestRequestClone(self): + requestName = self.insertRequest(self.rerecoCreateArgs) + respond = self.cloneRequestWithAuth(requestName) + print respond + self.assertEqual(respond[1], 200, "put request clone") + respond = self.getRequestWithNoStale('status=new') + self.assertEqual(self.resultLength(respond), 2) + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/test/python/WMCore_t/ReqMgr_t/TestConfig.py b/test/python/WMCore_t/ReqMgr_t/TestConfig.py index 44d0d14155..fc1c715192 100644 --- a/test/python/WMCore_t/ReqMgr_t/TestConfig.py +++ b/test/python/WMCore_t/ReqMgr_t/TestConfig.py @@ -90,7 +90,7 @@ config.main.tools.section_("cms_auth") config.main.server.socket_host = "127.0.0.1" config.main.server.environment = "staging" # must not be "production" -config.main.tools.cms_auth.policy = "dangerously_insecure" +#config.main.tools.cms_auth.policy = "dangerously_insecure" # go up /deployment/reqmgr2/__file__ first_part = os.path.abspath(__file__.rsplit('/', 3)[0]) diff --git a/test/python/WMCore_t/RequestManager_t/CherryPyThread_t/CherryPyPeriodicTask_t.py b/test/python/WMCore_t/RequestManager_t/CherryPyThread_t/CherryPyPeriodicTask_t.py new file mode 100644 index 0000000000..56112b71b5 --- /dev/null +++ b/test/python/WMCore_t/RequestManager_t/CherryPyThread_t/CherryPyPeriodicTask_t.py @@ -0,0 +1,50 @@ +from WMCore.ReqMgr.CherryPyThreads.CherryPyPeriodicTask import \ + SequentialTaskBase, PeriodicWorker, CherryPyPeriodicTask + + +class Hello(SequentialTaskBase): + + def setCallSequence(self): + self._callSequence = [self.printHello, self.printThere, self.printA] + + def printHello(self, config): + print "Hello" + + def printThere(self, config): + print "there" + + def printA(self, config): + print "A" + + +def sayHello(config): + print "Hi Hello" + +def sayBye(config): + print "Bye" + +class WMDataMining(CherryPyPeriodicTask): + + def __init__(self, rest, config): + + CherryPyPeriodicTask.__init__(self, config) + + def setConcurrentTasks(self, config): + """ + sets the list of functions which + """ + self.concurrentTasks = [{'func': sayBye, 'duration': config.activeDuration}, + {'func': Hello(), 'duration': config.archiveDuration}] + + +if __name__ == '__main__': + import cherrypy + from WMCore.Configuration import Configuration + config = Configuration() + config.section_("wmmining") + config.wmmining.activeDuration = 5 + config.wmmining.archiveDuration = 30 + + #helloTask = PeriodicWorker(sayHello, config.wmmining) + WMDataMining(None, config.wmmining) + cherrypy.quickstart() \ No newline at end of file diff --git a/test/python/WMCore_t/RequestManager_t/CherryPyThread_t/__init__.py b/test/python/WMCore_t/RequestManager_t/CherryPyThread_t/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/python/WMCore_t/Services_t/ReqMgr_t/ReqMgr_t.py b/test/python/WMCore_t/Services_t/ReqMgr_t/ReqMgr_t.py new file mode 100644 index 0000000000..03904171db --- /dev/null +++ b/test/python/WMCore_t/Services_t/ReqMgr_t/ReqMgr_t.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python + +import os +import unittest + +#from WMCore_t.ReqMgr_t.Config import Config +from WMCore_t.ReqMgr_t.TestConfig import config +from WMCore.Wrappers import JsonWrapper +from WMCore.Services.ReqMgr.ReqMgr import ReqMgr +from WMCore.WMBase import getWMBASE +from WMQuality.REST.RESTBaseUnitTestWithDBBackend import RESTBaseUnitTestWithDBBackend +from WMCore.ReqMgr.Auth import ADMIN_PERMISSION, DEFAULT_STATUS_PERMISSION, \ + CREATE_PERMISSION, DEFAULT_PERMISSION, ASSIGN_PERMISSION +from WMCore.REST.Test import fake_authz_headers + +# this needs to move in better location +def insertDataToCouch(couchUrl, couchDBName, data): + import WMCore.Database.CMSCouch + server = WMCore.Database.CMSCouch.CouchServer(couchUrl) + database = server.connectDatabase(couchDBName) + + doc = database.commit(data) + return doc + +def getAuthHeader(hmacData, reqAuth): + roles = {} + for role in reqAuth['role']: + roles[role] = {'group': reqAuth['group']} + + return fake_authz_headers(hmacData, roles = roles, format = "dict") + + +class ReqMgrTest(RESTBaseUnitTestWithDBBackend): + """ + Test WorkQueue Service client + It will start WorkQueue RESTService + Server DB sets from environment variable. + Client DB sets from environment variable. + + This checks whether DS call makes without error and return the results. + Not the correctness of functions. That will be tested in different module. + + """ + + + def setFakeDN(self): + # put into ReqMgr auxiliary database under "software" document scram/cmsms + # which we'll need a little for request injection + #Warning: this assumes the same structure in jenkins wmcore_root/test + self.admin_header = getAuthHeader(self.test_authz_key.data, ADMIN_PERMISSION) + self.create_header = getAuthHeader(self.test_authz_key.data, CREATE_PERMISSION) + self.default_header = getAuthHeader(self.test_authz_key.data, DEFAULT_PERMISSION) + self.assign_header = getAuthHeader(self.test_authz_key.data, ASSIGN_PERMISSION) + self.default_status_header = getAuthHeader(self.test_authz_key.data, DEFAULT_STATUS_PERMISSION) + + def setUp(self): + self.setConfig(config) + self.setCouchDBs([(config.views.data.couch_reqmgr_db, "ReqMgr"), + (config.views.data.couch_reqmgr_aux_db, None)]) + self.setSchemaModules([]) + + RESTBaseUnitTestWithDBBackend.setUp(self) + + self.setFakeDN() + + requestPath = os.path.join(getWMBASE(), "test", "data", "ReqMgr", "requests") + rerecoFile = open(os.path.join(requestPath, "ReReco.json"), 'r') + + rerecoArgs = JsonWrapper.load(rerecoFile) + self.rerecoCreateArgs = rerecoArgs["createRequest"] + self.rerecoAssignArgs = rerecoArgs["assignRequest"] + cmsswDoc = {"_id": "software"} + cmsswDoc[self.rerecoCreateArgs["ScramArch"]] = [] + cmsswDoc[self.rerecoCreateArgs["ScramArch"]].append(self.rerecoCreateArgs["CMSSWVersion"]) + insertDataToCouch(os.getenv("COUCHURL"), config.views.data.couch_reqmgr_aux_db, cmsswDoc) + self.reqSvc = ReqMgr(self.jsonSender["host"]) + self.reqSvc._noStale = True + self.reqSvc['requests'].additionalHeaders = self.create_header + + def tearDown(self): + RESTBaseUnitTestWithDBBackend.tearDown(self) + + + def testRequestSimpleCycle(self): + """ + test request cycle with one request without composite get condition. + post, get, put + """ + + # test post method + response = self.reqSvc.insertRequests(self.rerecoCreateArgs) + from pprint import pprint + pprint(response) + self.assertEqual(len(response), 1) + requestName = response[0]['RequestName'] + + ## test get method + # get by name + response = self.reqSvc.getRequestByNames(requestName) + self.assertEqual(len(response), 1) + + # get by status + response = self.reqSvc.getRequestByStatus('new') + self.assertEqual(len(response), 1) + + + self.reqSvc.updateRequestStatus(requestName, 'assignment-approved') + response = self.reqSvc.getRequestByStatus('assignment-approved') + self.assertEqual(len(response), 1) + + self.reqSvc.updateRequestProperty(requestName, {'RequestStatus': 'assigned', "SiteWhitelist": ["ABC"], "SiteBlacklist": ["A"]}) + response = self.reqSvc.getRequestByStatus('assignment-approved') + self.assertEqual(len(response), 0) + response = self.reqSvc.getRequestByStatus('assigned') + self.assertEqual(len(response), 1) + self.assertEqual(response[0].values()[0]["SiteWhitelist"], ["ABC"]) + +if __name__ == '__main__': + unittest.main() diff --git a/test/python/WMCore_t/Services_t/ReqMgr_t/__init__.py b/test/python/WMCore_t/Services_t/ReqMgr_t/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/python/cherrypyTest.py b/test/python/cherrypyTest.py new file mode 100644 index 0000000000..e69de29bb2