# Copyright (c) 2010-2012 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import array
from contextlib import contextmanager
import errno
import json
import mock
import os
from posix import stat_result, statvfs_result
from shutil import rmtree
import tempfile
import time
import unittest
from unittest import TestCase

from swift import __version__ as swiftver
from swift.common import ring, utils
from swift.common.recon import RECON_RELINKER_FILE, RECON_DRIVE_FILE, \
    DEFAULT_RECON_CACHE_PATH, server_type_to_recon_file
from swift.common.swob import Request
from swift.common.middleware import recon
from swift.common.storage_policy import StoragePolicy
from test.debug_logger import debug_logger
from test.unit import patch_policies


def fake_check_mount(a, b):
    raise OSError('Input/Output Error')


def fail_os_listdir():
    raise OSError('No such file or directory')


def fail_io_open(file_path, open_mode):
    raise IOError('No such file or directory')


class FakeApp(object):
    def __call__(self, env, start_response):
        return b"FAKE APP"


def start_response(*args):
    pass


class FakeFromCache(object):

    def __init__(self, out=None):
        self.fakeout = out
        self.fakeout_calls = []

    def fake_from_recon_cache(self, *args, **kwargs):
        self.fakeout_calls.append((args, kwargs))
        return self.fakeout


class OpenAndReadTester(object):

    def __init__(self, output_iter):
        self.index = 0
        self.out_len = len(output_iter) - 1
        self.data = output_iter
        self.output_iter = iter(output_iter)
        self.read_calls = []
        self.open_calls = []

    def __iter__(self):
        return self

    def __next__(self):
        if self.index == self.out_len:
            raise StopIteration
        else:
            line = self.data[self.index]
            self.index += 1
        return line

    next = __next__

    def read(self, *args, **kwargs):
        self.read_calls.append((args, kwargs))
        try:
            return next(self.output_iter)
        except StopIteration:
            return ''

    @contextmanager
    def open(self, *args, **kwargs):
        self.open_calls.append((args, kwargs))
        yield self


class MockOS(object):

    def __init__(self, ls_out=None, isdir_out=None, ismount_out=False,
                 statvfs_out=None):
        self.ls_output = ls_out
        self.isdir_output = isdir_out
        self.ismount_output = ismount_out
        self.statvfs_output = statvfs_out
        self.listdir_calls = []
        self.isdir_calls = []
        self.ismount_calls = []
        self.statvfs_calls = []

    def fake_listdir(self, *args, **kwargs):
        self.listdir_calls.append((args, kwargs))
        return self.ls_output

    def fake_isdir(self, *args, **kwargs):
        self.isdir_calls.append((args, kwargs))
        return self.isdir_output

    def fake_ismount(self, *args, **kwargs):
        self.ismount_calls.append((args, kwargs))
        if isinstance(self.ismount_output, Exception):
            raise self.ismount_output
        else:
            return self.ismount_output

    def fake_statvfs(self, *args, **kwargs):
        self.statvfs_calls.append((args, kwargs))
        return statvfs_result(self.statvfs_output)


class FakeRecon(object):

    def __init__(self):
        self.fake_replication_rtype = None
        self.fake_updater_rtype = None
        self.fake_auditor_rtype = None
        self.fake_expirer_rtype = None

    def fake_mem(self):
        return {'memtest': "1"}

    def fake_load(self):
        return {'loadtest': "1"}

    def fake_async(self):
        return {'asynctest': "1"}

    def fake_get_device_info(self):
        return {"/srv/1/node": ["sdb1"]}

    def fake_replication(self, recon_type):
        self.fake_replication_rtype = recon_type
        return {'replicationtest': "1"}

    def fake_sharding(self):
        return {"sharding_stats": "1"}

    def fake_relinker(self):
        return {"relinktest": "1"}

    def fake_reconstruction(self):
        return {'reconstructiontest': "1"}

    def fake_updater(self, recon_type):
        self.fake_updater_rtype = recon_type
        return {'updatertest': "1"}

    def fake_auditor(self, recon_type):
        self.fake_auditor_rtype = recon_type
        return {'auditortest': "1"}

    def fake_expirer(self, recon_type):
        self.fake_expirer_rtype = recon_type
        return {'expirertest': "1"}

    def fake_mounted(self):
        return {'mountedtest': "1"}

    def fake_unmounted(self):
        return {'unmountedtest': "1"}

    def fake_unmounted_empty(self):
        return []

    def fake_diskusage(self):
        return {'diskusagetest': "1"}

    def fake_ringmd5(self):
        return {'ringmd5test': "1"}

    def fake_swiftconfmd5(self):
        return {'/etc/swift/swift.conf': "abcdef"}

    def fake_quarantined(self):
        return {'quarantinedtest': "1"}

    def fake_sockstat(self):
        return {'sockstattest': "1"}

    def fake_driveaudit(self):
        return {'driveaudittest': "1"}

    def fake_time(self):
        return {'timetest': "1"}

    def nocontent(self):
        return None

    def raise_IOError(self, errno=None):
        mock_obj = mock.MagicMock()
        mock_obj.side_effect = IOError(errno, str(errno))
        return mock_obj

    def raise_ValueError(self, *args, **kwargs):
        raise ValueError

    def raise_Exception(self, *args, **kwargs):
        raise Exception


