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