def __init__(self):
     """Create a client and grab essential information from the server."""
     self.connection_attempts = []
     self.connected = False
     self.w = None
     self.nodes = set()
     self.replica_set_name = None
     self.cmd_line = None
     self.server_status = None
     self.version = Version(-1)  # Needs to be comparable with Version
     self.auth_enabled = False
     self.test_commands_enabled = False
     self.server_parameters = None
     self.is_mongos = False
     self.mongoses = []
     self.is_rs = False
     self.has_ipv6 = False
     self.tls = False
     self.ssl_certfile = False
     self.server_is_resolvable = is_server_resolvable()
     self.default_client_options = {}
     self.sessions_enabled = False
     self.client = None
     self.conn_lock = threading.Lock()
     self.is_data_lake = False
     if COMPRESSORS:
         self.default_client_options["compressors"] = COMPRESSORS
     if MONGODB_API_VERSION:
         server_api = ServerApi(MONGODB_API_VERSION)
         self.default_client_options["server_api"] = server_api
def is_run_on_requirement_satisfied(requirement):
    topology_satisfied = True
    req_topologies = requirement.get('topologies')
    if req_topologies:
        topology_satisfied = client_context.is_topology_type(req_topologies)

    min_version_satisfied = True
    req_min_server_version = requirement.get('minServerVersion')
    if req_min_server_version:
        min_version_satisfied = Version.from_string(
            req_min_server_version) <= client_context.version

    max_version_satisfied = True
    req_max_server_version = requirement.get('maxServerVersion')
    if req_max_server_version:
        max_version_satisfied = Version.from_string(
            req_max_server_version) >= client_context.version

    params_satisfied = True
    params = requirement.get('serverParameters')
    if params:
        for param, val in params.items():
            if param not in client_context.server_parameters:
                params_satisfied = False
            elif client_context.server_parameters[param] != val:
                params_satisfied = False

    return (topology_satisfied and min_version_satisfied
            and max_version_satisfied and params_satisfied)
def is_run_on_requirement_satisfied(requirement):
    topology_satisfied = True
    req_topologies = requirement.get('topologies')
    if req_topologies:
        topology_satisfied = client_context.is_topology_type(req_topologies)

    server_version = Version(*client_context.version[:3])

    min_version_satisfied = True
    req_min_server_version = requirement.get('minServerVersion')
    if req_min_server_version:
        min_version_satisfied = Version.from_string(
            req_min_server_version) <= server_version

    max_version_satisfied = True
    req_max_server_version = requirement.get('maxServerVersion')
    if req_max_server_version:
        max_version_satisfied = Version.from_string(
            req_max_server_version) >= server_version

    serverless = requirement.get('serverless')
    if serverless == "require":
        serverless_satisfied = client_context.serverless
    elif serverless == "forbid":
        serverless_satisfied = not client_context.serverless
    else:  # unset or "allow"
        serverless_satisfied = True

    params_satisfied = True
    params = requirement.get('serverParameters')
    if params:
        for param, val in params.items():
            if param not in client_context.server_parameters:
                params_satisfied = False
            elif client_context.server_parameters[param] != val:
                params_satisfied = False

    auth_satisfied = True
    req_auth = requirement.get('auth')
    if req_auth is not None:
        if req_auth:
            auth_satisfied = client_context.auth_enabled
        else:
            auth_satisfied = not client_context.auth_enabled

    return (topology_satisfied and min_version_satisfied
            and max_version_satisfied and serverless_satisfied
            and params_satisfied and auth_satisfied)
    def __init__(self):
        """Create a client and grab essential information from the server."""
        self.connection_attempts = []
        self.connected = False
        self.ismaster = {}
        self.w = None
        self.nodes = set()
        self.replica_set_name = None
        self.cmd_line = None
        self.server_status = None
        self.version = Version(-1)  # Needs to be comparable with Version
        self.auth_enabled = False
        self.test_commands_enabled = False
        self.is_mongos = False
        self.mongoses = []
        self.is_rs = False
        self.has_ipv6 = False
        self.ssl = False
        self.ssl_cert_none = False
        self.ssl_certfile = False
        self.server_is_resolvable = is_server_resolvable()
        self.default_client_options = {}
        self.sessions_enabled = False
        self.client = None
        self.conn_lock = threading.Lock()

        if COMPRESSORS:
            self.default_client_options["compressors"] = COMPRESSORS
示例#5
0
    def __init__(self):
        """Create a client and grab essential information from the server."""
        self.connection_attempts = []
        self.connected = False
        self.ismaster = {}
        self.w = None
        self.nodes = set()
        self.replica_set_name = None
        self.cmd_line = None
        self.version = Version(-1)  # Needs to be comparable with Version
        self.auth_enabled = False
        self.test_commands_enabled = False
        self.is_mongos = False
        self.is_rs = False
        self.has_ipv6 = False
        self.ssl = False
        self.ssl_cert_none = False
        self.ssl_certfile = False
        self.server_is_resolvable = is_server_resolvable()
        self.default_client_options = {}
        self.sessions_enabled = False
        self.client = None
        self.conn_lock = threading.Lock()

        if COMPRESSORS:
            self.default_client_options["compressors"] = COMPRESSORS
示例#6
0
 def require_version_max(self, *ver):
     """Run a test only if the server version is at most ``version``."""
     other_version = Version(*ver)
     return self.require(
         lambda: self.version <= other_version,
         "Server version must be at most %s" % str(other_version),
     )
def generate_test_classes(test_path,
                          module=__name__,
                          class_name_prefix='',
                          expected_failures=[],
                          bypass_test_generation_errors=False,
                          **kwargs):
    """Method for generating test classes. Returns a dictionary where keys are
    the names of test classes and values are the test class objects."""
    test_klasses = {}

    def test_base_class_factory(test_spec):
        """Utility that creates the base class to use for test generation.
        This is needed to ensure that cls.TEST_SPEC is appropriately set when
        the metaclass __init__ is invoked."""
        class SpecTestBase(with_metaclass(UnifiedSpecTestMeta)):
            TEST_SPEC = test_spec
            EXPECTED_FAILURES = expected_failures

        return SpecTestBase

    for dirpath, _, filenames in os.walk(test_path):
        dirname = os.path.split(dirpath)[-1]

        for filename in filenames:
            fpath = os.path.join(dirpath, filename)
            with open(fpath) as scenario_stream:
                # Use tz_aware=False to match how CodecOptions decodes
                # dates.
                opts = json_util.JSONOptions(tz_aware=False)
                scenario_def = json_util.loads(scenario_stream.read(),
                                               json_options=opts)

            test_type = os.path.splitext(filename)[0]
            snake_class_name = 'Test%s_%s_%s' % (
                class_name_prefix, dirname.replace(
                    '-', '_'), test_type.replace('-', '_').replace('.', '_'))
            class_name = snake_to_camel(snake_class_name)

            try:
                schema_version = Version.from_string(
                    scenario_def['schemaVersion'])
                mixin_class = _SCHEMA_VERSION_MAJOR_TO_MIXIN_CLASS.get(
                    schema_version[0])
                if mixin_class is None:
                    raise ValueError(
                        "test file '%s' has unsupported schemaVersion '%s'" %
                        (fpath, schema_version))
                module_dict = {'__module__': module}
                module_dict.update(kwargs)
                test_klasses[class_name] = type(class_name, (
                    mixin_class,
                    test_base_class_factory(scenario_def),
                ), module_dict)
            except Exception:
                if bypass_test_generation_errors:
                    continue
                raise

    return test_klasses
def remove_all_users(db):
    if Version.from_client(db.client).at_least(2, 5, 3, -1):
        db.command("dropAllUsersFromDatabase", 1,
                   writeConcern={"w": client_context.w})
    else:
        db = db.client.get_database(
            db.name, write_concern=WriteConcern(w=client_context.w))
        db.system.users.delete_many({})
示例#9
0
def remove_all_users(db):
    if Version.from_client(db.client).at_least(2, 5, 3, -1):
        db.command("dropAllUsersFromDatabase", 1,
                   writeConcern={"w": client_context.w})
    else:
        db = db.client.get_database(
            db.name, write_concern=WriteConcern(w=client_context.w))
        db.system.users.delete_many({})
示例#10
0
    def setUpClass(cls):
        super(MotorTransactionTest, cls).setUpClass()
        if not env.sessions_enabled:
            raise SkipTest("Sessions not supported")

        if not env.is_replica_set:
            raise SkipTest("Requires a replica set")

        if env.version < Version(3, 7):
            raise SkipTest("Requires MongoDB 3.7+")
 def setUp(self):
     super(TestCommandAndReadPreference, self).setUp()
     self.c = ReadPrefTester(
         '%s:%s' % (host, port),
         replicaSet=self.name,
         # Ignore round trip times, to test ReadPreference modes only.
         localThresholdMS=1000*1000)
     if client_context.auth_enabled:
         self.c.admin.authenticate(db_user, db_pwd)
     self.client_version = Version.from_client(self.c)
     self.addCleanup(self.c.drop_database, 'pymongo_test')
示例#12
0
 def setUp(self):
     super(TestCommandAndReadPreference, self).setUp()
     self.c = ReadPrefTester(
         '%s:%s' % (host, port),
         replicaSet=self.name,
         # Ignore round trip times, to test ReadPreference modes only.
         localThresholdMS=1000*1000)
     if client_context.auth_enabled:
         self.c.admin.authenticate(db_user, db_pwd)
     self.client_version = Version.from_client(self.c)
     self.addCleanup(self.c.drop_database, 'pymongo_test')
def is_run_on_requirement_satisfied(requirement):
    topology_satisfied = True
    req_topologies = requirement.get('topologies')
    if req_topologies:
        topology_satisfied = client_context.is_topology_type(req_topologies)

    min_version_satisfied = True
    req_min_server_version = requirement.get('minServerVersion')
    if req_min_server_version:
        min_version_satisfied = Version.from_string(
            req_min_server_version) <= client_context.version

    max_version_satisfied = True
    req_max_server_version = requirement.get('maxServerVersion')
    if req_max_server_version:
        max_version_satisfied = Version.from_string(
            req_max_server_version) >= client_context.version

    return (topology_satisfied and min_version_satisfied
            and max_version_satisfied)
 def setUpClass(cls):
     super(TestCommandAndReadPreference, cls).setUpClass()
     cls.c = ReadPrefTester(
         client_context.pair,
         # Ignore round trip times, to test ReadPreference modes only.
         localThresholdMS=1000*1000)
     cls.client_version = Version.from_client(cls.c)
     # mapReduce fails if the collection does not exist.
     coll = cls.c.pymongo_test.get_collection(
         'test', write_concern=WriteConcern(w=client_context.w))
     coll.insert_one({})
 def setUpClass(cls):
     super(TestCommandAndReadPreference, cls).setUpClass()
     cls.c = ReadPrefTester(
         client_context.pair,
         replicaSet=cls.name,
         # Ignore round trip times, to test ReadPreference modes only.
         localThresholdMS=1000*1000)
     if client_context.auth_enabled:
         cls.c.admin.authenticate(db_user, db_pwd)
     cls.client_version = Version.from_client(cls.c)
     # mapReduce and group fail with no collection
     coll = cls.c.pymongo_test.get_collection(
         'test', write_concern=WriteConcern(w=cls.w))
     coll.insert_one({})
示例#16
0
 def setUpClass(cls):
     super(TestCommandAndReadPreference, cls).setUpClass()
     cls.c = ReadPrefTester(
         client_context.pair,
         replicaSet=cls.name,
         # Ignore round trip times, to test ReadPreference modes only.
         localThresholdMS=1000 * 1000)
     if client_context.auth_enabled:
         cls.c.admin.authenticate(db_user, db_pwd)
     cls.client_version = Version.from_client(cls.c)
     # mapReduce and group fail with no collection
     coll = cls.c.pymongo_test.get_collection(
         'test', write_concern=WriteConcern(w=cls.w))
     coll.insert_one({})
    def setUp(self):
        super(UnifiedSpecTestMixinV1, self).setUp()

        # process schemaVersion
        # note: we check major schema version during class generation
        # note: we do this here because we cannot run assertions in setUpClass
        version = Version.from_string(self.TEST_SPEC['schemaVersion'])
        self.assertLessEqual(
            version, self.SCHEMA_VERSION,
            'expected schema version %s or lower, got %s' %
            (self.SCHEMA_VERSION, version))

        # initialize internals
        self.match_evaluator = MatchEvaluatorUtil(self)
示例#18
0
    def test_RetryableWriteError_error_label(self):
        listener = OvertCommandListener()
        client = rs_or_single_client(
            retryWrites=True, event_listeners=[listener])

        # Ensure collection exists.
        client.pymongo_test.testcoll.insert_one({})

        with self.fail_point(self.fail_insert):
            with self.assertRaises(WriteConcernError) as cm:
                client.pymongo_test.testcoll.insert_one({})
            self.assertTrue(cm.exception.has_error_label(
                'RetryableWriteError'))

        if client_context.version >= Version(4, 4):
            # In MongoDB 4.4+ we rely on the server returning the error label.
            self.assertIn(
                'RetryableWriteError',
                listener.results['succeeded'][-1].reply['errorLabels'])
    def __init__(self):
        """Create a client and grab essential information from the server."""
        self.connected = False
        self.ismaster = {}
        self.w = None
        self.nodes = set()
        self.replica_set_name = None
        self.rs_client = None
        self.cmd_line = None
        self.version = Version(-1)  # Needs to be comparable with Version
        self.auth_enabled = False
        self.test_commands_enabled = False
        self.is_mongos = False
        self.is_rs = False
        self.has_ipv6 = False

        try:
            client = pymongo.MongoClient(host,
                                         port,
                                         serverSelectionTimeoutMS=100)
            client.admin.command('ismaster')  # Can we connect?

            # If so, then reset client to defaults.
            self.client = pymongo.MongoClient(host, port)

        except pymongo.errors.ConnectionFailure:
            self.client = self.rs_or_standalone_client = None
        else:
            self.connected = True
            self.ismaster = self.client.admin.command('ismaster')
            self.w = len(self.ismaster.get("hosts", [])) or 1
            self.nodes = set([(host, port)])
            self.replica_set_name = self.ismaster.get('setName', '')
            self.rs_client = None
            self.version = Version.from_client(self.client)
            if self.replica_set_name:
                self.is_rs = True
                self.rs_client = pymongo.MongoClient(
                    pair, replicaSet=self.replica_set_name)

                nodes = [
                    partition_node(node.lower())
                    for node in self.ismaster.get('hosts', [])
                ]
                nodes.extend([
                    partition_node(node.lower())
                    for node in self.ismaster.get('passives', [])
                ])
                nodes.extend([
                    partition_node(node.lower())
                    for node in self.ismaster.get('arbiters', [])
                ])
                self.nodes = set(nodes)

            self.rs_or_standalone_client = self.rs_client or self.client

            try:
                self.cmd_line = self.client.admin.command('getCmdLineOpts')
            except pymongo.errors.OperationFailure as e:
                msg = e.details.get('errmsg', '')
                if e.code == 13 or 'unauthorized' in msg or 'login' in msg:
                    # Unauthorized.
                    self.auth_enabled = True
                else:
                    raise
            else:
                self.auth_enabled = self._server_started_with_auth()

            if self.auth_enabled:
                # See if db_user already exists.
                self.user_provided = self._check_user_provided()
                if not self.user_provided:
                    roles = {}
                    if self.version.at_least(2, 5, 3, -1):
                        roles = {'roles': ['root']}
                    self.client.admin.add_user(db_user, db_pwd, **roles)
                    self.client.admin.authenticate(db_user, db_pwd)

                if self.rs_client:
                    self.rs_client.admin.authenticate(db_user, db_pwd)

                # May not have this if OperationFailure was raised earlier.
                self.cmd_line = self.client.admin.command('getCmdLineOpts')

            if 'enableTestCommands=1' in self.cmd_line['argv']:
                self.test_commands_enabled = True
            elif 'parsed' in self.cmd_line:
                params = self.cmd_line['parsed'].get('setParameter', [])
                if 'enableTestCommands=1' in params:
                    self.test_commands_enabled = True

            self.is_mongos = (self.ismaster.get('msg') == 'isdbgrid')
            self.has_ipv6 = self._server_started_with_ipv6()
