-
Notifications
You must be signed in to change notification settings - Fork 290
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[FLOC-2137] Allow the public IP address of the node to be statically configured #2059
base: master
Are you sure you want to change the base?
Changes from all commits
d999f29
301b3fd
5248e27
c042b7b
f13fbd1
611376f
583b9db
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -110,7 +110,7 @@ def _get_external_ip(host, port): | |
:param host: A host to connect to. | ||
:param port: The port to connect to. | ||
|
||
:return unicode: IP address of external interface on this node. | ||
:return bytes: IP address of external interface on this node. | ||
""" | ||
while True: | ||
try: | ||
|
@@ -312,15 +312,16 @@ def get_service(self, reactor, options): | |
configuration = get_configuration(options) | ||
host = configuration['control-service']['hostname'] | ||
port = configuration['control-service']['port'] | ||
ip = self.get_external_ip(host, port) | ||
ip = configuration.get('hostname', self.get_external_ip(host, port)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suggest only running get_external_ip if configuration doesn't have an entry, given it adds noise to control service logs. |
||
|
||
tls_info = _context_factory_and_credential( | ||
options["agent-config"].parent(), host, port) | ||
|
||
return AgentLoopService( | ||
reactor=reactor, | ||
deployer=self.deployer_factory( | ||
node_uuid=tls_info.node_credential.uuid, hostname=ip, | ||
node_uuid=tls_info.node_credential.uuid, | ||
hostname=unicode(ip, "ascii"), | ||
cluster_uuid=tls_info.node_credential.cluster_uuid), | ||
host=host, port=port, | ||
context_factory=tls_info.context_factory, | ||
|
@@ -501,6 +502,9 @@ class AgentService(PRecord): | |
control_service_host = field(type=bytes, mandatory=True) | ||
control_service_port = field(type=int, mandatory=True) | ||
|
||
node_hostname = field(type=(str, type(None)), | ||
initial=None, mandatory=True) | ||
|
||
# Cannot use type=NodeCredential because one of the tests really wants to | ||
# set this to None. | ||
node_credential = field(mandatory=True) | ||
|
@@ -522,8 +526,10 @@ def from_configuration(cls, configuration): | |
:return: A new instance of ``cls`` with values loaded from the | ||
configuration. | ||
""" | ||
host = configuration['control-service']['hostname'] | ||
port = configuration['control-service']['port'] | ||
control_service_host = configuration['control-service']['hostname'] | ||
control_service_port = configuration['control-service']['port'] | ||
|
||
node_hostname = configuration.get('hostname') | ||
|
||
node_credential = configuration['node-credential'] | ||
ca_certificate = configuration['ca-certificate'] | ||
|
@@ -532,8 +538,10 @@ def from_configuration(cls, configuration): | |
backend_name = api_args.pop('backend') | ||
|
||
return cls( | ||
control_service_host=host, | ||
control_service_port=port, | ||
control_service_host=control_service_host, | ||
control_service_port=control_service_port, | ||
|
||
node_hostname=node_hostname, | ||
|
||
node_credential=node_credential, | ||
ca_certificate=ca_certificate, | ||
|
@@ -610,12 +618,17 @@ def get_deployer(self, api): | |
backend = self.get_backend() | ||
deployer_factory = self.deployers[backend.deployer_type] | ||
|
||
address = self.get_external_ip( | ||
self.control_service_host, self.control_service_port, | ||
) | ||
if self.node_hostname is None: | ||
hostname = self.get_external_ip( | ||
self.control_service_host, self.control_service_port, | ||
) | ||
else: | ||
hostname = self.node_hostname | ||
node_uuid = self.node_credential.uuid | ||
return deployer_factory( | ||
api=api, hostname=address, node_uuid=node_uuid, | ||
api=api, | ||
hostname=unicode(hostname, "ascii"), | ||
node_uuid=node_uuid, | ||
) | ||
|
||
def get_loop_service(self, deployer): | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -91,6 +91,8 @@ def setup_config(test, control_address=u"10.0.0.1", control_port=1234, | |
def deployer_factory_stub(**kw): | ||
if set(kw.keys()) != {"node_uuid", "cluster_uuid", "hostname"}: | ||
raise TypeError("wrong arguments") | ||
if not isinstance(kw["hostname"], unicode): | ||
raise TypeError("hostname not unicode") | ||
return deployer | ||
|
||
|
||
|
@@ -203,6 +205,8 @@ def test_initialized(self): | |
control_service_host=host, | ||
control_service_port=port, | ||
|
||
node_hostname=None, | ||
|
||
# Compare this separately :/ | ||
node_credential=None, | ||
ca_certificate=self.ca_set.root.credential.certificate, | ||
|
@@ -226,6 +230,21 @@ def test_initialized(self): | |
), | ||
) | ||
|
||
def test_initialized_hostname(self): | ||
host = b"192.0.2.13" | ||
port = 2314 | ||
name = u"from_config-test" | ||
|
||
setup_config(self, control_address=host, control_port=port, name=name) | ||
options = DatasetAgentOptions() | ||
options.parseOptions([b"--agent-config", self.config.path]) | ||
config = get_configuration(options) | ||
config['hostname'] = 'hostname.example' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So.... everywhere else we use IPs. This changes means we're OK with DNS hostnames appearing in e.g. REST API output? That might violate our schema. |
||
|
||
agent_service = AgentService.from_configuration(config) | ||
|
||
self.assertEqual(agent_service.node_hostname, "hostname.example") | ||
|
||
|
||
class AgentServiceGetAPITests(SynchronousTestCase): | ||
""" | ||
|
@@ -423,7 +442,7 @@ def test_backend_selection(self): | |
|
||
class Deployer(PRecord): | ||
api = field(mandatory=True) | ||
hostname = field(mandatory=True) | ||
hostname = field(type=unicode, mandatory=True) | ||
node_uuid = field(mandatory=True) | ||
|
||
class WrongDeployer(PRecord): | ||
|
@@ -454,7 +473,47 @@ def get_external_ip(host, port): | |
self.assertEqual( | ||
Deployer( | ||
api=api, | ||
hostname=ip, | ||
hostname=unicode(ip, "ascii"), | ||
node_uuid=self.ca_set.node.uuid, | ||
), | ||
deployer, | ||
) | ||
|
||
def test_hostname(self): | ||
""" | ||
``AgentService.get_deployer`` creates a new deployer supplied with the | ||
hostname from the configuration file, if one is provided. | ||
""" | ||
hostname = "hostname.example" | ||
|
||
class Deployer(PRecord): | ||
api = field(mandatory=True) | ||
hostname = field(unicode, mandatory=True) | ||
node_uuid = field(mandatory=True) | ||
|
||
agent_service = self.agent_service.set( | ||
"node_hostname", hostname, | ||
).set( | ||
"backends", [ | ||
BackendDescription( | ||
name=self.agent_service.backend_name, | ||
needs_reactor=False, needs_cluster_id=False, | ||
api_factory=None, deployer_type=DeployerType.p2p, | ||
), | ||
], | ||
).set( | ||
"deployers", { | ||
DeployerType.p2p: Deployer, | ||
}, | ||
) | ||
|
||
api = object() | ||
deployer = agent_service.get_deployer(api) | ||
|
||
self.assertEqual( | ||
Deployer( | ||
api=api, | ||
hostname=unicode(hostname, "ascii"), | ||
node_uuid=self.ca_set.node.uuid, | ||
), | ||
deployer, | ||
|
@@ -604,8 +663,9 @@ def test_config_validated(self): | |
|
||
def test_deployer_factory_called_with_ip(self): | ||
""" | ||
``AgentServiceFactory.main`` calls its ``deployer_factory`` with one | ||
of the node's IPs. | ||
If the configuration doesn't specify a ``hostname``, | ||
``AgentServiceFactory.main`` calls its ``deployer_factory`` with one of | ||
the node's IPs. | ||
""" | ||
spied = [] | ||
|
||
|
@@ -620,6 +680,34 @@ def deployer_factory(node_uuid, hostname, cluster_uuid): | |
agent.get_service(reactor, options) | ||
self.assertIn(spied[0], get_all_ips()) | ||
|
||
def test_deployer_factory_called_with_hostname(self): | ||
""" | ||
If the configuration does specify a ``hostname``, | ||
``AgentServiceFactory.main`` calls its ``deployer_factory`` with one of | ||
the hostname. | ||
""" | ||
spied = [] | ||
|
||
def deployer_factory(node_uuid, hostname, cluster_uuid): | ||
spied.append(hostname) | ||
return object() | ||
|
||
reactor = MemoryCoreReactor() | ||
options = DatasetAgentOptions() | ||
|
||
config = yaml.safe_load(self.config.getContent()) | ||
config["hostname"] = "hostname.local" | ||
self.config.setContent(yaml.safe_dump(config)) | ||
|
||
options.parseOptions([b"--agent-config", self.config.path]) | ||
|
||
agent = AgentServiceFactory(deployer_factory=deployer_factory) | ||
agent.get_service(reactor, options) | ||
|
||
self.assertEqual( | ||
(spied[0], type(spied[0])), | ||
("hostname.local", unicode)) | ||
|
||
def test_missing_configuration_file(self): | ||
""" | ||
``AgentServiceFactory.get_service`` raises an ``IOError`` if the given | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't strictly necessary though, right? We after all manage without right now. And it adds an extra burden of configuration on users that they don't necessarily need. Maybe indicate it's useful but not required? Or the caveats of not doing it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One thing we need to keep in mind: bandwidth costs. Data sent within AWS private network is free, data sent over external IPs costs money. I would hope that DNS lookups for AWS hosts within AWS get turned into the private IP, in which case documenting we want explicit hostname (as opposed to "address" which implies IP) is important. If hostnames always resolve to external IPs then it's possible that following this advice may cost our users a bunch of money on AWS, depending how AWS does bandwidth accounting...