You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by nn...@apache.org on 2015/09/17 03:21:47 UTC

[5/7] mesos git commit: Added NetworkInfo message to ContainerInfo and TaskStatus.

Added NetworkInfo message to ContainerInfo and TaskStatus.

This allows the frameworks to specify an intent to enable
ip-per-container. The IP information is supplied back to the framework
as well as state.json endpoints by including NetworkInfo inside
TaskStatus.

Review: https://reviews.apache.org/r/38367


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/f7b470e4
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/f7b470e4
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/f7b470e4

Branch: refs/heads/master
Commit: f7b470e46a84ddc6d9702c1e76d97073cd6aa48a
Parents: fd0a431
Author: Kapil Arya <ka...@mesosphere.io>
Authored: Wed Sep 16 17:02:06 2015 -0700
Committer: Niklas Q. Nielsen <ni...@qni.dk>
Committed: Wed Sep 16 18:16:08 2015 -0700

----------------------------------------------------------------------
 include/mesos/mesos.proto  | 64 ++++++++++++++++++++++++++++++
 src/common/http.cpp        | 45 +++++++++++++++++++++
 src/common/http.hpp        |  2 +
 src/slave/slave.cpp        | 10 +++++
 src/tests/master_tests.cpp | 88 +++++++++++++++++++++++++++++++++++++++++
 src/tests/slave_tests.cpp  | 88 +++++++++++++++++++++++++++++++++++++++++
 6 files changed, 297 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/f7b470e4/include/mesos/mesos.proto
----------------------------------------------------------------------
diff --git a/include/mesos/mesos.proto b/include/mesos/mesos.proto
index b1deed4..899d52f 100644
--- a/include/mesos/mesos.proto
+++ b/include/mesos/mesos.proto
@@ -1159,6 +1159,10 @@ message TaskStatus {
   // labels should be used to tag TaskStatus message with light-weight
   // meta-data.
   optional Labels labels = 12;
+
+  // Container related information that is resolved dynamically such as
+  // network address.
+  optional ContainerStatus container_status = 13;
 }
 
 
@@ -1352,6 +1356,52 @@ message Volume {
 
 
 /**
+ * Describes a network request by framework as well as network resolution
+ * provided by the the executor or Agent.
+ *
+ * A framework may request the network isolator on the Agent to assign an IP
+ * address to the container being launched. Alternatively, it can provide a
+ * specific IP address to be assigned to the container. The NetworkInfo message
+ * is not interpreted by the Master or Agent and is intended to be use by Agent
+ * modules implementing network isolation. If the modules are missing, the
+ * message is simply ignored. In future, the task launch will fail if there is
+ * no module providing the network isolation capabilities (MESOS-3390).
+ *
+ * An executor, Agent, or an Agent module may append NetworkInfos inside
+ * TaskStatus::container_status to provide information such as the container IP
+ * address and isolation groups.
+ */
+message NetworkInfo {
+  enum Protocol {
+    IPv4 = 1;
+    IPv6 = 2;
+  }
+
+  // Specify IP address requirement. Set protocol to the desired value to
+  // request the network isolator on the Agent to assign an IP address to the
+  // container being launched. If a specific IP address is specified in
+  // ip_address, this field should not be set.
+  optional Protocol protocol = 1;
+
+  // Statically assigned IP provided by the Framework. This IP will be assigned
+  // to the container by the network isolator module on the Agent. This field
+  // should not be used with the protocol field above.
+  // NOTE: It is up to the networking 'provider' (IPAM/Isolator) to interpret
+  // this either as a hint of as a requirement for assigning the IP.
+  optional string ip_address = 2;
+
+  // A group is the name given to a set of logically-related IPs that are
+  // allowed to communicate within themselves. For example, one might want
+  // to create separate groups for isolating dev, testing, qa and prod
+  // deployment environments.
+  repeated string groups = 3;
+
+  // To tag certain metadata to be used by Isolator/IPAM, e.g., rack, etc.
+  optional Labels labels = 4;
+};
+
+
+/**
  * Describes a container configuration and allows extensible
  * configurations for different container implementations.
  */
@@ -1410,6 +1460,20 @@ message ContainerInfo {
   // the type.
   optional DockerInfo docker = 3;
   optional MesosInfo mesos = 5;
+
+  // A list of network requests. A framework can request multiple IP addresses
+  // for the container.
+  repeated NetworkInfo network_infos = 7;
+}
+
+
+/**
+ * Container related information that is resolved during container setup. The
+ * information is sent back to the framework as part of the TaskStatus message.
+ */
+message ContainerStatus {
+  // This field can be reliably used to identify the container IP address.
+  repeated NetworkInfo network_infos = 1;
 }
 
 

http://git-wip-us.apache.org/repos/asf/mesos/blob/f7b470e4/src/common/http.cpp
----------------------------------------------------------------------
diff --git a/src/common/http.cpp b/src/common/http.cpp
index 85fb932..aaef10b 100644
--- a/src/common/http.cpp
+++ b/src/common/http.cpp
@@ -150,6 +150,48 @@ JSON::Array model(const Labels& labels)
 }
 
 