示例#20
0
class ClientContext(object):

    def __init__(self):
        """Create a client and grab essential information from the server."""
        self.connected = False
        self.ismaster = {}
        self.w = None
        self.nodes = set()
        self.replica_set_name = None
        self.rs_client = None
        self.cmd_line = None
        self.version = Version(-1)  # Needs to be comparable with Version
        self.auth_enabled = False
        self.test_commands_enabled = False
        self.is_mongos = False
        self.is_rs = False
        self.has_ipv6 = False

        try:
            client = pymongo.MongoClient(host, port,
                                         serverSelectionTimeoutMS=100)
            client.admin.command('ismaster')  # Can we connect?

            # If so, then reset client to defaults.
            self.client = pymongo.MongoClient(host, port)

        except pymongo.errors.ConnectionFailure:
            self.client = None
        else:
            self.connected = True
            self.ismaster = self.client.admin.command('ismaster')
            self.w = len(self.ismaster.get("hosts", [])) or 1
            self.nodes = set([(host, port)])
            self.replica_set_name = self.ismaster.get('setName', '')
            self.rs_client = None
            self.version = Version.from_client(self.client)
            if self.replica_set_name:
                self.is_rs = True
                self.rs_client = pymongo.MongoClient(
                    pair, replicaSet=self.replica_set_name)

                nodes = [partition_node(node)
                         for node in self.ismaster.get('hosts', [])]
                nodes.extend([partition_node(node)
                              for node in self.ismaster.get('passives', [])])
                nodes.extend([partition_node(node)
                              for node in self.ismaster.get('arbiters', [])])
                self.nodes = set(nodes)

            self.rs_or_standalone_client = self.rs_client or self.client

            try:
                self.cmd_line = self.client.admin.command('getCmdLineOpts')
            except pymongo.errors.OperationFailure as e:
                msg = e.details.get('errmsg', '')
                if e.code == 13 or 'unauthorized' in msg or 'login' in msg:
                    # Unauthorized.
                    self.auth_enabled = True
                else:
                    raise
            else:
                self.auth_enabled = self._server_started_with_auth()

            if self.auth_enabled:
                # See if db_user already exists.
                self.user_provided = self._check_user_provided()
                if not self.user_provided:
                    roles = {}
                    if self.version.at_least(2, 5, 3, -1):
                        roles = {'roles': ['root']}
                    self.client.admin.add_user(db_user, db_pwd, **roles)
                    self.client.admin.authenticate(db_user, db_pwd)

                if self.rs_client:
                    self.rs_client.admin.authenticate(db_user, db_pwd)

                # May not have this if OperationFailure was raised earlier.
                self.cmd_line = self.client.admin.command('getCmdLineOpts')

            if 'enableTestCommands=1' in self.cmd_line['argv']:
                self.test_commands_enabled = True
            elif 'parsed' in self.cmd_line:
                params = self.cmd_line['parsed'].get('setParameter', [])
                if 'enableTestCommands=1' in params:
                    self.test_commands_enabled = True

            self.is_mongos = (self.ismaster.get('msg') == 'isdbgrid')
            self.has_ipv6 = self._server_started_with_ipv6()

    def _check_user_provided(self):
        try:
            self.client.admin.authenticate(db_user, db_pwd)
            return True
        except pymongo.errors.OperationFailure as e:
            msg = e.details.get('errmsg', '')
            if e.code == 18 or 'auth fails' in msg:
                # Auth failed.
                return False
            else:
                raise

    def _server_started_with_auth(self):
        # MongoDB >= 2.0
        if 'parsed' in self.cmd_line:
            parsed = self.cmd_line['parsed']
            # MongoDB >= 2.6
            if 'security' in parsed:
                security = parsed['security']
                # >= rc3
                if 'authorization' in security:
                    return security['authorization'] == 'enabled'
                # < rc3
                return (security.get('auth', False) or
                        bool(security.get('keyFile')))
            return parsed.get('auth', False) or bool(parsed.get('keyFile'))
        # Legacy
        argv = self.cmd_line['argv']
        return '--auth' in argv or '--keyFile' in argv

    def _server_started_with_ipv6(self):
        if not socket.has_ipv6:
            return False

        if 'parsed' in self.cmd_line:
            if not self.cmd_line['parsed'].get('net', {}).get('ipv6'):
                return False
        else:
            if '--ipv6' not in self.cmd_line['argv']:
                return False

        # The server was started with --ipv6. Is there an IPv6 route to it?
        try:
            for info in socket.getaddrinfo(host, port):
                if info[0] == socket.AF_INET6:
                    return True
        except socket.error:
            pass

        return False

    def _require(self, condition, msg, func=None):
        def make_wrapper(f):
            @wraps(f)
            def wrap(*args, **kwargs):
                # Always raise SkipTest if we can't connect to MongoDB
                if not self.connected:
                    raise SkipTest("Cannot connect to MongoDB on %s" % pair)
                if condition:
                    return f(*args, **kwargs)
                raise SkipTest(msg)
            return wrap

        if func is None:
            def decorate(f):
                return make_wrapper(f)
            return decorate
        return make_wrapper(func)

    def require_connection(self, func):
        """Run a test only if we can connect to MongoDB."""
        return self._require(self.connected,
                             "Cannot connect to MongoDB on %s" % pair,
                             func=func)

    def require_version_min(self, *ver):
        """Run a test only if the server version is at least ``version``."""
        other_version = Version(*ver)
        return self._require(self.version >= other_version,
                             "Server version must be at least %s"
                             % str(other_version))

    def require_version_max(self, *ver):
        """Run a test only if the server version is at most ``version``."""
        other_version = Version(*ver)
        return self._require(self.version <= other_version,
                             "Server version must be at most %s"
                             % str(other_version))

    def require_auth(self, func):
        """Run a test only if the server is running with auth enabled."""
        return self.check_auth_with_sharding(
            self._require(self.auth_enabled,
                          "Authentication is not enabled on the server",
                          func=func))

    def require_no_auth(self, func):
        """Run a test only if the server is running without auth enabled."""
        return self._require(not self.auth_enabled,
                             "Authentication must not be enabled on the server",
                             func=func)

    def require_replica_set(self, func):
        """Run a test only if the client is connected to a replica set."""
        return self._require(self.is_rs,
                             "Not connected to a replica set",
                             func=func)

    def require_no_replica_set(self, func):
        """Run a test if the client is *not* connected to a replica set."""
        return self._require(
            not self.is_rs,
            "Connected to a replica set, not a standalone mongod",
            func=func)

    def require_ipv6(self, func):
        """Run a test only if the client can connect to a server via IPv6."""
        return self._require(self.has_ipv6,
                             "No IPv6",
                             func=func)

    def require_no_mongos(self, func):
        """Run a test only if the client is not connected to a mongos."""
        return self._require(not self.is_mongos,
                             "Must be connected to a mongod, not a mongos",
                             func=func)

    def require_mongos(self, func):
        """Run a test only if the client is connected to a mongos."""
        return self._require(self.is_mongos,
                             "Must be connected to a mongos",
                             func=func)

    def check_auth_with_sharding(self, func):
        """Skip a test when connected to mongos < 2.0 and running with auth."""
        condition = not (self.auth_enabled and
                         self.is_mongos and self.version < (2,))
        return self._require(condition,
                             "Auth with sharding requires MongoDB >= 2.0.0",
                             func=func)

    def require_test_commands(self, func):
        """Run a test only if the server has test commands enabled."""
        return self._require(self.test_commands_enabled,
                             "Test commands must be enabled",
                             func=func)
示例#21
0
 def setup_version(self):
     """Set self.version to the server's version."""
     self.version = Version.from_client(self.sync_cx)
class ClientContext(object):
    def __init__(self):
        """Create a client and grab essential information from the server."""
        self.connected = False
        self.ismaster = {}
        self.w = None
        self.nodes = set()
        self.replica_set_name = None
        self.rs_client = None
        self.cmd_line = None
        self.version = Version(-1)  # Needs to be comparable with Version
        self.auth_enabled = False
        self.test_commands_enabled = False
        self.is_mongos = False
        self.is_rs = False
        self.has_ipv6 = False

        try:
            client = pymongo.MongoClient(host,
                                         port,
                                         serverSelectionTimeoutMS=100)
            client.admin.command('ismaster')  # Can we connect?

            # If so, then reset client to defaults.
            self.client = pymongo.MongoClient(host, port)

        except pymongo.errors.ConnectionFailure:
            self.client = self.rs_or_standalone_client = None
        else:
            self.connected = True
            self.ismaster = self.client.admin.command('ismaster')
            self.w = len(self.ismaster.get("hosts", [])) or 1
            self.nodes = set([(host, port)])
            self.replica_set_name = self.ismaster.get('setName', '')
            self.rs_client = None
            self.version = Version.from_client(self.client)
            if self.replica_set_name:
                self.is_rs = True
                self.rs_client = pymongo.MongoClient(
                    pair, replicaSet=self.replica_set_name)

                nodes = [
                    partition_node(node.lower())
                    for node in self.ismaster.get('hosts', [])
                ]
                nodes.extend([
                    partition_node(node.lower())
                    for node in self.ismaster.get('passives', [])
                ])
                nodes.extend([
                    partition_node(node.lower())
                    for node in self.ismaster.get('arbiters', [])
                ])
                self.nodes = set(nodes)

            self.rs_or_standalone_client = self.rs_client or self.client

            try:
                self.cmd_line = self.client.admin.command('getCmdLineOpts')
            except pymongo.errors.OperationFailure as e:
                msg = e.details.get('errmsg', '')
                if e.code == 13 or 'unauthorized' in msg or 'login' in msg:
                    # Unauthorized.
                    self.auth_enabled = True
                else:
                    raise
            else:
                self.auth_enabled = self._server_started_with_auth()

            if self.auth_enabled:
                # See if db_user already exists.
                self.user_provided = self._check_user_provided()
                if not self.user_provided:
                    roles = {}
                    if self.version.at_least(2, 5, 3, -1):
                        roles = {'roles': ['root']}
                    self.client.admin.add_user(db_user, db_pwd, **roles)
                    self.client.admin.authenticate(db_user, db_pwd)

                if self.rs_client:
                    self.rs_client.admin.authenticate(db_user, db_pwd)

                # May not have this if OperationFailure was raised earlier.
                self.cmd_line = self.client.admin.command('getCmdLineOpts')

            if 'enableTestCommands=1' in self.cmd_line['argv']:
                self.test_commands_enabled = True
            elif 'parsed' in self.cmd_line:
                params = self.cmd_line['parsed'].get('setParameter', [])
                if 'enableTestCommands=1' in params:
                    self.test_commands_enabled = True

            self.is_mongos = (self.ismaster.get('msg') == 'isdbgrid')
            self.has_ipv6 = self._server_started_with_ipv6()

    def _check_user_provided(self):
        try:
            self.client.admin.authenticate(db_user, db_pwd)
            return True
        except pymongo.errors.OperationFailure as e:
            msg = e.details.get('errmsg', '')
            if e.code == 18 or 'auth fails' in msg:
                # Auth failed.
                return False
            else:
                raise

    def _server_started_with_auth(self):
        # MongoDB >= 2.0
        if 'parsed' in self.cmd_line:
            parsed = self.cmd_line['parsed']
            # MongoDB >= 2.6
            if 'security' in parsed:
                security = parsed['security']
                # >= rc3
                if 'authorization' in security:
                    return security['authorization'] == 'enabled'
                # < rc3
                return (security.get('auth', False)
                        or bool(security.get('keyFile')))
            return parsed.get('auth', False) or bool(parsed.get('keyFile'))
        # Legacy
        argv = self.cmd_line['argv']
        return '--auth' in argv or '--keyFile' in argv

    def _server_started_with_ipv6(self):
        if not socket.has_ipv6:
            return False

        if 'parsed' in self.cmd_line:
            if not self.cmd_line['parsed'].get('net', {}).get('ipv6'):
                return False
        else:
            if '--ipv6' not in self.cmd_line['argv']:
                return False

        # The server was started with --ipv6. Is there an IPv6 route to it?
        try:
            for info in socket.getaddrinfo(host, port):
                if info[0] == socket.AF_INET6:
                    return True
        except socket.error:
            pass

        return False

    def _require(self, condition, msg, func=None):
        def make_wrapper(f):
            @wraps(f)
            def wrap(*args, **kwargs):
                # Always raise SkipTest if we can't connect to MongoDB
                if not self.connected:
                    raise SkipTest("Cannot connect to MongoDB on %s" % pair)
                if condition:
                    return f(*args, **kwargs)
                raise SkipTest(msg)

            return wrap

        if func is None:

            def decorate(f):
                return make_wrapper(f)

            return decorate
        return make_wrapper(func)

    def require_connection(self, func):
        """Run a test only if we can connect to MongoDB."""
        return self._require(self.connected,
                             "Cannot connect to MongoDB on %s" % pair,
                             func=func)

    def require_version_min(self, *ver):
        """Run a test only if the server version is at least ``version``."""
        other_version = Version(*ver)
        return self._require(
            self.version >= other_version,
            "Server version must be at least %s" % str(other_version))

    def require_version_max(self, *ver):
        """Run a test only if the server version is at most ``version``."""
        other_version = Version(*ver)
        return self._require(
            self.version <= other_version,
            "Server version must be at most %s" % str(other_version))

    def require_auth(self, func):
        """Run a test only if the server is running with auth enabled."""
        return self.check_auth_with_sharding(
            self._require(self.auth_enabled,
                          "Authentication is not enabled on the server",
                          func=func))

    def require_no_auth(self, func):
        """Run a test only if the server is running without auth enabled."""
        return self._require(
            not self.auth_enabled,
            "Authentication must not be enabled on the server",
            func=func)

    def require_replica_set(self, func):
        """Run a test only if the client is connected to a replica set."""
        return self._require(self.is_rs,
                             "Not connected to a replica set",
                             func=func)

    def require_no_replica_set(self, func):
        """Run a test if the client is *not* connected to a replica set."""
        return self._require(
            not self.is_rs,
            "Connected to a replica set, not a standalone mongod",
            func=func)

    def require_ipv6(self, func):
        """Run a test only if the client can connect to a server via IPv6."""
        return self._require(self.has_ipv6, "No IPv6", func=func)

    def require_no_mongos(self, func):
        """Run a test only if the client is not connected to a mongos."""
        return self._require(not self.is_mongos,
                             "Must be connected to a mongod, not a mongos",
                             func=func)

    def require_mongos(self, func):
        """Run a test only if the client is connected to a mongos."""
        return self._require(self.is_mongos,
                             "Must be connected to a mongos",
                             func=func)

    def check_auth_with_sharding(self, func):
        """Skip a test when connected to mongos < 2.0 and running with auth."""
        condition = not (self.auth_enabled and self.is_mongos
                         and self.version < (2, ))
        return self._require(condition,
                             "Auth with sharding requires MongoDB >= 2.0.0",
                             func=func)

    def require_test_commands(self, func):
        """Run a test only if the server has test commands enabled."""
        return self._require(self.test_commands_enabled,
                             "Test commands must be enabled",
                             func=func)
