I'm currently working on a template-rendering CLI which has a fact plugin system for discovering facts about the current system. Some plugins are simple, one just exports environment variables, another checks the number of CPUs on the system via the num_cpu
crate.
Presently, I'm working on providing EC2 metadata facts using the EC2 metadata service. I haven't gotten very far, but I'm trying to at least do a generic discovery task to determine that:
- the EC2 metadata service is present by sending a HEAD request to
http://169.254.169.254/
. - the EC2 metadata service is, in fact, the EC2 metadata service by checking that the
Server
header isEC2ws
.
The full code can be seen in my pull request, but I'll summarize it here:
lazy_static! {
pub static ref EC2_METADATA_URL: String = format!("http://{}/", env::var("EC2_METADATA_HOST").unwrap_or("169.254.169.254".to_string()));
}
impl Ec2Plugin {
/// Determine whether we are currently running in EC2.
///
/// At present, we send a HEAD request to the EC2 metadata service and examine the `Server` header to determine
/// whether we're in EC2. If the `Server` header is `EC2ws`, we're in EC2.
fn is_ec2(&self) -> bool {
log::debug!("Checking the EC2 metadata server to detect whether we're in EC2...");
let fut = Client::default().head(EC2_METADATA_URL.as_str())
.header("User-Agent", "jinjer")
.send();
match fut.wait() {
Ok(response) => {
match response.status() {
StatusCode::OK => {
response.headers().get("Server").unwrap_or(&HeaderValue::from_static("Unknown")) == HeaderValue::from_static("EC2ws")
},
_ => {
log::debug!("Received non-200 response: {:?}", response);
false
}
}
},
Err(e) => {
log::debug!("Unable to query EC2 metadata service: {:?}", e);
false
}
}
}
}
For some reason, this code immediately with an error:
Unable to query EC2 metadata service: Connect(Timeout)
According to the Actix docs, the timeout is set to 5 seconds, but I'm seeing it time out in less than a millisecond.
Outside of this block of code, I'm instantiating an actix_rt::System
like so:
static CREATE_ACTIX_RUNTIME: Once = Once::new();
fn main() {
CREATE_ACTIX_RUNTIME.call_once(|| {
let _ = actix_rt::System::new("jinjer");
});
// execute code
}
It's strange to me that it's failing immediately.
One question I foresee being asked is "why use an asynchronous client at all?" My answer is that for this use-case, it's extremely important to keep execution time as small as possible, as there will be many fact providers executing at once, and I will need to launch a bunch of requests as I crawl each layer of the metadata service, as it mimics a filesystem of directories and files.
What am I missing or doing wrong?
EDIT Adding log output:
2019-06-21T20:29:50.228+0000 DEBUG [main] jinjer::facts::plugins::ec2: Checking the EC2 metadata server to detect whether we're in EC2...
2019-06-21T20:29:50.229+0000 TRACE [main] actix_connect::connector: TCP connector - connecting to "169.254.169.254" port:80
2019-06-21T20:29:50.229+0000 TRACE [main] mio::poll: registering with poller
2019-06-21T20:29:50.229+0000 DEBUG [main] tokio_reactor: adding I/O source: 0
2019-06-21T20:29:50.229+0000 TRACE [main] mio::poll: registering with poller
2019-06-21T20:29:50.229+0000 DEBUG [main] tokio_reactor::registration: scheduling Write for: 0
2019-06-21T20:29:50.229+0000 TRACE [main] mio::poll: deregistering handle with poller
2019-06-21T20:29:50.229+0000 DEBUG [main] tokio_reactor: dropping I/O source: 0
2019-06-21T20:29:50.229+0000 DEBUG [main] jinjer::facts::plugins::ec2: Unable to query EC2 metadata service: Connect(Timeout)
On the same machine:
$ curl -iI http://169.254.169.254/
HTTP/1.0 200 OK
Content-Type: text/plain
Accept-Ranges: bytes
ETag: "3954205079"
Last-Modified: Fri, 21 Jun 2019 19:51:37 GMT
Content-Length: 230
Connection: close
Date: Fri, 21 Jun 2019 20:31:10 GMT
Server: EC2ws