+JSON::Object model(const NetworkInfo& info)
+{
+  JSON::Object object;
+
+  if (info.has_ip_address()) {
+    object.values["ip_address"] = info.ip_address();
+  }
+
+  if (info.groups().size() > 0) {
+    JSON::Array array;
+    array.values.reserve(info.groups().size()); // MESOS-2353.
+    foreach (const string& group, info.groups()) {
+      array.values.push_back(group);
+    }
+    object.values["groups"] = std::move(array);
+  }
+
+  if (info.has_labels()) {
+    object.values["labels"] = std::move(model(info.labels()));
+  }
+
+  return object;
+}
+
+
+JSON::Object model(const ContainerStatus& status)
+{
+  JSON::Object object;
+
+  if (status.network_infos().size() > 0) {
+    JSON::Array array;
+    array.values.reserve(status.network_infos().size()); // MESOS-2353.
+    foreach (const NetworkInfo& info, status.network_infos()) {
+      array.values.push_back(model(info));
+    }
+    object.values["network_infos"] = std::move(array);
+  }
+
+  return object;
+}
+
+
 // Returns a JSON object modeled on a TaskStatus.
 JSON::Object model(const TaskStatus& status)
 {
@@ -159,7 +201,10 @@ JSON::Object model(const TaskStatus& status)
 
   if (status.has_labels()) {
     object.values["labels"] = std::move(model(status.labels()));
+  }
 
+  if (status.has_container_status()) {
+    object.values["container_status"] = model(status.container_status());
   }
 
   return object;

http://git-wip-us.apache.org/repos/asf/mesos/blob/f7b470e4/src/common/http.hpp
----------------------------------------------------------------------
diff --git a/src/common/http.hpp b/src/common/http.hpp
index 1e61888..058baa6 100644
--- a/src/common/http.hpp
+++ b/src/common/http.hpp
@@ -80,6 +80,8 @@ JSON::Object model(const Attributes& attributes);
 JSON::Object model(const CommandInfo& command);
 JSON::Object model(const ExecutorInfo& executorInfo);
 JSON::Array model(const Labels& labels);
+JSON::Object model(const NetworkInfo& info);
+JSON::Object model(const ContainerStatus& status);
 
 // These are the two identical ways to model a task, depending on
 // whether you have a 'Task' or a 'TaskInfo' available.

http://git-wip-us.apache.org/repos/asf/mesos/blob/f7b470e4/src/slave/slave.cpp
----------------------------------------------------------------------
diff --git a/src/slave/slave.cpp b/src/slave/slave.cpp
index 021786b..43fc737 100644
--- a/src/slave/slave.cpp
+++ b/src/slave/slave.cpp
@@ -2765,6 +2765,16 @@ void Slave::statusUpdate(StatusUpdate update, const UPID& pid)
             update.framework_id(), update.status()));
   }
 