示例#23
0
class ClientContext(object):

    def __init__(self):
        """Create a client and grab essential information from the server."""
        self.connected = False
        self.ismaster = {}
        self.w = None
        self.nodes = set()
        self.replica_set_name = None
        self.cmd_line = None
        self.version = Version(-1)  # Needs to be comparable with Version
        self.auth_enabled = False
        self.test_commands_enabled = False
        self.is_mongos = False
        self.is_rs = False
        self.has_ipv6 = False
        self.ssl = False
        self.ssl_cert_none = False
        self.ssl_certfile = False
        self.server_is_resolvable = is_server_resolvable()
        self.ssl_client_options = {}
        self.client = _connect(host, port)

        if HAVE_SSL and not self.client:
            # Is MongoDB configured for SSL?
            self.client = _connect(host, port, **_SSL_OPTIONS)
            if self.client:
                self.ssl = True
                self.ssl_client_options = _SSL_OPTIONS
                self.ssl_certfile = True
                if _SSL_OPTIONS.get('ssl_cert_reqs') == ssl.CERT_NONE:
                    self.ssl_cert_none = True

        if self.client:
            self.connected = True
            ismaster = self.client.admin.command('ismaster')
            if 'setName' in ismaster:
                self.replica_set_name = ismaster['setName']
                self.is_rs = True
                # It doesn't matter which member we use as the seed here.
                self.client = pymongo.MongoClient(
                    host,
                    port,
                    replicaSet=self.replica_set_name,
                    **self.ssl_client_options)
                # Get the authoritative ismaster result from the primary.
                self.ismaster = self.client.admin.command('ismaster')
                nodes = [partition_node(node.lower())
                         for node in self.ismaster.get('hosts', [])]
                nodes.extend([partition_node(node.lower())
                              for node in self.ismaster.get('passives', [])])
                nodes.extend([partition_node(node.lower())
                              for node in self.ismaster.get('arbiters', [])])
                self.nodes = set(nodes)
            else:
                self.ismaster = ismaster
                self.nodes = set([(host, port)])
            self.w = len(self.ismaster.get("hosts", [])) or 1
            self.version = Version.from_client(self.client)

            try:
                self.cmd_line = self.client.admin.command('getCmdLineOpts')
            except pymongo.errors.OperationFailure as e:
                msg = e.details.get('errmsg', '')
                if e.code == 13 or 'unauthorized' in msg or 'login' in msg:
                    # Unauthorized.
                    self.auth_enabled = True
                else:
                    raise
            else:
                self.auth_enabled = self._server_started_with_auth()

            if self.auth_enabled:
                # See if db_user already exists.
                self.user_provided = self._check_user_provided()
                if not self.user_provided:
                    roles = {}
                    if self.version.at_least(2, 5, 3, -1):
                        roles = {'roles': ['root']}
                    self.client.admin.add_user(db_user, db_pwd, **roles)
                    self.client.admin.authenticate(db_user, db_pwd)

                # May not have this if OperationFailure was raised earlier.
                self.cmd_line = self.client.admin.command('getCmdLineOpts')

            if 'enableTestCommands=1' in self.cmd_line['argv']:
                self.test_commands_enabled = True
            elif 'parsed' in self.cmd_line:
                params = self.cmd_line['parsed'].get('setParameter', [])
                if 'enableTestCommands=1' in params:
                    self.test_commands_enabled = True
                else:
                    params = self.cmd_line['parsed'].get('setParameter', {})
                    if params.get('enableTestCommands') == '1':
                        self.test_commands_enabled = True

            self.is_mongos = (self.ismaster.get('msg') == 'isdbgrid')
            self.has_ipv6 = self._server_started_with_ipv6()

    @property
    def host(self):
        if self.is_rs:
            primary = self.client.primary
            return primary[0] if primary is not None else host
        return host

    @property
    def port(self):
        if self.is_rs:
            primary = self.client.primary
            return primary[1] if primary is not None else port
        return port

    @property
    def pair(self):
        return "%s:%d" % (self.host, self.port)

    @property
    def has_secondaries(self):
        if not self.client:
            return False
        return bool(len(self.client.secondaries))

    def _check_user_provided(self):
        try:
            self.client.admin.authenticate(db_user, db_pwd)
            return True
        except pymongo.errors.OperationFailure as e:
            msg = e.details.get('errmsg', '')
            if e.code == 18 or 'auth fails' in msg:
                # Auth failed.
                return False
            else:
                raise

    def _server_started_with_auth(self):
        # MongoDB >= 2.0
        if 'parsed' in self.cmd_line:
            parsed = self.cmd_line['parsed']
            # MongoDB >= 2.6
            if 'security' in parsed:
                security = parsed['security']
                # >= rc3
                if 'authorization' in security:
                    return security['authorization'] == 'enabled'
                # < rc3
                return (security.get('auth', False) or
                        bool(security.get('keyFile')))
            return parsed.get('auth', False) or bool(parsed.get('keyFile'))
        # Legacy
        argv = self.cmd_line['argv']
        return '--auth' in argv or '--keyFile' in argv

    def _server_started_with_ipv6(self):
        if not socket.has_ipv6:
            return False

        if 'parsed' in self.cmd_line:
            if not self.cmd_line['parsed'].get('net', {}).get('ipv6'):
                return False
        else:
            if '--ipv6' not in self.cmd_line['argv']:
                return False

        # The server was started with --ipv6. Is there an IPv6 route to it?
        try:
            for info in socket.getaddrinfo(self.host, self.port):
                if info[0] == socket.AF_INET6:
                    return True
        except socket.error:
            pass

        return False

    def _require(self, condition, msg, func=None):
        def make_wrapper(f):
            @wraps(f)
            def wrap(*args, **kwargs):
                # Always raise SkipTest if we can't connect to MongoDB
                if not self.connected:
                    raise SkipTest(
                        "Cannot connect to MongoDB on %s" % (self.pair,))
                if condition:
                    return f(*args, **kwargs)
                raise SkipTest(msg)
            return wrap

        if func is None:
            def decorate(f):
                return make_wrapper(f)
            return decorate
        return make_wrapper(func)

    def require_connection(self, func):
        """Run a test only if we can connect to MongoDB."""
        return self._require(
            self.connected,
            "Cannot connect to MongoDB on %s" % (self.pair,),
            func=func)

    def require_version_min(self, *ver):
        """Run a test only if the server version is at least ``version``."""
        other_version = Version(*ver)
        return self._require(self.version >= other_version,
                             "Server version must be at least %s"
                             % str(other_version))

    def require_version_max(self, *ver):
        """Run a test only if the server version is at most ``version``."""
        other_version = Version(*ver)
        return self._require(self.version <= other_version,
                             "Server version must be at most %s"
                             % str(other_version))

    def require_auth(self, func):
        """Run a test only if the server is running with auth enabled."""
        return self.check_auth_with_sharding(
            self._require(self.auth_enabled,
                          "Authentication is not enabled on the server",
                          func=func))

    def require_no_auth(self, func):
        """Run a test only if the server is running without auth enabled."""
        return self._require(not self.auth_enabled,
                             "Authentication must not be enabled on the server",
                             func=func)

    def require_replica_set(self, func):
        """Run a test only if the client is connected to a replica set."""
        return self._require(self.is_rs,
                             "Not connected to a replica set",
                             func=func)

    def require_secondaries_count(self, count):
        """Run a test only if the client is connected to a replica set that has
        `count` secondaries.
        """
        sec_count = 0 if not self.client else len(self.client.secondaries)
        return self._require(sec_count >= count,
                             "Need %d secondaries, %d available"
                             % (count, sec_count))

    def require_no_replica_set(self, func):
        """Run a test if the client is *not* connected to a replica set."""
        return self._require(
            not self.is_rs,
            "Connected to a replica set, not a standalone mongod",
            func=func)

    def require_ipv6(self, func):
        """Run a test only if the client can connect to a server via IPv6."""
        return self._require(self.has_ipv6,
                             "No IPv6",
                             func=func)

    def require_no_mongos(self, func):
        """Run a test only if the client is not connected to a mongos."""
        return self._require(not self.is_mongos,
                             "Must be connected to a mongod, not a mongos",
                             func=func)

    def require_mongos(self, func):
        """Run a test only if the client is connected to a mongos."""
        return self._require(self.is_mongos,
                             "Must be connected to a mongos",
                             func=func)

    def check_auth_with_sharding(self, func):
        """Skip a test when connected to mongos < 2.0 and running with auth."""
        condition = not (self.auth_enabled and
                         self.is_mongos and self.version < (2,))
        return self._require(condition,
                             "Auth with sharding requires MongoDB >= 2.0.0",
                             func=func)

    def require_test_commands(self, func):
        """Run a test only if the server has test commands enabled."""
        return self._require(self.test_commands_enabled,
                             "Test commands must be enabled",
                             func=func)

    def require_ssl(self, func):
        """Run a test only if the client can connect over SSL."""
        return self._require(self.ssl,
                             "Must be able to connect via SSL",
                             func=func)

    def require_no_ssl(self, func):
        """Run a test only if the client can connect over SSL."""
        return self._require(not self.ssl,
                             "Must be able to connect without SSL",
                             func=func)

    def require_ssl_cert_none(self, func):
        """Run a test only if the client can connect with ssl.CERT_NONE."""
        return self._require(self.ssl_cert_none,
                             "Must be able to connect with ssl.CERT_NONE",
                             func=func)

    def require_ssl_certfile(self, func):
        """Run a test only if the client can connect with ssl_certfile."""
        return self._require(self.ssl_certfile,
                             "Must be able to connect with ssl_certfile",
                             func=func)

    def require_server_resolvable(self, func):
        """Run a test only if the hostname 'server' is resolvable."""
        return self._require(self.server_is_resolvable,
                             "No hosts entry for 'server'. Cannot validate "
                             "hostname in the certificate",
                             func=func)
    def _init_client(self):
        self.client = self._connect(host, port)

        if self.client is not None:
            # Return early when connected to dataLake as mongohoused does not
            # support the getCmdLineOpts command and is tested without TLS.
            build_info = self.client.admin.command('buildInfo')
            if 'dataLake' in build_info:
                self.is_data_lake = True
                self.auth_enabled = True
                self.client = self._connect(host,
                                            port,
                                            username=db_user,
                                            password=db_pwd)
                self.connected = True
                return

        if HAVE_SSL and not self.client:
            # Is MongoDB configured for SSL?
            self.client = self._connect(host, port, **TLS_OPTIONS)
            if self.client:
                self.tls = True
                self.default_client_options.update(TLS_OPTIONS)
                self.ssl_certfile = True

        if self.client:
            self.connected = True

            try:
                self.cmd_line = self.client.admin.command('getCmdLineOpts')
            except pymongo.errors.OperationFailure as e:
                msg = e.details.get('errmsg', '')
                if e.code == 13 or 'unauthorized' in msg or 'login' in msg:
                    # Unauthorized.
                    self.auth_enabled = True
                else:
                    raise
            else:
                self.auth_enabled = self._server_started_with_auth()

            if self.auth_enabled:
                # See if db_user already exists.
                if not self._check_user_provided():
                    _create_user(self.client.admin, db_user, db_pwd)

                self.client = self._connect(host,
                                            port,
                                            username=db_user,
                                            password=db_pwd,
                                            replicaSet=self.replica_set_name,
                                            **self.default_client_options)

                # May not have this if OperationFailure was raised earlier.
                self.cmd_line = self.client.admin.command('getCmdLineOpts')

            self.server_status = self.client.admin.command('serverStatus')
            if self.storage_engine == "mmapv1":
                # MMAPv1 does not support retryWrites=True.
                self.default_client_options['retryWrites'] = False

            ismaster = self.ismaster
            self.sessions_enabled = 'logicalSessionTimeoutMinutes' in ismaster

            if 'setName' in ismaster:
                self.replica_set_name = str(ismaster['setName'])
                self.is_rs = True
                if self.auth_enabled:
                    # It doesn't matter which member we use as the seed here.
                    self.client = pymongo.MongoClient(
                        host,
                        port,
                        username=db_user,
                        password=db_pwd,
                        replicaSet=self.replica_set_name,
                        **self.default_client_options)
                else:
                    self.client = pymongo.MongoClient(
                        host,
                        port,
                        replicaSet=self.replica_set_name,
                        **self.default_client_options)

                # Get the authoritative ismaster result from the primary.
                ismaster = self.ismaster
                nodes = [
                    partition_node(node.lower())
                    for node in ismaster.get('hosts', [])
                ]
                nodes.extend([
                    partition_node(node.lower())
                    for node in ismaster.get('passives', [])
                ])
                nodes.extend([
                    partition_node(node.lower())
                    for node in ismaster.get('arbiters', [])
                ])
                self.nodes = set(nodes)
            else:
                self.nodes = set([(host, port)])
            self.w = len(ismaster.get("hosts", [])) or 1
            self.version = Version.from_client(self.client)
            self.server_parameters = self.client.admin.command(
                'getParameter', '*')

            if 'enableTestCommands=1' in self.cmd_line['argv']:
                self.test_commands_enabled = True
            elif 'parsed' in self.cmd_line:
                params = self.cmd_line['parsed'].get('setParameter', [])
                if 'enableTestCommands=1' in params:
                    self.test_commands_enabled = True
                else:
                    params = self.cmd_line['parsed'].get('setParameter', {})
                    if params.get('enableTestCommands') == '1':
                        self.test_commands_enabled = True

            self.is_mongos = (self.ismaster.get('msg') == 'isdbgrid')
            self.has_ipv6 = self._server_started_with_ipv6()
            if self.is_mongos:
                # Check for another mongos on the next port.
                address = self.client.address
                next_address = address[0], address[1] + 1
                self.mongoses.append(address)
                mongos_client = self._connect(*next_address,
                                              **self.default_client_options)
                if mongos_client:
                    ismaster = mongos_client.admin.command('ismaster')
                    if ismaster.get('msg') == 'isdbgrid':
                        self.mongoses.append(next_address)
示例#25
0
 def require_version_min(self, *ver):
     """Run a test only if the server version is at least ``version``."""
     other_version = Version(*ver)
     return self._require(
         self.version >= other_version,
         "Server version must be at least %s" % str(other_version))
示例#26
0
    def __init__(self):
        """Create a client and grab essential information from the server."""
        # Seed host. This may be updated further down.
        self.host, self.port = host, port
        self.connected = False
        self.ismaster = {}
        self.w = None
        self.nodes = set()
        self.replica_set_name = None
        self.rs_client = None
        self.cmd_line = None
        self.version = Version(-1)  # Needs to be comparable with Version
        self.auth_enabled = False
        self.test_commands_enabled = False
        self.is_mongos = False
        self.is_rs = False
        self.has_ipv6 = False
        self.ssl_cert_none = False
        self.ssl_certfile = False
        self.server_is_resolvable = is_server_resolvable()

        self.client = self.rs_or_standalone_client = None

        def connect(**kwargs):
            try:
                client = pymongo.MongoClient(self.host,
                                             self.port,
                                             serverSelectionTimeoutMS=100,
                                             **kwargs)
                client.admin.command('ismaster')  # Can we connect?
                # If connected, then return client with default timeout
                return pymongo.MongoClient(self.host, self.port, **kwargs)
            except pymongo.errors.ConnectionFailure:
                return None

        self.client = connect()

        if HAVE_SSL and not self.client:
            # Is MongoDB configured for SSL?
            self.client = connect(ssl=True, ssl_cert_reqs=ssl.CERT_NONE)
            if self.client:
                self.ssl_cert_none = True

            # Can client connect with certfile?
            client = connect(
                ssl=True,
                ssl_cert_reqs=ssl.CERT_NONE,
                ssl_certfile=CLIENT_PEM,
            )
            if client:
                self.ssl_certfile = True
                self.client = client

        if self.client:
            self.connected = True
            self.ismaster = self.client.admin.command('ismaster')
            self.w = len(self.ismaster.get("hosts", [])) or 1
            self.nodes = set([(self.host, self.port)])
            self.replica_set_name = self.ismaster.get('setName', '')
            self.rs_client = None
            self.version = Version.from_client(self.client)
            if self.replica_set_name:
                self.is_rs = True
                self.rs_client = pymongo.MongoClient(
                    self.ismaster['primary'], replicaSet=self.replica_set_name)
                # Force connection
                self.rs_client.admin.command('ismaster')
                self.host, self.port = self.rs_client.primary
                self.client = connect()

                nodes = [
                    partition_node(node.lower())
                    for node in self.ismaster.get('hosts', [])
                ]
                nodes.extend([
                    partition_node(node.lower())
                    for node in self.ismaster.get('passives', [])
                ])
                nodes.extend([
                    partition_node(node.lower())
                    for node in self.ismaster.get('arbiters', [])
                ])
                self.nodes = set(nodes)

            self.rs_or_standalone_client = self.rs_client or self.client

            try:
                self.cmd_line = self.client.admin.command('getCmdLineOpts')
            except pymongo.errors.OperationFailure as e:
                msg = e.details.get('errmsg', '')
                if e.code == 13 or 'unauthorized' in msg or 'login' in msg:
                    # Unauthorized.
                    self.auth_enabled = True
                else:
                    raise
            else:
                self.auth_enabled = self._server_started_with_auth()

            if self.auth_enabled:
                # See if db_user already exists.
                self.user_provided = self._check_user_provided()
                if not self.user_provided:
                    roles = {}
                    if self.version.at_least(2, 5, 3, -1):
                        roles = {'roles': ['root']}
                    self.client.admin.add_user(db_user, db_pwd, **roles)
                    self.client.admin.authenticate(db_user, db_pwd)

                if self.rs_client:
                    self.rs_client.admin.authenticate(db_user, db_pwd)

                # May not have this if OperationFailure was raised earlier.
                self.cmd_line = self.client.admin.command('getCmdLineOpts')

            if 'enableTestCommands=1' in self.cmd_line['argv']:
                self.test_commands_enabled = True
            elif 'parsed' in self.cmd_line:
                params = self.cmd_line['parsed'].get('setParameter', [])
                if 'enableTestCommands=1' in params:
                    self.test_commands_enabled = True

            self.is_mongos = (self.ismaster.get('msg') == 'isdbgrid')
            self.has_ipv6 = self._server_started_with_ipv6()

        # Do this after we connect so we know who the primary is.
        self.pair = "%s:%d" % (self.host, self.port)