@patch_policies(legacy_only=True)
class TestReconSuccess(TestCase):

    def setUp(self):
        self.tempdir = tempfile.mkdtemp(prefix='swift_recon_md5_test')
        utils.mkdirs(self.tempdir)
        self.app = self._get_app()
        self.mockos = MockOS()
        self.fakecache = FakeFromCache()
        self.real_listdir = os.listdir
        self.real_isdir = os.path.isdir
        self.real_ismount = utils.ismount
        self.real_statvfs = os.statvfs
        os.listdir = self.mockos.fake_listdir
        os.path.isdir = self.mockos.fake_isdir
        utils.ismount = self.mockos.fake_ismount
        os.statvfs = self.mockos.fake_statvfs
        self.real_from_cache = self.app._from_recon_cache
        self.app._from_recon_cache = self.fakecache.fake_from_recon_cache
        self.frecon = FakeRecon()
        self.app.logger = debug_logger()

        # replace hash md5 implementation of the md5_hash_for_file function
        mock_hash_for_file = mock.patch(
            'swift.common.middleware.recon.md5_hash_for_file',
            lambda f, **kwargs: 'hash-' + os.path.basename(f))
        self.addCleanup(mock_hash_for_file.stop)
        mock_hash_for_file.start()

        self.ring_part_shift = 5
        self.ring_devs = [{'id': 0, 'zone': 0, 'weight': 1.0,
                           'ip': '10.1.1.1', 'port': 6200,
                           'device': 'sda1'},
                          {'id': 1, 'zone': 0, 'weight': 1.0,
                           'ip': '10.1.1.1', 'port': 6200,
                           'device': 'sdb1'},
                          None,
                          {'id': 3, 'zone': 2, 'weight': 1.0,
                           'ip': '10.1.2.1', 'port': 6200,
                           'device': 'sdc1'},
                          {'id': 4, 'zone': 2, 'weight': 1.0,
                           'ip': '10.1.2.2', 'port': 6200,
                           'device': 'sdd1'}]
        self._create_rings()

    def tearDown(self):
        os.listdir = self.real_listdir
        os.path.isdir = self.real_isdir
        utils.ismount = self.real_ismount
        os.statvfs = self.real_statvfs
        del self.mockos
        self.app._from_recon_cache = self.real_from_cache
        del self.fakecache
        rmtree(self.tempdir)

    def _get_app(self):
        app = recon.ReconMiddleware(FakeApp(), {'swift_dir': self.tempdir})
        return app

    def _create_ring(self, ringpath, replica_map, devs, part_shift):
        ring.RingData(replica_map, devs, part_shift).save(ringpath,
                                                          mtime=None)

    def _create_rings(self):
        # make the rings unique so they have different md5 sums
        rings = {
            'account.ring.gz': [
                array.array('H', [3, 1, 3, 1]),
                array.array('H', [0, 3, 1, 4]),
                array.array('H', [1, 4, 0, 3])],
            'container.ring.gz': [
                array.array('H', [4, 3, 0, 1]),
                array.array('H', [0, 1, 3, 4]),
                array.array('H', [3, 4, 0, 1])],
            'object.ring.gz': [
                array.array('H', [0, 1, 0, 1]),
                array.array('H', [0, 1, 0, 1]),
                array.array('H', [3, 4, 3, 4])],
            'object-1.ring.gz': [
                array.array('H', [1, 0, 1, 0]),
                array.array('H', [1, 0, 1, 0]),
                array.array('H', [4, 3, 4, 3])],
            'object-2.ring.gz': [
                array.array('H', [1, 1, 1, 0]),
                array.array('H', [1, 0, 1, 3]),
                array.array('H', [4, 2, 4, 3])]
        }

        for ringfn, replica_map in rings.items():
            ringpath = os.path.join(self.tempdir, ringfn)
            self._create_ring(ringpath, replica_map, self.ring_devs,
                              self.ring_part_shift)

    def _full_recon_path(self, server_type, recon_file=None):
        if server_type:
            recon_file = server_type_to_recon_file(server_type)
        return os.path.join(DEFAULT_RECON_CACHE_PATH, recon_file)

    @patch_policies([
        StoragePolicy(0, 'stagecoach'),
        StoragePolicy(1, 'pinto', is_deprecated=True),
        StoragePolicy(2, 'toyota', is_default=True),
    ])
    def test_get_ring_md5(self):
        # We should only see configured and present rings, so to handle the
        # "normal" case just patch the policies to match the existing rings.
        expt_out = {'%s/account.ring.gz' % self.tempdir:
                    'hash-account.ring.gz',
                    '%s/container.ring.gz' % self.tempdir:
                    'hash-container.ring.gz',
                    '%s/object.ring.gz' % self.tempdir:
                    'hash-object.ring.gz',
                    '%s/object-1.ring.gz' % self.tempdir:
                    'hash-object-1.ring.gz',
                    '%s/object-2.ring.gz' % self.tempdir:
                    'hash-object-2.ring.gz'}

        # We need to instantiate app after overriding the configured policies.
        app = self._get_app()
        # object-{1,2}.ring.gz should both appear as they are present on disk
        # and were configured as policies.
        self.assertEqual(sorted(app.get_ring_md5().items()),
                         sorted(expt_out.items()))

    def test_get_ring_md5_ioerror_produces_none_hash(self):
        # Ring files that are present but produce an IOError on read should
        # still produce a ringmd5 entry with a None for the hash. Note that
        # this is different than if an expected ring file simply doesn't exist,
        # in which case it is excluded altogether from the ringmd5 response.
        expt_out = {'%s/account.ring.gz' % self.tempdir: None,
                    '%s/container.ring.gz' % self.tempdir: None,
                    '%s/object.ring.gz' % self.tempdir: None}
        with mock.patch('swift.common.middleware.recon.md5_hash_for_file',
                        side_effect=IOError):
            ringmd5 = self.app.get_ring_md5()
        self.assertEqual(sorted(ringmd5.items()),
                         sorted(expt_out.items()))

    def test_get_ring_md5_failed_ring_hash_recovers_without_restart(self):
        # Ring files that are present but produce an IOError on read will
        # show a None hash, but if they can be read later their hash
        # should become available in the ringmd5 response.
        expt_out = {'%s/account.ring.gz' % self.tempdir: None,
                    '%s/container.ring.gz' % self.tempdir: None,
                    '%s/object.ring.gz' % self.tempdir: None}
        with mock.patch('swift.common.middleware.recon.md5_hash_for_file',
                        side_effect=IOError):
            ringmd5 = self.app.get_ring_md5()
        self.assertEqual(sorted(ringmd5.items()),
                         sorted(expt_out.items()))

        # If we fix a ring and it can be read again, its hash should then
        # appear using the same app instance
        def fake_hash_for_file(fn):
            if 'object' not in fn:
                raise IOError
            return 'hash-' + os.path.basename(fn)

        expt_out = {'%s/account.ring.gz' % self.tempdir: None,
                    '%s/container.ring.gz' % self.tempdir: None,
                    '%s/object.ring.gz' % self.tempdir:
                    'hash-object.ring.gz'}

        with mock.patch('swift.common.middleware.recon.md5_hash_for_file',
                        fake_hash_for_file):
            ringmd5 = self.app.get_ring_md5()
        self.assertEqual(sorted(ringmd5.items()),
                         sorted(expt_out.items()))

    @patch_policies([
        StoragePolicy(0, 'stagecoach'),
        StoragePolicy(2, 'bike', is_default=True),
        StoragePolicy(3502, 'train')
    ])
    def test_get_ring_md5_missing_ring_recovers_without_restart(self):
        # If a configured ring is missing when the app is instantiated, but is
        # later moved into place, we shouldn't need to restart object-server
        # for it to appear in recon.
        expt_out = {'%s/account.ring.gz' % self.tempdir:
                    'hash-account.ring.gz',
                    '%s/container.ring.gz' % self.tempdir:
                    'hash-container.ring.gz',
                    '%s/object.ring.gz' % self.tempdir:
                    'hash-object.ring.gz',
                    '%s/object-2.ring.gz' % self.tempdir:
                    'hash-object-2.ring.gz'}

        # We need to instantiate app after overriding the configured policies.
        app = self._get_app()
        # object-1.ring.gz should not appear as it's present but unconfigured.
        # object-3502.ring.gz should not appear as it's configured but not
        # (yet) present.
        self.assertEqual(sorted(app.get_ring_md5().items()),
                         sorted(expt_out.items()))

        # Simulate the configured policy's missing ringfile being moved into
        # place during runtime
        ringfn = 'object-3502.ring.gz'
        ringpath = os.path.join(self.tempdir, ringfn)
        ringmap = [array.array('H', [1, 2, 1, 4]),
                   array.array('H', [4, 0, 1, 3]),
                   array.array('H', [1, 1, 0, 3])]
        self._create_ring(os.path.join(self.tempdir, ringfn),
                          ringmap, self.ring_devs, self.ring_part_shift)
        expt_out[ringpath] = 'hash-' + ringfn

        # We should now see it in the ringmd5 response, without a restart
        # (using the same app instance)
        self.assertEqual(sorted(app.get_ring_md5().items()),
                         sorted(expt_out.items()))

    @patch_policies([
        StoragePolicy(0, 'stagecoach', is_default=True),
        StoragePolicy(2, 'bike'),
        StoragePolicy(2305, 'taxi')
    ])
    def test_get_ring_md5_excludes_configured_missing_obj_rings(self):
        # Object rings that are configured but missing aren't meant to appear
        # in the ringmd5 response.
        expt_out = {'%s/account.ring.gz' % self.tempdir:
                    'hash-account.ring.gz',
                    '%s/container.ring.gz' % self.tempdir:
                    'hash-container.ring.gz',
                    '%s/object.ring.gz' % self.tempdir:
                    'hash-object.ring.gz',
                    '%s/object-2.ring.gz' % self.tempdir:
                    'hash-object-2.ring.gz'}

        # We need to instantiate app after overriding the configured policies.
        app = self._get_app()
        # object-1.ring.gz should not appear as it's present but unconfigured.
        # object-2305.ring.gz should not appear as it's configured but not
        # present.
        self.assertEqual(sorted(app.get_ring_md5().items()),
                         sorted(expt_out.items()))

    @patch_policies([
        StoragePolicy(0, 'zero', is_default=True),
    ])
    def test_get_ring_md5_excludes_unconfigured_present_obj_rings(self):
        # Object rings that are present but not configured in swift.conf
        # aren't meant to appear in the ringmd5 response.
        expt_out = {'%s/account.ring.gz' % self.tempdir:
                    'hash-account.ring.gz',
                    '%s/container.ring.gz' % self.tempdir:
                    'hash-container.ring.gz',
                    '%s/object.ring.gz' % self.tempdir:
                    'hash-object.ring.gz'}

        # We need to instantiate app after overriding the configured policies.
        app = self._get_app()
        # object-{1,2}.ring.gz should not appear as they are present on disk
        # but were not configured as policies.
        self.assertEqual(sorted(app.get_ring_md5().items()),
                         sorted(expt_out.items()))

    def test_from_recon_cache(self):
        oart = OpenAndReadTester(['{"notneeded": 5, "testkey1": "canhazio"}'])
        self.app._from_recon_cache = self.real_from_cache
        rv = self.app._from_recon_cache(['testkey1', 'notpresentkey'],
                                        'test.cache', openr=oart.open)
        self.assertEqual(oart.read_calls, [((), {})])
        self.assertEqual(oart.open_calls, [(('test.cache', 'r'), {})])
        self.assertEqual(rv, {'notpresentkey': None, 'testkey1': 'canhazio'})
        self.app._from_recon_cache = self.fakecache.fake_from_recon_cache

    def test_from_recon_cache_ioerror(self):
        oart = self.frecon.raise_IOError()
        self.app._from_recon_cache = self.real_from_cache
        rv = self.app._from_recon_cache(['testkey1', 'notpresentkey'],
                                        'test.cache', openr=oart)
        self.assertEqual(rv, {'notpresentkey': None, 'testkey1': None})
        self.assertIn('Error reading recon cache file: ',
                      self.app.logger.get_lines_for_level('error'))
        # Now try with ignore_missing but not ENOENT
        self.app.logger.clear()
        rv = self.app._from_recon_cache(['testkey1', 'notpresentkey'],
                                        'test.cache', openr=oart,
                                        ignore_missing=True)
        self.assertEqual(rv, {'notpresentkey': None, 'testkey1': None})
        self.assertIn('Error reading recon cache file: ',
                      self.app.logger.get_lines_for_level('error'))
        # Now try again with ignore_missing with ENOENT
        self.app.logger.clear()
        oart = self.frecon.raise_IOError(errno.ENOENT)
        rv = self.app._from_recon_cache(['testkey1', 'notpresentkey'],
                                        'test.cache', openr=oart,
                                        ignore_missing=True)
        self.assertEqual(rv, {'notpresentkey': None, 'testkey1': None})
        self.assertEqual(self.app.logger.get_lines_for_level('error'), [])
        self.app._from_recon_cache = self.fakecache.fake_from_recon_cache

    def test_from_recon_cache_valueerror(self):
        oart = self.frecon.raise_ValueError
        self.app._from_recon_cache = self.real_from_cache
        rv = self.app._from_recon_cache(['testkey1', 'notpresentkey'],
                                        'test.cache', openr=oart)
        self.assertEqual(rv, {'notpresentkey': None, 'testkey1': None})
        self.assertIn('Error parsing recon cache file: ',
                      self.app.logger.get_lines_for_level('error'))
        self.app._from_recon_cache = self.fakecache.fake_from_recon_cache

    def test_from_recon_cache_exception(self):
        oart = self.frecon.raise_Exception
        self.app._from_recon_cache = self.real_from_cache
        rv = self.app._from_recon_cache(['testkey1', 'notpresentkey'],
                                        'test.cache', openr=oart)
        self.assertEqual(rv, {'notpresentkey': None, 'testkey1': None})
        self.assertIn('Error retrieving recon data: ',
                      self.app.logger.get_lines_for_level('error'))
        self.app._from_recon_cache = self.fakecache.fake_from_recon_cache

    def test_get_mounted(self):
        mounts_content = [
            'rootfs / rootfs rw 0 0',
            'none /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0',
            'none /proc proc rw,nosuid,nodev,noexec,relatime 0 0',
            'none /dev devtmpfs rw,relatime,size=248404k,nr_inodes=62101,'
            'mode=755 0 0',
            'none /dev/pts devpts rw,nosuid,noexec,relatime,gid=5,mode=620,'
            'ptmxmode=000 0 0',
            '/dev/disk/by-uuid/e5b143bd-9f31-49a7-b018-5e037dc59252 / ext4'
            ' rw,relatime,errors=remount-ro,barrier=1,data=ordered 0 0',
            'none /sys/fs/fuse/connections fusectl rw,relatime 0 0',
            'none /sys/kernel/debug debugfs rw,relatime 0 0',
            'none /sys/kernel/security securityfs rw,relatime 0 0',
            'none /dev/shm tmpfs rw,nosuid,nodev,relatime 0 0',
            'none /var/run tmpfs rw,nosuid,relatime,mode=755 0 0',
            'none /var/lock tmpfs rw,nosuid,nodev,noexec,relatime 0 0',
            'none /lib/init/rw tmpfs rw,nosuid,relatime,mode=755 0 0',
            '/dev/loop0 /mnt/sdb1 xfs rw,noatime,nodiratime,attr2,'
            'logbufs=8,noquota 0 0',
            'rpc_pipefs /var/lib/nfs/rpc_pipefs rpc_pipefs rw,relatime 0 0',
            'nfsd /proc/fs/nfsd nfsd rw,relatime 0 0',
            'none /proc/fs/vmblock/mountPoint vmblock rw,relatime 0 0',
            '']
        mounted_resp = [
            {'device': 'rootfs', 'path': '/'},
            {'device': 'none', 'path': '/sys'},
            {'device': 'none', 'path': '/proc'},
            {'device': 'none', 'path': '/dev'},
            {'device': 'none', 'path': '/dev/pts'},
            {'device': '/dev/disk/by-uuid/'
             'e5b143bd-9f31-49a7-b018-5e037dc59252', 'path': '/'},
            {'device': 'none', 'path': '/sys/fs/fuse/connections'},
            {'device': 'none', 'path': '/sys/kernel/debug'},
            {'device': 'none', 'path': '/sys/kernel/security'},
            {'device': 'none', 'path': '/dev/shm'},
            {'device': 'none', 'path': '/var/run'},
            {'device': 'none', 'path': '/var/lock'},
            {'device': 'none', 'path': '/lib/init/rw'},
            {'device': '/dev/loop0', 'path': '/mnt/sdb1'},
            {'device': 'rpc_pipefs', 'path': '/var/lib/nfs/rpc_pipefs'},
            {'device': 'nfsd', 'path': '/proc/fs/nfsd'},
            {'device': 'none', 'path': '/proc/fs/vmblock/mountPoint'}]
        oart = OpenAndReadTester(mounts_content)
        rv = self.app.get_mounted(openr=oart.open)
        self.assertEqual(oart.open_calls, [(('/proc/mounts', 'r'), {})])
        self.assertEqual(rv, mounted_resp)

    def test_get_load(self):
        oart = OpenAndReadTester(['0.03 0.03 0.00 1/220 16306'])
        rv = self.app.get_load(openr=oart.open)
        self.assertEqual(oart.read_calls, [((), {})])
        self.assertEqual(oart.open_calls, [(('/proc/loadavg', 'r'), {})])
        self.assertEqual(rv, {'5m': 0.029999999999999999, '15m': 0.0,
                              'processes': 16306, 'tasks': '1/220',
                              '1m': 0.029999999999999999})

    def test_get_mem(self):
        meminfo_content = ['MemTotal:         505840 kB',
                           'MemFree:           26588 kB',
                           'Buffers:           44948 kB',
                           'Cached:           146376 kB',
                           'SwapCached:        14736 kB',
                           'Active:           194900 kB',
                           'Inactive:         193412 kB',
                           'Active(anon):      94208 kB',
                           'Inactive(anon):   102848 kB',
                           'Active(file):     100692 kB',
                           'Inactive(file):    90564 kB',
                           'Unevictable:           0 kB',
                           'Mlocked:               0 kB',
                           'SwapTotal:        407544 kB',
                           'SwapFree:         313436 kB',
                           'Dirty:               104 kB',
                           'Writeback:             0 kB',
                           'AnonPages:        185268 kB',
                           'Mapped:             9592 kB',
                           'Shmem:                68 kB',
                           'Slab:              61716 kB',
                           'SReclaimable:      46620 kB',
                           'SUnreclaim:        15096 kB',
                           'KernelStack:        1760 kB',
                           'PageTables:         8832 kB',
                           'NFS_Unstable:          0 kB',
                           'Bounce:                0 kB',
                           'WritebackTmp:          0 kB',
                           'CommitLimit:      660464 kB',
                           'Committed_AS:     565608 kB',
                           'VmallocTotal:   34359738367 kB',
                           'VmallocUsed:      266724 kB',
                           'VmallocChunk:   34359467156 kB',
                           'HardwareCorrupted:     0 kB',
                           'HugePages_Total:       0',
                           'HugePages_Free:        0',
                           'HugePages_Rsvd:        0',
                           'HugePages_Surp:        0',
                           'Hugepagesize:       2048 kB',
                           'DirectMap4k:       10240 kB',
                           'DirectMap2M:      514048 kB',
                           '']
        meminfo_resp = {'WritebackTmp': '0 kB',
                        'SwapTotal': '407544 kB',
                        'Active(anon)': '94208 kB',
                        'SwapFree': '313436 kB',
                        'DirectMap4k': '10240 kB',
                        'KernelStack': '1760 kB',
                        'MemFree': '26588 kB',
                        'HugePages_Rsvd': '0',
                        'Committed_AS': '565608 kB',
                        'Active(file)': '100692 kB',
                        'NFS_Unstable': '0 kB',
                        'VmallocChunk': '34359467156 kB',
                        'Writeback': '0 kB',
                        'Inactive(file)': '90564 kB',
                        'MemTotal': '505840 kB',
                        'VmallocUsed': '266724 kB',
                        'HugePages_Free': '0',
                        'AnonPages': '185268 kB',
                        'Active': '194900 kB',
                        'Inactive(anon)': '102848 kB',
                        'CommitLimit': '660464 kB',
                        'Hugepagesize': '2048 kB',
                        'Cached': '146376 kB',
                        'SwapCached': '14736 kB',
                        'VmallocTotal': '34359738367 kB',
                        'Shmem': '68 kB',
                        'Mapped': '9592 kB',
                        'SUnreclaim': '15096 kB',
                        'Unevictable': '0 kB',
                        'SReclaimable': '46620 kB',
                        'Mlocked': '0 kB',
                        'DirectMap2M': '514048 kB',
                        'HugePages_Surp': '0',
                        'Bounce': '0 kB',
                        'Inactive': '193412 kB',
                        'PageTables': '8832 kB',
                        'HardwareCorrupted': '0 kB',
                        'HugePages_Total': '0',
                        'Slab': '61716 kB',
                        'Buffers': '44948 kB',
                        'Dirty': '104 kB'}
        oart = OpenAndReadTester(meminfo_content)
        rv = self.app.get_mem(openr=oart.open)
        self.assertEqual(oart.open_calls, [(('/proc/meminfo', 'r'), {})])
        self.assertEqual(rv, meminfo_resp)

    def test_get_async_info(self):
        now = time.time()
        from_cache_response = {'async_pending': 5, 'async_pending_last': now}
        self.fakecache.fakeout = from_cache_response
        rv = self.app.get_async_info()
        self.assertEqual(self.fakecache.fakeout_calls,
                         [((['async_pending', 'async_pending_last'],
                             self._full_recon_path('object')), {})])
        self.assertEqual(rv, {'async_pending': 5, 'async_pending_last': now})

    def test_get_replication_info_account(self):
        from_cache_response = {
            "replication_stats": {
                "attempted": 1, "diff": 0,
                "diff_capped": 0, "empty": 0,
                "failure": 0, "hashmatch": 0,
                "failure_nodes": {
                    "192.168.0.1": 0,
                    "192.168.0.2": 0},
                "no_change": 2, "remote_merge": 0,
                "remove": 0, "rsync": 0,
                "start": 1333044050.855202,
                "success": 2, "ts_repl": 0},
            "replication_time": 0.2615511417388916,
            "replication_last": 1357969645.25}
        self.fakecache.fakeout = from_cache_response
        rv = self.app.get_replication_info('account')
        self.assertEqual(self.fakecache.fakeout_calls,
                         [((['replication_time', 'replication_stats',
                             'replication_last'],
                             self._full_recon_path('account')), {})])
        self.assertEqual(rv, {
            "replication_stats": {
                "attempted": 1, "diff": 0,
                "diff_capped": 0, "empty": 0,
                "failure": 0, "hashmatch": 0,
                "failure_nodes": {
                    "192.168.0.1": 0,
                    "192.168.0.2": 0},
                "no_change": 2, "remote_merge": 0,
                "remove": 0, "rsync": 0,
                "start": 1333044050.855202,
                "success": 2, "ts_repl": 0},
            "replication_time": 0.2615511417388916,
            "replication_last": 1357969645.25})

    def test_get_replication_info_container(self):
        from_cache_response = {
            "replication_time": 200.0,
            "replication_stats": {
                "attempted": 179, "diff": 0,
                "diff_capped": 0, "empty": 0,
                "failure": 0, "hashmatch": 0,
                "failure_nodes": {
                    "192.168.0.1": 0,
                    "192.168.0.2": 0},
                "no_change": 358, "remote_merge": 0,
                "remove": 0, "rsync": 0,
                "start": 5.5, "success": 358,
                "ts_repl": 0},
            "replication_last": 1357969645.25}
        self.fakecache.fakeout_calls = []
        self.fakecache.fakeout = from_cache_response
        rv = self.app.get_replication_info('container')
        self.assertEqual(self.fakecache.fakeout_calls,
                         [((['replication_time', 'replication_stats',
                             'replication_last'],
                             self._full_recon_path('container')),
                           {})])
        self.assertEqual(rv, {
            "replication_time": 200.0,
            "replication_stats": {
                "attempted": 179, "diff": 0,
                "diff_capped": 0, "empty": 0,
                "failure": 0, "hashmatch": 0,
                "failure_nodes": {
                    "192.168.0.1": 0,
                    "192.168.0.2": 0},
                "no_change": 358, "remote_merge": 0,
                "remove": 0, "rsync": 0,
                "start": 5.5, "success": 358,
                "ts_repl": 0},
            "replication_last": 1357969645.25})

    def test_get_replication_object(self):
        from_cache_response = {
            "replication_time": 0.2615511417388916,
            "replication_stats": {
                "attempted": 179,
                "failure": 0, "hashmatch": 0,
                "failure_nodes": {
                    "192.168.0.1": 0,
                    "192.168.0.2": 0},
                "remove": 0, "rsync": 0,
                "start": 1333044050.855202, "success": 358},
            "replication_last": 1357969645.25,
            "object_replication_time": 0.2615511417388916,
            "object_replication_last": 1357969645.25}
        self.fakecache.fakeout_calls = []
        self.fakecache.fakeout = from_cache_response
        rv = self.app.get_replication_info('object')
        self.assertEqual(self.fakecache.fakeout_calls,
                         [((['replication_time', 'replication_stats',
                             'replication_last', 'object_replication_time',
                             'object_replication_last'],
                             self._full_recon_path('object')), {})])
        self.assertEqual(rv, {
            "replication_time": 0.2615511417388916,
            "replication_stats": {
                "attempted": 179,
                "failure": 0, "hashmatch": 0,
                "failure_nodes": {
                    "192.168.0.1": 0,
                    "192.168.0.2": 0},
                "remove": 0, "rsync": 0,
                "start": 1333044050.855202, "success": 358},
            "replication_last": 1357969645.25,
            "object_replication_time": 0.2615511417388916,
            "object_replication_last": 1357969645.25})

    def test_get_replication_info_unrecognized(self):
        rv = self.app.get_replication_info('unrecognized_recon_type')
        self.assertIsNone(rv)

    def test_get_reconstruction(self):
        from_cache_response = {
            "object_reconstruction_time": 0.2615511417388916,
            "object_reconstruction_last": 1357969645.25}
        self.fakecache.fakeout_calls = []
        self.fakecache.fakeout = from_cache_response
        rv = self.app.get_reconstruction_info()
        self.assertEqual(self.fakecache.fakeout_calls,
                         [((['object_reconstruction_last',
                             'object_reconstruction_time'],
                             '/var/cache/swift/object.recon'), {})])
        self.assertEqual(rv, {
            "object_reconstruction_time": 0.2615511417388916,
            "object_reconstruction_last": 1357969645.25})

    def test_get_updater_info_container(self):
        from_cache_response = {"container_updater_sweep": 18.476239919662476}
        self.fakecache.fakeout_calls = []
        self.fakecache.fakeout = from_cache_response
        rv = self.app.get_updater_info('container')
        self.assertEqual(self.fakecache.fakeout_calls,
                         [((['container_updater_sweep'],
                            self._full_recon_path('container')), {})])
        self.assertEqual(rv, {"container_updater_sweep": 18.476239919662476})

    def test_get_updater_info_object(self):
        from_cache_response = {"object_updater_sweep": 0.79848217964172363}
        self.fakecache.fakeout_calls = []
        self.fakecache.fakeout = from_cache_response
        rv = self.app.get_updater_info('object')
        self.assertEqual(self.fakecache.fakeout_calls,
                         [((['object_updater_sweep'],
                            self._full_recon_path('object')), {})])
        self.assertEqual(rv, {"object_updater_sweep": 0.79848217964172363})

    def test_get_updater_info_unrecognized(self):
        rv = self.app.get_updater_info('unrecognized_recon_type')
        self.assertIsNone(rv)

    def test_get_expirer_info_object(self):
        from_cache_response = {'object_expiration_pass': 0.79848217964172363,
                               'expired_last_pass': 99}
        self.fakecache.fakeout_calls = []
        self.fakecache.fakeout = from_cache_response
        rv = self.app.get_expirer_info('object')
        self.assertEqual(self.fakecache.fakeout_calls,
                         [((['object_expiration_pass', 'expired_last_pass'],
                            self._full_recon_path('object')), {})])
        self.assertEqual(rv, from_cache_response)

    def test_get_auditor_info_account(self):
        from_cache_response = {"account_auditor_pass_completed": 0.24,
                               "account_audits_failed": 0,
                               "account_audits_passed": 6,
                               "account_audits_since": "1333145374.1373529"}
        self.fakecache.fakeout_calls = []
        self.fakecache.fakeout = from_cache_response
        rv = self.app.get_auditor_info('account')
        self.assertEqual(self.fakecache.fakeout_calls,
                         [((['account_audits_passed',
                             'account_auditor_pass_completed',
                             'account_audits_since',
                             'account_audits_failed'],
                             self._full_recon_path('account')), {})])
        self.assertEqual(rv, {"account_auditor_pass_completed": 0.24,
                              "account_audits_failed": 0,
                              "account_audits_passed": 6,
                              "account_audits_since": "1333145374.1373529"})

    def test_get_auditor_info_container(self):
        from_cache_response = {"container_auditor_pass_completed": 0.24,
                               "container_audits_failed": 0,
                               "container_audits_passed": 6,
                               "container_audits_since": "1333145374.1373529"}
        self.fakecache.fakeout_calls = []
        self.fakecache.fakeout = from_cache_response
        rv = self.app.get_auditor_info('container')
        self.assertEqual(self.fakecache.fakeout_calls,
                         [((['container_audits_passed',
                             'container_auditor_pass_completed',
                             'container_audits_since',
                             'container_audits_failed'],
                             self._full_recon_path('container')), {})])
        self.assertEqual(rv, {"container_auditor_pass_completed": 0.24,
                              "container_audits_failed": 0,
                              "container_audits_passed": 6,
                              "container_audits_since": "1333145374.1373529"})

    def test_get_auditor_info_object(self):
        from_cache_response = {
            "object_auditor_stats_ALL": {
                "audit_time": 115.14418768882751,
                "bytes_processed": 234660,
                "completed": 115.4512460231781,
                "errors": 0,
                "files_processed": 2310,
                "quarantined": 0},
            "object_auditor_stats_ZBF": {
                "audit_time": 45.877294063568115,
                "bytes_processed": 0,
                "completed": 46.181446075439453,
                "errors": 0,
                "files_processed": 2310,
                "quarantined": 0}}
        self.fakecache.fakeout_calls = []
        self.fakecache.fakeout = from_cache_response
        rv = self.app.get_auditor_info('object')
        self.assertEqual(self.fakecache.fakeout_calls,
                         [((['object_auditor_stats_ALL',
                             'object_auditor_stats_ZBF'],
                             self._full_recon_path('object')), {})])
        self.assertEqual(rv, {
            "object_auditor_stats_ALL": {
                "audit_time": 115.14418768882751,
                "bytes_processed": 234660,
                "completed": 115.4512460231781,
                "errors": 0,
                "files_processed": 2310,
                "quarantined": 0},
            "object_auditor_stats_ZBF": {
                "audit_time": 45.877294063568115,
                "bytes_processed": 0,
                "completed": 46.181446075439453,
                "errors": 0,
                "files_processed": 2310,
                "quarantined": 0}})

    def test_get_auditor_info_object_parallel_once(self):
        from_cache_response = {
            "object_auditor_stats_ALL": {
                'disk1': {
                    "audit_time": 115.14418768882751,
                    "bytes_processed": 234660,
                    "completed": 115.4512460231781,
                    "errors": 0,
                    "files_processed": 2310,
                    "quarantined": 0},
                'disk2': {
                    "audit_time": 115,
                    "bytes_processed": 234660,
                    "completed": 115,
                    "errors": 0,
                    "files_processed": 2310,
                    "quarantined": 0}},
            "object_auditor_stats_ZBF": {'disk1disk2': {
                "audit_time": 45.877294063568115,
                "bytes_processed": 0,
                "completed": 46.181446075439453,
                "errors": 0,
                "files_processed": 2310,
                "quarantined": 0}}}
        self.fakecache.fakeout_calls = []
        self.fakecache.fakeout = from_cache_response
        rv = self.app.get_auditor_info('object')
        self.assertEqual(self.fakecache.fakeout_calls,
                         [((['object_auditor_stats_ALL',
                             'object_auditor_stats_ZBF'],
                             self._full_recon_path('object')), {})])
        self.assertEqual(rv, {
            "object_auditor_stats_ALL": {
                'disk1': {
                    "audit_time": 115.14418768882751,
                    "bytes_processed": 234660,
                    "completed": 115.4512460231781,
                    "errors": 0,
                    "files_processed": 2310,
                    "quarantined": 0},
                'disk2': {
                    "audit_time": 115,
                    "bytes_processed": 234660,
                    "completed": 115,
                    "errors": 0,
                    "files_processed": 2310,
                    "quarantined": 0}},
            "object_auditor_stats_ZBF": {'disk1disk2': {
                "audit_time": 45.877294063568115,
                "bytes_processed": 0,
                "completed": 46.181446075439453,
                "errors": 0,
                "files_processed": 2310,
                "quarantined": 0}}})

    def test_get_auditor_info_unrecognized(self):
        rv = self.app.get_auditor_info('unrecognized_recon_type')
        self.assertIsNone(rv)

    def test_get_unmounted(self):
        unmounted_resp = [{'device': 'fakeone', 'mounted': False},
                          {'device': 'faketwo', 'mounted': False}]
        self.mockos.ls_output = ['fakeone', 'faketwo']
        self.mockos.isdir_output = True
        self.mockos.ismount_output = False
        rv = self.app.get_unmounted()
        self.assertEqual(self.mockos.listdir_calls, [(('/srv/node',), {})])
        self.assertEqual(self.mockos.isdir_calls,
                         [(('/srv/node/fakeone',), {}),
                          (('/srv/node/faketwo',), {})])
        self.assertEqual(rv, unmounted_resp)

    def test_get_unmounted_excludes_files(self):
        unmounted_resp = []
        self.mockos.ls_output = ['somerando.log']
        self.mockos.isdir_output = False
        self.mockos.ismount_output = False
        rv = self.app.get_unmounted()
        self.assertEqual(self.mockos.listdir_calls, [(('/srv/node',), {})])
        self.assertEqual(self.mockos.isdir_calls,
                         [(('/srv/node/somerando.log',), {})])
        self.assertEqual(rv, unmounted_resp)

    def test_get_unmounted_all_mounted(self):
        unmounted_resp = []
        self.mockos.ls_output = ['fakeone', 'faketwo']
        self.mockos.isdir_output = True
        self.mockos.ismount_output = True
        rv = self.app.get_unmounted()
        self.assertEqual(self.mockos.listdir_calls, [(('/srv/node',), {})])
        self.assertEqual(self.mockos.isdir_calls,
                         [(('/srv/node/fakeone',), {}),
                          (('/srv/node/faketwo',), {})])
        self.assertEqual(rv, unmounted_resp)

    def test_get_unmounted_checkmount_fail(self):
        unmounted_resp = [{'device': 'fakeone', 'mounted': 'brokendrive'}]
        self.mockos.ls_output = ['fakeone']
        self.mockos.isdir_output = True
        self.mockos.ismount_output = OSError('brokendrive')
        rv = self.app.get_unmounted()
        self.assertEqual(self.mockos.listdir_calls, [(('/srv/node',), {})])
        self.assertEqual(self.mockos.isdir_calls,
                         [(('/srv/node/fakeone',), {})])
        self.assertEqual(self.mockos.ismount_calls,
                         [(('/srv/node/fakeone',), {})])
        self.assertEqual(rv, unmounted_resp)

    def test_get_unmounted_no_mounts(self):

        def fake_checkmount_true(*args):
            return True

        unmounted_resp = []
        self.mockos.ls_output = []
        self.mockos.isdir_output = False
        self.mockos.ismount_output = False
        rv = self.app.get_unmounted()
        self.assertEqual(self.mockos.listdir_calls, [(('/srv/node',), {})])
        self.assertEqual(self.mockos.isdir_calls, [])
        self.assertEqual(rv, unmounted_resp)

    def test_get_diskusage(self):
        # posix.statvfs_result(f_bsize=4096, f_frsize=4096, f_blocks=1963185,
        #                      f_bfree=1113075, f_bavail=1013351,
        #                      f_files=498736,
        #                      f_ffree=397839, f_favail=397839, f_flag=0,
        #                      f_namemax=255)
        statvfs_content = (4096, 4096, 1963185, 1113075, 1013351, 498736,
                           397839, 397839, 0, 255)
        du_resp = [{'device': 'canhazdrive1', 'avail': 4150685696,
                    'mounted': True, 'used': 3890520064, 'size': 8041205760}]
        self.mockos.ls_output = ['canhazdrive1']
        self.mockos.isdir_output = True
        self.mockos.statvfs_output = statvfs_content
        self.mockos.ismount_output = True
        rv = self.app.get_diskusage()
        self.assertEqual(self.mockos.listdir_calls, [(('/srv/node',), {})])
        self.assertEqual(self.mockos.isdir_calls,
                         [(('/srv/node/canhazdrive1',), {})])
        self.assertEqual(self.mockos.statvfs_calls,
                         [(('/srv/node/canhazdrive1',), {})])
        self.assertEqual(rv, du_resp)

    def test_get_diskusage_excludes_files(self):
        du_resp = []
        self.mockos.ls_output = ['somerando.log']
        self.mockos.isdir_output = False
        rv = self.app.get_diskusage()
        self.assertEqual(self.mockos.isdir_calls,
                         [(('/srv/node/somerando.log',), {})])
        self.assertEqual(self.mockos.statvfs_calls, [])
        self.assertEqual(rv, du_resp)

    def test_get_diskusage_checkmount_fail(self):
        du_resp = [{'device': 'canhazdrive1', 'avail': '',
                    'mounted': 'brokendrive', 'used': '', 'size': ''}]
        self.mockos.ls_output = ['canhazdrive1']
        self.mockos.isdir_output = True
        self.mockos.ismount_output = OSError('brokendrive')
        rv = self.app.get_diskusage()
        self.assertEqual(self.mockos.listdir_calls, [(('/srv/node',), {})])
        self.assertEqual(self.mockos.isdir_calls,
                         [(('/srv/node/canhazdrive1',), {})])
        self.assertEqual(self.mockos.ismount_calls,
                         [(('/srv/node/canhazdrive1',), {})])
        self.assertEqual(rv, du_resp)

    @mock.patch("swift.common.middleware.recon.check_mount", fake_check_mount)
    def test_get_diskusage_oserror(self):
        du_resp = [{'device': 'canhazdrive1', 'avail': '',
                    'mounted': 'Input/Output Error', 'used': '', 'size': ''}]
        self.mockos.ls_output = ['canhazdrive1']
        self.mockos.isdir_output = True
        rv = self.app.get_diskusage()
        self.assertEqual(rv, du_resp)

    def test_get_quarantine_count(self):
        dirs = [['sda'], ['accounts', 'containers', 'objects', 'objects-1']]
        self.mockos.ismount_output = True

        def fake_lstat(*args, **kwargs):
            # posix.lstat_result(st_mode=1, st_ino=2, st_dev=3, st_nlink=4,
            #                    st_uid=5, st_gid=6, st_size=7, st_atime=8,
            #                    st_mtime=9, st_ctime=10)
            return stat_result((1, 2, 3, 4, 5, 6, 7, 8, 9, 10))

        def fake_exists(*args, **kwargs):
            return True

        def fake_listdir(*args, **kwargs):
            return dirs.pop(0)

        with mock.patch("os.lstat", fake_lstat):
            with mock.patch("os.path.exists", fake_exists):
                with mock.patch("os.listdir", fake_listdir):
                    rv = self.app.get_quarantine_count()
        self.assertEqual(rv, {'objects': 4, 'accounts': 2, 'policies':
                              {'1': {'objects': 2}, '0': {'objects': 2}},
                              'containers': 2})

    def test_get_socket_info(self):
        sockstat_content = ['sockets: used 271',
                            'TCP: inuse 30 orphan 0 tw 0 alloc 31 mem 0',
                            'UDP: inuse 16 mem 4', 'UDPLITE: inuse 0',
                            'RAW: inuse 0', 'FRAG: inuse 0 memory 0',
                            '']
        oart = OpenAndReadTester(sockstat_content)
        self.app.get_socket_info(openr=oart.open)
        self.assertEqual(oart.open_calls, [
            (('/proc/net/sockstat', 'r'), {}),
            (('/proc/net/sockstat6', 'r'), {})])

    def test_get_driveaudit_info(self):
        from_cache_response = {'drive_audit_errors': 7}
        self.fakecache.fakeout = from_cache_response
        rv = self.app.get_driveaudit_error()
        self.assertEqual(self.fakecache.fakeout_calls,
                         [((['drive_audit_errors'],
                            self._full_recon_path(
                                None, recon_file=RECON_DRIVE_FILE)), {})])
        self.assertEqual(rv, {'drive_audit_errors': 7})

    def test_get_time(self):
        def fake_time():
            return 1430000000.0

        with mock.patch("time.time", fake_time):
            now = fake_time()
            rv = self.app.get_time()
            self.assertEqual(rv, now)

    def test_get_sharding_info(self):
        from_cache_response = {
            "sharding_stats": {
                "attempted": 0,
                "deferred": 0,
                "diff": 0,
                "diff_capped": 0,
                "empty": 0,
                "failure": 0,
                "hashmatch": 0,
                "no_change": 0,
                "remote_merge": 0,
                "remove": 0,
                "rsync": 0,
                "start": 1614136398.5729735,
                "success": 0,
                "ts_repl": 0,
                "sharding": {
                    "audit_root": {
                        "attempted": 0,
                        "failure": 0,
                        "success": 0,
                    },
                    "audit_shard": {
                        "attempted": 0,
                        "failure": 0,
                        "success": 0,
                    },
                    "cleaved": {
                        "attempted": 0,
                        "failure": 0,
                        "max_time": 0,
                        "min_time": 0,
                        "success": 0,
                    },
                    "created": {
                        "attempted": 0,
                        "failure": 0,
                        "success": 0,
                    },
                    "misplaced": {
                        "attempted": 0,
                        "failure": 0,
                        "found": 0,
                        "placed": 0,
                        "success": 0,
                        "unplaced": 0,
                    },
                    "scanned": {
                        "attempted": 0,
                        "failure": 0,
                        "found": 0,
                        "max_time": 0,
                        "min_time": 0,
                        "success": 0,
                    },
                    "sharding_candidates": {
                        "found": 0,
                        "top": [],
                    },
                    "visited": {
                        "attempted": 0,
                        "completed": 0,
                        "failure": 0,
                        "skipped": 6,
                        "success": 0,
                    }
                },
            },
            "sharding_time": 600,
            "sharding_last": 1614136398.6680582}
        self.fakecache.fakeout_calls = []
        self.fakecache.fakeout = from_cache_response
        rv = self.app.get_sharding_info()
        self.assertEqual(self.fakecache.fakeout_calls, [
            ((['sharding_stats', 'sharding_time', 'sharding_last'],
              self._full_recon_path('container')), {})])
        self.assertEqual(rv, from_cache_response)

    def test_get_relinker_info(self):
        from_cache_response = {
            "devices": {
                "sdb3": {
                    "parts_done": 523,
                    "policies": {
                        "1": {
                            "next_part_power": 11,
                            "start_time": 1618998724.845616,
                            "stats": {
                                "errors": 0,
                                "files": 1630,
                                "hash_dirs": 1630,
                                "linked": 1630,
                                "policies": 1,
                                "removed": 0
                            },
                            "timestamp": 1618998730.24672,
                            "total_parts": 1029,
                            "total_time": 5.400741815567017
                        }},
                    "start_time": 1618998724.845946,
                    "stats": {
                        "errors": 0,
                        "files": 836,
                        "hash_dirs": 836,
                        "linked": 836,
                        "removed": 0
                    },
                    "timestamp": 1618998730.24672,
                    "total_parts": 523,
                    "total_time": 5.400741815567017
                },
                "sdb7": {
                    "parts_done": 506,
                    "policies": {
                        "1": {
                            "next_part_power": 11,
                            "part_power": 10,
                            "parts_done": 506,
                            "start_time": 1618998724.845616,
                            "stats": {
                                "errors": 0,
                                "files": 794,
                                "hash_dirs": 794,
                                "linked": 794,
                                "removed": 0
                            },
                            "step": "relink",
                            "timestamp": 1618998730.166175,
                            "total_parts": 506,
                            "total_time": 5.320528984069824
                        }
                    },
                    "start_time": 1618998724.845616,
                    "stats": {
                        "errors": 0,
                        "files": 794,
                        "hash_dirs": 794,
                        "linked": 794,
                        "removed": 0
                    },
                    "timestamp": 1618998730.166175,
                    "total_parts": 506,
                    "total_time": 5.320528984069824
                }
            },
            "workers": {
                "100": {
                    "drives": ["sda1"],
                    "return_code": 0,
                    "timestamp": 1618998730.166175}
            }}
        self.fakecache.fakeout_calls = []
        self.fakecache.fakeout = from_cache_response
        rv = self.app.get_relinker_info()
        self.assertEqual(self.fakecache.fakeout_calls,
                         [((['devices', 'workers'],
                            self._full_recon_path(
                                None, recon_file=RECON_RELINKER_FILE)),
                           {'ignore_missing': True})])
        self.assertEqual(rv, from_cache_response)