+  // Fill in the container IP address with the IP from the agent PID, if not
+  // already filled in.
+  // TODO(karya): Fill in the IP address by looking up the executor PID.
+  ContainerStatus* containerStatus =
+    update.mutable_status()->mutable_container_status();
+  if (containerStatus->network_infos().size() == 0) {
+    NetworkInfo* networkInfo = containerStatus->add_network_infos();
+    networkInfo->set_ip_address(stringify(self().address.ip));
+  }
+
   TaskStatus status = update.status();
 
   Executor* executor = framework->getExecutor(status.task_id());

http://git-wip-us.apache.org/repos/asf/mesos/blob/f7b470e4/src/tests/master_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/master_tests.cpp b/src/tests/master_tests.cpp
index dd65fcc..06d74c3 100644
--- a/src/tests/master_tests.cpp
+++ b/src/tests/master_tests.cpp
@@ -3244,6 +3244,94 @@ TEST_F(MasterTest, TaskStatusLabels)
 }
 
 
+// This test verifies that TaskStatus::container_status is exposed over the
+// master state endpoint.
+TEST_F(MasterTest, TaskStatusContainerStatus)
+{
+  Try<PID<Master>> master = StartMaster();
+  ASSERT_SOME(master);
+
+  MockExecutor exec(DEFAULT_EXECUTOR_ID);
+
+  Try<PID<Slave>> slave = StartSlave(&exec);
+  ASSERT_SOME(slave);
+
+  MockScheduler sched;
+  MesosSchedulerDriver driver(
+      &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
+
+  EXPECT_CALL(sched, registered(&driver, _, _))
+    .Times(1);
+
+  Future<vector<Offer>> offers;
+  EXPECT_CALL(sched, resourceOffers(&driver, _))
+    .WillOnce(FutureArg<1>(&offers))
+    .WillRepeatedly(Return()); // Ignore subsequent offers.
+
+  driver.start();
+
+  AWAIT_READY(offers);
+  EXPECT_NE(0u, offers.get().size());
+
+  TaskInfo task = createTask(offers.get()[0], "sleep 100", DEFAULT_EXECUTOR_ID);
+
+  ExecutorDriver* execDriver;
+  EXPECT_CALL(exec, registered(_, _, _, _))
+    .WillOnce(SaveArg<0>(&execDriver));
+
+  EXPECT_CALL(exec, launchTask(_, _))
+    .WillOnce(SendStatusUpdateFromTask(TASK_RUNNING));
+
+  Future<TaskStatus> status;
+  EXPECT_CALL(sched, statusUpdate(&driver, _))
+    .WillOnce(FutureArg<1>(&status));
+
+  driver.launchTasks(offers.get()[0].id(), {task});
+
+  AWAIT_READY(status);
+
+  const string slaveIPAddress = stringify(slave.get().address.ip);
+
+  // Validate that the Slave has passed in its IP address in
+  // TaskStatus.container_status.network_infos[0].ip_address.
+  EXPECT_TRUE(status.get().has_container_status());
+  EXPECT_EQ(1, status.get().container_status().network_infos().size());
+  EXPECT_TRUE(
+      status.get().container_status().network_infos(0).has_ip_address());
+  EXPECT_EQ(
+      slaveIPAddress,
+      status.get().container_status().network_infos(0).ip_address());
+
+  // Now do the same validation with state endpoint.
+  Future<process::http::Response> response =
+    process::http::get(master.get(), "state.json");
+  AWAIT_READY(response);
+
+  EXPECT_SOME_EQ(
+      "application/json",
+      response.get().headers.get("Content-Type"));
+
+  Try<JSON::Object> parse = JSON::parse<JSON::Object>(response.get().body);
+  ASSERT_SOME(parse);
+
+  // Validate that the IP address passed in by the Slave is available at the
+  // state endpoint.
+  ASSERT_SOME_EQ(
+      slaveIPAddress,
+      parse.get().find<JSON::String>(
+          "frameworks[0].tasks[0].statuses[0]"
+          ".container_status.network_infos[0].ip_address"));
+
+  EXPECT_CALL(exec, shutdown(_))
+    .Times(AtMost(1));
+
+  driver.stop();
+  driver.join();
+
+  Shutdown(); // Must shutdown before 'containerizer' gets deallocated.
+}
+
+
 // This tests the 'active' field in slave entries from the master's
 // state endpoint. We first verify an active slave, deactivate it
 // and verify that the 'active' field is false.

http://git-wip-us.apache.org/repos/asf/mesos/blob/f7b470e4/src/tests/slave_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/slave_tests.cpp b/src/tests/slave_tests.cpp
index 4a1a586..d158ccc 100644
--- a/src/tests/slave_tests.cpp
+++ b/src/tests/slave_tests.cpp
@@ -2221,6 +2221,94 @@ TEST_F(SlaveTest, TaskStatusLabels)
 }
 
 