示例#27
0
    def test_mongodb_x509_auth(self):
        # Expects the server to be running with the server.pem and ca.pem
        # as well as --auth
        #
        #   --sslPEMKeyFile=/path/to/pymongo/test/certificates/server.pem
        #   --sslCAFile=/path/to/pymongo/test/certificates/ca.pem
        #   --auth
        if not CERT_SSL:
            raise SkipTest("No mongod available over SSL with certs")

        if not Version.from_client(ssl_client).at_least(2, 5, 3, -1):
            raise SkipTest("MONGODB-X509 tests require MongoDB 2.5.3 or newer")
        if not server_started_with_auth(ssl_client):
            raise SkipTest('Authentication is not enabled on server')

        self.addCleanup(ssl_client['$external'].logout)
        self.addCleanup(remove_all_users, ssl_client['$external'])
        self.addCleanup(remove_all_users, ssl_client.admin)

        ssl_client.admin.add_user('admin', 'pass')
        ssl_client.admin.authenticate('admin', 'pass')

        # Give admin all necessary privileges.
        ssl_client['$external'].add_user(MONGODB_X509_USERNAME,
                                         roles=[{
                                             'role': 'readWriteAnyDatabase',
                                             'db': 'admin'
                                         }, {
                                             'role': 'userAdminAnyDatabase',
                                             'db': 'admin'
                                         }])

        ssl_client.admin.logout()

        coll = ssl_client.pymongo_test.test
        self.assertRaises(OperationFailure, coll.count)
        self.assertTrue(
            ssl_client.admin.authenticate(MONGODB_X509_USERNAME,
                                          mechanism='MONGODB-X509'))
        coll.drop()
        uri = ('mongodb://%s@%s:%d/?authMechanism='
               'MONGODB-X509' %
               (quote_plus(MONGODB_X509_USERNAME), host, port))
        # SSL options aren't supported in the URI...
        self.assertTrue(MongoClient(uri, ssl=True, ssl_certfile=CLIENT_PEM))

        # Should require a username
        uri = ('mongodb://%s:%d/?authMechanism=MONGODB-X509' % (host, port))
        client_bad = MongoClient(uri,
                                 ssl=True,
                                 ssl_cert_reqs="CERT_NONE",
                                 ssl_certfile=CLIENT_PEM)
        self.assertRaises(OperationFailure,
                          client_bad.pymongo_test.test.delete_one, {})

        # Auth should fail if username and certificate do not match
        uri = ('mongodb://%s@%s:%d/?authMechanism='
               'MONGODB-X509' % (quote_plus("not the username"), host, port))

        bad_client = MongoClient(uri,
                                 ssl=True,
                                 ssl_cert_reqs="CERT_NONE",
                                 ssl_certfile=CLIENT_PEM)
        with self.assertRaises(OperationFailure):
            bad_client.pymongo_test.test.find_one()

        self.assertRaises(OperationFailure,
                          ssl_client.admin.authenticate,
                          "not the username",
                          mechanism="MONGODB-X509")

        # Invalid certificate (using CA certificate as client certificate)
        uri = ('mongodb://%s@%s:%d/?authMechanism='
               'MONGODB-X509' %
               (quote_plus(MONGODB_X509_USERNAME), host, port))
        try:
            connected(
                MongoClient(uri,
                            ssl=True,
                            ssl_cert_reqs="CERT_NONE",
                            ssl_certfile=CA_PEM,
                            serverSelectionTimeoutMS=100))
        except OperationFailure:
            pass
        else:
            self.fail("Invalid certificate accepted.")
示例#28
0
    def __init__(self):
        """Create a client and grab essential information from the server."""
        # Seed host. This may be updated further down.
        self.host, self.port = host, port
        self.connected = False
        self.ismaster = {}
        self.w = None
        self.nodes = set()
        self.replica_set_name = None
        self.cmd_line = None
        self.version = Version(-1)  # Needs to be comparable with Version
        self.auth_enabled = False
        self.test_commands_enabled = False
        self.is_mongos = False
        self.is_rs = False
        self.has_ipv6 = False
        self.ssl = False
        self.ssl_cert_none = False
        self.ssl_certfile = False
        self.server_is_resolvable = is_server_resolvable()
        self.ssl_client_options = {}
        self.client = _connect(self.host, self.port)

        if HAVE_SSL and not self.client:
            # Is MongoDB configured for SSL?
            self.client = _connect(self.host, self.port, **_SSL_OPTIONS)
            if self.client:
                self.ssl = True
                self.ssl_client_options = _SSL_OPTIONS
                self.ssl_certfile = True
                if _SSL_OPTIONS.get('ssl_cert_reqs') == ssl.CERT_NONE:
                    self.ssl_cert_none = True

        if self.client:
            self.connected = True
            self.ismaster = self.client.admin.command('ismaster')
            self.w = len(self.ismaster.get("hosts", [])) or 1
            self.nodes = set([(self.host, self.port)])
            self.replica_set_name = self.ismaster.get('setName', '')
            self.version = Version.from_client(self.client)
            if self.replica_set_name:
                self.is_rs = True
                self.client = pymongo.MongoClient(
                    self.ismaster['primary'],
                    replicaSet=self.replica_set_name,
                    **self.ssl_client_options)
                # Force connection
                self.client.admin.command('ismaster')
                self.host, self.port = self.client.primary

                nodes = [partition_node(node.lower())
                         for node in self.ismaster.get('hosts', [])]
                nodes.extend([partition_node(node.lower())
                              for node in self.ismaster.get('passives', [])])
                nodes.extend([partition_node(node.lower())
                              for node in self.ismaster.get('arbiters', [])])
                self.nodes = set(nodes)

            try:
                self.cmd_line = self.client.admin.command('getCmdLineOpts')
            except pymongo.errors.OperationFailure as e:
                msg = e.details.get('errmsg', '')
                if e.code == 13 or 'unauthorized' in msg or 'login' in msg:
                    # Unauthorized.
                    self.auth_enabled = True
                else:
                    raise
            else:
                self.auth_enabled = self._server_started_with_auth()

            if self.auth_enabled:
                # See if db_user already exists.
                self.user_provided = self._check_user_provided()
                if not self.user_provided:
                    roles = {}
                    if self.version.at_least(2, 5, 3, -1):
                        roles = {'roles': ['root']}
                    self.client.admin.add_user(db_user, db_pwd, **roles)
                    self.client.admin.authenticate(db_user, db_pwd)

                # May not have this if OperationFailure was raised earlier.
                self.cmd_line = self.client.admin.command('getCmdLineOpts')

            if 'enableTestCommands=1' in self.cmd_line['argv']:
                self.test_commands_enabled = True
            elif 'parsed' in self.cmd_line:
                params = self.cmd_line['parsed'].get('setParameter', [])
                if 'enableTestCommands=1' in params:
                    self.test_commands_enabled = True
                else:
                    params = self.cmd_line['parsed'].get('setParameter', {})
                    if params.get('enableTestCommands') == '1':
                        self.test_commands_enabled = True

            self.is_mongos = (self.ismaster.get('msg') == 'isdbgrid')
            self.has_ipv6 = self._server_started_with_ipv6()
class UnifiedSpecTestMixinV1(IntegrationTest):
    """Mixin class to run test cases from test specification files.

    Assumes that tests conform to the `unified test format
    <https://github.com/mongodb/specifications/blob/master/source/unified-test-format/unified-test-format.rst>`_.

    Specification of the test suite being currently run is available as
    a class attribute ``TEST_SPEC``.
    """
    SCHEMA_VERSION = Version.from_string('1.1')

    @staticmethod
    def should_run_on(run_on_spec):
        if not run_on_spec:
            # Always run these tests.
            return True

        for req in run_on_spec:
            if is_run_on_requirement_satisfied(req):
                return True
        return False

    def insert_initial_data(self, initial_data):
        for collection_data in initial_data:
            coll_name = collection_data['collectionName']
            db_name = collection_data['databaseName']
            documents = collection_data['documents']

            coll = self.client.get_database(db_name).get_collection(
                coll_name, write_concern=WriteConcern(w="majority"))
            coll.drop()

            if len(documents) > 0:
                coll.insert_many(documents)
            else:
                # ensure collection exists
                result = coll.insert_one({})
                coll.delete_one({'_id': result.inserted_id})

    @classmethod
    def setUpClass(cls):
        # super call creates internal client cls.client
        super(UnifiedSpecTestMixinV1, cls).setUpClass()

        # process file-level runOnRequirements
        run_on_spec = cls.TEST_SPEC.get('runOnRequirements', [])
        if not cls.should_run_on(run_on_spec):
            raise unittest.SkipTest('%s runOnRequirements not satisfied' %
                                    (cls.__name__, ))

        # add any special-casing for skipping tests here
        if client_context.storage_engine == 'mmapv1':
            if 'retryable-writes' in cls.TEST_SPEC['description']:
                raise unittest.SkipTest(
                    "MMAPv1 does not support retryWrites=True")

    @classmethod
    def tearDownClass(cls):
        super(UnifiedSpecTestMixinV1, cls).tearDownClass()
        cls.client.close()

    def setUp(self):
        super(UnifiedSpecTestMixinV1, self).setUp()

        # process schemaVersion
        # note: we check major schema version during class generation
        # note: we do this here because we cannot run assertions in setUpClass
        version = Version.from_string(self.TEST_SPEC['schemaVersion'])
        self.assertLessEqual(
            version, self.SCHEMA_VERSION,
            'expected schema version %s or lower, got %s' %
            (self.SCHEMA_VERSION, version))

        # initialize internals
        self.match_evaluator = MatchEvaluatorUtil(self)

    def maybe_skip_test(self, spec):
        # add any special-casing for skipping tests here
        if client_context.storage_engine == 'mmapv1':
            if 'Dirty explicit session is discarded' in spec['description']:
                raise unittest.SkipTest(
                    "MMAPv1 does not support retryWrites=True")

    def process_error(self, exception, spec):
        is_error = spec.get('isError')
        is_client_error = spec.get('isClientError')
        error_contains = spec.get('errorContains')
        error_code = spec.get('errorCode')
        error_code_name = spec.get('errorCodeName')
        error_labels_contain = spec.get('errorLabelsContain')
        error_labels_omit = spec.get('errorLabelsOmit')
        expect_result = spec.get('expectResult')

        if is_error:
            # already satisfied because exception was raised
            pass

        if is_client_error:
            self.assertNotIsInstance(exception, PyMongoError)

        if error_contains:
            if isinstance(exception, BulkWriteError):
                errmsg = str(exception.details).lower()
            else:
                errmsg = str(exception).lower()
            self.assertIn(error_contains.lower(), errmsg)

        if error_code:
            self.assertEqual(error_code, exception.details.get('code'))

        if error_code_name:
            self.assertEqual(error_code_name,
                             exception.details.get('codeName'))

        if error_labels_contain:
            labels = [
                err_label for err_label in error_labels_contain
                if exception.has_error_label(err_label)
            ]
            self.assertEqual(labels, error_labels_contain)

        if error_labels_omit:
            for err_label in error_labels_omit:
                if exception.has_error_label(err_label):
                    self.fail("Exception '%s' unexpectedly had label '%s'" %
                              (exception, err_label))

        if expect_result:
            if isinstance(exception, BulkWriteError):
                result = parse_bulk_write_error_result(exception)
                self.match_evaluator.match_result(expect_result, result)
            else:
                self.fail("expectResult can only be specified with %s "
                          "exceptions" % (BulkWriteError, ))

    def __raise_if_unsupported(self, opname, target, *target_types):
        if not isinstance(target, target_types):
            self.fail('Operation %s not supported for entity '
                      'of type %s' % (opname, type(target)))

    def __entityOperation_createChangeStream(self, target, *args, **kwargs):
        if client_context.storage_engine == 'mmapv1':
            self.skipTest("MMAPv1 does not support change streams")
        self.__raise_if_unsupported('createChangeStream', target, MongoClient,
                                    Database, Collection)
        return target.watch(*args, **kwargs)

    def _clientOperation_createChangeStream(self, target, *args, **kwargs):
        return self.__entityOperation_createChangeStream(
            target, *args, **kwargs)

    def _databaseOperation_createChangeStream(self, target, *args, **kwargs):
        return self.__entityOperation_createChangeStream(
            target, *args, **kwargs)

    def _collectionOperation_createChangeStream(self, target, *args, **kwargs):
        return self.__entityOperation_createChangeStream(
            target, *args, **kwargs)

    def _databaseOperation_runCommand(self, target, **kwargs):
        self.__raise_if_unsupported('runCommand', target, Database)
        # Ensure the first key is the command name.
        ordered_command = SON([(kwargs.pop('command_name'), 1)])
        ordered_command.update(kwargs['command'])
        kwargs['command'] = ordered_command
        return target.command(**kwargs)

    def __entityOperation_aggregate(self, target, *args, **kwargs):
        self.__raise_if_unsupported('aggregate', target, Database, Collection)
        return list(target.aggregate(*args, **kwargs))

    def _databaseOperation_aggregate(self, target, *args, **kwargs):
        return self.__entityOperation_aggregate(target, *args, **kwargs)

    def _collectionOperation_aggregate(self, target, *args, **kwargs):
        return self.__entityOperation_aggregate(target, *args, **kwargs)

    def _collectionOperation_bulkWrite(self, target, *args, **kwargs):
        self.__raise_if_unsupported('bulkWrite', target, Collection)
        write_result = target.bulk_write(*args, **kwargs)
        return parse_bulk_write_result(write_result)

    def _collectionOperation_find(self, target, *args, **kwargs):
        self.__raise_if_unsupported('find', target, Collection)
        find_cursor = target.find(*args, **kwargs)
        return list(find_cursor)

    def _collectionOperation_findOneAndReplace(self, target, *args, **kwargs):
        self.__raise_if_unsupported('findOneAndReplace', target, Collection)
        return target.find_one_and_replace(*args, **kwargs)

    def _collectionOperation_findOneAndUpdate(self, target, *args, **kwargs):
        self.__raise_if_unsupported('findOneAndReplace', target, Collection)
        return target.find_one_and_update(*args, **kwargs)

    def _collectionOperation_insertMany(self, target, *args, **kwargs):
        self.__raise_if_unsupported('insertMany', target, Collection)
        result = target.insert_many(*args, **kwargs)
        return {idx: _id for idx, _id in enumerate(result.inserted_ids)}

    def _collectionOperation_insertOne(self, target, *args, **kwargs):
        self.__raise_if_unsupported('insertOne', target, Collection)
        result = target.insert_one(*args, **kwargs)
        return {'insertedId': result.inserted_id}

    def _sessionOperation_withTransaction(self, target, *args, **kwargs):
        if client_context.storage_engine == 'mmapv1':
            self.skipTest('MMAPv1 does not support document-level locking')
        self.__raise_if_unsupported('withTransaction', target, ClientSession)
        return target.with_transaction(*args, **kwargs)

    def _sessionOperation_startTransaction(self, target, *args, **kwargs):
        if client_context.storage_engine == 'mmapv1':
            self.skipTest('MMAPv1 does not support document-level locking')
        self.__raise_if_unsupported('startTransaction', target, ClientSession)
        return target.start_transaction(*args, **kwargs)

    def _changeStreamOperation_iterateUntilDocumentOrError(
            self, target, *args, **kwargs):
        self.__raise_if_unsupported('iterateUntilDocumentOrError', target,
                                    ChangeStream)
        return next(target)

    def run_entity_operation(self, spec):
        target = self.entity_map[spec['object']]
        opname = spec['name']
        opargs = spec.get('arguments')
        expect_error = spec.get('expectError')
        if opargs:
            arguments = parse_spec_options(copy.deepcopy(opargs))
            prepare_spec_arguments(spec, arguments, camel_to_snake(opname),
                                   self.entity_map, self.run_operations)
        else:
            arguments = tuple()

        if isinstance(target, MongoClient):
            method_name = '_clientOperation_%s' % (opname, )
        elif isinstance(target, Database):
            method_name = '_databaseOperation_%s' % (opname, )
        elif isinstance(target, Collection):
            method_name = '_collectionOperation_%s' % (opname, )
        elif isinstance(target, ChangeStream):
            method_name = '_changeStreamOperation_%s' % (opname, )
        elif isinstance(target, ClientSession):
            method_name = '_sessionOperation_%s' % (opname, )
        elif isinstance(target, GridFSBucket):
            raise NotImplementedError
        else:
            method_name = 'doesNotExist'

        try:
            method = getattr(self, method_name)
        except AttributeError:
            try:
                cmd = getattr(target, camel_to_snake(opname))
            except AttributeError:
                self.fail('Unsupported operation %s on entity %s' %
                          (opname, target))
        else:
            cmd = functools.partial(method, target)

        try:
            result = cmd(**dict(arguments))
        except Exception as exc:
            if expect_error:
                return self.process_error(exc, expect_error)
            raise

        if 'expectResult' in spec:
            self.match_evaluator.match_result(spec['expectResult'], result)

        save_as_entity = spec.get('saveResultAsEntity')
        if save_as_entity:
            self.entity_map[save_as_entity] = result

    def __set_fail_point(self, client, command_args):
        if not client_context.test_commands_enabled:
            self.skipTest('Test commands must be enabled')

        cmd_on = SON([('configureFailPoint', 'failCommand')])
        cmd_on.update(command_args)
        client.admin.command(cmd_on)
        self.addCleanup(client.admin.command,
                        'configureFailPoint',
                        cmd_on['configureFailPoint'],
                        mode='off')

    def _testOperation_failPoint(self, spec):
        self.__set_fail_point(client=self.entity_map[spec['client']],
                              command_args=spec['failPoint'])

    def _testOperation_targetedFailPoint(self, spec):
        session = self.entity_map[spec['session']]
        if not session._pinned_address:
            self.fail("Cannot use targetedFailPoint operation with unpinned "
                      "session %s" % (spec['session'], ))

        client = single_client('%s:%s' % session._pinned_address)
        self.__set_fail_point(client=client, command_args=spec['failPoint'])
        self.addCleanup(client.close)

    def _testOperation_assertSessionTransactionState(self, spec):
        session = self.entity_map[spec['session']]
        expected_state = getattr(_TxnState, spec['state'].upper())
        self.assertEqual(expected_state, session._transaction.state)

    def _testOperation_assertSessionPinned(self, spec):
        session = self.entity_map[spec['session']]
        self.assertIsNotNone(session._pinned_address)

    def _testOperation_assertSessionUnpinned(self, spec):
        session = self.entity_map[spec['session']]
        self.assertIsNone(session._pinned_address)

    def __get_last_two_command_lsids(self, listener):
        cmd_started_events = []
        for event in reversed(listener.results):
            if isinstance(event, CommandStartedEvent):
                cmd_started_events.append(event)
        if len(cmd_started_events) < 2:
            self.fail('Needed 2 CommandStartedEvents to compare lsids, '
                      'got %s' % (len(cmd_started_events)))
        return tuple([e.command['lsid'] for e in cmd_started_events][:2])

    def _testOperation_assertDifferentLsidOnLastTwoCommands(self, spec):
        listener = self.entity_map.get_listener_for_client(spec['client'])
        self.assertNotEqual(*self.__get_last_two_command_lsids(listener))

    def _testOperation_assertSameLsidOnLastTwoCommands(self, spec):
        listener = self.entity_map.get_listener_for_client(spec['client'])
        self.assertEqual(*self.__get_last_two_command_lsids(listener))

    def _testOperation_assertSessionDirty(self, spec):
        session = self.entity_map[spec['session']]
        self.assertTrue(session._server_session.dirty)

    def _testOperation_assertSessionNotDirty(self, spec):
        session = self.entity_map[spec['session']]
        return self.assertFalse(session._server_session.dirty)

    def _testOperation_assertCollectionExists(self, spec):
        database_name = spec['databaseName']
        collection_name = spec['collectionName']
        collection_name_list = list(
            self.client.get_database(database_name).list_collection_names())
        self.assertIn(collection_name, collection_name_list)

    def _testOperation_assertCollectionNotExists(self, spec):
        database_name = spec['databaseName']
        collection_name = spec['collectionName']
        collection_name_list = list(
            self.client.get_database(database_name).list_collection_names())
        self.assertNotIn(collection_name, collection_name_list)

    def _testOperation_assertIndexExists(self, spec):
        collection = self.client[spec['databaseName']][spec['collectionName']]
        index_names = [idx['name'] for idx in collection.list_indexes()]
        self.assertIn(spec['indexName'], index_names)

    def _testOperation_assertIndexNotExists(self, spec):
        collection = self.client[spec['databaseName']][spec['collectionName']]
        for index in collection.list_indexes():
            self.assertNotEqual(spec['indexName'], index['name'])

    def run_special_operation(self, spec):
        opname = spec['name']
        method_name = '_testOperation_%s' % (opname, )
        try:
            method = getattr(self, method_name)
        except AttributeError:
            self.fail('Unsupported special test operation %s' % (opname, ))
        else:
            method(spec['arguments'])

    def run_operations(self, spec):
        for op in spec:
            target = op['object']
            if target != 'testRunner':
                self.run_entity_operation(op)
            else:
                self.run_special_operation(op)

    def check_events(self, spec):
        for event_spec in spec:
            client_name = event_spec['client']
            events = event_spec['events']
            listener = self.entity_map.get_listener_for_client(client_name)

            if len(events) == 0:
                self.assertEqual(listener.results, [])
                continue

            if len(events) > len(listener.results):
                self.fail('Expected to see %s events, got %s' %
                          (len(events), len(listener.results)))

            for idx, expected_event in enumerate(events):
                self.match_evaluator.match_event(expected_event,
                                                 listener.results[idx])

    def verify_outcome(self, spec):
        for collection_data in spec:
            coll_name = collection_data['collectionName']
            db_name = collection_data['databaseName']
            expected_documents = collection_data['documents']

            coll = self.client.get_database(db_name).get_collection(
                coll_name,
                read_preference=ReadPreference.PRIMARY,
                read_concern=ReadConcern(level='local'))

            if expected_documents:
                sorted_expected_documents = sorted(expected_documents,
                                                   key=lambda doc: doc['_id'])
                actual_documents = list(
                    coll.find({}, sort=[('_id', ASCENDING)]))
                self.assertListEqual(sorted_expected_documents,
                                     actual_documents)

    def run_scenario(self, spec):
        # maybe skip test manually
        self.maybe_skip_test(spec)

        # process test-level runOnRequirements
        run_on_spec = spec.get('runOnRequirements', [])
        if not self.should_run_on(run_on_spec):
            raise unittest.SkipTest('runOnRequirements not satisfied')

        # process skipReason
        skip_reason = spec.get('skipReason', None)
        if skip_reason is not None:
            raise unittest.SkipTest('%s' % (skip_reason, ))

        # process createEntities
        self.entity_map = EntityMapUtil(self)
        self.entity_map.create_entities_from_spec(
            self.TEST_SPEC.get('createEntities', []))

        # process initialData
        self.insert_initial_data(self.TEST_SPEC.get('initialData', []))

        # process operations
        self.run_operations(spec['operations'])

        # process expectEvents
        self.check_events(spec.get('expectEvents', []))

        # process outcome
        self.verify_outcome(spec.get('outcome', []))