class TestReconMiddleware(unittest.TestCase):

    def fake_list(self, path):
        return ['a', 'b']

    def setUp(self):
        self.frecon = FakeRecon()
        self.real_listdir = os.listdir
        os.listdir = self.fake_list
        self.app = recon.ReconMiddleware(FakeApp(), {'object_recon': "true"})
        self.real_app_get_device_info = self.app.get_device_info
        self.real_app_get_swift_conf_md5 = self.app.get_swift_conf_md5
        os.listdir = self.real_listdir
        # self.app.object_recon = True
        self.app.get_mem = self.frecon.fake_mem
        self.app.get_load = self.frecon.fake_load
        self.app.get_async_info = self.frecon.fake_async
        self.app.get_device_info = self.frecon.fake_get_device_info
        self.app.get_replication_info = self.frecon.fake_replication
        self.app.get_reconstruction_info = self.frecon.fake_reconstruction
        self.app.get_auditor_info = self.frecon.fake_auditor
        self.app.get_updater_info = self.frecon.fake_updater
        self.app.get_expirer_info = self.frecon.fake_expirer
        self.app.get_mounted = self.frecon.fake_mounted
        self.app.get_unmounted = self.frecon.fake_unmounted
        self.app.get_diskusage = self.frecon.fake_diskusage
        self.app.get_ring_md5 = self.frecon.fake_ringmd5
        self.app.get_swift_conf_md5 = self.frecon.fake_swiftconfmd5
        self.app.get_quarantine_count = self.frecon.fake_quarantined
        self.app.get_socket_info = self.frecon.fake_sockstat
        self.app.get_driveaudit_error = self.frecon.fake_driveaudit
        self.app.get_time = self.frecon.fake_time
        self.app.get_sharding_info = self.frecon.fake_sharding
        self.app.get_relinker_info = self.frecon.fake_relinker

    def test_recon_get_mem(self):
        get_mem_resp = [b'{"memtest": "1"}']
        req = Request.blank('/recon/mem', environ={'REQUEST_METHOD': 'GET'})
        resp = self.app(req.environ, start_response)
        self.assertEqual(resp, get_mem_resp)

    def test_recon_get_version(self):
        req = Request.blank('/recon/version',
                            environ={'REQUEST_METHOD': 'GET'})
        resp = self.app(req.environ, start_response)
        self.assertEqual(resp, [json.dumps({
            'version': swiftver}).encode('ascii')])

    def test_recon_get_load(self):
        get_load_resp = [b'{"loadtest": "1"}']
        req = Request.blank('/recon/load', environ={'REQUEST_METHOD': 'GET'})
        resp = self.app(req.environ, start_response)
        self.assertEqual(resp, get_load_resp)

    def test_recon_get_async(self):
        get_async_resp = [b'{"asynctest": "1"}']
        req = Request.blank('/recon/async', environ={'REQUEST_METHOD': 'GET'})
        resp = self.app(req.environ, start_response)
        self.assertEqual(resp, get_async_resp)

    def test_get_device_info(self):
        get_device_resp = [b'{"/srv/1/node": ["sdb1"]}']
        req = Request.blank('/recon/devices',
                            environ={'REQUEST_METHOD': 'GET'})
        resp = self.app(req.environ, start_response)
        self.assertEqual(resp, get_device_resp)

    def test_reconstruction_info(self):
        get_reconstruction_resp = [b'{"reconstructiontest": "1"}']
        req = Request.blank('/recon/reconstruction/object',
                            environ={'REQUEST_METHOD': 'GET'})
        resp = self.app(req.environ, start_response)
        self.assertEqual(resp, get_reconstruction_resp)

    def test_recon_get_replication_notype(self):
        get_replication_resp = [b'{"replicationtest": "1"}']
        req = Request.blank('/recon/replication',
                            environ={'REQUEST_METHOD': 'GET'})
        resp = self.app(req.environ, start_response)
        self.assertEqual(resp, get_replication_resp)
        self.assertEqual(self.frecon.fake_replication_rtype, 'object')
        self.frecon.fake_replication_rtype = None

    def test_recon_get_replication_all(self):
        get_replication_resp = [b'{"replicationtest": "1"}']
        # test account
        req = Request.blank('/recon/replication/account',
                            environ={'REQUEST_METHOD': 'GET'})
        resp = self.app(req.environ, start_response)
        self.assertEqual(resp, get_replication_resp)
        self.assertEqual(self.frecon.fake_replication_rtype, 'account')
        self.frecon.fake_replication_rtype = None
        # test container
        req = Request.blank('/recon/replication/container',
                            environ={'REQUEST_METHOD': 'GET'})
        resp = self.app(req.environ, start_response)
        self.assertEqual(resp, get_replication_resp)
        self.assertEqual(self.frecon.fake_replication_rtype, 'container')
        self.frecon.fake_replication_rtype = None
        # test object
        req = Request.blank('/recon/replication/object',
                            environ={'REQUEST_METHOD': 'GET'})
        resp = self.app(req.environ, start_response)
        self.assertEqual(resp, get_replication_resp)
        self.assertEqual(self.frecon.fake_replication_rtype, 'object')
        self.frecon.fake_replication_rtype = None

    def test_recon_get_auditor_invalid(self):
        get_auditor_resp = [b'Invalid path: /recon/auditor/invalid']
        req = Request.blank('/recon/auditor/invalid',
                            environ={'REQUEST_METHOD': 'GET'})
        resp = self.app(req.environ, start_response)
        self.assertEqual(resp, get_auditor_resp)

    def test_recon_get_auditor_notype(self):
        get_auditor_resp = [b'Invalid path: /recon/auditor']
        req = Request.blank('/recon/auditor',
                            environ={'REQUEST_METHOD': 'GET'})
        resp = self.app(req.environ, start_response)
        self.assertEqual(resp, get_auditor_resp)

    def test_recon_get_auditor_all(self):
        get_auditor_resp = [b'{"auditortest": "1"}']
        req = Request.blank('/recon/auditor/account',
                            environ={'REQUEST_METHOD': 'GET'})
        resp = self.app(req.environ, start_response)
        self.assertEqual(resp, get_auditor_resp)
        self.assertEqual(self.frecon.fake_auditor_rtype, 'account')
        self.frecon.fake_auditor_rtype = None
        req = Request.blank('/recon/auditor/container',
                            environ={'REQUEST_METHOD': 'GET'})
        resp = self.app(req.environ, start_response)
        self.assertEqual(resp, get_auditor_resp)
        self.assertEqual(self.frecon.fake_auditor_rtype, 'container')
        self.frecon.fake_auditor_rtype = None
        req = Request.blank('/recon/auditor/object',
                            environ={'REQUEST_METHOD': 'GET'})
        resp = self.app(req.environ, start_response)
        self.assertEqual(resp, get_auditor_resp)
        self.assertEqual(self.frecon.fake_auditor_rtype, 'object')
        self.frecon.fake_auditor_rtype = None

    def test_recon_get_updater_invalid(self):
        get_updater_resp = [b'Invalid path: /recon/updater/invalid']
        req = Request.blank('/recon/updater/invalid',
                            environ={'REQUEST_METHOD': 'GET'})
        resp = self.app(req.environ, start_response)
        self.assertEqual(resp, get_updater_resp)

    def test_recon_get_updater_notype(self):
        get_updater_resp = [b'Invalid path: /recon/updater']
        req = Request.blank('/recon/updater',
                            environ={'REQUEST_METHOD': 'GET'})
        resp = self.app(req.environ, start_response)
        self.assertEqual(resp, get_updater_resp)

    def test_recon_get_updater(self):
        get_updater_resp = [b'{"updatertest": "1"}']
        req = Request.blank('/recon/updater/container',
                            environ={'REQUEST_METHOD': 'GET'})
        resp = self.app(req.environ, start_response)
        self.assertEqual(self.frecon.fake_updater_rtype, 'container')
        self.frecon.fake_updater_rtype = None
        self.assertEqual(resp, get_updater_resp)
        req = Request.blank('/recon/updater/object',
                            environ={'REQUEST_METHOD': 'GET'})
        resp = self.app(req.environ, start_response)
        self.assertEqual(resp, get_updater_resp)
        self.assertEqual(self.frecon.fake_updater_rtype, 'object')
        self.frecon.fake_updater_rtype = None

    def test_recon_get_expirer_invalid(self):
        get_updater_resp = [b'Invalid path: /recon/expirer/invalid']
        req = Request.blank('/recon/expirer/invalid',
                            environ={'REQUEST_METHOD': 'GET'})
        resp = self.app(req.environ, start_response)
        self.assertEqual(resp, get_updater_resp)

    def test_recon_get_expirer_notype(self):
        get_updater_resp = [b'Invalid path: /recon/expirer']
        req = Request.blank('/recon/expirer',
                            environ={'REQUEST_METHOD': 'GET'})
        resp = self.app(req.environ, start_response)
        self.assertEqual(resp, get_updater_resp)

    def test_recon_get_expirer_object(self):
        get_expirer_resp = [b'{"expirertest": "1"}']
        req = Request.blank('/recon/expirer/object',
                            environ={'REQUEST_METHOD': 'GET'})
        resp = self.app(req.environ, start_response)
        self.assertEqual(resp, get_expirer_resp)
        self.assertEqual(self.frecon.fake_expirer_rtype, 'object')
        self.frecon.fake_updater_rtype = None

    def test_recon_get_mounted(self):
        get_mounted_resp = [b'{"mountedtest": "1"}']
        req = Request.blank('/recon/mounted',
                            environ={'REQUEST_METHOD': 'GET'})
        resp = self.app(req.environ, start_response)
        self.assertEqual(resp, get_mounted_resp)

    def test_recon_get_unmounted(self):
        get_unmounted_resp = [b'{"unmountedtest": "1"}']
        self.app.get_unmounted = self.frecon.fake_unmounted
        req = Request.blank('/recon/unmounted',
                            environ={'REQUEST_METHOD': 'GET'})
        resp = self.app(req.environ, start_response)
        self.assertEqual(resp, get_unmounted_resp)

    def test_recon_get_unmounted_empty(self):
        get_unmounted_resp = b'[]'
        self.app.get_unmounted = self.frecon.fake_unmounted_empty
        req = Request.blank('/recon/unmounted',
                            environ={'REQUEST_METHOD': 'GET'})
        resp = b''.join(self.app(req.environ, start_response))
        self.assertEqual(resp, get_unmounted_resp)

    def test_recon_get_diskusage(self):
        get_diskusage_resp = [b'{"diskusagetest": "1"}']
        req = Request.blank('/recon/diskusage',
                            environ={'REQUEST_METHOD': 'GET'})
        resp = self.app(req.environ, start_response)
        self.assertEqual(resp, get_diskusage_resp)

    def test_recon_get_ringmd5(self):
        get_ringmd5_resp = [b'{"ringmd5test": "1"}']
        req = Request.blank('/recon/ringmd5',
                            environ={'REQUEST_METHOD': 'GET'})
        resp = self.app(req.environ, start_response)
        self.assertEqual(resp, get_ringmd5_resp)

    def test_recon_get_swiftconfmd5(self):
        get_swiftconfmd5_resp = [b'{"/etc/swift/swift.conf": "abcdef"}']
        req = Request.blank('/recon/swiftconfmd5',
                            environ={'REQUEST_METHOD': 'GET'})
        resp = self.app(req.environ, start_response)
        self.assertEqual(resp, get_swiftconfmd5_resp)

    def test_recon_get_quarantined(self):
        get_quarantined_resp = [b'{"quarantinedtest": "1"}']
        req = Request.blank('/recon/quarantined',
                            environ={'REQUEST_METHOD': 'GET'})
        resp = self.app(req.environ, start_response)
        self.assertEqual(resp, get_quarantined_resp)

    def test_recon_get_sockstat(self):
        get_sockstat_resp = [b'{"sockstattest": "1"}']
        req = Request.blank('/recon/sockstat',
                            environ={'REQUEST_METHOD': 'GET'})
        resp = self.app(req.environ, start_response)
        self.assertEqual(resp, get_sockstat_resp)

    def test_recon_invalid_path(self):
        req = Request.blank('/recon/invalid',
                            environ={'REQUEST_METHOD': 'GET'})
        resp = self.app(req.environ, start_response)
        self.assertEqual(resp, [b'Invalid path: /recon/invalid'])

    def test_no_content(self):
        self.app.get_load = self.frecon.nocontent
        req = Request.blank('/recon/load', environ={'REQUEST_METHOD': 'GET'})
        resp = self.app(req.environ, start_response)
        self.assertEqual(resp, [b'Internal server error.'])

    def test_recon_pass(self):
        req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'})
        resp = self.app(req.environ, start_response)
        self.assertEqual(resp, b'FAKE APP')

    def test_recon_get_driveaudit(self):
        get_driveaudit_resp = [b'{"driveaudittest": "1"}']
        req = Request.blank('/recon/driveaudit',
                            environ={'REQUEST_METHOD': 'GET'})
        resp = self.app(req.environ, start_response)
        self.assertEqual(resp, get_driveaudit_resp)

    def test_recon_get_time(self):
        get_time_resp = [b'{"timetest": "1"}']
        req = Request.blank('/recon/time',
                            environ={'REQUEST_METHOD': 'GET'})
        resp = self.app(req.environ, start_response)
        self.assertEqual(resp, get_time_resp)

    def test_get_device_info_function(self):
        """Test get_device_info function call success"""
        resp = self.app.get_device_info()
        self.assertEqual(['sdb1'], resp['/srv/1/node'])

    def test_get_device_info_fail(self):
        """Test get_device_info failure by failing os.listdir"""
        os.listdir = fail_os_listdir
        resp = self.real_app_get_device_info()
        os.listdir = self.real_listdir
        device_path = list(resp)[0]
        self.assertIsNone(resp[device_path])

    def test_get_swift_conf_md5(self):
        """Test get_swift_conf_md5 success"""
        resp = self.app.get_swift_conf_md5()
        self.assertEqual('abcdef', resp['/etc/swift/swift.conf'])

    def test_get_swift_conf_md5_fail(self):
        """Test get_swift_conf_md5 failure by failing file open"""
        with mock.patch('swift.common.middleware.recon.md5_hash_for_file',
                        side_effect=IOError):
            resp = self.real_app_get_swift_conf_md5()
        self.assertIsNone(resp['/etc/swift/swift.conf'])

    def test_recon_get_sharding(self):
        get_sharding_resp = [
            b'{"sharding_stats": "1"}']
        req = Request.blank('/recon/sharding',
                            environ={'REQUEST_METHOD': 'GET'})
        resp = self.app(req.environ, start_response)
        self.assertEqual(resp, get_sharding_resp)

    def test_recon_get_relink(self):
        get_recon_resp = [
            b'{"relinktest": "1"}']
        req = Request.blank('/recon/relinker',
                            environ={'REQUEST_METHOD': 'GET'})
        resp = self.app(req.environ, start_response)
        self.assertEqual(resp, get_recon_resp)


if __name__ == '__main__':
    unittest.main()
