Skip to content

Commit

Permalink
Ring checker in swift-recon
Browse files Browse the repository at this point in the history
This patch validates the server end points on the ring. And also generates
a report on issues found.

Change-Id: I913799a35d5c9178164021cfb7fcb448141b058b
  • Loading branch information
MahatiC committed Feb 25, 2015
1 parent 9196c31 commit a248a5c
Show file tree
Hide file tree
Showing 2 changed files with 168 additions and 0 deletions.
56 changes: 56 additions & 0 deletions swift/cli/recon.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,34 @@ def scout(self, host):
url, content, status = self.scout_host(base_url, self.recon_type)
return url, content, status

def scout_server_type(self, host):
"""
Obtain Server header by calling OPTIONS.
:param host: host to check
:returns: Server type, status
"""
try:
url = "http://%s:%s/" % (host[0], host[1])
req = urllib2.Request(url)
req.get_method = lambda: 'OPTIONS'
conn = urllib2.urlopen(req)
header = conn.info().getheader('Server')
server_header = header.split('/')
content = server_header[0]
status = 200
except urllib2.HTTPError as err:
if not self.suppress_errors or self.verbose:
print("-> %s: %s" % (url, err))
content = err
status = err.code
except urllib2.URLError as err:
if not self.suppress_errors or self.verbose:
print("-> %s: %s" % (url, err))
content = err
status = -1
return url, content, status


class SwiftRecon(object):
"""
Expand Down Expand Up @@ -334,6 +362,29 @@ def umount_check(self, hosts):
print("Device errors: %s on %s" % (entry, node))
print("=" * 79)

def server_type_check(self, hosts):
"""
Check for server types on the ring
:param hosts: set of hosts to check. in the format of:
set([('127.0.0.1', 6020), ('127.0.0.2', 6030)])
"""
errors = {}
recon = Scout("server_type_check", self.verbose, self.suppress_errors,
self.timeout)
print("[%s] Validating server type '%s' on %s hosts..." %
(self._ptime(), self.server_type, len(hosts)))
for url, response, status in self.pool.imap(
recon.scout_server_type, hosts):
if status == 200:
if response != self.server_type + '-server':
errors[url] = response
print("%s/%s hosts ok, %s error[s] while checking hosts." % (
len(hosts) - len(errors), len(hosts), len(errors)))
for host in errors:
print("Invalid: %s is %s" % (host, errors[host]))
print("=" * 79)

def expirer_check(self, hosts):
"""
Obtain and print expirer statistics
Expand Down Expand Up @@ -872,6 +923,8 @@ def main(self):
help="Get cluster load average stats")
args.add_option('--quarantined', '-q', action="store_true",
help="Get cluster quarantine stats")
args.add_option('--validate-servers', action="store_true",
help="Validate servers on the ring")
args.add_option('--md5', action="store_true",
help="Get md5sum of servers ring and compare to "
"local copy")
Expand Down Expand Up @@ -938,6 +991,7 @@ def main(self):
self.get_ringmd5(hosts, swift_dir)
self.quarantine_check(hosts)
self.socket_usage(hosts)
self.server_type_check(hosts)
else:
if options.async:
if self.server_type == 'object':
Expand Down Expand Up @@ -966,6 +1020,8 @@ def main(self):
self.expirer_check(hosts)
else:
print("Error: Can't check expired on non object servers.")
if options.validate_servers:
self.server_type_check(hosts)
if options.loadstats:
self.load_check(hosts)
if options.diskusage:
Expand Down
112 changes: 112 additions & 0 deletions test/unit/cli/test_recon.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class TestScout(unittest.TestCase):
def setUp(self, *_args, **_kwargs):
self.scout_instance = recon.Scout("type", suppress_errors=True)
self.url = 'http://127.0.0.1:8080/recon/type'
self.server_type_url = 'http://127.0.0.1:8080/'

@mock.patch('eventlet.green.urllib2.urlopen')
def test_scout_ok(self, mock_urlopen):
Expand Down Expand Up @@ -85,6 +86,37 @@ def test_scout_http_error(self, mock_urlopen):
self.assertTrue(isinstance(content, urllib2.HTTPError))
self.assertEqual(status, 404)