示例#30
0
    def _init_client(self):
        self.client = self._connect(host, port)
        if HAVE_SSL and not self.client:
            # Is MongoDB configured for SSL?
            self.client = self._connect(host, port, **_SSL_OPTIONS)
            if self.client:
                self.ssl = True
                self.default_client_options.update(_SSL_OPTIONS)
                self.ssl_certfile = True
                if _SSL_OPTIONS.get('ssl_cert_reqs') == ssl.CERT_NONE:
                    self.ssl_cert_none = True

        if self.client:
            self.connected = True

            try:
                self.cmd_line = self.client.admin.command('getCmdLineOpts')
            except pymongo.errors.OperationFailure as e:
                msg = e.details.get('errmsg', '')
                if e.code == 13 or 'unauthorized' in msg or 'login' in msg:
                    # Unauthorized.
                    self.auth_enabled = True
                else:
                    raise
            else:
                self.auth_enabled = self._server_started_with_auth()

            if self.auth_enabled:
                # See if db_user already exists.
                if not self._check_user_provided():
                    _create_user(self.client.admin, db_user, db_pwd)

                self.client = self._connect(
                    host, port, username=db_user, password=db_pwd,
                    replicaSet=self.replica_set_name,
                    **self.default_client_options)

                # May not have this if OperationFailure was raised earlier.
                self.cmd_line = self.client.admin.command('getCmdLineOpts')

            self.server_status = self.client.admin.command('serverStatus')
            self.ismaster = ismaster = self.client.admin.command('isMaster')
            self.sessions_enabled = 'logicalSessionTimeoutMinutes' in ismaster

            if 'setName' in ismaster:
                self.replica_set_name = str(ismaster['setName'])
                self.is_rs = True
                if self.auth_enabled:
                    # It doesn't matter which member we use as the seed here.
                    self.client = pymongo.MongoClient(
                        host,
                        port,
                        username=db_user,
                        password=db_pwd,
                        replicaSet=self.replica_set_name,
                        **self.default_client_options)
                else:
                    self.client = pymongo.MongoClient(
                        host,
                        port,
                        replicaSet=self.replica_set_name,
                        **self.default_client_options)

                # Get the authoritative ismaster result from the primary.
                self.ismaster = self.client.admin.command('ismaster')
                nodes = [partition_node(node.lower())
                         for node in self.ismaster.get('hosts', [])]
                nodes.extend([partition_node(node.lower())
                              for node in self.ismaster.get('passives', [])])
                nodes.extend([partition_node(node.lower())
                              for node in self.ismaster.get('arbiters', [])])
                self.nodes = set(nodes)
            else:
                self.ismaster = ismaster
                self.nodes = set([(host, port)])
            self.w = len(self.ismaster.get("hosts", [])) or 1
            self.version = Version.from_client(self.client)

            if 'enableTestCommands=1' in self.cmd_line['argv']:
                self.test_commands_enabled = True
            elif 'parsed' in self.cmd_line:
                params = self.cmd_line['parsed'].get('setParameter', [])
                if 'enableTestCommands=1' in params:
                    self.test_commands_enabled = True
                else:
                    params = self.cmd_line['parsed'].get('setParameter', {})
                    if params.get('enableTestCommands') == '1':
                        self.test_commands_enabled = True

            self.is_mongos = (self.ismaster.get('msg') == 'isdbgrid')
            self.has_ipv6 = self._server_started_with_ipv6()
            if self.is_mongos:
                # Check for another mongos on the next port.
                address = self.client.address
                next_address = address[0], address[1] + 1
                self.mongoses.append(address)
                mongos_client = self._connect(*next_address,
                                              **self.default_client_options)
                if mongos_client:
                    ismaster = mongos_client.admin.command('ismaster')
                    if ismaster.get('msg') == 'isdbgrid':
                        self.mongoses.append(next_address)
示例#31
0
    def __init__(self):
        """Create a client and grab essential information from the server."""
        self.connected = False
        self.ismaster = {}
        self.w = None
        self.nodes = set()
        self.replica_set_name = None
        self.cmd_line = None
        self.version = Version(-1)  # Needs to be comparable with Version
        self.auth_enabled = False
        self.test_commands_enabled = False
        self.is_mongos = False
        self.is_rs = False
        self.has_ipv6 = False
        self.ssl = False
        self.ssl_cert_none = False
        self.ssl_certfile = False
        self.server_is_resolvable = is_server_resolvable()
        self.ssl_client_options = {}
        self.client = _connect(host, port)

        if HAVE_SSL and not self.client:
            # Is MongoDB configured for SSL?
            self.client = _connect(host, port, **_SSL_OPTIONS)
            if self.client:
                self.ssl = True
                self.ssl_client_options = _SSL_OPTIONS
                self.ssl_certfile = True
                if _SSL_OPTIONS.get('ssl_cert_reqs') == ssl.CERT_NONE:
                    self.ssl_cert_none = True

        if self.client:
            self.connected = True
            ismaster = self.client.admin.command('ismaster')
            if 'setName' in ismaster:
                self.replica_set_name = ismaster['setName']
                self.is_rs = True
                # It doesn't matter which member we use as the seed here.
                self.client = pymongo.MongoClient(
                    host,
                    port,
                    replicaSet=self.replica_set_name,
                    **self.ssl_client_options)
                # Get the authoritative ismaster result from the primary.
                self.ismaster = self.client.admin.command('ismaster')
                nodes = [partition_node(node.lower())
                         for node in self.ismaster.get('hosts', [])]
                nodes.extend([partition_node(node.lower())
                              for node in self.ismaster.get('passives', [])])
                nodes.extend([partition_node(node.lower())
                              for node in self.ismaster.get('arbiters', [])])
                self.nodes = set(nodes)
            else:
                self.ismaster = ismaster
                self.nodes = set([(host, port)])
            self.w = len(self.ismaster.get("hosts", [])) or 1
            self.version = Version.from_client(self.client)

            try:
                self.cmd_line = self.client.admin.command('getCmdLineOpts')
            except pymongo.errors.OperationFailure as e:
                msg = e.details.get('errmsg', '')
                if e.code == 13 or 'unauthorized' in msg or 'login' in msg:
                    # Unauthorized.
                    self.auth_enabled = True
                else:
                    raise
            else:
                self.auth_enabled = self._server_started_with_auth()

            if self.auth_enabled:
                # See if db_user already exists.
                self.user_provided = self._check_user_provided()
                if not self.user_provided:
                    roles = {}
                    if self.version.at_least(2, 5, 3, -1):
                        roles = {'roles': ['root']}
                    self.client.admin.add_user(db_user, db_pwd, **roles)
                    self.client.admin.authenticate(db_user, db_pwd)

                # May not have this if OperationFailure was raised earlier.
                self.cmd_line = self.client.admin.command('getCmdLineOpts')

            if 'enableTestCommands=1' in self.cmd_line['argv']:
                self.test_commands_enabled = True
            elif 'parsed' in self.cmd_line:
                params = self.cmd_line['parsed'].get('setParameter', [])
                if 'enableTestCommands=1' in params:
                    self.test_commands_enabled = True
                else:
                    params = self.cmd_line['parsed'].get('setParameter', {})
                    if params.get('enableTestCommands') == '1':
                        self.test_commands_enabled = True

            self.is_mongos = (self.ismaster.get('msg') == 'isdbgrid')
            self.has_ipv6 = self._server_started_with_ipv6()
示例#32
0
    def __init__(self):
        """Create a client and grab essential information from the server."""
        self.connected = False
        self.ismaster = {}
        self.w = None
        self.nodes = set()
        self.replica_set_name = None
        self.rs_client = None
        self.cmd_line = None
        self.version = Version(-1)  # Needs to be comparable with Version
        self.auth_enabled = False
        self.test_commands_enabled = False
        self.is_mongos = False
        self.is_rs = False
        self.has_ipv6 = False

        try:
            client = pymongo.MongoClient(host, port,
                                         serverSelectionTimeoutMS=100)
            client.admin.command('ismaster')  # Can we connect?

            # If so, then reset client to defaults.
            self.client = pymongo.MongoClient(host, port)

        except pymongo.errors.ConnectionFailure:
            self.client = None
        else:
            self.connected = True
            self.ismaster = self.client.admin.command('ismaster')
            self.w = len(self.ismaster.get("hosts", [])) or 1
            self.nodes = set([(host, port)])
            self.replica_set_name = self.ismaster.get('setName', '')
            self.rs_client = None
            self.version = Version.from_client(self.client)
            if self.replica_set_name:
                self.is_rs = True
                self.rs_client = pymongo.MongoClient(
                    pair, replicaSet=self.replica_set_name)

                nodes = [partition_node(node)
                         for node in self.ismaster.get('hosts', [])]
                nodes.extend([partition_node(node)
                              for node in self.ismaster.get('passives', [])])
                nodes.extend([partition_node(node)
                              for node in self.ismaster.get('arbiters', [])])
                self.nodes = set(nodes)

            self.rs_or_standalone_client = self.rs_client or self.client

            try:
                self.cmd_line = self.client.admin.command('getCmdLineOpts')
            except pymongo.errors.OperationFailure as e:
                msg = e.details.get('errmsg', '')
                if e.code == 13 or 'unauthorized' in msg or 'login' in msg:
                    # Unauthorized.
                    self.auth_enabled = True
                else:
                    raise
            else:
                self.auth_enabled = self._server_started_with_auth()

            if self.auth_enabled:
                # See if db_user already exists.
                self.user_provided = self._check_user_provided()
                if not self.user_provided:
                    roles = {}
                    if self.version.at_least(2, 5, 3, -1):
                        roles = {'roles': ['root']}
                    self.client.admin.add_user(db_user, db_pwd, **roles)
                    self.client.admin.authenticate(db_user, db_pwd)

                if self.rs_client:
                    self.rs_client.admin.authenticate(db_user, db_pwd)

                # May not have this if OperationFailure was raised earlier.
                self.cmd_line = self.client.admin.command('getCmdLineOpts')

            if 'enableTestCommands=1' in self.cmd_line['argv']:
                self.test_commands_enabled = True
            elif 'parsed' in self.cmd_line:
                params = self.cmd_line['parsed'].get('setParameter', [])
                if 'enableTestCommands=1' in params:
                    self.test_commands_enabled = True

            self.is_mongos = (self.ismaster.get('msg') == 'isdbgrid')
            self.has_ipv6 = self._server_started_with_ipv6()
示例#33
0
def _all_users(db):
    if Version.from_client(db.client).at_least(2, 5, 3, -1):
        return set(u['user'] for u in db.command('usersInfo').get('users', []))
    else:
        return set(u['user'] for u in db.system.users.find())
