Skip to content

Commit 71deb00

Browse files
authored
GG-32415: Fix timezone mess when using SQL (#21)
1 parent 8dead15 commit 71deb00

8 files changed

Lines changed: 103 additions & 22 deletions

File tree

pygridgain/connection/__init__.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,13 @@
2323
import socket
2424
from threading import RLock
2525
from typing import Union
26+
from tzlocal import get_localzone
2627

2728
from pygridgain.constants import *
2829
from pygridgain.exceptions import (
2930
HandshakeError, ParameterError, SocketError, connection_errors,
3031
)
31-
from pygridgain.datatypes import Byte, Int, Short, String, UUIDObject
32+
from pygridgain.datatypes import Byte, ByteArrayObject, Int, Short, String, UUIDObject
3233
from pygridgain.datatypes.internal import Struct
3334
from pygridgain.utils import DaemonicTimer
3435

@@ -204,6 +205,11 @@ def read_response(self) -> Union[dict, OrderedDict]:
204205
('version_patch', Short),
205206
('message', String),
206207
])
208+
elif self.get_protocol_version() >= (1, 7, 0):
209+
response_end = Struct([
210+
('features', ByteArrayObject),
211+
('node_uuid', UUIDObject),
212+
])
207213
elif self.get_protocol_version() >= (1, 4, 0):
208214
response_end = Struct([
209215
('node_uuid', UUIDObject),
@@ -276,10 +282,13 @@ def _connect_version(
276282

277283
protocol_version = self.client.protocol_version
278284

285+
timezone = get_localzone().tzname(None)
286+
279287
hs_request = HandshakeRequest(
280-
protocol_version,
281-
self.username,
282-
self.password
288+
protocol_version=protocol_version,
289+
username=self.username,
290+
password=self.password,
291+
timezone=timezone,
283292
)
284293

285294
with BinaryStream(self) as stream:

pygridgain/connection/handshake.py

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,25 @@
1515
#
1616
from typing import Optional, Tuple
1717

18-
from pygridgain.datatypes import Byte, Int, Short, String
18+
from pygridgain.datatypes import Byte, ByteArrayObject, Int, MapObject, Short, String
1919
from pygridgain.datatypes.internal import Struct
2020

2121
OP_HANDSHAKE = 1
2222

23+
USER_ATTR_TIMEZONE = 'client.timezone'
24+
2325

2426
class HandshakeRequest:
2527
""" Handshake request. """
2628
handshake_struct = None
2729
username = None
2830
password = None
2931
protocol_version = None
32+
timezone = None
3033

3134
def __init__(
3235
self, protocol_version: Tuple[int, int, int],
33-
username: Optional[str] = None, password: Optional[str] = None
36+
username: Optional[str] = None, password: Optional[str] = None, timezone: str = None,
3437
):
3538
fields = [
3639
('length', Int),
@@ -41,6 +44,15 @@ def __init__(
4144
('client_code', Byte),
4245
]
4346
self.protocol_version = protocol_version
47+
self.timezone = timezone
48+
if protocol_version >= (1, 7, 0):
49+
fields.extend([
50+
('features', ByteArrayObject),
51+
])
52+
if protocol_version >= (1, 7, 1):
53+
fields.extend([
54+
('user_attributes', MapObject),
55+
])
4456
if username and password:
4557
self.username = username
4658
self.password = password
@@ -59,6 +71,21 @@ def from_python(self, stream):
5971
'version_patch': self.protocol_version[2],
6072
'client_code': 2, # fixed value defined by protocol
6173
}
74+
if self.protocol_version >= (1, 7, 0):
75+
handshake_data.update({
76+
'features': None,
77+
})
78+
handshake_data['length'] += 1
79+
if self.protocol_version >= (1, 7, 1):
80+
user_attributes = (
81+
MapObject.HASH_MAP, {
82+
USER_ATTR_TIMEZONE: self.timezone
83+
})
84+
85+
handshake_data.update({
86+
'user_attributes': user_attributes,
87+
})
88+
handshake_data['length'] += 6 + 5 + len(USER_ATTR_TIMEZONE) + 5 + len(self.timezone)
6289
if self.username and self.password:
6390
handshake_data.update({
6491
'username': self.username,
@@ -69,5 +96,4 @@ def from_python(self, stream):
6996
len(self.username),
7097
len(self.password),
7198
])
72-
7399
self.handshake_struct.from_python(stream, handshake_data)

pygridgain/constants.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,18 @@
3030
]
3131

3232
PROTOCOLS = {
33+
(1, 7, 1),
34+
(1, 7, 0),
35+
(1, 6, 0),
36+
(1, 5, 0),
3337
(1, 4, 0),
3438
(1, 3, 0),
3539
(1, 2, 0),
3640
}
3741

3842
PROTOCOL_VERSION_MAJOR = 1
39-
PROTOCOL_VERSION_MINOR = 4
40-
PROTOCOL_VERSION_PATCH = 0
43+
PROTOCOL_VERSION_MINOR = 7
44+
PROTOCOL_VERSION_PATCH = 1
4145

4246
MAX_LONG = 9223372036854775807
4347
MIN_LONG = -9223372036854775808

requirements/install.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22

33
typing==3.6.6; python_version<'3.5'
44
attrs==18.1.0
5+
tzlocal==2.1

tests/config/ignite-config.xml.jinja2

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22

33
<!--
4-
Copyright 2019 GridGain Systems, Inc. and Contributors.
4+
Copyright 2021 GridGain Systems, Inc. and Contributors.
55
66
Licensed under the GridGain Community Edition License (the "License");
77
you may not use this file except in compliance with the License.
@@ -59,12 +59,12 @@
5959
<property name="discoverySpi">
6060
<bean class="org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi">
6161
<property name="localAddress" value="127.0.0.1"/>
62-
<property name="localPort" value="48500"/>
62+
<property name="localPort" value="{{ discovery_port }}"/>
6363
<property name="ipFinder">
6464
<bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder">
6565
<property name="addresses">
6666
<list>
67-
<value>127.0.0.1:48500..48510</value>
67+
<value>127.0.0.1:{{ discovery_port }}..{{ discovery_port_range }}</value>
6868
</list>
6969
</property>
7070
</bean>
@@ -76,7 +76,7 @@
7676
<property name="communicationSpi">
7777
<bean class="org.apache.ignite.spi.communication.tcp.TcpCommunicationSpi">
7878
<property name="localAddress" value="127.0.0.1"/>
79-
<property name="localPort" value="48100"/>
79+
<property name="localPort" value="{{ communication_port }}"/>
8080
</bean>
8181
</property>
8282

tests/conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,8 @@ def server3(request):
8282

8383
@pytest.fixture(scope='module')
8484
def start_ignite_server(use_ssl):
85-
def start(idx=1):
86-
return _start_ignite(idx, use_ssl=use_ssl)
85+
def start(idx=1, cluster_idx=1, jvm_opts=''):
86+
return _start_ignite(idx, use_ssl=use_ssl, cluster_idx=cluster_idx, jvm_opts=jvm_opts)
8787

8888
return start
8989

tests/test_sql.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,19 @@
1313
# See the License for the specific language governing permissions and
1414
# limitations under the License.
1515
#
16+
from datetime import datetime
17+
18+
import pytest
19+
1620
from pygridgain.api import (
1721
sql_fields, sql_fields_cursor_get_page,
18-
cache_get_or_create, sql, sql_cursor_get_page,
22+
sql, sql_cursor_get_page,
1923
cache_get_configuration,
2024
)
2125
from pygridgain.datatypes.prop_codes import *
2226
from pygridgain.utils import entity_id
2327
from pygridgain.binary import unwrap_binary
28+
from tests.util import kill_process_tree
2429

2530
initial_data = [
2631
('John', 'Doe', 5),
@@ -187,3 +192,27 @@ def test_long_multipage_query(client):
187192
assert value == field_number * page[0]
188193

189194
client.sql(drop_query)
195+
196+
197+
@pytest.mark.parametrize('timezone', ['UTC', 'GMT+5', 'GMT-3'])
198+
def test_server_in_different_timezone(start_ignite_server, start_client, timezone):
199+
server_id = 10
200+
server = start_ignite_server(idx=server_id, cluster_idx=2, jvm_opts=f'-Duser.timezone={timezone}')
201+
try:
202+
client = start_client()
203+
client.connect('127.0.0.1', 10800 + server_id)
204+
205+
client.get_or_create_cache('PUBLIC')
206+
client.sql('create table test(key int primary key, time datetime)')
207+
208+
current_time = datetime(year=2020, month=2, day=12, hour=12, minute=32, second=55)
209+
client.sql(f"insert into test (key, time) VALUES (1, '{current_time}')")
210+
211+
page = client.sql('SELECT time FROM test')
212+
received = next(page)[0][0]
213+
214+
assert current_time == received
215+
216+
client.close()
217+
finally:
218+
kill_process_tree(server.pid)

tests/util.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def get_ignite_config_path(use_ssl=False):
7676
if use_ssl:
7777
file_name = "ignite-config-ssl.xml"
7878
else:
79-
file_name = "ignite-config.xml.jinja2"
79+
file_name = "ignite-default.xml.jinja2"
8080

8181
return os.path.join(get_test_dir(), "config", file_name)
8282

@@ -113,18 +113,30 @@ def create_config_file(tpl_name, file_name, **kwargs):
113113
f.write(template.render(**kwargs))
114114

115115

116-
def _start_ignite(idx=1, debug=False, use_ssl=False):
116+
def _start_ignite(idx=1, debug=False, use_ssl=False, cluster_idx=1, jvm_opts=''):
117117
clear_logs(idx)
118118

119119
runner = get_ignite_runner()
120120

121121
env = os.environ.copy()
122122

123123
if debug:
124-
env["JVM_OPTS"] = "-Djava.net.preferIPv4Stack=true -Xdebug -Xnoagent -Djava.compiler=NONE " \
125-
"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 "
126-
127-
params = {'ignite_instance_idx': str(idx), 'ignite_client_port': 10800 + idx, 'use_ssl': use_ssl}
124+
env["JVM_OPTS"] = env.get("JVM_OPTS", '') + \
125+
"-Djava.net.preferIPv4Stack=true -Xdebug -Xnoagent -Djava.compiler=NONE " \
126+
"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address={5005} "
127+
128+
if jvm_opts:
129+
env["JVM_OPTS"] = env.get("JVM_OPTS", '') + jvm_opts
130+
131+
port_offset = (cluster_idx - 1) * 10
132+
params = {
133+
'ignite_instance_idx': str(idx),
134+
'ignite_client_port': 10800 + idx,
135+
'use_ssl': use_ssl,
136+
'discovery_port': 48500 + port_offset,
137+
'discovery_port_range': 48510 + port_offset,
138+
'communication_port': 48100 + port_offset,
139+
}
128140

129141
create_config_file('log4j.xml.jinja2', f'log4j-{idx}.xml', **params)
130142
create_config_file('ignite-config.xml.jinja2', f'ignite-config-{idx}.xml', **params)

0 commit comments

Comments
 (0)