@mock.patch('eventlet.green.urllib2.urlopen')
def test_scout_server_type_ok(self, mock_urlopen):
def getheader(name):
d = {'Server': 'server-type'}
return d.get(name)
mock_urlopen.return_value.info.return_value.getheader = getheader
url, content, status = self.scout_instance.scout_server_type(
("127.0.0.1", "8080"))
self.assertEqual(url, self.server_type_url)
self.assertEqual(content, 'server-type')
self.assertEqual(status, 200)

@mock.patch('eventlet.green.urllib2.urlopen')
def test_scout_server_type_url_error(self, mock_urlopen):
mock_urlopen.side_effect = urllib2.URLError("")
url, content, status = self.scout_instance.scout_server_type(
("127.0.0.1", "8080"))
self.assertTrue(isinstance(content, urllib2.URLError))
self.assertEqual(url, self.server_type_url)
self.assertEqual(status, -1)

@mock.patch('eventlet.green.urllib2.urlopen')
def test_scout_server_type_http_error(self, mock_urlopen):
mock_urlopen.side_effect = urllib2.HTTPError(
self.server_type_url, 404, "Internal error", None, None)
url, content, status = self.scout_instance.scout_server_type(
("127.0.0.1", "8080"))
self.assertEqual(url, self.server_type_url)
self.assertTrue(isinstance(content, urllib2.HTTPError))
self.assertEqual(status, 404)


class TestRecon(unittest.TestCase):
def setUp(self, *_args, **_kwargs):
Expand Down Expand Up @@ -289,6 +321,86 @@ def fake_urlopen(url, timeout):

return mock.patch('eventlet.green.urllib2.urlopen', fake_urlopen)

def test_server_type_check(self):
hosts = [('127.0.0.1', 6010), ('127.0.0.1', 6011),
('127.0.0.1', 6012)]

# sample json response from http://<host>:<port>/
responses = {6010: 'object-server', 6011: 'container-server',
6012: 'account-server'}

def mock_scout_server_type(app, host):
url = 'http://%s:%s/' % (host[0], host[1])
response = responses[host[1]]
status = 200
return url, response, status

stdout = StringIO()
patches = [
mock.patch('swift.cli.recon.Scout.scout_server_type',
mock_scout_server_type),
mock.patch('sys.stdout', new=stdout),
]

res_object = 'Invalid: http://127.0.0.1:6010/ is object-server'
res_container = 'Invalid: http://127.0.0.1:6011/ is container-server'
res_account = 'Invalid: http://127.0.0.1:6012/ is account-server'
valid = "1/1 hosts ok, 0 error[s] while checking hosts."

#Test for object server type - default
with nested(*patches):
self.recon.server_type_check(hosts)

output = stdout.getvalue()
self.assertTrue(res_container in output.splitlines())
self.assertTrue(res_account in output.splitlines())
stdout.truncate(0)

#Test ok for object server type - default
with nested(*patches):
self.recon.server_type_check([hosts[0]])

output = stdout.getvalue()
self.assertTrue(valid in output.splitlines())
stdout.truncate(0)

#Test for account server type
with nested(*patches):
self.recon.server_type = 'account'
self.recon.server_type_check(hosts)

output = stdout.getvalue()
self.assertTrue(res_container in output.splitlines())
self.assertTrue(res_object in output.splitlines())
stdout.truncate(0)

#Test ok for account server type
with nested(*patches):
self.recon.server_type = 'account'
self.recon.server_type_check([hosts[2]])

output = stdout.getvalue()
self.assertTrue(valid in output.splitlines())
stdout.truncate(0)

#Test for container server type
with nested(*patches):
self.recon.server_type = 'container'
self.recon.server_type_check(hosts)

output = stdout.getvalue()
self.assertTrue(res_account in output.splitlines())
self.assertTrue(res_object in output.splitlines())
stdout.truncate(0)

#Test ok for container server type
with nested(*patches):
self.recon.server_type = 'container'
self.recon.server_type_check([hosts[1]])

output = stdout.getvalue()
self.assertTrue(valid in output.splitlines())

def test_get_swiftconfmd5(self):
hosts = set([('10.1.1.1', 10000),
('10.2.2.2', 10000)])
Expand Down

0 comments on commit a248a5c

Please sign in to comment.