示例#34
0
    def __init__(self):
        """Create a client and grab essential information from the server."""
        self.connected = False
        self.ismaster = {}
        self.w = None
        self.nodes = set()
        self.replica_set_name = None
        self.cmd_line = None
        self.version = Version(-1)  # Needs to be comparable with Version
        self.auth_enabled = False
        self.test_commands_enabled = False
        self.is_mongos = False
        self.is_rs = False
        self.has_ipv6 = False
        self.ssl = False
        self.ssl_cert_none = False
        self.ssl_certfile = False
        self.server_is_resolvable = is_server_resolvable()
        self.ssl_client_options = {}
        self.sessions_enabled = False
        self.client = _connect(host, port)

        if HAVE_SSL and not self.client:
            # Is MongoDB configured for SSL?
            self.client = _connect(host, port, **_SSL_OPTIONS)
            if self.client:
                self.ssl = True
                self.ssl_client_options = _SSL_OPTIONS
                self.ssl_certfile = True
                if _SSL_OPTIONS.get('ssl_cert_reqs') == ssl.CERT_NONE:
                    self.ssl_cert_none = True

        if self.client:
            self.connected = True

            try:
                self.cmd_line = self.client.admin.command('getCmdLineOpts')
            except pymongo.errors.OperationFailure as e:
                msg = e.details.get('errmsg', '')
                if e.code == 13 or 'unauthorized' in msg or 'login' in msg:
                    # Unauthorized.
                    self.auth_enabled = True
                else:
                    raise
            else:
                self.auth_enabled = self._server_started_with_auth()

            if self.auth_enabled:
                # See if db_user already exists.
                if not self._check_user_provided():
                    self.client.admin.add_user(db_user, db_pwd, roles=['root'])

                self.client = _connect(host,
                                       port,
                                       username=db_user,
                                       password=db_pwd,
                                       replicaSet=self.replica_set_name,
                                       **self.ssl_client_options)

                # May not have this if OperationFailure was raised earlier.
                self.cmd_line = self.client.admin.command('getCmdLineOpts')

            self.ismaster = ismaster = self.client.admin.command('isMaster')
            self.sessions_enabled = 'logicalSessionTimeoutMinutes' in ismaster

            if 'setName' in ismaster:
                self.replica_set_name = ismaster['setName']
                self.is_rs = True
                if self.auth_enabled:
                    # It doesn't matter which member we use as the seed here.
                    self.client = pymongo.MongoClient(
                        host,
                        port,
                        username=db_user,
                        password=db_pwd,
                        replicaSet=self.replica_set_name,
                        **self.ssl_client_options)
                else:
                    self.client = pymongo.MongoClient(
                        host,
                        port,
                        replicaSet=self.replica_set_name,
                        **self.ssl_client_options)

                # Get the authoritative ismaster result from the primary.
                self.ismaster = self.client.admin.command('ismaster')
                nodes = [
                    partition_node(node.lower())
                    for node in self.ismaster.get('hosts', [])
                ]
                nodes.extend([
                    partition_node(node.lower())
                    for node in self.ismaster.get('passives', [])
                ])
                nodes.extend([
                    partition_node(node.lower())
                    for node in self.ismaster.get('arbiters', [])
                ])
                self.nodes = set(nodes)
            else:
                self.ismaster = ismaster
                self.nodes = set([(host, port)])
            self.w = len(self.ismaster.get("hosts", [])) or 1
            self.version = Version.from_client(self.client)

            if 'enableTestCommands=1' in self.cmd_line['argv']:
                self.test_commands_enabled = True
            elif 'parsed' in self.cmd_line:
                params = self.cmd_line['parsed'].get('setParameter', [])
                if 'enableTestCommands=1' in params:
                    self.test_commands_enabled = True
                else:
                    params = self.cmd_line['parsed'].get('setParameter', {})
                    if params.get('enableTestCommands') == '1':
                        self.test_commands_enabled = True

            self.is_mongos = (self.ismaster.get('msg') == 'isdbgrid')
            self.has_ipv6 = self._server_started_with_ipv6()
示例#35
0
    def test_mongodb_x509_auth(self):
        # Expects the server to be running with the server.pem, ca.pem
        # and crl.pem provided in mongodb and the server tests as well as
        # --auth
        #
        #   --sslPEMKeyFile=/path/to/pymongo/test/certificates/server.pem
        #   --sslCAFile=/path/to/pymongo/test/certificates/ca.pem
        #   --sslCRLFile=/path/to/pymongo/test/certificates/crl.pem
        #   --auth
        if not CERT_SSL:
            raise SkipTest("No mongod available over SSL with certs")

        if not Version.from_client(ssl_client).at_least(2, 5, 3, -1):
            raise SkipTest("MONGODB-X509 tests require MongoDB 2.5.3 or newer")
        if not server_started_with_auth(ssl_client):
            raise SkipTest('Authentication is not enabled on server')

        self.addCleanup(ssl_client['$external'].logout)
        self.addCleanup(remove_all_users, ssl_client['$external'])

        # Give admin all necessary privileges.
        ssl_client['$external'].add_user(MONGODB_X509_USERNAME, roles=[
            {'role': 'readWriteAnyDatabase', 'db': 'admin'},
            {'role': 'userAdminAnyDatabase', 'db': 'admin'}])

        coll = ssl_client.pymongo_test.test
        self.assertRaises(OperationFailure, coll.count)
        self.assertTrue(ssl_client.admin.authenticate(
            MONGODB_X509_USERNAME, mechanism='MONGODB-X509'))
        coll.drop()
        uri = ('mongodb://%s@%s:%d/?authMechanism='
               'MONGODB-X509' % (
                   quote_plus(MONGODB_X509_USERNAME), host, port))
        # SSL options aren't supported in the URI...
        self.assertTrue(MongoClient(uri,
                                    ssl=True, ssl_certfile=CLIENT_PEM))

        # Should require a username
        uri = ('mongodb://%s:%d/?authMechanism=MONGODB-X509' % (host,
                                                                port))
        client_bad = MongoClient(uri, ssl=True, ssl_certfile=CLIENT_PEM)
        self.assertRaises(OperationFailure,
                          client_bad.pymongo_test.test.delete_one, {})

        # Auth should fail if username and certificate do not match
        uri = ('mongodb://%s@%s:%d/?authMechanism='
               'MONGODB-X509' % (
                   quote_plus("not the username"), host, port))

        bad_client = MongoClient(uri, ssl=True, ssl_certfile=CLIENT_PEM)
        with self.assertRaises(OperationFailure):
            bad_client.pymongo_test.test.find_one()

        self.assertRaises(OperationFailure, ssl_client.admin.authenticate,
                          "not the username",
                          mechanism="MONGODB-X509")

        # Invalid certificate (using CA certificate as client certificate)
        uri = ('mongodb://%s@%s:%d/?authMechanism='
               'MONGODB-X509' % (
                   quote_plus(MONGODB_X509_USERNAME), host, port))
        # These tests will raise SSLError (>= 3.2) or ConnectionFailure
        # (2.x) depending on where OpenSSL first sees the PEM file.
        try:
            connected(MongoClient(uri, ssl=True, ssl_certfile=CA_PEM,
                                  serverSelectionTimeoutMS=100))
        except (ssl.SSLError, ConnectionFailure):
            pass
        else:
            self.fail("Invalid certificate accepted.")

        try:
            connected(MongoClient(pair, ssl=True, ssl_certfile=CA_PEM,
                                  serverSelectionTimeoutMS=100))
        except (ssl.SSLError, ConnectionFailure):
            pass
        else:
            self.fail("Invalid certificate accepted.")
class ClientContext(object):
    def __init__(self):
        """Create a client and grab essential information from the server."""
        self.connection_attempts = []
        self.connected = False
        self.w = None
        self.nodes = set()
        self.replica_set_name = None
        self.cmd_line = None
        self.server_status = None
        self.version = Version(-1)  # Needs to be comparable with Version
        self.auth_enabled = False
        self.test_commands_enabled = False
        self.server_parameters = None
        self.is_mongos = False
        self.mongoses = []
        self.is_rs = False
        self.has_ipv6 = False
        self.tls = False
        self.ssl_certfile = False
        self.server_is_resolvable = is_server_resolvable()
        self.default_client_options = {}
        self.sessions_enabled = False
        self.client = None
        self.conn_lock = threading.Lock()
        self.is_data_lake = False
        if COMPRESSORS:
            self.default_client_options["compressors"] = COMPRESSORS
        if MONGODB_API_VERSION:
            server_api = ServerApi(MONGODB_API_VERSION)
            self.default_client_options["server_api"] = server_api

    @property
    def client_options(self):
        """Return the MongoClient options for creating a duplicate client."""
        opts = client_context.default_client_options.copy()
        if client_context.auth_enabled:
            opts['username'] = db_user
            opts['password'] = db_pwd
        if self.replica_set_name:
            opts['replicaSet'] = self.replica_set_name
        return opts

    @property
    def ismaster(self):
        return self.client.admin.command('isMaster')

    def _connect(self, host, port, **kwargs):
        # Jython takes a long time to connect.
        if sys.platform.startswith('java'):
            timeout_ms = 10000
        else:
            timeout_ms = 5000
        kwargs.update(self.default_client_options)
        client = pymongo.MongoClient(host,
                                     port,
                                     serverSelectionTimeoutMS=timeout_ms,
                                     **kwargs)
        try:
            try:
                client.admin.command('isMaster')  # Can we connect?
            except pymongo.errors.OperationFailure as exc:
                # SERVER-32063
                self.connection_attempts.append(
                    'connected client %r, but isMaster failed: %s' %
                    (client, exc))
            else:
                self.connection_attempts.append(
                    'successfully connected client %r' % (client, ))
            # If connected, then return client with default timeout
            return pymongo.MongoClient(host, port, **kwargs)
        except pymongo.errors.ConnectionFailure as exc:
            self.connection_attempts.append('failed to connect client %r: %s' %
                                            (client, exc))
            return None
        finally:
            client.close()

    def _init_client(self):
        self.client = self._connect(host, port)

        if self.client is not None:
            # Return early when connected to dataLake as mongohoused does not
            # support the getCmdLineOpts command and is tested without TLS.
            build_info = self.client.admin.command('buildInfo')
            if 'dataLake' in build_info:
                self.is_data_lake = True
                self.auth_enabled = True
                self.client = self._connect(host,
                                            port,
                                            username=db_user,
                                            password=db_pwd)
                self.connected = True
                return

        if HAVE_SSL and not self.client:
            # Is MongoDB configured for SSL?
            self.client = self._connect(host, port, **TLS_OPTIONS)
            if self.client:
                self.tls = True
                self.default_client_options.update(TLS_OPTIONS)
                self.ssl_certfile = True

        if self.client:
            self.connected = True

            try:
                self.cmd_line = self.client.admin.command('getCmdLineOpts')
            except pymongo.errors.OperationFailure as e:
                msg = e.details.get('errmsg', '')
                if e.code == 13 or 'unauthorized' in msg or 'login' in msg:
                    # Unauthorized.
                    self.auth_enabled = True
                else:
                    raise
            else:
                self.auth_enabled = self._server_started_with_auth()

            if self.auth_enabled:
                # See if db_user already exists.
                if not self._check_user_provided():
                    _create_user(self.client.admin, db_user, db_pwd)

                self.client = self._connect(host,
                                            port,
                                            username=db_user,
                                            password=db_pwd,
                                            replicaSet=self.replica_set_name,
                                            **self.default_client_options)

                # May not have this if OperationFailure was raised earlier.
                self.cmd_line = self.client.admin.command('getCmdLineOpts')

            self.server_status = self.client.admin.command('serverStatus')
            if self.storage_engine == "mmapv1":
                # MMAPv1 does not support retryWrites=True.
                self.default_client_options['retryWrites'] = False

            ismaster = self.ismaster
            self.sessions_enabled = 'logicalSessionTimeoutMinutes' in ismaster

            if 'setName' in ismaster:
                self.replica_set_name = str(ismaster['setName'])
                self.is_rs = True
                if self.auth_enabled:
                    # It doesn't matter which member we use as the seed here.
                    self.client = pymongo.MongoClient(
                        host,
                        port,
                        username=db_user,
                        password=db_pwd,
                        replicaSet=self.replica_set_name,
                        **self.default_client_options)
                else:
                    self.client = pymongo.MongoClient(
                        host,
                        port,
                        replicaSet=self.replica_set_name,
                        **self.default_client_options)

                # Get the authoritative ismaster result from the primary.
                ismaster = self.ismaster
                nodes = [
                    partition_node(node.lower())
                    for node in ismaster.get('hosts', [])
                ]
                nodes.extend([
                    partition_node(node.lower())
                    for node in ismaster.get('passives', [])
                ])
                nodes.extend([
                    partition_node(node.lower())
                    for node in ismaster.get('arbiters', [])
                ])
                self.nodes = set(nodes)
            else:
                self.nodes = set([(host, port)])
            self.w = len(ismaster.get("hosts", [])) or 1
            self.version = Version.from_client(self.client)
            self.server_parameters = self.client.admin.command(
                'getParameter', '*')

            if 'enableTestCommands=1' in self.cmd_line['argv']:
                self.test_commands_enabled = True
            elif 'parsed' in self.cmd_line:
                params = self.cmd_line['parsed'].get('setParameter', [])
                if 'enableTestCommands=1' in params:
                    self.test_commands_enabled = True
                else:
                    params = self.cmd_line['parsed'].get('setParameter', {})
                    if params.get('enableTestCommands') == '1':
                        self.test_commands_enabled = True

            self.is_mongos = (self.ismaster.get('msg') == 'isdbgrid')
            self.has_ipv6 = self._server_started_with_ipv6()
            if self.is_mongos:
                # Check for another mongos on the next port.
                address = self.client.address
                next_address = address[0], address[1] + 1
                self.mongoses.append(address)
                mongos_client = self._connect(*next_address,
                                              **self.default_client_options)
                if mongos_client:
                    ismaster = mongos_client.admin.command('ismaster')
                    if ismaster.get('msg') == 'isdbgrid':
                        self.mongoses.append(next_address)

    def init(self):
        with self.conn_lock:
            if not self.client and not self.connection_attempts:
                self._init_client()

    def connection_attempt_info(self):
        return '\n'.join(self.connection_attempts)

    @property
    def host(self):
        if self.is_rs:
            primary = self.client.primary
            return str(primary[0]) if primary is not None else host
        return host

    @property
    def port(self):
        if self.is_rs:
            primary = self.client.primary
            return primary[1] if primary is not None else port
        return port

    @property
    def pair(self):
        return "%s:%d" % (self.host, self.port)

    @property
    def has_secondaries(self):
        if not self.client:
            return False
        return bool(len(self.client.secondaries))

    @property
    def storage_engine(self):
        try:
            return self.server_status.get("storageEngine", {}).get("name")
        except AttributeError:
            # Raised if self.server_status is None.
            return None

    def _check_user_provided(self):
        """Return True if db_user/db_password is already an admin user."""
        client = pymongo.MongoClient(host,
                                     port,
                                     username=db_user,
                                     password=db_pwd,
                                     serverSelectionTimeoutMS=100,
                                     **self.default_client_options)

        try:
            return db_user in _all_users(client.admin)
        except pymongo.errors.OperationFailure as e:
            msg = e.details.get('errmsg', '')
            if e.code == 18 or 'auth fails' in msg:
                # Auth failed.
                return False
            else:
                raise

    def _server_started_with_auth(self):
        # MongoDB >= 2.0
        if 'parsed' in self.cmd_line:
            parsed = self.cmd_line['parsed']
            # MongoDB >= 2.6
            if 'security' in parsed:
                security = parsed['security']
                # >= rc3
                if 'authorization' in security:
                    return security['authorization'] == 'enabled'
                # < rc3
                return (security.get('auth', False)
                        or bool(security.get('keyFile')))
            return parsed.get('auth', False) or bool(parsed.get('keyFile'))
        # Legacy
        argv = self.cmd_line['argv']
        return '--auth' in argv or '--keyFile' in argv

    def _server_started_with_ipv6(self):
        if not socket.has_ipv6:
            return False

        if 'parsed' in self.cmd_line:
            if not self.cmd_line['parsed'].get('net', {}).get('ipv6'):
                return False
        else:
            if '--ipv6' not in self.cmd_line['argv']:
                return False

        # The server was started with --ipv6. Is there an IPv6 route to it?
        try:
            for info in socket.getaddrinfo(self.host, self.port):
                if info[0] == socket.AF_INET6:
                    return True
        except socket.error:
            pass

        return False

    def _require(self, condition, msg, func=None):
        def make_wrapper(f):
            @wraps(f)
            def wrap(*args, **kwargs):
                self.init()
                # Always raise SkipTest if we can't connect to MongoDB
                if not self.connected:
                    raise SkipTest("Cannot connect to MongoDB on %s" %
                                   (self.pair, ))
                if condition():
                    return f(*args, **kwargs)
                raise SkipTest(msg)

            return wrap

        if func is None:

            def decorate(f):
                return make_wrapper(f)

            return decorate
        return make_wrapper(func)

    def create_user(self, dbname, user, pwd=None, roles=None, **kwargs):
        kwargs['writeConcern'] = {'w': self.w}
        return _create_user(self.client[dbname], user, pwd, roles, **kwargs)

    def drop_user(self, dbname, user):
        self.client[dbname].command('dropUser',
                                    user,
                                    writeConcern={'w': self.w})

    def require_connection(self, func):
        """Run a test only if we can connect to MongoDB."""
        return self._require(
            lambda: True,  # _require checks if we're connected
            "Cannot connect to MongoDB on %s" % (self.pair, ),
            func=func)

    def require_no_mmap(self, func):
        """Run a test only if the server is not using the MMAPv1 storage
        engine. Only works for standalone and replica sets; tests are
        run regardless of storage engine on sharded clusters. """
        def is_not_mmap():
            if self.is_mongos:
                return True
            return self.storage_engine != 'mmapv1'

        return self._require(is_not_mmap,
                             "Storage engine must not be MMAPv1",
                             func=func)

    def require_version_min(self, *ver):
        """Run a test only if the server version is at least ``version``."""
        other_version = Version(*ver)
        return self._require(
            lambda: self.version >= other_version,
            "Server version must be at least %s" % str(other_version))

    def require_version_max(self, *ver):
        """Run a test only if the server version is at most ``version``."""
        other_version = Version(*ver)
        return self._require(
            lambda: self.version <= other_version,
            "Server version must be at most %s" % str(other_version))

    def require_auth(self, func):
        """Run a test only if the server is running with auth enabled."""
        return self.check_auth_with_sharding(
            self._require(lambda: self.auth_enabled,
                          "Authentication is not enabled on the server",
                          func=func))

    def require_no_auth(self, func):
        """Run a test only if the server is running without auth enabled."""
        return self._require(
            lambda: not self.auth_enabled,
            "Authentication must not be enabled on the server",
            func=func)

    def require_replica_set(self, func):
        """Run a test only if the client is connected to a replica set."""
        return self._require(lambda: self.is_rs,
                             "Not connected to a replica set",
                             func=func)

    def require_secondaries_count(self, count):
        """Run a test only if the client is connected to a replica set that has
        `count` secondaries.
        """
        def sec_count():
            return 0 if not self.client else len(self.client.secondaries)

        return self._require(lambda: sec_count() >= count,
                             "Not enough secondaries available")

    def require_no_replica_set(self, func):
        """Run a test if the client is *not* connected to a replica set."""
        return self._require(
            lambda: not self.is_rs,
            "Connected to a replica set, not a standalone mongod",
            func=func)

    def require_ipv6(self, func):
        """Run a test only if the client can connect to a server via IPv6."""
        return self._require(lambda: self.has_ipv6, "No IPv6", func=func)

    def require_no_mongos(self, func):
        """Run a test only if the client is not connected to a mongos."""
        return self._require(lambda: not self.is_mongos,
                             "Must be connected to a mongod, not a mongos",
                             func=func)

    def require_mongos(self, func):
        """Run a test only if the client is connected to a mongos."""
        return self._require(lambda: self.is_mongos,
                             "Must be connected to a mongos",
                             func=func)

    def require_multiple_mongoses(self, func):
        """Run a test only if the client is connected to a sharded cluster
        that has 2 mongos nodes."""
        return self._require(lambda: len(self.mongoses) > 1,
                             "Must have multiple mongoses available",
                             func=func)

    def require_standalone(self, func):
        """Run a test only if the client is connected to a standalone."""
        return self._require(lambda: not (self.is_mongos or self.is_rs),
                             "Must be connected to a standalone",
                             func=func)

    def require_no_standalone(self, func):
        """Run a test only if the client is not connected to a standalone."""
        return self._require(lambda: self.is_mongos or self.is_rs,
                             "Must be connected to a replica set or mongos",
                             func=func)

    def check_auth_with_sharding(self, func):
        """Skip a test when connected to mongos < 2.0 and running with auth."""
        condition = lambda: not (self.auth_enabled and self.is_mongos and self.
                                 version < (2, ))
        return self._require(condition,
                             "Auth with sharding requires MongoDB >= 2.0.0",
                             func=func)

    def is_topology_type(self, topologies):
        if 'single' in topologies and not (self.is_mongos or self.is_rs):
            return True
        if 'replicaset' in topologies and self.is_rs:
            return True
        if 'sharded' in topologies and self.is_mongos:
            return True
        if 'sharded-replicaset' in topologies and self.is_mongos:
            shards = list(client_context.client.config.shards.find())
            for shard in shards:
                # For a 3-member RS-backed sharded cluster, shard['host']
                # will be 'replicaName/ip1:port1,ip2:port2,ip3:port3'
                # Otherwise it will be 'ip1:port1'
                host_spec = shard['host']
                if not len(host_spec.split('/')) > 1:
                    return False
            return True
        return False

    def require_cluster_type(self, topologies=[]):
        """Run a test only if the client is connected to a cluster that
        conforms to one of the specified topologies. Acceptable topologies
        are 'single', 'replicaset', and 'sharded'."""
        def _is_valid_topology():
            return self.is_topology_type(topologies)

        return self._require(_is_valid_topology,
                             "Cluster type not in %s" % (topologies))

    def require_test_commands(self, func):
        """Run a test only if the server has test commands enabled."""
        return self._require(lambda: self.test_commands_enabled,
                             "Test commands must be enabled",
                             func=func)

    def require_failCommand_fail_point(self, func):
        """Run a test only if the server supports the failCommand fail
        point."""
        return self._require(lambda: self.supports_failCommand_fail_point,
                             "failCommand fail point must be supported",
                             func=func)

    def require_failCommand_appName(self, func):
        """Run a test only if the server supports the failCommand appName."""
        # SERVER-47195
        return self._require(lambda:
                             (self.test_commands_enabled and self.version >=
                              (4, 4, -1)),
                             "failCommand appName must be supported",
                             func=func)

    def require_tls(self, func):
        """Run a test only if the client can connect over TLS."""
        return self._require(lambda: self.tls,
                             "Must be able to connect via TLS",
                             func=func)

    def require_no_tls(self, func):
        """Run a test only if the client can connect over TLS."""
        return self._require(lambda: not self.tls,
                             "Must be able to connect without TLS",
                             func=func)

    def require_ssl_certfile(self, func):
        """Run a test only if the client can connect with ssl_certfile."""
        return self._require(lambda: self.ssl_certfile,
                             "Must be able to connect with ssl_certfile",
                             func=func)

    def require_server_resolvable(self, func):
        """Run a test only if the hostname 'server' is resolvable."""
        return self._require(lambda: self.server_is_resolvable,
                             "No hosts entry for 'server'. Cannot validate "
                             "hostname in the certificate",
                             func=func)

    def require_sessions(self, func):
        """Run a test only if the deployment supports sessions."""
        return self._require(lambda: self.sessions_enabled,
                             "Sessions not supported",
                             func=func)

    def supports_retryable_writes(self):
        if self.storage_engine == 'mmapv1':
            return False
        if not self.sessions_enabled:
            return False
        if self.version.at_least(3, 6):
            return self.is_mongos or self.is_rs
        return False

    def require_retryable_writes(self, func):
        """Run a test only if the deployment supports retryable writes."""
        return self._require(self.supports_retryable_writes,
                             "This server does not support retryable writes",
                             func=func)

    def supports_transactions(self):
        if self.storage_engine == 'mmapv1':
            return False

        if self.version.at_least(4, 1, 8):
            return self.is_mongos or self.is_rs

        if self.version.at_least(4, 0):
            return self.is_rs

        return False

    def require_transactions(self, func):
        """Run a test only if the deployment might support transactions.

        *Might* because this does not test the storage engine or FCV.
        """
        return self._require(self.supports_transactions,
                             "Transactions are not supported",
                             func=func)

    def require_no_api_version(self, func):
        """Skip this test when testing with requireApiVersion."""
        return self._require(lambda: not MONGODB_API_VERSION,
                             "This test does not work with requireApiVersion",
                             func=func)

    def mongos_seeds(self):
        return ','.join('%s:%s' % address for address in self.mongoses)

    @property
    def supports_failCommand_fail_point(self):
        """Does the server support the failCommand fail point?"""
        if self.is_mongos:
            return (self.version.at_least(4, 1, 5)
                    and self.test_commands_enabled)
        else:
            return (self.version.at_least(4, 0) and self.test_commands_enabled)

    @property
    def requires_hint_with_min_max_queries(self):
        """Does the server require a hint with min/max queries."""
        # Changed in SERVER-39567.
        return self.version.at_least(4, 1, 10)
