swift 2.32.0__py2.py3-none-any.whl → 2.34.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. swift/account/auditor.py +11 -0
  2. swift/account/reaper.py +11 -1
  3. swift/account/replicator.py +22 -0
  4. swift/account/server.py +13 -12
  5. swift-2.32.0.data/scripts/swift-account-audit → swift/cli/account_audit.py +6 -2
  6. swift-2.32.0.data/scripts/swift-config → swift/cli/config.py +1 -1
  7. swift-2.32.0.data/scripts/swift-dispersion-populate → swift/cli/dispersion_populate.py +6 -2
  8. swift-2.32.0.data/scripts/swift-drive-audit → swift/cli/drive_audit.py +12 -3
  9. swift-2.32.0.data/scripts/swift-get-nodes → swift/cli/get_nodes.py +6 -2
  10. swift/cli/info.py +131 -3
  11. swift-2.32.0.data/scripts/swift-oldies → swift/cli/oldies.py +6 -3
  12. swift-2.32.0.data/scripts/swift-orphans → swift/cli/orphans.py +7 -2
  13. swift-2.32.0.data/scripts/swift-recon-cron → swift/cli/recon_cron.py +9 -18
  14. swift-2.32.0.data/scripts/swift-reconciler-enqueue → swift/cli/reconciler_enqueue.py +2 -3
  15. swift/cli/relinker.py +1 -1
  16. swift/cli/reload.py +141 -0
  17. swift/cli/ringbuilder.py +24 -0
  18. swift/common/daemon.py +12 -2
  19. swift/common/db.py +14 -9
  20. swift/common/db_auditor.py +2 -2
  21. swift/common/db_replicator.py +6 -0
  22. swift/common/exceptions.py +12 -0
  23. swift/common/http_protocol.py +76 -3
  24. swift/common/manager.py +120 -5
  25. swift/common/memcached.py +24 -25
  26. swift/common/middleware/account_quotas.py +144 -43
  27. swift/common/middleware/backend_ratelimit.py +166 -24
  28. swift/common/middleware/catch_errors.py +1 -3
  29. swift/common/middleware/cname_lookup.py +3 -5
  30. swift/common/middleware/container_sync.py +6 -10
  31. swift/common/middleware/crypto/crypto_utils.py +4 -5
  32. swift/common/middleware/crypto/decrypter.py +4 -5
  33. swift/common/middleware/crypto/kms_keymaster.py +2 -1
  34. swift/common/middleware/proxy_logging.py +57 -43
  35. swift/common/middleware/ratelimit.py +6 -7
  36. swift/common/middleware/recon.py +6 -7
  37. swift/common/middleware/s3api/acl_handlers.py +10 -1
  38. swift/common/middleware/s3api/controllers/__init__.py +3 -0
  39. swift/common/middleware/s3api/controllers/acl.py +3 -2
  40. swift/common/middleware/s3api/controllers/logging.py +2 -2
  41. swift/common/middleware/s3api/controllers/multi_upload.py +31 -15
  42. swift/common/middleware/s3api/controllers/obj.py +20 -1
  43. swift/common/middleware/s3api/controllers/object_lock.py +44 -0
  44. swift/common/middleware/s3api/s3api.py +6 -0
  45. swift/common/middleware/s3api/s3request.py +190 -74
  46. swift/common/middleware/s3api/s3response.py +48 -8
  47. swift/common/middleware/s3api/s3token.py +2 -2
  48. swift/common/middleware/s3api/utils.py +2 -1
  49. swift/common/middleware/slo.py +508 -310
  50. swift/common/middleware/staticweb.py +45 -14
  51. swift/common/middleware/tempauth.py +6 -4
  52. swift/common/middleware/tempurl.py +134 -93
  53. swift/common/middleware/x_profile/exceptions.py +1 -4
  54. swift/common/middleware/x_profile/html_viewer.py +9 -10
  55. swift/common/middleware/x_profile/profile_model.py +1 -2
  56. swift/common/middleware/xprofile.py +1 -2
  57. swift/common/request_helpers.py +101 -8
  58. swift/common/statsd_client.py +207 -0
  59. swift/common/storage_policy.py +1 -1
  60. swift/common/swob.py +5 -2
  61. swift/common/utils/__init__.py +331 -1774
  62. swift/common/utils/base.py +138 -0
  63. swift/common/utils/config.py +443 -0
  64. swift/common/utils/logs.py +999 -0
  65. swift/common/utils/timestamp.py +23 -2
  66. swift/common/wsgi.py +19 -3
  67. swift/container/auditor.py +11 -0
  68. swift/container/backend.py +136 -31
  69. swift/container/reconciler.py +11 -2
  70. swift/container/replicator.py +64 -7
  71. swift/container/server.py +276 -146
  72. swift/container/sharder.py +86 -42
  73. swift/container/sync.py +11 -1
  74. swift/container/updater.py +12 -2
  75. swift/obj/auditor.py +20 -3
  76. swift/obj/diskfile.py +63 -25
  77. swift/obj/expirer.py +154 -47
  78. swift/obj/mem_diskfile.py +2 -1
  79. swift/obj/mem_server.py +1 -0
  80. swift/obj/reconstructor.py +28 -4
  81. swift/obj/replicator.py +63 -24
  82. swift/obj/server.py +76 -59
  83. swift/obj/updater.py +12 -2
  84. swift/obj/watchers/dark_data.py +72 -34
  85. swift/proxy/controllers/account.py +3 -2
  86. swift/proxy/controllers/base.py +254 -148
  87. swift/proxy/controllers/container.py +274 -289
  88. swift/proxy/controllers/obj.py +120 -166
  89. swift/proxy/server.py +17 -13
  90. {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/AUTHORS +14 -4
  91. {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/METADATA +9 -7
  92. {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/RECORD +97 -120
  93. {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/entry_points.txt +39 -0
  94. swift-2.34.0.dist-info/pbr.json +1 -0
  95. swift-2.32.0.data/scripts/swift-account-auditor +0 -23
  96. swift-2.32.0.data/scripts/swift-account-info +0 -52
  97. swift-2.32.0.data/scripts/swift-account-reaper +0 -23
  98. swift-2.32.0.data/scripts/swift-account-replicator +0 -34
  99. swift-2.32.0.data/scripts/swift-account-server +0 -23
  100. swift-2.32.0.data/scripts/swift-container-auditor +0 -23
  101. swift-2.32.0.data/scripts/swift-container-info +0 -56
  102. swift-2.32.0.data/scripts/swift-container-reconciler +0 -21
  103. swift-2.32.0.data/scripts/swift-container-replicator +0 -34
  104. swift-2.32.0.data/scripts/swift-container-server +0 -23
  105. swift-2.32.0.data/scripts/swift-container-sharder +0 -37
  106. swift-2.32.0.data/scripts/swift-container-sync +0 -23
  107. swift-2.32.0.data/scripts/swift-container-updater +0 -23
  108. swift-2.32.0.data/scripts/swift-dispersion-report +0 -24
  109. swift-2.32.0.data/scripts/swift-form-signature +0 -20
  110. swift-2.32.0.data/scripts/swift-init +0 -119
  111. swift-2.32.0.data/scripts/swift-object-auditor +0 -29
  112. swift-2.32.0.data/scripts/swift-object-expirer +0 -33
  113. swift-2.32.0.data/scripts/swift-object-info +0 -60
  114. swift-2.32.0.data/scripts/swift-object-reconstructor +0 -33
  115. swift-2.32.0.data/scripts/swift-object-relinker +0 -23
  116. swift-2.32.0.data/scripts/swift-object-replicator +0 -37
  117. swift-2.32.0.data/scripts/swift-object-server +0 -27
  118. swift-2.32.0.data/scripts/swift-object-updater +0 -23
  119. swift-2.32.0.data/scripts/swift-proxy-server +0 -23
  120. swift-2.32.0.data/scripts/swift-recon +0 -24
  121. swift-2.32.0.data/scripts/swift-ring-builder +0 -37
  122. swift-2.32.0.data/scripts/swift-ring-builder-analyzer +0 -22
  123. swift-2.32.0.data/scripts/swift-ring-composer +0 -22
  124. swift-2.32.0.dist-info/pbr.json +0 -1
  125. {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/LICENSE +0 -0
  126. {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/WHEEL +0 -0
  127. {swift-2.32.0.dist-info → swift-2.34.0.dist-info}/top_level.txt +0 -0
swift/cli/reload.py ADDED
@@ -0,0 +1,141 @@
1
+ # Copyright (c) 2022 NVIDIA
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12
+ # implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ """
17
+ Safely reload WSGI servers while minimizing client downtime and errors by
18
+
19
+ * validating that the process is a Swift WSGI server manager,
20
+ * checking that the configuration file used is valid,
21
+ * sending the "seamless reload" signal, and
22
+ * waiting for the reload to complete.
23
+ """
24
+
25
+ from __future__ import print_function
26
+ import argparse
27
+ import errno
28
+ import os
29
+ import os.path
30
+ import signal
31
+ import subprocess
32
+ import sys
33
+ import time
34
+
35
+ from swift.common.manager import get_child_pids
36
+
37
+
38
+ EXIT_BAD_PID = 2 # similar to argparse exiting 2 on an unknown arg
39
+ EXIT_RELOAD_FAILED = 1
40
+ EXIT_RELOAD_TIMEOUT = 128 + errno.ETIMEDOUT
41
+
42
+
43
+ def validate_manager_pid(pid):
44
+ try:
45
+ with open('/proc/%d/cmdline' % pid, 'r') as fp:
46
+ cmd = fp.read().strip('\x00').split('\x00')
47
+ sid = os.getsid(pid)
48
+ except (IOError, OSError):
49
+ print("Failed to get process information for %s" % pid,
50
+ file=sys.stderr)
51
+ exit(EXIT_BAD_PID)
52
+
53
+ scripts = [os.path.basename(c) for c in cmd
54
+ if '/bin/' in c and '/bin/python' not in c]
55
+
56
+ if len(scripts) != 1 or not scripts[0].startswith("swift-"):
57
+ print("Non-swift process: %r" % ' '.join(cmd), file=sys.stderr)
58
+ exit(EXIT_BAD_PID)
59
+
60
+ if scripts[0] not in {"swift-proxy-server", "swift-account-server",
61
+ "swift-container-server", "swift-object-server"}:
62
+ print("Process does not support config checks: %s" % scripts[0],
63
+ file=sys.stderr)
64
+ exit(EXIT_BAD_PID)
65
+
66
+ if sid != pid:
67
+ print("Process appears to be a %s worker, not a manager. "
68
+ "Did you mean %s?" % (scripts[0], sid), file=sys.stderr)
69
+ exit(EXIT_BAD_PID)
70
+
71
+ return cmd, scripts[0]
72
+
73
+
74
+ def main(args=None):
75
+ parser = argparse.ArgumentParser(__doc__)
76
+ parser.add_argument("pid", type=int,
77
+ help="server PID which should be reloaded")
78
+ wait_group = parser.add_mutually_exclusive_group()
79
+ wait_group.add_argument("-t", "--timeout", type=float, default=300.0,
80
+ help="max time to wait for reload to complete")
81
+ wait_group.add_argument("-w", "--no-wait",
82
+ action="store_false", dest="wait",
83
+ help="skip waiting for reload to complete")
84
+ parser.add_argument("-v", "--verbose", action="store_true",
85
+ help="display more information as the process reloads")
86
+ args = parser.parse_args(args)
87
+
88
+ cmd, script = validate_manager_pid(args.pid)
89
+
90
+ if args.verbose:
91
+ print("Checking config for %s" % script)
92
+ try:
93
+ subprocess.check_call(cmd + ["--test-config"])
94
+ except subprocess.CalledProcessError:
95
+ print("Failed to validate config", file=sys.stderr)
96
+ exit(EXIT_RELOAD_FAILED)
97
+
98
+ if args.wait:
99
+ try:
100
+ original_children = get_child_pids(args.pid)
101
+ children_since_reload = set()
102
+
103
+ if args.verbose:
104
+ print("Sending USR1 signal")
105
+ os.kill(args.pid, signal.SIGUSR1)
106
+
107
+ start = time.time()
108
+ while time.time() - start < args.timeout:
109
+ children = get_child_pids(args.pid)
110
+ new_children = (children - original_children
111
+ - children_since_reload)
112
+ if new_children:
113
+ if args.verbose:
114
+ print("Found new children: %s" % ", ".join(
115
+ str(pid) for pid in new_children))
116
+ children_since_reload |= new_children
117
+ if children_since_reload - children:
118
+ # At least one new child exited; presumably, it was
119
+ # the temporary child waiting to shutdown sockets
120
+ break
121
+ # We want this to be fairly low, since the temporary child
122
+ # may not hang around very long
123
+ time.sleep(0.1)
124
+ else:
125
+ print("Timed out reloading %s" % script, file=sys.stderr)
126
+ exit(EXIT_RELOAD_TIMEOUT)
127
+
128
+ except subprocess.CalledProcessError:
129
+ # This could pop during any of the calls to get_child_pids
130
+ print("Process seems to have died!", file=sys.stderr)
131
+ exit(EXIT_RELOAD_FAILED)
132
+ else: # --no-wait
133
+ if args.verbose:
134
+ print("Sending USR1 signal")
135
+ os.kill(args.pid, signal.SIGUSR1)
136
+
137
+ print("Reloaded %s" % script)
138
+
139
+
140
+ if __name__ == "__main__":
141
+ main()
swift/cli/ringbuilder.py CHANGED
@@ -22,9 +22,11 @@ from itertools import islice
22
22
  from operator import itemgetter
23
23
  from os import mkdir
24
24
  from os.path import basename, abspath, dirname, exists, join as pathjoin
25
+ import sys
25
26
  from sys import argv as sys_argv, exit, stderr, stdout
26
27
  from textwrap import wrap
27
28
  from time import time
29
+ import traceback
28
30
  from datetime import timedelta
29
31
  import optparse
30
32
  import math
@@ -1698,3 +1700,25 @@ def main(arguments=None):
1698
1700
  exit(2)
1699
1701
  else:
1700
1702
  getattr(Commands, command, Commands.unknown)()
1703
+
1704
+
1705
+ def error_handling_main():
1706
+ # We exit code 1 on WARNING statuses, 2 on ERROR. This means we need
1707
+ # to handle any uncaught exceptions by printing the usual backtrace,
1708
+ # but then exiting 2 (not 1 as is usual for a python
1709
+ # exception).
1710
+
1711
+ # We *don't* want to do this in main(), however, because we don't want to
1712
+ # pollute the test environment or cause a bunch of test churn to mock out
1713
+ # sys.excepthook
1714
+
1715
+ def exit_with_status_two(tp, val, tb):
1716
+ traceback.print_exception(tp, val, tb)
1717
+ exit(2)
1718
+
1719
+ sys.excepthook = exit_with_status_two
1720
+ main()
1721
+
1722
+
1723
+ if __name__ == '__main__':
1724
+ error_handling_main()
swift/common/daemon.py CHANGED
@@ -159,7 +159,7 @@ class DaemonStrategy(object):
159
159
  except KeyboardInterrupt:
160
160
  self.logger.notice('User quit')
161
161
  finally:
162
- self.cleanup()
162
+ self.cleanup(stopping=True)
163
163
  self.running = False
164
164
 
165
165
  def _fork(self, once, **kwargs):
@@ -167,6 +167,8 @@ class DaemonStrategy(object):
167
167
  if pid == 0:
168
168
  signal.signal(signal.SIGHUP, signal.SIG_DFL)
169
169
  signal.signal(signal.SIGTERM, signal.SIG_DFL)
170
+ # only MAINPID should be sending notifications
171
+ os.environ.pop('NOTIFY_SOCKET', None)
170
172
 
171
173
  self.daemon.run(once, **kwargs)
172
174
 
@@ -245,7 +247,15 @@ class DaemonStrategy(object):
245
247
  self.daemon.post_multiprocess_run()
246
248
  return 0
247
249
 
248
- def cleanup(self):
250
+ def cleanup(self, stopping=False):
251
+ """
252
+ Cleanup worker processes
253
+
254
+ :param stopping: if set, tell systemd we're stopping
255
+ """
256
+
257
+ if stopping:
258
+ utils.systemd_notify(self.logger, "STOPPING=1")
249
259
  for p in self.spawned_pids():
250
260
  try:
251
261
  os.kill(p, signal.SIGTERM)
swift/common/db.py CHANGED
@@ -138,7 +138,8 @@ class GreenDBConnection(sqlite3.Connection):
138
138
  timeout = BROKER_TIMEOUT
139
139
  self.timeout = timeout
140
140
  self.db_file = database
141
- super(GreenDBConnection, self).__init__(database, 0, *args, **kwargs)
141
+ super(GreenDBConnection, self).__init__(
142
+ database, timeout=0, *args, **kwargs)
142
143
 
143
144
  def cursor(self, cls=None):
144
145
  if cls is None:
@@ -724,22 +725,26 @@ class DatabaseBroker(object):
724
725
  return -1
725
726
  return row['sync_point']
726
727
 
727
- def get_syncs(self, incoming=True):
728
+ def get_syncs(self, incoming=True, include_timestamp=False):
728
729
  """
729
730
  Get a serialized copy of the sync table.
730
731
 
731
732
  :param incoming: if True, get the last incoming sync, otherwise get
732
733
  the last outgoing sync
733
- :returns: list of {'remote_id', 'sync_point'}
734
+ :param include_timestamp: If True include the updated_at timestamp
735
+ :returns: list of {'remote_id', 'sync_point'} or
736
+ {'remote_id', 'sync_point', 'updated_at'}
737
+ if include_timestamp is True.
734
738
  """
735
739
  with self.get() as conn:
740
+ columns = 'remote_id, sync_point'
741
+ if include_timestamp:
742
+ columns += ', updated_at'
736
743
  curs = conn.execute('''
737
- SELECT remote_id, sync_point FROM %s_sync
738
- ''' % ('incoming' if incoming else 'outgoing'))
739
- result = []
740
- for row in curs:
741
- result.append({'remote_id': row[0], 'sync_point': row[1]})
742
- return result
744
+ SELECT %s FROM %s_sync
745
+ ''' % (columns, 'incoming' if incoming else 'outgoing'))
746
+ curs.row_factory = dict_factory
747
+ return [r for r in curs]
743
748
 
744
749
  def get_max_row(self, table=None):
745
750
  if not table:
@@ -96,7 +96,7 @@ class DatabaseAuditor(Daemon):
96
96
  time.sleep(random() * self.interval)
97
97
  while True:
98
98
  self.logger.info(
99
- 'Begin {} audit pass.'.format(self.server_type))
99
+ 'Begin %s audit pass.', self.server_type)
100
100
  begin = time.time()
101
101
  try:
102
102
  reported = self._one_audit_pass(reported)
@@ -116,7 +116,7 @@ class DatabaseAuditor(Daemon):
116
116
  def run_once(self, *args, **kwargs):
117
117
  """Run the database audit once."""
118
118
  self.logger.info(
119
- 'Begin {} audit "once" mode'.format(self.server_type))
119
+ 'Begin %s audit "once" mode', self.server_type)
120
120
  begin = reported = time.time()
121
121
  self._one_audit_pass(reported)
122
122
  elapsed = time.time() - begin
@@ -240,6 +240,12 @@ class Replicator(Daemon):
240
240
  self.handoffs_only = config_true_value(conf.get('handoffs_only', 'no'))
241
241
  self.handoff_delete = config_auto_int_value(
242
242
  conf.get('handoff_delete', 'auto'), 0)
243
+ if self.handoff_delete >= self.ring.replica_count:
244
+ self.logger.warning(
245
+ 'handoff_delete=%d is too high to have an effect on a ring '
246
+ 'with replica count %d. Disabling.',
247
+ self.handoff_delete, self.ring.replica_count)
248
+ self.handoff_delete = 0
243
249
 
244
250
  def _zero_stats(self):
245
251
  """Zero out the stats."""
@@ -243,6 +243,18 @@ class QuarantineRequest(SwiftException):
243
243
  pass
244
244
 
245
245
 
246
+ class MemcacheConnectionError(Exception):
247
+ pass
248
+
249
+
250
+ class MemcacheIncrNotFoundError(MemcacheConnectionError):
251
+ pass
252
+
253
+
254
+ class MemcachePoolTimeout(Timeout):
255
+ pass
256
+
257
+
246
258
  class ClientException(Exception):
247
259
 
248
260
  def __init__(self, msg, http_scheme='', http_host='', http_port='',
@@ -16,15 +16,21 @@
16
16
  from eventlet import wsgi, websocket
17
17
  import six
18
18
 
19
+ from swift.common.utils import generate_trans_id
20
+ from swift.common.http import HTTP_NO_CONTENT, HTTP_RESET_CONTENT, \
21
+ HTTP_NOT_MODIFIED
19
22
 
20
23
  if six.PY2:
21
24
  from eventlet.green import httplib as http_client
25
+ from cgi import escape
22
26
  else:
23
27
  from eventlet.green.http import client as http_client
28
+ from html import escape
24
29
 
25
30
 
26
31
  class SwiftHttpProtocol(wsgi.HttpProtocol):
27
32
  default_request_version = "HTTP/1.0"
33
+ reject_bad_requests = False
28
34
 
29
35
  def __init__(self, *args, **kwargs):
30
36
  # See https://github.com/eventlet/eventlet/pull/590
@@ -52,7 +58,7 @@ class SwiftHttpProtocol(wsgi.HttpProtocol):
52
58
  self.server.log.info('ERROR WSGI: ' + f, *a)
53
59
 
54
60
  class MessageClass(wsgi.HttpProtocol.MessageClass):
55
- '''Subclass to see when the client didn't provide a Content-Type'''
61
+ """Subclass to see when the client didn't provide a Content-Type"""
56
62
  # for py2:
57
63
  def parsetype(self):
58
64
  if self.typeheader is None:
@@ -61,7 +67,7 @@ class SwiftHttpProtocol(wsgi.HttpProtocol):
61
67
 
62
68
  # for py3:
63
69
  def get_default_type(self):
64
- '''If the client didn't provide a content type, leave it blank.'''
70
+ """If the client didn't provide a content type, leave it blank."""
65
71
  return ''
66
72
 
67
73
  def parse_request(self):
@@ -241,6 +247,74 @@ class SwiftHttpProtocol(wsgi.HttpProtocol):
241
247
  self.conn_state[2] = wsgi.STATE_IDLE
242
248
  return got
243
249
 
250
+ def send_error(self, code, message=None, explain=None):
251
+ """Send and log an error reply, we are overriding the cpython parent
252
+ class method, so we can have logger generate txn_id's for error
253
+ response from wsgi since we are at the edge of the proxy server.
254
+ This sends an error response (so it must be called before any output
255
+ has been generated), logs the error, and finally sends a piece of HTML
256
+ explaining the error to the user.
257
+
258
+ :param code: an HTTP error code
259
+ 3 digits
260
+ :param message: a simple optional 1 line reason phrase.
261
+ *( HTAB / SP / VCHAR / %x80-FF )
262
+ defaults to short entry matching the response code
263
+ :param explain: a detailed message defaults to the long entry
264
+ matching the response code.
265
+ """
266
+
267
+ try:
268
+ shortmsg, longmsg = self.responses[code]
269
+ except KeyError:
270
+ shortmsg, longmsg = '???', '???'
271
+ if message is None:
272
+ message = shortmsg
273
+ if explain is None:
274
+ explain = longmsg
275
+
276
+ try:
277
+ # assume we have a LogAdapter
278
+ txn_id = self.server.app.logger.txn_id # just in case it was set
279
+ except AttributeError:
280
+ # turns out we don't have a LogAdapter, so go direct
281
+ txn_id = generate_trans_id('')
282
+ self.log_error("code %d, message %s, (txn: %s)", code,
283
+ message, txn_id)
284
+ else:
285
+ # we do have a LogAdapter, but likely not yet a txn_id
286
+ txn_id = txn_id or generate_trans_id('')
287
+ self.server.app.logger.txn_id = txn_id
288
+ self.log_error("code %d, message %s", code, message)
289
+ self.send_response(code, message)
290
+ self.send_header('Connection', 'close')
291
+
292
+ # Message body is omitted for cases described in:
293
+ # - RFC7230: 3.3. 1xx, 204(No Content), 304(Not Modified)
294
+ # - RFC7231: 6.3.6. 205(Reset Content)
295
+ body = None
296
+ exclude_status = (HTTP_NO_CONTENT,
297
+ HTTP_RESET_CONTENT,
298
+ HTTP_NOT_MODIFIED)
299
+ if (code >= 200 and
300
+ code not in exclude_status):
301
+ # HTML encode to prevent Cross Site Scripting attacks
302
+ # (see bug https://bugs.python.org/issue1100201)
303
+ content = (self.error_message_format % {
304
+ 'code': code,
305
+ 'message': escape(message, quote=False),
306
+ 'explain': escape(explain, quote=False)
307
+ })
308
+ body = content.encode('UTF-8', 'replace')
309
+ self.send_header("Content-Type", self.error_content_type)
310
+ self.send_header('Content-Length', str(len(body)))
311
+ self.send_header('X-Trans-Id', txn_id)
312
+ self.send_header('X-Openstack-Request-Id', txn_id)
313
+ self.end_headers()
314
+
315
+ if self.command != 'HEAD' and body:
316
+ self.wfile.write(body)
317
+
244
318
 
245
319
  class SwiftHttpProxiedProtocol(SwiftHttpProtocol):
246
320
  """
@@ -271,7 +345,6 @@ class SwiftHttpProxiedProtocol(SwiftHttpProtocol):
271
345
  # ourselves and our gateway proxy before processing the client
272
346
  # protocol request. Hopefully the operator will know what to do!
273
347
  msg = 'Invalid PROXY line %r' % connection_line
274
- self.log_message(msg)
275
348
  # Even assuming HTTP we don't even known what version of HTTP the
276
349
  # client is sending? This entire endeavor seems questionable.
277
350
  self.request_version = self.default_request_version
swift/common/manager.py CHANGED
@@ -13,9 +13,11 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
 
16
+
16
17
  from __future__ import print_function
17
18
  import functools
18
19
  import errno
20
+ from optparse import OptionParser
19
21
  import os
20
22
  import resource
21
23
  import signal
@@ -23,8 +25,13 @@ import time
23
25
  import subprocess
24
26
  import re
25
27
  import six
28
+ import sys
26
29
  import tempfile
27
- from distutils.spawn import find_executable
30
+ try:
31
+ from shutil import which
32
+ except ImportError:
33
+ # py2
34
+ from distutils.spawn import find_executable as which
28
35
 
29
36
  from swift.common.utils import search_tree, remove_file, write_file, readconf
30
37
  from swift.common.exceptions import InvalidPidFileException
@@ -176,6 +183,17 @@ def kill_group(pid, sig):
176
183
  os.kill(-pid, sig)
177
184
 
178
185
 
186
+ def get_child_pids(pid):
187
+ """
188
+ Get the current set of all child PIDs for a PID.
189
+
190
+ :param pid: process id
191
+ """
192
+ output = subprocess.check_output(
193
+ ["ps", "--ppid", str(pid), "--no-headers", "-o", "pid"])
194
+ return {int(pid) for pid in output.split()}
195
+
196
+
179
197
  def format_server_name(servername):
180
198
  """
181
199
  Formats server name as swift compatible server names
@@ -204,7 +222,7 @@ def verify_server(server):
204
222
  if not server:
205
223
  return False
206
224
  _, cmd = format_server_name(server)
207
- if find_executable(cmd) is None:
225
+ if which(cmd) is None:
208
226
  return False
209
227
  return True
210
228
 
@@ -696,9 +714,7 @@ class Server(object):
696
714
  print('Removing pid file %s with invalid pid' % pid_file)
697
715
  remove_file(pid_file)
698
716
  continue
699
- ps_cmd = ['ps', '--ppid', str(pid), '--no-headers', '-o', 'pid']
700
- for pid in subprocess.check_output(ps_cmd).split():
701
- pid = int(pid)
717
+ for pid in get_child_pids(pid):
702
718
  if self._signal_pid(sig, pid, pid_file, kwargs.get('verbose')):
703
719
  pids[pid] = pid_file
704
720
  return pids
@@ -923,3 +939,102 @@ class Server(object):
923
939
 
924
940
  """
925
941
  return self.kill_running_pids(**kwargs)
942
+
943
+
944
+ USAGE = \
945
+ """%prog <server>[.<config>] [<server>[.<config>] ...] <command> [options]
946
+
947
+ where:
948
+ <server> is the name of a swift service e.g. proxy-server.
949
+ The '-server' part of the name may be omitted.
950
+ 'all', 'main' and 'rest' are reserved words that represent a
951
+ group of services.
952
+ all: Expands to all swift daemons.
953
+ main: Expands to main swift daemons.
954
+ (proxy, container, account, object)
955
+ rest: Expands to all remaining background daemons (beyond
956
+ "main").
957
+ (updater, replicator, auditor, etc)
958
+ <config> is an explicit configuration filename without the
959
+ .conf extension. If <config> is specified then <server> should
960
+ refer to a directory containing the configuration file, e.g.:
961
+
962
+ swift-init object.1 start
963
+
964
+ will start an object-server using the configuration file
965
+ /etc/swift/object-server/1.conf
966
+ <command> is a command from the list below.
967
+
968
+ Commands:
969
+ """ + '\n'.join(["%16s: %s" % x for x in Manager.list_commands()])
970
+
971
+
972
+ def main():
973
+ parser = OptionParser(USAGE)
974
+ parser.add_option('-v', '--verbose', action="store_true",
975
+ default=False, help="display verbose output")
976
+ parser.add_option('-w', '--no-wait', action="store_false", dest="wait",
977
+ default=True, help="won't wait for server to start "
978
+ "before returning")
979
+ parser.add_option('-o', '--once', action="store_true",
980
+ default=False, help="only run one pass of daemon")
981
+ # this is a negative option, default is options.daemon = True
982
+ parser.add_option('-n', '--no-daemon', action="store_false", dest="daemon",
983
+ default=True, help="start server interactively")
984
+ parser.add_option('-g', '--graceful', action="store_true",
985
+ default=False, help="send SIGHUP to supporting servers")
986
+ parser.add_option('-c', '--config-num', metavar="N", type="int",
987
+ dest="number", default=0,
988
+ help="send command to the Nth server only")
989
+ parser.add_option('-k', '--kill-wait', metavar="N", type="int",
990
+ dest="kill_wait", default=KILL_WAIT,
991
+ help="wait N seconds for processes to die (default 15)")
992
+ parser.add_option('-r', '--run-dir', type="str",
993
+ dest="run_dir", default=RUN_DIR,
994
+ help="alternative directory to store running pid files "
995
+ "default: %s" % RUN_DIR)
996
+ # Changing behaviour if missing config
997
+ parser.add_option('--strict', dest='strict', action='store_true',
998
+ help="Return non-zero status code if some config is "
999
+ "missing. Default mode if all servers are "
1000
+ "explicitly named.")
1001
+ # a negative option for strict
1002
+ parser.add_option('--non-strict', dest='strict', action='store_false',
1003
+ help="Return zero status code even if some config is "
1004
+ "missing. Default mode if any server is a glob or "
1005
+ "one of aliases `all`, `main` or `rest`.")
1006
+ # SIGKILL daemon after kill_wait period
1007
+ parser.add_option('--kill-after-timeout', dest='kill_after_timeout',
1008
+ action='store_true',
1009
+ help="Kill daemon and all children after kill-wait "
1010
+ "period.")
1011
+
1012
+ options, args = parser.parse_args()
1013
+
1014
+ if len(args) < 2:
1015
+ parser.print_help()
1016
+ print('ERROR: specify server(s) and command')
1017
+ return 1
1018
+
1019
+ command = args[-1]
1020
+ servers = args[:-1]
1021
+
1022
+ # this is just a silly swap for me cause I always try to "start main"
1023
+ commands = dict(Manager.list_commands()).keys()
1024
+ if command not in commands and servers[0] in commands:
1025
+ servers.append(command)
1026
+ command = servers.pop(0)
1027
+
1028
+ manager = Manager(servers, run_dir=options.run_dir)
1029
+ try:
1030
+ status = manager.run_command(command, **options.__dict__)
1031
+ except UnknownCommandError:
1032
+ parser.print_help()
1033
+ print('ERROR: unknown command, %s' % command)
1034
+ status = 1
1035
+
1036
+ return 1 if status else 0
1037
+
1038
+
1039
+ if __name__ == "__main__":
1040
+ sys.exit(main())