+// This test verifies that TaskStatus::container_status an is exposed over
+// the slave state endpoint.
+TEST_F(SlaveTest, TaskStatusContainerStatus)
+{
+  Try<PID<Master>> master = StartMaster();
+  ASSERT_SOME(master);
+
+  MockExecutor exec(DEFAULT_EXECUTOR_ID);
+
+  Try<PID<Slave>> slave = StartSlave(&exec);
+  ASSERT_SOME(slave);
+
+  MockScheduler sched;
+  MesosSchedulerDriver driver(
+      &sched, DEFAULT_FRAMEWORK_INFO, master.get(), DEFAULT_CREDENTIAL);
+
+  EXPECT_CALL(sched, registered(&driver, _, _))
+    .Times(1);
+
+  Future<vector<Offer>> offers;
+  EXPECT_CALL(sched, resourceOffers(&driver, _))
+    .WillOnce(FutureArg<1>(&offers))
+    .WillRepeatedly(Return()); // Ignore subsequent offers.
+
+  driver.start();
+
+  AWAIT_READY(offers);
+  EXPECT_NE(0u, offers.get().size());
+
+  TaskInfo task = createTask(offers.get()[0], "sleep 100", DEFAULT_EXECUTOR_ID);
+
+  ExecutorDriver* execDriver;
+  EXPECT_CALL(exec, registered(_, _, _, _))
+    .WillOnce(SaveArg<0>(&execDriver));
+
+  EXPECT_CALL(exec, launchTask(_, _))
+    .WillOnce(SendStatusUpdateFromTask(TASK_RUNNING));
+
+  Future<TaskStatus> status;
+  EXPECT_CALL(sched, statusUpdate(&driver, _))
+    .WillOnce(FutureArg<1>(&status));
+
+  driver.launchTasks(offers.get()[0].id(), {task});
+
+  AWAIT_READY(status);
+
+  const string slaveIPAddress = stringify(slave.get().address.ip);
+
+  // Validate that the Slave has passed in its IP address in
+  // TaskStatus.container_status.network_infos[0].ip_address.
+  EXPECT_TRUE(status.get().has_container_status());
+  EXPECT_EQ(1, status.get().container_status().network_infos().size());
+  EXPECT_TRUE(
+      status.get().container_status().network_infos(0).has_ip_address());
+  EXPECT_EQ(
+      slaveIPAddress,
+      status.get().container_status().network_infos(0).ip_address());
+
+  // Now do the same validation with state endpoint.
+  Future<process::http::Response> response =
+    process::http::get(slave.get(), "state.json");
+  AWAIT_READY(response);
+
+  EXPECT_SOME_EQ(
+      "application/json",
+      response.get().headers.get("Content-Type"));
+
+  Try<JSON::Object> parse = JSON::parse<JSON::Object>(response.get().body);
+  ASSERT_SOME(parse);
+
+  // Validate that the IP address passed in by the Slave is available at the
+  // state endpoint.
+  ASSERT_SOME_EQ(
+      slaveIPAddress,
+      parse.get().find<JSON::String>(
+          "frameworks[0].executors[0].tasks[0].statuses[0]"
+          ".container_status.network_infos[0].ip_address"));
+
+  EXPECT_CALL(exec, shutdown(_))
+    .Times(AtMost(1));
+
+  driver.stop();
+  driver.join();
+
+  Shutdown(); // Must shutdown before 'containerizer' gets deallocated.
+}
+
+
 // Test that we can set the executors environment variables and it
 // won't inhert the slaves.
 TEST_F(SlaveTest, ExecutorEnvironmentVariables)