示例#37
0
class ClientContext(object):
    def __init__(self):
        """Create a client and grab essential information from the server."""
        self.connected = False
        self.ismaster = {}
        self.w = None
        self.nodes = set()
        self.replica_set_name = None
        self.cmd_line = None
        self.version = Version(-1)  # Needs to be comparable with Version
        self.auth_enabled = False
        self.test_commands_enabled = False
        self.is_mongos = False
        self.is_rs = False
        self.has_ipv6 = False
        self.ssl = False
        self.ssl_cert_none = False
        self.ssl_certfile = False
        self.server_is_resolvable = is_server_resolvable()
        self.ssl_client_options = {}
        self.client = _connect(host, port)

        if HAVE_SSL and not self.client:
            # Is MongoDB configured for SSL?
            self.client = _connect(host, port, **_SSL_OPTIONS)
            if self.client:
                self.ssl = True
                self.ssl_client_options = _SSL_OPTIONS
                self.ssl_certfile = True
                if _SSL_OPTIONS.get('ssl_cert_reqs') == ssl.CERT_NONE:
                    self.ssl_cert_none = True

        if self.client:
            self.connected = True
            ismaster = self.client.admin.command('ismaster')
            if 'setName' in ismaster:
                self.replica_set_name = ismaster['setName']
                self.is_rs = True
                # It doesn't matter which member we use as the seed here.
                self.client = pymongo.MongoClient(
                    host,
                    port,
                    replicaSet=self.replica_set_name,
                    **self.ssl_client_options)
                # Get the authoritative ismaster result from the primary.
                self.ismaster = self.client.admin.command('ismaster')
                nodes = [
                    partition_node(node.lower())
                    for node in self.ismaster.get('hosts', [])
                ]
                nodes.extend([
                    partition_node(node.lower())
                    for node in self.ismaster.get('passives', [])
                ])
                nodes.extend([
                    partition_node(node.lower())
                    for node in self.ismaster.get('arbiters', [])
                ])
                self.nodes = set(nodes)
            else:
                self.ismaster = ismaster
                self.nodes = set([(host, port)])
            self.w = len(self.ismaster.get("hosts", [])) or 1
            self.version = Version.from_client(self.client)

            try:
                self.cmd_line = self.client.admin.command('getCmdLineOpts')
            except pymongo.errors.OperationFailure as e:
                msg = e.details.get('errmsg', '')
                if e.code == 13 or 'unauthorized' in msg or 'login' in msg:
                    # Unauthorized.
                    self.auth_enabled = True
                else:
                    raise
            else:
                self.auth_enabled = self._server_started_with_auth()

            if self.auth_enabled:
                # See if db_user already exists.
                self.user_provided = self._check_user_provided()
                if not self.user_provided:
                    roles = {}
                    if self.version.at_least(2, 5, 3, -1):
                        roles = {'roles': ['root']}
                    self.client.admin.add_user(db_user, db_pwd, **roles)

                self.client = _connect(host,
                                       port,
                                       username=db_user,
                                       password=db_pwd,
                                       replicaSet=self.replica_set_name,
                                       **self.ssl_client_options)

                # May not have this if OperationFailure was raised earlier.
                self.cmd_line = self.client.admin.command('getCmdLineOpts')

            if 'enableTestCommands=1' in self.cmd_line['argv']:
                self.test_commands_enabled = True
            elif 'parsed' in self.cmd_line:
                params = self.cmd_line['parsed'].get('setParameter', [])
                if 'enableTestCommands=1' in params:
                    self.test_commands_enabled = True
                else:
                    params = self.cmd_line['parsed'].get('setParameter', {})
                    if params.get('enableTestCommands') == '1':
                        self.test_commands_enabled = True

            self.is_mongos = (self.ismaster.get('msg') == 'isdbgrid')
            self.has_ipv6 = self._server_started_with_ipv6()

    @property
    def host(self):
        if self.is_rs:
            primary = self.client.primary
            return primary[0] if primary is not None else host
        return host

    @property
    def port(self):
        if self.is_rs:
            primary = self.client.primary
            return primary[1] if primary is not None else port
        return port

    @property
    def pair(self):
        return "%s:%d" % (self.host, self.port)

    @property
    def has_secondaries(self):
        if not self.client:
            return False
        return bool(len(self.client.secondaries))

    def _check_user_provided(self):
        """Return True if db_user/db_password is already an admin user."""
        client = pymongo.MongoClient(host,
                                     port,
                                     username=db_user,
                                     password=db_pwd,
                                     serverSelectionTimeoutMS=100,
                                     **self.ssl_client_options)

        try:
            return db_user in _all_users(client.admin)
        except pymongo.errors.OperationFailure as e:
            msg = e.details.get('errmsg', '')
            if e.code == 18 or 'auth fails' in msg:
                # Auth failed.
                return False
            else:
                raise

    def _server_started_with_auth(self):
        # MongoDB >= 2.0
        if 'parsed' in self.cmd_line:
            parsed = self.cmd_line['parsed']
            # MongoDB >= 2.6
            if 'security' in parsed:
                security = parsed['security']
                # >= rc3
                if 'authorization' in security:
                    return security['authorization'] == 'enabled'
                # < rc3
                return (security.get('auth', False)
                        or bool(security.get('keyFile')))
            return parsed.get('auth', False) or bool(parsed.get('keyFile'))
        # Legacy
        argv = self.cmd_line['argv']
        return '--auth' in argv or '--keyFile' in argv

    def _server_started_with_ipv6(self):
        if not socket.has_ipv6:
            return False

        if 'parsed' in self.cmd_line:
            if not self.cmd_line['parsed'].get('net', {}).get('ipv6'):
                return False
        else:
            if '--ipv6' not in self.cmd_line['argv']:
                return False

        # The server was started with --ipv6. Is there an IPv6 route to it?
        try:
            for info in socket.getaddrinfo(self.host, self.port):
                if info[0] == socket.AF_INET6:
                    return True
        except socket.error:
            pass

        return False

    def _require(self, condition, msg, func=None):
        def make_wrapper(f):
            @wraps(f)
            def wrap(*args, **kwargs):
                # Always raise SkipTest if we can't connect to MongoDB
                if not self.connected:
                    raise SkipTest("Cannot connect to MongoDB on %s" %
                                   (self.pair, ))
                if condition:
                    return f(*args, **kwargs)
                raise SkipTest(msg)

            return wrap

        if func is None:

            def decorate(f):
                return make_wrapper(f)

            return decorate
        return make_wrapper(func)

    def require_connection(self, func):
        """Run a test only if we can connect to MongoDB."""
        return self._require(self.connected,
                             "Cannot connect to MongoDB on %s" % (self.pair, ),
                             func=func)

    def require_version_min(self, *ver):
        """Run a test only if the server version is at least ``version``."""
        other_version = Version(*ver)
        return self._require(
            self.version >= other_version,
            "Server version must be at least %s" % str(other_version))

    def require_version_max(self, *ver):
        """Run a test only if the server version is at most ``version``."""
        other_version = Version(*ver)
        return self._require(
            self.version <= other_version,
            "Server version must be at most %s" % str(other_version))

    def require_auth(self, func):
        """Run a test only if the server is running with auth enabled."""
        return self.check_auth_with_sharding(
            self._require(self.auth_enabled,
                          "Authentication is not enabled on the server",
                          func=func))

    def require_no_auth(self, func):
        """Run a test only if the server is running without auth enabled."""
        return self._require(
            not self.auth_enabled,
            "Authentication must not be enabled on the server",
            func=func)

    def require_replica_set(self, func):
        """Run a test only if the client is connected to a replica set."""
        return self._require(self.is_rs,
                             "Not connected to a replica set",
                             func=func)

    def require_secondaries_count(self, count):
        """Run a test only if the client is connected to a replica set that has
        `count` secondaries.
        """
        sec_count = 0 if not self.client else len(self.client.secondaries)
        return self._require(
            sec_count >= count,
            "Need %d secondaries, %d available" % (count, sec_count))

    def require_no_replica_set(self, func):
        """Run a test if the client is *not* connected to a replica set."""
        return self._require(
            not self.is_rs,
            "Connected to a replica set, not a standalone mongod",
            func=func)

    def require_ipv6(self, func):
        """Run a test only if the client can connect to a server via IPv6."""
        return self._require(self.has_ipv6, "No IPv6", func=func)

    def require_no_mongos(self, func):
        """Run a test only if the client is not connected to a mongos."""
        return self._require(not self.is_mongos,
                             "Must be connected to a mongod, not a mongos",
                             func=func)

    def require_mongos(self, func):
        """Run a test only if the client is connected to a mongos."""
        return self._require(self.is_mongos,
                             "Must be connected to a mongos",
                             func=func)

    def check_auth_with_sharding(self, func):
        """Skip a test when connected to mongos < 2.0 and running with auth."""
        condition = not (self.auth_enabled and self.is_mongos
                         and self.version < (2, ))
        return self._require(condition,
                             "Auth with sharding requires MongoDB >= 2.0.0",
                             func=func)

    def require_test_commands(self, func):
        """Run a test only if the server has test commands enabled."""
        return self._require(self.test_commands_enabled,
                             "Test commands must be enabled",
                             func=func)

    def require_ssl(self, func):
        """Run a test only if the client can connect over SSL."""
        return self._require(self.ssl,
                             "Must be able to connect via SSL",
                             func=func)

    def require_no_ssl(self, func):
        """Run a test only if the client can connect over SSL."""
        return self._require(not self.ssl,
                             "Must be able to connect without SSL",
                             func=func)

    def require_ssl_cert_none(self, func):
        """Run a test only if the client can connect with ssl.CERT_NONE."""
        return self._require(self.ssl_cert_none,
                             "Must be able to connect with ssl.CERT_NONE",
                             func=func)

    def require_ssl_certfile(self, func):
        """Run a test only if the client can connect with ssl_certfile."""
        return self._require(self.ssl_certfile,
                             "Must be able to connect with ssl_certfile",
                             func=func)

    def require_server_resolvable(self, func):
        """Run a test only if the hostname 'server' is resolvable."""
        return self._require(self.server_is_resolvable,
                             "No hosts entry for 'server'. Cannot validate "
                             "hostname in the certificate",
                             func=func)
