diff --git a/BUILD.md b/BUILD.md index f19da166a..a4d66730c 100644 --- a/BUILD.md +++ b/BUILD.md @@ -13,16 +13,15 @@ The client depends on: - OpenSSL - The Aerospike C client - ### RedHat 6+ and CentOS 6+ The following are dependencies for: - - RedHat Enterprise (RHEL) 6 or newer - - CentOS 6 or newer - - Related distributions which use the `yum` package manager +- RedHat Enterprise (RHEL) 6 or newer +- CentOS 6 or newer +- Related distributions which use the `yum` package manager -``` +```sh sudo yum install openssl-devel sudo yum install python26-devel # on CentOS 6 and similar sudo yum install python-devel # on CentOS 7 @@ -32,21 +31,19 @@ sudo yum install python-setuptools To get `python26-devel` on older distros such as CentOS 5, see [Stack Overflow](http://stackoverflow.com/a/11684053/582436). - ### Debian 6+ and Ubuntu 14.04+ The following are dependencies for: - - Debian 6 or newer - - Ubuntu 14.04 or newer - - Related distributions which use the `apt` package manager +- Debian 6 or newer +- Ubuntu 14.04 or newer +- Related distributions which use the `apt` package manager -``` +```sh sudo apt-get install libssl-dev sudo apt-get install build-essential python-dev ``` - ### OS X By default OS X will be missing command line tools. On Mavericks (OS X 10.9) @@ -58,7 +55,6 @@ The dependencies can be installed through the OS X package manager [Homebrew](ht brew install openssl - ## Build To build the library: @@ -66,23 +62,22 @@ To build the library: git submodule update --init python setup.py build --force - The helper `scripts/aerospike-client-c.sh` is triggered by `setup.py` to download the appropriate C client. However, if one is present this will not happen, and a build may fail against an old client. At that point you should remove the directory `aerospike-client-c` and run the build command again. - ### Troubleshooting OS X builds + Building on OS X versions >= 10.11 , may cause a few additional errors to be generated. If the build command fails with an error similar to: `error: could not create '/usr/local/aerospike/lua': Permission denied` there are a couple of options: - + - Rerun the build command with the additional command line flags `--user --prefix=` *Note that there are no charcters after the '='.* This will cause the library to only be installed for the current user, and store the library's data files in a user specific location. - rerun the command with sudo. If an error similar to `ld: targeted OS version does not support use of thread local variables` appears, it can be fixed by temporarily setting the `MACOSX_DEPLOYMENT_TARGET` environment variable to `'10.12'` e.g. -``` +```sh MACOSX_DEPLOYMENT_TARGET=10.12 python setup.py build --force MACOSX_DEPLOYMENT_TARGET=10.12 python setup.py install --force ``` @@ -96,7 +91,7 @@ you will need to first build the C client manually. 2. Install the dependencies. See the [README](https://github.com/aerospike/aerospike-client-c/blob/master/README.md). 3. Change directory to the C client, and build it. -``` +```sh git submodule update --init make ``` @@ -104,7 +99,7 @@ make 4. Clone the [aerospike/aerospike-lua-core](https://github.com/aerospike/aerospike-lua-core) repo from GitHub. 5. Change directory to the Python client and build it. -``` +```sh export DOWNLOAD_C_CLIENT=0 export AEROSPIKE_C_HOME=/path/to/aerospike-c-client export AEROSPIKE_LUA_PATH=/path/to/aerospike-lua-core/src @@ -113,65 +108,49 @@ python setup.py build --force If using sudo, you may need to set the values inline with the command: -``` +```bash sudo DOWNLOAD_C_CLIENT=0 AEROSPIKE_C_HOME=/path/to/aerospike-c-client AEROSPIKE_LUA_PATH=/path/to/aerospike-lua-core/src python setup.py build --force ``` - ## Install To install the library: python setup.py install --force - - ### Troubleshooting OS X Installation + Installing on OS X versions >= 10.11 , may cause a few additional errors to be generated. If the install command fails with an error similar to: `error: could not create '/usr/local/aerospike/lua': Permission denied` there are a couple of options: - + - Rerun the install command with the additional command line flags `--user --prefix=` *Note that there are no charcters after the '='.* This will cause the library to only be installed for the current user, and store the library's data files in a user specific location. - rerun the command with sudo. -### Lua System Modules - -Stream UDF functionality requires a local copy of the system Lua modules. -By default, those Lua files are copied to an `aerospike` directory inside of Python's' installation path for system dependent packages. This directory can be viewed by running `python -c "import sys; print(sys.prefix);" ` - - -**Note** The default search location for the lua system files is `/usr/local/aerospike/lua`. If the .lua files are stored somewhere besides `/usr/local/aerospike/lua`. and you wish to perform Stream UDF operations it will be necessary to specify the locations of the system modules as a configuration parameter to the Aerospike client constructor: - - config = {'hosts': [('127.0.0.1', 3000)], 'lua': {'system_path': '/path/to/lua'} ...} - my_client = aerospike.client(config) - ## Examples **Note** If you did not install the library, then you will need to setup your `PYTHONPATH` environment variable. The `PYTHONPATH` should contain an entry for the directory where the Python module is stored. This is usually in `build/lib.*`. - Examples are in the `examples` directory. The following examples are available: -* `kvs.py` — Key-Value Store API Example -* `query.py` — Query API Example -* `scan.py` — Scan API Example -* `info.py` — Info API Example -* `simple.lua` — Simple UDF Example +- `kvs.py` — Key-Value Store API Example +- `query.py` — Query API Example +- `scan.py` — Scan API Example +- `info.py` — Info API Example +- `simple.lua` — Simple UDF Example Each example provides help/usage information when you specify the `--help` option. For example, for help on the `kvs.py` example, then run: python examples/client/kvs.py --help - ### Running Examples Simply call `python` with the path to the example python examples/client/kvs.py - ## License The Aerospike Python Client is made availabled under the terms of the Apache License, Version 2, as stated in the file `LICENSE`. -Individual files may be made available under their own specific license, +Individual files may be made available under their own specific license, all compatible with Apache License, Version 2. Please see individual files for details. diff --git a/README.rst b/README.rst index 8a9e4b06b..b0bad11f4 100644 --- a/README.rst +++ b/README.rst @@ -91,21 +91,6 @@ using an outdated ``pip``. Versions of ``pip`` older than 7.0.0 should be upgraded, as well as versions of ``setuptools`` older than 18.0.0. -Lua files -~~~~~~~~~~ - -The system .lua files used for client side aggregation will be installed. -By default pip will install the .lua files in a subdirectory named `aerospike/lua/` inside of the Python -installations directory for platform specific files. The location of the files can be found by running: - -``pip show -f aerospike`` - - -**Note** If the .lua files are stored somewhere besides `/usr/local/aerospike/lua`. and you wish to perform Stream UDF operations it will be necessary to specify the locations of the system modules as a configuration parameter to the Aerospike client constructor: - - config = {'hosts': [('127.0.0.1', 3000)], 'lua': {'system_path': '/path/to/lua'} ...} - my_client = aerospike.client(config) - OS X Installation ~~~~~~~~~~~~~~~~~~ diff --git a/VERSION b/VERSION index 1545d9665..40c341bdc 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.5.0 +3.6.0 diff --git a/api-changes.md b/api-changes.md index 489c2e869..1511ea803 100644 --- a/api-changes.md +++ b/api-changes.md @@ -1,143 +1,4 @@ -# API Changes +# Backward Incompatible API Changes - -## Version 3.2.0 -### Additional Features -* Updated to C client `4.3.11` -* Added `client.map_get_by_value_list` and `client.map_get_by_key_list` These methods require Aerospike Server version >= 3.16.0.1 . - - -## Version 3.1.0 - -### Additional Features -* Updated to C client `4.3.6` -* Added `exists` policy field to operate policies - -### Backwards Incompatible API changes -* Updated the args passed to AerospikeError constructor internally to contain 5 arguments. - The arguments previously were `error code`, `error message`, `error file`, `error line`. - A fifth argument `in_doubt` has been added to the internal calls. so the arguments passed to the constructor are now : `error_code`, `error_message`, `error_file`, `error_line`, `in_doubt` - - This means that code such as the following will now raise a ValueError - -```python -try: - client.get(key) -except AerospikeError as e: - code, msg, file, line = e.args - print(code, msg, file, line) -``` - - This can be fixed by unpacking the fifth value from the Error's `args` tuple - -```python -try: - client.get(key) -except AerospikeError as e: - code, msg, file, line, in_doubt = e.args - print(code, msg, file, line) -``` - - -## Version 3.0.0 - - -### Additional Features - -* Updated to C client `4.3.1` -* Added a new list increment operation OP_LIST_INCREMENT . It can be used to increase an element of a list by a provided amount. -* Added the option to specify a specific node to run on a scan on, via a new optional nodename parameter. -* Added the option to specify that Query.results and Query.foreach should not return bins, via a new optional parameter options to both methods. -* Added aerospike.info_all() to allow sending of an info command to all nodes in the current cluster. -* Added linearize_read option to read policies. Requires Enterprise server >= 4.0.0 - -### Deprecations -* `client#info` has been deprecated. In order to send requests to the entire cluster, the new method `client#info_all` should be used. In order to send requests to hosts specified in a list, we recommend a loop invoking multiple calls to aerospike.info_node. See below for an example implementation: - -```python - -def info_to_host_list(client, request, hosts, policy=None): - output = {} - for host in hosts: - try: - response = client.info_node(request, host, policy) - output[host] = response - except Exception as e: - # Handle the error gracefully here - output[host] = e - return output -``` - -* Setting of global policy defaults via top level entries in the 'policies' dictionary in the constructor config dict has been deprecated. See the constructor documentation for the new recommended method of specifying defaults. - - -### Backwards Incompatible API changes -#### LDT Removal -Removed LDT (`client#llist`) methods and support. Server version 3.14 is the last version of the Aerospike server to support the functionality. - - -#### Index Methods - -* Methods which create indexes, ( `index_string_create`, `index_integer_create`, `index_list_create`, `index_map_keys_create`, `index_map_values_create` and, `index_geo2dsphere_create` ), will now raise an `IndexFoundError` if the specified bin has already been indexed, or if an index with the same name already exists. - -* Methods which drop indexes (`index_remove`), will now raise an `IndexNotFound` if the named index does not exist. - -#### Shared memory layout change -Shared memory layout has changed, and accordingly the default SHM key has changed from `0xA6000000` to `0xA7000000` . If manually specifiying an -SHM key, it is crucial to ensure that a separate key is used in order to prevent this version's client from sharing memory with a previous version. - -#### Changed and removed policy field names: -In all policies, (except for `info` and `admin`), `timeout` has been split into `total_timeout` and `socket_timeout` -`total_timeout` is an int representing total transaction timeout in milliseconds. The `total_timeout` is tracked on the client and sent to the server along with the transaction in the wire protocol. The client will most likely timeout first, but the server also has the capability to timeout the transaction. If `total_timeout` is not zero and `total_timeout` is reached before the transaction completes, the transaction will return error `TimeoutError`. If `total_timeout` is zero, there will be no total time limit. See the documentation for individual policies for the default values. - -`socket_timeout` is an int representing socket idle timeout in milliseconds when processing a database command. If `socket_timeout` is not zero and the socket has been idle for at least `socket_timeout`, both max_retries and `total_timeout` are checked. If `max_retries` and `total_timeout` are not exceeded, the transaction is retried. If both `socket_timeout` and `total_timeout` are non-zero and `socket_timeout > total_timeout`, then `socket_timeout` will be set to `total_timeout`. If `socket_timeout` is zero, there will be no socket idle limit. See the documentation for individual policies for the default values. - -`retry` has beeen removed. - -`max_retries` has been added, it is an integer specifying the number of times a transaction should be retried before aborting. If `max_retries` is exceeded, `TimeoutError` will be raised. - -WARNING: Database writes that are not idempotent (such as `client#increment`) should not be retried because the write operation may be performed multiple times if the client timed out previous transaction attempts. It’s important to use a distinct write policy for non-idempotent writes which sets `max_retries` = 0; - -The default value for `max_retries` is 2. - -#### Changes in policy defaults for `aerospike.client` constructor -In this version, individual config dictionaries (`read`, `write`, `apply`, `operate`, `scan`, `query`, `batch`, `remove`) for method types should be used inside of the top level `policies` dictionary for setting policies. In previous versions, individual policies were set at the top level `policies` dictionary rather than in per method type dictionaries. The type of policy which affects each method can be found in the documenation. See the main documentation for the keys and values available for these new configuration dictionaries. See below for an example of the change in constructor usage: - -```python -# Pre Python client 3.0.0 - -hosts = [('localhost', 3000)] - -timeout = 2345 -key_policy = aerospike.POLICY_KEY_SEND - -# This changes defaults for all methods, requiring a config dictionary to be passed in to all methods which -# should use different values. -policies = {timeout': timeout, 'key': key_policy, 'retry': aerospike.POLICY_RETRY_ONCE} -config = {'hosts': hosts, 'policies': policies} - -client = aerospike.client(config) -``` - -```python -# Post Python client 3.0.0 - -hosts = [('localhost', 3000)] - -write_total_timeout = 4321 -read_total_timeout = 1234 -write_key_policy = aerospike.POLICY_KEY_SEND - -read_policy = {'total_timeout': read_total_timeout, 'max_retries': 1} -write_policy = {'total_timeout': write_total_timeout, 'key': write_key_policy, 'max_retries': 1} - -# operate policies affect methods such as client#increment, so these should not be retried since they are not idempotent. -operate_policy = {'max_retries': 0} - -# Change the defaults for read, write, and operate methods, all other methods will use builtin defaults. -policies = {'read': read_policy, 'write': write_policy, 'operate': operate_policy} - -config = {'hosts': hosts, 'policies': policies} - -client = aerospike.client(config) -``` +The documentation of backward incompatible API changes has been moved to +. \ No newline at end of file diff --git a/doc/aerospike.rst b/doc/aerospike.rst index 856038308..a91177d53 100644 --- a/doc/aerospike.rst +++ b/doc/aerospike.rst @@ -74,7 +74,12 @@ in an in-memory primary index. * **max_nodes** maximum number of nodes allowed. Pad so new nodes can be added without configuration changes (default: 16) * **max_namespaces** similarly pad (default: 8) * **takeover_threshold_sec** take over tending if the cluster hasn't been checked for this many seconds (default: 30) - * **shm_key** explicitly set the shm key for this client. If **use_shared_connection** is not set, or set to `False`, the user must provide a value for this field in order for shared memory to work correctly. If , and only if, **use_shared_connection** is set to `True`, the key will be implicitly evaluated per unique hostname, and can be inspected with :meth:`~aerospike.Client.shm_key` . It is still possible to specify a key when using **use_shared_connection** = `True`. (default: 0xA7000000) + * **shm_key** + | explicitly set the shm key for this client. + | If **use_shared_connection** is not set, or set to `False`, the user must provide a value for this field in order for shared memory to work correctly. + | If , and only if, **use_shared_connection** is set to `True`, the key will be implicitly evaluated per unique hostname, and can be inspected with :meth:`~aerospike.Client.shm_key` . + | It is still possible to specify a key when using **use_shared_connection** = `True`. + | (default: 0xA8000000) * **use_shared_connection** :class:`bool` indicating whether this instance should share its connection to the Aerospike cluster with other client instances in the same process. (default: ``False``) * **tls** a :class:`dict` of optional TLS configuration parameters. **TLS usage requires Aerospike Enterprise Edition** * **enable** a :class:`bool` indicating whether tls should be enabled or not. Default: ``False`` @@ -102,6 +107,16 @@ in an in-memory primary index. * **tend_interval** polling interval in milliseconds for tending the cluster (default: 1000) * **compression_threshold** compress data for transmission if the object size is greater than a given number of bytes (default: 0, meaning 'never compress') (**Deprecated**, set this in the 'write' policy dictionary) * **cluster_name** only server nodes matching this name will be used when determining the cluster + * **rack_id** + | An integer. Rack where this client instance resides. + | **rack_aware** and **POLICY_REPLICA_PREFER_RACK** and server rack configuration must also be set to enable this functionality. + | Default 0. + * **rack_aware** + | Boolean. Track server rack data. + | This field is useful when directing read commands to the server node that contains the key and exists on the same rack as the client. + | This serves to lower cloud provider costs when nodes are distributed across different racks/data centers. + | **rack_id** and **POLICY_REPLICA_PREFER_RACK** and server rack configuration must also be set to enable this functionality. + | Default False :return: an instance of the :py:class:`aerospike.Client` class. @@ -1490,6 +1505,12 @@ Specifies which partition replica to read from. Distribute reads across nodes containing key's master and replicated partition in round-robin fashion. Currently restricted to master and one prole. +.. data:: POLICY_REPLICA_PREFER_RACK + + Try node on the same rack as the client first. If there are no nodes on the same rack, use POLICY_REPLICA_SEQUENCE instead. + + **rack_aware** and **rack_id** must be set in the config argument of the client constructor in order to enable this functionality + .. rubric:: Retry Policy Options Specifies the behavior of failed operations. diff --git a/doc/client.rst b/doc/client.rst index 3f3621014..e5da2963f 100644 --- a/doc/client.rst +++ b/doc/client.rst @@ -2878,6 +2878,8 @@ Batch Policies | Default: ``False`` * **consistency_level** one of the ``aerospike.POLICY_CONSISTENCY_*`` values such as :data:`aerospike.POLICY_CONSISTENCY_ONE` | Default: ``aerospike.POLICY_CONSISTENCY_ONE`` + * **replica** one of the ``aerospike.POLICY_REPLICA_*`` values such as :data:`aerospike.POLICY_REPLICA_MASTER` + | Default: ``aerospike.POLICY_REPLICA_SEQUENCE`` * **concurrent** :class:`bool` Determine if batch commands to each server are run in parallel threads. Default `False` * **allow_inline** :class:`bool` . Allow batch to be processed immediately in the server's receiving thread when the server deems it to be appropriate. If `False`, the batch will always be processed in separate transaction threads. This field is only relevant for the new batch index protocol. Default `True`. * **send_set_name** :class:`bool` Send set name field to server for every key in the batch for batch index protocol. This is only necessary when authentication is enabled and security roles are defined on a per set basis. Default: `False` diff --git a/setup.py b/setup.py index 548fa75f9..05973666b 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,7 @@ os.environ['ARCHFLAGS'] = '-arch x86_64' AEROSPIKE_C_VERSION = os.getenv('AEROSPIKE_C_VERSION') if not AEROSPIKE_C_VERSION: - AEROSPIKE_C_VERSION = '4.3.20' + AEROSPIKE_C_VERSION = '4.4.0' DOWNLOAD_C_CLIENT = os.getenv('DOWNLOAD_C_CLIENT') AEROSPIKE_C_HOME = os.getenv('AEROSPIKE_C_HOME') PREFIX = None diff --git a/src/include/exception_types.h b/src/include/exception_types.h index ce9544d73..6ba4d130d 100644 --- a/src/include/exception_types.h +++ b/src/include/exception_types.h @@ -63,8 +63,6 @@ struct exceptions { PyObject *RecordExistsError; PyObject *RecordTooBig; PyObject *RecordBusy; - PyObject *BinExistsError; - PyObject *BinNotFound; PyObject *BinIncompatibleType; //Index exceptions @@ -117,9 +115,9 @@ struct server_exceptions_struct { int server_exceptions_codes[10]; }; struct record_exceptions_struct { - PyObject * *record_exceptions[10]; - char * record_exceptions_name[10]; - int record_exceptions_codes[10]; + PyObject * *record_exceptions[8]; + char * record_exceptions_name[8]; + int record_exceptions_codes[8]; }; struct index_exceptions_struct { diff --git a/src/main/aerospike.c b/src/main/aerospike.c index 42d056f12..ee151117d 100644 --- a/src/main/aerospike.c +++ b/src/main/aerospike.c @@ -94,7 +94,7 @@ AerospikeConstants operator_constants[] = { MOD_INIT(aerospike) { - const char version[8] = "3.5.0"; + const char version[8] = "3.6.0"; // Makes things "thread-safe" PyEval_InitThreads(); int i = 0; diff --git a/src/main/client/type.c b/src/main/client/type.c index 9d022ef37..58d066c92 100644 --- a/src/main/client/type.c +++ b/src/main/client/type.c @@ -32,7 +32,10 @@ #include "tls_config.h" #include "policy_config.h" -enum {INIT_NO_CONFIG_ERR = 1, INIT_CONFIG_TYPE_ERR, INIT_LUA_USER_ERR, + +static int set_rack_aware_config(as_config* conf, PyObject* config_dict); + +enum {INIT_SUCCESS, INIT_NO_CONFIG_ERR, INIT_CONFIG_TYPE_ERR, INIT_LUA_USER_ERR, INIT_LUA_SYS_ERR, INIT_HOST_TYPE_ERR, INIT_EMPTY_HOSTS_ERR, INIT_INVALID_ADRR_ERR, INIT_SERIALIZE_ERR, INIT_DESERIALIZE_ERR, INIT_COMPRESSION_ERR, INIT_POLICY_PARAM_ERR}; @@ -1258,6 +1261,12 @@ static int AerospikeClient_Type_Init(AerospikeClient * self, PyObject * args, Py } } + if (set_rack_aware_config(&config, py_config) != INIT_SUCCESS) { + + return INIT_POLICY_PARAM_ERR; + } + + PyObject* py_max_socket_idle = NULL; py_max_socket_idle = PyDict_GetItemString(py_config, "max_socket_idle"); if (py_max_socket_idle && PyInt_Check(py_max_socket_idle)) { @@ -1337,6 +1346,39 @@ static int AerospikeClient_Type_Init(AerospikeClient * self, PyObject * args, Py } +static int set_rack_aware_config(as_config*conf, PyObject* config_dict) { + PyObject* py_config_value; + long rack_id; + py_config_value = PyDict_GetItemString(config_dict, "rack_aware"); + if (py_config_value) { + if (PyBool_Check(py_config_value)) { + conf->rack_aware = PyObject_IsTrue(py_config_value); + } else { + return INIT_POLICY_PARAM_ERR; // A non boolean was passed in as the value of rack_aware + } + } + + py_config_value = PyDict_GetItemString(config_dict, "rack_id"); + if (py_config_value) { + if (PyLong_Check(py_config_value)) { + rack_id = PyLong_AsLong(py_config_value); + } else if (PyInt_Check(py_config_value)) { + rack_id = PyInt_AsLong(py_config_value); + } else { + return INIT_POLICY_PARAM_ERR; // A non integer passed in. + } + if (rack_id == -1 && PyErr_Occurred()) { + return INIT_POLICY_PARAM_ERR; // We had overflow. + } + + if (rack_id > INT_MAX || rack_id < INT_MIN) { + return INIT_POLICY_PARAM_ERR; // Magnitude too great for an integer in C. + } + conf->rack_id = (int)rack_id; + } + return INIT_SUCCESS; +} + static void AerospikeClient_Type_Dealloc(PyObject * self) { diff --git a/src/main/conversions.c b/src/main/conversions.c index 23c411824..26e3e8243 100644 --- a/src/main/conversions.c +++ b/src/main/conversions.c @@ -482,7 +482,7 @@ as_status pyobject_to_val(AerospikeClient * self, as_error * err, PyObject * py_ } else if (AS_Matches_Classname(py_obj, AS_CDT_INFINITE_NAME)) { *val = (as_val *) as_val_reserve(&as_cmp_inf); } else { - if (aerospike_has_double(self->as) && PyFloat_Check(py_obj)) { + if (PyFloat_Check(py_obj)) { double d = PyFloat_AsDouble(py_obj); *val = (as_val *) as_double_new(d); } else { @@ -651,7 +651,7 @@ as_status pyobject_to_record(AerospikeClient * self, as_error * err, PyObject * } else if (!strcmp(value->ob_type->tp_name, "aerospike.null")) { ret_val = as_record_set_nil(rec, name); } else { - if (aerospike_has_double(self->as) && PyFloat_Check(value)) { + if (PyFloat_Check(value)) { double val = PyFloat_AsDouble(value); ret_val = as_record_set_double(rec, name, val); } else { @@ -813,7 +813,7 @@ as_status pyobject_to_astype_write(AerospikeClient * self, as_error * err, PyObj } else if (AS_Matches_Classname(py_value, AS_CDT_INFINITE_NAME)) { *val = (as_val *) as_val_reserve(&as_cmp_inf); }else { - if (aerospike_has_double(self->as) && PyFloat_Check(py_value)) { + if (PyFloat_Check(py_value)) { double d = PyFloat_AsDouble(py_value); *val = (as_val *) as_double_new(d); } else { @@ -1628,17 +1628,8 @@ void initialize_bin_for_strictypes(AerospikeClient *self, as_error *err, PyObjec Py_XDECREF(py_ustr1); } else if (PyFloat_Check(py_value)) { int64_t val = PyFloat_AsDouble(py_value); - if (aerospike_has_double(self->as)) { - as_double_init((as_double *) &binop_bin->value, val); - binop_bin->valuep = &binop_bin->value; - } else { - as_bytes *bytes; - GET_BYTES_POOL(bytes, static_pool, err); - serialize_based_on_serializer_policy(self, SERIALIZER_PYTHON, - &bytes, py_value, err); - ((as_val *) &binop_bin->value)->type = AS_UNKNOWN; - binop_bin->valuep = (as_bin_value *) bytes; - } + as_double_init((as_double *) &binop_bin->value, val); + binop_bin->valuep = &binop_bin->value; } else if (PyList_Check(py_value)) { as_list * list = NULL; pyobject_to_list(self, err, py_value, &list, static_pool, SERIALIZER_PYTHON); diff --git a/src/main/exception.c b/src/main/exception.c index f74d6d3b6..3bb825c77 100644 --- a/src/main/exception.c +++ b/src/main/exception.c @@ -51,14 +51,12 @@ PyObject * AerospikeException_New(void) struct record_exceptions_struct record_array = { {&exceptions_array.RecordKeyMismatch, &exceptions_array.RecordNotFound, &exceptions_array.RecordGenerationError, &exceptions_array.RecordExistsError, &exceptions_array.RecordTooBig, &exceptions_array.RecordBusy, - &exceptions_array.BinNameError, &exceptions_array.BinExistsError, &exceptions_array.BinNotFound, - &exceptions_array.BinIncompatibleType}, + &exceptions_array.BinNameError, &exceptions_array.BinIncompatibleType}, {"RecordKeyMismatch", "RecordNotFound", "RecordGenerationError", "RecordExistsError", "RecordTooBig", "RecordBusy", - "BinNameError", "BinExistsError", "BinNotFound", "BinIncompatibleType"}, + "BinNameError", "BinIncompatibleType"}, {AEROSPIKE_ERR_RECORD_KEY_MISMATCH, AEROSPIKE_ERR_RECORD_NOT_FOUND, AEROSPIKE_ERR_RECORD_GENERATION, AEROSPIKE_ERR_RECORD_EXISTS, AEROSPIKE_ERR_RECORD_TOO_BIG, AEROSPIKE_ERR_RECORD_BUSY, - AEROSPIKE_ERR_BIN_NAME, AEROSPIKE_ERR_BIN_EXISTS, AEROSPIKE_ERR_BIN_NOT_FOUND, - AEROSPIKE_ERR_BIN_INCOMPATIBLE_TYPE} + AEROSPIKE_ERR_BIN_NAME, AEROSPIKE_ERR_BIN_INCOMPATIBLE_TYPE} }; struct index_exceptions_struct index_array = { diff --git a/src/main/policy.c b/src/main/policy.c index 183388f1a..639ef202a 100644 --- a/src/main/policy.c +++ b/src/main/policy.c @@ -128,6 +128,7 @@ AerospikeConstants aerospike_constants[] = { { AS_POLICY_REPLICA_MASTER , "POLICY_REPLICA_MASTER" }, { AS_POLICY_REPLICA_ANY , "POLICY_REPLICA_ANY" }, { AS_POLICY_REPLICA_SEQUENCE , "POLICY_REPLICA_SEQUENCE" }, + { AS_POLICY_REPLICA_PREFER_RACK , "POLICY_REPLICA_PREFER_RACK" }, { AS_POLICY_CONSISTENCY_LEVEL_ONE , "POLICY_CONSISTENCY_ONE" }, { AS_POLICY_CONSISTENCY_LEVEL_ALL , "POLICY_CONSISTENCY_ALL" }, { AS_POLICY_COMMIT_LEVEL_ALL , "POLICY_COMMIT_LEVEL_ALL" }, @@ -750,6 +751,7 @@ as_status pyobject_to_policy_batch(as_error * err, PyObject * py_policy, POLICY_SET_FIELD(send_set_name, bool); POLICY_SET_FIELD(deserialize, bool); POLICY_SET_FIELD(linearize_read, bool); + POLICY_SET_FIELD(replica, as_policy_replica); // Update the policy POLICY_UPDATE(); diff --git a/test/new_tests/test_admin_grant_privileges.py b/test/new_tests/test_admin_grant_privileges.py index ad36a51cb..dc8a23b3a 100644 --- a/test/new_tests/test_admin_grant_privileges.py +++ b/test/new_tests/test_admin_grant_privileges.py @@ -32,7 +32,8 @@ def setup_method(self, method): try: self.client.admin_drop_role("usr-sys-admin-test") - except: + time.sleep(1) + except e.InvalidRole: pass self.client.admin_create_role("usr-sys-admin-test", [{"code": aerospike.PRIV_USER_ADMIN}, @@ -45,19 +46,20 @@ def teardown_method(self, method): Teardown method """ - self.client.admin_drop_role("usr-sys-admin-test") + try: + self.client.admin_drop_role("usr-sys-admin-test") + time.sleep(1) + except e.InvalidRole: + pass self.client.close() def test_admin_grant_privileges_no_parameters(self): """ Grant privileges with no parameters """ - with pytest.raises(TypeError) as typeError: + with pytest.raises(TypeError): self.client.admin_grant_privileges() - assert "Required argument 'role' (pos 1) not found" in str( - typeError.value) - def test_admin_grant_privileges_positive(self): """ Grant privileges positive diff --git a/test/new_tests/test_admin_grant_roles.py b/test/new_tests/test_admin_grant_roles.py index 0b36b0441..5c627e4a8 100644 --- a/test/new_tests/test_admin_grant_roles.py +++ b/test/new_tests/test_admin_grant_roles.py @@ -31,15 +31,19 @@ def setup_method(self, method): try: self.client.admin_drop_user("example-test") - except: + time.sleep(1) + except e.InvalidUser: pass policy = {} user = "example-test" password = "foo2" roles = ["read-write"] - self.client.admin_create_user(user, password, roles, policy) - + try: + self.client.admin_create_user(user, password, roles, policy) + time.sleep(1) + except e.UserExistsError: + pass self.delete_users = [] def teardown_method(self, method): @@ -48,9 +52,11 @@ def teardown_method(self, method): """ policy = {} - - self.client.admin_drop_user("example-test", policy) - + try: + self.client.admin_drop_user("example-test", policy) + time.sleep(1) + except e.InvalidUser: + pass self.client.close() def test_grant_roles_without_any_parameters(self): diff --git a/test/new_tests/test_admin_query_role.py b/test/new_tests/test_admin_query_role.py index db3c91a43..a65ece086 100644 --- a/test/new_tests/test_admin_query_role.py +++ b/test/new_tests/test_admin_query_role.py @@ -57,12 +57,9 @@ def test_admin_query_role_no_parameters(self): """ Query role with no parameters """ - with pytest.raises(TypeError) as typeError: + with pytest.raises(TypeError): self.client.admin_query_role() - assert "Required argument 'role' (pos 1) not found" in str( - typeError.value) - def test_admin_query_role_positive(self): """ Query role positive diff --git a/test/new_tests/test_admin_query_user.py b/test/new_tests/test_admin_query_user.py index 6b2437404..260a54c45 100644 --- a/test/new_tests/test_admin_query_user.py +++ b/test/new_tests/test_admin_query_user.py @@ -30,15 +30,19 @@ def setup_method(self, method): self.client = aerospike.client(config).connect(user, password) try: self.client.admin_drop_user("example-test") - except: + time.sleep(1) + except e.InvalidUser: pass policy = {} user = "example-test" password = "foo2" roles = ["read-write", "sys-admin", "read"] - self.client.admin_create_user(user, password, roles, policy) - + try: + self.client.admin_create_user(user, password, roles, policy) + time.sleep(1) + except e.UserExistsError: + pass self.delete_users = [] def teardown_method(self, method): @@ -48,18 +52,19 @@ def teardown_method(self, method): policy = {} - self.client.admin_drop_user("example-test", policy) + try: + self.client.admin_drop_user("example-test", policy) + time.sleep(1) + except e.InvalidUser: + pass self.client.close() def test_query_user_without_any_parameters(self): - with pytest.raises(TypeError) as typeError: + with pytest.raises(TypeError): self.client.admin_query_user() - assert "Required argument 'user' (pos 1) not found" in str( - typeError.value) - def test_query_user_with_proper_parameters(self): user = "example-test" diff --git a/test/new_tests/test_admin_query_users.py b/test/new_tests/test_admin_query_users.py index 233c12b8c..c3c2fcebf 100644 --- a/test/new_tests/test_admin_query_users.py +++ b/test/new_tests/test_admin_query_users.py @@ -31,15 +31,19 @@ def setup_method(self, method): try: self.client.admin_drop_user("example-test") - except: + time.sleep(1) + except e.InvalidUser: pass policy = {} user = "example-test" password = "foo2" roles = ["read-write", "sys-admin", "read"] - self.client.admin_create_user(user, password, roles, policy) - + try: + self.client.admin_create_user(user, password, roles, policy) + time.sleep(1) + except e.UserExistsError: + pass self.delete_users = [] def teardown_method(self, method): diff --git a/test/new_tests/test_admin_revoke_privileges.py b/test/new_tests/test_admin_revoke_privileges.py index d36555d7d..b4404737f 100644 --- a/test/new_tests/test_admin_revoke_privileges.py +++ b/test/new_tests/test_admin_revoke_privileges.py @@ -31,31 +31,32 @@ def setup_method(self, method): self.client = aerospike.client(config).connect(user, password) try: self.client.admin_drop_role("usr-sys-admin-test") - except: + time.sleep(1) + except e.InvalidRole: pass self.client.admin_create_role("usr-sys-admin-test", [{"code": aerospike.PRIV_USER_ADMIN}, {"code": aerospike.PRIV_SYS_ADMIN}]) + time.sleep(1) self.delete_users = [] def teardown_method(self, method): """ Teardown method """ - - self.client.admin_drop_role("usr-sys-admin-test") + try: + self.client.admin_drop_role("usr-sys-admin-test") + except e.InvalidRole: + pass self.client.close() def test_admin_revoke_privileges_no_parameters(self): """ Revoke privileges with no parameters """ - with pytest.raises(TypeError) as typeError: + with pytest.raises(TypeError): self.client.admin_revoke_privileges() - assert "Required argument 'role' (pos 1) not found" in str( - typeError.value) - def test_admin_revoke_privileges_positive(self): """ revoke privileges positive diff --git a/test/new_tests/test_admin_revoke_roles.py b/test/new_tests/test_admin_revoke_roles.py index e8c748d01..ef7a240c4 100644 --- a/test/new_tests/test_admin_revoke_roles.py +++ b/test/new_tests/test_admin_revoke_roles.py @@ -30,14 +30,19 @@ def setup_method(self, method): self.client = aerospike.client(config).connect(user, password) try: self.client.admin_drop_user("example-test") - except: + time.sleep(1) + except e.InvalidUser: pass policy = {} user = "example-test" password = "foo2" roles = ["read-write", "sys-admin", "read"] - self.client.admin_create_user(user, password, roles, policy) + try: + self.client.admin_create_user(user, password, roles, policy) + time.sleep(1) + except e.UserExistsError: + pass self.delete_users = [] @@ -48,18 +53,18 @@ def teardown_method(self, method): policy = {} - self.client.admin_drop_user("example-test", policy) - + try: + self.client.admin_drop_user("example-test", policy) + time.sleep(1) + except e.InvalidUser: + pass self.client.close() def test_revoke_roles_without_any_parameters(self): - with pytest.raises(TypeError) as typeError: + with pytest.raises(TypeError): self.client.admin_revoke_roles() - assert "Required argument 'user' (pos 1) not found" in str( - typeError.value) - def test_revoke_roles_with_proper_parameters(self): policy = {} diff --git a/test/new_tests/test_admin_set_password.py b/test/new_tests/test_admin_set_password.py index ba0abfa15..fdfd664bc 100644 --- a/test/new_tests/test_admin_set_password.py +++ b/test/new_tests/test_admin_set_password.py @@ -2,6 +2,7 @@ import pytest import sys +import time from .test_base_class import TestBaseClass from aerospike import exception as e @@ -29,11 +30,17 @@ def setup_method(self, method): self.client = aerospike.client(config).connect(user, password) try: self.client.admin_drop_user("testsetpassworduser") - except: + time.sleep(2) + except e.InvalidUser: pass - self.client.admin_create_user( - "testsetpassworduser", "aerospike", ["read"], {}) + try: + self.client.admin_create_user( + "testsetpassworduser", "aerospike", ["read"], {}) + except e.UserExistsError: + pass + + time.sleep(2) self.delete_users = [] def teardown_method(self, method): @@ -41,8 +48,11 @@ def teardown_method(self, method): Teardown method """ - self.client.admin_drop_user("testsetpassworduser") - + try: + self.client.admin_drop_user("testsetpassworduser") + time.sleep(2) + except e.InvalidUser: + pass self.client.close() def test_set_password_without_any_parameters(self): diff --git a/test/new_tests/test_new_constructor.py b/test/new_tests/test_new_constructor.py index 5e50fd36d..070dcd102 100644 --- a/test/new_tests/test_new_constructor.py +++ b/test/new_tests/test_new_constructor.py @@ -78,3 +78,27 @@ def test_setting_wrong_type(): config = {'hosts': host, 'policies': policies} with pytest.raises(e.ParamError): client = aerospike.client(config) + +def test_setting_rack_aware_and_rack_id(): + config = {'hosts': host, 'rack_aware': True, 'rack_id': 0x1234} + client = aerospike.client(config) + assert client is not None + +def test_setting_rack_aware_non_bool(): + config = {'hosts': host, 'rack_aware': "True"} + with pytest.raises(e.ParamError): + client = aerospike.client(config) + +@pytest.mark.parametrize( + "rack_id", + ( + 'test_id', # String + '3.14', # Float + -(1 << 40), # Too small + (1 << 32), # Too large + ) +) +def test_setting_rack_id_wrong_type(rack_id): + config = {'hosts': host, 'rack_id': rack_id} + with pytest.raises(e.ParamError): + client = aerospike.client(config)