You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kudu.apache.org by al...@apache.org on 2020/03/19 15:57:00 UTC
[kudu] branch master updated: [util] KUDU-3067 add OpenStack
metadata detector
This is an automated email from the ASF dual-hosted git repository.
alexey pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/kudu.git
The following commit(s) were added to refs/heads/master by this push:
new a80d747 [util] KUDU-3067 add OpenStack metadata detector
a80d747 is described below
commit a80d7472110ae2349a82c2150aad61079969b337
Author: Alexey Serbin <al...@apache.org>
AuthorDate: Wed Mar 18 16:09:58 2020 -0700
[util] KUDU-3067 add OpenStack metadata detector
This patch adds OpenStack metadata detector that works with OpenStack
Nova metadata server (see [1] for details). In addition, this patch
fixes the existing AWS detector to tell apart a true EC2 instance
from a masquerading OpenStack one [2].
I couldn't get access to an OpenStack instance, but I asked the reporter
of KUDU-3067 to test how it works and report back.
1. https://docs.openstack.org/nova/latest/user/metadata.html#metadata-service
2. https://docs.openstack.org/nova/latest/user/metadata.html#metadata-ec2-format
Change-Id: I84cc6d155ab1fbd7b401f5349d292f46fcac3a34
Reviewed-on: http://gerrit.cloudera.org:8080/15488
Tested-by: Kudu Jenkins
Reviewed-by: Adar Dembo <ad...@cloudera.com>
Reviewed-by: liusheng <li...@gmail.com>
---
src/kudu/util/cloud/instance_detector-test.cc | 11 +++++-
src/kudu/util/cloud/instance_detector.cc | 2 ++
src/kudu/util/cloud/instance_metadata.cc | 51 +++++++++++++++++++++++++--
src/kudu/util/cloud/instance_metadata.h | 35 ++++++++++++++----
4 files changed, 89 insertions(+), 10 deletions(-)
diff --git a/src/kudu/util/cloud/instance_detector-test.cc b/src/kudu/util/cloud/instance_detector-test.cc
index 0a406e8..a846e62 100644
--- a/src/kudu/util/cloud/instance_detector-test.cc
+++ b/src/kudu/util/cloud/instance_detector-test.cc
@@ -61,8 +61,9 @@ TEST(InstanceDetectorTest, Basic) {
const auto s_aws = AwsInstanceMetadata().Init();
const auto s_azure = AzureInstanceMetadata().Init();
const auto s_gce = GceInstanceMetadata().Init();
+ const auto s_openstack = OpenStackInstanceMetadata().Init();
- if (s_aws.ok() || s_azure.ok() || s_gce.ok()) {
+ if (s_aws.ok() || s_azure.ok() || s_gce.ok() || s_openstack.ok()) {
ASSERT_TRUE(s.ok()) << s.ToString();
ASSERT_NE(nullptr, metadata.get());
LOG(INFO) << Substitute("detected $0 environment",
@@ -76,15 +77,23 @@ TEST(InstanceDetectorTest, Basic) {
if (s_aws.ok()) {
ASSERT_FALSE(s_azure.ok());
ASSERT_FALSE(s_gce.ok());
+ ASSERT_FALSE(s_openstack.ok());
ASSERT_EQ(CloudType::AWS, metadata->type());
} else if (s_azure.ok()) {
ASSERT_FALSE(s_aws.ok());
ASSERT_FALSE(s_gce.ok());
+ ASSERT_FALSE(s_openstack.ok());
ASSERT_EQ(CloudType::AZURE, metadata->type());
} else if (s_gce.ok()) {
ASSERT_FALSE(s_aws.ok());
ASSERT_FALSE(s_azure.ok());
+ ASSERT_FALSE(s_openstack.ok());
ASSERT_EQ(CloudType::GCE, metadata->type());
+ } else if (s_openstack.ok()) {
+ ASSERT_FALSE(s_aws.ok());
+ ASSERT_FALSE(s_azure.ok());
+ ASSERT_FALSE(s_gce.ok());
+ ASSERT_EQ(CloudType::OPENSTACK, metadata->type());
}
}
diff --git a/src/kudu/util/cloud/instance_detector.cc b/src/kudu/util/cloud/instance_detector.cc
index 183139d..92523cf 100644
--- a/src/kudu/util/cloud/instance_detector.cc
+++ b/src/kudu/util/cloud/instance_detector.cc
@@ -46,6 +46,8 @@ InstanceDetector::InstanceDetector()
{ unique_ptr<InstanceMetadata>(new AzureInstanceMetadata), nullptr });
detectors_.push_back(
{ unique_ptr<InstanceMetadata>(new GceInstanceMetadata), nullptr });
+ detectors_.push_back(
+ { unique_ptr<InstanceMetadata>(new OpenStackInstanceMetadata), nullptr });
}
InstanceDetector::~InstanceDetector() {
diff --git a/src/kudu/util/cloud/instance_metadata.cc b/src/kudu/util/cloud/instance_metadata.cc
index cc26419..f8d640b 100644
--- a/src/kudu/util/cloud/instance_metadata.cc
+++ b/src/kudu/util/cloud/instance_metadata.cc
@@ -91,6 +91,18 @@ DEFINE_string(cloud_gce_instance_id_url,
TAG_FLAG(cloud_gce_instance_id_url, advanced);
TAG_FLAG(cloud_gce_instance_id_url, runtime);
+// See https://docs.openstack.org/nova/latest/user/metadata.html#metadata-service
+// and https://docs.openstack.org/nova/latest/user/metadata.html#metadata-openstack-format
+// for details.
+DEFINE_string(cloud_openstack_metadata_url,
+ "http://169.254.169.254/openstack/latest/meta_data.json",
+ "The URL to fetch metadata of an OpenStack instance via Nova "
+ "metadata service. OpenStack Nova metadata server does not "
+ "provide a separate URL to fetch instance UUID, at least with "
+ "12.0.0 (Liberty) release.");
+TAG_FLAG(cloud_openstack_metadata_url, advanced);
+TAG_FLAG(cloud_openstack_metadata_url, runtime);
+
DEFINE_validator(cloud_metadata_server_request_timeout_ms,
[](const char* name, const uint32_t val) {
if (val == 0) {
@@ -112,6 +124,7 @@ const char* TypeToString(CloudType type) {
static const char* const kTypeAws = "AWS";
static const char* const kTypeAzure = "Azure";
static const char* const kTypeGce = "GCE";
+ static const char* const kTypeOpenStack = "OpenStack";
switch (type) {
case CloudType::AWS:
return kTypeAws;
@@ -119,6 +132,8 @@ const char* TypeToString(CloudType type) {
return kTypeAzure;
case CloudType::GCE:
return kTypeGce;
+ case CloudType::OPENSTACK:
+ return kTypeOpenStack;
default:
LOG(FATAL) << static_cast<uint16_t>(type) << ": unknown cloud type";
break; // unreachable
@@ -133,7 +148,7 @@ Status InstanceMetadata::Init() {
// As of now, fetching the instance identifier from metadata service is
// the criterion for successful initialization of the instance metadata.
DCHECK(!is_initialized_);
- RETURN_NOT_OK(FetchInstanceId(nullptr));
+ RETURN_NOT_OK(Fetch(instance_id_url(), request_timeout(), request_headers()));
is_initialized_ = true;
return Status::OK();
}
@@ -162,8 +177,19 @@ Status InstanceMetadata::Fetch(const string& url,
return Status::OK();
}
-Status InstanceMetadata::FetchInstanceId(string* id) {
- return Fetch(instance_id_url(), request_timeout(), request_headers(), id);
+Status AwsInstanceMetadata::Init() {
+ // Try if the metadata server speaks AWS API.
+ RETURN_NOT_OK(InstanceMetadata::Init());
+
+ // If OpenStack instance metadata server is configured to emulate EC2,
+ // one way to tell it apart from a true EC2 instance is to check whether it
+ // speaks OpenStack API:
+ // https://docs.openstack.org/nova/latest/user/metadata.html#metadata-ec2-format
+ OpenStackInstanceMetadata openstack;
+ if (openstack.Init().ok()) {
+ return Status::ServiceUnavailable("found OpenStack instance, not AWS one");
+ }
+ return Status::OK();
}
Status AwsInstanceMetadata::GetNtpServer(string* server) const {
@@ -213,5 +239,24 @@ const string& GceInstanceMetadata::instance_id_url() const {
return FLAGS_cloud_gce_instance_id_url;
}
+Status OpenStackInstanceMetadata::GetNtpServer(string* /* server */) const {
+ // OpenStack doesn't provide a dedicated NTP server for an instance.
+ return Status::NotSupported("OpenStack doesn't provide a dedicated NTP server");
+}
+
+const vector<string>& OpenStackInstanceMetadata::request_headers() const {
+ // OpenStack Nova doesn't require any specific headers supplied with a
+ // generic query to the metadata server.
+ static const vector<string> kRequestHeaders = {};
+ return kRequestHeaders;
+}
+
+const string& OpenStackInstanceMetadata::instance_id_url() const {
+ // NOTE: OpenStack Nova metadata server doesn't provide a separate URL to
+ // fetch ID of an instance (at least with 1.12.0 release):
+ // https://docs.openstack.org/nova/latest/user/metadata.html#metadata-openstack-format
+ return FLAGS_cloud_openstack_metadata_url;
+}
+
} // namespace cloud
} // namespace kudu
diff --git a/src/kudu/util/cloud/instance_metadata.h b/src/kudu/util/cloud/instance_metadata.h
index 213f11a..e26ce83 100644
--- a/src/kudu/util/cloud/instance_metadata.h
+++ b/src/kudu/util/cloud/instance_metadata.h
@@ -34,6 +34,7 @@ enum class CloudType {
AWS,
AZURE,
GCE,
+ OPENSTACK,
};
const char* TypeToString(CloudType type);
@@ -45,6 +46,12 @@ const char* TypeToString(CloudType type);
//
// Concrete classes implementing this interface use stable APIs to retrieve
// corresponding information (published by corresponding cloud providers).
+//
+// NOTE:
+// It's assumed Kudu processes can access the metadata service (i.e. query
+// particular URLs of a metadata server) without specifying any credentials.
+// In other words, the metadata server should not be firewalled or hardened
+// with access controls to allow this code to work.
class InstanceMetadata {
public:
InstanceMetadata();
@@ -77,7 +84,7 @@ class InstanceMetadata {
static Status Fetch(const std::string& url,
MonoDelta timeout,
const std::vector<std::string>& headers,
- std::string* out);
+ std::string* out = nullptr);
// The timeout used for HTTP requests sent to the metadata server. The base
// implementation assumes the metadata server is robust enough to respond
@@ -94,11 +101,6 @@ class InstanceMetadata {
virtual const std::string& instance_id_url() const = 0;
private:
- // Fetch cloud instance identifier from the metadata server. Returns
- // Status::OK() in case of success, populating the output parameter 'id' with
- // the identifier of the instance. Returns non-OK status in case of errors.
- Status FetchInstanceId(std::string* id);
-
// Whether this object has been initialized.
bool is_initialized_;
};
@@ -111,6 +113,8 @@ class AwsInstanceMetadata : public InstanceMetadata {
AwsInstanceMetadata() = default;
~AwsInstanceMetadata() = default;
+ Status Init() override WARN_UNUSED_RESULT;
+
CloudType type() const override { return CloudType::AWS; }
Status GetNtpServer(std::string* server) const override WARN_UNUSED_RESULT;
@@ -150,5 +154,24 @@ class GceInstanceMetadata : public InstanceMetadata {
const std::string& instance_id_url() const override;
};
+// More information on Nova metadata server for OpenStack cloud instances is at:
+// https://docs.openstack.org/nova/latest/user/metadata.html#metadata-service
+//
+// TODO(aserbin): when necessary, implement extracting instance uuid out of the
+// meta_data.json content fetched from
+// FLAGS_cloud_openstack_metadata_url.
+class OpenStackInstanceMetadata : public InstanceMetadata {
+ public:
+ OpenStackInstanceMetadata() = default;
+ ~OpenStackInstanceMetadata() = default;
+
+ CloudType type() const override { return CloudType::OPENSTACK; }
+ Status GetNtpServer(std::string* server) const override WARN_UNUSED_RESULT;
+
+ protected:
+ const std::vector<std::string>& request_headers() const override;
+ const std::string& instance_id_url() const override;
+};
+
} // namespace cloud
} // namespace kudu