示例#38
0
class ClientContext(object):

    def __init__(self):
        """Create a client and grab essential information from the server."""
        self.connection_attempts = []
        self.connected = False
        self.ismaster = {}
        self.w = None
        self.nodes = set()
        self.replica_set_name = None
        self.cmd_line = None
        self.server_status = None
        self.version = Version(-1)  # Needs to be comparable with Version
        self.auth_enabled = False
        self.test_commands_enabled = False
        self.is_mongos = False
        self.mongoses = []
        self.is_rs = False
        self.has_ipv6 = False
        self.ssl = False
        self.ssl_cert_none = False
        self.ssl_certfile = False
        self.server_is_resolvable = is_server_resolvable()
        self.default_client_options = {}
        self.sessions_enabled = False
        self.client = None
        self.conn_lock = threading.Lock()

        if COMPRESSORS:
            self.default_client_options["compressors"] = COMPRESSORS

    def _connect(self, host, port, **kwargs):
        # Jython takes a long time to connect.
        if sys.platform.startswith('java'):
            timeout_ms = 10000
        else:
            timeout_ms = 5000
        if COMPRESSORS:
            kwargs["compressors"] = COMPRESSORS
        client = pymongo.MongoClient(
            host, port, serverSelectionTimeoutMS=timeout_ms, **kwargs)
        try:
            try:
                client.admin.command('isMaster')  # Can we connect?
            except pymongo.errors.OperationFailure as exc:
                # SERVER-32063
                self.connection_attempts.append(
                    'connected client %r, but isMaster failed: %s' % (
                        client, exc))
            else:
                self.connection_attempts.append(
                    'successfully connected client %r' % (client,))
            # If connected, then return client with default timeout
            return pymongo.MongoClient(host, port, **kwargs)
        except pymongo.errors.ConnectionFailure as exc:
            self.connection_attempts.append(
                'failed to connect client %r: %s' % (client, exc))
            return None

    def _init_client(self):
        self.client = self._connect(host, port)
        if HAVE_SSL and not self.client:
            # Is MongoDB configured for SSL?
            self.client = self._connect(host, port, **_SSL_OPTIONS)
            if self.client:
                self.ssl = True
                self.default_client_options.update(_SSL_OPTIONS)
                self.ssl_certfile = True
                if _SSL_OPTIONS.get('ssl_cert_reqs') == ssl.CERT_NONE:
                    self.ssl_cert_none = True

        if self.client:
            self.connected = True

            try:
                self.cmd_line = self.client.admin.command('getCmdLineOpts')
            except pymongo.errors.OperationFailure as e:
                msg = e.details.get('errmsg', '')
                if e.code == 13 or 'unauthorized' in msg or 'login' in msg:
                    # Unauthorized.
                    self.auth_enabled = True
                else:
                    raise
            else:
                self.auth_enabled = self._server_started_with_auth()

            if self.auth_enabled:
                # See if db_user already exists.
                if not self._check_user_provided():
                    _create_user(self.client.admin, db_user, db_pwd)

                self.client = self._connect(
                    host, port, username=db_user, password=db_pwd,
                    replicaSet=self.replica_set_name,
                    **self.default_client_options)

                # May not have this if OperationFailure was raised earlier.
                self.cmd_line = self.client.admin.command('getCmdLineOpts')

            self.server_status = self.client.admin.command('serverStatus')
            self.ismaster = ismaster = self.client.admin.command('isMaster')
            self.sessions_enabled = 'logicalSessionTimeoutMinutes' in ismaster

            if 'setName' in ismaster:
                self.replica_set_name = str(ismaster['setName'])
                self.is_rs = True
                if self.auth_enabled:
                    # It doesn't matter which member we use as the seed here.
                    self.client = pymongo.MongoClient(
                        host,
                        port,
                        username=db_user,
                        password=db_pwd,
                        replicaSet=self.replica_set_name,
                        **self.default_client_options)
                else:
                    self.client = pymongo.MongoClient(
                        host,
                        port,
                        replicaSet=self.replica_set_name,
                        **self.default_client_options)

                # Get the authoritative ismaster result from the primary.
                self.ismaster = self.client.admin.command('ismaster')
                nodes = [partition_node(node.lower())
                         for node in self.ismaster.get('hosts', [])]
                nodes.extend([partition_node(node.lower())
                              for node in self.ismaster.get('passives', [])])
                nodes.extend([partition_node(node.lower())
                              for node in self.ismaster.get('arbiters', [])])
                self.nodes = set(nodes)
            else:
                self.ismaster = ismaster
                self.nodes = set([(host, port)])
            self.w = len(self.ismaster.get("hosts", [])) or 1
            self.version = Version.from_client(self.client)

            if 'enableTestCommands=1' in self.cmd_line['argv']:
                self.test_commands_enabled = True
            elif 'parsed' in self.cmd_line:
                params = self.cmd_line['parsed'].get('setParameter', [])
                if 'enableTestCommands=1' in params:
                    self.test_commands_enabled = True
                else:
                    params = self.cmd_line['parsed'].get('setParameter', {})
                    if params.get('enableTestCommands') == '1':
                        self.test_commands_enabled = True

            self.is_mongos = (self.ismaster.get('msg') == 'isdbgrid')
            self.has_ipv6 = self._server_started_with_ipv6()
            if self.is_mongos:
                # Check for another mongos on the next port.
                address = self.client.address
                next_address = address[0], address[1] + 1
                self.mongoses.append(address)
                mongos_client = self._connect(*next_address,
                                              **self.default_client_options)
                if mongos_client:
                    ismaster = mongos_client.admin.command('ismaster')
                    if ismaster.get('msg') == 'isdbgrid':
                        self.mongoses.append(next_address)

    def init(self):
        with self.conn_lock:
            if not self.client and not self.connection_attempts:
                self._init_client()

    def connection_attempt_info(self):
        return '\n'.join(self.connection_attempts)

    @property
    def host(self):
        if self.is_rs:
            primary = self.client.primary
            return str(primary[0]) if primary is not None else host
        return host

    @property
    def port(self):
        if self.is_rs:
            primary = self.client.primary
            return primary[1] if primary is not None else port
        return port

    @property
    def pair(self):
        return "%s:%d" % (self.host, self.port)

    @property
    def has_secondaries(self):
        if not self.client:
            return False
        return bool(len(self.client.secondaries))

    def _check_user_provided(self):
        """Return True if db_user/db_password is already an admin user."""
        client = pymongo.MongoClient(
            host, port,
            username=db_user,
            password=db_pwd,
            serverSelectionTimeoutMS=100,
            **self.default_client_options)

        try:
            return db_user in _all_users(client.admin)
        except pymongo.errors.OperationFailure as e:
            msg = e.details.get('errmsg', '')
            if e.code == 18 or 'auth fails' in msg:
                # Auth failed.
                return False
            else:
                raise

    def _server_started_with_auth(self):
        # MongoDB >= 2.0
        if 'parsed' in self.cmd_line:
            parsed = self.cmd_line['parsed']
            # MongoDB >= 2.6
            if 'security' in parsed:
                security = parsed['security']
                # >= rc3
                if 'authorization' in security:
                    return security['authorization'] == 'enabled'
                # < rc3
                return (security.get('auth', False) or
                        bool(security.get('keyFile')))
            return parsed.get('auth', False) or bool(parsed.get('keyFile'))
        # Legacy
        argv = self.cmd_line['argv']
        return '--auth' in argv or '--keyFile' in argv

    def _server_started_with_ipv6(self):
        if not socket.has_ipv6:
            return False

        if 'parsed' in self.cmd_line:
            if not self.cmd_line['parsed'].get('net', {}).get('ipv6'):
                return False
        else:
            if '--ipv6' not in self.cmd_line['argv']:
                return False

        # The server was started with --ipv6. Is there an IPv6 route to it?
        try:
            for info in socket.getaddrinfo(self.host, self.port):
                if info[0] == socket.AF_INET6:
                    return True
        except socket.error:
            pass

        return False

    def _require(self, condition, msg, func=None):
        def make_wrapper(f):
            @wraps(f)
            def wrap(*args, **kwargs):
                self.init()
                # Always raise SkipTest if we can't connect to MongoDB
                if not self.connected:
                    raise SkipTest(
                        "Cannot connect to MongoDB on %s" % (self.pair,))
                if condition():
                    return f(*args, **kwargs)
                raise SkipTest(msg)
            return wrap

        if func is None:
            def decorate(f):
                return make_wrapper(f)
            return decorate
        return make_wrapper(func)

    def create_user(self, dbname, user, pwd=None, roles=None, **kwargs):
        kwargs['writeConcern'] = {'w': self.w}
        return _create_user(self.client[dbname], user, pwd, roles, **kwargs)

    def drop_user(self, dbname, user):
        self.client[dbname].command(
            'dropUser', user, writeConcern={'w': self.w})

    def require_connection(self, func):
        """Run a test only if we can connect to MongoDB."""
        return self._require(
            lambda: True,  # _require checks if we're connected
            "Cannot connect to MongoDB on %s" % (self.pair,),
            func=func)

    def require_no_mmap(self, func):
        """Run a test only if the server is not using the MMAPv1 storage
        engine. Only works for standalone and replica sets; tests are
        run regardless of storage engine on sharded clusters. """
        def is_not_mmap():
            if self.is_mongos:
                return True
            try:
                storage_engine = self.server_status.get(
                    'storageEngine').get('name')
            except AttributeError:
                # Raised if the storageEngine key does not exist or if
                # self.server_status is None.
                return False
            return storage_engine != 'mmapv1'

        return self._require(
            is_not_mmap, "Storage engine must not be MMAPv1", func=func)

    def require_version_min(self, *ver):
        """Run a test only if the server version is at least ``version``."""
        other_version = Version(*ver)
        return self._require(lambda: self.version >= other_version,
                             "Server version must be at least %s"
                             % str(other_version))

    def require_version_max(self, *ver):
        """Run a test only if the server version is at most ``version``."""
        other_version = Version(*ver)
        return self._require(lambda: self.version <= other_version,
                             "Server version must be at most %s"
                             % str(other_version))

    def require_auth(self, func):
        """Run a test only if the server is running with auth enabled."""
        return self.check_auth_with_sharding(
            self._require(lambda: self.auth_enabled,
                          "Authentication is not enabled on the server",
                          func=func))

    def require_no_auth(self, func):
        """Run a test only if the server is running without auth enabled."""
        return self._require(lambda: not self.auth_enabled,
                             "Authentication must not be enabled on the server",
                             func=func)

    def require_replica_set(self, func):
        """Run a test only if the client is connected to a replica set."""
        return self._require(lambda: self.is_rs,
                             "Not connected to a replica set",
                             func=func)

    def require_secondaries_count(self, count):
        """Run a test only if the client is connected to a replica set that has
        `count` secondaries.
        """
        def sec_count():
            return 0 if not self.client else len(self.client.secondaries)
        return self._require(lambda: sec_count() >= count,
                             "Not enough secondaries available")

    def require_no_replica_set(self, func):
        """Run a test if the client is *not* connected to a replica set."""
        return self._require(
            lambda: not self.is_rs,
            "Connected to a replica set, not a standalone mongod",
            func=func)

    def require_ipv6(self, func):
        """Run a test only if the client can connect to a server via IPv6."""
        return self._require(lambda: self.has_ipv6,
                             "No IPv6",
                             func=func)

    def require_no_mongos(self, func):
        """Run a test only if the client is not connected to a mongos."""
        return self._require(lambda: not self.is_mongos,
                             "Must be connected to a mongod, not a mongos",
                             func=func)

    def require_mongos(self, func):
        """Run a test only if the client is connected to a mongos."""
        return self._require(lambda: self.is_mongos,
                             "Must be connected to a mongos",
                             func=func)

    def require_multiple_mongoses(self, func):
        """Run a test only if the client is connected to a sharded cluster
        that has 2 mongos nodes."""
        return self._require(lambda: len(self.mongoses) > 1,
                             "Must have multiple mongoses available",
                             func=func)

    def require_standalone(self, func):
        """Run a test only if the client is connected to a standalone."""
        return self._require(lambda: not (self.is_mongos or self.is_rs),
                             "Must be connected to a standalone",
                             func=func)

    def require_no_standalone(self, func):
        """Run a test only if the client is not connected to a standalone."""
        return self._require(lambda: self.is_mongos or self.is_rs,
                             "Must be connected to a replica set or mongos",
                             func=func)

    def check_auth_with_sharding(self, func):
        """Skip a test when connected to mongos < 2.0 and running with auth."""
        condition = lambda: not (self.auth_enabled and
                         self.is_mongos and self.version < (2,))
        return self._require(condition,
                             "Auth with sharding requires MongoDB >= 2.0.0",
                             func=func)

    def is_topology_type(self, topologies):
        if 'single' in topologies and not (self.is_mongos or self.is_rs):
            return True
        if 'replicaset' in topologies and self.is_rs:
            return True
        if 'sharded' in topologies and self.is_mongos:
            return True
        return False

    def require_cluster_type(self, topologies=[]):
        """Run a test only if the client is connected to a cluster that
        conforms to one of the specified topologies. Acceptable topologies
        are 'single', 'replicaset', and 'sharded'."""
        def _is_valid_topology():
            return self.is_topology_type(topologies)
        return self._require(
            _is_valid_topology,
            "Cluster type not in %s" % (topologies))

    def require_test_commands(self, func):
        """Run a test only if the server has test commands enabled."""
        return self._require(lambda: self.test_commands_enabled,
                             "Test commands must be enabled",
                             func=func)

    def require_ssl(self, func):
        """Run a test only if the client can connect over SSL."""
        return self._require(lambda: self.ssl,
                             "Must be able to connect via SSL",
                             func=func)

    def require_no_ssl(self, func):
        """Run a test only if the client can connect over SSL."""
        return self._require(lambda: not self.ssl,
                             "Must be able to connect without SSL",
                             func=func)

    def require_ssl_cert_none(self, func):
        """Run a test only if the client can connect with ssl.CERT_NONE."""
        return self._require(lambda: self.ssl_cert_none,
                             "Must be able to connect with ssl.CERT_NONE",
                             func=func)

    def require_ssl_certfile(self, func):
        """Run a test only if the client can connect with ssl_certfile."""
        return self._require(lambda: self.ssl_certfile,
                             "Must be able to connect with ssl_certfile",
                             func=func)

    def require_server_resolvable(self, func):
        """Run a test only if the hostname 'server' is resolvable."""
        return self._require(lambda: self.server_is_resolvable,
                             "No hosts entry for 'server'. Cannot validate "
                             "hostname in the certificate",
                             func=func)

    def require_sessions(self, func):
        """Run a test only if the deployment supports sessions."""
        return self._require(lambda: self.sessions_enabled,
                             "Sessions not supported",
                             func=func)

    def supports_transactions(self):
        if self.version.at_least(4, 1, 8):
            return self.is_mongos or self.is_rs

        if self.version.at_least(4, 0):
            return self.is_rs
        return False

    def require_transactions(self, func):
        """Run a test only if the deployment might support transactions.

        *Might* because this does not test the storage engine or FCV.
        """
        return self._require(self.supports_transactions,
                             "Transactions are not supported",
                             func=func)

    def mongos_seeds(self):
        return ','.join('%s:%s' % address for address in self.mongoses)

    @property
    def supports_reindex(self):
        """Does the connected server support reindex?"""
        return not (self.version.at_least(4, 1, 0) and self.is_mongos)

    @property
    def supports_getpreverror(self):
        """Does the connected server support getpreverror?"""
        return not (self.version.at_least(4, 1, 0) or self.is_mongos)

    @property
    def requires_hint_with_min_max_queries(self):
        """Does the server require a hint with min/max queries."""
        # Changed in SERVER-39567.
        return self.version.at_least(4, 1, 10)
示例#39
0
 def setup_version(self):
     """Set self.version to the server's version."""
     self.version = Version.from_client(self.sync_cx)