1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015
|
"""
-----------------------------------------------------------------------------
First coded by Luca - De Whiskey's - De Vitis <luca@debian.org> on 2003.05.11
-----------------------------------------------------------------------------
$Id: ZopeCTL.py,v 1.5 2003/11/03 14:12:54 luca Exp $
"""
__version__ = '$Revision: 1.5 $'
__source__ = '$Source: /cvsroot/pkg-zope/zopectl/ZopeCTL.py,v $'
__license__ = """
Copyright (C) 2003 Luca - De Whiskey's - De Vitis <luca@debian.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
__all__ = [
'ZopeInstanceError',
'ZopeInstanceNotice',
'ZopeControlEvent',
'ZopeControlDisaster',
'ZopeControlError',
'ZopeInstance',
'ZopeController'
]
import os
import sys
from ConfigParser import ConfigParser
from signal import SIGTERM, SIGINT, SIGHUP, SIGUSR2
from time import sleep
from pwd import getpwnam
from grp import getgrnam
from getopt import getopt
from warnings import warn
from glob import glob
def _(string):
return string
class ZopeInstanceConf(dict):
"""
NAME
zopectl.conf, zopectl.ini, zopectlrc - Zope instances configuration files
DESCRIPTION
The configuration file consists of sections, lead by a "[section]" header
and followed by "name: value" entries, with continuations in the style of
RFC 822; "name=value" is also accepted. Note that leading whitespace is
removed from values. The optional values can contain format strings which
refer to other values in the same section, or values in a special DEFAULT
section. Format string are in the form '%(item)s', which means the this
portion of value will be replaced with the value of 'item' key. Lines
beginning with "#" or ";" are ignored and may be used to provide comments.
zopectl first reads zopectlrc file (configuring itself and loading system
wide instances defaults values), then reads any instance's configuration file.
zopectlrc file must be composed of two sections:
instances The section in which are defined system wide defaults for any
zope instance and optional useful variables.
zopectlrc The section in which are defined values the modify the behaviour
of zopectl command itself (actually none).
Each instance configuration file may define one or more instances, one per
section. An optional DEFAULT section may be provided to override some system
wide default options. Each section may at last override any previously
set default option defining it anew. In any case, some of the option
keys are required, and other are not.
OPTIONS
REQUIRED KEYS
basename The configuration file basename without any file
extension.
section The name of the section that described the
instance.
name The name of the instance. A reasonable default is
%(basename)s/%(section)s.
user The user name that will be used to run the
instance.
group The group name that will be used to run the
instance. A reasonable default is %(user)s
data-fs-in The location of the default Data.fs file for new
instances.
instance-home Where instance files are located. A reasonable
default is /var/lib/zope/instance/%(name)s.
zope-home Where z2.py, zpasswd.py an the ZServer component
are located.
software-home Where other components and product are located.
OPTIONAL KEYS
access-file This instance access file location.
access-username This instance access initial/emergency username.
access-password The initial/emergency user's password.
access-encoding The format in which the initial/emergency user's
password is encoded.
access-domains The domains from which the initial/emergency
user can log in (it is acceptable to leave
this blank).
pcgi-error-log The value of PCGI_ERROR_LOG in the pcgi resource
file.
pcgi-exe The value of PCGI_EXE in the pcgi resource file.
pcgi-module-path The value of PCGI_MODULE_PATH in the pcgi resource
file.
pcgi-name The value of PCGI_NAME in the pcgi resource file.
Must be "Zope".
pcgi-pid-file The value of PCGI_PID_FILE in the pcgi resource
file.
pcgi-publisher The value of PCGI_PUBLISHER in the pcgi resource
file.
pcgi-resource-file The PCGI resource file location.
pcgi-socket-file The value of PCGI_SOCKET_FILE in the pcgi resource
file.
fastcgi-path-or-port A UNIX socket file, or a port number that will be
used by the FastCGI server.
z2-detailed-log-file The instance detailed log file location.
event-log-file The instance event log file.
event-log-format The instance custom event logs format.
event-log-severity The instance minimum accepted level for event logs.
zsyslog The syslog device on which send event logs.
zsyslog-facility The facility to use sending event logs to syslog.
zsyslog-server The syslog UDP address:port to use to send event
zsyslog-access Like zsyslog, but for access logs rather then
event logs.
zsyslog-access-facility Like zsyslog-facility, but for access logs
rather then event logs.
zsyslog-access-server Like zsyslog-server, but for access logs rather
then event logs.
z2-log-file The instance log file location.
z2-pid-file The instance PID file location.
profile-publisher A file name to which zope writes the profiling
information of each request.
suppress-accessrule If set, all SiteRoots are suppressed (used by
SiteAccess products).
suppress-siteroot If set, all site access rules are suppressed (used
by SiteAccess products).
zsp-ownerous-skip If set, will cause the Zope Security Policy to
skip checks relating to ownership, for servers on
which ownership is not important.
zsp-authenticated-skip If set, will cause the Zope Security Policy to
skip checks relating to authentication, for
servers which serve only anonymous content.
zsession-add-notify An optional full Zope path name of a callable
object to be set as the "script to call on object
addition" of the session_data transient object
container created in temp_folder at startup.
zsession-del-notify An optional full Zope path name of a callable
object to be set as the "script to call on object
deletion" of the session_data transient object
container created in temp_folder at startup.
zsession-timeout-mins The number of minutes to be used as the "data
object timeout" of the
"/temp_folder/session_data" transient object
container.
zsession-object-limit The number of items to use as a "maximum number
of subobjects" value of the "/temp_folder"
session data transient object container.
webdav-source-port-clients Setting this variable enables the retrieval
of the document source through the standard
HTTP port instead of the WebDAV port. The
value of this variable is a regular
expression that is matched against the
user-agent string of the client.
stx-default-level Set this variable to change the default level for
<Hx> elements.
zope-dtml-request-autoquote Set this variable to one of "no", "0" or
"disabled" to disable autoquoting of
implicitly retrieved REQUEST data that
contain a '<' when used in a dtml-var
construction.
base-port A value of [address:]port type that will be used
to generate HTTP, FTP and monitor [address]:port
values. Multiple values separated by white space
are allowed.
http-port A value of [address:]port type that will be used
to listen for incoming HTTP connections. Multiple
values separated by white space are allowed.
webdav-port A value of [address:]port type that will be used
to listen for incoming WebDAV connections.
Multiple values separated by white space are
allowed.
ftp-port A value of [address:]port type that will be used
to listen for incoming FTP connections. Multiple
values separated by white space are allowed.
icp-port A value of [address:]port type that will be used
for load balancing between multiple back-end Zope
servers and a ICP-aware front end. Multiple values
separated by white space are allowed.
monitor-port A value of [address:]port type that will be used
for the secure monitor server port. Multiple
values separated by white space are allowed.
use-management-process A value that will be use to decide whether to
start the management process or not.
check-interval A value that will be used as interpreter check
interval (in seconds).
dns A value that will be used as the IP address of the
DNS server.
listen A value that will be used as the IP address to
listen on for incoming connections.
locale A value that will be used as the locale identifier
for the instance message's localization.
number-of-tread A value that will be used as the number of thread
to use if ZODB3 is used.
zope-database-quota An integer number of bytes.
zope-read-only If this variable is set, then the database is
opened in read only mode. If this variable is set
to a string parsable by DateTime.DateTime, then
the database is opened read-only as of the time
given.
OTHER KEYS
Other neither REQUIRED, nor OPTIONAL keys may be provided as well.
Those key will be ignored for the purpose of zopectl or instances
configuration, but may provide useful values in format strings.
EXAMPLES
See /usr/share/doc/zopectl/examples/debugrc
SEE ALSO
zope-z2(8), zope-zpasswd(8), zopectl(8)
AUTHOR
Luca - De Whiskey's - De Vitis <luca@debian.org>
"""
def __init__(self, conf = {}, *items, **more):
"""
ZopeInstanceConf(conf = {}, *items, **more)
Initialize a ZopeInstance configuration table. Interface is more like a
dictionary: the main difference from a dictionary is that you can't add
more keys.
Each value in *items must be a 2 items sequence where the first item is the
configuration key, while the last is the value. Other key=value pairs may
be specified trough **more.
"""
self.__headers = ['command', 'flag', 'type', 'value']
self.__true = ('1', 'yes', 'true', 'on')
self.__false = ('0', 'no', 'false', 'off')
self.__table = {
'access-file': ['pwd', None, None, None],
'access-username': ['pwd', '-u', None, None],
'access-password': ['pwd', '-p', None, None],
'access-encoding': ['pwd', '-e', None, None],
'access-domains': ['pwd', '-d', None, None],
'basename': [None, None, None, None],
'check-interval': ['z2', '-i', None, None],
'debug': ['z2', '-D', 'flag', None],
'dns': ['z2', '-d', None, None],
'data-fs-in': [None, None, None, None],
'force-http-connection-close': ['z2', '-C', 'flag', None],
'group': [None, None, None, None],
'instance-home': ['z2', None, None, None],
'software-home': ['z2', None, None, None],
'zope-home': ['z2', '-z', None, None],
'listen': ['z2', '-a', None, None],
'locale': ['z2', '-L', None, None],
'name': [None, None, None, None],
'number-of-tread': ['z2', '-t', None, None],
'pcgi-error-log': ['pcgi', None, None, None],
'pcgi-exe': ['pcgi', None, None, None],
'pcgi-module-path': ['pcgi', None, None, None],
'pcgi-name': ['pcgi', None, None, None],
'pcgi-pid-file': ['pcgi', None, None, None],
'pcgi-publisher': ['pcgi', None, None, None],
'pcgi-resource-file': ['z2', '-p', None, None],
'pcgi-socket-file': ['pcgi', None, None, None],
'pcgi-wrapper': [None, None, None, None],
'profile-publisher': ['z2', None, None, None],
'suppress-accessrule': ['z2', None, None, None],
'suppress-siteroot': ['z2', None, None, None],
'zsp-ownerous-skip': ['z2', None, None, None],
'zsp-authenticated-skip': ['z2', None, None, None],
'zsession-add-notify': ['z2', None, None, None],
'zsession-del-notify': ['z2', None, None, None],
'zsession-timeout-mins': ['z2', None, None, None],
'zsession-object-limit': ['z2', None, None, None],
'webdav-source-port-clients': ['z2', None, None, None],
'stx-default-level': ['z2', None, None, None],
'zope-dtml-request-autoquote': ['z2', None, None, None],
'zope-database-quota': ['z2', None, None, None],
'zope-read-only': ['z2', None, None, None],
'base-port': ['z2', '-P', 'multi', None],
'ftp-port': ['z2', '-f', 'multi', None],
'http-port': ['z2', '-w', 'multi', None],
'icp-port': ['z2', '--icp', 'multi', None],
'monitor-port': ['z2', '-m', 'multi', None],
'webdav-port': ['z2', '-W', 'multi', None],
'fastcgi-path-or-port': ['z2', '-F', 'multi', None],
'read-only': ['z2', '-r', 'flag', None],
'section': [None, None, None, None],
'use-management-process': ['z2', '-Z', 'bool', None],
'user': ['z2', '-u', None, None],
'z2-detailed-log-file': ['z2', '-M', None, None],
'event-log-format': ['z2', None, None, None],
'event-log-file': ['z2', None, None, None],
'event-log-severity': ['z2', None, None, None],
'zsyslog': ['z2', None, None, None],
'zsyslog-facility': ['z2', None, None, None],
'zsyslog-server': ['z2', None, None, None],
'zsyslog-access': ['z2', None, None, None],
'zsyslog-access-facility': ['z2', None, None, None],
'zsyslog-access-server': ['z2', None, None, None],
'z2-log-file': ['z2', '-l', None, None],
'z2-pid-file': ['z2', '--pid', None, None]
}
self.update(conf)
for item in items:
self[item[0]] = item[1]
self.update(more)
def __len__(self):
return len(filter(None, self.__table.values()))
def __getitem__(self, key):
if self.__table.has_key(key):
return self.__table[key][3]
else:
raise KeyError, key
def __setitem__(self, key, value):
if self.__table.has_key(key):
self.__table[key][3] = value
else:
self.__table[key] = [ None, None, None, value ]
def __contains__(self,key):
return self.__table.has_key(key)
def keys(self):
return self.__table.keys()
def values(self):
return [ value[3] for value in self.__table.values() ]
def items(self):
return zip(self.keys(), self.values())
def has_key(self):
return self.__table.has_key()
def get(self, key, default = None):
if self.__table.has_key(key):
return self.__table[key]
return default
def clear(self):
for key in self.__table.keys():
self.__table[key][3] = None
def setdefaults(self, key, default = None):
result = self.get(key, default)
if not self.__table.has_key(key):
self.__setitem__(key, default)
return result
def iterkeys(self):
return self.__table.iterkeys()
def update(self, items):
for key in items.keys():
self.__setitem__(key, items[key])
def copy(self):
return ZopeInstanceConf(self.items())
def command(self, key):
return self.__table[key][0]
def flag(self, key):
return self.__table[key][1]
def type(self, key):
return self.__table[key][2]
def value(self, key):
return self.__table[key][3]
def options(self, name):
options = []
for (command, flag, _type, value) in self.__table.values():
if (command == name) and ((value is not None) and (flag is not None)):
lower = value.lower()
if (_type == 'bool') and (lower in self.__true):
options.extend([flag, '1'])
elif (_type == 'bool') and (lower in self.__false):
options.extend([flag, '0'])
elif (_type == 'flag') and (lower in self.__true):
options.append(flag)
elif (_type == 'flag') and (lower in self.__false):
pass
elif _type == 'multi':
for item in value.split():
options.extend([flag, item])
elif _type == 'single':
options.extend([flag, ("'%s'" % value)])
else:
options.extend([flag, value])
return options
def env(self, name):
env = []
for (key, (command, flag, _type, value)) in self.__table.items():
if (command == name) and ((value is not None) and (flag is None)):
env.append(('%s=%s' % (key.upper().replace('-', '_'), value)))
return env
class Event(Exception):
def notify(self):
if not self.__dict__.has_key('notified'):
sys.stdout.write(str(self))
sys.stdout.flush()
self.notified = 1
class ZopeInstanceError(Event):
def __init__(self, name, *args):
event = {
'kill': _('could not send signal(%s): %s'),
'running': _('already running with pid(%s)'),
'pidfile': _('could not get PID from file(%s): %s'),
'config': _('unknown configuration key(%s)'),
'zpasswd': _('could not create access file(%s)'),
'access': _('access file(%s) is not a regular file'),
'run': _('system(%s) exited with status(%s)'),
'path': _('missing required path(%s)')
}
self.args = (name, event[name], args)
def __str__(self):
return self.args[1] % self.args[2]
class ZopeInstanceNotice(Event):
def __init__(self, name, *args):
event = {
'noaccess': _("%s not found; please run: zopectl --access '%s'"),
}
self.args = (name, event[name], args)
def __str__(self):
return self.args[1] % self.args[2]
class ZopeInstance:
def __init__(self, conf = {}):
"""
ZopeInstance(conf = {})
Initialize a ZopeInstance by applying the values passed in 'conf'.
Valid keys follow are those described in ZopeInstanceConf.
"""
self.__conf = ZopeInstanceConf(conf)
self.name = self.__conf['name']
self.__command = {
'pwd': [os.path.join(self.__conf['zope-home'], 'zpasswd.py')],
'z2': [os.path.join(self.__conf['zope-home'], 'z2.py')]
}
self.__command['pwd'].extend(self.__conf.options('pwd'))
self.__command['pwd'].append(self.__conf['access-file'])
self.__command['z2'].extend(self.__conf.options('z2'))
self.__command['z2'].extend(self.__conf.env('z2'))
self.do = {
'create': self.create,
'init': self.init,
'access': self.access,
'pcgi': self.pcgi,
'delete': self.delete,
'start': self.start,
'stop': self.stop,
'restart': self.restart,
'logrotate': self.logrotate,
'dump': self.dump
}
def __run(self, argv):
command = ' '.join(argv)
status = os.system(command)
if status != 0:
raise ZopeInstanceError('run', (command[30:] + '...'), status)
def __mkdir(self, path, mode = 0755, user = os.getuid(), group = os.getgid()):
if not os.path.exists(path):
(parent, last) = os.path.split(os.path.normpath(path))
parents = [last]
while parent and not os.path.exists(parent):
(parent, last) = os.path.split(parent)
parents.insert(0, last)
if not parent and os.path.isabs(path):
parent = '/'
for i in range(len(parents)):
os.mkdir(os.path.join(parent, *parents[:i+1]))
os.chmod(path, mode)
os.chown(path, user, group)
def __touch(self, path, mode = 0644, user = os.getuid(), group = os.getgid()):
(dirname, filename) = os.path.split(os.path.normpath(path))
self.__mkdir(0755, user, group, dirname)
file(path, 'a').close()
os.chmod(path, mode)
os.chown(path, user, group)
def __pid(self):
pidfile = self.__conf['z2-pid-file']
try:
return int(open(pidfile, 'r').read())
except Exception, e:
raise ZopeInstanceError('pidfile', pidfile, str(e))
def __kill(self, signal):
pid = self.__pid()
try:
os.kill(pid, signal)
except Exception, e:
raise ZopeInstanceError('kill', signal, str(e))
return pid
def __start(self):
for key in ('pcgi-pid-file', 'z2-pid-file'):
self.__mkdir(os.path.dirname(self.__conf.value(key)))
self.__run(self.__command['z2'])
def __str__(self):
try:
pid = self.__kill(0)
except:
pid = None
return '\n'.join(
['\n pid: %s' % pid] +
[' %s: %s' % (key, value) for (key, value,) in self.__conf.items()]
)
def conf(self, key = None):
"""
conf(key = None) -> ZopeInstanceConf or value
If Key is None, returns a copy of its own configuration, else the
value associated with key.
"""
if key is None:
return self.__conf.copy()
return self.__conf[key]
def isrunning(self):
"""
isrunning() -> bool
Returns 1 if the instance is running, 0 otherwise.
"""
if os.path.exists(self.__conf['z2-pid-file']):
try:
self.__kill(0)
except ZopeInstanceError, e:
return 0
else:
return 1
else:
return 0
def create(self):
"""
DEPRECATED: use init() instead.
create() -> None
Create all the path and files needed to run the instance.
"""
warn(
_('W: This action is deprecated: use init instead'),
DeprecationWarning
)
self.init()
def init(self):
"""
init() -> None
Initialize the instance home directory and needed paths.
"""
instance_home = os.path.normpath(self.__conf['instance-home'])
data_fs_in = os.path.normpath(self.__conf['data-fs-in'])
data_fs = os.path.normpath(os.path.join(instance_home, 'var', 'Data.fs'))
group = getgrnam(self.__conf['group'])[2]
user = getpwnam(self.__conf['user'])[2]
uid = os.getuid()
self.__mkdir(os.path.join(instance_home, 'Extensions'))
self.__mkdir(os.path.join(instance_home, 'import'))
self.__mkdir(os.path.join(instance_home, 'var'), 01775, uid, group)
required = (
self.__conf['z2-log-file'],
self.__conf['event-log-file'],
self.__conf['z2-detailed-log-file'],
self.__conf['pcgi-error-log']
)
for path in filter(None, required):
self.__mkdir(os.path.dirname(path), 01755, user, group)
if not os.path.exists(data_fs):
file(data_fs, 'w').write(file(data_fs_in, 'r').read())
for path in glob(data_fs + '*'):
os.chown(path, user, group)
os.chmod(data_fs, 0644)
def access(self):
access_file = os.path.normpath(self.__conf['access-file'])
self.__mkdir(os.path.dirname(access_file))
if not os.path.exists(access_file):
self.__run(self.__command['pwd'])
def pcgi(self):
pcgi_resource_file = self.__conf['pcgi-resource-file']
parent = os.path.dirname(os.path.normpath(pcgi_resource_file))
if not os.path.exists(parent):
self.__mkdir(parent)
header = [
'#!%s' % self.__conf['pcgi-wrapper'],
'# Atomatically generated by zopectl(8): Do _NOT_ edit!',
'INSTANCE_HOME=%s' % self.__conf['instance-home']
]
open(pcgi_resource_file, 'w').write('\n'.join(
header + self.__conf.env('pcgi'))
)
os.chmod(pcgi_resource_file, 0755)
def delete(self):
"""
delete() -> None
Stops the instance and remove all the path and files needed to run it.
This won't remove import, Extensions, Products directories or Data.fs
file: they are data files, so they might be useful.
"""
try:
self.stop()
except ZopeInstanceError:
pass
data_fs = os.path.join(self.__conf['instance-home'], 'var', 'Data.fs')
deleting = [
self.__conf['pcgi-resource-file'],
self.__conf['access-file']
] + glob(data_fs + '.*')
for path in filter(os.path.exists, deleting):
os.remove(path)
def start(self):
"""
start() -> None
Starts the instance using the z2.py command with a proper argv.
If instance is already running, a proper ZopeInstanceError is raised.
"""
required = (
self.__conf['pcgi-resource-file'],
os.path.join(self.__conf['instance-home'], 'var', 'Data.fs'),
)
for path in required:
if not os.path.exists(path):
raise ZopeInstanceError('path', path)
if not os.path.exists(self.__conf['access-file']):
raise ZopeInstanceNotice('noaccess', self.__conf['access-file'], self.name)
if not self.isrunning():
self.__start()
else:
raise ZopeInstanceError('running', self.__pid())
def stop(self):
"""
stop() -> None
Stops the instance by sending SIGTERM to its PID.
"""
self.__kill(SIGTERM)
def restart(self):
"""
restart() -> None
If instance is running, restarts it by sending SIGHUP to its PID; in
any other case starts the instance.
"""
if self.isrunning():
self.__kill(SIGHUP)
else:
self.__start()
def logrotate(self):
"""
logrotate() -> None
If instance is running, force it to reopen its log files by sending
SIGUSR2 to its PID.
"""
self.__kill(SIGUSR2)
def dump(self):
print self
class ZopeControlEvent(Event):
def __init__(self, name, *args):
event = {
'failed': _(' failed: %s\n'),
'timeout': _(' timed out (failure?): %s\n'),
'arrived': _(' %s(%s),'),
'done': _(' done.\n'),
'start': _('Starting Zope instance %s...\n'),
'stop': _('Stopping Zope instance %s...\n'),
'restart': _('Restarting Zope instance %s...\n'),
'logrotate': _('Forcing logs reopening for Zope instance %s...'),
'dump': _('Dumping Zope Instances: %s'),
'create': _('[DEPRECATED] Creating home for Zope instance %s...'),
'init': _('Initializing Zope instance home for %s...'),
'pcgi': _('Creating PCGI resource file for %s...'),
'delete': _('Deleting Zope Instance %s...'),
'access': _('Setting up initial user for %s...'),
'wait': _(' Waiting instances in late:')
}
self.args = (name, event[name], args)
def __str__(self):
return self.args[1] % self.args[2]
class ZopeControlError(ZopeControlEvent):
def __init__(self, action, instance, error):
self.args = ('failure', instance, action, error)
def __str__(self):
return '%s: %s: %s' % self.args[1:]
class ZopeControlDisaster(ZopeControlEvent):
def __init__(self, events):
self.args = ('disaster', events)
def __str__(self):
return ''.join([ '\n ' + str(event) for event in self.args[1] ])
class ZopeController:
"""
NAME
zopectl - Zope instances controlling tool
SYNOPSIS
zopectl options [ action [ [ instance_name ] ... ] ]
zopectl [ options ] action [ [ instance_name ] ... ]
DESCRIPTION
zopectl is an utility capable of managing multiple zope instances. It can
apply a sequence of actions to multiple instances. All the instances are
described files either in /etc/zopectl/*.conf or in /etc/zopectl/*.ini (see
zopectlrc(5)).
OPTIONS
Each option need an argument. Each argument must be a comma separated list
of instance_name. The special '*' instance_name will trigger the option for
all known instances.
For each instance_name resulting from option parsing:
-s, --start Apply action 'start'
-S, --stop Apply action 'stop'
-r, --restart Apply action 'restart'
-f, --force-reload Apply action 'force-reload'
-a, --access Apply action 'access'
-p, --pcgi Apply action 'pcgi'
-i, --init Apply action 'init'
-d, --delete Apply action 'delete'
-l, --logrotate Apply action 'logrotate'
-D, --dump Apply action 'dump'
If action is provided as argument, zopectl will apply action to any
instance_name specified as argument. If no instance_name is specified,
zopectl will apply action to all known instance_name.
Unknown instance_name are ignored.
ACTIONS
action may be one of the following:
start Starts the instance using /usr/lib/zope/z2.py
stop Sends SIGTERM to instance
restart If instance is running, sends SIGHUP to instance; else
falls back to start action
force-reload Stops the instance, reload the configuration end then start it
anew
logrotate Sends SIGUSR2 to instance which make instance to reopen log
files (see Zope's SIGNALS.txt)
access Regenerate/create the emergency user file
pcgi Regenerate/create the PCGI resource file
delete Stops instance and remove all files and directory used by
instance (logs are not removed)
init Initialize instance_name files and directories setting up
permissions and ownership, but does not create access file or
PCGI resource file.
dump Dump a tagged list 'name: value' of the instance
configuration.
EXAMPLES
Just a test:
# zopectl --start \* --restart \* --force-reload \* stop
This command will apply each action in sequence, so, at the end, it won't do
anything of real use.
# zopectl --init new_instance --pcgy new_instance --access new_instance
This command will completly set up the instance named 'new_instance'. create
action is deprecated, so you should now use these three.
Your Zope logrotate.conf may include the following lines:
postrotate
zopectl logrotate
endscript
NOTE
create action is DEPRECATED. Use init instead.
FILES
/etc/zopectl/zopectlrc The zopectl runtime configuration file
/etc/zopectl/*.{conf,ini} The instances configuration files
SEE ALSO
zope-z2(8), zope-zpasswd(8), zopectlrc(5)
AUTHOR
Luca - De Whiskey's - De Vitis <luca@debian.org>
"""
def __init__(self, selfrc, paths):
"""
ZopeController(selfrc, paths)
Initialize the ZopeController instance by loading the ZopeController RC
file, and all the configuration paths. It then builds a list of
ZopeInstance instances based on the value found in the various
paths. Wrong paths are silently ignored.
selfrc should be a valid configuration files with 2 configuration
sections:
instances:
The default value to pass to each instance (usually, those
in [REQUIRED] section in the ZopeInstanceConf documentation.
zopectlrc:
This is the section dedicated to configuration of
ZopeController instance (currently no option has been defined).
"""
conf = ConfigParser()
conf.read(selfrc)
self.__conf = {
'instances': {},
'zopectlrc': {}
}
if conf.has_section('instances'):
for option in conf.options('instances'):
self.__conf['instances'][option] = conf.get('instances', option, 1)
self.paths = paths
self.__load()
def __load(self, instances = None):
"""
__load(instances = None) -> None
Load the configuration options for each instance name in
'instances'. If 'instances' is None, all available instances are
loaded. Unknown values are ignored.
"""
self.instances = []
defaults = self.__conf['instances'].copy()
for path in self.paths:
defaults['basename'] = os.path.splitext(os.path.split(path)[1])[0]
conf = ConfigParser(defaults)
conf.read(path)
for section in conf.sections():
var = {'section': section}
instance = conf.get(section, 'name')
if ((instances is None) or (instance in instances)):
options = {}
for name in conf.defaults().keys():
options[name] = conf.get('DEFAULT', name, 0, var)
for name in conf.options(section):
options[name] = conf.get(section, name, 0, var)
self.instances.append(ZopeInstance(options))
def __apply(self, instances, action, timeout = 0):
"""
__apply(instances, action, timeout = 0) -> None
Apply action 'action' to each instance in 'instances'. If timeout
'timeout' is greater then 0, it waits for each action to be
completed.
"""
late = []
loaded = [ instance.name for instance in self.instances ]
for instance in instances:
if (instance in loaded):
late.append(self.instances[loaded.index(instance)])
failures = []
for instance in late[:]:
ZopeControlEvent(action, instance.name).notify()
try:
instance.do[action]()
except Exception, e:
ZopeControlEvent('failed', e).notify()
late.remove(instance)
failures.append(ZopeControlError(action, instance.name, e))
else:
if (not timeout):
ZopeControlEvent('done').notify()
if (timeout > 0):
ZopeControlEvent('wait').notify()
for time in range(timeout):
sleep(1)
for instance in late[:]:
running = instance.isrunning()
if (((action == 'stop') and (not running)) or running):
ZopeControlEvent('arrived', instance.name, time).notify()
late.remove(instance)
if (not late):
ZopeControlEvent('done').notify()
break
for instance in late:
failures.append(ZopeControlError('timeout', instance.name, timeout))
if (len(failures) > 1):
raise ZopeControlDisaster(failures)
elif failures:
raise failures[0]
def __getopt(self, *argv):
self.name = os.path.splitext(os.path.basename(argv[0]))[0]
self.actions = []
flags = (
('-s', '--start'),
('-S', '--stop'),
('-r', '--restart'),
('-f', '--force-reload'),
('-c', '--create'),
('-i', '--init'),
('-a', '--access'),
('-p', '--pcgi'),
('-d', '--delete'),
('-l', '--logrotate'),
('-D', '--dump'),
)
short = (':'.join([ flag[0][1] for flag in flags ]) + ':')
_long = [ (flag[1][2:] + '=') for flag in flags ]
(options, self.args,) = getopt(argv[1:], short, _long)
for (option, instances,) in options:
for pair in flags:
if (option in pair):
action = pair[1][2:]
self.actions.append([action, instances.split(',')])
any = [ instance.name for instance in self.instances ]
for index in range(len(self.actions)):
if (self.actions[index][1] == ['*']):
self.actions[index][1] = any
if self.args:
action = self.args[0]
if (action in [ pair[1][2:] for pair in flags ]):
instances = self.args[1:]
if instances:
self.actions.append([action, instances])
else:
self.actions.append([action, any])
def main(self, *argv):
"""
usage:
%(name)s options [ action [ [ instance_name ] ... ] ]
%(name)s [ options ] action [ [ instance_name ] ... ]
options:
Each option need an argument. Each argument must be a comma separated
list of instance names. Unknown instance names are ignored. The
special '*' instance_name means 'any known instance'.
For each instance_name resulting from option parsing:
o -s, --start Apply action 'start'
o -S, --stop Apply action 'stop'
o -r, --restart Apply action 'restart'
o -f, --force-reload Apply action 'force-reload'
o -i, --init Apply action 'init'
o -a, --access Apply action 'access'
o -p, --pcgi Apply action 'pcgi'
o -d, --delete Apply action 'delete'
o -l, --logrotate Apply action 'logrotate'
action:
For each instance_name (all, if no one is specified):
o start Start instance_name
o stop Stops instance_name
o restart Restarts instance_name
o force-reload Force instance_name configuration reloading
o init Initialize instance_name files and directories
o access Create initial/emergency user for instance_name
o pcgi Create PCGI resource file for instance_name
o delete Stops and remove instance_name files and directories
o logrotate Force instance_name to reopen logs
"""
steps = {
'start': ((self.__apply, ['start', 30]),),
'stop': ((self.__apply, ['stop', 5]),),
'restart': ((self.__apply, ['restart', 30]),),
'logrotate': ((self.__apply, ['logrotate']),),
'status': ((self.__apply, ['status']),),
'access': ((self.__apply, ['access']),),
'delete': ((self.__apply, ['delete']),),
'dump': ((self.__apply, ['dump']),),
'pcgi': ((self.__apply, ['pcgi']),),
'init': ((self.__apply, ['init']),),
'force-reload': (
(self.__apply, ['stop', 5]),
(self.__load, []),
(self.__apply, ['start', 30])
)
}
self.__getopt(*argv)
for action, instances in self.actions:
failures = []
for step, args in steps[action]:
try:
step(instances, *args)
except ZopeControlEvent, e:
if (e.args[0] == 'disaster'):
failures.extend(e.args[1])
else:
failures.append(e)
if len(failures) > 1:
raise ZopeControlDisaster(failures)
elif failures:
raise failures[0]
return 0
def status(self, event):
"""
status(event) -> exit_status
Returns the exit status value associated with the event, prints the
event message and run.__doc__ if needed.
"""
event.notify()
error = {
'notice': 0,
'usage': 1,
'failure': 2,
'disaster': 3,
'other': 4
}
if isinstance(event, ZopeControlEvent):
status = error[event.args[0]]
if (event.args[0] == 'usage'):
print _(self.main.__doc__) % { 'name': self.name }
elif isinstance(event, ZopeInstanceNotice):
status = error['notice']
else:
status = error['other']
return status
|