def test_client_properties_get_second_positive(self):
        config = ClientConfig()
        prop = ClientProperty("test", 1000, TimeUnit.MILLISECOND)
        config.set_property(prop.name, -1000)

        props = ClientProperties(config.get_properties())
        self.assertEqual(1, props.get_seconds_positive_or_default(prop))
    def test_client_properties_get_second(self):
        config = ClientConfig()
        prop = ClientProperty("test", time_unit=TimeUnit.MILLISECOND)
        config.set_property(prop.name, 1000)

        props = ClientProperties(config.get_properties())
        self.assertEqual(1, props.get_seconds(prop))
    def test_client_properties_with_config_and_default_value(self):
        config = ClientConfig()
        prop = ClientProperty("key", "def-value")
        config.set_property(prop.name, "value")

        props = ClientProperties(config.get_properties())
        self.assertEqual("value", props.get(prop))
    def test_client_properties_get_second_positive_unsupported_type(self):
        config = ClientConfig()
        prop = ClientProperty("test", "value", TimeUnit.MILLISECOND)
        config.set_property(prop.name, None)

        props = ClientProperties(config.get_properties())
        with self.assertRaises(ValueError):
            props.get_seconds_positive_or_default(prop)
    def test_client_properties_set_false_when_default_is_true(self):
        config = ClientConfig()
        prop = ClientProperty("test", True)
        config.set_property(prop.name, False)

        props = ClientProperties(config.get_properties())

        self.assertFalse(props.get(prop))
class HazelcastCloudDiscovery(object):
    """
    Discovery service that discover nodes via Hazelcast.cloud
    https://coordinator.hazelcast.cloud/cluster/discovery?token=<TOKEN>
    """
    _CLOUD_URL_PATH = "/cluster/discovery?token="
    _PRIVATE_ADDRESS_PROPERTY = "private-address"
    _PUBLIC_ADDRESS_PROPERTY = "public-address"

    CLOUD_URL_BASE_PROPERTY = ClientProperty("hazelcast.client.cloud.url", "https://coordinator.hazelcast.cloud")
    """
    Internal client property to change base url of cloud discovery endpoint.
    Used for testing cloud discovery.
    """

    def __init__(self, host, url, connection_timeout):
        self._host = host
        self._url = url
        self._connection_timeout = connection_timeout
        # Default context operates only on TLSv1+, checks certificates,hostname and validity
        self._ctx = ssl.create_default_context()

    def discover_nodes(self):
        """
        Discovers nodes from Hazelcast.cloud.

        :return: (dict), Dictionary that maps private addresses to public addresses.
        """
        try:
            return self._call_service()
        except Exception as ex:
            raise ex

    def _call_service(self):
        try:
            https_connection = http_client.HTTPSConnection(host=self._host,
                                                           timeout=self._connection_timeout,
                                                           context=self._ctx)
            https_connection.request(method="GET", url=self._url, headers={"Accept-Charset": "UTF-8"})
            https_response = https_connection.getresponse()
        except ssl.SSLError as ex:
            raise HazelcastCertificationError(str(ex))
        self._check_error(https_response)
        return self._parse_response(https_response)

    def _check_error(self, https_response):
        response_code = https_response.status
        if response_code != 200:  # HTTP OK
            error_message = https_response.read().decode("utf-8")
            raise IOError(error_message)

    def _parse_response(self, https_response):
        json_value = json.loads(https_response.read().decode("utf-8"))
        private_to_public_addresses = dict()
        for value in json_value:
            private_address = value[self._PRIVATE_ADDRESS_PROPERTY]
            public_address = value[self._PUBLIC_ADDRESS_PROPERTY]

            public_addr = self._parse_address(public_address)
            private_addr = self._parse_address(private_address)
            if private_addr.port == -1:
                # If not explicitly given, set the port of the private address to port of the public address
                private_addr.port = public_addr.port
            private_to_public_addresses[private_addr] = public_addr

        return private_to_public_addresses

    def _parse_address(self, address):
        if ':' in address:
            host, port = address.split(':')
            return Address(host, int(port))
        return Address(address, -1)

    @staticmethod
    def get_host_and_url(properties, cloud_token):
        """
        Helper method to get host and url that can be used in HTTPSConnection.

        :param properties: Client config properties.
        :param cloud_token: Cloud discovery token.
        :return: Host and URL pair
        """
        host = properties.get(HazelcastCloudDiscovery.CLOUD_URL_BASE_PROPERTY.name,
                              HazelcastCloudDiscovery.CLOUD_URL_BASE_PROPERTY.default_value)
        host = host.replace("https://", "")
        host = host.replace("http://", "")
        return host, HazelcastCloudDiscovery._CLOUD_URL_PATH + cloud_token
 def test_client_property_defaults(self):
     prop = ClientProperty("name")
     self.assertEqual("name", prop.name)
     self.assertIsNone(prop.default_value)
     self.assertIsNone(prop.time_unit)
 def test_client_property(self):
     prop = ClientProperty("name", 0, TimeUnit.SECOND)
     self.assertEqual("name", prop.name)
     self.assertEqual(0, prop.default_value)
     self.assertEqual(TimeUnit.SECOND, prop.time_unit)