[netemul] Environment configuration model
Classes for environment configuration parsing (placed in CMXMetadata ->
facets).
TEST: added unittests for model parsing
Change-Id: I5ecbf9edaa9df5b59fb765bd31813adb5767291d
diff --git a/bin/netemul_runner/model/BUILD.gn b/bin/netemul_runner/model/BUILD.gn
new file mode 100644
index 0000000..ec4b26a
--- /dev/null
+++ b/bin/netemul_runner/model/BUILD.gn
@@ -0,0 +1,44 @@
+# Copyright 2018 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+source_set("model") {
+ sources = [
+ "config.cc",
+ "config.h",
+ "endpoint.cc",
+ "endpoint.h",
+ "environment.cc",
+ "environment.h",
+ "launch_app.cc",
+ "launch_app.h",
+ "launch_service.cc",
+ "launch_service.h",
+ "network.cc",
+ "network.h",
+ ]
+
+ deps = [
+ "//garnet/public/lib/fsl",
+ "//garnet/public/lib/fxl",
+ "//garnet/public/lib/json:json",
+ "//garnet/public/lib/pkg_url",
+ "//third_party/rapidjson",
+ ]
+}
+
+executable("model_unittest") {
+ testonly = true
+ sources = [
+ "model_unittest.cc",
+ ]
+
+ deps = [
+ ":model",
+ "//garnet/public/lib/fxl/test:gtest_main",
+ "//garnet/public/lib/gtest",
+ "//garnet/public/lib/json",
+ "//garnet/public/lib/pkg_url",
+ "//third_party/rapidjson",
+ ]
+}
diff --git a/bin/netemul_runner/model/config.cc b/bin/netemul_runner/model/config.cc
new file mode 100644
index 0000000..5d634eb
--- /dev/null
+++ b/bin/netemul_runner/model/config.cc
@@ -0,0 +1,63 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "config.h"
+
+namespace netemul {
+namespace config {
+
+static const char* kNetworks = "networks";
+static const char* kEnvironment = "environment";
+
+const char Config::Facet[] = "fuchsia.netemul";
+
+bool Config::ParseFromJSON(const rapidjson::Value& value,
+ json::JSONParser* json_parser) {
+ // null value keeps config as it is
+ if (value.IsNull()) {
+ return true;
+ }
+
+ if (!value.IsObject()) {
+ json_parser->ReportError("fuchsia.netemul object must be an Object");
+ return false;
+ }
+
+ auto nets_value = value.FindMember(kNetworks);
+ if (nets_value != value.MemberEnd()) {
+ if (!nets_value->value.IsArray()) {
+ json_parser->ReportError("\"networks\" property must be an Array");
+ return false;
+ }
+ const auto& nets = nets_value->value.GetArray();
+ for (auto n = nets.Begin(); n != nets.End(); n++) {
+ auto& net = networks_.emplace_back();
+ if (!net.ParseFromJSON(*n, json_parser)) {
+ return false;
+ }
+ }
+ }
+
+ auto env_value = value.FindMember(kEnvironment);
+ if (env_value == value.MemberEnd()) {
+ // parse from empty object if not present
+ if (!environment_.ParseFromJSON(rapidjson::Value(rapidjson::kObjectType),
+ json_parser)) {
+ return false;
+ }
+ } else {
+ if (!environment_.ParseFromJSON(env_value->value, json_parser)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+const std::vector<Network>& Config::networks() const { return networks_; }
+
+const Environment& Config::environment() const { return environment_; }
+
+} // namespace config
+} // namespace netemul
\ No newline at end of file
diff --git a/bin/netemul_runner/model/config.h b/bin/netemul_runner/model/config.h
new file mode 100644
index 0000000..71ec3eb
--- /dev/null
+++ b/bin/netemul_runner/model/config.h
@@ -0,0 +1,39 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef GARNET_BIN_NETEMUL_RUNNER_MODEL_CONFIG_H_
+#define GARNET_BIN_NETEMUL_RUNNER_MODEL_CONFIG_H_
+
+#include <vector>
+#include "environment.h"
+#include "lib/fxl/macros.h"
+#include "lib/json/json_parser.h"
+#include "network.h"
+
+namespace netemul {
+namespace config {
+
+class Config {
+ public:
+ Config() = default;
+ Config(Config&& other) = default;
+
+ static const char Facet[];
+ bool ParseFromJSON(const rapidjson::Value& value,
+ json::JSONParser* json_parser);
+
+ const std::vector<Network>& networks() const;
+
+ const Environment& environment() const;
+
+ private:
+ std::vector<Network> networks_;
+ Environment environment_;
+
+ FXL_DISALLOW_COPY_AND_ASSIGN(Config);
+};
+
+} // namespace config
+} // namespace netemul
+#endif // GARNET_BIN_NETEMUL_RUNNER_MODEL_CONFIG_H_
diff --git a/bin/netemul_runner/model/endpoint.cc b/bin/netemul_runner/model/endpoint.cc
new file mode 100644
index 0000000..5f9922b
--- /dev/null
+++ b/bin/netemul_runner/model/endpoint.cc
@@ -0,0 +1,93 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "endpoint.h"
+#include <cstdio>
+#include <memory>
+
+namespace netemul {
+namespace config {
+
+static const char* kName = "name";
+static const char* kMtu = "mtu";
+static const char* kMac = "mac";
+static const char* kUp = "up";
+static const bool kDefaultUp = true;
+static const uint16_t kDefaultMtu = 1500;
+
+bool Endpoint::ParseFromJSON(const rapidjson::Value& value,
+ json::JSONParser* parser) {
+ if (!value.IsObject()) {
+ parser->ReportError("endpoint must be object type");
+ return false;
+ }
+
+ auto name = value.FindMember(kName);
+ if (name == value.MemberEnd()) {
+ parser->ReportError("endpoint must have name property");
+ return false;
+ } else if ((!name->value.IsString()) || name->value.GetStringLength() == 0) {
+ parser->ReportError("endpoint name must be a non-empty string");
+ return false;
+ } else {
+ name_ = name->value.GetString();
+ }
+
+ auto mtu = value.FindMember(kMtu);
+ if (mtu == value.MemberEnd()) {
+ mtu_ = kDefaultMtu;
+ } else if (!mtu->value.IsNumber()) {
+ parser->ReportError("endpoint mtu must be number");
+ return false;
+ } else {
+ auto v = static_cast<uint16_t>(mtu->value.GetUint());
+ if (v == 0) {
+ parser->ReportError(
+ "endpoint with zero mtu is invalid, omit to use default");
+ return false;
+ }
+ mtu_ = v;
+ }
+
+ auto mac = value.FindMember(kMac);
+ if (mac == value.MemberEnd()) {
+ mac_ = nullptr;
+ } else if (!mac->value.IsString()) {
+ parser->ReportError("endpoint mac must be string");
+ return false;
+ } else {
+ auto macval = std::make_unique<Mac>();
+ if (std::sscanf(mac->value.GetString(),
+ "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", macval->d,
+ macval->d + 1, macval->d + 2, macval->d + 3, macval->d + 4,
+ macval->d + 5) != 6) {
+ parser->ReportError("Can't parse supplied mac address");
+ return false;
+ }
+ mac_ = std::move(macval);
+ }
+
+ auto up = value.FindMember(kUp);
+ if (up == value.MemberEnd()) {
+ up_ = kDefaultUp;
+ } else if (!up->value.IsBool()) {
+ parser->ReportError("endpoint up must be bool");
+ return false;
+ } else {
+ up_ = up->value.GetBool();
+ }
+
+ return true;
+}
+
+const std::string& Endpoint::name() const { return name_; }
+
+const std::unique_ptr<Mac>& Endpoint::mac() const { return mac_; }
+
+uint16_t Endpoint::mtu() const { return mtu_; }
+
+bool Endpoint::up() const { return up_; }
+
+} // namespace config
+} // namespace netemul
\ No newline at end of file
diff --git a/bin/netemul_runner/model/endpoint.h b/bin/netemul_runner/model/endpoint.h
new file mode 100644
index 0000000..5d61f62
--- /dev/null
+++ b/bin/netemul_runner/model/endpoint.h
@@ -0,0 +1,41 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef GARNET_BIN_NETEMUL_RUNNER_MODEL_ENDPOINT_H_
+#define GARNET_BIN_NETEMUL_RUNNER_MODEL_ENDPOINT_H_
+
+#include "lib/fxl/macros.h"
+#include "lib/json/json_parser.h"
+
+namespace netemul {
+namespace config {
+
+struct Mac {
+ uint8_t d[6];
+};
+
+class Endpoint {
+ public:
+ Endpoint() = default;
+ Endpoint(Endpoint&& other) = default;
+
+ bool ParseFromJSON(const rapidjson::Value& value, json::JSONParser* parser);
+
+ const std::string& name() const;
+ const std::unique_ptr<Mac>& mac() const;
+ uint16_t mtu() const;
+ bool up() const;
+
+ private:
+ std::string name_;
+ std::unique_ptr<Mac> mac_;
+ uint16_t mtu_;
+ bool up_;
+
+ FXL_DISALLOW_COPY_AND_ASSIGN(Endpoint);
+};
+
+} // namespace config
+} // namespace netemul
+#endif // GARNET_BIN_NETEMUL_RUNNER_MODEL_ENDPOINT_H_
diff --git a/bin/netemul_runner/model/environment.cc b/bin/netemul_runner/model/environment.cc
new file mode 100644
index 0000000..3bc4a34
--- /dev/null
+++ b/bin/netemul_runner/model/environment.cc
@@ -0,0 +1,172 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "environment.h"
+
+namespace netemul {
+namespace config {
+
+static const char* kDefaultName = "test-env";
+static const char* kName = "name";
+static const char* kServices = "services";
+static const char* kDevices = "devices";
+static const char* kChildren = "children";
+static const char* kTest = "test";
+static const char* kInheritServices = "inherit_services";
+static const char* kApps = "apps";
+static const char* kSetup = "setup";
+static const bool kDefaultInheritServices = true;
+
+bool Environment::ParseFromJSON(const rapidjson::Value& value,
+ json::JSONParser* parser) {
+ if (!value.IsObject()) {
+ parser->ReportError("environment must be object type");
+ return false;
+ }
+
+ auto name = value.FindMember(kName);
+ if (name == value.MemberEnd()) {
+ name_ = kDefaultName;
+ } else if (!name->value.IsString()) {
+ parser->ReportError("environment name must be string value");
+ return false;
+ } else {
+ name_ = name->value.GetString();
+ }
+
+ auto inherit = value.FindMember(kInheritServices);
+ if (inherit == value.MemberEnd()) {
+ inherit_services_ = kDefaultInheritServices;
+ } else if (!inherit->value.IsBool()) {
+ parser->ReportError("inherit_services must be boolean value");
+ return false;
+ } else {
+ inherit_services_ = inherit->value.GetBool();
+ }
+
+ auto devices = value.FindMember(kDevices);
+ if (devices == value.MemberEnd()) {
+ devices_.clear();
+ } else if (!devices->value.IsArray()) {
+ parser->ReportError("environment devices must be array of strings");
+ return false;
+ } else {
+ auto devs = devices->value.GetArray();
+ devices_.clear();
+ for (auto d = devs.Begin(); d != devs.End(); d++) {
+ if (!d->IsString()) {
+ parser->ReportError("environment devices must be array of strings");
+ return false;
+ }
+ devices_.emplace_back(d->GetString());
+ }
+ }
+
+ auto services = value.FindMember(kServices);
+ if (services == value.MemberEnd()) {
+ services_.clear();
+ } else if (!services->value.IsObject()) {
+ parser->ReportError("environment services must be object");
+ return false;
+ } else {
+ for (auto s = services->value.MemberBegin();
+ s != services->value.MemberEnd(); s++) {
+ auto& ns = services_.emplace_back(s->name.GetString());
+ if (!ns.ParseFromJSON(s->value, parser)) {
+ return false;
+ }
+ }
+ }
+
+ auto test = value.FindMember(kTest);
+ if (test == value.MemberEnd()) {
+ test_.clear();
+ } else if (!test->value.IsArray()) {
+ parser->ReportError("environment tests must be array of objects");
+ return false;
+ } else {
+ auto test_arr = test->value.GetArray();
+ for (auto t = test_arr.Begin(); t != test_arr.End(); t++) {
+ auto& nt = test_.emplace_back();
+ if (!nt.ParseFromJSON(*t, parser)) {
+ return false;
+ }
+ }
+ }
+
+ auto children = value.FindMember(kChildren);
+ if (children == value.MemberEnd()) {
+ children_.clear();
+ } else if (!children->value.IsArray()) {
+ parser->ReportError("environment children must be array of objects");
+ return false;
+ } else {
+ auto ch_arr = children->value.GetArray();
+ for (auto c = ch_arr.Begin(); c != ch_arr.End(); c++) {
+ auto& nc = children_.emplace_back();
+ if (!nc.ParseFromJSON(*c, parser)) {
+ return false;
+ }
+ }
+ }
+
+ auto apps = value.FindMember(kApps);
+ if (apps == value.MemberEnd()) {
+ apps_.clear();
+ } else if (!apps->value.IsArray()) {
+ parser->ReportError("environment apps must be array");
+ return false;
+ } else {
+ auto app_arr = apps->value.GetArray();
+ for (auto a = app_arr.Begin(); a != app_arr.End(); a++) {
+ auto& na = apps_.emplace_back();
+ if (!na.ParseFromJSON(*a, parser)) {
+ return false;
+ }
+ }
+ }
+
+ auto setup = value.FindMember(kSetup);
+ if (setup == value.MemberEnd()) {
+ setup_.clear();
+ } else if (!setup->value.IsArray()) {
+ parser->ReportError("environment setup must be array");
+ return false;
+ } else {
+ auto setup_arr = setup->value.GetArray();
+ for (auto s = setup_arr.Begin(); s != setup_arr.End(); s++) {
+ auto& ns = setup_.emplace_back();
+ if (!ns.ParseFromJSON(*s, parser)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+const std::string& Environment::name() const { return name_; }
+
+const std::vector<Environment>& Environment::children() const {
+ return children_;
+}
+
+const std::vector<std::string>& Environment::devices() const {
+ return devices_;
+}
+
+const std::vector<LaunchService>& Environment::services() const {
+ return services_;
+}
+
+const std::vector<LaunchApp>& Environment::test() const { return test_; }
+
+bool Environment::inherit_services() const { return inherit_services_; }
+
+const std::vector<LaunchApp>& Environment::apps() const { return apps_; }
+
+const std::vector<LaunchApp>& Environment::setup() const { return setup_; }
+
+} // namespace config
+} // namespace netemul
\ No newline at end of file
diff --git a/bin/netemul_runner/model/environment.h b/bin/netemul_runner/model/environment.h
new file mode 100644
index 0000000..bd827d8
--- /dev/null
+++ b/bin/netemul_runner/model/environment.h
@@ -0,0 +1,47 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef GARNET_BIN_NETEMUL_RUNNER_MODEL_ENVIRONMENT_H_
+#define GARNET_BIN_NETEMUL_RUNNER_MODEL_ENVIRONMENT_H_
+
+#include "launch_app.h"
+#include "launch_service.h"
+#include "lib/fxl/macros.h"
+#include "lib/json/json_parser.h"
+
+namespace netemul {
+namespace config {
+
+class Environment {
+ public:
+ Environment() = default;
+ Environment(Environment&& other) = default;
+
+ bool ParseFromJSON(const rapidjson::Value& value, json::JSONParser* parser);
+
+ const std::string& name() const;
+ const std::vector<Environment>& children() const;
+ const std::vector<std::string>& devices() const;
+ const std::vector<LaunchService>& services() const;
+ const std::vector<LaunchApp>& test() const;
+ const std::vector<LaunchApp>& apps() const;
+ const std::vector<LaunchApp>& setup() const;
+ bool inherit_services() const;
+
+ private:
+ std::string name_;
+ std::vector<Environment> children_;
+ std::vector<std::string> devices_;
+ std::vector<LaunchService> services_;
+ std::vector<LaunchApp> test_;
+ std::vector<LaunchApp> apps_;
+ std::vector<LaunchApp> setup_;
+ bool inherit_services_{};
+
+ FXL_DISALLOW_COPY_AND_ASSIGN(Environment);
+};
+
+} // namespace config
+} // namespace netemul
+#endif // GARNET_BIN_NETEMUL_RUNNER_MODEL_ENVIRONMENT_H_
diff --git a/bin/netemul_runner/model/launch_app.cc b/bin/netemul_runner/model/launch_app.cc
new file mode 100644
index 0000000..2e691fb
--- /dev/null
+++ b/bin/netemul_runner/model/launch_app.cc
@@ -0,0 +1,91 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "launch_app.h"
+#include "lib/pkg_url/fuchsia_pkg_url.h"
+
+namespace netemul {
+namespace config {
+
+static const char* kUrl = "url";
+static const char* kArguments = "arguments";
+static const char* kEmptyUrl = "";
+
+bool LaunchApp::ParseFromJSON(const rapidjson::Value& value,
+ json::JSONParser* parser) {
+ if (value.IsString()) {
+ // value is a string, parse as url only
+ auto url = value.GetString();
+ if (value.GetStringLength() != 0) {
+ component::FuchsiaPkgUrl pkgUrl;
+ if (!pkgUrl.Parse(url)) {
+ parser->ReportError(
+ "launch options url is not a valid fuchsia package url");
+ return false;
+ }
+ }
+ url_ = url;
+ arguments_.clear();
+
+ } else if (value.IsObject()) {
+ auto url = value.FindMember(kUrl);
+ if (url == value.MemberEnd()) {
+ url_ = kEmptyUrl;
+ } else if (!url->value.IsString()) {
+ parser->ReportError("launch options url must be string");
+ return false;
+ } else {
+ auto v = url->value.GetString();
+ if (url->value.GetStringLength() != 0) {
+ component::FuchsiaPkgUrl pkgUrl;
+ if (!pkgUrl.Parse(v)) {
+ parser->ReportError(
+ "launch options url is not a valid fuchsia package url");
+ return false;
+ }
+ }
+ url_ = v;
+ }
+
+ auto arguments = value.FindMember(kArguments);
+ if (arguments == value.MemberEnd()) {
+ arguments_.clear();
+ } else if (!arguments->value.IsArray()) {
+ parser->ReportError("launch options arguments must be array of string");
+ return false;
+ } else {
+ auto arg_arr = arguments->value.GetArray();
+ for (auto a = arg_arr.Begin(); a != arg_arr.End(); a++) {
+ if (!a->IsString()) {
+ parser->ReportError(
+ "launch options arguments element must be string");
+ return false;
+ }
+ arguments_.emplace_back(a->GetString());
+ }
+ }
+ } else {
+ parser->ReportError("launch options must be of type object or string");
+ return false;
+ }
+
+ return true;
+}
+
+const std::string& LaunchApp::url() const { return url_; }
+
+const std::vector<std::string>& LaunchApp::arguments() const {
+ return arguments_;
+}
+
+const std::string& LaunchApp::GetUrlOrDefault(const std::string& def) const {
+ if (url_.empty()) {
+ return def;
+ } else {
+ return url_;
+ }
+}
+
+} // namespace config
+} // namespace netemul
\ No newline at end of file
diff --git a/bin/netemul_runner/model/launch_app.h b/bin/netemul_runner/model/launch_app.h
new file mode 100644
index 0000000..e6fbdc1
--- /dev/null
+++ b/bin/netemul_runner/model/launch_app.h
@@ -0,0 +1,35 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef GARNET_BIN_NETEMUL_RUNNER_MODEL_LAUNCH_APP_H_
+#define GARNET_BIN_NETEMUL_RUNNER_MODEL_LAUNCH_APP_H_
+
+#include "lib/fxl/macros.h"
+#include "lib/json/json_parser.h"
+
+namespace netemul {
+namespace config {
+
+class LaunchApp {
+ public:
+ LaunchApp() = default;
+ LaunchApp(LaunchApp&& other) = default;
+
+ bool ParseFromJSON(const rapidjson::Value& value, json::JSONParser* parser);
+
+ const std::string& GetUrlOrDefault(const std::string& def) const;
+
+ const std::string& url() const;
+ const std::vector<std::string>& arguments() const;
+
+ private:
+ std::string url_;
+ std::vector<std::string> arguments_;
+
+ FXL_DISALLOW_COPY_AND_ASSIGN(LaunchApp);
+};
+
+} // namespace config
+} // namespace netemul
+#endif // GARNET_BIN_NETEMUL_RUNNER_MODEL_LAUNCH_APP_H_
diff --git a/bin/netemul_runner/model/launch_service.cc b/bin/netemul_runner/model/launch_service.cc
new file mode 100644
index 0000000..c5833d4
--- /dev/null
+++ b/bin/netemul_runner/model/launch_service.cc
@@ -0,0 +1,23 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "launch_service.h"
+#include "lib/pkg_url/fuchsia_pkg_url.h"
+
+namespace netemul {
+namespace config {
+
+LaunchService::LaunchService(std::string name) : name_(std::move(name)) {}
+
+bool LaunchService::ParseFromJSON(const rapidjson::Value& value,
+ json::JSONParser* parser) {
+ return launch_.ParseFromJSON(value, parser);
+}
+
+const std::string& LaunchService::name() const { return name_; }
+
+const LaunchApp& LaunchService::launch() const { return launch_; }
+
+} // namespace config
+} // namespace netemul
\ No newline at end of file
diff --git a/bin/netemul_runner/model/launch_service.h b/bin/netemul_runner/model/launch_service.h
new file mode 100644
index 0000000..a5fd4ed
--- /dev/null
+++ b/bin/netemul_runner/model/launch_service.h
@@ -0,0 +1,35 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef GARNET_BIN_NETEMUL_RUNNER_MODEL_LAUNCH_SERVICE_H_
+#define GARNET_BIN_NETEMUL_RUNNER_MODEL_LAUNCH_SERVICE_H_
+
+#include "launch_app.h"
+#include "lib/fxl/macros.h"
+#include "lib/json/json_parser.h"
+
+namespace netemul {
+namespace config {
+
+class LaunchService {
+ public:
+ explicit LaunchService(std::string name);
+ LaunchService(LaunchService&& other) = default;
+
+ bool ParseFromJSON(const rapidjson::Value& value, json::JSONParser* parser);
+
+ const std::string& name() const;
+ const LaunchApp& launch() const;
+
+ private:
+ std::string name_;
+ LaunchApp launch_;
+
+ FXL_DISALLOW_COPY_AND_ASSIGN(LaunchService);
+};
+
+} // namespace config
+} // namespace netemul
+
+#endif // GARNET_BIN_NETEMUL_RUNNER_MODEL_LAUNCH_SERVICE_H_
diff --git a/bin/netemul_runner/model/model_unittest.cc b/bin/netemul_runner/model/model_unittest.cc
new file mode 100644
index 0000000..d96a2a9
--- /dev/null
+++ b/bin/netemul_runner/model/model_unittest.cc
@@ -0,0 +1,276 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <iostream>
+#include "config.h"
+#include "gtest/gtest.h"
+
+namespace netemul {
+namespace config {
+namespace testing {
+
+class ModelTest : public ::testing::Test {
+ protected:
+ void ExpectFailedParse(const char* json, const char* msg) {
+ Config config;
+ json::JSONParser parser;
+ auto doc = parser.ParseFromString(
+ json, ::testing::UnitTest::GetInstance()->current_test_info()->name());
+
+ ASSERT_FALSE(parser.HasError());
+
+ EXPECT_FALSE(config.ParseFromJSON(doc, &parser)) << msg;
+ ASSERT_TRUE(parser.HasError());
+ std::cout << "Parse failed as expected: " << parser.error_str()
+ << std::endl;
+ }
+};
+
+TEST_F(ModelTest, ParseTest) {
+ const char* json =
+ R"(
+ {
+ "environment": {
+ "children": [
+ {
+ "name": "child-1",
+ "test": [
+ {
+ "arguments": [
+ "-t",
+ "1",
+ "-n",
+ "child-1-url"
+ ],
+ "url": "fuchsia-pkg://fuchsia.com/netemul_sandbox_test#meta/env_build_run.cmx"
+ },
+ {
+ "arguments": [
+ "-t",
+ "1",
+ "-n",
+ "child-1-no-url"
+ ]
+ }
+ ]
+ },
+ {
+ "inherit_services": false,
+ "name": "child-2",
+ "test": [ "fuchsia-pkg://fuchsia.com/some_test#meta/some_test.cmx" ],
+ "apps" : [ "fuchsia-pkg://fuchsia.com/some_app#meta/some_app.cmx" ]
+ }
+ ],
+ "devices": [
+ "ep0",
+ "ep1"
+ ],
+ "name": "root",
+ "setup": [
+ {
+ "url": "fuchsia-pkg://fuchsia.com/some_setup#meta/some_setup.cmx",
+ "arguments": ["-arg"]
+ }
+ ],
+ "services": {
+ "fuchsia.net.SocketProvider": "fuchsia-pkg://fuchsia.com/netstack#meta/netstack.cmx",
+ "fuchsia.netstack.Netstack": "fuchsia-pkg://fuchsia.com/netstack#meta/netstack.cmx",
+ "fuchsia.some.Service" : {
+ "url" : "fuchsia-pkg://fuchsia.com/some_service#meta/some_service.cmx",
+ "arguments" : ["-a1", "-a2"]
+ }
+ }
+ },
+ "networks": [
+ {
+ "endpoints": [
+ {
+ "mac": "70:00:01:02:03:04",
+ "mtu": 1000,
+ "name": "ep0",
+ "up": false
+ },
+ {
+ "name": "ep1"
+ }
+ ],
+ "name": "test-net"
+ }
+ ]
+ }
+)";
+
+ config::Config config;
+ json::JSONParser parser;
+ auto doc = parser.ParseFromString(json, "ParseTest");
+ EXPECT_FALSE(parser.HasError()) << "Parse error: " << parser.error_str();
+
+ EXPECT_TRUE(config.ParseFromJSON(doc, &parser))
+ << "Parse error: " << parser.error_str();
+
+ // sanity check the objects:
+ auto& root_env = config.environment();
+ EXPECT_EQ(root_env.name(), "root");
+ EXPECT_EQ(root_env.inherit_services(), true);
+ EXPECT_EQ(root_env.children().size(), 2ul);
+ EXPECT_EQ(root_env.devices().size(), 2ul);
+ EXPECT_EQ(root_env.services().size(), 3ul);
+ EXPECT_TRUE(root_env.test().empty());
+ EXPECT_TRUE(root_env.apps().empty());
+ EXPECT_EQ(root_env.setup().size(), 1ul);
+
+ // check the devices
+ EXPECT_EQ(root_env.devices()[0], "ep0");
+ EXPECT_EQ(root_env.devices()[1], "ep1");
+
+ // check the services
+ EXPECT_EQ(root_env.services()[0].name(), "fuchsia.net.SocketProvider");
+ EXPECT_EQ(root_env.services()[0].launch().url(),
+ "fuchsia-pkg://fuchsia.com/netstack#meta/netstack.cmx");
+ EXPECT_TRUE(root_env.services()[0].launch().arguments().empty());
+ EXPECT_EQ(root_env.services()[1].name(), "fuchsia.netstack.Netstack");
+ EXPECT_EQ(root_env.services()[1].launch().url(),
+ "fuchsia-pkg://fuchsia.com/netstack#meta/netstack.cmx");
+ EXPECT_TRUE(root_env.services()[1].launch().arguments().empty());
+ EXPECT_EQ(root_env.services()[2].name(), "fuchsia.some.Service");
+ EXPECT_EQ(root_env.services()[2].launch().url(),
+ "fuchsia-pkg://fuchsia.com/some_service#meta/some_service.cmx");
+ EXPECT_EQ(root_env.services()[2].launch().arguments().size(), 2ul);
+
+ // check the child environments
+ auto& c0 = root_env.children()[0];
+ EXPECT_EQ(c0.name(), "child-1");
+ EXPECT_EQ(c0.inherit_services(), true);
+ EXPECT_TRUE(c0.children().empty());
+ EXPECT_TRUE(c0.devices().empty());
+ EXPECT_TRUE(c0.services().empty());
+ EXPECT_EQ(c0.test().size(), 2ul);
+ EXPECT_TRUE(c0.apps().empty());
+ EXPECT_TRUE(c0.setup().empty());
+ auto& c1 = root_env.children()[1];
+ EXPECT_EQ(c1.name(), "child-2");
+ EXPECT_EQ(c1.inherit_services(), false);
+ EXPECT_TRUE(c1.children().empty());
+ EXPECT_TRUE(c1.devices().empty());
+ EXPECT_TRUE(c1.services().empty());
+ EXPECT_TRUE(c1.setup().empty());
+ EXPECT_EQ(c1.test().size(), 1ul);
+ EXPECT_EQ(c1.apps().size(), 1ul);
+
+ // check test structures:
+ auto& t0 = c0.test()[0];
+ auto& t1 = c0.test()[1];
+ auto& t2 = c1.test()[0];
+ EXPECT_EQ(
+ t0.url(),
+ "fuchsia-pkg://fuchsia.com/netemul_sandbox_test#meta/env_build_run.cmx");
+ EXPECT_EQ(t0.arguments().size(), 4ul);
+
+ EXPECT_TRUE(t1.url().empty());
+ EXPECT_EQ(t1.arguments().size(), 4ul);
+ EXPECT_EQ(t2.url(), "fuchsia-pkg://fuchsia.com/some_test#meta/some_test.cmx");
+ EXPECT_TRUE(t2.arguments().empty());
+
+ // check apps:
+ auto& app0 = c1.apps()[0];
+ EXPECT_EQ(app0.url(), "fuchsia-pkg://fuchsia.com/some_app#meta/some_app.cmx");
+ EXPECT_TRUE(app0.arguments().empty());
+
+ // check setup:
+ auto& setup = root_env.setup()[0];
+ EXPECT_EQ(setup.url(),
+ "fuchsia-pkg://fuchsia.com/some_setup#meta/some_setup.cmx");
+ EXPECT_EQ(setup.arguments().size(), 1ul);
+ EXPECT_EQ(setup.arguments()[0], "-arg");
+
+ // check network object:
+ EXPECT_EQ(config.networks().size(), 1ul);
+ auto& net = config.networks()[0];
+ EXPECT_EQ(net.name(), "test-net");
+ EXPECT_EQ(net.endpoints().size(), 2ul);
+
+ // check endpoints:
+ auto& ep0 = net.endpoints()[0];
+ auto& ep1 = net.endpoints()[1];
+ EXPECT_EQ(ep0.name(), "ep0");
+ EXPECT_EQ(ep0.mtu(), 1000u);
+ EXPECT_TRUE(ep0.mac());
+ const uint8_t mac_cmp[] = {0x70, 0x00, 0x01, 0x02, 0x03, 0x04};
+ EXPECT_EQ(memcmp(mac_cmp, ep0.mac()->d, sizeof(mac_cmp)), 0);
+ EXPECT_EQ(ep0.up(), false);
+
+ EXPECT_EQ(ep1.name(), "ep1");
+ EXPECT_EQ(ep1.mtu(), 1500u); // default mtu check
+ EXPECT_FALSE(ep1.mac()); // mac not set
+ EXPECT_EQ(ep1.up(), true); // default up
+}
+
+TEST_F(ModelTest, NetworkNoName) {
+ const char* json = R"({"networks":[{}]})";
+ ExpectFailedParse(json, "network without name accepted");
+
+ const char* json2 = R"({"networks":[{"name":""}]})";
+ ExpectFailedParse(json2, "network with empty name accepted");
+};
+
+TEST_F(ModelTest, EndpointNoName) {
+ const char* json = R"({"networks":[{"name":"net","endpoints":[{}]}]})";
+ ExpectFailedParse(json, "endpoint without name accepted");
+
+ const char* json2 =
+ R"({"networks":[{"name":"net","endpoints":[{"name":""}]}]})";
+ ExpectFailedParse(json2, "endpoint with empty name accepted");
+};
+
+TEST_F(ModelTest, EndpointBadMtu) {
+ const char* json =
+ R"({"networks":[{"name":"net","endpoints":[{"name":"a","mtu":0}]}]})";
+ ExpectFailedParse(json, "endpoint without 0 mtu accepted");
+}
+
+TEST_F(ModelTest, EndpointBadMac) {
+ const char* json =
+ R"({"networks":[{"name":"net","endpoints":[{"name":"a","mac":"xx:xx:xx"}]}]})";
+ ExpectFailedParse(json, "endpoint with invalid mac accepted");
+}
+
+TEST_F(ModelTest, TestBadUrl) {
+ const char* json = R"({"environment":{"test":[{"url":"blablabla"}]}})";
+ ExpectFailedParse(json, "test with bad url accepted");
+}
+
+TEST_F(ModelTest, ServiceBadUrl) {
+ const char* json =
+ R"({"environment":{"services":{"some.service":"blablabla"}}})";
+ ExpectFailedParse(json, "service with bad url accepted");
+}
+
+TEST_F(ModelTest, LaunchAppGetOrDefault) {
+ const char* json1 =
+ R"({"url":"fuchsia-pkg://fuchsia.com/some_url#meta/some_url.cmx"})";
+ json::JSONParser parser;
+ auto doc1 = parser.ParseFromString(json1, "LaunchApGetOrDefault");
+ config::LaunchApp app1;
+
+ EXPECT_FALSE(parser.HasError()) << "Parse error: " << parser.error_str();
+ EXPECT_TRUE(app1.ParseFromJSON(doc1, &parser))
+ << "Parse error: " << parser.error_str();
+
+ const char* json2 = R"({"url":""})";
+ auto doc2 = parser.ParseFromString(json2, "LaunchApGetOrDefault");
+
+ config::LaunchApp app2;
+ EXPECT_FALSE(parser.HasError()) << "Parse error: " << parser.error_str();
+ EXPECT_TRUE(app2.ParseFromJSON(doc2, &parser))
+ << "Parse error: " << parser.error_str();
+
+ const char* fallback = "fuchsia-pkg://fuchsia.com/fallback#meta/fallback.cmx";
+ EXPECT_EQ(app1.GetUrlOrDefault(fallback),
+ "fuchsia-pkg://fuchsia.com/some_url#meta/some_url.cmx");
+ EXPECT_EQ(app2.GetUrlOrDefault(fallback), fallback);
+}
+
+} // namespace testing
+} // namespace config
+} // namespace netemul
diff --git a/bin/netemul_runner/model/network.cc b/bin/netemul_runner/model/network.cc
new file mode 100644
index 0000000..7248c88
--- /dev/null
+++ b/bin/netemul_runner/model/network.cc
@@ -0,0 +1,55 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "network.h"
+
+namespace netemul {
+namespace config {
+
+static const char* kName = "name";
+static const char* kEndpoints = "endpoints";
+
+bool Network::ParseFromJSON(const rapidjson::Value& value,
+ json::JSONParser* json_parser) {
+ if (!value.IsObject()) {
+ json_parser->ReportError("network entry must be an object");
+ return false;
+ }
+
+ auto name = value.FindMember(kName);
+ if (name == value.MemberEnd()) {
+ json_parser->ReportError("network must have name property set");
+ return false;
+ } else if ((!name->value.IsString()) || name->value.GetStringLength() == 0) {
+ json_parser->ReportError("network name must be a non-empty string");
+ return false;
+ } else {
+ name_ = name->value.GetString();
+ }
+
+ auto endpoints = value.FindMember(kEndpoints);
+ if (endpoints == value.MemberEnd()) {
+ endpoints_.clear();
+ } else if (!endpoints->value.IsArray()) {
+ json_parser->ReportError("network endpoints must be an array");
+ return false;
+ } else {
+ auto eps = endpoints->value.GetArray();
+ for (auto e = eps.Begin(); e != eps.End(); e++) {
+ auto& ne = endpoints_.emplace_back();
+ if (!ne.ParseFromJSON(*e, json_parser)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+const std::string& Network::name() const { return name_; }
+
+const std::vector<Endpoint>& Network::endpoints() const { return endpoints_; }
+
+} // namespace config
+} // namespace netemul
\ No newline at end of file
diff --git a/bin/netemul_runner/model/network.h b/bin/netemul_runner/model/network.h
new file mode 100644
index 0000000..69fb949
--- /dev/null
+++ b/bin/netemul_runner/model/network.h
@@ -0,0 +1,35 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef GARNET_BIN_NETEMUL_RUNNER_MODEL_NETWORK_H_
+#define GARNET_BIN_NETEMUL_RUNNER_MODEL_NETWORK_H_
+
+#include "endpoint.h"
+#include "lib/fxl/macros.h"
+#include "lib/json/json_parser.h"
+
+namespace netemul {
+namespace config {
+
+class Network {
+ public:
+ Network() = default;
+ Network(Network&& other) = default;
+
+ bool ParseFromJSON(const rapidjson::Value& value,
+ json::JSONParser* json_parser);
+
+ const std::string& name() const;
+ const std::vector<Endpoint>& endpoints() const;
+
+ private:
+ std::string name_;
+ std::vector<Endpoint> endpoints_;
+
+ FXL_DISALLOW_COPY_AND_ASSIGN(Network);
+};
+
+} // namespace config
+} // namespace netemul
+#endif // GARNET_BIN_NETEMUL_RUNNER_MODEL_NETWORK_H_
diff --git a/bin/netemul_runner/test/BUILD.gn b/bin/netemul_runner/test/BUILD.gn
index 76c1ab6..e9ad883 100644
--- a/bin/netemul_runner/test/BUILD.gn
+++ b/bin/netemul_runner/test/BUILD.gn
@@ -6,6 +6,7 @@
test_package("netemul_sandbox_test") {
deps = [
+ "//garnet/bin/netemul_runner/model:model_unittest",
"//garnet/bin/netemul_runner/test/netstack_socks",
"//garnet/bin/netemul_runner/test/svc_list",
]
@@ -28,5 +29,8 @@
{
name = "netstack_socks"
},
+ {
+ name = "model_unittest"
+ },
]
}
diff --git a/bin/netemul_runner/test/meta/model_unittest.cmx b/bin/netemul_runner/test/meta/model_unittest.cmx
new file mode 100644
index 0000000..44fcfdf
--- /dev/null
+++ b/bin/netemul_runner/test/meta/model_unittest.cmx
@@ -0,0 +1,5 @@
+{
+ "program": {
+ "binary": "test/model_unittest"
+ }
+}